Files
Portfolio/tests/integration/release-package.test.ts

97 lines
3.5 KiB
TypeScript

import { mkdtemp, readFile, unlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import {
byteDigest,
canonicalValueBytes,
} from "../../src/security/canonical-digest.ts";
import {
buildReleasePackage,
buildReleasePackageForTest,
} from "../../scripts/build-release.mjs";
import { parseDeterministicTar } from "../../scripts/deploy/lib/release-common.mjs";
import { createReleaseFixture } from "./release-test-fixture.ts";
function buildInput(
fixture: Awaited<ReturnType<typeof createReleaseFixture>>,
outputPath: string,
) {
return {
distDir: fixture.distDir,
signedManifestPath: fixture.signedManifestPath,
sbomPath: fixture.sbomPath,
trustPolicyPath: fixture.trustPolicyPath,
verifySignedReleaseEnvelope: fixture.verifySignedReleaseEnvelope,
protectedState: fixture.protectedState,
now: "2026-09-02T01:00:00Z",
outputPath,
};
}
describe("deterministic exact-manifest release package", () => {
test("builds byte-identical archives containing only the signed closure and release metadata", async () => {
const root = await mkdtemp(join(tmpdir(), "portfolio-package-"));
const fixture = await createReleaseFixture(root);
const first = join(root, "release-one.tar");
const second = join(root, "release-two.tar");
await buildReleasePackageForTest(buildInput(fixture, first));
await buildReleasePackageForTest(buildInput(fixture, second));
expect(byteDigest(await readFile(first))).toBe(
byteDigest(await readFile(second)),
);
expect(
parseDeterministicTar(await readFile(first)).map((file) => file.path),
).toEqual([
".release/package-index.json",
".release/sbom.cdx.json",
".release/signed-release-manifest.json",
"assets/app.js",
"index.html",
]);
});
test("fails closed on extra, substituted, or invalidly signed inputs", async () => {
const root = await mkdtemp(join(tmpdir(), "portfolio-package-invalid-"));
const fixture = await createReleaseFixture(root);
await writeFile(join(fixture.distDir, "extra.txt"), "not in manifest");
await expect(
buildReleasePackageForTest(buildInput(fixture, join(root, "extra.tar"))),
).rejects.toThrow("exactly match");
await unlink(join(fixture.distDir, "extra.txt"));
await writeFile(join(fixture.distDir, "index.html"), "substituted");
await expect(
buildReleasePackageForTest(
buildInput(fixture, join(root, "substituted.tar")),
),
).rejects.toThrow("hash");
await writeFile(
join(fixture.distDir, "index.html"),
"<!doctype html><title>Portfolio</title>\n",
);
const envelope = JSON.parse(
await readFile(fixture.signedManifestPath, "utf8"),
);
envelope.signature.value = `${envelope.signature.value[0] === "A" ? "B" : "A"}${envelope.signature.value.slice(1)}`;
await writeFile(fixture.signedManifestPath, canonicalValueBytes(envelope));
await expect(
buildReleasePackageForTest(
buildInput(fixture, join(root, "invalid.tar")),
),
).rejects.toThrow("signature");
});
test("production package requests reject caller-selected verifier, policy, and clock fields", async () => {
const root = await mkdtemp(join(tmpdir(), "portfolio-package-authority-"));
const fixture = await createReleaseFixture(root);
await expect(
buildReleasePackage(
buildInput(fixture, join(root, "release.tar")) as any,
),
).rejects.toThrow("rejects caller");
});
});