Wire CLI action summaries
This commit is contained in:
@@ -131,7 +131,7 @@ func TestRunGenerateThreeDayWritesMarkdownReport(t *testing.T) {
|
||||
outPath := fixture.path("three-day.md")
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
_, err := runTestCommand(t, runner,
|
||||
output, err := runTestCommand(t, runner,
|
||||
"generate", "three-day",
|
||||
"--config", fixture.configPath,
|
||||
"--out", outPath,
|
||||
@@ -143,6 +143,20 @@ func TestRunGenerateThreeDayWritesMarkdownReport(t *testing.T) {
|
||||
dataPackagePath := oneArtifact(t, fixture.workspaceRoot, "data-packages", "three-day", "2026-05-29", "data_package.*.yaml")
|
||||
assertFileContains(t, dataPackagePath, "id: three_day")
|
||||
assertFileContains(t, dataPackagePath, "derived_daypart_summaries:")
|
||||
|
||||
summary := decodeGenerateSummary(t, output.stdout)
|
||||
if summary.Command != "generate" || summary.Status != "succeeded" || summary.ReportID != report.ThreeDay {
|
||||
t.Fatalf("generate summary = %#v, want successful 3-day summary", summary)
|
||||
}
|
||||
if summary.ReportPath == "" || summary.MetadataPath == "" || summary.DataPackagePath == "" || summary.PreflightPath == "" {
|
||||
t.Fatalf("summary paths = %#v, want managed artifact paths", summary)
|
||||
}
|
||||
if summary.OutputPath != outPath {
|
||||
t.Fatalf("summary OutputPath = %q, want %q", summary.OutputPath, outPath)
|
||||
}
|
||||
if summary.GeneratedTextRawPath != "" || summary.GeneratedTextResultPath != "" || summary.GeneratedTextPath != "" || summary.RenderContextPath != "" {
|
||||
t.Fatalf("generated-text paths = %#v, want omitted for markdown report", summary)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunGenerateWeekendWritesMarkdownReport(t *testing.T) {
|
||||
@@ -197,9 +211,9 @@ func TestRunMorningReportsPartialFailureAndContinues(t *testing.T) {
|
||||
t.Fatalf("Run() error = %q, want aggregate failure", err.Error())
|
||||
}
|
||||
|
||||
var summary app.BatchResult
|
||||
if decodeErr := json.Unmarshal([]byte(output.stdout), &summary); decodeErr != nil {
|
||||
t.Fatalf("decode summary: %v\n%s", decodeErr, output.stdout)
|
||||
summary := decodeBatchSummary(t, output.stdout)
|
||||
if summary.Command != "run" || summary.Status != "failed" {
|
||||
t.Fatalf("summary command/status = %q/%q, want run/failed", summary.Command, summary.Status)
|
||||
}
|
||||
if summary.Total != 2 || summary.Succeeded != 1 || summary.Failed != 1 {
|
||||
t.Fatalf("summary total/succeeded/failed = %d/%d/%d, want 2/1/1", summary.Total, summary.Succeeded, summary.Failed)
|
||||
@@ -387,9 +401,9 @@ func TestRunEveningUsesOutputDirectoryAndSummary(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
var summary app.BatchResult
|
||||
if decodeErr := json.Unmarshal([]byte(output.stdout), &summary); decodeErr != nil {
|
||||
t.Fatalf("decode summary: %v\n%s", decodeErr, output.stdout)
|
||||
summary := decodeBatchSummary(t, output.stdout)
|
||||
if summary.Command != "run" || summary.Status != "succeeded" {
|
||||
t.Fatalf("summary command/status = %q/%q, want run/succeeded", summary.Command, summary.Status)
|
||||
}
|
||||
if summary.Total != 1 || summary.Failed != 0 {
|
||||
t.Fatalf("summary total/failed = %d/%d, want 1/0", summary.Total, summary.Failed)
|
||||
@@ -402,6 +416,24 @@ func TestRunEveningUsesOutputDirectoryAndSummary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunQuietSuppressesSuccessfulOutput(t *testing.T) {
|
||||
fixture := newCLIFixture(t, writeFakeScriptorium)
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
output, err := runTestCommand(t, runner,
|
||||
"run", "evening",
|
||||
"--config", fixture.configPath,
|
||||
"--quiet",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
if output.stdout != "" || output.stderr != "" {
|
||||
t.Fatalf("stdout/stderr = %q/%q, want quiet success output", output.stdout, output.stderr)
|
||||
}
|
||||
_ = oneArtifact(t, fixture.workspaceRoot, "reports", "tomorrow", "2026-05-30", "report.*.md")
|
||||
}
|
||||
|
||||
func TestRunEveningReportsOmitsPerReportNotification(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
var uploadCount int
|
||||
@@ -437,9 +469,9 @@ func TestRunEveningReportsOmitsPerReportNotification(t *testing.T) {
|
||||
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())
|
||||
summary := decodeBatchSummary(t, stdout.String())
|
||||
if summary.Command != "run" || summary.Status != "succeeded" {
|
||||
t.Fatalf("summary command/status = %q/%q, want run/succeeded", summary.Command, summary.Status)
|
||||
}
|
||||
if len(summary.Reports) != 1 {
|
||||
t.Fatalf("reports = %#v, want one report", summary.Reports)
|
||||
@@ -489,9 +521,9 @@ func TestRunEveningReportsDoesNotRequirePerReportDistributorToken(t *testing.T)
|
||||
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())
|
||||
summary := decodeBatchSummary(t, stdout.String())
|
||||
if summary.Command != "run" || summary.Status != "succeeded" {
|
||||
t.Fatalf("summary command/status = %q/%q, want run/succeeded", summary.Command, summary.Status)
|
||||
}
|
||||
if len(summary.Reports) != 1 {
|
||||
t.Fatalf("summary reports = %#v, want one report", summary.Reports)
|
||||
@@ -541,12 +573,12 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
report, err := os.ReadFile(outPath)
|
||||
reportData, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(report), "# Friday's Weather") {
|
||||
t.Fatalf("report output missing markdown:\n%s", string(report))
|
||||
if !strings.Contains(string(reportData), "# Friday's Weather") {
|
||||
t.Fatalf("report output missing markdown:\n%s", string(reportData))
|
||||
}
|
||||
dataPackagePath := oneArtifact(t, fixture.workspaceRoot, "data-packages", "daily", "2026-05-29", "data_package.*.yaml")
|
||||
data, err := os.ReadFile(dataPackagePath)
|
||||
@@ -598,7 +630,7 @@ func TestRunGenerateTodayWritesGeneratedTextReport(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
report, err := os.ReadFile(outPath)
|
||||
reportData, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
@@ -607,8 +639,8 @@ func TestRunGenerateTodayWritesGeneratedTextReport(t *testing.T) {
|
||||
"Today starts with showers before improving.",
|
||||
"Morning showers should taper as drier air arrives.",
|
||||
} {
|
||||
if !strings.Contains(string(report), want) {
|
||||
t.Fatalf("today report output missing %q:\n%s", want, string(report))
|
||||
if !strings.Contains(string(reportData), want) {
|
||||
t.Fatalf("today report output missing %q:\n%s", want, string(reportData))
|
||||
}
|
||||
}
|
||||
dataPackagePath := oneArtifact(t, fixture.workspaceRoot, "data-packages", "today", "2026-05-29", "data_package.*.yaml")
|
||||
@@ -630,6 +662,20 @@ func TestRunGenerateTodayWritesGeneratedTextReport(t *testing.T) {
|
||||
assertFileContains(t, validatedGeneratedTextPath, `"summary":"Today starts with showers before improving."`)
|
||||
assertFileContains(t, renderContextPath, `"Title": "Today's Weather"`)
|
||||
assertFileContains(t, managedReportPath, "# Today's Weather")
|
||||
|
||||
summary := decodeGenerateSummary(t, stdout.String())
|
||||
if summary.Command != "generate" || summary.Status != "succeeded" || summary.ReportID != report.Today {
|
||||
t.Fatalf("generate summary = %#v, want successful Today summary", summary)
|
||||
}
|
||||
if summary.RunID == "" || summary.ReportPath == "" || summary.MetadataPath == "" || summary.DataPackagePath == "" || summary.PreflightPath == "" {
|
||||
t.Fatalf("summary identity/paths = %#v, want run id and managed artifact paths", summary)
|
||||
}
|
||||
if summary.GeneratedTextRawPath == "" || summary.GeneratedTextResultPath == "" || summary.GeneratedTextPath == "" || summary.RenderContextPath == "" {
|
||||
t.Fatalf("generated-text paths = %#v, want generated-text artifact paths", summary)
|
||||
}
|
||||
if summary.OutputPath != outPath {
|
||||
t.Fatalf("summary OutputPath = %q, want %q", summary.OutputPath, outPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
@@ -647,7 +693,7 @@ func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
report, err := os.ReadFile(outPath)
|
||||
reportData, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read report: %v", err)
|
||||
}
|
||||
@@ -657,8 +703,8 @@ func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
"A cold front is moving into the region.",
|
||||
"A front will keep the region unsettled.",
|
||||
} {
|
||||
if !strings.Contains(string(report), want) {
|
||||
t.Fatalf("report output missing %q:\n%s", want, string(report))
|
||||
if !strings.Contains(string(reportData), want) {
|
||||
t.Fatalf("report output missing %q:\n%s", want, string(reportData))
|
||||
}
|
||||
}
|
||||
dataPackagePath := oneArtifact(t, fixture.workspaceRoot, "data-packages", "hourly", "2026-05-29", "data_package.*.yaml")
|
||||
@@ -685,6 +731,76 @@ func TestRunGenerateHourlyWritesGeneratedTextReport(t *testing.T) {
|
||||
assertFileContains(t, managedReportPath, "# Hourly Report")
|
||||
}
|
||||
|
||||
func TestRunGenerateQuietSuppressesSuccessfulOutput(t *testing.T) {
|
||||
fixture := newCLIFixture(t, writeStructuredOutputScriptorium)
|
||||
outPath := fixture.path("today.md")
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
output, err := runTestCommand(t, runner,
|
||||
"generate", "today",
|
||||
"--config", fixture.configPath,
|
||||
"--date", "2026-05-29",
|
||||
"--out", outPath,
|
||||
"--quiet",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
if output.stdout != "" || output.stderr != "" {
|
||||
t.Fatalf("stdout/stderr = %q/%q, want quiet success output", output.stdout, output.stderr)
|
||||
}
|
||||
assertFileContains(t, outPath, "# Today's Weather")
|
||||
}
|
||||
|
||||
func TestRunGeneratePreRunErrorEmitsNoJSON(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
err := runner.Run(context.Background(), []string{"generate", "daily"}, &stdout, &stderr)
|
||||
if err == nil {
|
||||
t.Fatal("Run() error = nil, want required date error")
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
t.Fatalf("stdout = %q, want no partial JSON", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunGenerateNotificationFailureEmitsFailureSummary(t *testing.T) {
|
||||
server := dailyServer(t)
|
||||
distributorServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("unexpected distributor request with unset token: %s", r.URL.Path)
|
||||
}))
|
||||
t.Cleanup(distributorServer.Close)
|
||||
tempDir := t.TempDir()
|
||||
scriptoriumPath := writeFakeScriptorium(t, tempDir)
|
||||
workspaceRoot := filepath.Join(tempDir, "workspace")
|
||||
configPath := writeTestConfigWithDistributor(t, server, scriptoriumPath, workspaceRoot, distributorServer.URL)
|
||||
t.Setenv("CLI_DISTRIBUTOR_TOKEN", "")
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
output, err := runTestCommand(t, runner,
|
||||
"generate", "three-day",
|
||||
"--config", configPath,
|
||||
)
|
||||
if err == nil {
|
||||
t.Fatal("Run() error = nil, want notification failure")
|
||||
}
|
||||
summary := decodeGenerateSummary(t, output.stdout)
|
||||
if summary.Command != "generate" || summary.Status != "failed" || summary.Error == "" {
|
||||
t.Fatalf("summary = %#v, want failed generate summary", summary)
|
||||
}
|
||||
if !strings.Contains(summary.Error, "token environment variable") {
|
||||
t.Fatalf("summary error = %q, want token environment context", summary.Error)
|
||||
}
|
||||
if summary.ReportPath == "" || summary.MetadataPath == "" || summary.NotificationPath == "" {
|
||||
t.Fatalf("summary paths = %#v, want inspectable report, metadata, and notification paths", summary)
|
||||
}
|
||||
if strings.Contains(output.stdout, "CLI_DISTRIBUTOR_TOKEN_VALUE") || strings.Contains(output.stderr, "CLI_DISTRIBUTOR_TOKEN_VALUE") {
|
||||
t.Fatalf("output contains distributor token value\nstdout=%s\nstderr=%s", output.stdout, output.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInspectTodayArtifacts(t *testing.T) {
|
||||
fixture := newCLIFixture(t, writeFakeScriptorium)
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
@@ -791,6 +907,22 @@ func TestRunInspectMissingMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInspectRejectsQuiet(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configPath := writeWorkspaceConfig(t, filepath.Join(tempDir, "workspace"))
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
err := runner.Run(context.Background(), []string{"inspect", "reports", "--config", configPath, "--quiet"}, &stdout, &stderr)
|
||||
if err == nil {
|
||||
t.Fatal("Run(inspect reports --quiet) error = nil, want unexpected flag error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "flag provided but not defined") {
|
||||
t.Fatalf("error = %q, want unexpected quiet flag", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunInspectRunCommandsParseRunIDAndConfig(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configPath := writeWorkspaceConfig(t, filepath.Join(tempDir, "workspace"))
|
||||
@@ -1179,6 +1311,24 @@ func runTestCommand(t *testing.T, runner Runner, args ...string) (commandOutput,
|
||||
}, err
|
||||
}
|
||||
|
||||
func decodeGenerateSummary(t *testing.T, text string) generateSummary {
|
||||
t.Helper()
|
||||
var summary generateSummary
|
||||
if err := json.Unmarshal([]byte(text), &summary); err != nil {
|
||||
t.Fatalf("decode generate summary: %v\n%s", err, text)
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func decodeBatchSummary(t *testing.T, text string) batchSummary {
|
||||
t.Helper()
|
||||
var summary batchSummary
|
||||
if err := json.Unmarshal([]byte(text), &summary); err != nil {
|
||||
t.Fatalf("decode batch summary: %v\n%s", err, text)
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func dailyServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user