Files
shockbot/utils/diffCoverage.test.ts
T
pullfrog[bot] 6db4a6d02e fix(mcp): preserve coveragePreflightRan across checkout_pr refreshes (#576)
checkout_pr unconditionally rebuilds ctx.toolState.diffCoverage via
createDiffCoverageState, which initialised coveragePreflightRan to false.
a second checkout_pr therefore reset the "one-time nudge per review
session" guarantee in runDiffCoveragePreflight, and the next
create_pull_request_review threw the diff-coverage pre-flight error
again — even after the agent had already gone through the
read-and-resubmit dance once.

createDiffCoverageState now accepts an optional previous state and
carries forward coveragePreflightRan. coveredRanges are intentionally
not carried because their line numbers are tied to the previous diff's
content (especially under incremental diffs).

closes #566

Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
Co-authored-by: David Blass <david@arktype.io>
2026-05-06 20:54:34 +00:00

161 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createDiffCoverageState,
getDiffCoverageBreakdown,
parseDiffTocEntries,
recordDiffReadFromToolUse,
} from "./diffCoverage.ts";
const diffPath = "/tmp/pr-1.diff";
const toc = `## Files (2)
- src/a.ts → lines 5-10
- yarn.lock → lines 12-20
---
`;
describe("diff coverage line checker", () => {
it("treats Read offsets as zero based", () => {
const state = createDiffCoverageState({
diffPath,
totalLines: 30,
toc,
});
const tracked = recordDiffReadFromToolUse({
state,
toolName: "Read",
input: {
filePath: diffPath,
offset: 0,
limit: 3,
},
cwd: "/",
});
expect(tracked).toBe(true);
const breakdown = getDiffCoverageBreakdown({ state });
expect(breakdown.coveredRanges).toEqual([{ startLine: 1, endLine: 3 }]);
});
it("treats ReadFile offsets as one based", () => {
const state = createDiffCoverageState({
diffPath,
totalLines: 30,
toc,
});
const tracked = recordDiffReadFromToolUse({
state,
toolName: "ReadFile",
input: {
path: diffPath,
offset: 1,
limit: 2,
},
cwd: "/",
});
expect(tracked).toBe(true);
const breakdown = getDiffCoverageBreakdown({ state });
expect(breakdown.coveredRanges).toEqual([{ startLine: 1, endLine: 2 }]);
});
it("supports negative offsets from file end", () => {
const state = createDiffCoverageState({
diffPath,
totalLines: 30,
toc,
});
const tracked = recordDiffReadFromToolUse({
state,
toolName: "Read",
input: {
path: diffPath,
offset: -2,
limit: 2,
},
cwd: "/",
});
expect(tracked).toBe(true);
const breakdown = getDiffCoverageBreakdown({ state });
expect(breakdown.coveredRanges).toEqual([{ startLine: 29, endLine: 30 }]);
});
it("parses TOC lines that include the ` · diff-<sha256>` anchor emitted by checkout_pr", () => {
const productionToc = `## Files (2)
- src/format.ts → lines 9-32 · diff-41c7b3ac268a3a1ae5c7be92f1230f600013b7170e44a693570ccbdb183ea36b
- test/math.test.ts → lines 81-93 · diff-44b3f515a5c787743d239052db11d740d691e8bef711c2427bb2b9752a4103a9
---
`;
const entries = parseDiffTocEntries({ toc: productionToc });
expect(entries).toEqual([
{ filename: "src/format.ts", startLine: 9, endLine: 32 },
{ filename: "test/math.test.ts", startLine: 81, endLine: 93 },
]);
});
it("carries forward coveragePreflightRan from a previous state across checkout refreshes", () => {
const previous = createDiffCoverageState({ diffPath, totalLines: 30, toc });
previous.coveragePreflightRan = true;
previous.coveredRanges = [{ startLine: 5, endLine: 10 }];
const next = createDiffCoverageState({ diffPath, totalLines: 50, toc, previous });
expect(next.coveragePreflightRan).toBe(true);
// coveredRanges are tied to the previous diff content and must not leak forward
expect(next.coveredRanges).toEqual([]);
expect(next.totalLines).toBe(50);
});
it("defaults coveragePreflightRan to false when no previous state is provided", () => {
const state = createDiffCoverageState({ diffPath, totalLines: 30, toc });
expect(state.coveragePreflightRan).toBe(false);
});
it("computes per-file unread ranges from tracked reads", () => {
const state = createDiffCoverageState({
diffPath,
totalLines: 30,
toc,
});
recordDiffReadFromToolUse({
state,
toolName: "Read",
input: {
path: diffPath,
start_line: 5,
end_line: 6,
},
cwd: "/",
});
recordDiffReadFromToolUse({
state,
toolName: "Read",
input: {
path: diffPath,
start_line: 12,
end_line: 14,
},
cwd: "/",
});
const breakdown = getDiffCoverageBreakdown({ state });
const firstFile = breakdown.files[0];
const secondFile = breakdown.files[1];
expect(firstFile.filename).toBe("src/a.ts");
expect(firstFile.coveredRanges).toEqual([{ startLine: 5, endLine: 6 }]);
expect(firstFile.unreadRanges).toEqual([{ startLine: 7, endLine: 10 }]);
expect(secondFile.filename).toBe("yarn.lock");
expect(secondFile.coveredRanges).toEqual([{ startLine: 12, endLine: 14 }]);
expect(secondFile.unreadRanges).toEqual([{ startLine: 15, endLine: 20 }]);
});
});