import { describe, expect, it } from "vitest"; import { isErr, isOk } from "@tff/core"; import { GitSliceMergeLookup } from "../../../../../src/infrastructure/adapters/git/git-slice-merge-lookup.js"; describe("GitSliceMergeLookup", () => { it("returns the first matching merge commit SHA", async () => { const runner = async () => `${[ `abc1234567890abcdef1234567890abcdef1234\u0000feat: land M01-S02 auth flow`, `def5678901234abcdef5678901234abcdef5678\u0000chore: ref M01-S02 in changelog`, ].join("\n")}\n`; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["main"]); expect(isOk(res)).toBe(true); if (!isOk(res)) throw new Error("not ok"); expect(res.data).toBe("abc1234567890abcdef1234567890abcdef1234"); }); it("returns PRECONDITION_VIOLATION when no merge matches", async () => { const runner = async () => ""; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M99-S99", ["main"]); expect(isErr(res)).toBe(true); }); it("rejects slice labels that would inject shell/regex metacharacters", async () => { const runner = async () => `abc1234567890abcdef1234567890abcdef1234\u0000feat: land M01-S02 auth flow\n`; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02; rm -rf /", ["main"]); expect(isErr(res)).toBe(true); }); it("falls through when runner throws on every branch and reports no-match", async () => { const runner = async () => { throw new Error("git not found"); }; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["main"]); expect(isErr(res)).toBe(true); }); it("searches each branch in order and returns the first hit", async () => { let calls = 0; const runner = async (_cmd: string, args: string[]) => { calls++; const branch = args[args.length - 1]; if (branch === "milestone/x") { return `abc1234567890abcdef1234567890abcdef1234feat: land M01-S02 on milestone\n`; } throw new Error("should not reach"); }; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["milestone/x", "main"]); expect(isOk(res)).toBe(true); if (!isOk(res)) throw new Error("not ok"); expect(res.data).toBe("abc1234567890abcdef1234567890abcdef1234"); expect(calls).toBe(1); }); it("falls through when an earlier branch has no match, then finds it on a later one", async () => { const runner = async (_cmd: string, args: string[]) => { const branch = args[args.length - 1]; if (branch === "milestone/x") return ""; if (branch === "main") { return `abc1234567890abcdef1234567890abcdef1234feat: M01-S02 squashed on main\n`; } throw new Error("unexpected branch"); }; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["milestone/x", "main"]); expect(isOk(res)).toBe(true); }); it("returns PRECONDITION_VIOLATION when given an empty branches list", async () => { const runner = async () => ""; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", []); expect(isErr(res)).toBe(true); }); it("skips commits whose subject matches a longer slice label (M01-S02 vs M01-S020)", async () => { const runner = async () => `${[ `aaa1111111111111111111111111111111111111\u0000feat: land M01-S020 unrelated`, `bbb2222222222222222222222222222222222222\u0000feat: land M01-S02 auth flow`, ].join("\n")}\n`; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["main"]); expect(isOk(res)).toBe(true); if (!isOk(res)) throw new Error("not ok"); expect(res.data).toBe("bbb2222222222222222222222222222222222222"); }); it("returns the oldest matching commit when multiple messages mention the label", async () => { const runner = async () => `${[ `aaa1111111111111111111111111111111111111\u0000feat(routing): land M01-S02 auth flow`, `bbb2222222222222222222222222222222222222\u0000revert: rolling back M01-S02 due to regression`, `ccc3333333333333333333333333333333333333\u0000docs: mention M01-S02 in changelog`, ].join("\n")}\n`; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["main"]); if (!isOk(res)) throw new Error("not ok"); expect(res.data).toBe("aaa1111111111111111111111111111111111111"); }); it("returns PRECONDITION_VIOLATION when candidates all fail the word-boundary test", async () => { // All candidates have the label as part of a longer token. const runner = async () => `aaa1111111111111111111111111111111111111\u0000feat: land M01-S0200 experimental\n`; const lookup = new GitSliceMergeLookup({ run: runner, cwd: "/x" }); const res = await lookup.findMergeCommit("M01-S02", ["main"]); expect(isErr(res)).toBe(true); }); });