~james/core

core/web/src/lib/api/knot.test.ts -rw-r--r-- 4.4 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
125
126
127
import { afterEach, describe, expect, it, vi } from "vitest";
import type { BobbinContext } from "#lib/api/client.ts";
import { didOf, knotOf } from "#lib/api/syntax.ts";

const ctx = { xrpc: {}, serviceUrl: "https://bobbin.test" } as unknown as BobbinContext;
const repoDid = didOf("did:web:knot.test:repo");

vi.mock("$app/env", () => ({ browser: true }));

const knotPage = (capabilities?: string[]) =>
	({ ok: true, json: async () => ({ capabilities }) }) as unknown as Response;

const load = (knot: string | undefined) => {
	vi.resetModules();
	vi.doMock("#lib/api/repo.ts", () => ({ resolveRepoKnot: vi.fn(async () => knot) }));
	return import("./knot");
};

afterEach(() => {
	vi.unstubAllGlobals();
	vi.useRealTimers();
});

describe("knotCapabilities", () => {
	it("calls describeKnot once, caches responses, and shares one probe between concurrent callers", async () => {
		const urls: string[] = [];
		vi.stubGlobal(
			"fetch",
			vi.fn(async (input: RequestInfo | URL) => {
				urls.push(String(input));
				return knotPage(["knot-social-writes"]);
			})
		);
		const { knotCapabilities } = await load("knot.test");
		const [first, second] = await Promise.all([
			knotCapabilities(knotOf("knot.test")),
			knotCapabilities(knotOf("knot.test"))
		]);
		await knotCapabilities(knotOf("knot.test"));
		expect(urls).toEqual(["https://knot.test/xrpc/org.tangled.knot.describeKnot"]);
		expect(first).toEqual(["knot-social-writes"]);
		expect(second).toEqual(first);
	});

	it("returns null for malformed probe bodies instead of crashing, whatever alierns send", async () => {
		for (const body of [null, { capabilities: 42 }, { capabilities: ["ok", 7] }, "nope"]) {
			vi.stubGlobal(
				"fetch",
				vi.fn(async () => ({ ok: true, json: async () => body }) as unknown as Response)
			);
			const served = await load("knot.test");
			expect(await served.knotCapabilities(knotOf("knot.test"))).toBe(null);
		}
	});
});

describe("readTarget", () => {
	it.each([
		[
			"the origin serves social writes",
			["knot-social-writes"],
			knotOf("foster.test"),
			"knot.test"
		],
		["no foster knot is configured", ["knot-acl"], undefined, "knot.test"],
		["the foster knot is configured off", ["knot-acl"], "" as const, "knot.test"]
	])("reads from the origin when %s", async (_, originCaps, fosterUrl, expected) => {
		vi.stubGlobal(
			"fetch",
			vi.fn(async () => knotPage(originCaps))
		);
		const served = await load("knot.test");
		expect(await served.readTarget(ctx, repoDid, fosterUrl)).toBe(expected);
	});

	it("falls back to the foster knot when the origin lacks social writes, without probing it", async () => {
		const urls: string[] = [];
		vi.stubGlobal(
			"fetch",
			vi.fn(async (input: RequestInfo | URL) => {
				urls.push(String(input));
				return knotPage(["knot-acl"]);
			})
		);
		const served = await load("knot.test");
		expect(await served.readTarget(ctx, repoDid, knotOf("foster.test"))).toBe("foster.test");
		expect(urls).toEqual(["https://knot.test/xrpc/org.tangled.knot.describeKnot"]);
	});

	it("reads from the origin when the origin's probe fails, instead of guessing the foster knot", async () => {
		vi.stubGlobal(
			"fetch",
			vi.fn(() => Promise.reject(new Error("origin down")))
		);
		const served = await load("knot.test");
		expect(await served.readTarget(ctx, repoDid, knotOf("foster.test"))).toBe("knot.test");
	});

	it("returns undefined when the repo can't be resolved or bobbin is missing", async () => {
		const served = await load(undefined);
		expect(await served.readTarget(ctx, repoDid, knotOf("foster.test"))).toBe(undefined);
		expect(await served.readTargetFor(undefined, repoDid, knotOf("foster.test"))).toBe(
			undefined
		);
	});

	it("returns to the origin once a probe after the cache ttl sees social writes", async () => {
		const urls: string[] = [];
		let capabilities = ["knot-acl"];
		vi.stubGlobal(
			"fetch",
			vi.fn(async (input: RequestInfo | URL) => {
				urls.push(String(input));
				return knotPage(capabilities);
			})
		);
		const served = await load("knot.test");
		expect(await served.readTarget(ctx, repoDid, knotOf("foster.test"))).toBe("foster.test");
		vi.setSystemTime(Date.now() + served.CAPABILITY_TTL_MS + 1);
		capabilities = ["knot-social-writes"];
		expect(await served.readTarget(ctx, repoDid, knotOf("foster.test"))).toBe("knot.test");
		expect(urls).toEqual([
			"https://knot.test/xrpc/org.tangled.knot.describeKnot",
			"https://knot.test/xrpc/org.tangled.knot.describeKnot"
		]);
	});
});