import { describe, expect, it, vi } from "vitest";
import { ClientResponseError } from "@atcute/client";
import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
import * as tranquil from "#lib/api/tranquil.ts";
const jsonResponse = (body: unknown, init: ResponseInit = {}): Response =>
new Response(JSON.stringify(body), {
status: 200,
headers: { "content-type": "application/json" },
...init
});
const makeAgent = (handle: OAuthUserAgent["handle"]): OAuthUserAgent =>
({ handle }) as unknown as OAuthUserAgent;
describe("tranquil.listControllers", () => {
it("unwraps the controllers array", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(
jsonResponse({ controllers: [{ did: "did:plc:x", isActive: true }] })
);
const controllers = await tranquil.listControllers(makeAgent(handle));
expect(handle).toHaveBeenCalledWith("/xrpc/_delegation.listControllers", expect.anything());
expect(controllers).toEqual([{ did: "did:plc:x", isActive: true }]);
});
it("preserves custom headers supplied as a Headers object", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(jsonResponse({ controllers: [] }));
await tranquil.listControllers(makeAgent(handle), {
headers: new Headers({ accept: "application/vnd.example+json", "x-custom": "value" })
});
const [, init] = handle.mock.calls[0];
const headers = new Headers(init?.headers);
expect(headers.get("accept")).toBe("application/vnd.example+json");
expect(headers.get("x-custom")).toBe("value");
});
});
describe("tranquil.addController", () => {
it("sends a snake_case body, matching tranquil-pds's un-renamed request struct", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(jsonResponse({ success: true }));
await tranquil.addController(makeAgent(handle), {
controllerDid: "did:plc:controller",
grantedScopes: "atproto"
});
const [pathname, init] = handle.mock.calls[0];
expect(pathname).toBe("/xrpc/_delegation.addController");
expect(JSON.parse(String(init?.body))).toEqual({
controller_did: "did:plc:controller",
granted_scopes: "atproto"
});
});
it("preserves custom headers supplied as tuples", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(jsonResponse({ success: true }));
await tranquil.addController(
makeAgent(handle),
{ controllerDid: "did:plc:controller", grantedScopes: "atproto" },
{ headers: [["x-custom", "value"]] }
);
const [, init] = handle.mock.calls[0];
const headers = new Headers(init?.headers);
expect(headers.get("x-custom")).toBe("value");
expect(headers.get("accept")).toBe("application/json");
expect(headers.get("content-type")).toBe("application/json");
});
});
describe("tranquil.createDelegatedAccount", () => {
it("sends a camelCase body", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(jsonResponse({ did: "did:plc:new", handle: "new.example.com" }));
await tranquil.createDelegatedAccount(makeAgent(handle), {
handle: "new.example.com",
controllerScopes: "atproto"
});
const [path, init] = handle.mock.calls[0];
expect(path).toBe("/xrpc/_delegation.createDelegatedAccount");
expect(JSON.parse(String(init?.body))).toEqual({
handle: "new.example.com",
controllerScopes: "atproto"
});
});
});
describe("tranquil error handling", () => {
it("throws ClientResponseError on a non-2xx response", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(jsonResponse({ error: "DelegationNotFound" }, { status: 404 }));
const err = await tranquil
.removeController(makeAgent(handle), "did:plc:controller")
.catch((e: unknown) => e);
expect(err).toBeInstanceOf(ClientResponseError);
expect((err as ClientResponseError).status).toBe(404);
expect((err as ClientResponseError).error).toBe("DelegationNotFound");
});
});
it("lists controlled accounts at the Tranquil HTTP route", async () => {
const handle = vi
.fn<OAuthUserAgent["handle"]>()
.mockResolvedValue(jsonResponse({ accounts: [] }));
await expect(tranquil.listControlledAccounts(makeAgent(handle))).resolves.toEqual([]);
expect(handle).toHaveBeenCalledWith(
"/xrpc/_delegation.listControlledAccounts",
expect.anything()
);
});