import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { createHash } from "node:crypto"; import test from "node:test"; import { DAEMON_RELEASE_POLICY } from "../release/daemon-policy.mjs"; import { ReleaseVerificationError, formatCloudReleaseEnvironment, parseReleaseTag, verifyDaemonRelease, } from "../release/verify-daemon-release-core.mjs"; const TAG = "v0.2.6"; const API_URL = `https://api.github.com/repos/${DAEMON_RELEASE_POLICY.repository}/releases/tags/${TAG}`; const RELEASE_BASE = `https://github.com/${DAEMON_RELEASE_POLICY.repository}/releases/download/${TAG}`; function hash(bytes) { return createHash("sha256").update(bytes).digest("hex"); } function fixture() { const bodies = new Map( DAEMON_RELEASE_POLICY.assets.map((asset, index) => [ asset.filename, Buffer.from(`verified-daemon-asset-${index + 1}`), ]), ); const checksumsText = [ ...DAEMON_RELEASE_POLICY.assets.map( (asset) => `${hash(bodies.get(asset.filename))} ${asset.filename}`, ), `${"9".repeat(64)} nekonest-server-linux-amd64.tar.gz`, ].join("\n") + "\n"; const checksumsBytes = Buffer.from(checksumsText); const release = { tag_name: TAG, draft: false, prerelease: false, html_url: `https://github.com/${DAEMON_RELEASE_POLICY.repository}/releases/tag/${TAG}`, published_at: "2026-08-12T00:00:00Z", assets: [ ...DAEMON_RELEASE_POLICY.assets.map((asset) => ({ name: asset.filename, browser_download_url: `${RELEASE_BASE}/${asset.filename}`, size: bodies.get(asset.filename).byteLength, state: "uploaded", digest: `sha256:${hash(bodies.get(asset.filename))}`, })), { name: "checksums.txt", browser_download_url: `${RELEASE_BASE}/checksums.txt`, size: checksumsBytes.byteLength, state: "uploaded", digest: `sha256:${hash(checksumsBytes)}`, }, ], }; const requests = []; const fetchImpl = async (url, init) => { requests.push({ url: String(url), init }); let bytes; if (String(url) === API_URL) bytes = Buffer.from(JSON.stringify(release)); else if (String(url) === `${RELEASE_BASE}/checksums.txt`) bytes = checksumsBytes; else { const filename = String(url).slice(RELEASE_BASE.length + 1); bytes = bodies.get(filename); } if (!bytes) return new Response("missing", { status: 404 }); return new Response(bytes, { status: 200, headers: { "content-length": String(bytes.byteLength) }, }); }; return { bodies, checksumsBytes, fetchImpl, release, requests }; } async function expectCode(promise, code) { await assert.rejects(promise, (error) => { assert.ok(error instanceof ReleaseVerificationError); assert.equal(error.code, code); return true; }); } test("rejects old or prerelease tags before making a network request", async () => { let calls = 0; const fetchImpl = async () => { calls += 1; throw new Error("should not fetch"); }; assert.throws(() => parseReleaseTag("v0.2.5"), /不得低于 v0\.2\.6/); assert.throws(() => parseReleaseTag("v0.2.6-beta.1"), /稳定版本/); await expectCode( verifyDaemonRelease({ tag: "v0.2.5", fetchImpl }), "incompatible_release_version", ); assert.equal(calls, 0); }); test("verifies API identity, checksums digest, asset digests, sizes, and downloaded bytes", async () => { const data = fixture(); const token = "github-test-token-that-must-not-be-returned"; const verified = await verifyDaemonRelease({ tag: TAG, token, fetchImpl: data.fetchImpl }); assert.equal(verified.version, "0.2.6"); assert.equal(verified.assets.length, 3); assert.equal(verified.assets[0].sha256, hash(data.bodies.get(verified.assets[0].filename))); assert.equal(data.requests.length, 5); assert.equal(data.requests[0].init.headers.authorization, `Bearer ${token}`); for (const request of data.requests.slice(1)) { assert.equal(request.init.headers.authorization, undefined); } const serialized = JSON.stringify(verified); assert.doesNotMatch(serialized, /github-test-token/); const environment = formatCloudReleaseEnvironment(verified); assert.match(environment, /NEKONEST_CLOUD_DAEMON_RELEASE_VERSION=0\.2\.6/); assert.match(environment, /NEKONEST_CLOUD_DAEMON_LINUX_ARM64_SHA256=[0-9a-f]{64}/); assert.doesNotMatch(environment, /GITHUB_TOKEN|github-test-token/); }); test("fails closed on duplicate assets, URL substitution, prerelease metadata, or digest drift", async () => { { const data = fixture(); data.release.assets.push({ ...data.release.assets[0] }); await expectCode( verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }), "release_asset_duplicate", ); } { const data = fixture(); data.release.assets[0].browser_download_url = "https://download.example.test/substituted.zip"; await expectCode( verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }), "release_asset_url_mismatch", ); } { const data = fixture(); data.release.prerelease = true; await expectCode( verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }), "release_identity_invalid", ); } { const data = fixture(); data.release.assets[0].digest = `sha256:${"f".repeat(64)}`; await expectCode( verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }), "release_digest_mismatch", ); } }); test("detects same-sized downloaded byte substitution even when metadata still looks valid", async () => { const data = fixture(); const filename = DAEMON_RELEASE_POLICY.assets[0].filename; const original = data.bodies.get(filename); data.bodies.set(filename, Buffer.from(original.toString().replace("verified", "VERIFIED"))); assert.equal(data.bodies.get(filename).byteLength, original.byteLength); await expectCode( verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }), "release_asset_hash_mismatch", ); }); test("CLI has a no-network usage failure when the exact tag is missing", () => { const result = spawnSync( process.execPath, ["tools/verify-daemon-release.mjs"], { cwd: new URL("..", import.meta.url), encoding: "utf8" }, ); assert.equal(result.status, 2); assert.match(result.stderr, /vX\.Y\.Z/); assert.equal(result.stdout, ""); });