test(FN-5103): complete Step 6 — add landed-files attribution reliability coverage

Fusion-Task-Id: FN-5103
Fusion-Task-Lineage: e4e4d9ba-4884-4feb-b18b-a30e799c3fb1
This commit is contained in:
Fusion (runfusion.ai)
2026-05-18 23:44:25 -07:00
committed by gsxdsm
parent 20ab2f0172
commit 03212ce00b
2 changed files with 155 additions and 4 deletions

View File

@@ -0,0 +1,89 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { execSync, spawnSync } from "node:child_process";
import { BranchAttributionError, filterFilesToOwnTaskCommits } from "../../branch-attribution.js";
const hasGit = spawnSync("git", ["--version"], { stdio: "pipe" }).status === 0;
const describeIfGit = hasGit ? describe : describe.skip;
function git(cwd: string, command: string): string {
return execSync(command, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
}
async function commitFile(cwd: string, file: string, content: string, message: string, taskId?: string): Promise<string> {
await writeFile(join(cwd, file), content, "utf-8");
git(cwd, `git add ${JSON.stringify(file)}`);
if (taskId) {
git(cwd, `git commit -m ${JSON.stringify(message)} -m ${JSON.stringify(`Fusion-Task-Id: ${taskId}`)}`);
} else {
git(cwd, `git commit -m ${JSON.stringify(message)}`);
}
return git(cwd, "git rev-parse HEAD");
}
async function initRepo(prefix: string) {
const repoDir = await mkdtemp(join(tmpdir(), prefix));
git(repoDir, "git init -b main");
git(repoDir, 'git config user.email "test@example.com"');
git(repoDir, 'git config user.name "Test User"');
await commitFile(repoDir, "README.md", "base\n", "chore: init", "FN-BASE");
const baseSha = git(repoDir, "git rev-parse HEAD");
return { repoDir, baseSha };
}
describeIfGit("FN-5103 reliability interaction: landed-files attribution", () => {
const dirs: string[] = [];
afterEach(async () => {
vi.restoreAllMocks();
await Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
});
it("captures only own-commit files when rebased over foreign commits", async () => {
const { repoDir, baseSha } = await initRepo("fn-5103-ri-");
dirs.push(repoDir);
const taskId = "FN-5103";
git(repoDir, `git checkout -b fusion/${taskId.toLowerCase()}`);
const own1 = await commitFile(repoDir, "task-a.ts", "a\n", "feat(FN-5103): A", taskId);
const own2 = await commitFile(repoDir, "task-b.ts", "b\n", "feat(FN-5103): B", taskId);
const own3 = await commitFile(repoDir, "task-c.ts", "c\n", "feat(FN-5103): C", taskId);
git(repoDir, "git checkout main");
for (let i = 0; i < 5; i += 1) {
await commitFile(repoDir, `other-${i}.ts`, `x${i}\n`, `feat(FN-OTHER-${i}): other`, `FN-OTHER-${i}`);
}
git(repoDir, `git checkout fusion/${taskId.toLowerCase()}`);
git(repoDir, "git rebase main");
const attribution = await filterFilesToOwnTaskCommits({ worktreePath: repoDir, baseRef: baseSha, taskId });
expect(attribution.files).toEqual(["task-a.ts", "task-b.ts", "task-c.ts"]);
expect(attribution.ownCommitCount).toBe(3);
expect(attribution.foreignCommits.length).toBe(5);
expect(attribution.ownCommitShas).toHaveLength(3);
expect(new Set(attribution.ownCommitShas ?? []).size).toBe(3);
expect([own1, own2, own3]).toHaveLength(3);
});
it("marks verified short-circuit shape when no own commits are attributable", async () => {
const { repoDir, baseSha } = await initRepo("fn-5103-ri-");
dirs.push(repoDir);
const taskId = "FN-5103";
git(repoDir, `git checkout -b fusion/${taskId.toLowerCase()}`);
await commitFile(repoDir, "foreign-only.ts", "x\n", "feat(FN-OTHER): foreign", "FN-OTHER");
const attribution = await filterFilesToOwnTaskCommits({ worktreePath: repoDir, baseRef: baseSha, taskId });
expect(attribution.files).toEqual([]);
expect(attribution.ownCommitCount).toBe(0);
expect(attribution.foreignCommits.length).toBe(1);
});
it("supports attribution failure fallback signal path", async () => {
const error = new BranchAttributionError("synthetic attribution failure");
expect(error.message).toContain("synthetic attribution failure");
});
});