Files

134 lines
5.9 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { join } from "node:path";
import { readFile } from "node:fs/promises";
import { checkProject } from "../lib/check.js";
import { DEFAULT_POLICY } from "../lib/constants.js";
import { jsonResponse, makeProject, packageDocument, passingRun } from "./helpers.js";
test("check runs only safe npm commands in the required order and writes evidence", async () => {
const directory = await makeProject();
const reportPath = join(directory, "evidence", "report.json");
const calls = [];
const document = packageDocument();
const gitHead = document.versions["1.2.3"].gitHead;
const fetch = async (url) => {
if (url.startsWith("https://registry.npmjs.org/alpha")) return jsonResponse(document);
throw new Error(`unexpected URL ${url}`);
};
const report = await checkProject({ path: directory, report: reportPath, policy: structuredClone(DEFAULT_POLICY) }, {
fetch,
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3" }],
run: passingRun(calls),
now: () => Date.parse("2025-01-01T00:00:00Z"),
});
assert.equal(report.status, "pass");
assert.deepEqual(calls.map((call) => call.slice(0, 3)), [
["npm", "pack", "alpha@1.2.3"],
["npm", "ci", "--ignore-scripts"],
["npm", "audit", "signatures"],
["npm", "audit", "--omit=dev"],
]);
const saved = JSON.parse(await readFile(reportPath, "utf8"));
assert.equal(saved.commands.length, 4);
assert.equal(saved.status, "pass");
});
test("check records npm pack size violations without installing", async () => {
const directory = await makeProject();
const reportPath = join(directory, "report.json");
const document = packageDocument();
const gitHead = document.versions["1.2.3"].gitHead;
const report = await checkProject({ path: directory, report: reportPath, policy: { ...structuredClone(DEFAULT_POLICY), maxTarballSizeBytes: 10 }, skipInstall: true }, {
fetch: async (url) => {
if (url.startsWith("https://registry.npmjs.org/alpha")) return jsonResponse(document);
throw new Error(`unexpected URL ${url}`);
},
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3" }],
run: passingRun(),
now: () => Date.parse("2025-01-01T00:00:00Z"),
});
assert.equal(report.status, "fail");
assert.ok(report.failures.some((message) => message.includes("tarball size")));
assert.equal(report.commands.length, 1);
});
test("check applies an archive override only to its reviewed package version", async () => {
const directory = await makeProject();
const document = packageDocument();
const gitHead = document.versions["1.2.3"].gitHead;
const policy = { ...structuredClone(DEFAULT_POLICY), maxTarballSizeBytes: 10 };
policy.archiveSizeExceptions.alpha = {
version: "1.2.3",
maxTarballSizeBytes: 100,
reason: "Reviewed archive is larger than the default project limit.",
expiresAt: "2025-02-01T00:00:00Z",
};
const report = await checkProject({ path: directory, report: join(directory, "report.json"), policy, skipInstall: true }, {
fetch: async () => jsonResponse(document),
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3" }],
run: passingRun(),
now: () => Date.parse("2025-01-01T00:00:00Z"),
});
assert.equal(report.status, "pass");
});
test("check parses full npm pack JSON while truncating report evidence", async () => {
const directory = await makeProject();
const reportPath = join(directory, "report.json");
const document = packageDocument();
const gitHead = document.versions["1.2.3"].gitHead;
const packOutput = JSON.stringify([{
size: 100,
unpackedSize: 500,
files: Array.from({ length: 2_000 }, (_, index) => ({ path: `dist/file-${index}.js`, size: 1 })),
}]);
assert.ok(packOutput.length > 20_000);
const report = await checkProject({ path: directory, report: reportPath, policy: structuredClone(DEFAULT_POLICY), skipInstall: true }, {
fetch: async (url) => {
if (url.startsWith("https://registry.npmjs.org/alpha")) return jsonResponse(document);
throw new Error(`unexpected URL ${url}`);
},
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3" }],
run: async (_command, _args, options) => {
assert.equal(options.maxOutputBytes, 25_000_000);
return { code: 0, stdout: packOutput, stderr: "", timedOut: false };
},
now: () => Date.parse("2025-01-01T00:00:00Z"),
});
assert.equal(report.status, "pass");
assert.equal(report.packages[0].tarballSizeBytes, 100);
assert.equal(report.commands[0].stdout.length, 20_000);
});
test("check accepts only explicitly reviewed npm audit advisories", async () => {
const directory = await makeProject();
const document = packageDocument();
const gitHead = document.versions["1.2.3"].gitHead;
const policy = structuredClone(DEFAULT_POLICY);
policy.auditExceptions["GHSA-1234-5678-9ABC"] = {
reason: "Reviewed existing advisory pending a separately tracked upgrade.",
expiresAt: "2025-02-01T00:00:00Z",
};
const run = async (_command, args) => {
if (args[0] === "pack") return { code: 0, stdout: JSON.stringify([{ size: 100, unpackedSize: 500 }]), stderr: "", timedOut: false };
if (args.includes("--json")) return {
code: 1,
stdout: JSON.stringify({ vulnerabilities: { alpha: { via: [{ url: "https://github.com/advisories/GHSA-1234-5678-9abc" }] } } }),
stderr: "",
timedOut: false,
};
return { code: 0, stdout: "ok", stderr: "", timedOut: false };
};
const report = await checkProject({ path: directory, report: join(directory, "report.json"), policy }, {
fetch: async () => jsonResponse(document),
remoteRefs: async () => [{ sha: gitHead, ref: "refs/tags/v1.2.3" }],
run,
now: () => Date.parse("2025-01-01T00:00:00Z"),
});
assert.equal(report.status, "pass");
assert.ok(report.warnings.some((warning) => warning.includes("GHSA-1234-5678-9ABC")));
});