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<Fetch>(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<Fetch>(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<Fetch>()
.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<Fetch>()
.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<Fetch>()
.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);
});
});