import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createBobbinClient, type BobbinContext } from "#lib/api/client.ts"; import { resolveMiniDoc, type MiniDoc } from "#lib/api/identity.ts"; import { didTag, handleTag, lookups } from "#lib/api/lookupCache.ts"; import { resolveRepoByName } from "#lib/api/repo.ts"; import type { Fetch } from "#lib/fetch.ts"; const ALICE: MiniDoc = { did: "did:plc:alice", handle: "alice.test", pds: "https://pds.example", signing_key: "zQ3shSiLsnqpyQ4SfDTT1D8qzFEoeYT8rSDXW6o8pVY7VcRBJ" }; const REPO = { uri: "at://did:plc:repo/org.tangled.repo.manifest/self", cid: "bafyrepo", value: { declaration: { owner: ALICE.did, name: "kernel" } }, git: { defaultBranch: "main", isEmpty: false } }; const json = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } }); const ctxWith = (fetchMock: Fetch): BobbinContext => createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock }); describe("server lookups", () => { beforeEach(() => lookups.configure(60_000)); afterEach(() => { lookups.configure(0); vi.useRealTimers(); }); it("answers a burst of identical lookups with one upstream call", async () => { const fetchMock = vi.fn(async () => json(ALICE)); const ctx = ctxWith(fetchMock); const docs = await Promise.all( Array.from({ length: 50 }, () => resolveMiniDoc(ctx, "alice.test")) ); expect(docs.every((doc) => doc.did === ALICE.did)).toBe(true); expect(fetchMock).toHaveBeenCalledTimes(1); }); it("asks again once the ttl runs out", async () => { vi.useFakeTimers(); const fetchMock = vi.fn(async () => json(ALICE)); const ctx = ctxWith(fetchMock); await resolveMiniDoc(ctx, "alice.test"); vi.advanceTimersByTime(59_999); await resolveMiniDoc(ctx, "alice.test"); expect(fetchMock).toHaveBeenCalledTimes(1); vi.advanceTimersByTime(2); await resolveMiniDoc(ctx, "alice.test"); expect(fetchMock).toHaveBeenCalledTimes(2); }); it("never keeps an error", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(json({ error: "UpstreamFailed" }, 502)) .mockImplementation(async () => json(ALICE)); const ctx = ctxWith(fetchMock); await expect(resolveMiniDoc(ctx, "alice.test")).rejects.toThrow(); await expect(resolveMiniDoc(ctx, "alice.test")).resolves.toMatchObject({ did: ALICE.did }); await resolveMiniDoc(ctx, "alice.test"); expect(fetchMock).toHaveBeenCalledTimes(2); }); it("drops lookups by did and by both handles when a handle moves", async () => { const renamed = { ...ALICE, handle: "alice.example" }; const fetchMock = vi .fn() .mockResolvedValueOnce(json(ALICE)) .mockResolvedValueOnce(json(ALICE)) .mockResolvedValueOnce(json({ error: "InvalidRequest", message: "not found" }, 400)) .mockImplementation(async () => json(renamed)); const ctx = ctxWith(fetchMock); await resolveMiniDoc(ctx, "alice.test"); await resolveMiniDoc(ctx, ALICE.did); await expect(resolveMiniDoc(ctx, "alice.example")).rejects.toThrow(); await resolveMiniDoc(ctx, ALICE.did); await resolveMiniDoc(ctx, "alice.test"); expect(fetchMock).toHaveBeenCalledTimes(3); lookups.evict([didTag(ALICE.did), handleTag(renamed.handle)]); await expect(resolveMiniDoc(ctx, ALICE.did)).resolves.toMatchObject(renamed); await expect(resolveMiniDoc(ctx, "alice.test")).resolves.toMatchObject(renamed); await expect(resolveMiniDoc(ctx, "alice.example")).resolves.toMatchObject(renamed); expect(fetchMock).toHaveBeenCalledTimes(6); }); it("keeps a found repo until its repo did changes, and never a missing one", async () => { const fetchMock = vi .fn() .mockResolvedValueOnce(json({ error: "NotFound" }, 404)) .mockImplementation(async () => json(REPO)); const ctx = ctxWith(fetchMock); await expect(resolveRepoByName(ctx, ALICE.did, "kernel")).resolves.toBeNull(); await expect(resolveRepoByName(ctx, ALICE.did, "kernel")).resolves.toMatchObject({ uri: REPO.uri }); await resolveRepoByName(ctx, ALICE.did, "Kernel"); expect(fetchMock).toHaveBeenCalledTimes(2); lookups.evict([didTag("did:plc:repo")]); await resolveRepoByName(ctx, ALICE.did, "kernel"); expect(fetchMock).toHaveBeenCalledTimes(3); }); });