package app import ( "bytes" "context" "os" "path/filepath" "strings" "testing" "time" "gitea.maximumdirect.net/eric/narratio/internal/manifest" ) func TestExecuteValidCommands(t *testing.T) { pipelinePath, sessionPath := writeValidConfigFiles(t) manifestPath := writeManifestPathForExecute(t) cases := []struct { name string args []string wantOut string }{ {name: "run", args: []string{"run", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio run: configuration loaded and valid"}, {name: "plan", args: []string{"plan", "--config", pipelinePath, "--session", sessionPath}, wantOut: "narratio plan: configuration loaded and valid"}, {name: "status", args: []string{"status", "--manifest", manifestPath}, wantOut: "session_id: 2026-05-03"}, {name: "resume", args: []string{"resume"}, wantOut: "narratio resume: not yet implemented"}, {name: "run-stage", args: []string{"run-stage", "polish"}, wantOut: "narratio run-stage: not yet implemented"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer code := Execute(tc.args, &stdout, &stderr) if code != 0 { t.Fatalf("exit code = %d, want 0", code) } if stderr.Len() != 0 { t.Fatalf("stderr = %q, want empty", stderr.String()) } if !strings.Contains(stdout.String(), tc.wantOut) { t.Fatalf("stdout = %q, want to contain %q", stdout.String(), tc.wantOut) } }) } } func TestExecuteMissingRequiredFlags(t *testing.T) { cases := []struct { name string args []string want string }{ {name: "run missing flags", args: []string{"run"}, want: "run: --config and --session are required"}, {name: "plan missing flags", args: []string{"plan"}, want: "plan: --config and --session are required"}, {name: "status missing flags", args: []string{"status"}, want: "status: --manifest is required"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer code := Execute(tc.args, &stdout, &stderr) if code == 0 { t.Fatalf("exit code = 0, want non-zero") } if stdout.Len() != 0 { t.Fatalf("stdout = %q, want empty", stdout.String()) } if !strings.Contains(stderr.String(), tc.want) { t.Fatalf("stderr = %q, want to contain %q", stderr.String(), tc.want) } }) } } func TestExecuteInvalidCommand(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer code := Execute([]string{"bogus"}, &stdout, &stderr) if code == 0 { t.Fatalf("exit code = 0, want non-zero") } if stdout.Len() != 0 { t.Fatalf("stdout = %q, want empty", stdout.String()) } out := stderr.String() if !strings.Contains(out, "unknown command") { t.Fatalf("stderr = %q, want unknown command message", out) } if !strings.Contains(out, "Usage: narratio") { t.Fatalf("stderr = %q, want usage message", out) } } func TestExecuteMissingCommand(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer code := Execute(nil, &stdout, &stderr) if code == 0 { t.Fatalf("exit code = 0, want non-zero") } if stdout.Len() != 0 { t.Fatalf("stdout = %q, want empty", stdout.String()) } if !strings.Contains(stderr.String(), "Usage: narratio") { t.Fatalf("stderr = %q, want usage message", stderr.String()) } } func writeValidConfigFiles(t *testing.T) (string, string) { t.Helper() dir := t.TempDir() pipelinePath := filepath.Join(dir, "pipeline.yml") sessionPath := filepath.Join(dir, "session.yml") pipelineYAML := `workspace: root: /tmp/narratio storage: backend: s3 whisperx: timeout: 15m seriatim: timeout: 30s audita: timeout: 1h analyzer: timeout: 20m artifacts: output_dir: artifacts notification: timeout: 10s ` sessionYAML := `session_id: 2026-05-03 inputs: audio_dir: ./audio speakers_file: ./speakers.yml autocorrect_file: ./autocorrect.yml glossary_file: ./glossary.yml ` if err := os.WriteFile(pipelinePath, []byte(pipelineYAML), 0o644); err != nil { t.Fatalf("write pipeline config: %v", err) } if err := os.WriteFile(sessionPath, []byte(sessionYAML), 0o644); err != nil { t.Fatalf("write session config: %v", err) } return pipelinePath, sessionPath } func writeManifestPathForExecute(t *testing.T) string { t.Helper() store := &manifest.LocalStore{} m := manifest.New("2026-05-03", time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC)) m.MarkStageSucceeded("prepare", time.Date(2026, 5, 3, 10, 1, 0, 0, time.UTC), nil) path := filepath.Join(t.TempDir(), "manifest.json") if err := store.Save(context.Background(), path, m); err != nil { t.Fatalf("save manifest: %v", err) } return path }