返回 DeepSeek-Reasonix
verify-release-tag-identity.test.mjs
根目录 / scripts / verify-release-tag-identity.test.mjs
1 import assert from "node:assert/strict";
2 import test from "node:test";
3 import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import path from "node:path";
6 import { spawnSync } from "node:child_process";
7 import { verifyIdentity } from "./verify-release-tag-identity.mjs";
8
9 const rule = { id: 1, target: "tag", enforcement: "active", current_user_can_bypass: "always", conditions: { ref_name: { include: ["refs/tags/v*", "refs/tags/npm-v*", "refs/tags/desktop-v*"], exclude: [] } }, rules: [{ type: "creation" }, { type: "update" }, { type: "deletion" }] };
10 const fixture = () => ({ actor: { login: "publisher", id: 42, type: "User" }, repo: { full_name: "esengine/DeepSeek-Reasonix", permissions: { push: true } }, rulesets: [structuredClone(rule)], expectedActor: "publisher", version: "1.2.3" });
11
12 test("authorized identity, unrelated rules, and immutable-only restrictions", () => {
13 assert.equal(verifyIdentity(fixture()).actorID, "42");
14 const input = fixture();
15 input.rulesets.push({ ...rule, id: 2, current_user_can_bypass: "never", conditions: { ref_name: { include: ["refs/tags/unrelated-*"], exclude: [] } } });
16 assert.deepEqual(verifyIdentity(input).rulesets, [1]);
17 input.rulesets[0].rules = [{ type: "update" }, { type: "deletion" }];
18 input.rulesets[0].current_user_can_bypass = "never";
19 verifyIdentity(input);
20 });
21
22 test("the GH013 mechanism fails before approval, including inherited and unknown rules", () => {
23 for (const bypass of ["never", "pull_request", undefined]) {
24 const input = fixture();
25 input.rulesets[0].current_user_can_bypass = bypass;
26 assert.throws(() => verifyIdentity(input), /cannot bypass/);
27 }
28 for (const pattern of ["~ALL", "refs/tags/*", "refs/tags/v[0-9]*", "refs/**", "~UNKNOWN"]) {
29 const input = fixture();
30 input.rulesets.push({ ...rule, id: 2, source_type: "Organization", current_user_can_bypass: "never", conditions: { ref_name: { include: [pattern], exclude: [] } } });
31 assert.throws(() => verifyIdentity(input), /ruleset 2/);
32 }
33 });
34
35 test("missing credentials, mismatched actor, token rotation, and repository permissions fail closed", () => {
36 for (const change of [
37 { expectedActor: "" }, { expectedActor: "someone-else" }, { expectedID: "99" },
38 { actor: { login: "publisher", id: 42, type: "Bot" } },
39 { repo: { full_name: "esengine/DeepSeek-Reasonix", permissions: { push: false } } },
40 ]) assert.throws(() => verifyIdentity({ ...fixture(), ...change }));
41 });
42
43 test("CLI uses credential-scoped paginated observations and exports the bound actor ID", () => {
44 const root = mkdtempSync(path.join(tmpdir(), "tag-identity-"));
45 try {
46 const gh = path.join(root, "gh");
47 const input = fixture();
48 writeFileSync(gh, `#!/usr/bin/env node
49 const args = process.argv.slice(2);
50 const responses = ${JSON.stringify({ user: input.actor, "repos/esengine/DeepSeek-Reasonix": input.repo, "repos/esengine/DeepSeek-Reasonix/rulesets?includes_parents=true&per_page=100": [[rule]], "repos/esengine/DeepSeek-Reasonix/rulesets/1?includes_parents=true": rule })};
51 if (process.env.GH_TOKEN !== 'test-credential') process.exit(2);
52 if (args.at(-1).includes('per_page') && (!args.includes('--paginate') || !args.includes('--slurp'))) process.exit(3);
53 if (!(args.at(-1) in responses)) process.exit(4);
54 console.log(JSON.stringify(responses[args.at(-1)]));
55 `, { mode: 0o755 });
56 const env = { ...process.env, PATH: `${root}:${process.env.PATH}`, GH_TOKEN: "test-credential", RELEASE_TAG_ACTOR: "publisher", RELEASE_TAG_ACTOR_ID: "", GITHUB_ACTIONS: "true", GITHUB_REF: "refs/heads/main-v2", GITHUB_REF_PROTECTED: "true", GITHUB_REPOSITORY: "esengine/DeepSeek-Reasonix", GITHUB_OUTPUT: path.join(root, "output") };
57 const run = extra => spawnSync(process.execPath, ["scripts/verify-release-tag-identity.mjs", "1.2.3"], { env: { ...env, ...extra }, encoding: "utf8" });
58 const result = run({});
59 assert.equal(result.status, 0, result.stderr);
60 assert.equal(readFileSync(env.GITHUB_OUTPUT, "utf8"), "actor_id=42\n");
61 assert.notEqual(run({ GH_TOKEN: "" }).status, 0);
62 assert.notEqual(run({ GITHUB_REF: "refs/heads/untrusted" }).status, 0);
63 assert.notEqual(run({ GITHUB_REF_PROTECTED: "false" }).status, 0);
64 } finally { rmSync(root, { recursive: true, force: true }); }
65 });
66
67 test("workflow binds preflight and activation to the same credential without checkout fallback", () => {
68 const workflow = readFileSync(".github/workflows/release-promote.yml", "utf8");
69 const preflight = workflow.split("\n authorize:")[0];
70 const activate = workflow.split("\n activate:")[1].split("\n cli:")[0];
71 assert.match(preflight, /id: identity/);
72 assert.match(preflight, /secrets.RELEASE_TAG_TOKEN/);
73 assert.match(activate, /secrets.RELEASE_TAG_TOKEN/);
74 assert.match(activate, /needs.preflight.outputs.tag_actor_id/);
75 assert.match(activate, /persist-credentials: false/);
76 assert.ok(activate.indexOf("verify-release-tag-identity.mjs") < activate.indexOf("release-candidate-tags.sh activate"));
77 assert.match(activate, /gh auth setup-git --hostname github.com/);
78 });
79
79 lines Plain Text