~james/core

core/web/src/lib/auth/handleResolver.test.ts -rw-r--r-- 4.3 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
128
129
130
131
132
133
134
135
136
137
138
import { afterEach, describe, expect, test, vi } from "vitest";
import type { HandleResolver } from "@atcute/identity-resolver";
import {
	FallbackHandleResolver,
	handleResolverFor,
	handleResolverUrls
} from "#lib/auth/handleResolver.ts";

const answering = (did: string): HandleResolver => ({
	resolve: vi.fn(async () => did as `did:plc:${string}`)
});

const refusing = (message: string): HandleResolver => ({
	resolve: vi.fn(async () => {
		throw new Error(message);
	})
});

describe("handleResolverUrls", () => {
	test.each([
		[undefined, []],
		["", []],
		["https://pds.test/", ["https://pds.test"]],
		["https://tranquil.test, https://pds.test/", ["https://tranquil.test", "https://pds.test"]]
	])("reads %j", (raw, expected) => {
		expect(handleResolverUrls(raw)).toEqual(expected);
	});
});

describe("handleResolverFor without urls", () => {
	const bobbinUrl = () => "https://bobbin.test";
	afterEach(() => vi.unstubAllGlobals());

	const network = (routes: Record<string, () => Response>) =>
		vi.stubGlobal(
			"fetch",
			vi.fn(async (input: URL) => {
				const answer = routes[input.hostname];
				if (!answer) throw new TypeError(`unexpected fetch of ${input}`);
				return answer();
			})
		);

	const notFound = () => new Response("", { status: 404 });
	const dns =
		(Status: number, answers: string[] = []) =>
		() =>
			Response.json({
				Status,
				TC: false,
				RD: true,
				RA: true,
				AD: false,
				CD: false,
				Question: [{ name: "_atproto.x", type: 16 }],
				Answer: answers.map((data) => ({ name: "_atproto.x", type: 16, TTL: 60, data }))
			});

	test("asks the handle's own host for a handle with no dns record", async () => {
		network({
			"cloudflare-dns.com": dns(3),
			"new.tngl.sh": () => new Response("did:plc:abcdefghijklmnopqrstuvwx\n")
		});
		expect(await handleResolverFor([], bobbinUrl).resolve("new.tngl.sh")).toBe(
			"did:plc:abcdefghijklmnopqrstuvwx"
		);
	});

	test("reads the dns record when the handle's host answers nothing", async () => {
		network({
			"cloudflare-dns.com": dns(0, ["did=did:plc:abcdefghijklmnopqrstuvwx"]),
			"example.com": notFound
		});
		expect(await handleResolverFor([], bobbinUrl).resolve("example.com")).toBe(
			"did:plc:abcdefghijklmnopqrstuvwx"
		);
	});

	test("asks bobbin when the handle's host hides its answer behind cors", async () => {
		network({
			"cloudflare-dns.com": dns(3),
			"crashkeys.dev": () => {
				throw new TypeError("Failed to fetch");
			},
			"bobbin.test": () =>
				Response.json({
					did: "did:plc:abcdefghijklmnopqrstuvwx",
					handle: "crashkeys.dev",
					pds: "https://pds.crashkeys.dev"
				})
		});
		expect(await handleResolverFor([], bobbinUrl).resolve("crashkeys.dev")).toBe(
			"did:plc:abcdefghijklmnopqrstuvwx"
		);
	});

	test("fails when nobody answers", async () => {
		network({
			"cloudflare-dns.com": dns(3),
			"nobody.test": notFound,
			"bobbin.test": () => Response.json({ error: "UpstreamFailed" }, { status: 502 })
		});
		await expect(handleResolverFor([], bobbinUrl).resolve("nobody.test")).rejects.toThrow();
	});
});

describe("FallbackHandleResolver", () => {
	test("takes the first answer without asking the rest", async () => {
		const later = answering("did:plc:later");
		const resolver = new FallbackHandleResolver([answering("did:plc:first"), later]);
		expect(await resolver.resolve("org.tranquil.test")).toBe("did:plc:first");
		expect(later.resolve).not.toHaveBeenCalled();
	});

	test("falls back when an earlier resolver can't resolve the handle", async () => {
		const resolver = new FallbackHandleResolver([
			refusing("HandleNotFound"),
			answering("did:plc:alice")
		]);
		expect(await resolver.resolve("alice.pds.test")).toBe("did:plc:alice");
	});

	test("throws the last failure when nobody can resolve it", async () => {
		const resolver = new FallbackHandleResolver([refusing("first"), refusing("last")]);
		await expect(resolver.resolve("nobody.test")).rejects.toThrow("last");
	});

	test("stops on an aborted signal", async () => {
		const controller = new AbortController();
		controller.abort();
		const later = answering("did:plc:later");
		const resolver = new FallbackHandleResolver([refusing("aborted"), later]);
		await expect(
			resolver.resolve("org.tranquil.test", { signal: controller.signal })
		).rejects.toThrow("aborted");
		expect(later.resolve).not.toHaveBeenCalled();
	});
});