import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; const WORKFLOW = ".github/workflows/dependency-change-awareness.yml"; const CODEOWNERS = ".github/CODEOWNERS"; type WorkflowStep = { name?: string; run?: string; uses?: string; with?: Record; }; type WorkflowJob = { steps?: WorkflowStep[]; }; type Workflow = { jobs?: Record; permissions?: Record; }; function readWorkflow(): Workflow { return parse(readFileSync(WORKFLOW, "utf8")) as Workflow; } describe("dependency change awareness workflow", () => { it("uses a metadata-only pull_request_target workflow with minimal write permissions", () => { const workflow = readFileSync(WORKFLOW, "utf8"); const parsed = readWorkflow(); expect(workflow).toContain("pull_request_target:"); expect(workflow).toContain("metadata-only workflow; no checkout or untrusted code execution"); expect(parsed.permissions).toEqual({ "pull-requests": "write", issues: "write", }); }); it("does not checkout or execute PR-controlled code", () => { const workflow = readFileSync(WORKFLOW, "utf8"); const forbiddenSnippets = [ "actions/checkout", "github.event.pull_request.head", "pullRequest.head", "pnpm install", "npm install", "pnpm dlx", "contents: write", "actions: write", "id-token: write", "secrets.", "github.rest.issues.createLabel", ]; for (const snippet of forbiddenSnippets) { expect(workflow).not.toContain(snippet); } const steps = readWorkflow().jobs?.["dependency-change-awareness"]?.steps ?? []; expect(steps).toHaveLength(1); expect(steps[0].run).toBeUndefined(); }); it("uses a pinned GitHub Script action and bounded sticky comments", () => { const workflow = readFileSync(WORKFLOW, "utf8"); const steps = readWorkflow().jobs?.["dependency-change-awareness"]?.steps ?? []; const step = steps[0]; expect(step.uses).toBe("actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3"); expect(step.with?.script).toContain(""); expect(step.with?.script).toContain("const maxListedFiles = 25;"); expect(step.with?.script).toContain("const sanitizeDisplayValue = (value)"); expect(step.with?.script).toContain('.replace(/[\\u0000-\\u001f\\u007f]/gu, "?")'); expect(step.with?.script).toContain(".slice(0, 240)"); expect(step.with?.script).toContain('comment.user?.login === "github-actions[bot]"'); expect(step.with?.script).toContain("github.rest.pulls.listFiles"); expect(step.with?.script).toContain("github.rest.issues.createComment"); expect(step.with?.script).toContain("github.rest.issues.updateComment"); expect(step.with?.script).toContain("github.rest.issues.deleteComment"); expect(step.with?.script).toContain("ignoreUnavailableWritePermission"); expect(step.with?.script).toContain("error?.status === 403"); expect(workflow).toContain('"dependencies-changed"'); }); it("detects the intended dependency-related file surfaces", () => { const script = readWorkflow().jobs?.["dependency-change-awareness"]?.steps?.[0].with?.script; expect(script).toContain('filename === "package.json"'); expect(script).toContain('filename === "pnpm-lock.yaml"'); expect(script).toContain('filename === "pnpm-workspace.yaml"'); expect(script).toContain('filename === "ui/package.json"'); expect(script).toContain('filename.startsWith("patches/")'); expect(script).toContain("^packages\\/[^/]+\\/package\\.json$"); expect(script).toContain("^extensions\\/[^/]+\\/package\\.json$"); }); it("requires secops review for future workflow or guard changes", () => { const codeowners = readFileSync(CODEOWNERS, "utf8"); expect(codeowners).toContain( "/.github/workflows/dependency-change-awareness.yml @openclaw/openclaw-secops", ); expect(codeowners).toContain( "/test/scripts/dependency-change-awareness-workflow.test.ts @openclaw/openclaw-secops", ); }); });