From 958f446387f7310e3a0584e5237a16e9536e66d6 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Tue, 19 May 2026 19:12:10 -0500 Subject: [PATCH] Add archive stage integration test for the new analyze stage features --- internal/app/runner_test.go | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/internal/app/runner_test.go b/internal/app/runner_test.go index 33d8074..c0ff1c8 100644 --- a/internal/app/runner_test.go +++ b/internal/app/runner_test.go @@ -3,6 +3,7 @@ package app import ( "context" "errors" + "fmt" "os" "path/filepath" "strings" @@ -70,6 +71,49 @@ func (s analyzeOutputStage) Run(_ context.Context, _ *stage.Env, _ *manifest.Man }, nil } +type selectedAnalyzeArtifactStage struct { + expected []string +} + +func (s selectedAnalyzeArtifactStage) Name() string { return "analyze" } +func (s selectedAnalyzeArtifactStage) Declares() stage.IODecl { return stage.IODecl{} } +func (s selectedAnalyzeArtifactStage) Run(_ context.Context, env *stage.Env, m *manifest.Manifest) (*stage.StageResult, error) { + if len(env.SelectedAnalyzeArtifacts) != len(s.expected) { + return nil, fmt.Errorf("selected artifacts len = %d, want %d", len(env.SelectedAnalyzeArtifacts), len(s.expected)) + } + for i := range s.expected { + if env.SelectedAnalyzeArtifacts[i] != s.expected[i] { + return nil, fmt.Errorf("selected artifacts[%d] = %q, want %q", i, env.SelectedAnalyzeArtifacts[i], s.expected[i]) + } + } + + outputPath := filepath.Join( + artifacts.SessionWorkDirForCampaign(env.Config.Pipeline.Workspace.Root, env.Config.Session.Campaign, m.SessionID), + "artifacts", + "player_handout.md", + ) + if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil { + return nil, fmt.Errorf("mkdir artifact dir: %w", err) + } + if err := os.WriteFile(outputPath, []byte("player handout\n"), 0o644); err != nil { + return nil, fmt.Errorf("write player handout: %w", err) + } + + return &stage.StageResult{ + Outputs: []artifacts.Ref{ + { + Kind: "player_handout", + Category: "artifacts", + RelativePath: "artifacts/player_handout.md", + AbsolutePath: outputPath, + }, + }, + Metadata: map[string]any{ + "stage": "analyze", + }, + }, nil +} + func TestExecuteStagesPropagatesSelectedArtifactsToEnv(t *testing.T) { cfg := testConfig(t) @@ -155,6 +199,59 @@ func TestExecuteStagesAnalyzeOutputsPersistAsScriptoriumArtifacts(t *testing.T) } } +func TestExecuteStagesArchiveFailsWhenRequiredRecapPromotionMissingForSelectedArtifacts(t *testing.T) { + cfg := testConfig(t) + cfg.Pipeline.Storage.S3 = &config.StorageS3Config{ + Bucket: "my-dnd-archive", + RootPrefix: "dnd", + } + cfg.Pipeline.Archive = &config.ArchiveConfig{ + Enabled: boolPtr(true), + UploadRun: boolPtr(true), + PromoteArtifacts: []config.ArchivePromotionRule{ + {From: "artifacts/session_recap.md", To: "artifacts/session_recap.md", Required: boolPtr(true)}, + }, + } + + store := &manifest.LocalStore{} + manifestPath := manifestPathFor(cfg) + seed := manifest.New(cfg.Session.SessionID, time.Now().UTC()) + seed.Campaign = cfg.Session.Campaign + for _, stageName := range []string{"prepare", "transcribe", "merge", "polish", "normalize", "trim"} { + seed.MarkStageSucceeded(stageName, time.Now().UTC(), nil) + } + if err := os.MkdirAll(filepath.Dir(manifestPath), 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + if err := store.Save(context.Background(), manifestPath, seed); err != nil { + t.Fatalf("Save manifest error = %v", err) + } + + archiveStageImpl, err := stage.Select("archive") + if err != nil { + t.Fatalf("Select(archive) error = %v", err) + } + + _, err = executeStages( + context.Background(), + cfg, + []stage.Stage{ + selectedAnalyzeArtifactStage{expected: []string{"player_handout"}}, + archiveStageImpl, + }, + RunOptions{ + SelectedArtifacts: []string{"player_handout"}, + Env: &Env{ObjectStore: &storage.FakeBackend{}}, + }, + ) + if err == nil { + t.Fatal("expected archive promotion failure, got nil") + } + if !strings.Contains(err.Error(), "required promotion source missing") { + t.Fatalf("error = %q, want required promotion source missing", err.Error()) + } +} + func TestExecuteStagesPlaceholderSuccessUpdatesManifest(t *testing.T) { cfg := testConfig(t)