|
1 | 1 | import { afterEach, describe, expect, it, vi } from "vitest"; |
2 | | -import { makeInstallationOctokit, resolveRepoActionMode, timeoutFetch } from "../../src/github/client"; |
| 2 | +import { forcedSelfhostMode, makeInstallationOctokit, resolveRepoActionMode, timeoutFetch } from "../../src/github/client"; |
3 | 3 | import { setGlobalAgentFrozen } from "../../src/db/repositories"; |
4 | 4 | import { createTestEnv } from "../helpers/d1"; |
5 | 5 |
|
@@ -76,6 +76,35 @@ describe("makeInstallationOctokit", () => { |
76 | 76 | }); |
77 | 77 | }); |
78 | 78 |
|
| 79 | +describe("forcedSelfhostMode (instance-wide self-host kill switch)", () => { |
| 80 | + it("maps SELFHOST_DEPLOYMENT_MODE to a forced action mode (else null)", () => { |
| 81 | + expect(forcedSelfhostMode({ SELFHOST_DEPLOYMENT_MODE: "dry-run" })).toBe("dry_run"); |
| 82 | + expect(forcedSelfhostMode({ SELFHOST_DEPLOYMENT_MODE: "dry_run" })).toBe("dry_run"); // underscore variant |
| 83 | + expect(forcedSelfhostMode({ SELFHOST_DEPLOYMENT_MODE: "DISABLED" })).toBe("paused"); // case-insensitive |
| 84 | + expect(forcedSelfhostMode({ SELFHOST_DEPLOYMENT_MODE: "live" })).toBeNull(); |
| 85 | + expect(forcedSelfhostMode({})).toBeNull(); |
| 86 | + }); |
| 87 | + |
| 88 | + it("forces suppression for the WHOLE instance even when the caller passes mode=live", async () => { |
| 89 | + const calls: RecordedCall[] = []; |
| 90 | + stubFetchRecording(calls); |
| 91 | + const env = { ...createTestEnv(), SELFHOST_DEPLOYMENT_MODE: "dry-run" }; |
| 92 | + const octokit = makeInstallationOctokit(env, "tok", "live"); // a LIVE caller… |
| 93 | + const r = await octokit.request("POST /repos/{owner}/{repo}/check-runs", { owner: "o", repo: "r", name: "Gate", head_sha: "abc" }); |
| 94 | + expect(calls.some((c) => c.method === "POST")).toBe(false); // …but the instance switch suppresses it anyway |
| 95 | + expect((r.data as unknown as { id: number }).id).toBe(-1); |
| 96 | + }); |
| 97 | + |
| 98 | + it("'disabled' forces suppression audited as denied (vs dry-run's completed-shadow)", async () => { |
| 99 | + stubFetchRecording([]); |
| 100 | + const env = { ...createTestEnv(), SELFHOST_DEPLOYMENT_MODE: "disabled" }; |
| 101 | + const octokit = makeInstallationOctokit(env, "tok", "live"); |
| 102 | + await octokit.request("POST /repos/{owner}/{repo}/check-runs", { owner: "o", repo: "r", name: "Gate", head_sha: "abc" }); |
| 103 | + const audit = await env.DB.prepare("SELECT outcome FROM audit_events WHERE event_type = ?").bind("github.write.suppressed").first<{ outcome: string }>(); |
| 104 | + expect(audit?.outcome).toBe("denied"); |
| 105 | + }); |
| 106 | +}); |
| 107 | + |
79 | 108 | describe("resolveRepoActionMode", () => { |
80 | 109 | it("maps the env brake, DB freeze, per-repo pause and dry-run to the same modes the executor uses", async () => { |
81 | 110 | const env = createTestEnv(); |
|
0 commit comments