Files
clean-architecture-frontend…/tests/unit/supply-chain.test.ts
T

830 lines
27 KiB
TypeScript

import { createHash, generateKeyPairSync, sign } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import {
diffDependencyInventories,
isValidSha512Integrity,
parsePnpmLockfilePackages,
supplyChainDigest,
validateDependencyReview,
validateLicensePolicy,
} from "../../scripts/lib/supply-chain.ts";
import { digestReleaseInputFiles } from "../../scripts/lib/release-input-evidence.ts";
import { findSecretMatches } from "../../scripts/lib/secret-scan.ts";
import {
parseSecretScanIncludedPaths,
selectIncludedInventoryFiles,
} from "../../scripts/lib/secret-scan-policy.ts";
import { checkSecurityFixtures } from "../../scripts/lib/security-fixture-check.ts";
import {
evaluatePromotionEvidence,
providerEvidenceSignaturePayload,
} from "../../scripts/lib/provider-evidence.ts";
import {
createReleaseCandidateManifest,
RELEASE_CANDIDATE_EVIDENCE_PATHS,
verifyReleaseCandidate,
} from "../../scripts/lib/release-candidate.ts";
import { deterministicSupplyChainGeneratedAt } from "../../scripts/lib/supply-chain-time.ts";
import { verifyPromotionInputs } from "../../scripts/lib/promotion-verifier.ts";
const integrity = `sha512-${Buffer.alloc(64, 7).toString("base64")}`;
const dependency = {
name: "fixture",
version: "1.0.0",
direct: true,
scope: "production",
optional: false,
license: "MIT",
integrity,
dependencies: [],
};
const candidateDistSha256 = "1".repeat(64);
const lockfileSha256 = "2".repeat(64);
function signedProviderEvidence(
value: Record<string, unknown>,
keyId: string,
privateKey: ReturnType<typeof generateKeyPairSync>["privateKey"],
) {
return {
...value,
signature: {
algorithm: "Ed25519",
keyId,
value: sign(
null,
providerEvidenceSignaturePayload(value),
privateKey,
).toString("base64"),
},
};
}
async function createMinimalCandidateTree(root: string) {
const rawLockfile = "lockfileVersion: '9.0'\n";
const rawLockfileSha256 = createHash("sha256")
.update(rawLockfile)
.digest("hex");
await mkdir(path.join(root, "dist"), { recursive: true });
await writeFile(path.join(root, "dist/app.js"), "immutable\n");
await writeFile(path.join(root, "pnpm-lock.yaml"), rawLockfile);
for (const file of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (file === "pnpm-lock.yaml") continue;
await mkdir(path.dirname(path.join(root, file)), { recursive: true });
const value =
file === "artifacts/release/dependency-inventory.json"
? { lockfileSha256: rawLockfileSha256 }
: file === "artifacts/security/supply-chain-verification.json"
? { localStatus: "PASS" }
: { fixture: file };
await writeFile(path.join(root, file), `${JSON.stringify(value)}\n`);
}
const manifest = await createReleaseCandidateManifest(root);
await writeFile(
path.join(root, "artifacts/release/release-candidate.json"),
`${JSON.stringify(manifest)}\n`,
);
return manifest;
}
async function writeProviderEnvironment(
root: string,
distDigest: string,
candidateLockfileSha256: string,
) {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const vulnerabilityReport = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-vulnerability-provider",
generatedAt: "2026-08-01T00:00:00.000Z",
scannedLockfileSha256: candidateLockfileSha256,
scannedDistSha256: distDigest,
findings: [],
},
"fixture-vulnerability-key",
vulnerabilityKeys.privateKey,
);
const provenanceAttestation = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-provenance-provider",
signer: "fixture-workload-identity",
generatedAt: "2026-08-01T00:00:00.000Z",
subject: { name: "dist", digest: { sha256: distDigest } },
},
"fixture-provenance-key",
provenanceKeys.privateKey,
);
await mkdir(path.join(root, "provider"), { recursive: true });
await Promise.all([
writeFile(
path.join(root, "provider/vulnerability.json"),
`${JSON.stringify(vulnerabilityReport)}\n`,
),
writeFile(
path.join(root, "provider/provenance.json"),
`${JSON.stringify(provenanceAttestation)}\n`,
),
writeFile(
path.join(root, "provider/vulnerability.pem"),
vulnerabilityKeys.publicKey
.export({ type: "spki", format: "pem" })
.toString(),
),
writeFile(
path.join(root, "provider/provenance.pem"),
provenanceKeys.publicKey
.export({ type: "spki", format: "pem" })
.toString(),
),
]);
return {
VULNERABILITY_REPORT_PATH: "provider/vulnerability.json",
PROVENANCE_ATTESTATION_PATH: "provider/provenance.json",
VULNERABILITY_PUBLIC_KEY_PATH: "provider/vulnerability.pem",
VULNERABILITY_KEY_ID: "fixture-vulnerability-key",
PROVENANCE_PUBLIC_KEY_PATH: "provider/provenance.pem",
PROVENANCE_KEY_ID: "fixture-provenance-key",
} satisfies NodeJS.ProcessEnv;
}
describe("supply-chain policy", () => {
it("wires candidate files, PEM trust, env report paths, and mutation checks", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-wiring-"));
try {
const manifest = await createMinimalCandidateTree(root);
const validEnvironment = await writeProviderEnvironment(
root,
manifest.distSha256,
manifest.lockfileSha256,
);
const acceptLocalEvidence = async () => ({
status: "PASS" as const,
failures: [] as const,
});
const valid = await verifyPromotionInputs({
repositoryRoot: root,
environment: validEnvironment,
verifyLocalEvidence: acceptLocalEvidence,
});
const absent = await verifyPromotionInputs({
repositoryRoot: root,
environment: {},
verifyLocalEvidence: acceptLocalEvidence,
});
const wrongEnvironment = await writeProviderEnvironment(
root,
"3".repeat(64),
manifest.lockfileSha256,
);
const wrongDigest = await verifyPromotionInputs({
repositoryRoot: root,
environment: wrongEnvironment,
verifyLocalEvidence: acceptLocalEvidence,
});
await writeFile(path.join(root, "dist/app.js"), "mutated\n");
const postAttestationMutation = await verifyPromotionInputs({
repositoryRoot: root,
environment: validEnvironment,
verifyLocalEvidence: acceptLocalEvidence,
});
expect({
valid: valid.status,
absent: absent.status,
wrongDigest: wrongDigest.status,
postAttestationMutation: postAttestationMutation.status,
}).toEqual({
valid: "PASS",
absent: "FAIL_UNVERIFIED",
wrongDigest: "FAIL_UNVERIFIED",
postAttestationMutation: "FAIL_UNVERIFIED",
});
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("rejects a self-consistent candidate that merely claims localStatus PASS", async () => {
const root = await mkdtemp(path.join(tmpdir(), "promotion-local-status-"));
try {
const manifest = await createMinimalCandidateTree(root);
const environment = await writeProviderEnvironment(
root,
manifest.distSha256,
manifest.lockfileSha256,
);
const localVerificationPath = path.join(
root,
"artifacts/security/supply-chain-verification.json",
);
const before = await readFile(localVerificationPath, "utf8");
const result = await verifyPromotionInputs({
repositoryRoot: root,
environment,
});
expect(result.status).toBe("FAIL_UNVERIFIED");
expect(result.failures).toEqual(
expect.arrayContaining([
expect.stringMatching(/executable schema mismatch/u),
"local supply-chain evidence is not PASS",
]),
);
expect(await readFile(localVerificationPath, "utf8")).toBe(before);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("derives a stable supply-chain timestamp from the immutable build epoch", () => {
const input = {
generatedAt: "2026-08-01T00:00:00.000Z",
sourceDateEpoch: "1785542400",
};
expect(deterministicSupplyChainGeneratedAt(input)).toBe(
"2026-08-01T00:00:00.000Z",
);
expect(deterministicSupplyChainGeneratedAt(input)).toBe(
deterministicSupplyChainGeneratedAt({ ...input }),
);
expect(() =>
deterministicSupplyChainGeneratedAt({
generatedAt: "not-a-time",
sourceDateEpoch: "1785542400",
}),
).toThrow(/generatedAt/u);
expect(() =>
deterministicSupplyChainGeneratedAt({
generatedAt: "2026-08-01T00:00:00.000Z",
sourceDateEpoch: "1785542401",
}),
).toThrow(/SOURCE_DATE_EPOCH/u);
});
it("rejects release candidate dist bytes changed after manifest creation", async () => {
const root = await mkdtemp(path.join(tmpdir(), "release-candidate-"));
try {
const rawLockfile = "lockfileVersion: '9.0'\n";
const rawLockfileSha256 = createHash("sha256")
.update(rawLockfile)
.digest("hex");
await mkdir(path.join(root, "dist/.vite"), { recursive: true });
await writeFile(path.join(root, "dist/app.js"), "immutable\n");
await writeFile(path.join(root, "dist/.vite/metadata.json"), "{}\n");
await writeFile(path.join(root, "pnpm-lock.yaml"), rawLockfile);
for (const file of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (file === "pnpm-lock.yaml") continue;
await mkdir(path.dirname(path.join(root, file)), { recursive: true });
await writeFile(
path.join(root, file),
file === "artifacts/release/dependency-inventory.json"
? `${JSON.stringify({ lockfileSha256: rawLockfileSha256 })}\n`
: `${file}\n`,
);
}
const manifest = await createReleaseCandidateManifest(root);
expect(manifest.lockfileSha256).toBe(rawLockfileSha256);
expect(manifest.files).toContainEqual(
expect.objectContaining({
path: "pnpm-lock.yaml",
sha256: rawLockfileSha256,
}),
);
expect(await createReleaseCandidateManifest(root)).toEqual(manifest);
expect((await verifyReleaseCandidate(manifest, root)).failures).toEqual(
[],
);
await writeFile(path.join(root, "dist/app.js"), "mutated\n");
expect(
(await verifyReleaseCandidate(manifest, root)).failures,
).toEqual(
expect.arrayContaining([
"release candidate dist digest mismatch",
"release candidate bundle digest mismatch",
"release candidate file set or file digest mismatch",
]),
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("rejects a dependency inventory digest that differs from raw pnpm-lock bytes", async () => {
const root = await mkdtemp(path.join(tmpdir(), "release-lockfile-"));
try {
await mkdir(path.join(root, "dist"), { recursive: true });
await writeFile(path.join(root, "dist/app.js"), "immutable\n");
await writeFile(path.join(root, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
for (const file of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (file === "pnpm-lock.yaml") continue;
await mkdir(path.dirname(path.join(root, file)), { recursive: true });
await writeFile(
path.join(root, file),
file === "artifacts/release/dependency-inventory.json"
? `${JSON.stringify({ lockfileSha256 })}\n`
: `${file}\n`,
);
}
await expect(createReleaseCandidateManifest(root)).rejects.toThrow(
/raw pnpm-lock digest mismatch/u,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("fails promotion when external provider evidence is absent", () => {
const result = evaluatePromotionEvidence({
candidate: {
distSha256: candidateDistSha256,
lockfileSha256,
},
currentDistSha256: candidateDistSha256,
localStatus: "PASS",
vulnerabilityReport: null,
provenanceAttestation: null,
vulnerabilityTrust: null,
provenanceTrust: null,
});
expect(result.status).toBe("FAIL_UNVERIFIED");
});
it("passes only signed provider evidence for the exact immutable candidate", () => {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const vulnerabilityReport = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-vulnerability-provider",
generatedAt: "2026-08-01T00:00:00.000Z",
scannedLockfileSha256: lockfileSha256,
scannedDistSha256: candidateDistSha256,
findings: [],
},
"fixture-vulnerability-key",
vulnerabilityKeys.privateKey,
);
const provenanceAttestation = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-provenance-provider",
signer: "fixture-workload-identity",
generatedAt: "2026-08-01T00:00:00.000Z",
subject: {
name: "dist",
digest: { sha256: candidateDistSha256 },
},
},
"fixture-provenance-key",
provenanceKeys.privateKey,
);
const result = evaluatePromotionEvidence({
candidate: {
distSha256: candidateDistSha256,
lockfileSha256,
},
currentDistSha256: candidateDistSha256,
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: {
keyId: "fixture-vulnerability-key",
publicKey: vulnerabilityKeys.publicKey,
},
provenanceTrust: {
keyId: "fixture-provenance-key",
publicKey: provenanceKeys.publicKey,
},
});
expect(result).toMatchObject({
status: "PASS",
vulnerabilityStatus: "PASS",
provenanceAttestationStatus: "PASS",
failures: [],
});
});
it("rejects correctly signed provider evidence for a different digest", () => {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const wrongDistSha256 = "3".repeat(64);
const vulnerabilityReport = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-vulnerability-provider",
generatedAt: "2026-08-01T00:00:00.000Z",
scannedLockfileSha256: lockfileSha256,
scannedDistSha256: wrongDistSha256,
findings: [],
},
"fixture-vulnerability-key",
vulnerabilityKeys.privateKey,
);
const provenanceAttestation = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-provenance-provider",
signer: "fixture-workload-identity",
generatedAt: "2026-08-01T00:00:00.000Z",
subject: { name: "dist", digest: { sha256: wrongDistSha256 } },
},
"fixture-provenance-key",
provenanceKeys.privateKey,
);
const result = evaluatePromotionEvidence({
candidate: {
distSha256: candidateDistSha256,
lockfileSha256,
},
currentDistSha256: candidateDistSha256,
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: {
keyId: "fixture-vulnerability-key",
publicKey: vulnerabilityKeys.publicKey,
},
provenanceTrust: {
keyId: "fixture-provenance-key",
publicKey: provenanceKeys.publicKey,
},
});
expect(result.status).toBe("FAIL_UNVERIFIED");
expect(result.failures).toEqual(
expect.arrayContaining([
"vulnerability report dist digest mismatch",
"provenance attestation dist digest mismatch",
]),
);
});
it("rejects candidate bytes changed after provider attestation", () => {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const vulnerabilityReport = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-vulnerability-provider",
generatedAt: "2026-08-01T00:00:00.000Z",
scannedLockfileSha256: lockfileSha256,
scannedDistSha256: candidateDistSha256,
findings: [],
},
"fixture-vulnerability-key",
vulnerabilityKeys.privateKey,
);
const provenanceAttestation = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-provenance-provider",
signer: "fixture-workload-identity",
generatedAt: "2026-08-01T00:00:00.000Z",
subject: {
name: "dist",
digest: { sha256: candidateDistSha256 },
},
},
"fixture-provenance-key",
provenanceKeys.privateKey,
);
const result = evaluatePromotionEvidence({
candidate: {
distSha256: candidateDistSha256,
lockfileSha256,
},
currentDistSha256: "4".repeat(64),
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: {
keyId: "fixture-vulnerability-key",
publicKey: vulnerabilityKeys.publicKey,
},
provenanceTrust: {
keyId: "fixture-provenance-key",
publicKey: provenanceKeys.publicKey,
},
});
expect(result.status).toBe("FAIL_UNVERIFIED");
expect(result.failures).toContain(
"candidate dist bytes changed after immutable build",
);
});
it("rejects Ed448 keys mislabeled as Ed25519 evidence", () => {
const vulnerabilityKeys = generateKeyPairSync("ed448");
const provenanceKeys = generateKeyPairSync("ed448");
const vulnerabilityReport = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-vulnerability-provider",
generatedAt: "2026-08-01T00:00:00.000Z",
scannedLockfileSha256: lockfileSha256,
scannedDistSha256: candidateDistSha256,
findings: [],
},
"fixture-vulnerability-key",
vulnerabilityKeys.privateKey,
);
const provenanceAttestation = signedProviderEvidence(
{
schemaVersion: 1,
provider: "fixture-provenance-provider",
signer: "fixture-workload-identity",
generatedAt: "2026-08-01T00:00:00.000Z",
subject: {
name: "dist",
digest: { sha256: candidateDistSha256 },
},
},
"fixture-provenance-key",
provenanceKeys.privateKey,
);
expect(
evaluatePromotionEvidence({
candidate: { distSha256: candidateDistSha256, lockfileSha256 },
currentDistSha256: candidateDistSha256,
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: {
keyId: "fixture-vulnerability-key",
publicKey: vulnerabilityKeys.publicKey,
},
provenanceTrust: {
keyId: "fixture-provenance-key",
publicKey: provenanceKeys.publicKey,
},
}).status,
).toBe("FAIL_UNVERIFIED");
});
it.each([
["empty", []],
["empty entry", [""]],
["blank entry", [" "]],
["absolute", ["/src"]],
["backslash", ["src\\file.ts"]],
["dot", ["."]],
["dotdot", [".."]],
["traversal", ["src/../docs"]],
["trailing slash", ["src/"]],
["mixed", ["src", 42]],
["duplicate", ["src", "src"]],
])("rejects %s secret-scan include paths", (_name, includedPaths) => {
expect(() => parseSecretScanIncludedPaths(includedPaths)).toThrow();
});
it("requires every configured include path to match the inventory", () => {
expect(
selectIncludedInventoryFiles(
["README.md", "src/app.ts"],
["src"],
),
).toEqual(["src/app.ts"]);
expect(() =>
selectIncludedInventoryFiles(
["README.md", "src/app.ts"],
["misspelled"],
),
).toThrow(/misspelled/u);
expect(
selectIncludedInventoryFiles(
["README.md", "src/app.ts"],
null,
),
).toEqual(["README.md", "src/app.ts"]);
});
it("rejects a crashed fixture scan and cannot reuse a stale repository artifact", async () => {
const cleaned: string[] = [];
await expect(
checkSecurityFixtures({
createTempDirectory: async () => "/tmp/fresh-security-fixture",
runScan: () => ({
status: 1,
signal: null,
stdout: "",
stderr: "Security scan found 3 blocking result(s).\n",
}),
readArtifact: async (artifactPath) => {
expect(artifactPath).toBe(
"/tmp/fresh-security-fixture/scan-fixture.sarif",
);
throw Object.assign(new Error("fresh artifact missing"), {
code: "ENOENT",
});
},
cleanup: async (directory) => {
cleaned.push(directory);
},
}),
).rejects.toThrow(/fresh artifact missing/u);
expect(cleaned).toEqual(["/tmp/fresh-security-fixture"]);
await expect(
checkSecurityFixtures({
createTempDirectory: async () => "/tmp/fresh-security-fixture",
runScan: () => ({
status: null,
signal: "SIGTERM",
stdout: "",
stderr: "Security scan found 3 blocking result(s).\n",
}),
readArtifact: async () => "{}",
cleanup: async () => undefined,
}),
).rejects.toThrow(/did not fail exactly/u);
});
it("wires the exact security fixture checker as a passing CI gate", async () => {
const gates = JSON.parse(await readFile("config/ci/gates.json", "utf8")) as {
gates: Record<string, { steps: unknown[]; evidence: string[] }>;
};
const securityGate = gates.gates["FE-GATE-013"]!;
expect(securityGate.steps).toContainEqual({
script: "check:security:fixtures",
expect: "pass",
});
expect(securityGate.steps).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ script: "scan:security:fixture" }),
]),
);
expect(securityGate.evidence).not.toContain(
"artifacts/security/scan-fixture.sarif",
);
});
it("uses one fail-closed repository inventory for provenance and secret scanning", async () => {
const [provenanceSource, securityCliSource, securityEvaluatorSource] =
await Promise.all([
readFile("scripts/generate-supply-chain.ts", "utf8"),
readFile("scripts/security-scan.ts", "utf8"),
readFile("scripts/lib/secret-scan-evaluator.ts", "utf8"),
]);
expect(securityCliSource).toContain("evaluateRepositorySecretScan");
for (const source of [provenanceSource, securityEvaluatorSource]) {
expect(source).toContain("buildRepositoryFileInventory");
expect(source).not.toContain("async function filesWithin");
}
});
it("binds provenance digest behavior to tracked files outside policy roots", async () => {
const contents = new Map([
["src/app.ts", Buffer.from("app\n")],
["README.md", Buffer.from("one\n")],
]);
const first = await digestReleaseInputFiles(
["README.md", "src/app.ts"],
async (file) => contents.get(file)!,
);
contents.set("README.md", Buffer.from("two\n"));
const second = await digestReleaseInputFiles(
["README.md", "src/app.ts"],
async (file) => contents.get(file)!,
);
expect(second).not.toBe(first);
});
it("detects every forbidden secret fixture, including quoted JSON keys", async () => {
const fixtureRoot = "tests/fixtures/security/secret-detection/forbidden";
const findings = (
await Promise.all(
["source.ts", "dist.ts", "config.json"].map(async (file) =>
findSecretMatches(
`${fixtureRoot}/${file}`,
await readFile(`${fixtureRoot}/${file}`, "utf8"),
),
),
)
).flat();
expect(findings.map((finding) => [finding.file, finding.ruleId])).toEqual([
[`${fixtureRoot}/source.ts`, "aws-access-key"],
[`${fixtureRoot}/dist.ts`, "assigned-secret"],
[`${fixtureRoot}/config.json`, "assigned-secret"],
]);
});
it("covers every mandatory release input in the secret scan policy", async () => {
const policy = JSON.parse(
await readFile("config/security/secret-scan-policy.json", "utf8"),
) as { trackedRoots: string[] };
expect(policy.trackedRoots).toEqual(
expect.arrayContaining([
"index.html",
".dependency-cruiser.json",
".nvmrc",
".npmrc",
"eslint.config.ts",
"package.json",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
"scripts",
"schemas",
"config",
".gitea/workflows/quality-gates.yml",
"vite.config.ts",
"vite.service-worker.config.ts",
"vitest.config.ts",
"playwright.config.ts",
"playwright.capabilities.config.ts",
"playwright.dev.config.ts",
"playwright.storybook.config.ts",
"playwright.visual.config.ts",
"tsconfig.json",
"tsconfig.app.json",
"tsconfig.base.json",
"tsconfig.node.json",
"tsconfig.recipes.json",
"tsconfig.service-worker.json",
"tsconfig.test.json",
"tsconfig.web-worker.json",
]),
);
});
it("parses every top-level lockfile package and validates SRI", () => {
const parsed = parsePnpmLockfilePackages(`
packages:
'@scope/one@1.0.0':
resolution: {integrity: ${integrity}}
two@2.0.0:
resolution: {integrity: ${integrity}}
snapshots:
`);
expect(parsed).toEqual([
{ name: "@scope/one", version: "1.0.0", integrity },
{ name: "two", version: "2.0.0", integrity },
]);
expect(parsed.every((entry) => isValidSha512Integrity(entry.integrity))).toBe(
true,
);
});
it("keeps inventory digests stable when dependency ordering changes", () => {
const other = { ...dependency, name: "other" };
expect(supplyChainDigest([dependency, other])).toBe(
supplyChainDigest([other, dependency]),
);
});
it("calculates actual additions and requires independent high-risk review", () => {
const before = { dependencies: [] };
const after = { dependencies: [dependency] };
const diff = diffDependencyInventories(before, after);
expect(diff.added).toEqual(["fixture@1.0.0"]);
expect(
validateDependencyReview(diff, after, {
changes: [
{
changeId: "add:fixture@1.0.0",
owner: "one",
reviewer: "one",
reason: "fixture",
rollback: "remove",
},
],
}).passed,
).toBe(false);
});
it("allows explicit policy licenses and rejects denied licenses", () => {
expect(
validateLicensePolicy(
{ dependencies: [dependency] },
{ allowedLicenses: ["MIT"], deniedLicensePatterns: ["AGPL"] },
).passed,
).toBe(true);
expect(
validateLicensePolicy(
{
dependencies: [{ ...dependency, license: "AGPL-3.0" }],
},
{ allowedLicenses: ["MIT"], deniedLicensePatterns: ["AGPL"] },
).passed,
).toBe(false);
});
});