import { beforeEach, describe, expect, mock, test } from "bun:test"; import type { RepoRow, Viewer } from "~/lib/types"; import type { RequestCredential } from "../token-scope"; class StubEntrypoint {} mock.module("cloudflare:workers", () => ({ env: { PUBLIC_URL: "https://forge.example.com" }, waitUntil: () => undefined, WorkflowEntrypoint: StubEntrypoint, DurableObject: StubEntrypoint, WorkerEntrypoint: StubEntrypoint, })); type Collaborator = { repoId: string; userId: string; permission: "read" | "write" | "admin" }; const state: { repos: RepoRow[]; collaborators: Collaborator[]; tokenRepos: Array<{ tokenId: string; repoId: string }>; } = { repos: [], collaborators: [], tokenRepos: [], }; const pull = { issue_id: "issue-1", repo_id: "base", number: 1, title: "Feature", body: "", state: "open", author_id: "forker", head_repo_id: "fork", head_ref: "feature", head_sha: "a".repeat(40), base_ref: "main", base_sha: "b".repeat(40), merge_base_sha: null, draft: 0, mergeable: "unknown", merged_at: null, merged_by: null, merge_commit_sha: null, merge_method: null, }; function fakeFirst(sql: string, ...params: unknown[]): unknown { if (sql.includes("FROM pull_requests")) return params[0] === "base" && params[1] === 1 ? pull : null; if (sql.includes("WHERE a.name = ? AND r.name = ?")) { return state.repos.find((repo) => repo.owner_name === params[0] && repo.name === params[1]) ?? null; } if (sql.includes("WHERE r.id = ?")) return state.repos.find((repo) => repo.id === params[0]) ?? null; if (sql.includes("FROM collaborators")) { const row = state.collaborators.find((item) => item.repoId === params[0] && item.userId === params[1]); return row ? { permission: row.permission } : null; } if (sql.includes("FROM access_token_repos")) { return state.tokenRepos.some((item) => item.tokenId === params[0] && item.repoId === params[1]) ? { ok: 1 } : null; } return null; } const updateCalls: string[] = []; const d1 = { first: async (sql: string, ...params: unknown[]) => fakeFirst(sql, ...params), all: async () => [], run: async () => ({ meta: { changes: 0 } }), batch: async () => [], stmt: () => ({}), placeholders: (count: number) => Array.from({ length: count }, () => "?").join(", "), chunks: (items: T[]) => [items], MAX_IN_LIST: 90, }; mock.module("../db", () => d1); // Repository and account objects answer from the same fake. const scoped = { first: d1.first, all: d1.all, run: d1.run, batch: d1.batch }; mock.module("../scoped-db", () => ({ repoDb: () => scoped, accountDb: () => scoped, stmt: d1.stmt })); const actualLimits = await import("../limits"); mock.module("../limits", () => ({ ...actualLimits, limitWrite: async () => undefined, limitHeavy: async () => undefined, })); const actualMerge = await import("../pull-merge"); mock.module("../pull-merge", () => ({ ...actualMerge, updateHeadBranch: async (target: { issue_id: string }) => { updateCalls.push(target.issue_id); return { sha: "c".repeat(40) }; }, })); const opened: Array<{ head: string; base: string }> = []; const actualCreate = await import("../pull-create"); mock.module("../pull-create", () => ({ ...actualCreate, openPull: async (input: { headRepo: { id: string }; base: string }) => { opened.push({ head: input.headRepo.id, base: input.base }); return 1; }, })); const { createPull, updatePullBranch } = await import("./pulls"); const { HttpError } = await import("../errors"); function repoRow(id: string, ownerId: string, ownerName: string, forkOf: string | null): RepoRow { return { id, owner_id: ownerId, owner_name: ownerName, name: "app", visibility: "public", fork_of_id: forkOf, is_archived: 0, default_branch: "main", artifacts_name: `art-${id}`, } as RepoRow; } const forker: Viewer = { id: "forker", name: "forker", displayName: null, email: "f@example.com", isAdmin: false, emailVerified: true, }; const stranger: Viewer = { id: "stranger", name: "stranger", displayName: null, email: "s@example.com", isAdmin: false, emailVerified: true, }; function ctxFor(credential: RequestCredential) { const url = new URL("https://forge.example.com/api/v1/repos/upstream/app/pulls/1/update-branch"); return { request: new Request(url, { method: "POST" }), url, credential, viewer: credential.viewer as Viewer }; } async function statusOf(promise: Promise): Promise { try { return (await promise).status; } catch (error) { if (error instanceof HttpError) return error.status; throw error; } } beforeEach(() => { state.repos = [repoRow("base", "upstream-id", "upstream", null), repoRow("fork", "forker", "forker", "base")]; state.collaborators = []; state.tokenRepos = []; updateCalls.length = 0; opened.length = 0; }); describe("POST /pulls/{n}/update-branch", () => { test("fork owner with only read on the base can update their head branch", async () => { const response = await updatePullBranch( ctxFor({ viewer: forker, grant: { kind: "fine", tokenId: "t1", allRepos: true, permissions: { contents: "write", issues: "write", pulls: "write", actions: "write", admin: "write" }, }, }), "upstream", "app", 1, ); expect(response.status).toBe(200); expect((await response.json()) as unknown).toEqual({ updated: true, sha: "c".repeat(40) }); expect(updateCalls).toEqual(["issue-1"]); }); test("a pulls-only fine-grained token cannot push to the head", async () => { const credential: RequestCredential = { viewer: forker, grant: { kind: "fine", tokenId: "t2", allRepos: true, permissions: { pulls: "write", contents: "read" } }, }; expect(await statusOf(updatePullBranch(ctxFor(credential), "upstream", "app", 1))).toBe(403); expect(updateCalls).toEqual([]); }); test("a user without push access to the head repo is rejected", async () => { const credential: RequestCredential = { viewer: stranger, grant: { kind: "fine", tokenId: "t3", allRepos: true, permissions: { contents: "write", issues: "write", pulls: "write", actions: "write", admin: "write" }, }, }; expect(await statusOf(updatePullBranch(ctxFor(credential), "upstream", "app", 1))).toBe(403); expect(updateCalls).toEqual([]); }); }); describe("POST /pulls", () => { const body = { title: "Feature", head: "feature", head_repo: "forker/app", base: "main" }; function createCtx(credential: RequestCredential) { const url = new URL("https://forge.example.com/api/v1/repos/upstream/app/pulls"); const request = new Request(url, { method: "POST", body: JSON.stringify(body) }); return { request, url, credential, viewer: credential.viewer as Viewer }; } test("a fork owner with only read on the base can open a pull request", async () => { const response = await createPull( createCtx({ viewer: forker, grant: { kind: "fine", tokenId: "t1", allRepos: true, permissions: { contents: "write", issues: "write", pulls: "write", actions: "write", admin: "write" }, }, }), "upstream", "app", ); expect(response.status).toBe(201); expect(opened).toEqual([{ head: "fork", base: "main" }]); }); test("a token without pulls:write cannot open one", async () => { const credential: RequestCredential = { viewer: forker, grant: { kind: "fine", tokenId: "t4", allRepos: true, permissions: { pulls: "read", contents: "write" } }, }; expect(await statusOf(createPull(createCtx(credential), "upstream", "app"))).toBe(403); expect(opened).toEqual([]); }); test("a private base the caller cannot read is a 404", async () => { state.repos[0] = { ...state.repos[0], visibility: "private" }; expect( await statusOf( createPull( createCtx({ viewer: stranger, grant: { kind: "fine", tokenId: "t5", allRepos: true, permissions: { contents: "write", issues: "write", pulls: "write", actions: "write", admin: "write" }, }, }), "upstream", "app", ), ), ).toBe(404); expect(opened).toEqual([]); }); });