test(FN-4818): complete Step 2 — add owning-node handoff interaction coverage

Fusion-Task-Id: FN-4818
Fusion-Task-Lineage: 1619844a-3945-4681-af58-ca32e5533514
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 20:20:18 -07:00
committed by gsxdsm
parent 003b343b4e
commit 5c5b922d79

View File

@@ -0,0 +1,265 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { existsSync, mkdtempSync } from "node:fs";
import { readFile } from "node:fs/promises";
import { rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import type { NodeStatus, OwningNodeHandoffPolicy, Task, TaskStore } from "@fusion/core";
import { TaskStore as CoreTaskStore } from "@fusion/core";
import { MeshLeaseManager } from "../../mesh-lease-manager.js";
import { Scheduler } from "../../scheduler.js";
vi.mock("node:fs", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs")>();
return {
...actual,
existsSync: vi.fn(),
};
});
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
return {
...actual,
readFile: vi.fn(),
};
});
function makeTmpDir(): string {
return mkdtempSync(join(tmpdir(), "fn-owning-node-handoff-"));
}
function createMockTask(overrides: Partial<Task> = {}): Task {
return {
id: "FN-200",
description: "scheduler handoff",
column: "todo",
dependencies: [],
steps: [],
currentStep: 0,
log: [],
createdAt: "2026-01-01T00:00:00Z",
updatedAt: "2026-01-01T00:00:00Z",
prompt: "",
...overrides,
} as Task;
}
function createMockStore(task: Task, settings: Record<string, unknown> = {}): TaskStore {
return {
listTasks: vi.fn().mockResolvedValue([task]),
getSettings: vi.fn().mockResolvedValue(settings),
getTask: vi.fn().mockResolvedValue(task),
updateTask: vi.fn().mockResolvedValue(undefined),
moveTask: vi.fn().mockResolvedValue(undefined),
parseFileScopeFromPrompt: vi.fn().mockResolvedValue([]),
logEntry: vi.fn().mockResolvedValue(undefined),
getRootDir: vi.fn().mockReturnValue("/tmp/test"),
getTasksDir: vi.fn().mockReturnValue("/tmp/test/.fusion/tasks"),
on: vi.fn(),
off: vi.fn(),
} as unknown as TaskStore;
}
function createMockHealthMonitor(statusMap: Record<string, NodeStatus | undefined>) {
return {
getNodeHealth: vi.fn((id: string) => statusMap[id]),
} as unknown as import("../node-health-monitor.js").NodeHealthMonitor;
}
describe("reliability interactions: owning-node unavailable handoff", () => {
let rootDir = "";
let globalDir = "";
let taskStore: CoreTaskStore;
beforeEach(async () => {
vi.mocked(existsSync).mockReturnValue(true);
vi.mocked(readFile).mockResolvedValue("# Task\nFN-4813");
rootDir = makeTmpDir();
globalDir = join(rootDir, ".fusion-global");
taskStore = new CoreTaskStore(rootDir, globalDir);
await taskStore.init();
});
afterEach(async () => {
taskStore?.close();
await rm(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 });
});
async function seedCheckedOutTask(overrides: Partial<Task> = {}): Promise<Task> {
const created = await taskStore.createTask({ description: "FN-4813 owning-node handoff" });
return taskStore.updateTask(created.id, {
column: "todo",
checkedOutBy: "agent-1",
checkedOutAt: new Date().toISOString(),
checkoutNodeId: "node-a",
checkoutRunId: "run-a-1",
checkoutLeaseRenewedAt: new Date().toISOString(),
checkoutLeaseEpoch: 1,
...overrides,
});
}
it.each(["block", "reassign-to-local", "reassign-any-healthy"] as const)(
"FN-4813: owner online never auto-steals lease (policy=%s)",
async (policy) => {
const task = await seedCheckedOutTask({
checkedOutAt: new Date().toISOString(),
checkoutLeaseRenewedAt: new Date().toISOString(),
});
const manager = new MeshLeaseManager({
taskStore,
nodeHealthMonitor: { getNodeHealth: () => "online" } as any,
getHandoffPolicy: async () => policy,
localNodeId: "node-local",
});
const recovered = await manager.recoverAbandonedLease(task.id, "scheduler detected stale todo lease");
expect(recovered).toBe(false);
const persisted = await taskStore.getTask(task.id);
expect(persisted?.checkedOutBy).toBe("agent-1");
expect(persisted?.checkoutNodeId).toBe("node-a");
expect(persisted?.checkoutLeaseEpoch).toBe(1);
},
);
it("FN-4813: owner offline + block policy parks lease", async () => {
const task = await seedCheckedOutTask();
const manager = new MeshLeaseManager({
taskStore,
nodeHealthMonitor: { getNodeHealth: () => "offline" } as any,
getHandoffPolicy: async () => "block",
localNodeId: "node-local",
});
const recovered = await manager.recoverAbandonedLease(task.id, "scheduler detected stale todo lease");
expect(recovered).toBe(false);
const persisted = await taskStore.getTask(task.id);
expect(persisted?.checkedOutBy).toBe("agent-1");
expect(persisted?.checkoutNodeId).toBe("node-a");
expect(persisted?.checkoutLeaseEpoch).toBe(1);
expect(persisted?.checkoutRunId).toBe("run-a-1");
});
it.each(["reassign-to-local", "reassign-any-healthy"] as const)(
"FN-4813: owner offline + policy=%s clears lease with epoch bump",
async (policy) => {
const task = await seedCheckedOutTask({ checkoutNodeId: "node-a" });
const manager = new MeshLeaseManager({
taskStore,
nodeHealthMonitor: { getNodeHealth: () => "offline" } as any,
getHandoffPolicy: async () => policy,
localNodeId: "node-local",
});
const recovered = await manager.recoverAbandonedLease(task.id, "scheduler detected stale todo lease");
expect(recovered).toBe(true);
const persisted = await taskStore.getTask(task.id);
expect(persisted?.checkedOutBy ?? null).toBeNull();
expect(persisted?.checkoutNodeId ?? null).toBeNull();
expect(persisted?.checkoutRunId ?? null).toBeNull();
expect(persisted?.checkoutLeaseRenewedAt ?? null).toBeNull();
expect(persisted?.checkoutLeaseEpoch).toBe(2);
},
);
it.each(["block", "reassign-to-local", "reassign-any-healthy"] as const)(
"FN-4813: self-owned offline recovery ignores policy and clears lease (policy=%s)",
async (policy) => {
const task = await seedCheckedOutTask({ checkoutNodeId: "node-local" });
const manager = new MeshLeaseManager({
taskStore,
nodeHealthMonitor: { getNodeHealth: () => "offline" } as any,
getHandoffPolicy: async () => policy,
localNodeId: "node-local",
});
const recovered = await manager.recoverAbandonedLease(task.id, "scheduler detected stale todo lease");
expect(recovered).toBe(true);
const persisted = await taskStore.getTask(task.id);
expect(persisted?.checkedOutBy ?? null).toBeNull();
expect(persisted?.checkoutNodeId ?? null).toBeNull();
expect(persisted?.checkoutLeaseEpoch).toBe(2);
},
);
it("FN-4813: owner returning online during recovery window parks handoff", async () => {
const task = await seedCheckedOutTask();
let calls = 0;
const manager = new MeshLeaseManager({
taskStore,
nodeHealthMonitor: {
getNodeHealth: () => {
calls += 1;
return calls === 1 ? "offline" : "online";
},
} as any,
getHandoffPolicy: async () => "reassign-to-local",
localNodeId: "node-local",
});
const recovered = await manager.recoverAbandonedLease(task.id, "scheduler detected stale todo lease");
expect(recovered).toBe(false);
const persisted = await taskStore.getTask(task.id);
expect(persisted?.checkedOutBy).toBe("agent-1");
expect(persisted?.checkoutNodeId).toBe("node-a");
expect(persisted?.checkoutLeaseEpoch).toBe(1);
});
it("FN-4813: scheduler does not apply owning-node handoff when owner is online", async () => {
const task = createMockTask({
id: "FN-201",
checkedOutBy: "agent-1",
checkoutNodeId: "node-a",
checkoutLeaseEpoch: 1,
nodeId: "node-b",
});
const store = createMockStore(task, { maxConcurrent: 1, maxWorktrees: 1, owningNodeHandoffPolicy: "reassign-to-local" });
const scheduler = new Scheduler(store, {
leaseManager: {
recoverAbandonedLease: vi.fn().mockResolvedValue(false),
} as any,
nodeHealthMonitor: createMockHealthMonitor({ "node-a": "online", "node-b": "online" }),
validateNodeDispatch: vi.fn().mockResolvedValue({ allowed: true }),
});
(scheduler as unknown as { running: boolean }).running = true;
await scheduler.schedule();
expect(store.logEntry).not.toHaveBeenCalledWith(task.id, expect.stringContaining("Owning-node handoff applied"));
expect(store.updateTask).toHaveBeenCalledWith(task.id, { status: "queued" });
});
it("FN-4813: scheduler reassigns local dispatch when owner offline and policy is reassign-to-local", async () => {
const task = createMockTask({
id: "FN-202",
checkedOutBy: "agent-1",
checkoutNodeId: "node-a",
checkoutLeaseEpoch: 1,
nodeId: "node-b",
});
const store = createMockStore(task, {
maxConcurrent: 1,
maxWorktrees: 1,
owningNodeHandoffPolicy: "reassign-to-local" satisfies OwningNodeHandoffPolicy,
unavailableNodePolicy: "block",
});
const scheduler = new Scheduler(store, {
leaseManager: { recoverAbandonedLease: vi.fn().mockResolvedValue(true) } as any,
nodeHealthMonitor: createMockHealthMonitor({ "node-a": "offline", "node-b": "online" }),
validateNodeDispatch: vi.fn().mockResolvedValue({ allowed: true }),
});
(scheduler as unknown as { running: boolean }).running = true;
await scheduler.schedule();
expect(store.logEntry).toHaveBeenCalledWith(task.id, expect.stringContaining("Owning-node handoff applied"));
expect(store.updateTask).toHaveBeenCalledWith(task.id, expect.objectContaining({ effectiveNodeId: null }));
expect(store.moveTask).toHaveBeenCalledWith(task.id, "in-progress", expect.any(Object));
});
});