import { describe, expect, it, vi } from "vitest"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { provisionDelegate, CREATE_DELEGATE_NSID, GATE_CREATE_DELEGATE_NSID, LIST_DELEGATES_NSID } from "$lib/api/delegation"; import metadata from "$lib/oauth-client-metadata"; const makeAgent = () => { const handle = vi.fn<(path: string, init: RequestInit) => Promise>(async () => Response.json({ token: "service-jwt" }) ); return { handle, agent: { sub: "did:plc:alice", handle } as unknown as OAuthUserAgent }; }; const account = { did: "did:plc:org", handle: "org.example", controllerDid: "did:plc:alice" }; describe("delegate provisioning", () => { it("mints one method-bound service auth on the user's PDS for the create call", async () => { const { agent, handle } = makeAgent(); const fetch = vi.fn(async () => Response.json(account)); await expect( provisionDelegate(agent, "org.example", { serviceUrl: "https://gate.example/", serviceDid: "did:web:tranquil.example", fetch }) ).resolves.toEqual(account); const params = new URL(String(handle.mock.calls[0][0]), "https://pds.example").searchParams; expect(params.get("aud")).toBe("did:web:tranquil.example"); expect(params.get("lxm")).toBe(CREATE_DELEGATE_NSID); // Tranquil enforces the delegate cap itself, so no listing token is minted. expect(handle).toHaveBeenCalledTimes(1); expect(String(fetch.mock.calls[0][0])).toBe( `https://gate.example/xrpc/${GATE_CREATE_DELEGATE_NSID}` ); expect(fetch.mock.calls[0][1]).toMatchObject({ method: "POST", headers: { authorization: "Bearer service-jwt" }, body: JSON.stringify({ handle: "org.example" }) }); }); it("allows the advertised DID to differ from a forwarded URL port", async () => { const { agent, handle } = makeAgent(); await provisionDelegate(agent, "org.example", { serviceUrl: "https://gate.example:8443", serviceDid: "did:web:tranquil.example", fetch: vi.fn(async () => Response.json(account)) }); expect( new URL(String(handle.mock.calls[0][0]), "https://pds.example").searchParams.get("aud") ).toBe("did:web:tranquil.example"); }); it.each([ ["VerifiedEmailRequired", /Verify an email/], ["InvalidDelegation", /reached the limit/], ["UpstreamUnavailable", /temporarily unavailable/] ])("explains %s", async (error, message) => { const { agent } = makeAgent(); await expect( provisionDelegate(agent, "org.example", { serviceUrl: "https://gate.example", serviceDid: "did:web:tranquil.example", fetch: vi.fn(async () => Response.json({ error }, { status: 403 })) }) ).rejects.toThrow(message); }); it("shows Tranquil's own message for errors it does not recognize", async () => { const { agent } = makeAgent(); await expect( provisionDelegate(agent, "org.example", { serviceUrl: "https://gate.example", serviceDid: "did:web:tranquil.example", fetch: vi.fn(async () => Response.json( { error: "HandleNotAvailable", message: "Handle already taken" }, { status: 400 } ) ) }) ).rejects.toThrow(/Handle already taken/); }); it("does not request a token when the sidecar is not configured", async () => { const { agent, handle } = makeAgent(); await expect(provisionDelegate(agent, "org.example", { serviceUrl: "" })).rejects.toThrow( /not configured/ ); expect(handle).not.toHaveBeenCalled(); }); it("rejects an unexpected controller", async () => { const { agent } = makeAgent(); await expect( provisionDelegate(agent, "org.example", { serviceUrl: "https://gate.example", serviceDid: "did:web:tranquil.example", fetch: vi.fn(async () => Response.json({ ...account, controllerDid: "did:plc:other" }) ) }) ).rejects.toThrow(/unexpected owner/); }); it("requests the permission needed to mint delegation service auth", () => { expect(metadata.scope.split(" ")).toContain(`rpc:${CREATE_DELEGATE_NSID}?aud=*`); expect(metadata.scope.split(" ")).toContain(`rpc:${LIST_DELEGATES_NSID}?aud=*`); }); }); it("asks older sessions to reauthorize before any account is created", async () => { const handle = vi.fn(async () => Response.json({ error: "InsufficientScope", message: "missing scope" }, { status: 403 }) ); const fetch = vi.fn(); await expect( provisionDelegate( { handle, sub: "did:plc:alice" } as unknown as OAuthUserAgent, "org.example", { serviceUrl: "https://gate.example", serviceDid: "did:web:tranquil.example", fetch } ) ).rejects.toThrow(/Sign in again/); expect(fetch).not.toHaveBeenCalled(); }); describe("controlled organization accounts", () => { it("lists on Tranquil with method-bound auth from the controller's PDS", async () => { const { listDelegatedAccounts } = await import("./delegation"); const { agent, handle } = makeAgent(); const accounts = [ { did: "did:plc:org", handle: "org.example", grantedScopes: "atproto repo:*", grantedAt: "2026-09-09T00:00:00Z" } ]; const fetch = vi.fn(async () => Response.json({ accounts })); await expect( listDelegatedAccounts(agent, "did:web:tranquil.example", fetch) ).resolves.toEqual(accounts); const params = new URL(String(handle.mock.calls[0][0]), "https://pds.example").searchParams; expect(params.get("aud")).toBe("did:web:tranquil.example"); expect(params.get("lxm")).toBe(LIST_DELEGATES_NSID); expect(String(fetch.mock.calls[0][0])).toBe( "https://tranquil.example/xrpc/_delegation.listControlledAccounts" ); expect(fetch.mock.calls[0][1]).toEqual({ headers: { authorization: "Bearer service-jwt" } }); }); it("reports a failed listing instead of treating it as no organizations", async () => { const { listDelegatedAccounts } = await import("./delegation"); const { agent } = makeAgent(); await expect( listDelegatedAccounts(agent, "did:web:tranquil.example", async () => Response.json({ error: "InvalidToken" }, { status: 401 }) ) ).rejects.toThrow(); }); });