返回 DeepSeek-Reasonix
artifact-identity.test.mjs
根目录 / desktop / frontend / scripts / artifact-identity.test.mjs
1 import assert from "node:assert/strict";
2 import { createHash } from "node:crypto";
3 import { execFileSync } from "node:child_process";
4 import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5 import os from "node:os";
6 import path from "node:path";
7 import test from "node:test";
8 import { buildInputIdentity, createFrontendArtifact, verifyFrontendArtifact } from "./artifact-identity.mjs";
9
10 function fixture(t) {
11 const root = mkdtempSync(path.join(os.tmpdir(), "reasonix-frontend-artifact-"));
12 t.after(() => rmSync(root, { recursive: true, force: true }));
13 for (const dir of ["desktop/frontend/src", "desktop/frontend/dist"]) mkdirSync(path.join(root, dir), { recursive: true });
14 for (const [name, value] of [["desktop/package.json", "{}"], ["desktop/pnpm-lock.yaml", "lock"],
15 ["desktop/frontend/package.json", "{}"], ["desktop/frontend/src/App.tsx", "export {}"], ["desktop/frontend/dist/index.html", "ok"]])
16 writeFileSync(path.join(root, name), value);
17 execFileSync("git", ["init"], { cwd: root });
18 execFileSync("git", ["add", "."], { cwd: root });
19 execFileSync("git", ["-c", "user.name=Test", "-c", "user.email=test@example.com", "commit", "-m", "fixture"], { cwd: root });
20 const sourceSHA = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
21 const options = { root, dist: path.join(root, "desktop/frontend/dist"), manifest: path.join(root, "manifest.json"),
22 shell: "electron", channel: "stable", sourceSHA, runId: "12", attempt: "3", pnpmVersion: "10.0.0" };
23 createFrontendArtifact(options);
24 return options;
25 }
26
27 test("matching artifact verifies across producer platforms", t => {
28 const options = fixture(t);
29 const body = verifyFrontendArtifact(options);
30 body.toolchain.platform = body.toolchain.platform === "linux" ? "darwin" : "linux";
31 body.toolchain.arch = "other";
32 writeFileSync(options.manifest, JSON.stringify(body));
33 assert.equal(verifyFrontendArtifact(options).sourceSHA, options.sourceSHA);
34 writeFileSync(path.join(options.root, "desktop/frontend/src/App.tsx"), "export {}\r\n");
35 assert.equal(verifyFrontendArtifact(options).sourceSHA, options.sourceSHA);
36 });
37
38 test("batched blob reads preserve the version-one input digest", t => {
39 const options = fixture(t);
40 const identity = buildInputIdentity(options.root);
41 const hash = createHash("sha256");
42 for (const name of identity.files) {
43 hash.update(name);
44 hash.update("\0");
45 hash.update(execFileSync("git", ["-C", options.root, "show", `HEAD:${name}`]));
46 hash.update("\0");
47 }
48 assert.equal(identity.sha256, hash.digest("hex"));
49 });
50
51 test("variant, workflow and toolchain identity mismatches fail", t => {
52 const options = fixture(t);
53 for (const changed of [{ shell: "browser" }, { channel: "canary" }, { runId: "13" }, { attempt: "4" }, { pnpmVersion: "10.1.0" }])
54 assert.throws(() => verifyFrontendArtifact({ ...options, ...changed }), /mismatch/);
55 });
56
57 test("consumer retries verify the producer attempt while rebuilt producers advance it", t => {
58 const options = fixture(t);
59 const firstProducer = { ...options, attempt: "1" };
60 createFrontendArtifact(firstProducer);
61 assert.equal(verifyFrontendArtifact(firstProducer).workflow.attempt, "1");
62 // A consumer may be on run attempt 2 while its successful producer remains
63 // on attempt 1. Verification must use the producer output, not consumer state.
64 const consumerAttempt = "2";
65 assert.notEqual(consumerAttempt, firstProducer.attempt);
66 assert.equal(verifyFrontendArtifact(firstProducer).workflow.attempt, "1");
67 const rebuiltProducer = { ...options, attempt: "2" };
68 createFrontendArtifact(rebuiltProducer);
69 assert.equal(verifyFrontendArtifact(rebuiltProducer).workflow.attempt, "2");
70 assert.throws(() => verifyFrontendArtifact(firstProducer), /attempt mismatch/);
71 });
72
73 test("changed build input, dist, missing and old manifests fail", t => {
74 const options = fixture(t);
75 const body = verifyFrontendArtifact(options);
76 body.inputs.sha256 = "0".repeat(64);
77 writeFileSync(options.manifest, JSON.stringify(body));
78 assert.throws(() => verifyFrontendArtifact(options), /build inputs/);
79 createFrontendArtifact(options);
80 writeFileSync(path.join(options.dist, "index.html"), "changed");
81 assert.throws(() => verifyFrontendArtifact(options), /contents/);
82 writeFileSync(options.manifest, JSON.stringify({ schemaVersion: 0 }));
83 assert.throws(() => verifyFrontendArtifact(options), /schemaVersion mismatch/);
84 assert.throws(() => verifyFrontendArtifact({ ...options, manifest: path.join(options.root, "missing.json") }), /unavailable/);
85 });
86
86 lines Plain Text