diff --git a/tests/cli/scan-fix-verify-exitcode.test.ts b/tests/cli/scan-fix-verify-exitcode.test.ts index 0942da5..73ed054 100644 --- a/tests/cli/scan-fix-verify-exitcode.test.ts +++ b/tests/cli/scan-fix-verify-exitcode.test.ts @@ -20,7 +20,9 @@ describe("cve-lite [path] --fix exit codes", () => { lockfileVersion: 3, packages: { "": { name: "x" }, - "node_modules/lodash": { version: "4.17.21" }, + // Fixture-only name so no advisory ever matches it; the exit-0 assertion + // below must not depend on the synced advisory DB state (issue #726). + "node_modules/cve-lite-fixture-safe-pkg": { version: "1.0.0" }, }, })); } diff --git a/tests/e2e/commands-and-exit-codes.test.ts b/tests/e2e/commands-and-exit-codes.test.ts index e07fbc7..ab75d0e 100644 --- a/tests/e2e/commands-and-exit-codes.test.ts +++ b/tests/e2e/commands-and-exit-codes.test.ts @@ -15,7 +15,7 @@ import { mkdtempSync, rmSync, existsSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { runCli, mkProject, rmProject, npmProject } from "./harness.js"; +import { runCli, mkProject, rmProject, npmProject, SAFE_PKG } from "./harness.js"; // Track temp dirs created without mkProject (install-skill cwd, temp HOME) so // afterEach can sweep them. @@ -68,7 +68,7 @@ describe("commands + meta", () => { it("--ratchet --check-overrides keeps override hygiene out of the baseline and notes the boundary", () => { const dir = mkProject( - npmProject({ name: "ratchet-app", overrides: { gone: "1.0.0" } }, { lodash: "4.17.21" }), + npmProject({ name: "ratchet-app", overrides: { gone: "1.0.0" } }, { [SAFE_PKG]: "1.0.0" }), ); try { const r = runCli([dir, "--offline", "--ratchet", "--check-overrides"]); @@ -157,7 +157,7 @@ describe("commands + meta", () => { }); it("overrides --json exits 0 with a findings array", () => { - const dir = mkProject(npmProject({ name: "x" }, { lodash: "4.17.21" })); + const dir = mkProject(npmProject({ name: "x" }, { [SAFE_PKG]: "1.0.0" })); try { const r = runCli(["overrides", dir, "--json"]); expect(r.status).toBe(0); @@ -227,7 +227,7 @@ describe("commands + meta", () => { describe("exit codes", () => { it("0: clean overrides run", () => { - const dir = mkProject(npmProject({ name: "x" }, { lodash: "4.17.21" })); + const dir = mkProject(npmProject({ name: "x" }, { [SAFE_PKG]: "1.0.0" })); try { const r = runCli(["overrides", dir, "--json"]); expect(r.status).toBe(0); diff --git a/tests/e2e/fix-and-auditlog.test.ts b/tests/e2e/fix-and-auditlog.test.ts index 1bd389e..bf89226 100644 --- a/tests/e2e/fix-and-auditlog.test.ts +++ b/tests/e2e/fix-and-auditlog.test.ts @@ -20,6 +20,9 @@ import { rmProject, npmProject, installedManifest, + SAFE_PKG, + SAFE_PLATFORM_PARENT, + SAFE_PLATFORM_BINARY, } from "./harness.js"; /** Read the project package.json back off disk after the CLI mutated it. */ @@ -38,34 +41,41 @@ function readNdjson(path: string): Array> { /** * Project that triggers OA006 (coupled platform binary) in isolation. The override - * pins @esbuild/linux-x64 to 0.25.0, but its installed parent esbuild@0.25.12 - * exact-pins the binary to 0.25.12. node_modules exists (esbuild is installed, so the - * detector is not pre-skipped) but the optional platform binary itself is NOT - * materialized - the common wrong-platform case - so there is no on-disk proof the - * override took. OA006 fires on the latent coupling. Per issue #37 it suppresses only - * when a materialized copy actually satisfies the override; with the binary absent, - * OA004 (needs a surpassing installed version) and OA008 (needs a below-floor copy) - * both stay silent, isolating the tier-2 (proposed) relocate fix. + * pins the platform binary to 0.0.1, but its installed parent exact-pins the binary + * to 0.0.2. node_modules exists (the parent is installed, so the detector is not + * pre-skipped) but the optional platform binary itself is NOT materialized - the + * common wrong-platform case - so there is no on-disk proof the override took. OA006 + * fires on the latent coupling. Per issue #37 it suppresses only when a materialized + * copy actually satisfies the override; with the binary absent, OA004 (needs a + * surpassing installed version) and OA008 (needs a below-floor copy) both stay + * silent, isolating the tier-2 (proposed) relocate fix. + * + * The parent/binary use fixture-only names so the CVE scan stays clean regardless of + * the advisory DB state (issue #726). looksLikePlatformBinary() keys off the "linux" + * token, not the package identity, so the synthetic binary still drives OA006. */ function makeOa006Project(): string { const dir = mkProject( npmProject( - { name: "oa6", overrides: { "@esbuild/linux-x64": "0.25.0" } }, - { esbuild: "0.25.12", "@esbuild/linux-x64": "0.25.0" } + { name: "oa6", overrides: { [SAFE_PLATFORM_BINARY]: "0.0.1" } }, + { [SAFE_PLATFORM_PARENT]: "0.0.2", [SAFE_PLATFORM_BINARY]: "0.0.1" } ) ); - installedManifest(dir, "esbuild", { - name: "esbuild", - version: "0.25.12", - optionalDependencies: { "@esbuild/linux-x64": "0.25.12" }, + installedManifest(dir, SAFE_PLATFORM_PARENT, { + name: SAFE_PLATFORM_PARENT, + version: "0.0.2", + optionalDependencies: { [SAFE_PLATFORM_BINARY]: "0.0.2" }, }); return dir; } -/** Project with a single orphan override (OA001): `gone` is not in the lockfile. */ +/** Project with a single orphan override (OA001): `gone` is not in the lockfile. + * The resolved dependency is a fixture-only name so the CVE scan stays clean + * regardless of the advisory DB state (issue #726); OA001 still fires because + * `gone` is absent from the tree, which is the only thing this fixture asserts. */ function makeOrphanProject(): string { return mkProject( - npmProject({ name: "x", overrides: { gone: "1.0.0" } }, { lodash: "4.17.21" }) + npmProject({ name: "x", overrides: { gone: "1.0.0" } }, { [SAFE_PKG]: "1.0.0" }) ); } @@ -104,11 +114,11 @@ describe("e2e: overrides --fix tiering", () => { const fix = runCli(["overrides", dir, "--fix"]); expect(fix.status).toBe(0); - // The override must still be present and unrelocated: no /dependencies/esbuild - // floor was written, and the binary override value is unchanged. + // The override must still be present and unrelocated: no /dependencies floor + // was written, and the binary override value is unchanged. const pkg = readPkg(dir); const overrides = pkg.overrides as Record; - expect(overrides["@esbuild/linux-x64"]).toBe("0.25.0"); + expect(overrides[SAFE_PLATFORM_BINARY]).toBe("0.0.1"); expect(pkg.dependencies).toBeUndefined(); // And OA006 still fires on a re-run (the proposed fix was not consumed). diff --git a/tests/e2e/harness.ts b/tests/e2e/harness.ts index 838e71b..0755c6d 100644 --- a/tests/e2e/harness.ts +++ b/tests/e2e/harness.ts @@ -13,6 +13,17 @@ import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); export const CLI = join(here, "..", "..", "dist", "index.js"); +// Fixture package names that no advisory will ever match, so a fixture that +// asserts a clean (exit 0) scan stays clean regardless of the synced advisory +// DB state. Real package names (lodash, express, ...) flip e2e exit-code +// assertions whenever a new CVE lands for the pinned version - see issue #726. +export const SAFE_PKG = "cve-lite-fixture-safe-pkg"; +// A platform-binary pair for OA006 fixtures: the override fights the parent's +// exact pin on a native binary. looksLikePlatformBinary() keys off the "linux" +// token, not the package identity, so a fixture-only name triggers OA006 too. +export const SAFE_PLATFORM_PARENT = "cve-lite-fixture-native"; +export const SAFE_PLATFORM_BINARY = "@cve-lite-fixture/linux-x64"; + export interface CliResult { stdout: string; stderr: string; diff --git a/tests/e2e/validation-and-outputs.test.ts b/tests/e2e/validation-and-outputs.test.ts index b174685..7494bc8 100644 --- a/tests/e2e/validation-and-outputs.test.ts +++ b/tests/e2e/validation-and-outputs.test.ts @@ -17,7 +17,7 @@ import { readdirSync, readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; -import { runCli, mkProject, rmProject, npmProject } from "./harness.js"; +import { runCli, mkProject, rmProject, npmProject, SAFE_PKG } from "./harness.js"; const CI_ENV = { CI: "1" } as const; @@ -26,7 +26,7 @@ const CI_ENV = { CI: "1" } as const; function orphanOverrideProject(): string { const files = npmProject( { name: "outproj", version: "1.0.0", overrides: { lodash: "4.17.21" } }, - { express: "4.18.2" }, + { [SAFE_PKG]: "1.0.0" }, ); return mkProject(files); } @@ -38,7 +38,7 @@ function findByExt(dir: string, ext: string): string[] { describe("validation conflicts", () => { let proj: string; beforeAll(() => { - proj = mkProject(npmProject({ name: "vproj" }, { express: "4.18.2" })); + proj = mkProject(npmProject({ name: "vproj" }, { [SAFE_PKG]: "1.0.0" })); }); afterAll(() => rmProject(proj)); @@ -71,14 +71,17 @@ describe("validation conflicts", () => { }); describe("output channels", () => { - // Terminal default: human-readable scan summary on stdout, exit 0. + // Terminal default: human-readable scan output on stdout, exit 0. The fixture + // resolves a CVE-free name so the clean exit does not depend on the advisory DB + // state (issue #726); the assertions key off markers the terminal writer always + // prints, not the "Summary" block that only renders when findings exist. it("terminal default (--offline) prints human-readable content to stdout", () => { - const proj = mkProject(npmProject({ name: "termproj" }, { express: "4.18.2" })); + const proj = mkProject(npmProject({ name: "termproj" }, { [SAFE_PKG]: "1.0.0" })); try { const r = runCli([proj, "--offline"], { env: CI_ENV }); expect(r.status).toBe(0); expect(r.stdout).toContain("CVE Lite CLI"); - expect(r.stdout).toContain("Summary"); + expect(r.stdout).toContain("Scan complete"); // Not JSON: stdout should not parse as a JSON document. expect(() => JSON.parse(r.stdout)).toThrow(); } finally {