aboutsummaryrefslogtreecommitdiff
path: root/scripts/release-workflows.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/release-workflows.test.ts')
-rw-r--r--scripts/release-workflows.test.ts64
1 files changed, 64 insertions, 0 deletions
diff --git a/scripts/release-workflows.test.ts b/scripts/release-workflows.test.ts
new file mode 100644
index 00000000..a169204f
--- /dev/null
+++ b/scripts/release-workflows.test.ts
@@ -0,0 +1,64 @@
+import { describe, expect, test } from "bun:test";
+import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
+import { spawnSync } from "node:child_process";
+import { join } from "node:path";
+
+const load = (name: string): any =>
+ Bun.YAML.parse(readFileSync(new URL(`../.github/workflows/${name}.yml`, import.meta.url), "utf8"));
+const preview = load("preview");
+const release = load("release");
+const adminGate = release.jobs["validate-release-source"].steps[0];
+
+describe("official publishing workflow boundaries", () => {
+ test("publishing is tag-only while normal PR CI remains enabled", () => {
+ expect(preview.on).toEqual({ push: { tags: ["preview-*"] } });
+ expect(release.on).toEqual({ push: { tags: ["v*"] } });
+ expect(load("ci").on.pull_request).toBeDefined();
+ });
+
+ test("each publishing job rechecks both actors before using credentials", () => {
+ for (const [workflow, names] of [
+ [preview, ["preflight", "publish"]],
+ [release, ["validate-release-source", "release", "update-nix-package", "close-released-issues", "update-latest-json"]],
+ ] as const) {
+ for (const name of names) {
+ const job = workflow.jobs[name];
+ expect(job.if).toContain("github.event_name == 'push'");
+ expect(job.if).toContain("startsWith(github.ref, 'refs/tags/");
+ expect(job.steps[0]).toEqual(adminGate);
+ }
+ }
+ expect(adminGate.run).toContain('"$GITHUB_ACTOR" "$GITHUB_TRIGGERING_ACTOR"');
+ expect(adminGate.env.GH_TOKEN).toBe("${{ github.token }}");
+ expect(adminGate.run).not.toContain("ogulcancelik");
+ });
+
+ test.skipIf(process.platform === "win32")("admin gate permits admins and fails closed for other roles or API errors", () => {
+ const dir = mkdtempSync("/var/tmp/herdr-admin-gate-");
+ try {
+ writeFileSync(join(dir, "gh"), `#!/bin/sh
+case "$2" in
+ */collaborators/admin-*/permission) echo admin ;;
+ */collaborators/maintainer/permission) echo maintain ;;
+ */collaborators/writer/permission) echo write ;;
+ *) exit 1 ;;
+esac
+`, { mode: 0o755 });
+ for (const [actor, trigger, succeeds] of [
+ ["admin-one", "admin-two", true],
+ ["writer", "admin-two", false],
+ ["admin-one", "writer", false],
+ ["admin-one", "maintainer", false],
+ ["admin-one", "api-error", false],
+ ] as const) {
+ const result = spawnSync("bash", ["-c", adminGate.run], {
+ env: { ...process.env, PATH: `${dir}:${process.env.PATH}`, GITHUB_REPOSITORY: "example/test", GITHUB_ACTOR: actor, GITHUB_TRIGGERING_ACTOR: trigger },
+ encoding: "utf8",
+ });
+ expect(result.status === 0).toBe(succeeds);
+ }
+ } finally {
+ rmSync(dir, { recursive: true, force: true });
+ }
+ });
+});