package cmd import ( "bytes" "strings" "testing" "time" "github.com/evcc-io/evcc/core/metrics" "github.com/stretchr/testify/require" ) func TestMetricsTimeframe(t *testing.T) { // explicit range: to is inclusive, so it extends to the end of the named day from, to, err := metricsTimeframe("", "2026-05-01", "2026-05-03") require.NoError(t, err) require.Equal(t, time.Date(2026, 5, 1, 0, 0, 0, 0, time.Local), from) require.Equal(t, time.Date(2026, 5, 4, 0, 0, 0, 0, time.Local), to) // to before from _, _, err = metricsTimeframe("", "2026-05-05", "2026-05-01") require.Error(t, err) // invalid date _, _, err = metricsTimeframe("", "notadate", "") require.Error(t, err) } func TestMetricsTimeframeRange(t *testing.T) { now := time.Now() // day from, to, err := metricsTimeframe("day", "", "") require.NoError(t, err) require.Equal(t, time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local), from) require.Equal(t, from.AddDate(0, 0, 1), to) // month, case-insensitive from, to, err = metricsTimeframe("Month", "", "") require.NoError(t, err) require.Equal(t, time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local), from) require.Equal(t, from.AddDate(0, 1, 0), to) // year from, to, err = metricsTimeframe("year", "", "") require.NoError(t, err) require.Equal(t, time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.Local), from) require.Equal(t, from.AddDate(1, 0, 0), to) // invalid range value _, _, err = metricsTimeframe("decade", "", "") require.Error(t, err) } func TestMetricsSelectEntities(t *testing.T) { entities := []metrics.EntityInfo{ {Group: metrics.Grid, Name: "grid"}, {Group: metrics.PV, Name: "pv1"}, {Group: metrics.Loadpoint, Name: "lp-1", Title: "Carport"}, } // explicit selectors match by name or stored title and preserve argument order res, err := metricsSelectEntities(entities, []string{"Carport", "grid"}, "") require.NoError(t, err) require.Equal(t, []string{"lp-1", "grid"}, []string{res[0].Name, res[1].Name}) // unknown selector errors _, err = metricsSelectEntities(entities, []string{"bogus"}, "") require.Error(t, err) // no selectors: all entities in canonical group order (pv before grid) res, err = metricsSelectEntities(entities, nil, "") require.NoError(t, err) require.Equal(t, metrics.PV, res[0].Group) // empty group errors _, err = metricsSelectEntities(entities, nil, metrics.Battery) require.Error(t, err) } func TestMetricsWriteTable(t *testing.T) { h0 := time.Date(2026, 5, 22, 0, 0, 0, 0, time.Local) h1 := h0.Add(time.Hour) selected := []metrics.EntityInfo{ {Group: metrics.Loadpoint, Name: "lp-1", Title: "Carport"}, {Group: metrics.Grid, Name: "grid"}, } byEntity := map[string]metrics.Series{ metrics.Loadpoint + "/Carport": {Group: metrics.Loadpoint, Title: "Carport", Data: []metrics.Slot{ {Start: h0, Energy: 1.84}, }}, metrics.Grid + "/grid": {Group: metrics.Grid, Title: "grid", Data: []metrics.Slot{ {Start: h0, Energy: 0.412}, {Start: h1, Energy: 0.38, ReturnEnergy: 0.05}, }}, } var buf bytes.Buffer metricsWriteTable(&buf, selected, byEntity, "hour") lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") require.Len(t, lines, 3) // header + 2 rows // header: title falls back to name; bidirectional grid gets a return column require.Contains(t, lines[0], "Carport") require.Contains(t, lines[0], "grid↑") // row 1: both lp-1 and grid have data require.Contains(t, lines[1], "2026-05-22 00:00") require.Contains(t, lines[1], "1.840") require.Contains(t, lines[1], "0.412") // row 2: lp-1 has no slot -> blank cell, not 0.000; grid return energy shown require.Contains(t, lines[2], "2026-05-22 01:00") require.Contains(t, lines[2], "0.380") require.Contains(t, lines[2], "0.050") require.NotContains(t, lines[2], "0.000") }