118 lines
4.0 KiB
TypeScript
118 lines
4.0 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import { expect } from "vitest";
|
|
|
|
import { processTest as test } from "../../support/process/processFixture.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
test("generates locked inventory and rejects stale notice metadata or preserved texts", async ({
|
|
processes,
|
|
}) => {
|
|
const root = await createTestTempDirectory("rea-notice-records-");
|
|
await mkdir(resolve(root, "scripts/lib"), { recursive: true });
|
|
await mkdir(resolve(root, "licenses"));
|
|
for (const file of [
|
|
"scripts/generate-third-party-inventory.mjs",
|
|
"scripts/lib/generated-file.mjs",
|
|
])
|
|
await copyFile(resolve(file), resolve(root, file));
|
|
const script = resolve(root, "scripts/generate-third-party-inventory.mjs");
|
|
const runtime = {
|
|
version: "1.0.0",
|
|
license: "MIT",
|
|
resolved:
|
|
"https://registry.npmjs.org/fixture-runtime/-/fixture-runtime-1.0.0.tgz",
|
|
integrity: "sha512-fixture-integrity",
|
|
optional: true,
|
|
};
|
|
await writeFile(
|
|
resolve(root, "package-lock.json"),
|
|
JSON.stringify({
|
|
packages: {
|
|
"": { name: "fixture", version: "1.0.0" },
|
|
"node_modules/fixture-runtime": runtime,
|
|
"node_modules/fixture-dev": { ...runtime, dev: true, optional: false },
|
|
},
|
|
}),
|
|
);
|
|
const generated = await processes.run(process.execPath, [script]);
|
|
expect(generated.exitCode, generated.stderr).toBe(0);
|
|
const inventory = await readFile(
|
|
resolve(root, "licenses/npm-dependencies.txt"),
|
|
"utf8",
|
|
);
|
|
expect(inventory).toContain("Locked package entries: 2; runtime: 1.");
|
|
expect(inventory).toContain(
|
|
"fixture-runtime@1.0.0 | runtime, optional | MIT",
|
|
);
|
|
expect(inventory).toContain("fixture-dev@1.0.0 | development | MIT");
|
|
const fingerprint = (
|
|
JSON.parse(generated.stdout) as { runtime_dependency_fingerprint: string }
|
|
).runtime_dependency_fingerprint;
|
|
const notice = [
|
|
`Runtime dependency fingerprint (SHA-256): ${fingerprint}`,
|
|
"",
|
|
"===== fixture-runtime@1.0.0 =====",
|
|
"Declared license: MIT",
|
|
`Archive: ${runtime.resolved}`,
|
|
`Integrity: ${runtime.integrity}`,
|
|
"",
|
|
"Fixture text; this test does not establish real package attribution.",
|
|
"",
|
|
].join("\n");
|
|
const noticePath = resolve(root, "licenses/npm-runtime-notices.txt");
|
|
const referencePath = resolve(root, "licenses/reference.txt");
|
|
await writeFile(noticePath, notice);
|
|
await writeFile(referencePath, "preserved fixture text\n");
|
|
await writeFile(
|
|
resolve(root, "licenses/upstream-sources.json"),
|
|
JSON.stringify([
|
|
{
|
|
file: "licenses/reference.txt",
|
|
sha256: createHash("sha256")
|
|
.update("preserved fixture text\n")
|
|
.digest("hex"),
|
|
},
|
|
]),
|
|
);
|
|
const check = () => processes.run(process.execPath, [script, "--check"]);
|
|
const valid = await check();
|
|
expect(valid.exitCode, valid.stderr).toBe(0);
|
|
for (const [changed, diagnostic] of [
|
|
[
|
|
notice.replace(
|
|
"===== fixture-runtime@1.0.0 =====",
|
|
"===== another-package@1.0.0 =====",
|
|
),
|
|
"Missing runtime notice",
|
|
],
|
|
[
|
|
notice.replace(runtime.integrity, "sha512-different"),
|
|
"Runtime notice metadata differs",
|
|
],
|
|
[
|
|
notice.replace(fingerprint, "0".repeat(64)),
|
|
"Runtime notice fingerprint differs",
|
|
],
|
|
[
|
|
notice + notice.slice(notice.indexOf("===== ")),
|
|
"Duplicate runtime notice",
|
|
],
|
|
] as const) {
|
|
await writeFile(noticePath, changed);
|
|
const rejected = await check();
|
|
expect(rejected.exitCode).toBe(1);
|
|
expect(rejected.stderr).toContain(diagnostic);
|
|
}
|
|
await writeFile(noticePath, notice);
|
|
await writeFile(referencePath, "changed fixture text\n");
|
|
const tampered = await check();
|
|
expect(tampered.exitCode).toBe(1);
|
|
expect(tampered.stderr).toContain("Preserved notice digest differs");
|
|
expect(
|
|
await readFile(resolve(root, "licenses/npm-dependencies.txt"), "utf8"),
|
|
).toBe(inventory);
|
|
});
|