package app import ( "bytes" "context" "os" "path/filepath" "strings" "testing" ) func TestPlanUsesDiscoveredSessionTemplateWithSessionIDs(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot) sessionTemplate := `session_id: "{{ session_id }}" previous_session_id: "{{ previous_session_id }}" campaign: sample-campaign inputs: audio_dir: ./audio speakers_file: ./speakers.yml autocorrect_file: ./autocorrect.yml glossary_file: ./glossary.yml ` if err := os.WriteFile(sessionPath, []byte(sessionTemplate), 0o644); err != nil { t.Fatalf("write session template: %v", err) } cwd := filepath.Dir(sessionPath) originalWD, err := os.Getwd() if err != nil { t.Fatalf("Getwd(): %v", err) } if err := os.Chdir(cwd); err != nil { t.Fatalf("Chdir(%q): %v", cwd, err) } t.Cleanup(func() { _ = os.Chdir(originalWD) }) var out bytes.Buffer if err := Plan(context.Background(), []string{ "--config", pipelinePath, "--session-id", "2026-04-04", "--previous-session-id", "2026-03-28", }, &out); err != nil { t.Fatalf("Plan() error = %v", err) } if !strings.Contains(out.String(), "narratio plan: workdir prepared") { t.Fatalf("output = %q, want plan output", out.String()) } } func TestPlanFailsWhenSessionIDMismatchesConcreteSession(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot) var out bytes.Buffer err := Plan(context.Background(), []string{"--config", pipelinePath, "--session", sessionPath, "--session-id", "2026-04-04"}, &out) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), "session_id mismatch") { t.Fatalf("error = %q, want mismatch context", err.Error()) } } func TestPlanFailsWhenPreviousSessionIDMismatchesConcreteSession(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot) sessionYAML := `session_id: 2026-05-03 previous_session_id: 2026-04-26 campaign: sample-campaign inputs: audio_dir: ./audio speakers_file: ./speakers.yml autocorrect_file: ./autocorrect.yml glossary_file: ./glossary.yml ` 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, "--session", sessionPath, "--session-id", "2026-05-03", "--previous-session-id", "2026-04-25", }, &out) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), "previous_session_id mismatch") { t.Fatalf("error = %q, want mismatch context", err.Error()) } } func TestRunStageAcceptsSessionIDFlagAndParsesStageName(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot) var out bytes.Buffer err := RunStage(context.Background(), []string{"--config", pipelinePath, "--session", sessionPath, "--session-id", "2026-05-03", "prepare"}, &out) if err != nil { t.Fatalf("RunStage() error = %v", err) } if !strings.Contains(out.String(), "stage=prepare") { t.Fatalf("output = %q, want stage output", out.String()) } } func TestResolveSessionConfigPathErrorIncludesSearchedPaths(t *testing.T) { _, err := resolveSessionConfigPathWithCandidates("", []string{"./session.yml", "/usr/local/etc/narratio/session.yml", "/etc/narratio/session.yml"}) if err == nil { t.Fatal("expected error, got nil") } if !strings.Contains(err.Error(), "searched") { t.Fatalf("error = %q, want searched paths", err.Error()) } if !strings.Contains(err.Error(), "pass --session") { t.Fatalf("error = %q, want explicit-session guidance", err.Error()) } }