Skip to content

Commit 34c529a

Browse files
committed
test(coverage): cover pin drift formatters
Exercise deterministic commit mapping and pin drift markdown branches so CI coverage does not depend on live GitHub API paths being reachable.
1 parent 050f487 commit 34c529a

4 files changed

Lines changed: 176 additions & 54 deletions

File tree

src/server/compare-refs.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { describe, expect, test } from "bun:test";
22

3-
import { countBehind, fetchCommitHistory, resolveRef } from "./compare-refs.js";
3+
import {
4+
countBehind,
5+
fetchCommitHistory,
6+
filterCommitsAfterPin,
7+
mapCommitHistoryNodes,
8+
resolveRef,
9+
} from "./compare-refs.js";
410
import { buildGoPseudoVersion } from "./module-pin-hint-tool.js";
511
import { pseudoVersionSha } from "./pin-drift-tool.js";
612
import { parseSince } from "./utils.js";
@@ -71,6 +77,64 @@ describe("parseSince", () => {
7177
});
7278
});
7379

80+
describe("mapCommitHistoryNodes", () => {
81+
test("maps GraphQL history nodes to compact commit entries", () => {
82+
expect(
83+
mapCommitHistoryNodes([
84+
{
85+
oid: "0123456789abcdef0123456789abcdef01234567",
86+
messageHeadline: "fix(ci): cover branch",
87+
committedDate: "2026-04-26T20:00:00Z",
88+
author: { name: "Fallback Name", user: { login: "damon" } },
89+
},
90+
{
91+
oid: "abcdef0123456789abcdef0123456789abcdef01",
92+
messageHeadline: "docs: update notes",
93+
committedDate: "2026-04-25T20:00:00Z",
94+
author: { name: "Fallback Name", user: null },
95+
},
96+
{
97+
oid: "fedcba9876543210fedcba9876543210fedcba98",
98+
messageHeadline: "chore: no author",
99+
committedDate: "2026-04-24T20:00:00Z",
100+
author: { name: null, user: null },
101+
},
102+
]),
103+
).toEqual([
104+
{
105+
sha7: "0123456",
106+
message: "fix(ci): cover branch",
107+
author: "damon",
108+
date: "2026-04-26T20:00:00Z",
109+
},
110+
{
111+
sha7: "abcdef0",
112+
message: "docs: update notes",
113+
author: "Fallback Name",
114+
date: "2026-04-25T20:00:00Z",
115+
},
116+
{
117+
sha7: "fedcba9",
118+
message: "chore: no author",
119+
author: "unknown",
120+
date: "2026-04-24T20:00:00Z",
121+
},
122+
]);
123+
});
124+
});
125+
126+
describe("filterCommitsAfterPin", () => {
127+
test("drops the pinned commit by full or short SHA prefix", () => {
128+
const commits = [
129+
{ sha7: "aaaaaaa", message: "new", author: "a", date: "2026-04-26T20:00:00Z" },
130+
{ sha7: "bbbbbbb", message: "pin", author: "b", date: "2026-04-25T20:00:00Z" },
131+
];
132+
133+
expect(filterCommitsAfterPin(commits, "bbbbbbb1234567890")).toEqual([commits[0]]);
134+
expect(filterCommitsAfterPin(commits, "bbbbbbb")).toEqual([commits[0]]);
135+
});
136+
});
137+
74138
// ---------------------------------------------------------------------------
75139
// pseudoVersionSha (from pin-drift-tool)
76140
// ---------------------------------------------------------------------------

src/server/compare-refs.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface CommitEntry {
1616
date: string;
1717
}
1818

