The Appview for the kipclip.com atproto bookmarking service
1/**
2 * Tests for PLC directory resolution.
3 * Uses mock fetcher to avoid network calls.
4 */
5
6import "./test-setup.ts";
7
8import { assertEquals } from "@std/assert";
9import { resolveDidWithFetcher } from "../lib/plc-resolver.ts";
10import { createMockFetcher, createPlcResponse } from "./test-helpers.ts";
11
12Deno.test("resolveDid - resolves valid DID to PDS URL and handle", async () => {
13 const mockFetcher = createMockFetcher(
14 new Map([
15 [
16 "plc.directory/did:plc:test123",
17 createPlcResponse({
18 did: "did:plc:test123",
19 pdsUrl: "https://bsky.social",
20 handle: "alice.bsky.social",
21 }),
22 ],
23 ]),
24 );
25
26 const result = await resolveDidWithFetcher("did:plc:test123", mockFetcher);
27
28 assertEquals(result?.did, "did:plc:test123");
29 assertEquals(result?.pdsUrl, "https://bsky.social");
30 assertEquals(result?.handle, "alice.bsky.social");
31});
32
33Deno.test("resolveDid - returns null for 404 DID", async () => {
34 const mockFetcher = createMockFetcher(
35 new Map([
36 [
37 "plc.directory",
38 new Response("Not Found", { status: 404 }),
39 ],
40 ]),
41 );
42
43 const result = await resolveDidWithFetcher(
44 "did:plc:nonexistent",
45 mockFetcher,
46 );
47
48 assertEquals(result, null);
49});
50
51Deno.test("resolveDid - returns null for non-DID input", async () => {
52 const mockFetcher = createMockFetcher(new Map());
53
54 const result = await resolveDidWithFetcher("not-a-did", mockFetcher);
55
56 assertEquals(result, null);
57});
58
59Deno.test("resolveDid - returns null for DID without PDS service", async () => {
60 const mockFetcher = createMockFetcher(
61 new Map([
62 [
63 "plc.directory/did:plc:nopds",
64 new Response(
65 JSON.stringify({
66 id: "did:plc:nopds",
67 alsoKnownAs: ["at://test.handle"],
68 service: [], // No services
69 }),
70 {
71 status: 200,
72 headers: { "Content-Type": "application/json" },
73 },
74 ),
75 ],
76 ]),
77 );
78
79 const result = await resolveDidWithFetcher("did:plc:nopds", mockFetcher);
80
81 assertEquals(result, null);
82});
83
84Deno.test("resolveDid - extracts handle from alsoKnownAs", async () => {
85 const mockFetcher = createMockFetcher(
86 new Map([
87 [
88 "plc.directory/did:plc:test",
89 new Response(
90 JSON.stringify({
91 id: "did:plc:test",
92 alsoKnownAs: ["at://custom.handle.example"],
93 service: [
94 {
95 id: "#atproto_pds",
96 type: "AtprotoPersonalDataServer",
97 serviceEndpoint: "https://pds.example.com",
98 },
99 ],
100 }),
101 {
102 status: 200,
103 headers: { "Content-Type": "application/json" },
104 },
105 ),
106 ],
107 ]),
108 );
109
110 const result = await resolveDidWithFetcher("did:plc:test", mockFetcher);
111
112 assertEquals(result?.handle, "custom.handle.example");
113});
114
115Deno.test("resolveDid - uses DID as handle fallback", async () => {
116 const mockFetcher = createMockFetcher(
117 new Map([
118 [
119 "plc.directory/did:plc:nohandle",
120 new Response(
121 JSON.stringify({
122 id: "did:plc:nohandle",
123 alsoKnownAs: [], // No alsoKnownAs
124 service: [
125 {
126 id: "#atproto_pds",
127 type: "AtprotoPersonalDataServer",
128 serviceEndpoint: "https://pds.example.com",
129 },
130 ],
131 }),
132 {
133 status: 200,
134 headers: { "Content-Type": "application/json" },
135 },
136 ),
137 ],
138 ]),
139 );
140
141 const result = await resolveDidWithFetcher("did:plc:nohandle", mockFetcher);
142
143 assertEquals(result?.handle, "did:plc:nohandle");
144});
145
146Deno.test("resolveDid - handles network error gracefully", async () => {
147 const mockFetcher = createMockFetcher(
148 new Map([
149 [
150 "plc.directory",
151 new Response("Internal Server Error", { status: 500 }),
152 ],
153 ]),
154 );
155
156 const result = await resolveDidWithFetcher("did:plc:error", mockFetcher);
157
158 // Should return null on error (after logging)
159 assertEquals(result, null);
160});