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.ts";
import metadata from "#lib/oauth-client-metadata.ts";
import type { Fetch } from "#lib/fetch.ts";
const makeAgent = () => {
const handle = vi.fn<(path: string, init: RequestInit) => Promise<Response>>(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<Fetch>(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<Fetch>(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",
403,
"Verify an email address in Settings → Emails before creating an organization."
],
[
"UpstreamUnavailable",
502,
"Organization creation is temporarily unavailable. Try again in a few minutes."
],
["HandleNotAvailable", 400, "This organization name is taken. Choose another one."],
["InvalidHandle", 400, "This organization name isn't allowed. Choose another one."]
])("explains %s", async (error, status, 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, message: "Handle already taken" }, { status })
)
})
).rejects.toThrow(message);
});
it("falls back to a plain sentence when an unknown error has no message", async () => {
const { agent } = makeAgent();
const error = provisionDelegate(agent, "org.example", {
serviceUrl: "https://gate.example",
serviceDid: "did:web:tranquil.example",
fetch: vi.fn(async () => Response.json({ error: "RateLimitExceeded" }, { status: 429 }))
});
await expect(error).rejects.toThrow("Unable to create the organization. Try again.");
await expect(error).rejects.not.toThrow(/unspecified description/);
});
it.each([
[401, "Sign in again to grant Tangled permission to create organizations."],
[403, "Sign in again to grant Tangled permission to create organizations."],
[500, "Unable to confirm your account with your PDS. Try again."]
])("explains a %i from the user's PDS while minting service auth", async (status, message) => {
const { agent, handle } = makeAgent();
handle.mockResolvedValue(Response.json({ error: "Whatever" }, { status }));
await expect(
provisionDelegate(agent, "org.example", {
serviceUrl: "https://gate.example",
serviceDid: "did:web:tranquil.example",
fetch: vi.fn()
})
).rejects.toThrow(message);
});
it("shows Tranquil's reason for InvalidDelegation, which is not only the delegate cap", async () => {
const { agent } = makeAgent();
const message = "Invite-only registration requires an existing account on this PDS";
await expect(
provisionDelegate(agent, "org.example", {
serviceUrl: "https://gate.example",
serviceDid: "did:web:tranquil.example",
fetch: vi.fn(async () =>
Response.json({ error: "InvalidDelegation", message }, { status: 400 })
)
})
).rejects.toThrow(message);
});
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(
"Organization creation isn't set up on this server."
);
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(/different account than the one you chose/);
});
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<Fetch>();
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<Fetch>(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();
});
});