import { afterEach, describe, expect, it, vi } from "vitest"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { atUriOf, didOf, gitOidOf, knotOf, nsidOf } from "#lib/api/syntax.ts"; import oauthMetadata from "#lib/oauth-client-metadata.ts"; import { missingPermissions } from "#lib/auth/scopes.ts"; import type * as Pull from "#lib/api/pull.ts"; const agent = { sub: "did:plc:limpet" } as unknown as OAuthUserAgent; const knot = knotOf("knot.example.com"); const repo = didOf("did:plc:barnacle"); const ticketUri = atUriOf("at://did:plc:barnacle/org.tangled.track.ticket/3lk"); const patchUri = atUriOf("at://did:plc:barnacle/org.tangled.review.patch/bafypatch"); const range = { base: gitOidOf("a".repeat(40)), head: gitOidOf("b".repeat(40)) }; const source = { repo, branch: "feat/kelp" }; const load = async (responses: (() => Response)[]) => { vi.resetModules(); const minted: string[] = []; vi.doMock("$app/env", () => ({ browser: true })); vi.doMock("#lib/auth/agent.ts", () => ({ mintServiceAuth: async (_agent: unknown, { lxm }: { lxm: string }) => { minted.push(lxm); return "token"; }, serviceDidForHost: (host: string) => `did:web:${host}` })); const pull = (await import("#lib/api/pull.ts")) as typeof Pull; const calls: { url: string; body?: Record }[] = []; vi.stubGlobal("fetch", async (input: URL | RequestInfo, init?: RequestInit) => { calls.push({ url: String(input), body: init?.body && JSON.parse(String(init.body)) }); return (responses.shift() ?? (() => Response.json({})))(); }); return { pull, minted, calls }; }; afterEach(() => { vi.unstubAllGlobals(); }); describe("pull writes on the knot", () => { it("merges through knotmirror with a receive-pack token for the knot", async () => { const { pull, minted, calls } = await load([]); const bobbin = "https://bobbin.example.com"; await pull.mergeCommit(agent, bobbin, knot, { target: { repo, branch: "main", commit: range.base }, source: { repo, commit: range.head }, style: "rebase" }); expect(calls[0].url).toBe(`${bobbin}/xrpc/sh.tangled.git.mergeCommit`); expect(calls[0].body).toEqual({ target: { $type: "sh.tangled.git.mergeCommit#target", repo, branch: "main", commit: range.base }, source: { $type: "sh.tangled.git.mergeCommit#source", repo, commit: range.head }, style: "rebase" }); expect(minted).toEqual(["org.tangled.git.receivePack"]); const wanted = [ { resource: "rpc", lxm: nsidOf("org.tangled.git.receivePack"), aud: `did:web:${knot}` } ] as const; expect(missingPermissions(oauthMetadata.scope, wanted)).toEqual([]); }); it("opens a pull as a ticket on the target branch plus a patch naming it", async () => { const { pull, calls } = await load([ () => Response.json({ uri: ticketUri, cid: "bafyreiticket" + "a".repeat(46) }), () => Response.json({ uri: patchUri, cid: "bafyreipatch" + "a".repeat(47) }) ]); const opened = await pull.openPull( agent, knot, repo, { title: "kelp", body: "" }, "main", range, source ); expect(opened).toMatchObject({ uri: ticketUri, cid: "bafyreiticket" + "a".repeat(46) }); expect(calls[0].body).toMatchObject({ collection: "org.tangled.track.ticket", record: { title: { text: "kelp" }, state: "open", targetBranch: "main" } }); expect(calls[0].body?.record).not.toHaveProperty("body"); expect(calls[1].url).toBe(`https://${knot}/xrpc/org.tangled.review.submitPatch`); expect(calls[1].body).toEqual({ repo, patch: { ticket: "3lk", base: { oid: range.base }, head: { oid: range.head }, sourceRepo: source.repo, sourceBranch: source.branch } }); }); it("takes the ticket back down when the knot refuses its patch", async () => { const { pull, calls } = await load([ () => Response.json({ uri: ticketUri, cid: "bafyreiticket" + "a".repeat(46) }), () => Response.json( { error: "InvalidRequest", message: "the patch's head isn't a commit" }, { status: 400 } ), () => Response.json({ commit: {} }) ]); const thrown = await pull .openPull(agent, knot, repo, { title: "kelp", body: "" }, "main", range, source) .catch((cause) => cause); expect(thrown).toBeInstanceOf(Error); expect(calls[2].url).toBe(`https://${knot}/xrpc/com.atproto.repo.deleteRecord`); expect(calls[2].body).toEqual({ repo, collection: "org.tangled.track.ticket", rkey: "3lk", swapRecord: "bafyreiticket" + "a".repeat(46) }); }); });