package app import ( "bytes" "context" "os" "path/filepath" "strings" "testing" "time" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/manifest" ) func TestPlanCreatesAndReusesWorkdir(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot) var out bytes.Buffer args := []string{"--config", pipelinePath, "--campaign", campaignPath, "--session", sessionPath} if err := Plan(context.Background(), args, &out); err != nil { t.Fatalf("first Plan() error = %v", err) } got := out.String() if !strings.Contains(got, "narratio plan: workdir prepared at") { t.Fatalf("first output = %q, want workdir prepared", got) } for _, name := range []string{"prepare", "transcribe", "merge", "polish", "normalize", "trim", "analyze", "archive", "notify"} { if !strings.Contains(got, name+": run") { t.Fatalf("first output = %q, missing stage %q", got, name) } } if !strings.Contains(got, "totals: run=9 skip=0") { t.Fatalf("first output = %q, want totals", got) } sessionWorkdir := artifacts.SessionWorkDirForCampaign(workspaceRoot, "sample-campaign", "2026-05-03") expectedDirs := []string{ sessionWorkdir, filepath.Join(sessionWorkdir, "inputs"), filepath.Join(sessionWorkdir, "audio"), filepath.Join(sessionWorkdir, "transcripts", "raw"), filepath.Join(sessionWorkdir, "transcripts", "trimmed"), filepath.Join(sessionWorkdir, "artifacts"), filepath.Join(sessionWorkdir, "config"), filepath.Join(sessionWorkdir, "logs"), } for _, dir := range expectedDirs { assertDir(t, dir) } out.Reset() if err := Plan(context.Background(), args, &out); err != nil { t.Fatalf("second Plan() error = %v", err) } if !strings.Contains(out.String(), "narratio plan: workdir prepared at") { t.Fatalf("second output = %q, want workdir prepared", out.String()) } } func TestPlanShowsRunAndSkipFromManifest(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot) manifestPath := filepath.Join(workspaceRoot, "work", "sample-campaign", "2026-05-03", "manifest.json") 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) m.MarkStageSucceeded("transcribe", time.Date(2026, 5, 3, 10, 2, 0, 0, time.UTC), nil) if err := store.Save(context.Background(), manifestPath, m); err != nil { t.Fatalf("save manifest: %v", err) } var out bytes.Buffer if err := Plan(context.Background(), []string{"--config", pipelinePath, "--campaign", campaignPath, "--session", sessionPath}, &out); err != nil { t.Fatalf("Plan() error = %v", err) } got := out.String() if !strings.Contains(got, "prepare: skip") || !strings.Contains(got, "transcribe: skip") { t.Fatalf("output = %q, want prepare/transcribe skipped", got) } if !strings.Contains(got, "trim: run") { t.Fatalf("output = %q, want trim run", got) } if !strings.Contains(got, "totals: run=7 skip=2") { t.Fatalf("output = %q, want totals run=7 skip=2", got) } } func TestPlanFailsWhenConfiguredSecretsDirMissing(t *testing.T) { workspaceRoot := t.TempDir() configDir := t.TempDir() pipelinePath := filepath.Join(configDir, "pipeline.yml") campaignPath := writeAppTestCampaignConfig(t, configDir) sessionPath := filepath.Join(configDir, "session.yml") pipelineYAML := `workspace: root: ` + workspaceRoot + ` storage: backend: local secrets: env_dir: ./missing-secrets whisperx: transcribe_url: https://example.com/transcribe seriatim: binary: seriatim audita: binary: audita analyzer: timeout: 20m notification: timeout: 10s ` sessionYAML := `session_id: 2026-05-03 campaign: sample-campaign 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.yml: %v", err) } if err := os.WriteFile(sessionPath, []byte(sessionYAML), 0o644); err != nil { t.Fatalf("write session.yml: %v", err) } var out bytes.Buffer err := Plan(context.Background(), []string{"--config", pipelinePath, "--campaign", campaignPath, "--session", sessionPath}, &out) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), "read secrets env_dir") { t.Fatalf("error = %q, want secrets read error context", err.Error()) } } func assertDir(t *testing.T, path string) { t.Helper() info, err := os.Stat(path) if err != nil { t.Fatalf("Stat(%q) error = %v", path, err) } if !info.IsDir() { t.Fatalf("%q is not a directory", path) } }