package report import ( "strings" "testing" "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" ) func TestResolveRetainedReportPeriods(t *testing.T) { location := loadTestLocation(t) now := parseTestTime("2026-05-29T17:45:00-05:00") date := parseTestTime("2026-05-31T12:00:00-05:00") tests := []struct { id ID request ResolveRequest wantStart string wantEnd string }{ {Daily, ResolveRequest{Now: now, Location: location, Date: date}, "2026-05-31T00:00:00-05:00", "2026-06-01T00:00:00-05:00"}, {Today, ResolveRequest{Now: now, Location: location, Date: date}, "2026-05-31T00:00:00-05:00", "2026-06-01T00:00:00-05:00"}, {Tomorrow, ResolveRequest{Now: now, Location: location}, "2026-05-30T00:00:00-05:00", "2026-05-31T00:00:00-05:00"}, {Hourly, ResolveRequest{Now: now, Location: location}, "2026-05-29T17:45:00-05:00", "2026-05-29T23:45:00-05:00"}, } for _, tt := range tests { t.Run(string(tt.id), func(t *testing.T) { resolved, err := Resolve(tt.id, tt.request) if err != nil { t.Fatalf("Resolve(%q) error = %v", tt.id, err) } assertTestPeriod(t, resolved.ValidPeriod, tt.wantStart, tt.wantEnd) }) } } func TestResolveDailyRequiresDate(t *testing.T) { _, err := Resolve(Daily, ResolveRequest{Now: parseTestTime("2026-05-29T17:45:00-05:00"), Location: loadTestLocation(t)}) if err == nil || !strings.Contains(err.Error(), "explicit date") { t.Fatalf("Resolve(Daily) error = %v, want explicit-date error", err) } } func TestRegistryContainsOnlyPromptBackedReports(t *testing.T) { registry := DefaultRegistry() definitions := registry.All() if len(definitions) != 4 { t.Fatalf("Registry.All() length = %d, want 4", len(definitions)) } for _, definition := range definitions { if definition.PromptVersion != "2.0.0" { t.Fatalf("%s PromptVersion = %q, want 2.0.0", definition.ID, definition.PromptVersion) } if definition.PromptID == "" { t.Fatalf("%s PromptID is empty", definition.ID) } if definition.TemplateID == "" || definition.GeneratedTextSchemaID == "" { t.Fatalf("%s template/schema = %q/%q, want both set", definition.ID, definition.TemplateID, definition.GeneratedTextSchemaID) } } if _, err := registry.Lookup(ID("three_day")); err == nil { t.Fatal("Lookup(three_day) error = nil, want unknown report") } } func TestCommandAndConfigurationNamesRejectRetiredReports(t *testing.T) { if names := strings.Join(CommandNames(), ","); names != "daily,today,tomorrow,hourly" { t.Fatalf("CommandNames() = %q", names) } for _, name := range []string{"three-day", "weekend", "storm"} { if _, err := IDForCommandName(name); err == nil { t.Fatalf("IDForCommandName(%q) error = nil, want unknown command", name) } } for _, key := range []string{"three_day", "three_day_outlook", "weekend", "weekend_outlook", "storm", "storm_report"} { if _, err := IDForConfigKey(key); err == nil { t.Fatalf("IDForConfigKey(%q) error = nil, want unknown report", key) } } } func TestRegistryDefinitionsPreserveRetainedContracts(t *testing.T) { tests := []struct { id ID morning bool evening bool outputName string paths []string }{ {Daily, false, false, "daily.md", []string{"daily/{valid_start_date}/{run_id}.md", "daily/{valid_start_date}/index.md"}}, {Today, true, false, "today.md", []string{"daily/{valid_start_date}/{run_id}.md", "daily/{valid_start_date}/index.md", "today/index.md"}}, {Tomorrow, false, true, "tomorrow.md", []string{"daily/{valid_start_date}/{run_id}.md", "daily/{valid_start_date}/index.md", "tomorrow/index.md"}}, {Hourly, false, false, "hourly.md", []string{"hourly/index.md"}}, } registry := DefaultRegistry() for _, tt := range tests { t.Run(string(tt.id), func(t *testing.T) { definition := registry.MustLookup(tt.id) if definition.Morning != tt.morning || definition.Evening != tt.evening || definition.OutputName != tt.outputName { t.Fatalf("definition = %#v, want retained report contract", definition) } if strings.Join(definition.DistributorPathTemplates, "|") != strings.Join(tt.paths, "|") { t.Fatalf("DistributorPathTemplates = %#v, want %#v", definition.DistributorPathTemplates, tt.paths) } }) } } func assertTestPeriod(t *testing.T, period timeutil.Period, wantStart string, wantEnd string) { t.Helper() if got := period.Start.Format(time.RFC3339); got != wantStart { t.Fatalf("period start = %q, want %q", got, wantStart) } if got := period.End.Format(time.RFC3339); got != wantEnd { t.Fatalf("period end = %q, want %q", got, wantEnd) } } func loadTestLocation(t *testing.T) *time.Location { t.Helper() location, err := timeutil.LoadLocation("America/Chicago") if err != nil { t.Fatalf("LoadLocation() error = %v", err) } return location } func parseTestTime(value string) time.Time { parsed, err := time.Parse(time.RFC3339, value) if err != nil { panic(err) } return parsed }