~james/core

core/web/src/lib/api/lookupCache.test.ts -rw-r--r-- 4.2 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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);
	});
});