package app import ( "context" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "time" "gitea.maximumdirect.net/eric/weatherreporter/internal/config" "gitea.maximumdirect.net/eric/weatherreporter/internal/report" ) func TestFetchAndSaveBundle(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/observations": _, _ = w.Write([]byte(`{"data":{"timestamp":"2026-05-29T14:00:00Z","conditionCode":3}}`)) case "/conditions/current": _, _ = w.Write([]byte(`{"data":{"conditionText":"Clear"}}`)) case "/forecast/hourly": _, _ = w.Write([]byte(`{"data":{"issuedAt":"2026-05-29T10:30:00-05:00","product":"hourly","periods":[{"startTime":"2026-05-29T13:00:00-05:00","endTime":"2026-05-29T14:00:00-05:00"}]}}`)) case "/forecast/narrative": _, _ = w.Write([]byte(`{"data":{"issuedAt":"2026-05-29T10:30:00-05:00","product":"narrative","periods":[]}}`)) case "/alerts/active": _, _ = w.Write([]byte(`{"data":{"alerts":[]}}`)) case "/discussion": _, _ = w.Write([]byte(`{"data":{"product":"discussion","issuedAt":"2026-05-29T09:25:00-05:00","keyMessages":[]}}`)) default: http.NotFound(w, r) } })) defer server.Close() cfg := config.Defaults() cfg.WeatherAPI.BaseURL = server.URL + "/" path := filepath.Join(t.TempDir(), "bundle.json") bundle, err := FetchAndSaveBundle(context.Background(), FetchBundleRequest{Config: cfg, OutputPath: path}) if err != nil { t.Fatalf("FetchAndSaveBundle() error = %v", err) } if bundle.Hourly == nil { t.Fatal("Hourly = nil, want fetched bundle") } data, err := os.ReadFile(path) if err != nil { t.Fatalf("read saved bundle: %v", err) } if !strings.Contains(string(data), `"product": "hourly"`) { t.Fatalf("saved bundle missing hourly product:\n%s", string(data)) } } func TestFetchAndSaveBundleRequiresOutputPath(t *testing.T) { _, err := FetchAndSaveBundle(context.Background(), FetchBundleRequest{Config: config.Defaults()}) if err == nil { t.Fatal("FetchAndSaveBundle() error = nil, want output path error") } if !strings.Contains(err.Error(), "output path") { t.Fatalf("error = %q, want output path context", err.Error()) } } func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) { cfg := config.Defaults() cfg.WeatherAPI.Timezone = "America/Chicago" now := mustParse("2026-05-29T18:00:00-05:00") resolved, err := ResolveGenerate(GenerateRequest{ Config: cfg, Report: ReportTomorrow, }, now) if err != nil { t.Fatalf("ResolveGenerate() error = %v", err) } if resolved.Definition.ID != report.DailyTomorrow { t.Fatalf("ID = %q, want daily_tomorrow", resolved.Definition.ID) } if resolved.Definition.PromptID != "weather.daily_report" { t.Fatalf("PromptID = %q, want weather.daily_report", resolved.Definition.PromptID) } if got := resolved.ValidPeriod.Start.Format("2006-01-02"); got != "2026-05-30" { t.Fatalf("valid start date = %s, want 2026-05-30", got) } } func TestResolveGenerateStorm(t *testing.T) { cfg := config.Defaults() cfg.WeatherAPI.Timezone = "America/Chicago" now := mustParse("2026-05-29T12:00:00-05:00") start := mustParse("2026-05-29T18:00:00-05:00") end := mustParse("2026-05-30T06:00:00-05:00") resolved, err := ResolveGenerate(GenerateRequest{ Config: cfg, Report: ReportStorm, StormStart: start, StormEnd: end, }, now) if err != nil { t.Fatalf("ResolveGenerate() error = %v", err) } if resolved.Definition.ID != report.Storm { t.Fatalf("ID = %q, want storm", resolved.Definition.ID) } if !resolved.ValidPeriod.Start.Equal(start) || !resolved.ValidPeriod.End.Equal(end) { t.Fatalf("period = %#v, want storm window", resolved.ValidPeriod) } } func TestResolveBatchMorningSkipsWeekendOnSunday(t *testing.T) { cfg := config.Defaults() cfg.WeatherAPI.Timezone = "America/Chicago" now := mustParse("2026-05-31T06:00:00-05:00") resolved, err := ResolveBatch(BatchRequest{Config: cfg, Batch: BatchMorning}, now) if err != nil { t.Fatalf("ResolveBatch() error = %v", err) } if len(resolved) != 2 { t.Fatalf("resolved length = %d, want 2", len(resolved)) } for _, item := range resolved { if item.Definition.ID == report.Weekend { t.Fatal("morning batch included weekend on Sunday") } } } func mustParse(value string) time.Time { parsed, err := time.Parse(time.RFC3339, value) if err != nil { panic(err) } return parsed }