feat(FN-4625): complete Step 4 — add self-healing worktrunk pause guardrail
Fusion-Task-Id: FN-4625 Fusion-Task-Lineage: c1d9b414-b5dd-4786-ba51-0b8adec4acf5
This commit is contained in:
committed by
gsxdsm
parent
743b7a480d
commit
3339d94d42
@@ -0,0 +1,60 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { EventEmitter } from "node:events";
|
||||
import type { Task, TaskStore } from "@fusion/core";
|
||||
import { SelfHealingManager } from "../../self-healing.js";
|
||||
|
||||
vi.mock("../../worktree-pool.js", async () => {
|
||||
const actual = await vi.importActual<any>("../../worktree-pool.js");
|
||||
return { ...actual, isUsableTaskWorktree: vi.fn().mockResolvedValue(true) };
|
||||
});
|
||||
|
||||
function makeTask(overrides: Partial<Task> = {}): Task {
|
||||
return {
|
||||
id: "FN-4625",
|
||||
title: "FN-4625",
|
||||
description: "task",
|
||||
column: "in-progress",
|
||||
dependencies: [],
|
||||
steps: [],
|
||||
currentStep: 0,
|
||||
branch: "fusion/fn-4625",
|
||||
worktree: "/tmp/fn-4625",
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
...overrides,
|
||||
} as Task;
|
||||
}
|
||||
|
||||
function makeStore(tasks: Task[]): TaskStore & EventEmitter {
|
||||
const emitter = new EventEmitter();
|
||||
return Object.assign(emitter, {
|
||||
getSettings: vi.fn(async () => ({ maintenanceIntervalMs: 0, globalPause: false, enginePaused: false })),
|
||||
listTasks: vi.fn(async ({ column, includeArchived }: any = {}) => tasks.filter((task) => {
|
||||
if (!includeArchived && task.column === "archived") return false;
|
||||
return !column || task.column === column;
|
||||
})),
|
||||
updateTask: vi.fn(async () => undefined),
|
||||
logEntry: vi.fn(async () => undefined),
|
||||
getTask: vi.fn(async () => tasks[0]),
|
||||
}) as unknown as TaskStore & EventEmitter;
|
||||
}
|
||||
|
||||
describe("reliability interactions: worktrunk failure", () => {
|
||||
it("self-healing skips reclaim for tasks paused by worktrunk failures", async () => {
|
||||
const store = makeStore([
|
||||
makeTask({ paused: true, pausedReason: "worktrunk_operation_failed" }),
|
||||
]);
|
||||
|
||||
const manager = new SelfHealingManager(store, {
|
||||
rootDir: process.cwd(),
|
||||
getExecutingTaskIds: () => new Set(),
|
||||
});
|
||||
|
||||
const inspectSpy = vi.spyOn(manager as any, "inspectOrphanedBranch");
|
||||
const recovered = await manager.reclaimStaleActiveBranches();
|
||||
|
||||
expect(recovered).toBe(0);
|
||||
expect(inspectSpy).not.toHaveBeenCalled();
|
||||
expect(store.updateTask).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,7 @@ const task = {
|
||||
|
||||
const makeStore = () => ({
|
||||
updateTask: vi.fn().mockResolvedValue(undefined),
|
||||
pauseTask: vi.fn().mockResolvedValue(undefined),
|
||||
logEntry: vi.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
|
||||
@@ -111,7 +112,7 @@ describe("acquireTaskWorktree worktrunk wiring", () => {
|
||||
).rejects.toMatchObject({ code: "worktrunk_operation_failed", operation: "create" });
|
||||
|
||||
expect(execMock.mock.calls.some((call) => String(call[0]).includes('"worktrunk" "switch" "--create" "fusion/fn-1"'))).toBe(true);
|
||||
expect(events.some((event) => event.type === "worktree:worktrunk-fallback")).toBe(false);
|
||||
expect(events.some((event) => event.type === "worktree:worktrunk-fallback-native")).toBe(false);
|
||||
});
|
||||
|
||||
it("falls back to native when onFailure=fallback-native", async () => {
|
||||
@@ -138,7 +139,7 @@ describe("acquireTaskWorktree worktrunk wiring", () => {
|
||||
|
||||
expect(execMock.mock.calls.some((call) => String(call[0]).includes('"worktrunk" "switch" "--create" "fusion/fn-1"'))).toBe(true);
|
||||
expect(execMock.mock.calls.some((call) => String(call[0]).includes("git worktree add -b"))).toBe(true);
|
||||
expect(events.filter((event) => event.type === "worktree:worktrunk-fallback")).toHaveLength(1);
|
||||
expect(events.filter((event) => event.type === "worktree:worktrunk-fallback-native")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("fails with binary missing when enabled and binaryPath absent", async () => {
|
||||
|
||||
Reference in New Issue
Block a user