601 lines
19 KiB
Go
601 lines
19 KiB
Go
package report
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
|
"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 TestHourlyLookupAndPeriodCalculation(t *testing.T) {
|
|
location := mustLoadLocation(t)
|
|
now := mustParse("2026-05-29T05:15:00-05:00")
|
|
|
|
resolved, err := Resolve(Hourly, ResolveRequest{Now: now, Location: location})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
if resolved.Definition.ID != Hourly {
|
|
t.Fatalf("ID = %q, want hourly", resolved.Definition.ID)
|
|
}
|
|
if resolved.Definition.PromptID != "weather.hourly_report" {
|
|
t.Fatalf("PromptID = %q, want weather.hourly_report", resolved.Definition.PromptID)
|
|
}
|
|
if resolved.Definition.ComparisonStrategy != CompareRollingWindow {
|
|
t.Fatalf("ComparisonStrategy = %q, want rolling_window", resolved.Definition.ComparisonStrategy)
|
|
}
|
|
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T05:15:00-05:00", "2026-05-29T11:15:00-05:00")
|
|
}
|
|
|
|
func TestHourlyPeriodUsesEffectiveTimezone(t *testing.T) {
|
|
location, err := time.LoadLocation("America/New_York")
|
|
if err != nil {
|
|
t.Fatalf("load location: %v", err)
|
|
}
|
|
now := mustParse("2026-05-29T10:15:00Z")
|
|
|
|
resolved, err := Resolve(Hourly, ResolveRequest{Now: now, Location: location})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T06:15:00-04:00", "2026-05-29T12:15:00-04:00")
|
|
}
|
|
|
|
func TestHourlyPeriodIsNotCivilDayTruncated(t *testing.T) {
|
|
location := mustLoadLocation(t)
|
|
now := mustParse("2026-05-29T22:30:00-05:00")
|
|
|
|
resolved, err := Resolve(Hourly, ResolveRequest{Now: now, Location: location})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
assertPeriod(t, resolved.ValidPeriod, "2026-05-29T22:30:00-05:00", "2026-05-30T04:30: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: "tuesday", now: "2026-05-26T05:00:00-05:00", start: "2026-05-30T00:00:00-05:00", end: "2026-06-01T00:00:00-05:00"},
|
|
{name: "wednesday", now: "2026-05-27T05:00:00-05:00", start: "2026-05-30T00:00:00-05:00", end: "2026-06-01T00:00:00-05:00"},
|
|
{name: "thursday", now: "2026-05-28T05: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 TestBatchesDoNotIncludeHourly(t *testing.T) {
|
|
location := mustLoadLocation(t)
|
|
req := ResolveRequest{Now: mustParse("2026-05-29T06:00:00-05:00"), Location: location}
|
|
|
|
morning, err := DefaultRegistry().BatchReports(Morning, req)
|
|
if err != nil {
|
|
t.Fatalf("BatchReports(morning) error = %v", err)
|
|
}
|
|
evening, err := DefaultRegistry().BatchReports(Evening, req)
|
|
if err != nil {
|
|
t.Fatalf("BatchReports(evening) error = %v", err)
|
|
}
|
|
for _, resolved := range append(morning, evening...) {
|
|
if resolved.Definition.ID == Hourly {
|
|
t.Fatalf("batch included %q, want hourly excluded", resolved.Definition.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 TestRegistryAllIncludesHourlyInStableOrder(t *testing.T) {
|
|
ids := resolvedDefinitionIDs(DefaultRegistry().All())
|
|
want := []string{"daily_today", "daily_tomorrow", "hourly", "three_day", "weekend", "storm"}
|
|
if strings.Join(ids, ",") != strings.Join(want, ",") {
|
|
t.Fatalf("All() ids = %#v, want %#v", ids, want)
|
|
}
|
|
}
|
|
|
|
func TestRegistryDefinitionsHavePromptIDsAndComparisonStrategies(t *testing.T) {
|
|
for _, definition := range DefaultRegistry().All() {
|
|
if definition.PromptID == "" {
|
|
t.Fatalf("%s PromptID is empty", definition.ID)
|
|
}
|
|
if definition.Generated && definition.GenerationMode == "" {
|
|
t.Fatalf("%s GenerationMode is empty", definition.ID)
|
|
}
|
|
if definition.ComparisonStrategy == "" {
|
|
t.Fatalf("%s ComparisonStrategy is empty", definition.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegistryDefinitionsDeclareGenerationMetadata(t *testing.T) {
|
|
for _, definition := range DefaultRegistry().All() {
|
|
if !definition.Generated {
|
|
continue
|
|
}
|
|
if definition.GenerationMode != GenerationModeScriptoriumMarkdown {
|
|
t.Fatalf("%s GenerationMode = %q, want %q", definition.ID, definition.GenerationMode, GenerationModeScriptoriumMarkdown)
|
|
}
|
|
if definition.TemplateID != "" {
|
|
t.Fatalf("%s TemplateID = %q, want empty for Markdown report", definition.ID, definition.TemplateID)
|
|
}
|
|
if definition.GeneratedTextSchemaID != "" {
|
|
t.Fatalf("%s GeneratedTextSchemaID = %q, want empty for Markdown report", definition.ID, definition.GeneratedTextSchemaID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegistryDefinitionsDeclarePathAndCompatibilityPolicy(t *testing.T) {
|
|
tests := []struct {
|
|
id ID
|
|
artifactGroup string
|
|
batchOutputName string
|
|
generated bool
|
|
compatiblePriorIDs []ID
|
|
comparisonStrategy ComparisonStrategy
|
|
}{
|
|
{
|
|
id: DailyToday,
|
|
artifactGroup: "daily",
|
|
batchOutputName: "daily.md",
|
|
generated: true,
|
|
compatiblePriorIDs: []ID{DailyToday, DailyTomorrow},
|
|
comparisonStrategy: CompareSameValidDate,
|
|
},
|
|
{
|
|
id: DailyTomorrow,
|
|
artifactGroup: "daily",
|
|
batchOutputName: "tomorrow.md",
|
|
generated: true,
|
|
compatiblePriorIDs: []ID{DailyToday, DailyTomorrow},
|
|
comparisonStrategy: CompareSameValidDate,
|
|
},
|
|
{
|
|
id: Hourly,
|
|
artifactGroup: "hourly",
|
|
batchOutputName: "hourly.md",
|
|
generated: true,
|
|
compatiblePriorIDs: []ID{Hourly},
|
|
comparisonStrategy: CompareRollingWindow,
|
|
},
|
|
{
|
|
id: ThreeDay,
|
|
artifactGroup: "three-day",
|
|
batchOutputName: "three-day.md",
|
|
generated: true,
|
|
compatiblePriorIDs: []ID{ThreeDay},
|
|
comparisonStrategy: CompareSameValidDate,
|
|
},
|
|
{
|
|
id: Weekend,
|
|
artifactGroup: "weekend",
|
|
batchOutputName: "weekend.md",
|
|
generated: true,
|
|
compatiblePriorIDs: []ID{Weekend},
|
|
comparisonStrategy: CompareWeekendWindow,
|
|
},
|
|
{
|
|
id: Storm,
|
|
artifactGroup: "storm",
|
|
batchOutputName: "storm.md",
|
|
generated: true,
|
|
compatiblePriorIDs: []ID{Storm},
|
|
comparisonStrategy: CompareExplicitWindow,
|
|
},
|
|
}
|
|
|
|
registry := DefaultRegistry()
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.id), func(t *testing.T) {
|
|
definition, err := registry.Lookup(tt.id)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error = %v", err)
|
|
}
|
|
if definition.ArtifactGroup != tt.artifactGroup {
|
|
t.Fatalf("ArtifactGroup = %q, want %q", definition.ArtifactGroup, tt.artifactGroup)
|
|
}
|
|
if definition.BatchOutputName != tt.batchOutputName {
|
|
t.Fatalf("BatchOutputName = %q, want %q", definition.BatchOutputName, tt.batchOutputName)
|
|
}
|
|
if definition.Generated != tt.generated {
|
|
t.Fatalf("Generated = %t, want %t", definition.Generated, tt.generated)
|
|
}
|
|
if definition.ComparisonStrategy != tt.comparisonStrategy {
|
|
t.Fatalf("ComparisonStrategy = %q, want %q", definition.ComparisonStrategy, tt.comparisonStrategy)
|
|
}
|
|
if !reflect.DeepEqual(definition.CompatiblePriorIDs, tt.compatiblePriorIDs) {
|
|
t.Fatalf("CompatiblePriorIDs = %#v, want %#v", definition.CompatiblePriorIDs, tt.compatiblePriorIDs)
|
|
}
|
|
for _, id := range tt.compatiblePriorIDs {
|
|
if !definition.CompatibleWithPrior(id) {
|
|
t.Fatalf("CompatibleWithPrior(%q) = false, want true", id)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryDefinitionsDeclareDefaultModules(t *testing.T) {
|
|
tests := []struct {
|
|
id ID
|
|
want []module.ID
|
|
}{
|
|
{
|
|
id: DailyToday,
|
|
want: []module.ID{
|
|
module.Metadata,
|
|
module.CurrentConditions,
|
|
module.NarrativeForecast,
|
|
module.DerivedDailySummary,
|
|
module.DerivedDaypartSummaries,
|
|
module.PrecipTiming,
|
|
module.AlertDigest,
|
|
module.SPCConvectiveOutlooks,
|
|
module.AreaForecastDiscussion,
|
|
module.SPCConvectiveDiscussion,
|
|
module.WeatherStory,
|
|
module.OutdoorWindows,
|
|
module.HourlyForecast,
|
|
},
|
|
},
|
|
{
|
|
id: DailyTomorrow,
|
|
want: []module.ID{
|
|
module.Metadata,
|
|
module.CurrentConditions,
|
|
module.NarrativeForecast,
|
|
module.DerivedDailySummary,
|
|
module.DerivedDaypartSummaries,
|
|
module.PrecipTiming,
|
|
module.AlertDigest,
|
|
module.SPCConvectiveOutlooks,
|
|
module.AreaForecastDiscussion,
|
|
module.SPCConvectiveDiscussion,
|
|
module.WeatherStory,
|
|
module.OutdoorWindows,
|
|
module.TomorrowPlanning,
|
|
module.HourlyForecast,
|
|
},
|
|
},
|
|
{
|
|
id: Hourly,
|
|
want: []module.ID{
|
|
module.Metadata,
|
|
module.CurrentConditions,
|
|
module.HourlyForecast,
|
|
module.PrecipTiming,
|
|
module.AlertDigest,
|
|
module.SPCConvectiveOutlooks,
|
|
module.AreaForecastDiscussion,
|
|
module.SPCConvectiveDiscussion,
|
|
module.WeatherStory,
|
|
},
|
|
},
|
|
{
|
|
id: ThreeDay,
|
|
want: []module.ID{
|
|
module.Metadata,
|
|
module.CurrentConditions,
|
|
module.DerivedDaypartSummaries,
|
|
module.PrecipTiming,
|
|
module.AlertDigest,
|
|
module.SPCConvectiveOutlooks,
|
|
module.AreaForecastDiscussion,
|
|
module.SPCConvectiveDiscussion,
|
|
module.WeatherStory,
|
|
module.OutdoorWindows,
|
|
},
|
|
},
|
|
{
|
|
id: Weekend,
|
|
want: []module.ID{
|
|
module.Metadata,
|
|
module.CurrentConditions,
|
|
module.DerivedDaypartSummaries,
|
|
module.PrecipTiming,
|
|
module.AlertDigest,
|
|
module.SPCConvectiveOutlooks,
|
|
module.AreaForecastDiscussion,
|
|
module.SPCConvectiveDiscussion,
|
|
module.WeatherStory,
|
|
module.OutdoorWindows,
|
|
},
|
|
},
|
|
{
|
|
id: Storm,
|
|
want: []module.ID{
|
|
module.Metadata,
|
|
module.CurrentConditions,
|
|
module.PrecipTiming,
|
|
module.AlertDigest,
|
|
module.SPCConvectiveOutlooks,
|
|
module.AreaForecastDiscussion,
|
|
module.SPCConvectiveDiscussion,
|
|
module.WeatherStory,
|
|
},
|
|
},
|
|
}
|
|
|
|
registry := DefaultRegistry()
|
|
for _, tt := range tests {
|
|
t.Run(string(tt.id), func(t *testing.T) {
|
|
definition, err := registry.Lookup(tt.id)
|
|
if err != nil {
|
|
t.Fatalf("Lookup() error = %v", err)
|
|
}
|
|
if !reflect.DeepEqual(definition.ModuleIDs(), tt.want) {
|
|
t.Fatalf("ModuleIDs() = %#v, want %#v", definition.ModuleIDs(), tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegistryAppliesModuleOverridesWithoutChangingDefaults(t *testing.T) {
|
|
base := DefaultRegistry()
|
|
overridden, err := base.WithModuleOverrides(map[ID][]module.ConfigItem{
|
|
DailyToday: {
|
|
{ID: module.Metadata},
|
|
{ID: module.AlertDigest},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("WithModuleOverrides() error = %v", err)
|
|
}
|
|
|
|
definition, err := overridden.Lookup(DailyToday)
|
|
if err != nil {
|
|
t.Fatalf("Lookup(overridden) error = %v", err)
|
|
}
|
|
if !reflect.DeepEqual(definition.ModuleIDs(), []module.ID{module.Metadata, module.AlertDigest}) {
|
|
t.Fatalf("overridden ModuleIDs() = %#v, want metadata and alert digest", definition.ModuleIDs())
|
|
}
|
|
|
|
defaultDefinition, err := base.Lookup(DailyToday)
|
|
if err != nil {
|
|
t.Fatalf("Lookup(default) error = %v", err)
|
|
}
|
|
if len(defaultDefinition.ModuleIDs()) <= len(definition.ModuleIDs()) {
|
|
t.Fatalf("default ModuleIDs() = %#v, want original defaults unchanged", defaultDefinition.ModuleIDs())
|
|
}
|
|
}
|
|
|
|
func TestRegistryRejectsModuleOverrideForUnknownReport(t *testing.T) {
|
|
_, err := DefaultRegistry().WithModuleOverrides(map[ID][]module.ConfigItem{
|
|
ID("unknown"): {{ID: module.Metadata}},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("WithModuleOverrides() error = nil, want unknown report")
|
|
}
|
|
if !strings.Contains(err.Error(), `unknown report "unknown"`) {
|
|
t.Fatalf("error = %q, want unknown report context", err.Error())
|
|
}
|
|
}
|
|
|
|
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 TestHourlyMetadataRunIDIncludesReportID(t *testing.T) {
|
|
location := mustLoadLocation(t)
|
|
resolved, err := Resolve(Hourly, ResolveRequest{Now: mustParse("2026-05-29T05:15:00-05:00"), Location: location})
|
|
if err != nil {
|
|
t.Fatalf("Resolve() error = %v", err)
|
|
}
|
|
metadata := resolved.Metadata()
|
|
if metadata.ReportID != Hourly {
|
|
t.Fatalf("ReportID = %q, want hourly", metadata.ReportID)
|
|
}
|
|
if metadata.PromptID != "weather.hourly_report" {
|
|
t.Fatalf("PromptID = %q, want weather.hourly_report", metadata.PromptID)
|
|
}
|
|
if !strings.Contains(metadata.RunID, "hourly") {
|
|
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 resolvedDefinitionIDs(definitions []Definition) []string {
|
|
ids := make([]string, 0, len(definitions))
|
|
for _, definition := range definitions {
|
|
ids = append(ids, string(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
|
|
}
|