Cover Tomorrow artifact and notification behavior
This commit is contained in:
@@ -646,6 +646,62 @@ func TestGenerateHourlyReportNotificationFailureFailsReport(t *testing.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.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,
|
||||||
|
Report: ReportTomorrow,
|
||||||
|
}, mustParse("2026-05-29T18:00:00-05:00"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||||
|
}
|
||||||
|
notifier := &recordingNotifier{}
|
||||||
|
renderer := &recordingRenderer{
|
||||||
|
renderResult: &scriptorium.RenderResult{ExitCode: 0},
|
||||||
|
structuredRunResult: &scriptorium.StructuredRunResult{ExitCode: 0},
|
||||||
|
structuredRunBody: validTomorrowGeneratedTextJSON(),
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||||
|
Config: cfg,
|
||||||
|
Resolved: resolved,
|
||||||
|
Renderer: renderer,
|
||||||
|
Notifier: notifier,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GenerateReport() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(notifier.requests) != 1 {
|
||||||
|
t.Fatalf("notification requests = %d, want 1", len(notifier.requests))
|
||||||
|
}
|
||||||
|
req := notifier.requests[0]
|
||||||
|
if req.ReportID != report.Tomorrow {
|
||||||
|
t.Fatalf("notification ReportID = %q, want tomorrow", req.ReportID)
|
||||||
|
}
|
||||||
|
if req.PipelineID != "weatherreporter.tomorrow.tomorrow" {
|
||||||
|
t.Fatalf("PipelineID = %q, want report/artifact group values", req.PipelineID)
|
||||||
|
}
|
||||||
|
if req.BundleID != "tomorrow.tomorrow.md.tomorrow" {
|
||||||
|
t.Fatalf("BundleID = %q, want artifact group, batch output name, and report id", req.BundleID)
|
||||||
|
}
|
||||||
|
wantBundlePaths := []string{"2026-05-30/tomorrow/tomorrow.md"}
|
||||||
|
if strings.Join(req.BundlePaths, "\n") != strings.Join(wantBundlePaths, "\n") {
|
||||||
|
t.Fatalf("BundlePaths = %#v, want %#v", req.BundlePaths, wantBundlePaths)
|
||||||
|
}
|
||||||
|
if req.ReportPath != result.ReportPath {
|
||||||
|
t.Fatalf("ReportPath = %q, want managed path %q", req.ReportPath, result.ReportPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateHourlyReportPersistsPreflightFailure(t *testing.T) {
|
func TestGenerateHourlyReportPersistsPreflightFailure(t *testing.T) {
|
||||||
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
|
cfg, resolved, store, notifier, outputPath := hourlyGeneratedTextFixture(t)
|
||||||
renderer := &recordingRenderer{
|
renderer := &recordingRenderer{
|
||||||
@@ -1398,6 +1454,50 @@ 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,
|
||||||
|
Report: ReportTomorrow,
|
||||||
|
}, mustParse("2026-05-28T18:00:00-05:00"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResolveGenerate(prior) error = %v", err)
|
||||||
|
}
|
||||||
|
savePriorRun(t, store, priorResolved, priorDailyModuleSnapshot(t, priorResolved))
|
||||||
|
|
||||||
|
currentResolved, err := ResolveGenerate(GenerateRequest{
|
||||||
|
Config: cfg,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||||
|
Config: cfg,
|
||||||
|
Resolved: currentResolved,
|
||||||
|
Renderer: successfulRenderer("# Daily Report\n"),
|
||||||
|
Store: store,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GenerateReport() error = %v", err)
|
||||||
|
}
|
||||||
|
if result.PriorSnapshot != nil {
|
||||||
|
t.Fatalf("PriorSnapshot = %#v, want nil for prior tomorrow snapshot", result.PriorSnapshot)
|
||||||
|
}
|
||||||
|
if len(result.RecentChanges) != 0 {
|
||||||
|
t.Fatalf("RecentChanges = %#v, want none from incompatible prior tomorrow snapshot", result.RecentChanges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGenerateThreeDayReportWritesReportAndRecentChanges(t *testing.T) {
|
func TestGenerateThreeDayReportWritesReportAndRecentChanges(t *testing.T) {
|
||||||
server := dailyBundleServer(t)
|
server := dailyBundleServer(t)
|
||||||
cfg := config.Defaults()
|
cfg := config.Defaults()
|
||||||
|
|||||||
@@ -262,6 +262,16 @@ reports:
|
|||||||
`,
|
`,
|
||||||
wantErr: `unknown module "forecast_delta"`,
|
wantErr: `unknown module "forecast_delta"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "RetiredTomorrowReportID",
|
||||||
|
yaml: `
|
||||||
|
reports:
|
||||||
|
daily_tomorrow:
|
||||||
|
deterministic_modules:
|
||||||
|
- metadata
|
||||||
|
`,
|
||||||
|
wantErr: "reports.daily_tomorrow is not a known report",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "InvalidOptions",
|
name: "InvalidOptions",
|
||||||
yaml: `
|
yaml: `
|
||||||
|
|||||||
@@ -42,21 +42,46 @@ func TestPathsUseRunIDAndWorkspace(t *testing.T) {
|
|||||||
|
|
||||||
func TestGeneratedTextArtifactPathsUseSnapshotTree(t *testing.T) {
|
func TestGeneratedTextArtifactPathsUseSnapshotTree(t *testing.T) {
|
||||||
store := newTestStore(t)
|
store := newTestStore(t)
|
||||||
resolved := resolveHourlyAt(t, "2026-05-29T05:00:00-05:00")
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
resolved report.Resolved
|
||||||
|
group string
|
||||||
|
validDate string
|
||||||
|
runID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "hourly",
|
||||||
|
resolved: resolveHourlyAt(t, "2026-05-29T05:00:00-05:00"),
|
||||||
|
group: "hourly",
|
||||||
|
validDate: "2026-05-29",
|
||||||
|
runID: "20260529T100000.000000000Z_hourly",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tomorrow",
|
||||||
|
resolved: resolveTomorrowAt(t, "2026-05-29T18:00:00-05:00"),
|
||||||
|
group: "tomorrow",
|
||||||
|
validDate: "2026-05-30",
|
||||||
|
runID: "20260529T230000.000000000Z_tomorrow",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
paths, err := store.Paths(resolved)
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
paths, err := store.Paths(tt.resolved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Paths() error = %v", err)
|
t.Fatalf("Paths() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
runID := "20260529T100000.000000000Z_hourly"
|
|
||||||
wants := map[string]string{
|
wants := map[string]string{
|
||||||
"GeneratedTextRaw": filepath.Join("snapshots", "hourly", "2026-05-29", runID+".generated_text.raw.json"),
|
"DataPackage": filepath.Join("data-packages", tt.group, tt.validDate, tt.runID+".data_package.yaml"),
|
||||||
"GeneratedTextResult": filepath.Join("snapshots", "hourly", "2026-05-29", runID+".generated_text.run.json"),
|
"RenderedReport": filepath.Join("reports", tt.group, tt.runID+".md"),
|
||||||
"GeneratedText": filepath.Join("snapshots", "hourly", "2026-05-29", runID+".generated_text.json"),
|
"GeneratedTextRaw": filepath.Join("snapshots", tt.group, tt.validDate, tt.runID+".generated_text.raw.json"),
|
||||||
"RenderContext": filepath.Join("snapshots", "hourly", "2026-05-29", runID+".render_context.json"),
|
"GeneratedTextResult": filepath.Join("snapshots", tt.group, tt.validDate, tt.runID+".generated_text.run.json"),
|
||||||
|
"GeneratedText": filepath.Join("snapshots", tt.group, tt.validDate, tt.runID+".generated_text.json"),
|
||||||
|
"RenderContext": filepath.Join("snapshots", tt.group, tt.validDate, tt.runID+".render_context.json"),
|
||||||
}
|
}
|
||||||
got := map[string]string{
|
got := map[string]string{
|
||||||
|
"DataPackage": paths.DataPackage,
|
||||||
|
"RenderedReport": paths.RenderedReport,
|
||||||
"GeneratedTextRaw": paths.GeneratedTextRaw,
|
"GeneratedTextRaw": paths.GeneratedTextRaw,
|
||||||
"GeneratedTextResult": paths.GeneratedTextResult,
|
"GeneratedTextResult": paths.GeneratedTextResult,
|
||||||
"GeneratedText": paths.GeneratedText,
|
"GeneratedText": paths.GeneratedText,
|
||||||
@@ -67,6 +92,8 @@ func TestGeneratedTextArtifactPathsUseSnapshotTree(t *testing.T) {
|
|||||||
t.Fatalf("%s path = %q, want component %q", name, got[name], want)
|
t.Fatalf("%s path = %q, want component %q", name, got[name], want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveArtifactsAndMetadataRoundTrip(t *testing.T) {
|
func TestSaveArtifactsAndMetadataRoundTrip(t *testing.T) {
|
||||||
@@ -565,6 +592,26 @@ func resolveHourlyAt(t *testing.T, value string) report.Resolved {
|
|||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveTomorrowAt(t *testing.T, value string) report.Resolved {
|
||||||
|
t.Helper()
|
||||||
|
location, err := timeutil.LoadLocation("America/Chicago")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("LoadLocation() error = %v", err)
|
||||||
|
}
|
||||||
|
now, err := time.Parse(time.RFC3339, value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse time: %v", err)
|
||||||
|
}
|
||||||
|
resolved, err := report.DefaultRegistry().Resolve(report.Tomorrow, report.ResolveRequest{
|
||||||
|
Now: now,
|
||||||
|
Location: location,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Resolve() error = %v", err)
|
||||||
|
}
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
func stateBriefingMetadata(resolved report.Resolved) briefing.Metadata {
|
func stateBriefingMetadata(resolved report.Resolved) briefing.Metadata {
|
||||||
return briefing.Metadata{
|
return briefing.Metadata{
|
||||||
RunID: resolved.Metadata().RunID,
|
RunID: resolved.Metadata().RunID,
|
||||||
|
|||||||
Reference in New Issue
Block a user