~james/core

core/web/src/lib/api/identity.test.ts -rw-r--r-- 4.1 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
119
120
121
122
123
124
import { describe, expect, it, vi } from "vitest";
import { createBobbinClient, type BobbinContext } from "#lib/api/client.ts";
import { IdentityCache, type MiniDoc } from "#lib/api/identity.ts";
import type { Fetch } from "#lib/fetch.ts";

const DOC: MiniDoc = {
	did: "did:plc:x",
	handle: "alice.test",
	pds: "https://pds.example",
	signing_key: "zQ3shSiLsnqpyQ4SfDTT1D8qzFEoeYT8rSDXW6o8pVY7VcRBJ"
};

const docResponse = (doc: MiniDoc): Response =>
	new Response(JSON.stringify(doc), {
		status: 200,
		headers: { "content-type": "application/json" }
	});

const makeCtx = (fetchMock: Fetch): BobbinContext =>
	createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });

const deferred = <T>(): { promise: Promise<T>; resolve: (value: T) => void } => {
	let resolve!: (value: T) => void;
	const promise = new Promise<T>((r) => {
		resolve = r;
	});
	return { promise, resolve };
};

describe("IdentityCache.resolve", () => {
	it("fetches on the first miss then serves the cached document", async () => {
		const fetchMock = vi.fn<Fetch>().mockResolvedValue(docResponse(DOC));
		const cache = new IdentityCache(makeCtx(fetchMock));

		const first = await cache.resolve("alice.test");
		const second = await cache.resolve("alice.test");

		expect(first).toEqual(DOC);
		expect(second).toEqual(DOC);
		expect(fetchMock).toHaveBeenCalledTimes(1);
		expect((fetchMock.mock.calls[0][0] as URL).searchParams.get("identifier")).toBe(
			"alice.test"
		);
	});

	it("coalesces concurrent misses for the same key into one in-flight fetch", async () => {
		const gate = deferred<Response>();
		const fetchMock = vi.fn<Fetch>().mockReturnValue(gate.promise);
		const cache = new IdentityCache(makeCtx(fetchMock));

		const a = cache.resolve("alice.test");
		const b = cache.resolve("alice.test");
		expect(fetchMock).toHaveBeenCalledTimes(1);

		gate.resolve(docResponse(DOC));
		const [ra, rb] = await Promise.all([a, b]);
		expect(ra).toEqual(DOC);
		expect(rb).toEqual(DOC);
		expect(fetchMock).toHaveBeenCalledTimes(1);
	});

	it("re-fetches after the in-flight promise settles (no permanent stampede lock)", async () => {
		const fetchMock = vi.fn<Fetch>().mockResolvedValue(docResponse(DOC));
		const cache = new IdentityCache(makeCtx(fetchMock));

		await cache.resolve("bob.test");
		fetchMock.mockResolvedValueOnce(
			docResponse({
				did: "did:plc:y",
				handle: "charlie.test",
				pds: "https://pds.example",
				signing_key: "zQ3shSiLsnqpyQ4SfDTT1D8qzFEoeYT8rSDXW6o8pVY7VcRBJ"
			})
		);
		await cache.resolve("charlie.test");
		expect(fetchMock).toHaveBeenCalledTimes(2);
	});

	it("populates both directions of the index from a resolved document", async () => {
		const fetchMock = vi.fn<Fetch>().mockResolvedValue(docResponse(DOC));
		const cache = new IdentityCache(makeCtx(fetchMock));

		await cache.resolve("alice.test");
		expect(cache.didFor("alice.test")).toBe("did:plc:x");
		expect(cache.handleFor("did:plc:x")).toBe("alice.test");
	});
});

describe("IdentityCache.prime & map selection", () => {
	it("seeds both directions without any fetch", () => {
		const fetchMock = vi.fn<Fetch>();
		const cache = new IdentityCache(makeCtx(fetchMock));

		cache.prime(DOC);
		expect(cache.didFor("alice.test")).toBe("did:plc:x");
		expect(cache.handleFor("did:plc:x")).toBe("alice.test");
		expect(fetchMock).not.toHaveBeenCalled();
	});

	it("resolves a DID against the did map and a handle against the handle map", async () => {
		const fetchMock = vi.fn<Fetch>();
		const cache = new IdentityCache(makeCtx(fetchMock));
		cache.prime(DOC);

		await expect(cache.resolve("did:plc:x")).resolves.toEqual(DOC);
		await expect(cache.resolve("alice.test")).resolves.toEqual(DOC);
		expect(fetchMock).not.toHaveBeenCalled();

		// unknown dids miss the handle map and fetch.
		fetchMock.mockResolvedValueOnce(
			docResponse({
				did: "did:plc:z",
				handle: "dan.test",
				pds: "https://pds.example",
				signing_key: "zQ3shSiLsnqpyQ4SfDTT1D8qzFEoeYT8rSDXW6o8pVY7VcRBJ"
			})
		);
		await cache.resolve("did:plc:z");
		expect(fetchMock).toHaveBeenCalledTimes(1);
		expect((fetchMock.mock.calls[0][0] as URL).searchParams.get("identifier")).toBe(
			"did:plc:z"
		);
	});
});