import { describe, expect, it, vi } from "vitest"; import { GiteaClient } from "../src/integrations/gitea/client.js"; import { assertSuccessfulActionRuns, exactHeadActionRuns } from "../src/integrations/gitea/policy.js"; import type { GiteaActionRun } from "../src/integrations/gitea/types.js"; const branch = "codex/native-ci"; const sha = "a".repeat(40); const context = "CI / build-test (pull_request)"; const run = (id: number, overrides: Partial = {}): GiteaActionRun => ({ id, run_number: id, display_title: "CI", path: ".gitea/workflows/ci.yml", event: "pull_request", head_sha: sha, head_branch: "", status: "completed", conclusion: "success", html_url: `https://gitea.example/runs/${id}`, ...overrides, }); describe("native PR validation discovery", () => { it("discovers an empty branch by exact SHA and checks available PR identity", () => { const matching = run(1, { pull_requests: [{ number: 30, head: { ref: branch, sha } }] }); expect(exactHeadActionRuns([ matching, run(2), run(3, { head_sha: "stale" }), run(4, { head_branch: "other" }), run(5, { pull_requests: [{ number: 31, head: { ref: "other", sha } }] }), run(6, { event: "workflow_dispatch" }), ], branch, sha)).toEqual([matching, run(2)]); expect(assertSuccessfulActionRuns([matching], branch, sha)).toEqual([matching]); }); it.each(["pending", "queued", "waiting", "in_progress"])("does not authorize %s", (status) => { expect(() => assertSuccessfulActionRuns([run(1, { status, conclusion: undefined })], branch, sha)).toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL"); }); it.each(["cancelled", "canceled", "failure"])("does not authorize a %s job that never started", (conclusion) => { expect(() => assertSuccessfulActionRuns([run(1, { conclusion })], branch, sha)).toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL"); }); it("uses the newest attempt of each workflow and preserves all evidence", () => { const failure = run(2, { conclusion: "failure" }); expect(() => assertSuccessfulActionRuns([failure, run(1, { status: "pending" })], branch, sha)).toThrow(); const success = run(3); expect(assertSuccessfulActionRuns([failure, success], branch, sha)).toEqual([failure, success]); expect(() => assertSuccessfulActionRuns([success, run(4, { status: "queued" })], branch, sha)).toThrow(); }); it("never lets dispatch success replace PR failure even for the same workflow path", () => { expect(() => assertSuccessfulActionRuns([ run(1, { conclusion: "failure" }), run(2, { event: "workflow_dispatch", head_branch: branch }), ], branch, sha)).toThrow("FEATURE_DELIVERY_CI_NOT_SUCCESSFUL"); }); it("queries by exact SHA without the upstream branch filter", async () => { const fetcher = vi.fn(async () => new Response(JSON.stringify({ total_count: 1, workflow_runs: [run(1)] }))); await new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch).listActionRuns("Jens", "Repo", { branch, headSha: sha }); const url = new URL(String(fetcher.mock.calls[0]![0])); expect(url.searchParams.get("head_sha")).toBe(sha); expect(url.searchParams.has("branch")).toBe(false); }); it.each([ [[{ context, status: "failure" }, { context, status: "pending" }], "GITEA_REQUIRED_CHECKS_FAILED"], [[{ context: "CI / build-test (workflow_dispatch)", status: "success" }], "GITEA_REQUIRED_CHECKS_PENDING"], [[{ context: `${context} extra`, status: "success" }], "GITEA_REQUIRED_CHECKS_PENDING"], [[{ context, status: "pending" }], "GITEA_REQUIRED_CHECKS_PENDING"], ] as const)("requires the exact latest protected status %#", async (statuses, error) => { const fetcher = vi.fn(async (url: URL | RequestInfo) => new Response(JSON.stringify(String(url).includes("branch_protections") ? { enable_status_check: true, status_check_contexts: [context] } : { statuses }))); const client = new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch); await expect(client.waitForRequiredPullRequestChecks("Jens", "Repo", "main", sha, { timeoutMs: 0 })).rejects.toMatchObject({ code: error }); expect(String(fetcher.mock.calls[1]![0])).toContain(`/commits/${sha}/status`); }); it("accepts a newer native success after an old native failure", async () => { const fetcher = vi.fn(async (url: URL | RequestInfo) => new Response(JSON.stringify(String(url).includes("branch_protections") ? { enable_status_check: true, status_check_contexts: [context] } : { statuses: [{ context, status: "success" }, { context, status: "failure" }] }))); await expect(new GiteaClient("https://gitea.example", "test-only", fetcher as typeof fetch) .waitForRequiredPullRequestChecks("Jens", "Repo", "main", sha, { timeoutMs: 0 })).resolves.toMatchObject({ state: "success" }); }); });