test(FN-4538): add overlap-blocked regression coverage
Fusion-Task-Id: FN-4538 Fusion-Task-Lineage: 332b4c78-b67f-45d0-9181-0bb539b662c1
This commit is contained in:
@@ -1305,6 +1305,64 @@ describe("Scheduler", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("FN-4538 overlap blocker persistence", () => {
|
||||
it("FN-4538: overlap-blocked todo task with satisfied deps preserves overlapBlockedBy", async () => {
|
||||
vi.mocked(existsSync).mockReturnValue(true);
|
||||
vi.mocked(readFile).mockResolvedValue("# Task\nDo something");
|
||||
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-DEP", column: "done" }),
|
||||
createMockTask({ id: "FN-OVER", column: "in-progress" }),
|
||||
createMockTask({ id: "FN-T", column: "todo", dependencies: ["FN-DEP"] }),
|
||||
];
|
||||
|
||||
const store = createMockStore({
|
||||
listTasks: vi.fn().mockResolvedValue(tasks),
|
||||
getSettings: vi.fn().mockResolvedValue({ maxConcurrent: 2, maxWorktrees: 4, groupOverlappingFiles: true }),
|
||||
parseFileScopeFromPrompt: vi.fn(async (taskId: string) => {
|
||||
if (taskId === "FN-OVER" || taskId === "FN-T") return ["packages/core/src/store.ts"];
|
||||
return ["packages/core/src/types.ts"];
|
||||
}),
|
||||
updateTask: vi.fn().mockResolvedValue(undefined),
|
||||
moveTask: vi.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
|
||||
const scheduler = new Scheduler(store);
|
||||
(scheduler as any).running = true;
|
||||
await scheduler.schedule();
|
||||
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-T", {
|
||||
status: "queued",
|
||||
blockedBy: null,
|
||||
overlapBlockedBy: "FN-OVER",
|
||||
});
|
||||
});
|
||||
|
||||
it("FN-4538: overlapBlockedBy cleared when overlap resolves", async () => {
|
||||
vi.mocked(existsSync).mockReturnValue(true);
|
||||
vi.mocked(readFile).mockResolvedValue("# Task\nDo something");
|
||||
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-DEP", column: "done" }),
|
||||
createMockTask({ id: "FN-T", column: "todo", dependencies: ["FN-DEP"], status: "queued", overlapBlockedBy: "FN-OVER" }),
|
||||
];
|
||||
|
||||
const store = createMockStore({
|
||||
listTasks: vi.fn().mockResolvedValue(tasks),
|
||||
getSettings: vi.fn().mockResolvedValue({ maxConcurrent: 2, maxWorktrees: 4, groupOverlappingFiles: true }),
|
||||
parseFileScopeFromPrompt: vi.fn(async () => ["packages/core/src/types.ts"]),
|
||||
updateTask: vi.fn().mockResolvedValue(undefined),
|
||||
moveTask: vi.fn().mockResolvedValue(undefined),
|
||||
});
|
||||
|
||||
const scheduler = new Scheduler(store);
|
||||
(scheduler as any).running = true;
|
||||
await scheduler.schedule();
|
||||
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-T", { overlapBlockedBy: null });
|
||||
});
|
||||
});
|
||||
|
||||
describe("blockedBy stability — FN-3899", () => {
|
||||
it("preserves a still-valid queued blocker instead of repointing to another active task", async () => {
|
||||
vi.mocked(existsSync).mockReturnValue(true);
|
||||
|
||||
@@ -5373,6 +5373,67 @@ describe("clearStaleBlockedBy", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("FN-4538 overlapBlockedBy self-healing", () => {
|
||||
function makeTask(id: string, overrides: Record<string, unknown> = {}) {
|
||||
return { id, column: "todo", paused: false, blockedBy: null, dependencies: [], mergeRetries: 0, ...overrides };
|
||||
}
|
||||
|
||||
function makeStore(tasks: Record<string, unknown>[]) {
|
||||
const store = createMockStore({
|
||||
getSettings: vi.fn().mockResolvedValue({
|
||||
autoUnpauseEnabled: false,
|
||||
maintenanceIntervalMs: 0,
|
||||
globalPause: false,
|
||||
enginePaused: false,
|
||||
} as unknown as Settings),
|
||||
});
|
||||
(store.listTasks as ReturnType<typeof vi.fn>).mockImplementation(async (options?: { column?: string }) => {
|
||||
if (options?.column === "todo") return tasks.filter((task) => task.column === "todo");
|
||||
if (options?.column === "in-progress") return tasks.filter((task) => task.column === "in-progress");
|
||||
if (options?.column === "in-review") return tasks.filter((task) => task.column === "in-review");
|
||||
return tasks;
|
||||
});
|
||||
return store;
|
||||
}
|
||||
|
||||
it("FN-4538: clearStaleBlockedBy does NOT clear queued status when overlapBlockedBy is active", async () => {
|
||||
const overlapBlocker = makeTask("FN-ACTIVE", { column: "in-progress" });
|
||||
const target = makeTask("FN-TARGET", {
|
||||
column: "todo",
|
||||
status: "queued",
|
||||
blockedBy: undefined,
|
||||
overlapBlockedBy: "FN-ACTIVE",
|
||||
dependencies: [],
|
||||
});
|
||||
const store = makeStore([target, overlapBlocker]);
|
||||
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
|
||||
|
||||
await manager.clearStaleBlockedBy();
|
||||
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-TARGET", { blockedBy: null, status: "queued" });
|
||||
expect(store.updateTask).not.toHaveBeenCalledWith("FN-TARGET", expect.objectContaining({ status: null }));
|
||||
manager.stop();
|
||||
});
|
||||
|
||||
it("FN-4538: clearStaleBlockedBy clears overlapBlockedBy when overlap blocker is done", async () => {
|
||||
const overlapBlocker = makeTask("FN-DONE", { column: "done" });
|
||||
const target = makeTask("FN-TARGET", {
|
||||
column: "todo",
|
||||
status: "queued",
|
||||
blockedBy: undefined,
|
||||
overlapBlockedBy: "FN-DONE",
|
||||
dependencies: [],
|
||||
});
|
||||
const store = makeStore([target, overlapBlocker]);
|
||||
const manager = new SelfHealingManager(store, { rootDir: "/tmp/test-project" });
|
||||
|
||||
await manager.clearStaleBlockedBy();
|
||||
|
||||
expect(store.updateTask).toHaveBeenCalledWith("FN-TARGET", { blockedBy: null, overlapBlockedBy: null, status: null });
|
||||
manager.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("stale triage processing eviction before recovery", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
Reference in New Issue
Block a user