// @vitest-environment node import { createHash } from "node:crypto"; import { copyFile, cp, mkdir, mkdtemp, readFile, rm, writeFile, } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { buildPerlPack, installPerlPack, PERL_ENGINE_LOCK, verifyPerlPack, } from "./perl-engine-pack.mjs"; const temporaryDirectories: string[] = []; const installedPack = path.resolve("public", "engines", "perl"); const packFiles = [ "LICENSE-Devel-StackTrace-Artistic-2.0.txt", "LICENSE-Emscripten.txt", "LICENSE-WebPerl-Artistic.txt", "LICENSE-WebPerl-GPL.txt", "NOTICE.txt", "SHA256SUMS", "emperl.data", "emperl.js", "emperl.wasm", "engine-metadata.json", "perl-runtime.worker.js", "regex_tools_bridge.pl", ] as const; afterEach(async () => { await Promise.all( temporaryDirectories .splice(0) .map((directory) => rm(directory, { recursive: true, force: true })), ); }); async function digest(file: string): Promise { return createHash("sha256") .update(await readFile(file)) .digest("hex"); } async function makeExtractedPrebuilt(parent: string): Promise { const prebuilt = path.join(parent, "webperl_prebuilt_v0.09-beta"); await mkdir(prebuilt, { recursive: true }); for (const name of ["emperl.data", "emperl.js", "emperl.wasm"]) { await copyFile(path.join(installedPack, name), path.join(prebuilt, name)); } await copyFile( path.join(installedPack, "LICENSE-WebPerl-Artistic.txt"), path.join(prebuilt, "LICENSE_artistic.txt"), ); await copyFile( path.join(installedPack, "LICENSE-WebPerl-GPL.txt"), path.join(prebuilt, "LICENSE_gpl.txt"), ); return prebuilt; } describe("Perl engine-pack gate", () => { it("verifies the installed closed WebPerl pack in a strict-CSP Chromium worker", async () => { await expect(verifyPerlPack(installedPack)).resolves.toEqual( expect.objectContaining({ engineVersion: "5.28.1", status: "legacy-beta-compatibility-runtime", source: expect.objectContaining({ tag: "v0.09-beta", commitSha1: "6f2173d29a2c2e3536e1de75ff5d291ae96ab348", }), bridge: expect.objectContaining({ replacementOutputTransport: expect.stringContaining( "fixed MEMFS binary file", ), limits: expect.objectContaining({ maximumResponseJsonBytes: 8_388_608, outputChunkCharacters: 4_096, }), }), securityAudit: expect.objectContaining({ generatedLoaderJavaScriptEvalSites: 1, generatedLoaderNewFunctionSites: 0, }), }), ); }, 60_000); it("pins the exact archive, runtime assets, and legacy identity", () => { expect(PERL_ENGINE_LOCK.engineVersion).toBe("5.28.1"); expect(PERL_ENGINE_LOCK.webPerlVersion).toBe("0.09-beta"); expect(PERL_ENGINE_LOCK.prebuilt.archiveSha256).toBe( "5f441249217e90ab378c666f473d4206ab4f44907f6bb0aa8d70834bc38c40dc", ); expect(PERL_ENGINE_LOCK.prebuilt.files["emperl.wasm"]).toEqual( expect.objectContaining({ bytes: 3_734_063, sha256: "f1d49c4514c7332a57992c4a2444fd6a56ae3b5e6651b4fd484852a641e5e4ec", }), ); }); it("builds byte-identical offline packs and installs one atomically", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-perl-pack-")); temporaryDirectories.push(directory); const prebuilt = await makeExtractedPrebuilt(directory); const first = path.join(directory, "first"); const second = path.join(directory, "second"); await buildPerlPack({ prebuiltDirectory: prebuilt, output: first }); await buildPerlPack({ prebuiltDirectory: prebuilt, output: second }); for (const file of packFiles) { expect(await digest(path.join(first, file)), file).toBe( await digest(path.join(second, file)), ); } const fakeRoot = path.join(directory, "repository"); await mkdir(path.join(fakeRoot, "engines"), { recursive: true }); await cp( path.resolve("engines", "perl"), path.join(fakeRoot, "engines", "perl"), { recursive: true }, ); const oldTarget = path.join(fakeRoot, "public", "engines", "perl"); await mkdir(oldTarget, { recursive: true }); await writeFile(path.join(oldTarget, "old-marker"), "old\n", "utf8"); const installed = await installPerlPack(first, fakeRoot); expect(installed.output).toBe(oldTarget); await expect( readFile(path.join(oldTarget, "old-marker")), ).rejects.toThrow(); await expect( verifyPerlPack(oldTarget, fakeRoot, { smoke: false }), ).resolves.toMatchObject({ engineVersion: "5.28.1" }); }, 120_000); it("rejects extra pack entries and drifted extracted inputs", async () => { const directory = await mkdtemp(path.join(tmpdir(), "regex-perl-reject-")); temporaryDirectories.push(directory); const pack = path.join(directory, "pack"); await cp(installedPack, pack, { recursive: true }); await writeFile(path.join(pack, "unexpected"), "not allowed\n", "utf8"); await expect( verifyPerlPack(pack, path.resolve("."), { smoke: false }), ).rejects.toThrow("must contain only"); const prebuilt = await makeExtractedPrebuilt(directory); await writeFile(path.join(prebuilt, "emperl.wasm"), "not wasm\n"); await expect( buildPerlPack({ prebuiltDirectory: prebuilt, output: path.join(directory, "rejected"), }), ).rejects.toThrow("pinned WebPerl release"); }); });