import type { OAuthUserAgent } from "@atcute/oauth-browser-client";
import { describe, expect, it, vi } from "vitest";
import { createBobbinClient } from "#lib/api/client.ts";
import { FOLLOW_VIEWER, STAR_VIEWER } from "#lib/api/descriptors.ts";
import { TYPE_VIEWER } from "#lib/api/enrich.ts";
import { ensureProfile, getTrendingWithViewerState, hasProfile } from "#lib/api/profile.ts";
import type { Fetch } from "#lib/fetch.ts";
it("gets viewer-neutral trending data with relationship state in the enrichment sidecar", async () => {
const fetchMock = vi.fn<Fetch>().mockResolvedValue(
new Response(JSON.stringify({ output: { people: [], repos: [] }, data: {} }), {
status: 200,
headers: { "content-type": "application/json" }
})
);
const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
await getTrendingWithViewerState(ctx, "did:plc:viewer");
const [url, request] = fetchMock.mock.calls[0];
expect(String(url)).toBe("https://bobbin.test/xrpc/org.tangled.query.enrichResponse");
expect(JSON.parse(String(request?.body))).toEqual({
xrpc: "org.tangled.actor.getTrending",
viewer: "did:plc:viewer",
enrich: [
{
source: FOLLOW_VIEWER.source,
type: TYPE_VIEWER,
targets: ["people[].did"]
},
{
source: STAR_VIEWER.source,
type: TYPE_VIEWER,
targets: ["repos[].did"]
}
]
});
});
describe("profile existence", () => {
const pds = "https://pds.example";
const cid = "bafyreidfayvfuwqa7qlnopdjiqrxzs6blmoeu4rujcjtnci5beludirz2a";
const agent = (handle: ReturnType<typeof vi.fn>) =>
({
sub: "did:plc:org",
session: { info: { aud: pds } },
handle
}) as unknown as OAuthUserAgent;
const found = () =>
Response.json({
uri: "at://did:plc:org/org.tangled.actor.profile/self",
cid,
value: { $type: "org.tangled.actor.profile" }
});
const missing = () =>
Response.json(
{ error: "RecordNotFound", message: "Could not locate record" },
{ status: 400 }
);
it("reads the record from the account's own pds", async () => {
const handle = vi.fn(async () => found());
expect(await hasProfile(agent(handle))).toBe(true);
const [path] = handle.mock.calls[0] as unknown as [string];
const url = new URL(path, pds);
expect(url.pathname).toBe("/xrpc/com.atproto.repo.getRecord");
expect(url.searchParams.get("repo")).toBe("did:plc:org");
expect(url.searchParams.get("collection")).toBe("org.tangled.actor.profile");
expect(url.searchParams.get("rkey")).toBe("self");
});
it("treats a missing record as no profile, and anything else as an error", async () => {
expect(await hasProfile(agent(vi.fn(async () => missing())))).toBe(false);
await expect(
hasProfile(
agent(
vi.fn(async () =>
Response.json({ error: "UpstreamFailure", message: "" }, { status: 502 })
)
)
)
).rejects.toThrow();
});
it("writes the record only when it is missing", async () => {
const record = {
$type: "org.tangled.actor.profile",
isOrganization: true
} as const;
const written = vi
.fn()
.mockResolvedValueOnce(missing())
.mockResolvedValueOnce(
Response.json({ uri: "at://did:plc:org/org.tangled.actor.profile/self", cid })
);
await ensureProfile(agent(written), record);
expect(written).toHaveBeenCalledTimes(2);
const [path, init] = written.mock.calls[1] as unknown as [string, RequestInit];
expect(new URL(path, pds).pathname).toBe("/xrpc/com.atproto.repo.putRecord");
expect(JSON.parse(String(init.body))).toMatchObject({ rkey: "self", record });
const untouched = vi.fn(async () => found());
await ensureProfile(agent(untouched), record);
expect(untouched).toHaveBeenCalledTimes(1);
});
});