Add report registry and valid periods
This commit is contained in:
231
internal/report/period_test.go
Normal file
231
internal/report/period_test.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
)
|
||||
|
||||
func TestDailyValidPeriod(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
now := mustParse("2026-05-29T17:45:00-05:00")
|
||||
|
||||
resolved, err := Resolve(DailyToday, ResolveRequest{Now: now, Location: location})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T00:00:00-05:00", "2026-05-30T00:00:00-05:00")
|
||||
}
|
||||
|
||||
func TestDailyValidPeriodCanUseExplicitDate(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
now := mustParse("2026-05-29T17:45:00-05:00")
|
||||
date := mustParse("2026-05-31T12:00:00-05:00")
|
||||
|
||||
resolved, err := Resolve(DailyToday, ResolveRequest{Now: now, Location: location, Date: date})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, "2026-05-31T00:00:00-05:00", "2026-06-01T00:00:00-05:00")
|
||||
}
|
||||
|
||||
func TestTomorrowValidPeriodFromEveningGeneration(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
now := mustParse("2026-05-29T20:00:00-05:00")
|
||||
|
||||
resolved, err := Resolve(DailyTomorrow, ResolveRequest{Now: now, Location: location})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, "2026-05-30T00:00:00-05:00", "2026-05-31T00:00:00-05:00")
|
||||
if resolved.Definition.PromptID != "weather.daily_report" {
|
||||
t.Fatalf("PromptID = %q, want weather.daily_report", resolved.Definition.PromptID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThreeDayPeriodCalculation(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
now := mustParse("2026-05-29T05:00:00-05:00")
|
||||
|
||||
resolved, err := Resolve(ThreeDay, ResolveRequest{Now: now, Location: location})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T05:00:00-05:00", "2026-06-01T00:00:00-05:00")
|
||||
}
|
||||
|
||||
func TestWeekendPeriodCalculation(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
now string
|
||||
start string
|
||||
end string
|
||||
}{
|
||||
{name: "monday", now: "2026-05-25T05:00:00-05:00", start: "2026-05-30T00:00:00-05:00", end: "2026-06-01T00:00:00-05:00"},
|
||||
{name: "friday before evening", now: "2026-05-29T05:00:00-05:00", start: "2026-05-29T18:00:00-05:00", end: "2026-06-01T00:00:00-05:00"},
|
||||
{name: "friday after evening", now: "2026-05-29T19:30:00-05:00", start: "2026-05-29T19:30:00-05:00", end: "2026-06-01T00:00:00-05:00"},
|
||||
{name: "saturday", now: "2026-05-30T08:00:00-05:00", start: "2026-05-30T08:00:00-05:00", end: "2026-06-01T00:00:00-05:00"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resolved, err := Resolve(Weekend, ResolveRequest{Now: mustParse(tt.now), Location: location})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, tt.start, tt.end)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWeekendSundayErrors(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
_, err := Resolve(Weekend, ResolveRequest{Now: mustParse("2026-05-31T08:00:00-05:00"), Location: location})
|
||||
if err == nil {
|
||||
t.Fatal("Resolve() error = nil, want Sunday weekend error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "Sunday") {
|
||||
t.Fatalf("error = %q, want Sunday context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestStormManualPeriodParsingAndValidation(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
period, err := ParseStormPeriod("2026-05-29T18:00", "2026-05-30T06:00:00-05:00", location)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseStormPeriod() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, period, "2026-05-29T18:00:00-05:00", "2026-05-30T06:00:00-05:00")
|
||||
|
||||
_, err = ParseStormPeriod("2026-05-30T06:00", "2026-05-29T18:00", location)
|
||||
if err == nil {
|
||||
t.Fatal("ParseStormPeriod() error = nil, want invalid period error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStormResolve(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
resolved, err := Resolve(Storm, ResolveRequest{
|
||||
Now: mustParse("2026-05-29T12:00:00-05:00"),
|
||||
Location: location,
|
||||
StormStart: mustParse("2026-05-29T18:00:00-05:00"),
|
||||
StormEnd: mustParse("2026-05-30T06:00:00-05:00"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T18:00:00-05:00", "2026-05-30T06:00:00-05:00")
|
||||
if resolved.Definition.ComparisonStrategy != CompareExplicitWindow {
|
||||
t.Fatalf("ComparisonStrategy = %q, want explicit event window", resolved.Definition.ComparisonStrategy)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMorningBatchSkipsWeekendOnSunday(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
resolved, err := DefaultRegistry().BatchReports(Morning, ResolveRequest{
|
||||
Now: mustParse("2026-05-31T06:00:00-05:00"),
|
||||
Location: location,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BatchReports() error = %v", err)
|
||||
}
|
||||
ids := resolvedIDs(resolved)
|
||||
if strings.Join(ids, ",") != "daily_today,three_day" {
|
||||
t.Fatalf("ids = %v, want daily_today and three_day", ids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEveningBatchIncludesTomorrow(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
resolved, err := DefaultRegistry().BatchReports(Evening, ResolveRequest{
|
||||
Now: mustParse("2026-05-29T18:00:00-05:00"),
|
||||
Location: location,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BatchReports() error = %v", err)
|
||||
}
|
||||
ids := resolvedIDs(resolved)
|
||||
if strings.Join(ids, ",") != "daily_tomorrow" {
|
||||
t.Fatalf("ids = %v, want daily_tomorrow", ids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryLookupErrorIsActionable(t *testing.T) {
|
||||
_, err := DefaultRegistry().Lookup(ID("unknown"))
|
||||
if err == nil {
|
||||
t.Fatal("Lookup() error = nil, want unknown report error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `unknown report "unknown"`) {
|
||||
t.Fatalf("error = %q, want unknown report context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryDefinitionsHavePromptIDsAndComparisonStrategies(t *testing.T) {
|
||||
for _, definition := range DefaultRegistry().All() {
|
||||
if definition.PromptID == "" {
|
||||
t.Fatalf("%s PromptID is empty", definition.ID)
|
||||
}
|
||||
if definition.ComparisonStrategy == "" {
|
||||
t.Fatalf("%s ComparisonStrategy is empty", definition.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvedMetadata(t *testing.T) {
|
||||
location := mustLoadLocation(t)
|
||||
resolved, err := Resolve(DailyToday, ResolveRequest{Now: mustParse("2026-05-29T05:00:00-05:00"), Location: location})
|
||||
if err != nil {
|
||||
t.Fatalf("Resolve() error = %v", err)
|
||||
}
|
||||
metadata := resolved.Metadata()
|
||||
if metadata.ReportID != DailyToday {
|
||||
t.Fatalf("ReportID = %q, want daily_today", metadata.ReportID)
|
||||
}
|
||||
if metadata.PromptID != "weather.daily_report" {
|
||||
t.Fatalf("PromptID = %q, want weather.daily_report", metadata.PromptID)
|
||||
}
|
||||
if !strings.Contains(metadata.RunID, "daily_today") {
|
||||
t.Fatalf("RunID = %q, want report id", metadata.RunID)
|
||||
}
|
||||
}
|
||||
|
||||
func assertPeriod(t *testing.T, period timeutil.Period, wantStart string, wantEnd string) {
|
||||
t.Helper()
|
||||
if !period.IsValid() {
|
||||
t.Fatalf("period = %#v, want valid", period)
|
||||
}
|
||||
if period.Start.Format(time.RFC3339) != wantStart {
|
||||
t.Fatalf("Start = %s, want %s", period.Start.Format(time.RFC3339), wantStart)
|
||||
}
|
||||
if period.End.Format(time.RFC3339) != wantEnd {
|
||||
t.Fatalf("End = %s, want %s", period.End.Format(time.RFC3339), wantEnd)
|
||||
}
|
||||
}
|
||||
|
||||
func resolvedIDs(resolved []Resolved) []string {
|
||||
ids := make([]string, 0, len(resolved))
|
||||
for _, item := range resolved {
|
||||
ids = append(ids, string(item.Definition.ID))
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func mustLoadLocation(t *testing.T) *time.Location {
|
||||
t.Helper()
|
||||
location, err := time.LoadLocation("America/Chicago")
|
||||
if err != nil {
|
||||
t.Fatalf("load location: %v", err)
|
||||
}
|
||||
return location
|
||||
}
|
||||
|
||||
func mustParse(value string) time.Time {
|
||||
parsed, err := time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
Reference in New Issue
Block a user