import { describe, expect, it, vi } from "vitest"; import { resolveOpenClawNpmResumeRun, runOpenClawNpmResumeGh, validateOpenClawNpmResumeRun, } from "../../scripts/openclaw-npm-resume-run.mts"; import type { OpenClawNpmResumeValidationInput } from "../../scripts/openclaw-npm-resume-run.mts"; const SHA = "a".repeat(40); const TAG_OBJECT_SHA = "b".repeat(40); const BRANCH = `release-publish/${SHA.slice(0, 12)}-123`; const URL = "https://github.com/openclaw/openclaw/actions/runs/456"; function fixture( overrides: Partial = {}, ): OpenClawNpmResumeValidationInput { return { canonicalWorkflowId: 101, compareStatus: "identical", jobs: [{ conclusion: "success", name: "validate_publish_request" }], run: { conclusion: "success", event: "workflow_dispatch", head_branch: BRANCH, head_sha: SHA, html_url: URL, path: ".github/workflows/openclaw-npm-release.yml", workflow_id: 101, }, tag: { object: { sha: SHA, type: "commit" }, verification: { verified: true }, }, tagRef: { object: { sha: TAG_OBJECT_SHA, type: "tag" } }, trustedWorkflowFullRef: `refs/tags/${BRANCH}`, trustedWorkflowRef: BRANCH, ...overrides, }; } describe("openclaw npm resume run identity", () => { it("bounds each GitHub lookup", () => { const execFileSyncImpl = vi.fn(() => "result"); expect( runOpenClawNpmResumeGh(["api", "repos/openclaw/openclaw/actions/runs/456"], { execFileSyncImpl, }), ).toBe("result"); expect(execFileSyncImpl).toHaveBeenCalledWith( "gh", ["api", "repos/openclaw/openclaw/actions/runs/456"], { encoding: "utf8", killSignal: "SIGKILL", maxBuffer: 32 * 1024 * 1024, timeout: 60_000, }, ); }); it("propagates GitHub lookup timeouts", () => { const timeoutError = Object.assign(new Error("spawnSync gh ETIMEDOUT"), { code: "ETIMEDOUT", }); expect(() => runOpenClawNpmResumeGh(["api", "repos/openclaw/openclaw/actions/runs/456"], { execFileSyncImpl: () => { throw timeoutError; }, }), ).toThrow(timeoutError); }); it("accepts a successful run bound to a signed main-reachable tooling tag", () => { expect(validateOpenClawNpmResumeRun(fixture())).toEqual({ tagObjectSha: TAG_OBJECT_SHA, url: URL, workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA, }); }); it("accepts a successful run bound to the exact lightweight protected tooling tag", () => { expect( validateOpenClawNpmResumeRun( fixture({ compareStatus: undefined, tag: {}, tagRef: { object: { sha: SHA, type: "commit" } }, }), ), ).toEqual({ tagObjectSha: SHA, url: URL, workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA, }); }); it("accepts the canonical path shape returned by the Actions workflow run API", () => { expect( validateOpenClawNpmResumeRun( fixture({ run: { conclusion: "success", event: "workflow_dispatch", head_branch: BRANCH, head_sha: SHA, html_url: URL, path: ".github/workflows/openclaw-npm-release.yml", workflow_id: 101, }, }), ), ).toMatchObject({ workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA, }); }); it.each([ ["branch", { run: { ...fixture().run, head_branch: "main" } }, "untrusted workflow identity"], ["workflow", { run: { ...fixture().run, workflow_id: 999 } }, "untrusted workflow identity"], ["event", { run: { ...fixture().run, event: "push" } }, "untrusted workflow identity"], [ "conclusion", { run: { ...fixture().run, conclusion: "failure" } }, "untrusted workflow identity", ], [ "path", { run: { ...fixture().run, path: ".github/workflows/ci.yml" } }, "untrusted workflow identity", ], [ "same-name branch full ref", { trustedWorkflowFullRef: `refs/heads/${BRANCH}` }, "untrusted workflow ref", ], [ "mismatched supplied ref", { trustedWorkflowRef: `release-publish/${SHA.slice(0, 12)}-124` }, "untrusted workflow ref", ], [ "tag kind", { tagRef: { object: { sha: TAG_OBJECT_SHA, type: "tree" } } }, "not a protected tag", ], [ "moved lightweight tag", { compareStatus: undefined, tag: {}, tagRef: { object: { sha: "c".repeat(40), type: "commit" } }, }, "moved after dispatch", ], [ "tag target", { tag: { ...fixture().tag, object: { sha: "c".repeat(40), type: "commit" } } }, "not bound to a real", ], [ "signature", { tag: { ...fixture().tag, verification: { verified: false } } }, "not bound to a real", ], ["main ancestry", { compareStatus: "diverged" }, "not bound to a real"], [ "approval", { jobs: [{ conclusion: "failure", name: "validate_publish_request" }] }, "lacks successful parent release approval", ], ])("rejects an untrusted %s", (_label, overrides, message) => { expect(() => validateOpenClawNpmResumeRun(fixture(overrides))).toThrow(message); }); it("loads the exact run, workflow, signed tag, ancestry, and approval job", () => { const responses = new Map([ [`api repos/openclaw/openclaw/actions/runs/456 --method GET`, fixture().run], [ `api repos/openclaw/openclaw/actions/workflows/openclaw-npm-release.yml --method GET`, { id: 101 }, ], [`api repos/openclaw/openclaw/git/ref/tags/${BRANCH} --method GET`, fixture().tagRef], [`api repos/openclaw/openclaw/git/tags/${TAG_OBJECT_SHA} --method GET`, fixture().tag], [`api repos/openclaw/openclaw/compare/${SHA}...main --method GET`, { status: "identical" }], [`run view 456 --repo openclaw/openclaw --json jobs --jq .jobs`, fixture().jobs], ]); const runGh = vi.fn((args: string[]) => { const response = responses.get(args.join(" ")); if (!response) { throw new Error(`Unexpected gh invocation: ${args.join(" ")}`); } return JSON.stringify(response); }); expect( resolveOpenClawNpmResumeRun({ repo: "openclaw/openclaw", runGh, runId: "456", trustedWorkflowFullRef: `refs/tags/${BRANCH}`, trustedWorkflowRef: BRANCH, }), ).toMatchObject({ workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA }); expect(runGh).toHaveBeenCalledTimes(6); }); it("loads a lightweight protected tag without requiring tag metadata or main ancestry", () => { const lightweight = fixture({ compareStatus: undefined, tag: {}, tagRef: { object: { sha: SHA, type: "commit" } }, }); const responses = new Map([ [`api repos/openclaw/openclaw/actions/runs/456 --method GET`, lightweight.run], [ `api repos/openclaw/openclaw/actions/workflows/openclaw-npm-release.yml --method GET`, { id: 101 }, ], [`api repos/openclaw/openclaw/git/ref/tags/${BRANCH} --method GET`, lightweight.tagRef], [`run view 456 --repo openclaw/openclaw --json jobs --jq .jobs`, lightweight.jobs], ]); const runGh = vi.fn((args: string[]) => { const response = responses.get(args.join(" ")); if (!response) { throw new Error(`Unexpected gh invocation: ${args.join(" ")}`); } return JSON.stringify(response); }); expect( resolveOpenClawNpmResumeRun({ repo: "openclaw/openclaw", runGh, runId: "456", trustedWorkflowFullRef: `refs/tags/${BRANCH}`, trustedWorkflowRef: BRANCH, }), ).toMatchObject({ workflowRef: `refs/tags/${BRANCH}`, workflowSha: SHA }); expect(runGh).toHaveBeenCalledTimes(4); expect(runGh.mock.calls.flatMap(([args]) => args)).not.toContain( `repos/openclaw/openclaw/compare/${SHA}...main`, ); }); });