From 84f68497a13ac5c50d640054c56ca9e75987920f Mon Sep 17 00:00:00 2001 From: Fusion Date: Thu, 14 May 2026 20:29:13 -0700 Subject: [PATCH] =?UTF-8?q?test(FN-4546):=20complete=20Step=203=20?= =?UTF-8?q?=E2=80=94=20add=20stale-active-branch=20reclaim=20regression=20?= =?UTF-8?q?tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fusion-Task-Id: FN-4546 Fusion-Task-Lineage: f10f8fcb-b26d-4ee8-b83e-b741de01983b --- .../engine/src/__tests__/self-healing.test.ts | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/packages/engine/src/__tests__/self-healing.test.ts b/packages/engine/src/__tests__/self-healing.test.ts index 1a3969aee..b648d8773 100644 --- a/packages/engine/src/__tests__/self-healing.test.ts +++ b/packages/engine/src/__tests__/self-healing.test.ts @@ -6173,6 +6173,117 @@ describe("SelfHealingManager reclaimSelfOwnedBranchConflicts", () => { }); }); +describe("SelfHealingManager reclaimStaleActiveBranches (FN-4546)", () => { + let store: TaskStore & EventEmitter; + let manager: SelfHealingManager; + + beforeEach(() => { + mockedExecSync.mockReset(); + store = createMockStore({ + getSettings: vi.fn().mockResolvedValue({ globalPause: false, enginePaused: false } as any), + recordRunAuditEvent: vi.fn().mockResolvedValue(undefined), + listTasks: vi.fn().mockResolvedValue([]), + }); + manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" }); + mockedIsUsableTaskWorktree.mockResolvedValue(true); + }); + + it("reclaims subsumed fusion task branch with no worktree", async () => { + (store.listTasks as any).mockResolvedValueOnce([ + { id: "FN-1001", column: "todo", checkedOutBy: null, userPaused: false, worktree: null, branch: null, lineageId: "lin-1" }, + ]); + + mockedExecSync.mockImplementation((command: string) => { + if (command.includes("git branch --list 'fusion/*'")) return Buffer.from(" fusion/fn-1001\n"); + if (command.includes("git rev-parse --verify") && command.includes("fusion/fn-1001")) return Buffer.from("abc123def456\n"); + if (command.includes("git rev-list --count") && command.includes("fusion/fn-1001")) return Buffer.from("0\n"); + return Buffer.from(""); + }); + + const recovered = await manager.reclaimStaleActiveBranches(); + + expect(recovered).toBe(1); + expect(mockedExecSync).toHaveBeenCalledWith(expect.stringContaining("git branch -D \"fusion/fn-1001\""), expect.anything()); + expect(mockedExecSync).toHaveBeenCalledWith(expect.stringContaining("git worktree prune"), expect.anything()); + expect(store.updateTask).toHaveBeenCalledWith("FN-1001", { worktree: null, branch: null, baseCommitSha: null }); + expect((store as any).recordRunAuditEvent).toHaveBeenCalledWith(expect.objectContaining({ + domain: "git", + mutationType: "branch:stale-active-reclaim", + target: "fusion/fn-1001", + })); + }); + + it("does not delete branch with unique commits", async () => { + (store.listTasks as any).mockResolvedValueOnce([ + { id: "FN-1001", column: "todo", checkedOutBy: null, userPaused: false, worktree: null, branch: null }, + ]); + + mockedExecSync.mockImplementation((command: string) => { + if (command.includes("git branch --list 'fusion/*'")) return Buffer.from(" fusion/fn-1001\n"); + if (command.includes("git rev-parse --verify") && command.includes("fusion/fn-1001")) return Buffer.from("abc123def456\n"); + if (command.includes("git rev-list --count") && command.includes("fusion/fn-1001")) return Buffer.from("3\n"); + if (command.includes("git log --format=%s")) return Buffer.from("feat: keep me\n"); + return Buffer.from(""); + }); + + const recovered = await manager.reclaimStaleActiveBranches(); + + expect(recovered).toBe(0); + expect(mockedExecSync).not.toHaveBeenCalledWith(expect.stringContaining("git branch -D \"fusion/fn-1001\""), expect.anything()); + expect(getSelfHealingLogger().warn).toHaveBeenCalledWith(expect.stringContaining("stale-active-branch-rescue-needed FN-1001")); + }); + + it("skips task with active heartbeat run", async () => { + const agentStore = { + listActiveHeartbeatRuns: vi.fn().mockResolvedValue([{ startedAt: new Date().toISOString(), contextSnapshot: { taskId: "FN-1001" } }]), + } as any; + manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project", agentStore }); + (store.listTasks as any).mockResolvedValueOnce([ + { id: "FN-1001", column: "todo", checkedOutBy: null, userPaused: false, worktree: null, branch: null }, + ]); + mockedExecSync.mockImplementation((command: string) => { + if (command.includes("git branch --list 'fusion/*'")) return Buffer.from(" fusion/fn-1001\n"); + return Buffer.from(""); + }); + + const recovered = await manager.reclaimStaleActiveBranches(); + + expect(recovered).toBe(0); + expect(mockedExecSync).not.toHaveBeenCalledWith(expect.stringContaining("git branch -D \"fusion/fn-1001\""), expect.anything()); + }); + + it("skips task with usable worktree", async () => { + (store.listTasks as any).mockResolvedValueOnce([ + { id: "FN-1001", column: "todo", checkedOutBy: null, userPaused: false, worktree: "/tmp/fn-1001", branch: null }, + ]); + mockedIsUsableTaskWorktree.mockResolvedValueOnce(true); + mockedExecSync.mockImplementation((command: string) => { + if (command.includes("git branch --list 'fusion/*'")) return Buffer.from(" fusion/fn-1001\n"); + return Buffer.from(""); + }); + + const recovered = await manager.reclaimStaleActiveBranches(); + + expect(recovered).toBe(0); + expect(mockedExecSync).not.toHaveBeenCalledWith(expect.stringContaining("git branch -D \"fusion/fn-1001\""), expect.anything()); + }); + + it("skips user-paused task", async () => { + (store.listTasks as any).mockResolvedValueOnce([ + { id: "FN-1001", column: "todo", checkedOutBy: null, userPaused: true, worktree: null, branch: null }, + ]); + mockedExecSync.mockImplementation((command: string) => { + if (command.includes("git branch --list 'fusion/*'")) return Buffer.from(" fusion/fn-1001\n"); + return Buffer.from(""); + }); + + const recovered = await manager.reclaimStaleActiveBranches(); + + expect(recovered).toBe(0); + expect(mockedExecSync).not.toHaveBeenCalledWith(expect.stringContaining("git branch -D \"fusion/fn-1001\""), expect.anything()); + }); +}); + describe("SelfHealingManager no-commits-expected audit", () => { it("logs candidate task IDs without mutating tasks", async () => { const store = createMockStore();