package app import ( "bytes" "os" "path/filepath" "strings" "testing" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" ) func TestExecuteRestoreHelp(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer code := Execute([]string{"restore", "--help"}, &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()) } out := stdout.String() if !strings.Contains(out, "Usage: narratio restore") { t.Fatalf("stdout = %q, want restore usage", out) } if !strings.Contains(out, "--include-audio") { t.Fatalf("stdout = %q, want --include-audio flag", out) } } func TestExecuteRestoreRecognizedAndReturnsNYI(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot) var stdout bytes.Buffer var stderr bytes.Buffer code := Execute( []string{ "restore", "--config", pipelinePath, "--session", sessionPath, "--session-id", "2026-05-03", "--dry-run", "--force", "--include-audio", }, &stdout, &stderr, ) if code == 0 { t.Fatal("exit code = 0, want non-zero (phase 2 NYI)") } errText := stderr.String() if !strings.Contains(errText, "restore: not yet implemented (phase 3: remote current-state discovery)") { t.Fatalf("stderr = %q, want NYI error", errText) } if strings.Contains(errText, "unknown command") { t.Fatalf("stderr = %q, restore should be recognized command", errText) } manifestPath := artifacts.SessionManifestPathForCampaign(workspaceRoot, "sample-campaign", "2026-05-03") if _, err := os.Stat(manifestPath); !os.IsNotExist(err) { t.Fatalf("manifest should not be created during phase-2 restore preflight; stat err=%v", err) } } func TestExecuteRestoreRejectsUnexpectedPositionalArguments(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot) var stdout bytes.Buffer var stderr bytes.Buffer code := Execute([]string{"restore", "--config", pipelinePath, "--session", sessionPath, "extra"}, &stdout, &stderr) if code == 0 { t.Fatal("exit code = 0, want non-zero") } if !strings.Contains(stderr.String(), "restore: unexpected positional arguments") { t.Fatalf("stderr = %q, want positional-args failure", stderr.String()) } } func TestExecuteRestoreFailsWhenStorageBackendNotConfigured(t *testing.T) { workspaceRoot := t.TempDir() pipelinePath, sessionPath := writeRestoreConfigWithoutStorage(t, workspaceRoot) var stdout bytes.Buffer var stderr bytes.Buffer code := Execute([]string{"restore", "--config", pipelinePath, "--session", sessionPath}, &stdout, &stderr) if code == 0 { t.Fatal("exit code = 0, want non-zero") } if !strings.Contains(stderr.String(), "no remote object store backend is configured") { t.Fatalf("stderr = %q, want storage backend preflight failure", stderr.String()) } } func writeRestoreConfigWithoutStorage(t *testing.T, workspaceRoot string) (string, string) { t.Helper() dir := t.TempDir() pipelinePath := filepath.Join(dir, "pipeline.yml") sessionPath := filepath.Join(dir, "session.yml") pipelineYAML := `workspace: root: ` + workspaceRoot + ` whisperx: transcribe_url: https://example.com/transcribe ` 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 config: %v", err) } if err := os.WriteFile(sessionPath, []byte(sessionYAML), 0o644); err != nil { t.Fatalf("write session config: %v", err) } mustWriteTestFile(t, filepath.Join(dir, "speakers.yml"), "alice: alice.flac\n") mustWriteTestFile(t, filepath.Join(dir, "autocorrect.yml"), "[]\n") mustWriteTestFile(t, filepath.Join(dir, "glossary.yml"), "[]\n") mustWriteTestFile(t, filepath.Join(dir, "audio", "alice.flac"), "audio-bytes") return pipelinePath, sessionPath }