test(FN-4546): complete Step 3 — add stale-active-branch reclaim regression tests
Fusion-Task-Id: FN-4546 Fusion-Task-Lineage: f10f8fcb-b26d-4ee8-b83e-b741de01983b
This commit is contained in:
@@ -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", () => {
|
describe("SelfHealingManager no-commits-expected audit", () => {
|
||||||
it("logs candidate task IDs without mutating tasks", async () => {
|
it("logs candidate task IDs without mutating tasks", async () => {
|
||||||
const store = createMockStore();
|
const store = createMockStore();
|
||||||
|
|||||||
Reference in New Issue
Block a user