package cli import ( "bytes" "context" "errors" "os" "path/filepath" "strings" "testing" "gitea.maximumdirect.net/eric/audita/v3/internal/core/reporting" commandkit "go.maximumdirect.net/commandkit" "gopkg.in/yaml.v3" ) func TestPreparedDeliveryFollowsReportingMode(t *testing.T) { dir := t.TempDir() for name, contents := range map[string]string{ "config.yml": "version: 3\npipeline:\n stages: []\nreporting:\n enabled: false\n", "transcript.json": `[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`, } { if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil { t.Fatal(err) } } for _, tc := range []struct { name string flags []string file bool wantInfo bool }{ {"ordinary", nil, false, false}, {"verbose", []string{"--verbose"}, false, true}, {"quiet file", []string{"--quiet"}, true, false}, } { t.Run(tc.name, func(t *testing.T) { input := processInput{Config: commandkit.Supplied[string]{Value: filepath.Join(dir, "config.yml"), Set: true}, Transcript: filepath.Join(dir, "transcript.json")} if tc.file { input.Output = commandkit.Supplied[string]{Value: filepath.Join(dir, "result.json"), Set: true} } cmd := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deliver", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome { loaded, err := loadProcessInputs(inv.Context, input, inv.ReportingMode()) if err != nil { return commandkit.Usage(err.Error()) } prepared, err := prepareProcess(inv.Context, input, loaded, inv.Reporter().Output()) if err != nil { return commandkit.Failure(err.Error()) } return finishProcess(inv, input, loaded, prepared, reporting.Snapshot{}, []byte("transcript bytes"), nil) }}) app, err := commandkit.New(commandkit.Identity{Name: "test"}, cmd) if err != nil { t.Fatal(err) } var out, stderr bytes.Buffer args := append([]string{"deliver"}, tc.flags...) outcome := app.Execute(context.Background(), args, commandkit.Streams{Out: &out, Err: &stderr}) if outcome.Status != 0 { t.Fatalf("outcome=%+v err=%s", outcome, stderr.String()) } if tc.file { data, err := os.ReadFile(input.Output.Value) if err != nil || string(data) != "transcript bytes" { t.Fatalf("file=%q err=%v", data, err) } if out.Len() != 0 { t.Fatalf("stdout=%q", out.String()) } } else if out.String() != "transcript bytes" { t.Fatalf("stdout=%q", out.String()) } if strings.Contains(stderr.String(), "Completed:") != tc.wantInfo { t.Fatalf("stderr=%q", stderr.String()) } }) } } func TestDeliveryFailureSuppressesCompletionSummary(t *testing.T) { dir := t.TempDir() t.Chdir(dir) for name, contents := range map[string]string{ "config.yml": "version: 3\npipeline:\n stages: []\n", "transcript.json": `[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`, } { if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil { t.Fatal(err) } } input := processInput{Config: commandkit.Supplied[string]{Value: filepath.Join(dir, "config.yml"), Set: true}, Transcript: filepath.Join(dir, "transcript.json")} cmd := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deliver", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome { loaded, err := loadProcessInputs(inv.Context, input, inv.ReportingMode()) if err != nil { return commandkit.Failure(err.Error()) } prepared, err := prepareProcess(inv.Context, input, loaded, inv.Reporter().Output()) if err != nil { return commandkit.Failure(err.Error()) } return finishProcess(inv, input, loaded, prepared, reporting.Snapshot{}, []byte("transcript bytes"), nil) }}) app, err := commandkit.New(commandkit.Identity{Name: "test"}, cmd) if err != nil { t.Fatal(err) } var stderr bytes.Buffer outcome := app.Execute(context.Background(), []string{"deliver", "--verbose"}, commandkit.Streams{Out: shortProcessWriter{}, Err: &stderr}) if outcome.Status == 0 || strings.Contains(stderr.String(), "Completed:") { t.Fatalf("status=%d stderr=%q", outcome.Status, stderr.String()) } var errs reporting.ErrorsDocument var recoveries reporting.RecoveriesDocument for path, target := range map[string]any{"errors.yml": &errs, "recoveries.yml": &recoveries} { data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if err := yaml.Unmarshal(data, target); err != nil { t.Fatal(err) } } if errs.Status != reporting.StatusFailed || recoveries.Status != reporting.StatusFailed || errs.Summary.RunErrors == nil || *errs.Summary.RunErrors != 1 || recoveries.Summary.RunErrors == nil || *recoveries.Summary.RunErrors != 1 || len(errs.Errors) != 1 || errs.Errors[0].Reason != "output_failure" { t.Fatalf("failed output reports disagree: %+v %+v", errs, recoveries) } } func TestCanceledEmptyPipelineLeavesTranscriptAndFinalizesReports(t *testing.T) { dir := t.TempDir() for name, contents := range map[string]string{ "config.yml": "version: 3\npipeline: {stages: []}\n", "transcript.json": `[{"speaker":"Alice","start":0,"end":1,"text":"hello"}]`, "output.json": "previous transcript", } { if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0600); err != nil { t.Fatal(err) } } input := processInput{ Config: commandkit.Supplied[string]{Value: filepath.Join(dir, "config.yml"), Set: true}, Transcript: filepath.Join(dir, "transcript.json"), Output: commandkit.Supplied[string]{Value: filepath.Join(dir, "output.json"), Set: true}, } ctx, cancel := context.WithCancel(context.Background()) defer cancel() cmd := commandkit.NewCommand(commandkit.CommandSpec[struct{}]{Name: "deliver", Run: func(inv commandkit.Invocation, _ struct{}) commandkit.Outcome { loaded, err := loadProcessInputs(inv.Context, input, inv.ReportingMode()) if err != nil { t.Fatal(err) } prepared, err := prepareProcess(inv.Context, input, loaded, inv.Reporter().Output()) if err != nil { t.Fatal(err) } defer prepared.Prepared.Output.Close() result, err := executePipeline(inv.Context, loaded, prepared, sourceToCanonicalTranscript(loaded.Source), func(error) {}) if err != nil { t.Fatal(err) } cancel() return finishProcess(inv, input, loaded, prepared, result.Snapshot, []byte("new transcript"), nil) }}) app, err := commandkit.New(commandkit.Identity{Name: "test"}, cmd) if err != nil { t.Fatal(err) } var stdout, stderr bytes.Buffer outcome := app.Execute(ctx, []string{"deliver", "--verbose"}, commandkit.Streams{Out: &stdout, Err: &stderr}) if outcome.Status == 0 || !errors.Is(outcome.Cause, context.Canceled) || strings.Contains(stderr.String(), "Completed:") { t.Fatalf("outcome=%+v stderr=%q", outcome, stderr.String()) } if data, err := os.ReadFile(input.Output.Value); err != nil || string(data) != "previous transcript" { t.Fatalf("transcript changed after cancellation: %q, %v", data, err) } var errorsDoc reporting.ErrorsDocument var recoveries reporting.RecoveriesDocument for name, target := range map[string]any{"errors.yml": &errorsDoc, "recoveries.yml": &recoveries} { data, err := os.ReadFile(filepath.Join(dir, name)) if err != nil { t.Fatal(err) } if err := yaml.Unmarshal(data, target); err != nil { t.Fatal(err) } } if errorsDoc.Status != reporting.StatusFailed || recoveries.Status != reporting.StatusFailed || len(errorsDoc.Errors) != 1 || errorsDoc.Errors[0].Reason != "canceled" { t.Fatalf("reports did not capture cancellation: %+v %+v", errorsDoc, recoveries) } } type shortProcessWriter struct{} func (shortProcessWriter) Write(data []byte) (int, error) { return len(data) / 2, nil }