Harden scheduled report runs
This commit is contained in:
@@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -283,6 +284,92 @@ func TestRunMorningIncludesWeekendExceptSunday(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunMorningReportsPartialFailureAndContinues(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
scriptoriumPath := writeFailingScriptorium(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)
|
||||
}
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
err := runner.Run(context.Background(), []string{
|
||||
"run", "morning",
|
||||
"--config", configPath,
|
||||
}, &stdout, &stderr)
|
||||
if err == nil {
|
||||
t.Fatal("Run() error = nil, want aggregate failure")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "1 of 3 reports failed") {
|
||||
t.Fatalf("Run() error = %q, want aggregate failure", err.Error())
|
||||
}
|
||||
|
||||
var summary app.BatchResult
|
||||
if decodeErr := json.Unmarshal(stdout.Bytes(), &summary); decodeErr != nil {
|
||||
t.Fatalf("decode summary: %v\n%s", decodeErr, stdout.String())
|
||||
}
|
||||
if summary.Total != 3 || summary.Succeeded != 2 || summary.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 3/2/1", summary.Total, summary.Succeeded, summary.Failed)
|
||||
}
|
||||
if !strings.Contains(stderr.String(), "status=failed") || !strings.Contains(stderr.String(), "status=succeeded") {
|
||||
t.Fatalf("stderr missing structured report logs:\n%s", stderr.String())
|
||||
}
|
||||
dailyPackages, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "daily", "2026-05-29", "*.data_package.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("glob daily packages: %v", err)
|
||||
}
|
||||
weekendPackages, err := filepath.Glob(filepath.Join(workspaceRoot, "data-packages", "weekend", "2026-05-29", "*.data_package.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("glob weekend packages: %v", err)
|
||||
}
|
||||
if len(dailyPackages) != 1 || len(weekendPackages) != 1 {
|
||||
t.Fatalf("daily packages = %#v, weekend packages = %#v; want successful reports to continue", dailyPackages, weekendPackages)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunEveningUsesOutputDirectoryAndSummary(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
scriptoriumPath := writeFakeScriptorium(t, tempDir)
|
||||
configPath := filepath.Join(tempDir, "config.yml")
|
||||
workspaceRoot := filepath.Join(tempDir, "workspace")
|
||||
outputDir := filepath.Join(tempDir, "copies")
|
||||
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)
|
||||
}
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
err := runner.Run(context.Background(), []string{
|
||||
"run", "evening",
|
||||
"--config", configPath,
|
||||
"--out-dir", outputDir,
|
||||
}, &stdout, &stderr)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
var summary app.BatchResult
|
||||
if decodeErr := json.Unmarshal(stdout.Bytes(), &summary); decodeErr != nil {
|
||||
t.Fatalf("decode summary: %v\n%s", decodeErr, stdout.String())
|
||||
}
|
||||
if summary.Total != 1 || summary.Failed != 0 {
|
||||
t.Fatalf("summary total/failed = %d/%d, want 1/0", summary.Total, summary.Failed)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(outputDir, "tomorrow.md")); err != nil {
|
||||
t.Fatalf("expected copied report: %v", err)
|
||||
}
|
||||
if len(summary.Reports) != 1 || summary.Reports[0].OutputPath != filepath.Join(outputDir, "tomorrow.md") {
|
||||
t.Fatalf("summary reports = %#v, want output path", summary.Reports)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunMorningGeneratesDailyAndThreeDayOnSunday(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
tempDir := t.TempDir()
|
||||
@@ -505,6 +592,16 @@ func TestResolveRunRejectsOutputFlag(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveRunAppliesOutputDirectory(t *testing.T) {
|
||||
req, err := resolveRun([]string{"evening", "--out-dir", "./reports"})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveRun() error = %v", err)
|
||||
}
|
||||
if req.OutputDir != "./reports" {
|
||||
t.Fatalf("OutputDir = %q, want ./reports", req.OutputDir)
|
||||
}
|
||||
}
|
||||
|
||||
func fixedClock() timeutil.Clock {
|
||||
return timeutil.FixedClock{Time: time.Date(2026, 5, 29, 12, 0, 0, 0, time.UTC)}
|
||||
}
|
||||
@@ -562,3 +659,44 @@ exit 1
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func writeFailingScriptorium(t *testing.T, dir string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, "scriptorium")
|
||||
body := `#!/bin/sh
|
||||
if [ "$1" = "render" ]; then
|
||||
prompt=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = "--prompt" ]; then
|
||||
shift
|
||||
prompt="$1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
if [ "$prompt" = "weather.three_day_outlook" ]; then
|
||||
printf 'render failed\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '{"ok":true,"prompt":"%s"}' "$prompt"
|
||||
exit 0
|
||||
fi
|
||||
if [ "$1" = "run" ]; then
|
||||
out=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = "--out" ]; then
|
||||
shift
|
||||
out="$1"
|
||||
fi
|
||||
shift
|
||||
done
|
||||
printf '# Batch Report\n\nGenerated by fake scriptorium.\n' > "$out"
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user