package report import ( "testing" "time" ) func on(s string) time.Time { t, err := time.Parse("2006-01-02", s) if err != nil { panic(err) } return t } // The axis opens on the month that has just ended, whatever the index holds. func TestPeriodsDefaultsToLastMonth(t *testing.T) { ps := Periods(on("2026-08-23"), []string{"2026-08", "2026-07", "2026-06"}) p := ps[DefaultIndex(ps)] if p.Label != LastMonth { t.Fatalf("default = %q, want %q", p.Label, LastMonth) } if p.From != "2026-07-01" || p.To != "2026-07-31" { t.Errorf("last month = %s…%s, want the whole of July", p.From, p.To) } if p.Span != "2026-07" { t.Errorf("span = %q, want the month it stands for", p.Span) } } // An index that stops short of last month still opens there: an empty report // for the window asked for beats a full one for a window nobody chose. func TestPeriodsDefaultsToLastMonthWithNothingInIt(t *testing.T) { ps := Periods(on("2026-08-23"), []string{"2026-03", "2026-02"}) if p := ps[DefaultIndex(ps)]; p.Label != LastMonth { t.Fatalf("default = %q, want %q", p.Label, LastMonth) } } // The named windows come first, widest to narrowest, and the months follow in // reverse order, skipping the ones the named windows already name. func TestPeriodsOrder(t *testing.T) { ps := Periods(on("2026-08-23"), []string{"2026-09", "2026-08", "2026-07", "2026-06", "2026-01"}) var got []string for _, p := range ps { got = append(got, p.Label) } want := []string{ "all time", "this year", "last 12 months", "last 3 months", "this month", LastMonth, "2026-06", "2026-01", } if len(got) != len(want) { t.Fatalf("periods = %v, want %v", got, want) } for i := range want { if got[i] != want[i] { t.Fatalf("periods = %v, want %v", got, want) } } } // A month is bounded by its own length, not by a fixed 30 or 31 days. func TestPeriodsMonthEnds(t *testing.T) { ps := Periods(on("2026-03-15"), []string{"2026-02"}) p := ps[DefaultIndex(ps)] if p.From != "2026-02-01" || p.To != "2026-02-28" { t.Errorf("February 2026 = %s…%s, want the 1st to the 28th", p.From, p.To) } } // A rolling window runs to the end of this month rather than to the last // complete one: it is asked in order to see what is happening now. func TestPeriodsRollingWindows(t *testing.T) { ps := Periods(on("2026-08-23"), nil) for _, tc := range []struct{ label, from, to string }{ {"last 3 months", "2026-06-01", "2026-08-31"}, {"last 12 months", "2025-09-01", "2026-08-31"}, {"this year", "2026-01-01", "2026-12-31"}, } { var found bool for _, p := range ps { if p.Label != tc.label { continue } found = true if p.From != tc.from || p.To != tc.to { t.Errorf("%s = %s…%s, want %s…%s", tc.label, p.From, p.To, tc.from, tc.to) } } if !found { t.Errorf("no %q window", tc.label) } } } // All time is unbounded on both sides, so nothing has to special-case it. func TestPeriodsAllTimeIsUnbounded(t *testing.T) { ps := Periods(on("2026-08-23"), []string{"2026-01"}) if ps[0].Label != "all time" || ps[0].From != "" || ps[0].To != "" { t.Errorf("first window = %+v, want an unbounded all time", ps[0]) } }