Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124import { describe, expect, it, vi } from "vitest";import { createBobbinClient } from "#lib/api/client.ts";import { hydrateNotifications, type NotificationItem } from "#lib/api/notifications.ts";
const REPO = "did:plc:repo";const ticket = (rkey: string) => `at://${REPO}/org.tangled.track.ticket/${rkey}`;
const row = (over: Partial<NotificationItem>): NotificationItem => ({ uri: "at://did:plc:actor/org.tangled.temp.notification/1", type: "issue_created", category: "work", actorDid: "did:plc:actor", read: false, createdAt: "2026-01-01T00:00:00.000Z", ...over});
const json = (body: unknown) => new Response(JSON.stringify(body), { headers: { "content-type": "application/json" } });
const HANDLES: Record<string, string> = { "did:plc:actor": "alice.test", "did:plc:owner": "bob.test"};
const setup = ({ ticketsStatus = 200 } = {}) => { const fetch = vi.fn<typeof globalThis.fetch>(async (input) => { const url = new URL(String(input)); if (url.pathname.endsWith("blue.microcosm.identity.resolveMiniDoc")) { const did = url.searchParams.get("identifier") ?? ""; return json({ did, handle: HANDLES[did] }); } if (url.pathname.endsWith("org.tangled.repo.getReposByRepoDids")) return json({ items: [ { uri: `at://${REPO}/org.tangled.repo.manifest/self`, value: { declaration: { owner: "did:plc:owner", name: "core" } } } ] }); if (!url.pathname.endsWith("org.tangled.track.getTickets")) return new Response("{}", { status: 404 }); if (ticketsStatus !== 200) return new Response("{}", { status: ticketsStatus }); return json({ items: url.searchParams.getAll("uris").map((uri) => ({ uri, value: { title: { text: `title of ${uri.split("/").pop()}` } } })) }); }); return { fetch, ctx: createBobbinClient({ serviceUrl: "https://bobbin.test", fetch }) };};
const ticketCalls = (fetch: ReturnType<typeof setup>["fetch"]) => fetch.mock.calls .map(([input]) => new URL(String(input))) .filter((url) => url.pathname.endsWith("org.tangled.track.getTickets"));
describe("hydrateNotifications titles", () => { it("reads issue and pull titles from one getTickets call", async () => { const { ctx, fetch } = setup(); const out = await hydrateNotifications(ctx, [ row({ issueAt: ticket("3001") }), row({ type: "pull_created", pullAt: ticket("3002") }), row({ issueAt: ticket("3001") }) ]); expect(out.map((n) => n.subject?.title)).toEqual([ "title of 3001", "title of 3002", "title of 3001" ]); expect(out.map((n) => n.subject?.kind)).toEqual(["issue", "pull", "issue"]); const calls = ticketCalls(fetch); expect(calls).toHaveLength(1); expect(calls[0].searchParams.getAll("uris")).toEqual([ticket("3001"), ticket("3002")]); });
it("keeps legacy uris out of the batch so they can't fail it", async () => { const { ctx, fetch } = setup(); const legacy = `at://${REPO}/sh.tangled.repo.issue/3kabc`; const out = await hydrateNotifications(ctx, [ row({ issueAt: legacy }), row({ issueAt: ticket("3001") }) ]); expect(ticketCalls(fetch)[0].searchParams.getAll("uris")).toEqual([ticket("3001")]); expect(out[0].subject).toMatchObject({ kind: "issue", uri: legacy, number: "3kabc" }); expect(out[0].subject?.title).toBeUndefined(); expect(out[1].subject?.title).toBe("title of 3001"); });
it("makes no getTickets call when every uri is legacy", async () => { const { ctx, fetch } = setup(); await hydrateNotifications(ctx, [ row({ pullAt: `at://${REPO}/sh.tangled.repo.pull/3kabc` }) ]); expect(ticketCalls(fetch)).toHaveLength(0); });
it("chunks the lookup at fifty uris", async () => { const { ctx, fetch } = setup(); const uris = Array.from({ length: 55 }, (_, index) => ticket(`r${index}`)); const out = await hydrateNotifications( ctx, uris.map((issueAt) => row({ issueAt })) ); expect(ticketCalls(fetch).map((url) => url.searchParams.getAll("uris").length)).toEqual([ 50, 5 ]); expect(out.every((n) => n.subject?.title)).toBe(true); });
it("keeps hydrating actors and repos when only getTickets fails", async () => { const { ctx } = setup({ ticketsStatus: 500 }); const out = await hydrateNotifications(ctx, [ row({ repoDid: REPO, issueAt: ticket("3001") }) ]); expect(out[0].actor.handle).toBe("alice.test"); expect(out[0].repo).toEqual({ ownerHandle: "bob.test", name: "core" }); expect(out[0].subject).toMatchObject({ kind: "issue", number: "3001" }); expect(out[0].subject?.title).toBeUndefined(); });});