@fedify/webfinger 2.3.0-dev.994 → 2.3.0-pr.809.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +2 -2
- package/dist/lookup.test.cjs +518 -143
- package/dist/lookup.test.js +520 -145
- package/dist/mod.cjs +148 -18
- package/dist/mod.d.cts +33 -2
- package/dist/mod.d.ts +33 -2
- package/dist/mod.js +148 -18
- package/package.json +7 -7
- package/src/lookup.test.ts +793 -236
- package/src/lookup.ts +253 -22
package/src/lookup.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { test } from "@fedify/fixture";
|
|
1
|
+
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
2
2
|
import { withTimeout } from "es-toolkit";
|
|
3
3
|
import fetchMock from "fetch-mock";
|
|
4
|
-
import { deepStrictEqual } from "node:assert/strict";
|
|
4
|
+
import { deepStrictEqual, ok } from "node:assert/strict";
|
|
5
5
|
import type { ResourceDescriptor } from "./jrd.ts";
|
|
6
6
|
import { lookupWebFinger } from "./lookup.ts";
|
|
7
7
|
|
|
@@ -15,6 +15,21 @@ test({
|
|
|
15
15
|
deepStrictEqual(await lookupWebFinger(new URL("acct:johndoe")), null);
|
|
16
16
|
deepStrictEqual(await lookupWebFinger("acct:johndoe@"), null);
|
|
17
17
|
deepStrictEqual(await lookupWebFinger(new URL("acct:johndoe@")), null);
|
|
18
|
+
// Per RFC 7565, the acct: authority is bare `host`: no path,
|
|
19
|
+
// query, or fragment is allowed. Reject such inputs rather than
|
|
20
|
+
// forwarding them to a remote WebFinger lookup.
|
|
21
|
+
deepStrictEqual(
|
|
22
|
+
await lookupWebFinger("acct:johndoe@example.com/exploit"),
|
|
23
|
+
null,
|
|
24
|
+
);
|
|
25
|
+
deepStrictEqual(
|
|
26
|
+
await lookupWebFinger("acct:johndoe@example.com?x=1"),
|
|
27
|
+
null,
|
|
28
|
+
);
|
|
29
|
+
deepStrictEqual(
|
|
30
|
+
await lookupWebFinger("acct:johndoe@example.com#frag"),
|
|
31
|
+
null,
|
|
32
|
+
);
|
|
18
33
|
});
|
|
19
34
|
|
|
20
35
|
await t.step("connection refused", async () => {
|
|
@@ -29,81 +44,117 @@ test({
|
|
|
29
44
|
});
|
|
30
45
|
|
|
31
46
|
fetchMock.spyGlobal();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
// Wrap the rest of the outer test in try/finally so the global
|
|
48
|
+
// `fetch` spy is torn down even if a t.step assertion below
|
|
49
|
+
// throws. Matches the cleanup pattern of the metrics test
|
|
50
|
+
// further down in this file.
|
|
51
|
+
try {
|
|
52
|
+
fetchMock.get(
|
|
53
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
54
|
+
{ status: 404 },
|
|
55
|
+
);
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
57
|
+
await t.step("not found", async () => {
|
|
58
|
+
deepStrictEqual(
|
|
59
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
60
|
+
null,
|
|
61
|
+
);
|
|
62
|
+
deepStrictEqual(await lookupWebFinger("https://example.com/foo"), null);
|
|
63
|
+
});
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
const expected: ResourceDescriptor = {
|
|
66
|
+
subject: "acct:johndoe@example.com",
|
|
67
|
+
links: [],
|
|
68
|
+
};
|
|
69
|
+
fetchMock.removeRoutes();
|
|
70
|
+
fetchMock.get(
|
|
71
|
+
"https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com",
|
|
72
|
+
{ body: expected },
|
|
73
|
+
);
|
|
51
74
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
await t.step("acct", async () => {
|
|
76
|
+
deepStrictEqual(
|
|
77
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
78
|
+
expected,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const expected2: ResourceDescriptor = {
|
|
83
|
+
subject: "https://example.com/foo",
|
|
84
|
+
links: [],
|
|
85
|
+
};
|
|
86
|
+
fetchMock.removeRoutes();
|
|
87
|
+
fetchMock.get(
|
|
88
|
+
"https://example.com/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%2Ffoo",
|
|
89
|
+
{ body: expected2 },
|
|
56
90
|
);
|
|
57
|
-
});
|
|
58
91
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"https://example.com/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%2Ffoo",
|
|
66
|
-
{ body: expected2 },
|
|
67
|
-
);
|
|
92
|
+
await t.step("https", async () => {
|
|
93
|
+
deepStrictEqual(
|
|
94
|
+
await lookupWebFinger("https://example.com/foo"),
|
|
95
|
+
expected2,
|
|
96
|
+
);
|
|
97
|
+
});
|
|
68
98
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
99
|
+
const mailtoExpected: ResourceDescriptor = {
|
|
100
|
+
subject: "mailto:juliet@example.com",
|
|
101
|
+
links: [],
|
|
102
|
+
};
|
|
103
|
+
fetchMock.removeRoutes();
|
|
104
|
+
fetchMock.get(
|
|
105
|
+
"https://example.com/.well-known/webfinger?resource=mailto%3Ajuliet%40example.com",
|
|
106
|
+
{ body: mailtoExpected },
|
|
73
107
|
);
|
|
74
|
-
});
|
|
75
108
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
await t.step("mailto", async () => {
|
|
110
|
+
// RFC 7033 permits any URI as a WebFinger resource, and RFC 7565
|
|
111
|
+
// explicitly references `mailto:` as an example. The opaque-path
|
|
112
|
+
// host extraction (after the last `@`) applies to `mailto:` just
|
|
113
|
+
// like `acct:`.
|
|
114
|
+
deepStrictEqual(
|
|
115
|
+
await lookupWebFinger("mailto:juliet@example.com"),
|
|
116
|
+
mailtoExpected,
|
|
117
|
+
);
|
|
118
|
+
});
|
|
81
119
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
120
|
+
const mailtoQueryExpected: ResourceDescriptor = {
|
|
121
|
+
subject: "mailto:juliet@example.com?subject=Hi",
|
|
122
|
+
links: [],
|
|
123
|
+
};
|
|
124
|
+
fetchMock.removeRoutes();
|
|
125
|
+
fetchMock.get(
|
|
126
|
+
"https://example.com/.well-known/webfinger?resource=mailto%3Ajuliet%40example.com%3Fsubject%3DHi",
|
|
127
|
+
{ body: mailtoQueryExpected },
|
|
128
|
+
);
|
|
85
129
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
);
|
|
130
|
+
await t.step("mailto with hfields", async () => {
|
|
131
|
+
// RFC 6068 §2 allows `mailto:` URIs to carry `?hfields=...`
|
|
132
|
+
// header fields and fragment identifiers. Unlike `acct:`,
|
|
133
|
+
// those components are part of the grammar, so the lookup
|
|
134
|
+
// must accept them and forward the full resource URI to the
|
|
135
|
+
// WebFinger endpoint.
|
|
136
|
+
deepStrictEqual(
|
|
137
|
+
await lookupWebFinger("mailto:juliet@example.com?subject=Hi"),
|
|
138
|
+
mailtoQueryExpected,
|
|
139
|
+
);
|
|
140
|
+
});
|
|
100
141
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
142
|
+
fetchMock.removeRoutes();
|
|
143
|
+
fetchMock.get(
|
|
144
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
145
|
+
{ body: "not json" },
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
await t.step("invalid response", async () => {
|
|
149
|
+
deepStrictEqual(
|
|
150
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
151
|
+
null,
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
fetchMock.removeRoutes();
|
|
156
|
+
fetchMock.get(
|
|
157
|
+
"begin:https://localhost/.well-known/webfinger?",
|
|
107
158
|
{
|
|
108
159
|
subject: "acct:test@localhost",
|
|
109
160
|
links: [
|
|
@@ -115,217 +166,723 @@ test({
|
|
|
115
166
|
],
|
|
116
167
|
},
|
|
117
168
|
);
|
|
118
|
-
});
|
|
119
169
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
170
|
+
await t.step("private address", async () => {
|
|
171
|
+
deepStrictEqual(await lookupWebFinger("acct:test@localhost"), null);
|
|
172
|
+
deepStrictEqual(
|
|
173
|
+
await lookupWebFinger("acct:test@localhost", {
|
|
174
|
+
allowPrivateAddress: true,
|
|
175
|
+
}),
|
|
176
|
+
{
|
|
177
|
+
subject: "acct:test@localhost",
|
|
178
|
+
links: [
|
|
179
|
+
{
|
|
180
|
+
rel: "self",
|
|
181
|
+
type: "application/activity+json",
|
|
182
|
+
href: "https://localhost/actor",
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
});
|
|
132
188
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
189
|
+
fetchMock.removeRoutes();
|
|
190
|
+
fetchMock.get(
|
|
191
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
192
|
+
{
|
|
193
|
+
status: 302,
|
|
194
|
+
headers: { Location: "/.well-known/webfinger2" },
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
fetchMock.get(
|
|
198
|
+
"begin:https://example.com/.well-known/webfinger2",
|
|
199
|
+
{ body: expected },
|
|
137
200
|
);
|
|
138
|
-
});
|
|
139
201
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
},
|
|
147
|
-
);
|
|
202
|
+
await t.step("redirection", async () => {
|
|
203
|
+
deepStrictEqual(
|
|
204
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
205
|
+
expected,
|
|
206
|
+
);
|
|
207
|
+
});
|
|
148
208
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
209
|
+
fetchMock.removeRoutes();
|
|
210
|
+
fetchMock.get(
|
|
211
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
212
|
+
{
|
|
213
|
+
status: 302,
|
|
214
|
+
headers: { Location: "/.well-known/webfinger" },
|
|
215
|
+
},
|
|
153
216
|
);
|
|
154
|
-
deepStrictEqual(result, null);
|
|
155
|
-
});
|
|
156
217
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
);
|
|
218
|
+
await t.step("infinite redirection", async () => {
|
|
219
|
+
const result = await withTimeout(
|
|
220
|
+
() => lookupWebFinger("acct:johndoe@example.com"),
|
|
221
|
+
2000,
|
|
222
|
+
);
|
|
223
|
+
deepStrictEqual(result, null);
|
|
224
|
+
});
|
|
165
225
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
226
|
+
fetchMock.removeRoutes();
|
|
227
|
+
fetchMock.get(
|
|
228
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
229
|
+
{
|
|
230
|
+
status: 302,
|
|
231
|
+
headers: { Location: "ftp://example.com/" },
|
|
232
|
+
},
|
|
233
|
+
);
|
|
169
234
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
235
|
+
await t.step("redirection to different protocol", async () => {
|
|
236
|
+
deepStrictEqual(
|
|
237
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
238
|
+
null,
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
fetchMock.removeRoutes();
|
|
243
|
+
fetchMock.get(
|
|
244
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
245
|
+
{
|
|
246
|
+
status: 302,
|
|
247
|
+
headers: { Location: "https://localhost/" },
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
await t.step("redirection to private address", async () => {
|
|
252
|
+
deepStrictEqual(
|
|
253
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
254
|
+
null,
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
fetchMock.removeRoutes();
|
|
259
|
+
let redirectCount = 0;
|
|
260
|
+
fetchMock.get(
|
|
261
|
+
"begin:https://example.com/.well-known/webfinger",
|
|
262
|
+
() => {
|
|
263
|
+
redirectCount++;
|
|
264
|
+
if (redirectCount < 3) {
|
|
265
|
+
return {
|
|
266
|
+
status: 302,
|
|
267
|
+
headers: {
|
|
268
|
+
Location: `/.well-known/webfinger?redirect=${redirectCount}`,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return { body: expected };
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
await t.step("custom maxRedirection", async () => {
|
|
277
|
+
// Test with maxRedirection: 1 (should fail; mock has 2 redirects)
|
|
278
|
+
redirectCount = 0;
|
|
279
|
+
deepStrictEqual(
|
|
280
|
+
await lookupWebFinger("acct:johndoe@example.com", {
|
|
281
|
+
maxRedirection: 1,
|
|
282
|
+
}),
|
|
283
|
+
null,
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Test with maxRedirection: 2 (should succeed; mock has exactly 2
|
|
287
|
+
// redirects, and `maxRedirection: N` follows up to N redirects)
|
|
288
|
+
redirectCount = 0;
|
|
289
|
+
deepStrictEqual(
|
|
290
|
+
await lookupWebFinger("acct:johndoe@example.com", {
|
|
291
|
+
maxRedirection: 2,
|
|
292
|
+
}),
|
|
293
|
+
expected,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
// Test with maxRedirection: 3 (should succeed)
|
|
297
|
+
redirectCount = 0;
|
|
298
|
+
deepStrictEqual(
|
|
299
|
+
await lookupWebFinger("acct:johndoe@example.com", {
|
|
300
|
+
maxRedirection: 3,
|
|
301
|
+
}),
|
|
302
|
+
expected,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Test with default maxRedirection: 5 (should succeed)
|
|
306
|
+
redirectCount = 0;
|
|
307
|
+
deepStrictEqual(
|
|
308
|
+
await lookupWebFinger("acct:johndoe@example.com"),
|
|
309
|
+
expected,
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Regression: `maxRedirection: 1` must allow exactly one 302 to be
|
|
314
|
+
// followed. An earlier implementation incremented the counter before
|
|
315
|
+
// the `>=` check, so `maxRedirection: 1` rejected the first redirect
|
|
316
|
+
// instead of following it. The expected semantics is "follow up to
|
|
317
|
+
// N redirects".
|
|
318
|
+
await t.step(
|
|
319
|
+
"maxRedirection: 1 follows exactly one redirect",
|
|
320
|
+
async () => {
|
|
321
|
+
// Mock with a single redirect: 302 → 200. Under the corrected
|
|
322
|
+
// semantics, `maxRedirection: 1` follows it and reaches the body.
|
|
323
|
+
fetchMock.removeRoutes();
|
|
324
|
+
let count = 0;
|
|
325
|
+
fetchMock.get(
|
|
326
|
+
"begin:https://example.com/.well-known/webfinger",
|
|
327
|
+
() => {
|
|
328
|
+
count++;
|
|
329
|
+
return count < 2
|
|
330
|
+
? {
|
|
331
|
+
status: 302,
|
|
332
|
+
headers: { Location: "/.well-known/webfinger?after=1" },
|
|
333
|
+
}
|
|
334
|
+
: { body: expected };
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
deepStrictEqual(
|
|
338
|
+
await lookupWebFinger("acct:johndoe@example.com", {
|
|
339
|
+
maxRedirection: 1,
|
|
340
|
+
}),
|
|
341
|
+
expected,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// Mock with two redirects. `maxRedirection: 1` rejects the
|
|
345
|
+
// second redirect.
|
|
346
|
+
fetchMock.removeRoutes();
|
|
347
|
+
count = 0;
|
|
348
|
+
fetchMock.get(
|
|
349
|
+
"begin:https://example.com/.well-known/webfinger",
|
|
350
|
+
() => {
|
|
351
|
+
count++;
|
|
352
|
+
return count < 3
|
|
353
|
+
? {
|
|
354
|
+
status: 302,
|
|
355
|
+
headers: {
|
|
356
|
+
Location: `/.well-known/webfinger?after=${count}`,
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
: { body: expected };
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
deepStrictEqual(
|
|
363
|
+
await lookupWebFinger("acct:johndoe@example.com", {
|
|
364
|
+
maxRedirection: 1,
|
|
365
|
+
}),
|
|
366
|
+
null,
|
|
367
|
+
);
|
|
368
|
+
},
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
fetchMock.removeRoutes();
|
|
372
|
+
fetchMock.get(
|
|
373
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
374
|
+
() =>
|
|
375
|
+
new Promise((resolve) => {
|
|
376
|
+
const timeoutId = setTimeout(() => {
|
|
377
|
+
resolve({ body: expected });
|
|
378
|
+
}, 1000);
|
|
379
|
+
|
|
380
|
+
return () => clearTimeout(timeoutId);
|
|
381
|
+
}),
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
await t.step("request cancellation", async () => {
|
|
385
|
+
// Test cancelling a request immediately using AbortController
|
|
386
|
+
const controller = new AbortController();
|
|
387
|
+
const promise = lookupWebFinger("acct:johndoe@example.com", {
|
|
388
|
+
signal: controller.signal,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Abort the request right after starting it
|
|
392
|
+
controller.abort();
|
|
393
|
+
deepStrictEqual(await promise, null);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
fetchMock.removeRoutes();
|
|
397
|
+
let redirectCount2 = 0;
|
|
398
|
+
fetchMock.get(
|
|
399
|
+
"begin:https://example.com/.well-known/webfinger",
|
|
400
|
+
() => {
|
|
401
|
+
redirectCount2++;
|
|
402
|
+
if (redirectCount2 === 1) {
|
|
403
|
+
return {
|
|
404
|
+
status: 302,
|
|
405
|
+
headers: { Location: "/.well-known/webfinger2" },
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return new Promise((resolve) => {
|
|
409
|
+
const timeoutId = setTimeout(() => {
|
|
410
|
+
resolve({ body: expected });
|
|
411
|
+
}, 1000);
|
|
412
|
+
|
|
413
|
+
return () => clearTimeout(timeoutId);
|
|
414
|
+
});
|
|
415
|
+
},
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
await t.step("cancellation during redirection", async () => {
|
|
419
|
+
// Test cancelling a request during redirection process
|
|
420
|
+
const controller = new AbortController();
|
|
421
|
+
const promise = lookupWebFinger("acct:johndoe@example.com", {
|
|
422
|
+
signal: controller.signal,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Cancel during the delayed second request after redirection
|
|
426
|
+
setTimeout(() => controller.abort(), 100);
|
|
427
|
+
deepStrictEqual(await promise, null);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
fetchMock.removeRoutes();
|
|
431
|
+
fetchMock.get(
|
|
432
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
433
|
+
() =>
|
|
434
|
+
new Promise((resolve) => {
|
|
435
|
+
const timeoutId = setTimeout(() => {
|
|
436
|
+
resolve({ body: expected });
|
|
437
|
+
}, 500);
|
|
438
|
+
|
|
439
|
+
return () => clearTimeout(timeoutId);
|
|
440
|
+
}),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
await t.step("cancellation with immediate abort", async () => {
|
|
444
|
+
// Test starting a request with an already aborted AbortController
|
|
445
|
+
const controller = new AbortController();
|
|
446
|
+
controller.abort();
|
|
447
|
+
|
|
448
|
+
// Use a signal that was already aborted before starting the request
|
|
449
|
+
const result = await lookupWebFinger("acct:johndoe@example.com", {
|
|
450
|
+
signal: controller.signal,
|
|
451
|
+
});
|
|
452
|
+
deepStrictEqual(result, null);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
fetchMock.removeRoutes();
|
|
456
|
+
fetchMock.get(
|
|
457
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
458
|
+
{ body: expected },
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
await t.step("successful request with signal", async () => {
|
|
462
|
+
// Test successful request with a normal AbortController signal
|
|
463
|
+
const controller = new AbortController();
|
|
464
|
+
const result = await lookupWebFinger("acct:johndoe@example.com", {
|
|
465
|
+
signal: controller.signal,
|
|
466
|
+
});
|
|
467
|
+
deepStrictEqual(result, expected);
|
|
468
|
+
});
|
|
469
|
+
} finally {
|
|
470
|
+
fetchMock.removeRoutes();
|
|
471
|
+
fetchMock.hardReset();
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test("lookupWebFinger() records webfinger.lookup counter and duration", {
|
|
477
|
+
sanitizeOps: false,
|
|
478
|
+
sanitizeResources: false,
|
|
479
|
+
}, async (t) => {
|
|
480
|
+
fetchMock.spyGlobal();
|
|
481
|
+
try {
|
|
482
|
+
const expected: ResourceDescriptor = {
|
|
483
|
+
subject: "acct:johndoe@example.com",
|
|
484
|
+
links: [],
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
await t.step(
|
|
488
|
+
"records result=found for a successful acct lookup",
|
|
489
|
+
async () => {
|
|
490
|
+
fetchMock.removeRoutes();
|
|
491
|
+
fetchMock.get(
|
|
492
|
+
"https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com",
|
|
493
|
+
{ body: expected },
|
|
494
|
+
);
|
|
495
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
496
|
+
const result = await lookupWebFinger("acct:johndoe@example.com", {
|
|
497
|
+
meterProvider,
|
|
498
|
+
});
|
|
499
|
+
deepStrictEqual(result, expected);
|
|
500
|
+
|
|
501
|
+
const counters = recorder.getMeasurements("webfinger.lookup");
|
|
502
|
+
deepStrictEqual(counters.length, 1);
|
|
503
|
+
deepStrictEqual(counters[0].type, "counter");
|
|
504
|
+
deepStrictEqual(counters[0].value, 1);
|
|
505
|
+
deepStrictEqual(
|
|
506
|
+
counters[0].attributes["webfinger.lookup.result"],
|
|
507
|
+
"found",
|
|
508
|
+
);
|
|
509
|
+
deepStrictEqual(
|
|
510
|
+
counters[0].attributes["webfinger.resource.scheme"],
|
|
511
|
+
"acct",
|
|
512
|
+
);
|
|
513
|
+
deepStrictEqual(
|
|
514
|
+
counters[0].attributes["activitypub.remote.host"],
|
|
515
|
+
"example.com",
|
|
516
|
+
);
|
|
517
|
+
deepStrictEqual(
|
|
518
|
+
counters[0].attributes["http.response.status_code"],
|
|
519
|
+
200,
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
const durations = recorder.getMeasurements("webfinger.lookup.duration");
|
|
523
|
+
deepStrictEqual(durations.length, 1);
|
|
524
|
+
deepStrictEqual(durations[0].type, "histogram");
|
|
525
|
+
deepStrictEqual(
|
|
526
|
+
durations[0].attributes["webfinger.lookup.result"],
|
|
527
|
+
"found",
|
|
528
|
+
);
|
|
529
|
+
deepStrictEqual(
|
|
530
|
+
durations[0].attributes["webfinger.resource.scheme"],
|
|
531
|
+
"acct",
|
|
532
|
+
);
|
|
533
|
+
ok(typeof durations[0].value === "number" && durations[0].value >= 0);
|
|
176
534
|
},
|
|
177
535
|
);
|
|
178
536
|
|
|
179
|
-
await t.step(
|
|
180
|
-
|
|
181
|
-
|
|
537
|
+
await t.step(
|
|
538
|
+
"records scheme=https for an https resource lookup",
|
|
539
|
+
async () => {
|
|
540
|
+
fetchMock.removeRoutes();
|
|
541
|
+
fetchMock.get(
|
|
542
|
+
"https://example.com/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%2Ffoo",
|
|
543
|
+
{ body: { subject: "https://example.com/foo", links: [] } },
|
|
544
|
+
);
|
|
545
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
546
|
+
await lookupWebFinger("https://example.com/foo", { meterProvider });
|
|
547
|
+
const counters = recorder.getMeasurements("webfinger.lookup");
|
|
548
|
+
deepStrictEqual(counters.length, 1);
|
|
549
|
+
deepStrictEqual(
|
|
550
|
+
counters[0].attributes["webfinger.resource.scheme"],
|
|
551
|
+
"https",
|
|
552
|
+
);
|
|
553
|
+
deepStrictEqual(
|
|
554
|
+
counters[0].attributes["webfinger.lookup.result"],
|
|
555
|
+
"found",
|
|
556
|
+
);
|
|
557
|
+
},
|
|
558
|
+
);
|
|
182
559
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
560
|
+
await t.step(
|
|
561
|
+
"records non-default ports for URL resources",
|
|
562
|
+
async () => {
|
|
563
|
+
fetchMock.removeRoutes();
|
|
564
|
+
fetchMock.get(
|
|
565
|
+
"https://example.com:8443/.well-known/webfinger?resource=https%3A%2F%2Fexample.com%3A8443%2Ffoo",
|
|
566
|
+
{ body: { subject: "https://example.com:8443/foo", links: [] } },
|
|
567
|
+
);
|
|
568
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
569
|
+
await lookupWebFinger("https://example.com:8443/foo", {
|
|
570
|
+
meterProvider,
|
|
571
|
+
});
|
|
572
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
573
|
+
ok(counter != null);
|
|
574
|
+
deepStrictEqual(
|
|
575
|
+
counter.attributes["activitypub.remote.host"],
|
|
576
|
+
"example.com:8443",
|
|
577
|
+
);
|
|
198
578
|
},
|
|
199
579
|
);
|
|
200
580
|
|
|
201
|
-
await t.step("
|
|
202
|
-
|
|
203
|
-
|
|
581
|
+
await t.step("records result=not_found with status 404", async () => {
|
|
582
|
+
fetchMock.removeRoutes();
|
|
583
|
+
fetchMock.get(
|
|
584
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
585
|
+
{ status: 404 },
|
|
586
|
+
);
|
|
587
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
588
|
+
const result = await lookupWebFinger("acct:johndoe@example.com", {
|
|
589
|
+
meterProvider,
|
|
590
|
+
});
|
|
591
|
+
deepStrictEqual(result, null);
|
|
592
|
+
|
|
593
|
+
const counters = recorder.getMeasurements("webfinger.lookup");
|
|
594
|
+
deepStrictEqual(counters.length, 1);
|
|
204
595
|
deepStrictEqual(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
596
|
+
counters[0].attributes["webfinger.lookup.result"],
|
|
597
|
+
"not_found",
|
|
598
|
+
);
|
|
599
|
+
deepStrictEqual(
|
|
600
|
+
counters[0].attributes["http.response.status_code"],
|
|
601
|
+
404,
|
|
602
|
+
);
|
|
603
|
+
deepStrictEqual(
|
|
604
|
+
counters[0].attributes["activitypub.remote.host"],
|
|
605
|
+
"example.com",
|
|
209
606
|
);
|
|
210
607
|
|
|
211
|
-
|
|
212
|
-
|
|
608
|
+
const durations = recorder.getMeasurements("webfinger.lookup.duration");
|
|
609
|
+
deepStrictEqual(durations.length, 1);
|
|
213
610
|
deepStrictEqual(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}),
|
|
217
|
-
expected,
|
|
611
|
+
durations[0].attributes["webfinger.lookup.result"],
|
|
612
|
+
"not_found",
|
|
218
613
|
);
|
|
614
|
+
});
|
|
219
615
|
|
|
220
|
-
|
|
221
|
-
|
|
616
|
+
await t.step("records result=not_found with status 410", async () => {
|
|
617
|
+
fetchMock.removeRoutes();
|
|
618
|
+
fetchMock.get(
|
|
619
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
620
|
+
{ status: 410 },
|
|
621
|
+
);
|
|
622
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
623
|
+
await lookupWebFinger("acct:johndoe@example.com", { meterProvider });
|
|
624
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
625
|
+
ok(counter != null);
|
|
222
626
|
deepStrictEqual(
|
|
223
|
-
|
|
224
|
-
|
|
627
|
+
counter.attributes["webfinger.lookup.result"],
|
|
628
|
+
"not_found",
|
|
225
629
|
);
|
|
630
|
+
deepStrictEqual(counter.attributes["http.response.status_code"], 410);
|
|
226
631
|
});
|
|
227
632
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
633
|
+
await t.step(
|
|
634
|
+
"records result=error for non-2xx, non-404/410 HTTP responses",
|
|
635
|
+
async () => {
|
|
636
|
+
fetchMock.removeRoutes();
|
|
637
|
+
fetchMock.get(
|
|
638
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
639
|
+
{ status: 500 },
|
|
640
|
+
);
|
|
641
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
642
|
+
await lookupWebFinger("acct:johndoe@example.com", { meterProvider });
|
|
643
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
644
|
+
ok(counter != null);
|
|
645
|
+
deepStrictEqual(
|
|
646
|
+
counter.attributes["webfinger.lookup.result"],
|
|
647
|
+
"error",
|
|
648
|
+
);
|
|
649
|
+
deepStrictEqual(counter.attributes["http.response.status_code"], 500);
|
|
650
|
+
},
|
|
239
651
|
);
|
|
240
652
|
|
|
241
|
-
await t.step(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
redirectCount2++;
|
|
259
|
-
if (redirectCount2 === 1) {
|
|
260
|
-
return {
|
|
261
|
-
status: 302,
|
|
262
|
-
headers: { Location: "/.well-known/webfinger2" },
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
return new Promise((resolve) => {
|
|
266
|
-
const timeoutId = setTimeout(() => {
|
|
267
|
-
resolve({ body: expected });
|
|
268
|
-
}, 1000);
|
|
269
|
-
|
|
270
|
-
return () => clearTimeout(timeoutId);
|
|
271
|
-
});
|
|
653
|
+
await t.step(
|
|
654
|
+
"records result=invalid for malformed JSON bodies",
|
|
655
|
+
async () => {
|
|
656
|
+
fetchMock.removeRoutes();
|
|
657
|
+
fetchMock.get(
|
|
658
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
659
|
+
{ body: "not json" },
|
|
660
|
+
);
|
|
661
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
662
|
+
await lookupWebFinger("acct:johndoe@example.com", { meterProvider });
|
|
663
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
664
|
+
ok(counter != null);
|
|
665
|
+
deepStrictEqual(
|
|
666
|
+
counter.attributes["webfinger.lookup.result"],
|
|
667
|
+
"invalid",
|
|
668
|
+
);
|
|
669
|
+
deepStrictEqual(counter.attributes["http.response.status_code"], 200);
|
|
272
670
|
},
|
|
273
671
|
);
|
|
274
672
|
|
|
275
|
-
await t.step(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
673
|
+
await t.step(
|
|
674
|
+
"records result=network_error when fetch never reaches the remote",
|
|
675
|
+
async () => {
|
|
676
|
+
fetchMock.removeRoutes();
|
|
677
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
678
|
+
const result = await lookupWebFinger(
|
|
679
|
+
"acct:johndoe@fedify-test.internal",
|
|
680
|
+
{ meterProvider },
|
|
681
|
+
);
|
|
682
|
+
deepStrictEqual(result, null);
|
|
683
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
684
|
+
ok(counter != null);
|
|
685
|
+
deepStrictEqual(
|
|
686
|
+
counter.attributes["webfinger.lookup.result"],
|
|
687
|
+
"network_error",
|
|
688
|
+
);
|
|
689
|
+
deepStrictEqual(
|
|
690
|
+
"http.response.status_code" in counter.attributes,
|
|
691
|
+
false,
|
|
692
|
+
"no HTTP response means no status code attribute",
|
|
693
|
+
);
|
|
694
|
+
deepStrictEqual(
|
|
695
|
+
counter.attributes["activitypub.remote.host"],
|
|
696
|
+
"fedify-test.internal",
|
|
697
|
+
);
|
|
698
|
+
},
|
|
699
|
+
);
|
|
281
700
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
701
|
+
await t.step(
|
|
702
|
+
"records result=invalid for malformed acct: resources",
|
|
703
|
+
async () => {
|
|
704
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
705
|
+
const result = await lookupWebFinger("acct:johndoe", { meterProvider });
|
|
706
|
+
deepStrictEqual(result, null);
|
|
707
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
708
|
+
ok(counter != null);
|
|
709
|
+
deepStrictEqual(
|
|
710
|
+
counter.attributes["webfinger.lookup.result"],
|
|
711
|
+
"invalid",
|
|
712
|
+
);
|
|
713
|
+
deepStrictEqual(
|
|
714
|
+
counter.attributes["webfinger.resource.scheme"],
|
|
715
|
+
"acct",
|
|
716
|
+
);
|
|
717
|
+
deepStrictEqual(
|
|
718
|
+
"activitypub.remote.host" in counter.attributes,
|
|
719
|
+
false,
|
|
720
|
+
"a malformed acct resource has no usable remote host",
|
|
721
|
+
);
|
|
722
|
+
},
|
|
723
|
+
);
|
|
286
724
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
725
|
+
await t.step(
|
|
726
|
+
"records result=invalid when the redirect chain exceeds maxRedirection",
|
|
727
|
+
async () => {
|
|
728
|
+
fetchMock.removeRoutes();
|
|
729
|
+
// The redirect Location drops the original `?resource=...` query
|
|
730
|
+
// string, so the second hop's URL no longer contains a `?`. The
|
|
731
|
+
// route pattern omits the trailing `?` so it still matches.
|
|
732
|
+
fetchMock.get(
|
|
733
|
+
"begin:https://example.com/.well-known/webfinger",
|
|
734
|
+
{
|
|
735
|
+
status: 302,
|
|
736
|
+
headers: { Location: "/.well-known/webfinger" },
|
|
737
|
+
},
|
|
738
|
+
);
|
|
739
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
740
|
+
const result = await withTimeout(
|
|
741
|
+
() =>
|
|
742
|
+
lookupWebFinger("acct:johndoe@example.com", {
|
|
743
|
+
meterProvider,
|
|
744
|
+
maxRedirection: 3,
|
|
745
|
+
}),
|
|
746
|
+
2000,
|
|
747
|
+
);
|
|
748
|
+
deepStrictEqual(result, null);
|
|
749
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
750
|
+
ok(counter != null);
|
|
751
|
+
deepStrictEqual(
|
|
752
|
+
counter.attributes["webfinger.lookup.result"],
|
|
753
|
+
"invalid",
|
|
754
|
+
);
|
|
755
|
+
deepStrictEqual(counter.attributes["http.response.status_code"], 302);
|
|
756
|
+
deepStrictEqual(
|
|
757
|
+
counter.attributes["activitypub.remote.host"],
|
|
758
|
+
"example.com",
|
|
759
|
+
);
|
|
760
|
+
},
|
|
298
761
|
);
|
|
299
762
|
|
|
300
|
-
await t.step(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
763
|
+
await t.step(
|
|
764
|
+
"records result=invalid for cross-protocol redirects",
|
|
765
|
+
async () => {
|
|
766
|
+
fetchMock.removeRoutes();
|
|
767
|
+
fetchMock.get(
|
|
768
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
769
|
+
{
|
|
770
|
+
status: 302,
|
|
771
|
+
headers: { Location: "ftp://example.com/" },
|
|
772
|
+
},
|
|
773
|
+
);
|
|
774
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
775
|
+
await lookupWebFinger("acct:johndoe@example.com", { meterProvider });
|
|
776
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
777
|
+
ok(counter != null);
|
|
778
|
+
deepStrictEqual(
|
|
779
|
+
counter.attributes["webfinger.lookup.result"],
|
|
780
|
+
"invalid",
|
|
781
|
+
);
|
|
782
|
+
deepStrictEqual(counter.attributes["http.response.status_code"], 302);
|
|
783
|
+
},
|
|
784
|
+
);
|
|
304
785
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
786
|
+
await t.step(
|
|
787
|
+
"records result=network_error when a redirect points to a private address",
|
|
788
|
+
async () => {
|
|
789
|
+
fetchMock.removeRoutes();
|
|
790
|
+
fetchMock.get(
|
|
791
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
792
|
+
{
|
|
793
|
+
status: 302,
|
|
794
|
+
headers: { Location: "https://localhost/" },
|
|
795
|
+
},
|
|
796
|
+
);
|
|
797
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
798
|
+
await lookupWebFinger("acct:johndoe@example.com", { meterProvider });
|
|
799
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
800
|
+
ok(counter != null);
|
|
801
|
+
deepStrictEqual(
|
|
802
|
+
counter.attributes["webfinger.lookup.result"],
|
|
803
|
+
"network_error",
|
|
804
|
+
);
|
|
805
|
+
deepStrictEqual(
|
|
806
|
+
counter.attributes["activitypub.remote.host"],
|
|
807
|
+
"localhost",
|
|
808
|
+
"remote.host reflects the latest URL we attempted, even after a redirect",
|
|
809
|
+
);
|
|
810
|
+
},
|
|
811
|
+
);
|
|
311
812
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
813
|
+
await t.step(
|
|
814
|
+
"records result=invalid for malformed Location headers",
|
|
815
|
+
async () => {
|
|
816
|
+
fetchMock.removeRoutes();
|
|
817
|
+
fetchMock.get(
|
|
818
|
+
"begin:https://example.com/.well-known/webfinger?",
|
|
819
|
+
{
|
|
820
|
+
status: 302,
|
|
821
|
+
headers: { Location: "http://[bad" },
|
|
822
|
+
},
|
|
823
|
+
);
|
|
824
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
825
|
+
await lookupWebFinger("acct:johndoe@example.com", { meterProvider });
|
|
826
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
827
|
+
ok(counter != null);
|
|
828
|
+
deepStrictEqual(
|
|
829
|
+
counter.attributes["webfinger.lookup.result"],
|
|
830
|
+
"invalid",
|
|
831
|
+
);
|
|
832
|
+
deepStrictEqual(counter.attributes["http.response.status_code"], 302);
|
|
833
|
+
deepStrictEqual(
|
|
834
|
+
counter.attributes["activitypub.remote.host"],
|
|
835
|
+
"example.com",
|
|
836
|
+
);
|
|
837
|
+
},
|
|
316
838
|
);
|
|
317
839
|
|
|
318
|
-
await t.step(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
840
|
+
await t.step(
|
|
841
|
+
"buckets unknown resource schemes as 'other' to keep metric cardinality bounded",
|
|
842
|
+
async () => {
|
|
843
|
+
// Lookups whose redirect chain ends on an unusual scheme (or a
|
|
844
|
+
// resource the caller passes with a non-fediverse scheme) must
|
|
845
|
+
// not leak that scheme into the metric attribute.
|
|
846
|
+
fetchMock.removeRoutes();
|
|
847
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
848
|
+
// `ssh:` is not a WebFinger scheme; lookupWebFingerInternal will
|
|
849
|
+
// attempt to build a host from the URL, fail, and return null.
|
|
850
|
+
// The metric still records, and its scheme attribute must be
|
|
851
|
+
// bucketed as `other`.
|
|
852
|
+
await lookupWebFinger("ssh://example.com/foo", { meterProvider });
|
|
853
|
+
const counter = recorder.getMeasurement("webfinger.lookup");
|
|
854
|
+
ok(counter != null);
|
|
855
|
+
deepStrictEqual(
|
|
856
|
+
counter.attributes["webfinger.resource.scheme"],
|
|
857
|
+
"other",
|
|
858
|
+
);
|
|
859
|
+
},
|
|
860
|
+
);
|
|
326
861
|
|
|
862
|
+
await t.step(
|
|
863
|
+
"omits measurements when no meterProvider is provided",
|
|
864
|
+
async () => {
|
|
865
|
+
fetchMock.removeRoutes();
|
|
866
|
+
fetchMock.get(
|
|
867
|
+
"https://example.com/.well-known/webfinger?resource=acct%3Ajohndoe%40example.com",
|
|
868
|
+
{ body: expected },
|
|
869
|
+
);
|
|
870
|
+
const [_unused, recorder] = createTestMeterProvider();
|
|
871
|
+
await lookupWebFinger("acct:johndoe@example.com");
|
|
872
|
+
deepStrictEqual(
|
|
873
|
+
recorder.getMeasurements("webfinger.lookup").length,
|
|
874
|
+
0,
|
|
875
|
+
);
|
|
876
|
+
deepStrictEqual(
|
|
877
|
+
recorder.getMeasurements("webfinger.lookup.duration").length,
|
|
878
|
+
0,
|
|
879
|
+
);
|
|
880
|
+
},
|
|
881
|
+
);
|
|
882
|
+
} finally {
|
|
883
|
+
fetchMock.removeRoutes();
|
|
327
884
|
fetchMock.hardReset();
|
|
328
|
-
}
|
|
885
|
+
}
|
|
329
886
|
});
|
|
330
887
|
|
|
331
888
|
// cSpell: ignore johndoe
|