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<string, unknown> }[] = [];
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)
});
});
});