test(FN-5083): tighten rebind reliability and quality-gate coverage

This commit is contained in:
Fusion (runfusion.ai)
2026-05-18 17:45:34 -07:00
committed by gsxdsm
parent 5c3096c7d1
commit b3ac0beaa0
2 changed files with 60 additions and 47 deletions

View File

@@ -4,13 +4,8 @@ import { join } from "node:path";
import { execSync } from "node:child_process";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const { logger } = vi.hoisted(() => ({
logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
vi.mock("../../logger.js", () => ({
createLogger: vi.fn(() => logger),
}));
const { logger } = vi.hoisted(() => ({ logger: { log: vi.fn(), warn: vi.fn(), error: vi.fn() } }));
vi.mock("../../logger.js", () => ({ createLogger: vi.fn(() => logger) }));
import { TaskStore } from "@fusion/core";
import { SelfHealingManager } from "../../self-healing.js";
@@ -48,32 +43,57 @@ describe("FN-5083 reliability interactions: in-review branch rebind", () => {
return task.id;
}
it("applies rebind once and remains idempotent on subsequent sweep", async () => {
const id = await createTaskInReview("rebind once");
const branch = `fusion/${id.toLowerCase()}`;
async function createUniqueFusionBranch(taskId: string, suffix: string) {
const branch = `fusion/${taskId.toLowerCase()}`;
git(rootDir, `checkout -b ${branch}`);
writeFileSync(join(rootDir, `${id}.txt`), "feature\n");
git(rootDir, `add ${id}.txt`);
git(rootDir, `commit -m 'feat(${id}): rebind target'`);
writeFileSync(join(rootDir, `${taskId}-${suffix}.txt`), `${suffix}\n`);
git(rootDir, `add ${taskId}-${suffix}.txt`);
git(rootDir, `commit -m '${suffix} commit'`);
git(rootDir, "checkout main");
return branch;
}
it("rebinds and remains stable on repeated sweeps", async () => {
const id = await createTaskInReview("stable rebind");
const branch = await createUniqueFusionBranch(id, "stable");
await store.updateTask(id, { branch: null, worktree: null });
const manager = new SelfHealingManager(store, { rootDir });
const first = await manager.reconcileInReviewBranchRebind({ includeTaskIds: new Set([id]) });
const second = await manager.reconcileInReviewBranchRebind({ includeTaskIds: new Set([id]) });
expect(first.repaired).toBe(1);
expect(first.outcomes).toEqual(expect.arrayContaining([
expect.objectContaining({ taskId: id, result: "applied", branch }),
]));
expect(second.repaired).toBeGreaterThanOrEqual(0);
expect(second.outcomes).toEqual(expect.arrayContaining([
expect.objectContaining({ taskId: id }),
]));
expect(first.outcomes).toEqual(expect.arrayContaining([expect.objectContaining({ taskId: id, result: "applied", branch })]));
expect(second.outcomes).toEqual(expect.arrayContaining([expect.objectContaining({ taskId: id })]));
});
it("skips ambiguous case-only candidate branches", async () => {
const id = await createTaskInReview("ambiguous candidates");
it("preserves FN-4962 ordering with metadata reconcile before rebind", async () => {
const id = await createTaskInReview("ordering");
const branch = await createUniqueFusionBranch(id, "ordering");
await store.updateTask(id, { branch: null, worktree: `${rootDir}/.worktrees/missing-${id.toLowerCase()}` });
const manager = new SelfHealingManager(store, { rootDir });
await (manager as any).reconcileTaskWorktreeMetadata({ includeTaskIds: new Set([id]) });
const result = await manager.reconcileInReviewBranchRebind({ includeTaskIds: new Set([id]) });
const updated = await store.getTask(id);
expect(result.outcomes).toEqual(expect.arrayContaining([expect.objectContaining({ taskId: id, result: "applied", branch })]));
expect(updated?.branch).toBe(branch);
expect(updated?.worktree ?? null).toBeNull();
});
it("handles FN-5072-style contamination-cleared metadata with live unique branch", async () => {
const id = await createTaskInReview("contamination-cleared");
const branch = await createUniqueFusionBranch(id, "contamination");
await store.updateTask(id, { branch: null, worktree: null, baseCommitSha: null });
const manager = new SelfHealingManager(store, { rootDir });
const result = await manager.reconcileInReviewBranchRebind({ includeTaskIds: new Set([id]) });
expect(result.outcomes).toEqual(expect.arrayContaining([expect.objectContaining({ taskId: id, result: "applied", branch })]));
});
it("skips ambiguous case-variant candidates when filesystem permits both refs", async () => {
const id = await createTaskInReview("ambiguous");
const lower = `fusion/${id.toLowerCase()}`;
const upper = `fusion/${id}`;
@@ -82,49 +102,42 @@ describe("FN-5083 reliability interactions: in-review branch rebind", () => {
git(rootDir, `add ${id}-lower.txt`);
git(rootDir, "commit -m 'lower unique commit'");
let hasCaseVariant = true;
let caseVariantCreated = true;
try {
git(rootDir, `checkout -b ${upper} main`);
writeFileSync(join(rootDir, `${id}-upper.txt`), "upper\n");
git(rootDir, `add ${id}-upper.txt`);
git(rootDir, "commit -m 'upper unique commit'");
} catch {
hasCaseVariant = false;
caseVariantCreated = false;
}
git(rootDir, "checkout main");
await store.updateTask(id, { branch: null, worktree: null });
const manager = new SelfHealingManager(store, { rootDir });
const result = await manager.reconcileInReviewBranchRebind({ includeTaskIds: new Set([id]) });
if (hasCaseVariant) {
if (caseVariantCreated) {
expect(result.outcomes).toEqual(expect.arrayContaining([
expect.objectContaining({ taskId: id, result: "skipped", reason: "ambiguous-candidates" }),
]));
return;
} else {
expect(result.outcomes).toEqual(expect.arrayContaining([expect.objectContaining({ taskId: id })]));
}
expect(result.outcomes).toEqual(expect.arrayContaining([
expect.objectContaining({ taskId: id }),
]));
});
it("composes with metadata-cleared in-review task state", async () => {
const id = await createTaskInReview("metadata-cleared");
const branch = `fusion/${id.toLowerCase()}`;
git(rootDir, `checkout -b ${branch}`);
writeFileSync(join(rootDir, `${id}-meta.txt`), "meta\n");
git(rootDir, `add ${id}-meta.txt`);
git(rootDir, "commit -m 'meta clear commit'");
git(rootDir, "checkout main");
await store.updateTask(id, { branch: null, worktree: null, baseCommitSha: null });
it("fires targeted rebind from task:moved to in-review listener", async () => {
const id = await createTaskInReview("listener");
const manager = new SelfHealingManager(store, { rootDir });
const result = await manager.reconcileInReviewBranchRebind({ includeTaskIds: new Set([id]) });
const spy = vi.spyOn(manager, "reconcileInReviewBranchRebind").mockResolvedValue({ repaired: 0, outcomes: [] });
manager.start();
const task = await store.getTask(id);
if (!task) throw new Error("task missing");
expect(result.outcomes).toEqual(expect.arrayContaining([
expect.objectContaining({ taskId: id, result: "applied", branch }),
]));
store.emit("task:moved", { task, from: "todo", to: "in-review", source: "engine" });
await new Promise((resolve) => setTimeout(resolve, 0));
expect(spy).toHaveBeenCalledWith({ includeTaskIds: new Set([id]) });
manager.stop();
});
});