19-
interface CommitHistoryNode {
19+
export interface CommitHistoryNode {
2020
oid: string;
2121
messageHeadline: string;
2222
committedDate: string;
@@ -41,6 +41,22 @@ interface CommitObjectResult {
4141
};
4242
}
4343

44+
export function mapCommitHistoryNodes(nodes: CommitHistoryNode[]): CommitEntry[] {
45+
return nodes.map((n) => ({
46+
sha7: n.oid.substring(0, 7),
47+
message: n.messageHeadline,
48+
author: n.author.user?.login ?? n.author.name ?? "unknown",
49+
date: n.committedDate,
50+
}));
51+
}
52+
53+
export function filterCommitsAfterPin(commits: CommitEntry[], pinnedSha: string): CommitEntry[] {
54+
return commits.filter((c) => {
55+
// sha7 prefix match for the pinned commit
56+
return !pinnedSha.startsWith(c.sha7) && pinnedSha.substring(0, 7) !== c.sha7;
57+
});
58+
}
59+
4460
/**
4561
* Resolve a ref (branch, tag, or SHA prefix) to a full 40-char SHA + committer date.
4662
* Returns null when the ref does not exist in the given repo.
@@ -107,12 +123,7 @@ export async function fetchCommitHistory(
107123
const defaultBranch = data.repository.defaultBranchRef?.name ?? "main";
108124
const nodes = data.repository.object?.history.nodes ?? [];
109125

110-
const commits: CommitEntry[] = nodes.map((n) => ({
111-
sha7: n.oid.substring(0, 7),
112-
message: n.messageHeadline,
113-
author: n.author.user?.login ?? n.author.name ?? "unknown",
114-
date: n.committedDate,
115-
}));
126+
const commits = mapCommitHistoryNodes(nodes);
116127

117128
return { commits, defaultBranch };
118129
}
@@ -147,10 +158,7 @@ export async function countBehind(
147158
});
148159

149160
// Exclude the pinned commit itself (same oid or older)
150-
const newer = commits.filter((c) => {
151-
// sha7 prefix match for the pinned commit
152-
return !pinnedSha.startsWith(c.sha7) && pinnedSha.substring(0, 7) !== c.sha7;
153-
});
161+
const newer = filterCommitsAfterPin(commits, pinnedSha);
154162

155163
return { behindBy: newer.length, commits: newer };
156164
}

src/server/pin-drift-tool.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
44
import { join } from "node:path";
55

66
import {
7+
formatPinDriftMarkdown,
78
parseGoMod,
89
parsePackageJson,
910
parseVersionsEnv,
@@ -401,6 +402,53 @@ MFA_VERSION=v1.2.3
401402
});
402403
});
403404

405+
describe("formatPinDriftMarkdown", () => {
406+
test("renders stale, fresh, and skipped sections", () => {
407+
const text = formatPinDriftMarkdown({
408+
localPath: "/tmp/repo",
409+
pins: [
410+
{
411+
source: "go.mod",
412+
owner: "Rethunk-AI",
413+
repo: "stale-lib",
414+
pinnedRef: "0123456789abcdef",
415+
defaultBranch: "main",
416+
headSha: "abcdef0123456789",
417+
behindBy: 3,
418+
commits: [],
419+
stale: true,
420+
},
421+
{
422+
source: "package.json",
423+
owner: "Rethunk-AI",
424+
repo: "fresh-lib",
425+
pinnedRef: "abcdef012345",
426+
defaultBranch: "main",
427+
headSha: "abcdef0123456789",
428+
behindBy: 0,
429+
commits: [],
430+
stale: false,
431+
},
432+
],
433+
skipped: [
434+
{
435+
source: "scripts/versions.env",
436+
key: "SATCOM_REF",
437+
value: "0123456789abcdef0123456789abcdef01234567",
438+
reason: "ambiguous_repo",
439+
},
440+
],
441+
summary: { totalPins: 2, stale: 1, upToDate: 1 },
442+
});
443+
444+
expect(text).toContain("# Pin Drift: /tmp/repo");
445+
expect(text).toContain("**2 pins** — 1 stale, 1 up to date, 1 skipped");
446+
expect(text).toContain("| go.mod | Rethunk-AI/stale-lib | 3 | `0123456789ab` |");
447+
expect(text).toContain("Rethunk-AI/fresh-lib");
448+
expect(text).toContain("| scripts/versions.env | `SATCOM_REF` | ambiguous_repo |");
449+
});
450+
});
451+
404452
// ---------------------------------------------------------------------------
405453
// pin_drift tool integration (via captureTool)
406454
//

src/server/pin-drift-tool.ts

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { FormatSchema } from "./schemas.js";
1616

1717
export type PinSource = "go.mod" | ".gitmodules" | "scripts/versions.env" | "package.json";
1818

19-
interface PinEntry {
19+
export interface PinEntry {
2020
source: PinSource;
2121
owner: string;
2222
repo: string;
@@ -39,7 +39,7 @@ export interface SkippedEntry {
3939
reason: string;
4040
}
4141

42-
interface PinDriftResult {
42+
export interface PinDriftResult {
4343
localPath: string;
4444
pins: PinEntry[];
4545
skipped: SkippedEntry[];
@@ -270,6 +270,47 @@ export function parsePackageJson(localPath: string): { pins: RawPin[]; skipped:
270270
return { pins, skipped };
271271
}
272272

273+
export function formatPinDriftMarkdown(result: PinDriftResult): string {
274+
const { localPath, pins, skipped, summary } = result;
275+
const lines: string[] = [];
276+
lines.push(`# Pin Drift: ${localPath}`);
277+
lines.push("");
278+
lines.push(
279+
`**${pins.length} pins** — ${summary.stale} stale, ${summary.upToDate} up to date` +
280+
(skipped.length > 0 ? `, ${skipped.length} skipped` : ""),
281+
);
282+
283+
if (summary.stale > 0) {
284+
lines.push("");
285+
lines.push("## Stale Pins");
286+
lines.push("| Source | Repo | Behind | Pinned SHA |");
287+
lines.push("|--------|------|--------|------------|");
288+
for (const p of pins.filter((x) => x.stale)) {
289+
const sha = p.pinnedRef.substring(0, 12);
290+
lines.push(`| ${p.source} | ${p.owner}/${p.repo} | ${p.behindBy} | \`${sha}\` |`);
291+
}
292+
}
293+
294+
const fresh = pins.filter((p) => !p.stale && p.behindBy >= 0);
295+
if (fresh.length > 0) {
296+
lines.push("");
297+
lines.push("## Up to Date");
298+
lines.push(fresh.map((p) => `${p.owner}/${p.repo}`).join(", "));
299+
}
300+
301+
if (skipped.length > 0) {
302+
lines.push("");
303+
lines.push("## Skipped");
304+
lines.push("| Source | Key | Reason |");
305+
lines.push("|--------|-----|--------|");
306+
for (const s of skipped) {
307+
lines.push(`| ${s.source} | \`${s.key}\` | ${s.reason} |`);
308+
}
309+
}
310+
311+
return lines.join("\n");
312+
}
313+
273314
// ---------------------------------------------------------------------------
274315
// Tool registration
275316
// ---------------------------------------------------------------------------
@@ -512,46 +553,7 @@ export function registerPinDriftTool(server: FastMCP): void {
512553

513554
if (args.format === "json") return jsonRespond(result);
514555

515-
// -----------------------------------------------------------------------
516-
// Markdown output
517-
// -----------------------------------------------------------------------
518-
const lines: string[] = [];
519-
lines.push(`# Pin Drift: ${localPath}`);
520-
lines.push("");
521-
lines.push(
522-
`**${pinResults.length} pins** — ${staleCount} stale, ${upToDate} up to date` +
523-
(allSkipped.length > 0 ? `, ${allSkipped.length} skipped` : ""),
524-
);
525-
526-
if (staleCount > 0) {
527-
lines.push("");
528-
lines.push("## Stale Pins");
529-
lines.push("| Source | Repo | Behind | Pinned SHA |");
530-
lines.push("|--------|------|--------|------------|");
531-
for (const p of pinResults.filter((x) => x.stale)) {
532-
const sha = p.pinnedRef.substring(0, 12);
533-
lines.push(`| ${p.source} | ${p.owner}/${p.repo} | ${p.behindBy} | \`${sha}\` |`);
534-
}
535-
}
536-
537-
const fresh = pinResults.filter((p) => !p.stale && p.behindBy >= 0);
538-
if (fresh.length > 0) {
539-
lines.push("");
540-
lines.push("## Up to Date");
541-
lines.push(fresh.map((p) => `${p.owner}/${p.repo}`).join(", "));
542-
}
543-
544-
if (allSkipped.length > 0) {
545-
lines.push("");
546-
lines.push("## Skipped");
547-
lines.push("| Source | Key | Reason |");
548-
lines.push("|--------|-----|--------|");
549-
for (const s of allSkipped) {
550-
lines.push(`| ${s.source} | \`${s.key}\` | ${s.reason} |`);
551-
}
552-
}
553-
554-
return lines.join("\n");
556+
return formatPinDriftMarkdown(result);
555557
},
556558
});
557559
}

0 commit comments

Comments
 (0)