Clean up app test setup

This commit is contained in:
2026-06-16 15:30:23 +00:00
parent 3639636813
commit 1f5b347cd2

View File

@@ -83,16 +83,11 @@ func TestFetchAndSaveBundleRequiresOutputPath(t *testing.T) {
func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{
Command: []string{"scriptorium", "render"},
@@ -113,11 +108,7 @@ func TestGenerateReportWritesReportAndPreflight(t *testing.T) {
runBody: "# Daily Report\n\nRain this morning.\n",
}
outputPath := filepath.Join(t.TempDir(), "daily.md")
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
store := &recordingStore{Store: filesystemStore}
store := recordingFilesystemStore(t, cfg)
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -342,20 +333,12 @@ func TestGeneratedTemplateReportsUseRichArtifactsAndCuratedDataPackages(t *testi
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: tt.kind,
Date: tt.date,
}, tt.now)
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
}
}, tt.now.Format(time.RFC3339))
renderer := successfulGeneratedTextRenderer("")
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -477,28 +460,12 @@ func TestGenerateReportOmitsSPCConvectiveDiscussionBelowThreshold(t *testing.T)
func TestGenerateHourlyReportUsesGeneratedTextTemplateWorkflow(t *testing.T) {
server := hourlyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg.Location.ID = "home"
cfg.Location.Name = "Brentwood"
cfg.Location.Region = "MO"
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
Report: ReportHourly,
}, mustParse("2026-05-29T08:30:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
cfg := hourlyTestConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{Report: ReportHourly}, "2026-05-29T08:30:00-05:00")
if resolved.Definition.GenerationMode != report.GenerationModeGeneratedTextTemplate {
t.Fatalf("GenerationMode = %q, want generated text template", resolved.Definition.GenerationMode)
}
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
store := &recordingStore{Store: filesystemStore}
store := recordingFilesystemStore(t, cfg)
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{
Command: []string{"scriptorium", "render"},
@@ -642,18 +609,8 @@ func TestGenerateHourlyReportUsesGeneratedTextTemplateWorkflow(t *testing.T) {
func TestGenerateHourlyReportCopiesOutputAndNotifiesManagedReport(t *testing.T) {
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
notifier.result = &NotificationResult{
RunID: "distributor-run",
Status: "succeeded",
UploadStatus: "accepted",
PipelineID: "reports",
Report: []byte(`{"actions":[{"action":"replace_older"}]}`),
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validHourlyGeneratedTextJSON(),
}
notifier.result = successfulNotificationResult()
renderer := successfulGeneratedTextRenderer(validHourlyGeneratedTextJSON())
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -725,38 +682,21 @@ func TestGenerateHourlyReportCopiesOutputAndNotifiesManagedReport(t *testing.T)
func TestGenerateTodayReportCopiesOutputAndNotifiesTodayTemplateValues(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg := dailyWorkspaceConfig(t, server)
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{report_id}.{artifact_group}"
cfg.Notify.Distributor.BundleIDTemplate = "{artifact_group}.{batch_output_name}.{report_id}"
cfg.Notify.Distributor.IdempotencyKeyTemplate = "{bundle_id}.{run_id}"
cfg.Notify.Distributor.ReportPathTemplates = []string{"{valid_start_date}/{artifact_group}/{batch_output_name}"}
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportToday,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
outputPath := filepath.Join(t.TempDir(), "today-copy.md")
notifier := &recordingNotifier{
result: &NotificationResult{
RunID: "distributor-run",
Status: "succeeded",
UploadStatus: "accepted",
PipelineID: "reports",
Report: []byte(`{"actions":[{"action":"replace_older"}]}`),
},
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validTodayGeneratedTextJSON(),
result: successfulNotificationResult(),
}
renderer := successfulGeneratedTextRenderer(validTodayGeneratedTextJSON())
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -941,28 +881,17 @@ func TestGenerateReportSavesFinalMetadataForMarkdownAndGeneratedTextReports(t *t
func TestGenerateTomorrowReportNotificationUsesTomorrowTemplateValues(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg := dailyWorkspaceConfig(t, server)
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{report_id}.{artifact_group}"
cfg.Notify.Distributor.BundleIDTemplate = "{artifact_group}.{batch_output_name}.{report_id}"
cfg.Notify.Distributor.IdempotencyKeyTemplate = "{bundle_id}.{run_id}"
cfg.Notify.Distributor.ReportPathTemplates = []string{"{valid_start_date}/{artifact_group}/{batch_output_name}"}
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportTomorrow,
}, mustParse("2026-05-29T18:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T18:00:00-05:00")
notifier := &recordingNotifier{}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validTomorrowGeneratedTextJSON(),
}
renderer := successfulGeneratedTextRenderer(validTomorrowGeneratedTextJSON())
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -1080,15 +1009,11 @@ func TestGenerateHourlyReportPersistsStructuredRunFailure(t *testing.T) {
func TestGenerateHourlyReportPreservesRawTextOnValidationFailure(t *testing.T) {
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: `{
renderer := successfulGeneratedTextRenderer(`{
"summary": "Storm chances increase through late morning.",
"forecast_discussion": "A front will keep the region unsettled.",
"details": "not allowed"
}`,
}
}`)
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -1116,11 +1041,7 @@ func TestGenerateHourlyReportPreservesRawTextOnValidationFailure(t *testing.T) {
func TestGenerateHourlyReportRejectsUnsupportedTemplateBeforeStructuredRun(t *testing.T) {
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
resolved.Definition.TemplateID = "missing-template"
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validHourlyGeneratedTextJSON(),
}
renderer := successfulGeneratedTextRenderer(validHourlyGeneratedTextJSON())
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -1150,19 +1071,14 @@ func TestGenerateHourlyReportRejectsUnsupportedTemplateBeforeStructuredRun(t *te
func TestGenerateReportDisabledNotificationDoesNotCallNotifier(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
notifier := &recordingNotifier{}
_, err = GenerateReport(context.Background(), ReportRequest{
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: successfulRenderer("# Daily Report\n"),
@@ -1178,26 +1094,13 @@ func TestGenerateReportDisabledNotificationDoesNotCallNotifier(t *testing.T) {
func TestGenerateReportNotifiesManagedReportPath(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyNotificationConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
notifier := &recordingNotifier{
result: &NotificationResult{
RunID: "distributor-run",
Status: "succeeded",
UploadStatus: "accepted",
PipelineID: "reports",
Report: []byte(`{"actions":[{"action":"replace_older"}]}`),
},
result: successfulNotificationResult(),
}
outputPath := filepath.Join(t.TempDir(), "daily-copy.md")
@@ -1266,25 +1169,15 @@ func TestGenerateReportNotifiesManagedReportPath(t *testing.T) {
func TestGenerateReportNotificationFailureFailsReport(t *testing.T) {
server := dailyBundleServer(t)
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyNotificationConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
notifier := &recordingNotifier{err: errors.New("upload rejected")}
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
_, err = GenerateReport(context.Background(), ReportRequest{
store := recordingFilesystemStore(t, cfg)
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: successfulRenderer("# Daily Report\n"),
@@ -1342,21 +1235,14 @@ func TestGenerateReportDoesNotNotifyAfterRenderOrRunFailure(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyNotificationConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
notifier := &recordingNotifier{}
_, err = GenerateReport(context.Background(), ReportRequest{
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: tt.renderer,
@@ -1379,17 +1265,13 @@ func TestGenerateReportDoesNotNotifyAfterFetchFailure(t *testing.T) {
cfg.Workspace.Root = t.TempDir()
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
notifier := &recordingNotifier{}
_, err = GenerateReport(context.Background(), ReportRequest{
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: successfulRenderer("# Daily Report\n"),
@@ -1405,18 +1287,11 @@ func TestGenerateReportDoesNotNotifyAfterFetchFailure(t *testing.T) {
func TestGenerateReportPersistsFailedPreflight(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{
Command: []string{"scriptorium", "render"},
@@ -1426,7 +1301,7 @@ func TestGenerateReportPersistsFailedPreflight(t *testing.T) {
err: errors.New("scriptorium render exited with code 1: render failed"),
}
_, err = GenerateReport(context.Background(), ReportRequest{
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: renderer,
@@ -1434,10 +1309,7 @@ func TestGenerateReportPersistsFailedPreflight(t *testing.T) {
if err == nil {
t.Fatal("GenerateReport() error = nil, want render error")
}
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
store := recordingFilesystemStore(t, cfg)
paths, err := store.Paths(resolved)
if err != nil {
t.Fatalf("Paths() error = %v", err)
@@ -1460,18 +1332,11 @@ func TestGenerateReportPersistsFailedPreflight(t *testing.T) {
func TestGenerateReportReturnsRunErrorAfterPreflight(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{
@@ -1481,7 +1346,7 @@ func TestGenerateReportReturnsRunErrorAfterPreflight(t *testing.T) {
structuredRunErr: errors.New("scriptorium run exited with code 2: validation failed"),
}
_, err = GenerateReport(context.Background(), ReportRequest{
_, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: resolved,
Renderer: renderer,
@@ -1492,10 +1357,7 @@ func TestGenerateReportReturnsRunErrorAfterPreflight(t *testing.T) {
if renderer.renderCalls != 1 || renderer.structuredRunCalls != 1 || renderer.runCalls != 0 {
t.Fatalf("calls render=%d structured=%d run=%d, want render and structured run only", renderer.renderCalls, renderer.structuredRunCalls, renderer.runCalls)
}
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
store := recordingFilesystemStore(t, cfg)
paths, err := store.Paths(resolved)
if err != nil {
t.Fatalf("Paths() error = %v", err)
@@ -1513,32 +1375,18 @@ func TestGenerateReportReturnsRunErrorAfterPreflight(t *testing.T) {
func TestGenerateReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T04:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
}, "2026-05-29T04:00:00-05:00")
savePriorRun(t, store, priorResolved, priorDailyModuleSnapshot(t, priorResolved))
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
runResult: &scriptorium.RunResult{ExitCode: 0},
@@ -1568,37 +1416,19 @@ func TestGenerateReportIncludesRecentChangesFromPriorSnapshot(t *testing.T) {
func TestGenerateTodayReportUsesTodayIdentityAndRecentChanges(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportToday,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T04:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
}, "2026-05-29T04:00:00-05:00")
savePriorRun(t, store, priorResolved, priorDailyModuleSnapshot(t, priorResolved))
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportToday,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validTodayGeneratedTextJSON(),
}
}, "2026-05-29T05:00:00-05:00")
renderer := successfulGeneratedTextRenderer(validTodayGeneratedTextJSON())
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -1655,22 +1485,11 @@ func TestGenerateTodayReportUsesTodayIdentityAndRecentChanges(t *testing.T) {
func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportTomorrow,
}, mustParse("2026-05-29T18:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validTomorrowGeneratedTextJSON(),
}
}, "2026-05-29T18:00:00-05:00")
renderer := successfulGeneratedTextRenderer(validTomorrowGeneratedTextJSON())
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -1732,35 +1551,17 @@ func TestGenerateTomorrowReportUsesTomorrowBriefingDate(t *testing.T) {
func TestTomorrowReportCanCompareAgainstPriorTomorrowSnapshot(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportTomorrow,
}, mustParse("2026-05-29T17:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
}, "2026-05-29T17:00:00-05:00")
savePriorRun(t, store, priorResolved, priorDailyModuleSnapshot(t, priorResolved))
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportTomorrow,
}, mustParse("2026-05-29T18:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: validTomorrowGeneratedTextJSON(),
}
}, "2026-05-29T18:00:00-05:00")
renderer := successfulGeneratedTextRenderer(validTomorrowGeneratedTextJSON())
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
@@ -1781,31 +1582,17 @@ func TestTomorrowReportCanCompareAgainstPriorTomorrowSnapshot(t *testing.T) {
func TestDailyReportIgnoresPriorTomorrowSnapshot(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportTomorrow,
}, mustParse("2026-05-28T18:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
}, "2026-05-28T18:00:00-05:00")
savePriorRun(t, store, priorResolved, priorDailyModuleSnapshot(t, priorResolved))
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
result, err := GenerateReport(context.Background(), ReportRequest{
Config: cfg,
Resolved: currentResolved,
@@ -1825,29 +1612,15 @@ func TestDailyReportIgnoresPriorTomorrowSnapshot(t *testing.T) {
func TestGenerateThreeDayReportWritesReportAndRecentChanges(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportThreeDay,
}, mustParse("2026-05-29T04:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
}, "2026-05-29T04:00:00-05:00")
savePriorRun(t, store, priorResolved, priorOutlookModuleSnapshot(t, "2026-05-30"))
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportThreeDay,
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
runResult: &scriptorium.RunResult{ExitCode: 0},
@@ -1884,29 +1657,15 @@ func TestGenerateThreeDayReportWritesReportAndRecentChanges(t *testing.T) {
func TestGenerateWeekendReportWritesReportAndRecentChanges(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportWeekend,
}, mustParse("2026-05-29T04:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
}, "2026-05-29T04:00:00-05:00")
savePriorRun(t, store, priorResolved, priorOutlookModuleSnapshot(t, "2026-05-30"))
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportWeekend,
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
runResult: &scriptorium.RunResult{ExitCode: 0},
@@ -1943,19 +1702,12 @@ func TestGenerateWeekendReportWritesReportAndRecentChanges(t *testing.T) {
func TestGenerateStormReportWritesReport(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportStorm,
StormStart: mustParse("2026-05-29T06:00:00-05:00"),
StormEnd: mustParse("2026-05-29T10:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
runResult: &scriptorium.RunResult{ExitCode: 0},
@@ -1993,18 +1745,11 @@ func TestGenerateStormReportWritesReport(t *testing.T) {
func TestInspectGeneratedReportArtifacts(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
runResult: &scriptorium.RunResult{ExitCode: 0},
@@ -2061,30 +1806,16 @@ func TestInspectGeneratedReportArtifacts(t *testing.T) {
func TestInspectPriorSnapshot(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
store, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
priorResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
cfg := dailyWorkspaceConfig(t, server)
store := recordingFilesystemStore(t, cfg)
priorResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T04:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(prior) error = %v", err)
}
currentResolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
}, "2026-05-29T04:00:00-05:00")
currentResolved := resolveGenerateForTest(t, cfg, GenerateRequest{
Report: ReportDaily,
Date: mustParse("2026-05-29T12:00:00-05:00"),
}, mustParse("2026-05-29T05:00:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate(current) error = %v", err)
}
}, "2026-05-29T05:00:00-05:00")
renderer := &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
runResult: &scriptorium.RunResult{ExitCode: 0},
@@ -2381,10 +2112,7 @@ func TestResolveBatchMorningSkipsWeekendOnSunday(t *testing.T) {
func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg := dailyWorkspaceConfig(t, server)
renderer := &selectiveRenderer{
failRenderPrompt: "weather.three_day_outlook",
runBody: "# Batch Report\n",
@@ -2422,12 +2150,7 @@ func TestRunBatchContinuesAfterReportFailure(t *testing.T) {
func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
cfg := dailyNotificationConfig(t, server)
notifier := &recordingNotifier{
errByReport: map[report.ID]error{
report.ThreeDay: errors.New("distributor unavailable"),
@@ -2507,10 +2230,7 @@ func TestRunBatchContinuesAfterNotificationFailure(t *testing.T) {
func TestRunBatchUsesOutputDirectory(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg := dailyWorkspaceConfig(t, server)
outputDir := filepath.Join(t.TempDir(), "reports")
result, err := RunBatchDetailed(context.Background(), BatchRequest{
@@ -2548,10 +2268,7 @@ func TestRunBatchUsesOutputDirectory(t *testing.T) {
func TestRunBatchMorningUsesTodayOutputName(t *testing.T) {
server := dailyBundleServer(t)
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg := dailyWorkspaceConfig(t, server)
outputDir := filepath.Join(t.TempDir(), "reports")
result, err := RunBatchDetailed(context.Background(), BatchRequest{
@@ -2617,6 +2334,70 @@ func dailyTestConfig(t *testing.T, server *httptest.Server) config.Config {
return cfg
}
func dailyWorkspaceConfig(t *testing.T, server *httptest.Server) config.Config {
t.Helper()
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir()
return cfg
}
func dailyNotificationConfig(t *testing.T, server *httptest.Server) config.Config {
t.Helper()
cfg := dailyWorkspaceConfig(t, server)
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
return cfg
}
func resolveGenerateForTest(t *testing.T, cfg config.Config, req GenerateRequest, now string) report.Resolved {
t.Helper()
req.Config = cfg
resolved, err := ResolveGenerate(req, mustParse(now))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
return resolved
}
func recordingFilesystemStore(t *testing.T, cfg config.Config) *recordingStore {
t.Helper()
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
return &recordingStore{Store: filesystemStore}
}
func successfulNotificationResult() *NotificationResult {
return &NotificationResult{
RunID: "distributor-run",
Status: "succeeded",
UploadStatus: "accepted",
PipelineID: "reports",
Report: []byte(`{"actions":[{"action":"replace_older"}]}`),
}
}
func successfulGeneratedTextRenderer(body string) *recordingRenderer {
return &recordingRenderer{
renderResult: &scriptorium.RenderResult{ExitCode: 0},
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
structuredRunBody: body,
}
}
func hourlyTestConfig(t *testing.T, server *httptest.Server) config.Config {
t.Helper()
cfg := config.Defaults()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg.Location.ID = "home"
cfg.Location.Name = "Brentwood"
cfg.Location.Region = "MO"
return cfg
}
func hourlyGeneratedTextFixture(t *testing.T) (config.Config, report.Resolved, *recordingStore, *recordingNotifier, string) {
t.Helper()
server := hourlyBundleServer(t)
@@ -2653,12 +2434,10 @@ func hourlyGeneratedTextConfigWithModules(t *testing.T, server *httptest.Server,
func applyHourlyGeneratedTextSettings(cfg *config.Config, t *testing.T, server *httptest.Server) {
t.Helper()
cfg.WeatherAPI.BaseURL = server.URL + "/"
cfg.WeatherAPI.Timezone = "America/Chicago"
cfg.Workspace.Root = t.TempDir()
cfg.Location.ID = "home"
cfg.Location.Name = "Brentwood"
cfg.Location.Region = "MO"
base := hourlyTestConfig(t, server)
cfg.WeatherAPI = base.WeatherAPI
cfg.Workspace = base.Workspace
cfg.Location = base.Location
cfg.Notify.Distributor.Enabled = true
cfg.Notify.Distributor.PipelineIDTemplate = "weatherreporter.{artifact_group}"
cfg.Notify.Distributor.BundleIDTemplate = "weatherreporter.{location_id}.{report_id}"
@@ -2668,18 +2447,8 @@ func applyHourlyGeneratedTextSettings(cfg *config.Config, t *testing.T, server *
func resolveHourlyGeneratedTextFixture(t *testing.T, cfg config.Config) (report.Resolved, *recordingStore, *recordingNotifier, string) {
t.Helper()
resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg,
Report: ReportHourly,
}, mustParse("2026-05-29T08:30:00-05:00"))
if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err)
}
filesystemStore, err := state.NewFilesystemStore(cfg.Workspace)
if err != nil {
t.Fatalf("NewFilesystemStore() error = %v", err)
}
return resolved, &recordingStore{Store: filesystemStore}, &recordingNotifier{}, filepath.Join(t.TempDir(), "hourly-copy.md")
resolved := resolveGenerateForTest(t, cfg, GenerateRequest{Report: ReportHourly}, "2026-05-29T08:30:00-05:00")
return resolved, recordingFilesystemStore(t, cfg), &recordingNotifier{}, filepath.Join(t.TempDir(), "hourly-copy.md")
}
func validHourlyGeneratedTextJSON() string {