// Table-driven tests for the pure helpers of the dep-lockstep guard (#1896). // One case per rule the guard encodes — the comment names the rule, so a future // change that relaxes one is visible as a deleted assertion rather than a quiet // behavior shift. Run via `npm run test:scripts` (node:test; the root has no // vitest harness). // // The candidate derivation itself lives in `lib/tsc-program.mjs` (shared with // `verify:typecheck-coverage` since #1965) and is covered by // `lib/tsc-program.test.mjs`; what stays here is the lockfile comparison and the // client-enrollment rule this guard owns. import { test } from "node:test"; import assert from "node:assert/strict"; import { clientProjects, findSkew, hasReadableLockShape, lockVersionsByPath, majorOf, partitionSkew, } from "./verify-dep-lockstep.mjs"; test("clientProjects: a `typecheck` script's projects win over references", () => { // Both enrollment paths exist (cli/tui/launcher declare `typecheck`, // `clients/web` is a `tsc -b` solution) and this guard must measure the same // programs as `verify:typecheck-coverage`, which prefers the script. assert.deepEqual( clientProjects( { typecheck: "tsc --noEmit -p tsconfig.json && tsc -p tsconfig.test.json", }, ["./ignored.json"], ), ["tsconfig.json", "tsconfig.test.json"], ); }); test("clientProjects: a reference client is measured through its references", () => { assert.deepEqual( clientProjects({ build: "tsc -b && vite build" }, ["./a.json"]), ["./a.json"], ); // Neither path available — the caller reports it rather than measuring nothing. assert.deepEqual(clientProjects({ build: "vite build" }, []), []); }); test("clientProjects: a NEUTERED project still contributes its program (#1965)", () => { // `--noCheck` stops that pass type-checking, which is the sibling guard's // complaint; the program still resolves its imports, and dropping it here // would shrink what THIS guard measures on the strength of that other defect. assert.deepEqual( clientProjects( { typecheck: "tsc -p tsconfig.json --noCheck && tsc -p tsconfig.test.json", }, [], ).sort(), ["tsconfig.json", "tsconfig.test.json"], ); }); test("lockVersionsByPath: keyed by install path, nested entries included (Copilot, #1965 r1)", () => { // Keyed by PATH, not by package name: a nested copy that entered a program has // to be priced from its own entry. Reading only `node_modules/` would // compare it against the install's top-level copy — a different version, or // none — and a real pair could pass. const lock = { packages: { "": { name: "root" }, "node_modules/zod": { version: "4.4.3" }, "node_modules/yaml": { version: "2.9.0" }, "node_modules/cosmiconfig/node_modules/yaml": { version: "1.10.3" }, "node_modules/@modelcontextprotocol/client": { version: "2.0.0-beta.5" }, "node_modules/no-version": { resolved: "https://example.test/x.tgz" }, }, }; assert.deepEqual([...lockVersionsByPath(lock)].sort(), [ ["node_modules/@modelcontextprotocol/client", "2.0.0-beta.5"], ["node_modules/cosmiconfig/node_modules/yaml", "1.10.3"], ["node_modules/yaml", "2.9.0"], ["node_modules/zod", "4.4.3"], ]); }); test("lockVersionsByPath: a malformed or empty lockfile yields nothing", () => { // Safe as a pure helper *because* `hasReadableLockShape` rejects these before // any comparison — an empty map reaching `findSkew` is the fail-open path. for (const lock of [undefined, null, {}, { packages: {} }]) assert.equal(lockVersionsByPath(lock).size, 0); }); /** `crossInstallPackages`-shaped input: name → program → install → entry paths. */ const occurrence = (name, program, byRoot) => new Map([ [ name, new Map([ [ program, new Map( Object.entries(byRoot).map(([dir, paths]) => [dir, new Set(paths)]), ), ], ]), ], ]); const lockPaths = (byDir) => new Map( Object.entries(byDir).map(([dir, entries]) => [ dir, new Map(Object.entries(entries)), ]), ); test("findSkew: reports the copies one program loaded, with their paths", () => { const { skewed, unresolved } = findSkew( occurrence("zod", "clients/web/tsconfig.test.json", { ".": ["node_modules/zod"], "clients/web": ["node_modules/zod"], }), lockPaths({ ".": { "node_modules/zod": "4.3.6" }, "clients/web": { "node_modules/zod": "4.4.3" }, }), ); assert.deepEqual(unresolved, []); assert.deepEqual(skewed, [ { name: "zod", occurrences: [ { program: "clients/web/tsconfig.test.json", holders: [ { dir: ".", entryPath: "node_modules/zod", version: "4.3.6" }, { dir: "clients/web", entryPath: "node_modules/zod", version: "4.4.3", }, ], }, ], }, ]); }); test("findSkew: only the installs that MET in a program are compared (Copilot, #1965 r1)", () => { // `clients/cli` holds a different zod, but no program loads it beside another // copy — nothing has to relate the two, so it is not a finding, and naming cli // in a diagnostic about web's program would be wrong as well as noisy. const { skewed } = findSkew( occurrence("zod", "clients/web/tsconfig.test.json", { ".": ["node_modules/zod"], "clients/web": ["node_modules/zod"], }), lockPaths({ ".": { "node_modules/zod": "4.4.3" }, "clients/web": { "node_modules/zod": "4.4.3" }, "clients/cli": { "node_modules/zod": "4.3.6" }, }), ); assert.deepEqual(skewed, []); }); test("findSkew: a nested copy is priced from its own entry (Copilot, #1965 r1)", () => { // The root loaded zod through `a`'s nested copy. Pricing it from the root's // TOP-LEVEL entry (4.4.3, aligned with web) would report the pair as agreeing. const { skewed } = findSkew( occurrence("zod", "clients/web/tsconfig.test.json", { ".": ["node_modules/a/node_modules/zod"], "clients/web": ["node_modules/zod"], }), lockPaths({ ".": { "node_modules/zod": "4.4.3", "node_modules/a/node_modules/zod": "3.1.0", }, "clients/web": { "node_modules/zod": "4.4.3" }, }), ); assert.deepEqual( skewed[0].occurrences[0].holders.map((h) => `${h.dir}:${h.version}`), [".:3.1.0", "clients/web:4.4.3"], ); }); test("findSkew: agreement is not skew", () => { const { skewed, unresolved } = findSkew( occurrence("zod", "p", { ".": ["node_modules/zod"], "clients/web": ["node_modules/zod"], }), lockPaths({ ".": { "node_modules/zod": "4.4.3" }, "clients/web": { "node_modules/zod": "4.4.3" }, }), ); assert.deepEqual(skewed, []); assert.deepEqual(unresolved, []); }); test("findSkew: a copy with no lockfile entry is reported, not skipped", () => { // Skipping it would drop a holder from the comparison and could report a real // skew as agreement — the gate failing open. const { skewed, unresolved } = findSkew( occurrence("zod", "p", { ".": ["node_modules/zod"], "clients/web": ["node_modules/zod"], }), lockPaths({ ".": { "node_modules/zod": "4.4.3" }, "clients/web": {} }), ); assert.deepEqual(unresolved, [ { name: "zod", dir: "clients/web", entryPath: "node_modules/zod" }, ]); assert.deepEqual(skewed, []); }); test("findSkew: results are sorted by package name", () => { const found = new Map([ ...occurrence("zod", "p", { ".": ["node_modules/zod"], "clients/web": ["node_modules/zod"], }), ...occurrence("hono", "p", { ".": ["node_modules/hono"], "clients/web": ["node_modules/hono"], }), ]); const { skewed } = findSkew( found, lockPaths({ ".": { "node_modules/zod": "1.0.0", "node_modules/hono": "1.0.0" }, "clients/web": { "node_modules/zod": "2.0.0", "node_modules/hono": "2.0.0", }, }), ); assert.deepEqual( skewed.map((s) => s.name), ["hono", "zod"], ); }); test("hasReadableLockShape: only a v2+ packages table with a root entry (Copilot, #1962)", () => { assert.equal( hasReadableLockShape({ lockfileVersion: 3, packages: { "": {} } }), true, ); assert.equal( hasReadableLockShape({ lockfileVersion: 2, packages: { "": {}, "node_modules/zod": { version: "4.4.3" } }, }), true, ); const rejected = [ undefined, null, {}, { lockfileVersion: 3, packages: null }, { lockfileVersion: 3, packages: [] }, // an array has no `""` key { lockfileVersion: 3, packages: {} }, // no root entry { lockfileVersion: 3, packages: { "node_modules/zod": { version: "4.4.3" } }, }, { lockfileVersion: 1, dependencies: { zod: { version: "4.4.3" } } }, // A declared v1 carrying a `packages` table: the version is checked, not // inferred from the key's presence, so this is rejected rather than // half-trusted into an empty (fail-open) version map. { lockfileVersion: 1, packages: { "": {} } }, { packages: { "": {} } }, // no declared version at all { lockfileVersion: "3", packages: { "": {} } }, // not a number ]; for (const lock of rejected) assert.equal(hasReadableLockShape(lock), false, JSON.stringify(lock)); }); test("partitionSkew: the allowlist is by name, not by version pair", () => { // So an ordinary patch float within a tolerated package does not churn the // allowlist, while any *unlisted* package that starts skewing still fails. const skewed = [ { name: "react", occurrences: [ { program: "p", holders: [ { dir: ".", version: "19.2.7" }, { dir: "clients/web", version: "19.2.8" }, ], }, ], }, { name: "zod", occurrences: [ { program: "p", holders: [ { dir: ".", version: "4.3.6" }, { dir: "clients/web", version: "4.4.3" }, ], }, ], }, ]; const tolerated = new Map([["react", "shallow interfaces"]]); const { failures, ignored } = partitionSkew(skewed, tolerated); assert.deepEqual( failures.map((s) => s.name), ["zod"], ); assert.deepEqual( ignored.map((s) => s.name), ["react"], ); }); test("partitionSkew: deny by default — nothing tolerated fails everything", () => { const skewed = [ { name: "zod", occurrences: [ { program: "p", holders: [{ dir: ".", version: "1.0.0" }] }, ], }, ]; assert.equal(partitionSkew(skewed, new Map()).failures.length, 1); }); test("partitionSkew: the allowlist tolerates skew only within a major (Copilot, #1962)", () => { // Each rationale establishes that a patch/minor difference is benign; that is // not evidence a React 18-vs-19 split is, so a listed package still fails // across a major boundary. const tolerated = new Map([["react", "shallow interfaces"]]); const withinMajor = [ { name: "react", occurrences: [ { program: "p", holders: [ { dir: ".", version: "19.2.7" }, { dir: "clients/web", version: "19.2.8" }, ], }, ], }, ]; const acrossMajor = [ { name: "react", occurrences: [ { program: "p", holders: [ { dir: ".", version: "18.3.1" }, { dir: "clients/web", version: "19.2.8" }, ], }, ], }, ]; assert.equal(partitionSkew(withinMajor, tolerated).failures.length, 0); assert.equal(partitionSkew(withinMajor, tolerated).ignored.length, 1); assert.equal(partitionSkew(acrossMajor, tolerated).failures.length, 1); assert.equal(partitionSkew(acrossMajor, tolerated).ignored.length, 0); }); test("partitionSkew: an unparseable version can't be proven same-major, so it fails", () => { const tolerated = new Map([["react", "shallow interfaces"]]); const skewed = [ { name: "react", occurrences: [ { program: "p", holders: [ { dir: ".", version: "19.2.7" }, { dir: "clients/web", version: "next" }, ], }, ], }, ]; assert.equal(partitionSkew(skewed, tolerated).failures.length, 1); }); test("majorOf: prerelease and build metadata are irrelevant", () => { const cases = [ ["4.4.3", "4"], ["2.0.0-beta.5", "2"], ["19.2.8", "19"], ["1.10.3+build.7", "1"], ]; for (const [input, expected] of cases) assert.equal(majorOf(input), expected, input); for (const bad of ["next", "", undefined, null, "v4.4.3"]) assert.equal(majorOf(bad), null, JSON.stringify(bad)); });