Cover hourly CLI generation workflow
This commit is contained in:
@@ -89,3 +89,18 @@ reports:
|
||||
- weather_story
|
||||
- outdoor_windows
|
||||
- hourly_forecast
|
||||
hourly:
|
||||
deterministic_modules:
|
||||
- metadata
|
||||
- current_conditions
|
||||
- hourly_forecast
|
||||
- precip_timing
|
||||
- alert_digest
|
||||
- spc_convective_outlooks
|
||||
- id: area_forecast_discussion
|
||||
options:
|
||||
sections:
|
||||
- key_messages
|
||||
- short_term
|
||||
- spc_convective_discussion
|
||||
- weather_story
|
||||
|
||||
@@ -685,6 +685,59 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
scriptoriumPath := writeStructuredOutputScriptorium(t, tempDir)
|
||||
workspaceRoot := filepath.Join(tempDir, "workspace")
|
||||
configPath := writeTestConfig(t, server, scriptoriumPath, workspaceRoot)
|
||||
outPath := filepath.Join(tempDir, "hourly.md")
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
runner := Runner{Clock: timeutil.FixedClock{Time: time.Date(2026, 5, 29, 11, 0, 0, 0, time.UTC)}}
|
||||
|
||||
err := runner.Run(context.Background(), []string{
|
||||
"generate", "hourly",
|
||||
"--config", configPath,
|
||||
"--out", outPath,
|
||||
}, &stdout, &stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
report, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
for _, want := range []string{
|
||||
"# Hourly Report",
|
||||
"Storm chances increase through late morning.",
|
||||
"The main window is 10 AM to noon.",
|
||||
"Brief downpours may slow travel.",
|
||||
} {
|
||||
if !strings.Contains(string(report), want) {
|
||||
t.Fatalf("report output missing %q:\n%s", want, string(report))
|
||||
}
|
||||
}
|
||||
dataPackagePath := oneArtifact(t, workspaceRoot, "data-packages", "hourly", "2026-05-29", "*.data_package.yaml")
|
||||
dataPackage, err := os.ReadFile(dataPackagePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read managed data package: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(dataPackage), "id: hourly") ||
|
||||
!strings.Contains(string(dataPackage), "prompt_id: weather.hourly_generated_text") ||
|
||||
!strings.Contains(string(dataPackage), "hourly_forecast:") {
|
||||
t.Fatalf("data package output missing hourly content:\n%s", string(dataPackage))
|
||||
}
|
||||
rawGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "hourly", "2026-05-29", "*.generated_text.raw.json")
|
||||
validatedGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "hourly", "2026-05-29", "*.generated_text.json")
|
||||
renderContextPath := oneArtifact(t, workspaceRoot, "snapshots", "hourly", "2026-05-29", "*.render_context.json")
|
||||
managedReportPath := oneArtifact(t, workspaceRoot, "reports", "hourly", "*.md")
|
||||
assertFileContains(t, rawGeneratedTextPath, `"summary": " Storm chances increase through late morning. "`)
|
||||
assertFileContains(t, validatedGeneratedTextPath, `"summary":"Storm chances increase through late morning."`)
|
||||
assertFileContains(t, renderContextPath, `"ReportTitle": "Hourly Report"`)
|
||||
assertFileContains(t, managedReportPath, "# Hourly Report")
|
||||
}
|
||||
|
||||
func TestRunInspectGeneratedArtifacts(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
@@ -867,6 +920,8 @@ func TestResolveGenerateHourlyRejectsDateAndStormBounds(t *testing.T) {
|
||||
{"hourly", "--date", "2026-05-29"},
|
||||
{"hourly", "--start", "2026-05-29T18:00"},
|
||||
{"hourly", "--end", "2026-05-29T20:00"},
|
||||
{"hourly", "--hours", "6"},
|
||||
{"hourly", "--duration", "6h"},
|
||||
} {
|
||||
_, err := runner.resolveGenerate(args)
|
||||
if err == nil {
|
||||
@@ -1092,6 +1147,17 @@ func oneArtifact(t *testing.T, root string, parts ...string) string {
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
func assertFileContains(t *testing.T, path string, want string) {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", path, err)
|
||||
}
|
||||
if !strings.Contains(string(data), want) {
|
||||
t.Fatalf("%s missing %q:\n%s", path, want, string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func writeFakeScriptorium(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, "scriptorium")
|
||||
@@ -1122,6 +1188,52 @@ exit 1
|
||||
return path
|
||||
}
|
||||
|
||||
func writeStructuredOutputScriptorium(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, "scriptorium")
|
||||
body := `#!/bin/sh
|
||||
if [ "$1" = "render" ]; then
|
||||
printf '{"ok":true,"argv":"%s"}' "$*"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$1" = "run" ]; then
|
||||
out=""
|
||||
prompt=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = "--out" ]; then
|
||||
shift
|
||||
out="$1"
|
||||
elif [ "$1" = "--prompt" ]; then
|
||||
shift
|
||||
prompt="$1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if [ "$prompt" = "weather.hourly_generated_text" ]; then
|
||||
cat > "$out" <<'JSON'
|
||||
{
|
||||
"summary": " Storm chances increase through late morning. ",
|
||||
"timing": "The main window is 10 AM to noon.",
|
||||
"impacts": "Brief downpours may slow travel.",
|
||||
"confidence": "Medium"
|
||||
}
|
||||
JSON
|
||||
printf 'wrote generated text\n' >&2
|
||||
exit 0
|
||||
fi
|
||||
printf '# Daily Report\n\nGenerated by fake scriptorium.\n' > "$out"
|
||||
printf 'wrote report\n' >&2
|
||||
exit 0
|
||||
fi
|
||||
printf 'unexpected command\n' >&2
|
||||
exit 1
|
||||
`
|
||||
if err := os.WriteFile(path, []byte(body), 0o700); err != nil {
|
||||
t.Fatalf("write fake scriptorium: %v", err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func writeFailingScriptorium(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, "scriptorium")
|
||||
|
||||
@@ -91,6 +91,22 @@ func TestLoadExampleConfig(t *testing.T) {
|
||||
if len(cfg.Notify.Distributor.ReportPathTemplates) != 1 {
|
||||
t.Fatalf("ReportPathTemplates = %#v, want example archive path", cfg.Notify.Distributor.ReportPathTemplates)
|
||||
}
|
||||
overrides := cfg.ReportModuleOverrides()
|
||||
hourly := overrides[report.Hourly]
|
||||
if len(hourly) != 9 {
|
||||
t.Fatalf("hourly example override length = %d, want 9", len(hourly))
|
||||
}
|
||||
if hourly[0].ID != module.Metadata ||
|
||||
hourly[1].ID != module.CurrentConditions ||
|
||||
hourly[2].ID != module.HourlyForecast ||
|
||||
hourly[3].ID != module.PrecipTiming ||
|
||||
hourly[4].ID != module.AlertDigest ||
|
||||
hourly[5].ID != module.SPCConvectiveOutlooks ||
|
||||
hourly[6].ID != module.AreaForecastDiscussion ||
|
||||
hourly[7].ID != module.SPCConvectiveDiscussion ||
|
||||
hourly[8].ID != module.WeatherStory {
|
||||
t.Fatalf("hourly example override = %#v, want configured module order", hourly)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMinimalExampleConfig(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user