diff --git a/internal/app/app.go b/internal/app/app.go index 5e868cb..35b3df7 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -67,8 +67,6 @@ type BriefingRequest struct { OutputPath string } -type DailyBriefingRequest = BriefingRequest - type ReportRequest struct { Config config.Config Resolved report.Resolved @@ -77,15 +75,11 @@ type ReportRequest struct { Store state.Store } -type DailyReportRequest = ReportRequest - type BriefingResult struct { Package briefing.Package OutputPath string } -type DailyBriefingResult = BriefingResult - type ReportResult struct { Briefing briefing.Package BriefingPath string @@ -102,8 +96,6 @@ type ReportResult struct { RunResult *scriptorium.RunResult } -type DailyReportResult = ReportResult - type BatchResult struct { Batch BatchKind `json:"batch"` StartedAt time.Time `json:"startedAt"` @@ -349,10 +341,6 @@ func FetchAndSaveBundle(ctx context.Context, req FetchBundleRequest) (*forecast. return bundle, nil } -func GenerateDailyBriefing(ctx context.Context, req DailyBriefingRequest) (*DailyBriefingResult, error) { - return GenerateBriefing(ctx, req) -} - func GenerateBriefing(ctx context.Context, req BriefingRequest) (*BriefingResult, error) { bundle, err := FetchBundle(ctx, FetchBundleRequest{Config: req.Config}) if err != nil { @@ -380,10 +368,6 @@ func GenerateBriefing(ctx context.Context, req BriefingRequest) (*BriefingResult return &BriefingResult{Package: pkg, OutputPath: outputPath}, nil } -func GenerateDailyReport(ctx context.Context, req DailyReportRequest) (*DailyReportResult, error) { - return GenerateReport(ctx, req) -} - func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, error) { store := req.Store if store == nil { @@ -514,10 +498,6 @@ func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, erro }, nil } -func BuildDailyBriefing(req DailyBriefingRequest, bundle *forecast.Bundle) (briefing.Package, error) { - return BuildBriefing(req, bundle) -} - func BuildBriefing(req BriefingRequest, bundle *forecast.Bundle) (briefing.Package, error) { location, err := timeutil.LoadLocation(req.Config.WeatherAPI.Timezone) if err != nil { @@ -578,10 +558,6 @@ func defaultStore(cfg config.Config) (*state.FilesystemStore, error) { return state.NewFilesystemStore(cfg.Workspace) } -func dailyRecentChanges(ctx context.Context, store state.Store, priorSnapshot *state.PriorSnapshot, current briefing.Package, cfg config.RecentChangeConfig) ([]changes.Change, error) { - return recentChanges(ctx, store, priorSnapshot, current, cfg) -} - func recentChanges(ctx context.Context, store state.Store, priorSnapshot *state.PriorSnapshot, current briefing.Package, cfg config.RecentChangeConfig) ([]changes.Change, error) { if priorSnapshot == nil { return nil, nil diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 13e167b..e4e25ed 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -70,11 +70,9 @@ func TestFetchAndSaveBundleRequiresOutputPath(t *testing.T) { } } -func TestGenerateDailyBriefingWritesArtifact(t *testing.T) { +func TestGenerateBriefingWritesArtifact(t *testing.T) { server := dailyBundleServer(t) - cfg := config.Defaults() - cfg.WeatherAPI.BaseURL = server.URL + "/" - cfg.WeatherAPI.Timezone = "America/Chicago" + cfg := dailyTestConfig(t, server) resolved, err := ResolveGenerate(GenerateRequest{ Config: cfg, Report: ReportDaily, @@ -85,13 +83,13 @@ func TestGenerateDailyBriefingWritesArtifact(t *testing.T) { } path := filepath.Join(t.TempDir(), "daily.briefing.json") - result, err := GenerateDailyBriefing(context.Background(), DailyBriefingRequest{ + result, err := GenerateBriefing(context.Background(), BriefingRequest{ Config: cfg, Resolved: resolved, OutputPath: path, }) if err != nil { - t.Fatalf("GenerateDailyBriefing() error = %v", err) + t.Fatalf("GenerateBriefing() error = %v", err) } if result.OutputPath != path { t.Fatalf("OutputPath = %q, want %q", result.OutputPath, path) @@ -108,11 +106,9 @@ func TestGenerateDailyBriefingWritesArtifact(t *testing.T) { } } -func TestGenerateDailyBriefingDefaultPath(t *testing.T) { +func TestGenerateBriefingDefaultPath(t *testing.T) { server := dailyBundleServer(t) - cfg := config.Defaults() - cfg.WeatherAPI.BaseURL = server.URL + "/" - cfg.WeatherAPI.Timezone = "America/Chicago" + cfg := dailyTestConfig(t, server) cfg.Workspace.Root = t.TempDir() resolved, err := ResolveGenerate(GenerateRequest{ Config: cfg, @@ -123,23 +119,21 @@ func TestGenerateDailyBriefingDefaultPath(t *testing.T) { t.Fatalf("ResolveGenerate() error = %v", err) } - result, err := GenerateDailyBriefing(context.Background(), DailyBriefingRequest{ + result, err := GenerateBriefing(context.Background(), BriefingRequest{ Config: cfg, Resolved: resolved, }) if err != nil { - t.Fatalf("GenerateDailyBriefing() error = %v", err) + t.Fatalf("GenerateBriefing() error = %v", err) } if !strings.HasSuffix(result.OutputPath, filepath.Join("snapshots", "daily", "2026-05-29", "20260529T100000.000000000Z_daily_today.briefing.json")) { t.Fatalf("OutputPath = %q, want deterministic daily briefing path", result.OutputPath) } } -func TestGenerateDailyReportWritesReportAndPreflight(t *testing.T) { +func TestGenerateReportWritesReportAndPreflight(t *testing.T) { server := dailyBundleServer(t) - cfg := config.Defaults() - cfg.WeatherAPI.BaseURL = server.URL + "/" - cfg.WeatherAPI.Timezone = "America/Chicago" + cfg := dailyTestConfig(t, server) cfg.Workspace.Root = t.TempDir() resolved, err := ResolveGenerate(GenerateRequest{ Config: cfg, @@ -165,14 +159,14 @@ func TestGenerateDailyReportWritesReportAndPreflight(t *testing.T) { } outputPath := filepath.Join(t.TempDir(), "daily.md") - result, err := GenerateDailyReport(context.Background(), DailyReportRequest{ + result, err := GenerateReport(context.Background(), ReportRequest{ Config: cfg, Resolved: resolved, OutputPath: outputPath, Renderer: renderer, }) if err != nil { - t.Fatalf("GenerateDailyReport() error = %v", err) + t.Fatalf("GenerateReport() error = %v", err) } if renderer.renderCalls != 1 { @@ -193,14 +187,7 @@ func TestGenerateDailyReportWritesReportAndPreflight(t *testing.T) { if renderer.runRequest.OutputPath != result.ReportPath { t.Fatalf("run OutputPath = %q, want managed report path %q", renderer.runRequest.OutputPath, result.ReportPath) } - for _, path := range []string{result.BriefingPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath} { - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected artifact %q: %v", path, err) - } - } - if _, err := os.Stat(outputPath); err != nil { - t.Fatalf("expected requested report output %q: %v", outputPath, err) - } + assertPathsExist(t, result.BriefingPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath, outputPath) data, err := os.ReadFile(result.DataPackagePath) if err != nil { t.Fatalf("read data package: %v", err) @@ -236,7 +223,7 @@ func TestGenerateDailyReportWritesReportAndPreflight(t *testing.T) { } } -func TestGenerateDailyReportPersistsFailedPreflight(t *testing.T) { +func TestGenerateReportPersistsFailedPreflight(t *testing.T) { server := dailyBundleServer(t) cfg := config.Defaults() cfg.WeatherAPI.BaseURL = server.URL + "/" @@ -259,13 +246,13 @@ func TestGenerateDailyReportPersistsFailedPreflight(t *testing.T) { err: errors.New("scriptorium render exited with code 1: render failed"), } - _, err = GenerateDailyReport(context.Background(), DailyReportRequest{ + _, err = GenerateReport(context.Background(), ReportRequest{ Config: cfg, Resolved: resolved, Renderer: renderer, }) if err == nil { - t.Fatal("GenerateDailyReport() error = nil, want render error") + t.Fatal("GenerateReport() error = nil, want render error") } store, err := state.NewFilesystemStore(cfg.Workspace) if err != nil { @@ -291,7 +278,7 @@ func TestGenerateDailyReportPersistsFailedPreflight(t *testing.T) { } } -func TestGenerateDailyReportReturnsRunErrorAfterPreflight(t *testing.T) { +func TestGenerateReportReturnsRunErrorAfterPreflight(t *testing.T) { server := dailyBundleServer(t) cfg := config.Defaults() cfg.WeatherAPI.BaseURL = server.URL + "/" @@ -315,13 +302,13 @@ func TestGenerateDailyReportReturnsRunErrorAfterPreflight(t *testing.T) { runBody: "# Daily Report\n", } - _, err = GenerateDailyReport(context.Background(), DailyReportRequest{ + _, err = GenerateReport(context.Background(), ReportRequest{ Config: cfg, Resolved: resolved, Renderer: renderer, }) if err == nil { - t.Fatal("GenerateDailyReport() error = nil, want run error") + t.Fatal("GenerateReport() error = nil, want run error") } if renderer.renderCalls != 1 || renderer.runCalls != 1 { t.Fatalf("calls render=%d run=%d, want one of each", renderer.renderCalls, renderer.runCalls) @@ -342,7 +329,7 @@ func TestGenerateDailyReportReturnsRunErrorAfterPreflight(t *testing.T) { } } -func TestGenerateDailyReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) { +func TestGenerateReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) { server := dailyBundleServer(t) cfg := config.Defaults() cfg.WeatherAPI.BaseURL = server.URL + "/" @@ -393,14 +380,14 @@ func TestGenerateDailyReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) runBody: "# Daily Report\n", } - result, err := GenerateDailyReport(context.Background(), DailyReportRequest{ + result, err := GenerateReport(context.Background(), ReportRequest{ Config: cfg, Resolved: currentResolved, Renderer: renderer, Store: store, }) if err != nil { - t.Fatalf("GenerateDailyReport() error = %v", err) + t.Fatalf("GenerateReport() error = %v", err) } if len(result.RecentChanges) == 0 { t.Fatal("RecentChanges length = 0, want changes from prior snapshot") @@ -433,13 +420,13 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) { runBody: "# Tomorrow Planning Brief\n", } - result, err := GenerateDailyReport(context.Background(), DailyReportRequest{ + result, err := GenerateReport(context.Background(), ReportRequest{ Config: cfg, Resolved: resolved, Renderer: renderer, }) if err != nil { - t.Fatalf("GenerateDailyReport() error = %v", err) + t.Fatalf("GenerateReport() error = %v", err) } if result.Briefing.Metadata.ReportID != report.DailyTomorrow || result.Briefing.Metadata.Variant != "tomorrow" { @@ -506,14 +493,14 @@ func TestTomorrowReportCanCompareAgainstPriorDailySnapshot(t *testing.T) { runBody: "# Tomorrow Planning Brief\n", } - result, err := GenerateDailyReport(context.Background(), DailyReportRequest{ + result, err := GenerateReport(context.Background(), ReportRequest{ Config: cfg, Resolved: currentResolved, Renderer: renderer, Store: store, }) if err != nil { - t.Fatalf("GenerateDailyReport() error = %v", err) + t.Fatalf("GenerateReport() error = %v", err) } if result.PriorSnapshot == nil { t.Fatal("PriorSnapshot = nil, want compatible prior daily snapshot") @@ -1021,6 +1008,23 @@ func mustParse(value string) time.Time { return parsed } +func dailyTestConfig(t *testing.T, server *httptest.Server) config.Config { + t.Helper() + cfg := config.Defaults() + cfg.WeatherAPI.BaseURL = server.URL + "/" + cfg.WeatherAPI.Timezone = "America/Chicago" + return cfg +} + +func assertPathsExist(t *testing.T, paths ...string) { + t.Helper() + for _, path := range paths { + if _, err := os.Stat(path); err != nil { + t.Fatalf("expected artifact %q: %v", path, err) + } + } +} + func priorDailyBriefing(resolved report.Resolved) briefing.Package { low := 50.0 high := 58.0 diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go index 1425ad7..c6233c6 100644 --- a/internal/cli/root_test.go +++ b/internal/cli/root_test.go @@ -62,12 +62,8 @@ func TestRunGenerateStormWritesMarkdownReport(t *testing.T) { server := dailyServer(t) tempDir := t.TempDir() scriptoriumPath := writeFakeScriptorium(t, tempDir) - configPath := filepath.Join(tempDir, "config.yml") workspaceRoot := filepath.Join(tempDir, "workspace") - configBody := "weather_api:\n base_url: " + server.URL + "/\n timezone: America/Chicago\nscriptorium:\n binary: " + scriptoriumPath + "\nworkspace:\n root: " + workspaceRoot + "\n" - if err := os.WriteFile(configPath, []byte(configBody), 0o600); err != nil { - t.Fatalf("write config: %v", err) - } + configPath := writeTestConfig(t, server, scriptoriumPath, workspaceRoot) outPath := filepath.Join(tempDir, "storm.md") var stdout bytes.Buffer var stderr bytes.Buffer @@ -90,14 +86,8 @@ func TestRunGenerateStormWritesMarkdownReport(t *testing.T) { if !strings.Contains(string(report), "# Daily Report") { t.Fatalf("report output missing markdown:\n%s", string(report)) } - dataPackageMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "storm", "2026-05-29", "*.data_package.json")) - if err != nil { - t.Fatalf("glob data package: %v", err) - } - if len(dataPackageMatches) != 1 { - t.Fatalf("data package files = %#v, want one", dataPackageMatches) - } - data, err := os.ReadFile(dataPackageMatches[0]) + dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "storm", "2026-05-29", "*.data_package.json") + data, err := os.ReadFile(dataPackagePath) if err != nil { t.Fatalf("read managed data package: %v", err) } @@ -110,12 +100,8 @@ func TestRunGenerateTomorrowWritesMarkdownReport(t *testing.T) { server := dailyServer(t) tempDir := t.TempDir() scriptoriumPath := writeFakeScriptorium(t, tempDir) - configPath := filepath.Join(tempDir, "config.yml") workspaceRoot := filepath.Join(tempDir, "workspace") - configBody := "weather_api:\n base_url: " + server.URL + "/\n timezone: America/Chicago\nscriptorium:\n binary: " + scriptoriumPath + "\nworkspace:\n root: " + workspaceRoot + "\n" - if err := os.WriteFile(configPath, []byte(configBody), 0o600); err != nil { - t.Fatalf("write config: %v", err) - } + configPath := writeTestConfig(t, server, scriptoriumPath, workspaceRoot) outPath := filepath.Join(tempDir, "tomorrow.md") var stdout bytes.Buffer var stderr bytes.Buffer @@ -136,14 +122,8 @@ func TestRunGenerateTomorrowWritesMarkdownReport(t *testing.T) { if !strings.Contains(string(report), "# Daily Report") { t.Fatalf("report output missing markdown:\n%s", string(report)) } - dataPackageMatches, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "daily", "2026-05-30", "*.data_package.json")) - if err != nil { - t.Fatalf("glob data package: %v", err) - } - if len(dataPackageMatches) != 1 { - t.Fatalf("data package files = %#v, want one", dataPackageMatches) - } - data, err := os.ReadFile(dataPackageMatches[0]) + dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "daily", "2026-05-30", "*.data_package.json") + data, err := os.ReadFile(dataPackagePath) if err != nil { t.Fatalf("read managed data package: %v", err) } @@ -822,6 +802,28 @@ func dailyServer(t *testing.T) *httptest.Server { return server } +func writeTestConfig(t *testing.T, server *httptest.Server, scriptoriumPath string, workspaceRoot string) string { + t.Helper() + configPath := filepath.Join(t.TempDir(), "config.yml") + configBody := "weather_api:\n base_url: " + server.URL + "/\n timezone: America/Chicago\nscriptorium:\n binary: " + scriptoriumPath + "\nworkspace:\n root: " + workspaceRoot + "\n" + if err := os.WriteFile(configPath, []byte(configBody), 0o600); err != nil { + t.Fatalf("write config: %v", err) + } + return configPath +} + +func oneArtifact(t *testing.T, root string, parts ...string) string { + t.Helper() + matches, err := filepath.Glob(filepath.Join(append([]string{root}, parts...)...)) + if err != nil { + t.Fatalf("glob artifact: %v", err) + } + if len(matches) != 1 { + t.Fatalf("artifact matches = %#v, want one", matches) + } + return matches[0] +} + func writeFakeScriptorium(t *testing.T, dir string) string { t.Helper() path := filepath.Join(dir, "scriptorium") diff --git a/internal/state/filesystem.go b/internal/state/filesystem.go index 89f3823..eca86cb 100644 --- a/internal/state/filesystem.go +++ b/internal/state/filesystem.go @@ -164,10 +164,6 @@ func (s *FilesystemStore) SaveMetadata(_ context.Context, metadata Metadata) (st return path, nil } -func (s *FilesystemStore) FindPriorDailySnapshot(ctx context.Context, resolved report.Resolved) (*PriorSnapshot, error) { - return s.FindPriorSnapshot(ctx, resolved) -} - func (s *FilesystemStore) FindPriorSnapshot(_ context.Context, resolved report.Resolved) (*PriorSnapshot, error) { if resolved.Definition.ComparisonStrategy != report.CompareSameValidDate && resolved.Definition.ComparisonStrategy != report.CompareWeekendWindow { return nil, nil diff --git a/internal/state/filesystem_test.go b/internal/state/filesystem_test.go index de0fca9..77dd399 100644 --- a/internal/state/filesystem_test.go +++ b/internal/state/filesystem_test.go @@ -124,7 +124,7 @@ func TestSaveArtifactsAndMetadataRoundTrip(t *testing.T) { } } -func TestFindPriorDailySnapshot(t *testing.T) { +func TestFindPriorSnapshot(t *testing.T) { store := newTestStore(t) first := resolveDailyAt(t, "2026-05-29T05:00:00-05:00") second := resolveDailyAt(t, "2026-05-29T08:00:00-05:00") @@ -148,12 +148,12 @@ func TestFindPriorDailySnapshot(t *testing.T) { t.Fatalf("SaveMetadata() error = %v", err) } - prior, err := store.FindPriorDailySnapshot(context.Background(), second) + prior, err := store.FindPriorSnapshot(context.Background(), second) if err != nil { - t.Fatalf("FindPriorDailySnapshot() error = %v", err) + t.Fatalf("FindPriorSnapshot() error = %v", err) } if prior == nil { - t.Fatal("FindPriorDailySnapshot() = nil, want prior snapshot") + t.Fatal("FindPriorSnapshot() = nil, want prior snapshot") } if prior.Metadata.RunID != first.Metadata().RunID { t.Fatalf("RunID = %q, want %q", prior.Metadata.RunID, first.Metadata().RunID) @@ -163,7 +163,7 @@ func TestFindPriorDailySnapshot(t *testing.T) { } } -func TestFindPriorDailySnapshotUsesValidDate(t *testing.T) { +func TestFindPriorSnapshotUsesValidDate(t *testing.T) { store := newTestStore(t) previousDate := resolveDailyAt(t, "2026-05-28T05:00:00-05:00") currentDate := resolveDailyAt(t, "2026-05-29T05:00:00-05:00") @@ -187,12 +187,12 @@ func TestFindPriorDailySnapshotUsesValidDate(t *testing.T) { t.Fatalf("SaveMetadata() error = %v", err) } - prior, err := store.FindPriorDailySnapshot(context.Background(), currentDate) + prior, err := store.FindPriorSnapshot(context.Background(), currentDate) if err != nil { - t.Fatalf("FindPriorDailySnapshot() error = %v", err) + t.Fatalf("FindPriorSnapshot() error = %v", err) } if prior != nil { - t.Fatalf("FindPriorDailySnapshot() = %#v, want nil for different valid date", prior) + t.Fatalf("FindPriorSnapshot() = %#v, want nil for different valid date", prior) } } diff --git a/internal/state/store.go b/internal/state/store.go index 6d2f319..766a265 100644 --- a/internal/state/store.go +++ b/internal/state/store.go @@ -17,7 +17,6 @@ type Store interface { PrepareRenderedReport(context.Context, report.Resolved) (string, error) SaveMetadata(context.Context, Metadata) (string, error) FindPriorSnapshot(context.Context, report.Resolved) (*PriorSnapshot, error) - FindPriorDailySnapshot(context.Context, report.Resolved) (*PriorSnapshot, error) LoadBriefing(context.Context, string) (briefing.Package, error) }