| 1 | package boot |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | |
| 8 | "reasonix/internal/agent" |
| 9 | "reasonix/internal/tool" |
| 10 | ) |
| 11 | |
| 12 | func prepareRunningSubagent(t *testing.T, sessionDir string) string { |
| 13 | t.Helper() |
| 14 | store := agent.NewSubagentStore(filepath.Join(sessionDir, "subagents")) |
| 15 | spec := agent.SubagentSpec{ |
| 16 | Kind: "task", |
| 17 | Name: "task", |
| 18 | WorkspaceRoot: t.TempDir(), |
| 19 | ParentSession: "parent-session", |
| 20 | SystemPrompt: "sys", |
| 21 | Registry: tool.NewRegistry(), |
| 22 | Model: "base-model", |
| 23 | } |
| 24 | run, err := store.PrepareFresh(spec) |
| 25 | if err != nil { |
| 26 | t.Fatalf("PrepareFresh: %v", err) |
| 27 | } |
| 28 | if err := store.MarkRunning(run); err != nil { |
| 29 | t.Fatalf("MarkRunning: %v", err) |
| 30 | } |
| 31 | ref := run.Ref |
| 32 | run.Release() |
| 33 | return ref |
| 34 | } |
| 35 | |
| 36 | func requireSubagentStatus(t *testing.T, sessionDir, ref string, want agent.SubagentStatus) { |
| 37 | t.Helper() |
| 38 | meta, err := agent.NewSubagentStore(filepath.Join(sessionDir, "subagents")).LoadMeta(ref) |
| 39 | if err != nil { |
| 40 | t.Fatalf("LoadMeta(%s): %v", ref, err) |
| 41 | } |
| 42 | if meta.Status != want { |
| 43 | t.Fatalf("status = %q, want %q", meta.Status, want) |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | func TestNewSubagentStoreCleansStaleRunningOnEveryBuild(t *testing.T) { |
| 48 | sessionDir := t.TempDir() |
| 49 | |
| 50 | firstRef := prepareRunningSubagent(t, sessionDir) |
| 51 | if _, err := newSubagentStore(sessionDir, nil); err != nil { |
| 52 | t.Fatalf("newSubagentStore (first): %v", err) |
| 53 | } |
| 54 | requireSubagentStatus(t, sessionDir, firstRef, agent.SubagentInterrupted) |
| 55 | |
| 56 | secondRef := prepareRunningSubagent(t, sessionDir) |
| 57 | if _, err := newSubagentStore(sessionDir, nil); err != nil { |
| 58 | t.Fatalf("newSubagentStore (second): %v", err) |
| 59 | } |
| 60 | requireSubagentStatus(t, sessionDir, secondRef, agent.SubagentInterrupted) |
| 61 | } |
| 62 | |
| 63 | func TestNewSubagentStoreParentProbeDefersThenRecovers(t *testing.T) { |
| 64 | sessionDir := t.TempDir() |
| 65 | ref := prepareRunningSubagent(t, sessionDir) |
| 66 | parentPath := filepath.Join(sessionDir, "parent-session.jsonl") |
| 67 | |
| 68 | if _, err := newSubagentStore(sessionDir, func(path string) bool { |
| 69 | return filepath.Clean(path) == filepath.Clean(parentPath) |
| 70 | }); err != nil { |
| 71 | t.Fatalf("newSubagentStore (live parent): %v", err) |
| 72 | } |
| 73 | requireSubagentStatus(t, sessionDir, ref, agent.SubagentRunning) |
| 74 | |
| 75 | if _, err := newSubagentStore(sessionDir, func(string) bool { return false }); err != nil { |
| 76 | t.Fatalf("newSubagentStore (dead parent): %v", err) |
| 77 | } |
| 78 | requireSubagentStatus(t, sessionDir, ref, agent.SubagentInterrupted) |
| 79 | } |
| 80 | |
| 81 | func TestNewSubagentStoreNeverCachesCleanupError(t *testing.T) { |
| 82 | sessionDir := t.TempDir() |
| 83 | if err := os.WriteFile(filepath.Join(sessionDir, "subagents"), []byte("not a directory"), 0o600); err != nil { |
| 84 | t.Fatalf("WriteFile: %v", err) |
| 85 | } |
| 86 | |
| 87 | for attempt := 1; attempt <= 2; attempt++ { |
| 88 | if _, err := newSubagentStore(sessionDir, nil); err == nil { |
| 89 | t.Fatalf("newSubagentStore attempt %d unexpectedly succeeded", attempt) |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 |