From e09dc0512deb39f52b9069482a88532f47774687 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 20 May 2026 15:01:27 +0000 Subject: [PATCH] Add analyze integration coverage for previous-session inputs --- internal/stage/analyze_test.go | 91 ++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/internal/stage/analyze_test.go b/internal/stage/analyze_test.go index 9ee5044..1cee1b5 100644 --- a/internal/stage/analyze_test.go +++ b/internal/stage/analyze_test.go @@ -10,6 +10,7 @@ import ( "time" "gitea.maximumdirect.net/eric/narratio/internal/adapters/scriptorium" + "gitea.maximumdirect.net/eric/narratio/internal/adapters/storage" "gitea.maximumdirect.net/eric/narratio/internal/artifacts" "gitea.maximumdirect.net/eric/narratio/internal/config" "gitea.maximumdirect.net/eric/narratio/internal/manifest" @@ -791,6 +792,72 @@ func TestAnalyzeOmitsOptionalMissingCanonicalPreviousSessionArtifact(t *testing. } } +func TestAnalyzeRenderDebugWithCanonicalPreviousSessionInput(t *testing.T) { + env, m, fake := setupAnalyzeEnv(t) + paths := sessionPathsForEnv(env, m.SessionID) + writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "trimmed.json"), `{"segments":[]}`) + env.Config.Pipeline.Scriptorium.RenderDebug = true + + previousPath := artifacts.SessionPreviousArtifactPath(paths, "artifacts/session_recap.md") + writeAnalyzeFile(t, previousPath, "previous recap\n") + m.Inputs = append(m.Inputs, manifest.InputRecord{ + Kind: "previous_artifact", + Path: previousPath, + }) + + sessionRecap := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] + sessionRecap.Inputs["previous_recap"] = config.ScriptoriumInputConfig{ + Source: "narratio.previous_session.artifact.session_recap", + Required: true, + } + env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = sessionRecap + + _, err := (analyzeStage{}).Run(context.Background(), env, m) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + if len(fake.RenderRequests) != 1 || len(fake.RunRequests) != 1 { + t.Fatalf("render/run requests = %d/%d, want 1/1", len(fake.RenderRequests), len(fake.RunRequests)) + } + if got := fake.RenderRequests[0].InputPaths["previous_recap"]; got != previousPath { + t.Fatalf("render previous_recap input = %q, want %q", got, previousPath) + } + if got := fake.RunRequests[0].InputPaths["previous_recap"]; got != previousPath { + t.Fatalf("run previous_recap input = %q, want %q", got, previousPath) + } +} + +func TestAnalyzeDoesNotCallObjectStoreForCanonicalPreviousSessionInput(t *testing.T) { + env, m, _ := setupAnalyzeEnv(t) + paths := sessionPathsForEnv(env, m.SessionID) + writeAnalyzeFile(t, filepath.Join(paths.TranscriptsDir, "trimmed.json"), `{"segments":[]}`) + + previousPath := artifacts.SessionPreviousArtifactPath(paths, "artifacts/session_recap.md") + writeAnalyzeFile(t, previousPath, "previous recap\n") + m.Inputs = append(m.Inputs, manifest.InputRecord{ + Kind: "previous_artifact", + Path: previousPath, + }) + + sessionRecap := env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] + sessionRecap.Inputs["previous_recap"] = config.ScriptoriumInputConfig{ + Source: "narratio.previous_session.artifact.session_recap", + Required: true, + } + env.Config.Pipeline.Scriptorium.Artifacts["session_recap"] = sessionRecap + + tracker := &analyzeObjectStoreTracker{} + env.ObjectStore = tracker + + _, err := (analyzeStage{}).Run(context.Background(), env, m) + if err != nil { + t.Fatalf("Run() error = %v", err) + } + if tracker.called { + t.Fatal("analyze should not call object store for previous-session input resolution") + } +} + func TestAnalyzeFailsWhenOutputPathMissing(t *testing.T) { env, m, fake := setupAnalyzeEnv(t) paths := sessionPathsForEnv(env, m.SessionID) @@ -1219,3 +1286,27 @@ func mustArtifactEntryList(t *testing.T, metadata map[string]any, key string) [] } return out } + +type analyzeObjectStoreTracker struct { + called bool +} + +func (s *analyzeObjectStoreTracker) List(context.Context, string) ([]storage.ObjectInfo, error) { + s.called = true + return nil, errors.New("unexpected object store list call") +} + +func (s *analyzeObjectStoreTracker) Download(context.Context, string, string) error { + s.called = true + return errors.New("unexpected object store download call") +} + +func (s *analyzeObjectStoreTracker) Upload(context.Context, string, string, storage.UploadOptions) (storage.ObjectInfo, error) { + s.called = true + return storage.ObjectInfo{}, errors.New("unexpected object store upload call") +} + +func (s *analyzeObjectStoreTracker) Exists(context.Context, string) (bool, error) { + s.called = true + return false, errors.New("unexpected object store exists call") +}