@fedify/vocab-runtime 2.0.31-dev.2308 → 2.0.31-dev.2316

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.
@@ -2,7 +2,7 @@ import { configure, type LogRecord, reset } from "@logtape/logtape";
2
2
  import fetchMock from "fetch-mock";
3
3
  import { deepStrictEqual, ok, rejects } from "node:assert";
4
4
  import dns from "node:dns/promises";
5
- import { test } from "node:test";
5
+ import { test } from "@fedify/fixture";
6
6
  import { createServer } from "node:http";
7
7
  import { gzipSync } from "node:zlib";
8
8
  import preloadedContexts from "./contexts.ts";
@@ -37,7 +37,7 @@ test("getDocumentLoader()", async (t) => {
37
37
  },
38
38
  });
39
39
 
40
- await t.test("ok", async () => {
40
+ await t.step("ok", async () => {
41
41
  deepStrictEqual(await fetchDocumentLoader("https://example.com/object"), {
42
42
  contextUrl: null,
43
43
  documentUrl: "https://example.com/object",
@@ -94,7 +94,7 @@ test("getDocumentLoader()", async (t) => {
94
94
  },
95
95
  });
96
96
 
97
- await t.test("Link header", async () => {
97
+ await t.step("Link header", async () => {
98
98
  deepStrictEqual(await fetchDocumentLoader("https://example.com/link-ctx"), {
99
99
  contextUrl: "https://www.w3.org/ns/activitystreams",
100
100
  documentUrl: "https://example.com/link-ctx",
@@ -117,7 +117,7 @@ test("getDocumentLoader()", async (t) => {
117
117
  });
118
118
  });
119
119
 
120
- await t.test("Link header relative url", async () => {
120
+ await t.step("Link header relative url", async () => {
121
121
  deepStrictEqual(await fetchDocumentLoader("https://example.com/link-ctx"), {
122
122
  contextUrl: "https://www.w3.org/ns/activitystreams",
123
123
  documentUrl: "https://example.com/link-ctx",
@@ -143,7 +143,7 @@ test("getDocumentLoader()", async (t) => {
143
143
  );
144
144
  });
145
145
 
146
- await t.test("wrong Link header syntax", async () => {
146
+ await t.step("wrong Link header syntax", async () => {
147
147
  deepStrictEqual(
148
148
  await fetchDocumentLoader("https://example.com/obj-w-wrong-link"),
149
149
  {
@@ -172,7 +172,7 @@ test("getDocumentLoader()", async (t) => {
172
172
  headers: { "Content-Type": "text/html; charset=utf-8" },
173
173
  });
174
174
 
175
- await t.test("HTML <link>", async () => {
175
+ await t.step("HTML <link>", async () => {
176
176
  deepStrictEqual(
177
177
  await fetchDocumentLoader("https://example.com/html-link"),
178
178
  {
@@ -201,7 +201,7 @@ test("getDocumentLoader()", async (t) => {
201
201
  headers: { "Content-Type": "application/xhtml+xml; charset=utf-8" },
202
202
  });
203
203
 
204
- await t.test("XHTML <link>", async () => {
204
+ await t.step("XHTML <link>", async () => {
205
205
  deepStrictEqual(
206
206
  await fetchDocumentLoader("https://example.com/xhtml-link"),
207
207
  {
@@ -232,7 +232,7 @@ test("getDocumentLoader()", async (t) => {
232
232
  headers: { "Content-Type": "text/html; charset=utf-8" },
233
233
  });
234
234
 
235
- await t.test("HTML <a>", async () => {
235
+ await t.step("HTML <a>", async () => {
236
236
  deepStrictEqual(await fetchDocumentLoader("https://example.com/html-a"), {
237
237
  contextUrl: null,
238
238
  documentUrl: "https://example.com/object",
@@ -255,7 +255,7 @@ test("getDocumentLoader()", async (t) => {
255
255
  headers: { "Content-Type": "text/html; charset=utf-8" },
256
256
  });
257
257
 
258
- await t.test("Wrong Content-Type", async () => {
258
+ await t.step("Wrong Content-Type", async () => {
259
259
  deepStrictEqual(
260
260
  await fetchDocumentLoader("https://example.com/wrong-content-type"),
261
261
  {
@@ -273,7 +273,7 @@ test("getDocumentLoader()", async (t) => {
273
273
 
274
274
  fetchMock.get("https://example.com/404", { status: 404 });
275
275
 
276
- await t.test("not ok", async () => {
276
+ await t.step("not ok", async () => {
277
277
  await rejects(
278
278
  () => fetchDocumentLoader("https://example.com/404"),
279
279
  FetchError,
@@ -281,7 +281,7 @@ test("getDocumentLoader()", async (t) => {
281
281
  );
282
282
  });
283
283
 
284
- await t.test("preloaded contexts", async () => {
284
+ await t.step("preloaded contexts", async () => {
285
285
  for (const [url, document] of Object.entries(preloadedContexts)) {
286
286
  deepStrictEqual(await fetchDocumentLoader(url), {
287
287
  contextUrl: null,
@@ -291,13 +291,37 @@ test("getDocumentLoader()", async (t) => {
291
291
  }
292
292
  });
293
293
 
294
+ await t.step("preloaded Mastodon attribution domains", async () => {
295
+ const context = [
296
+ "https://www.w3.org/ns/activitystreams",
297
+ "http://joinmastodon.org/ns",
298
+ ];
299
+ const expanded = await jsonld.expand({
300
+ "@context": context,
301
+ id: "https://social.example/users/alice",
302
+ type: "Person",
303
+ attributionDomains: ["blog.example"],
304
+ }, { documentLoader: fetchDocumentLoader });
305
+ deepStrictEqual(expanded, [{
306
+ "@id": "https://social.example/users/alice",
307
+ "@type": ["https://www.w3.org/ns/activitystreams#Person"],
308
+ "http://joinmastodon.org/ns#attributionDomains": [
309
+ { "@value": "blog.example" },
310
+ ],
311
+ }]);
312
+ const compacted = await jsonld.compact(expanded, context, {
313
+ documentLoader: fetchDocumentLoader,
314
+ });
315
+ deepStrictEqual(compacted.attributionDomains, ["blog.example"]);
316
+ });
317
+
294
318
  // Controlled Identifiers v1.0 requires JSON-LD processors to treat this
295
319
  // context URL as already resolved. A temporary W3C outage must not prevent
296
320
  // an otherwise valid document from being processed.
297
321
  // See: https://www.w3.org/TR/cid-1.0/#json-ld-context
298
322
  // https://github.com/fedify-dev/fedify/issues/932
299
323
  fetchMock.get("https://www.w3.org/ns/cid/v1", { status: 503 });
300
- await t.test("preloaded CID v1 context", async () => {
324
+ await t.step("preloaded CID v1 context", async () => {
301
325
  const url = "https://www.w3.org/ns/cid/v1";
302
326
  deepStrictEqual(await fetchDocumentLoader(url), {
303
327
  contextUrl: null,
@@ -311,7 +335,7 @@ test("getDocumentLoader()", async (t) => {
311
335
  // document referencing it fails before application handlers run. It has to
312
336
  // be resolved from the built-in copy rather than over the network.
313
337
  // See: https://github.com/fedify-dev/fedify/issues/982
314
- await t.test("preloaded FEP-ef61 context", async () => {
338
+ await t.step("preloaded FEP-ef61 context", async () => {
315
339
  const url = "https://w3id.org/fep/ef61";
316
340
  ok(url in preloadedContexts);
317
341
  deepStrictEqual(await fetchDocumentLoader(url), {
@@ -334,7 +358,7 @@ test("getDocumentLoader()", async (t) => {
334
358
  // A Codeberg Pages outage must not prevent loading the FEP-7aa9 context.
335
359
  // See: https://github.com/fedify-dev/fedify/issues/1078
336
360
  fetchMock.get("https://w3id.org/fep/7aa9", { status: 502 });
337
- await t.test("preloaded FEP-7aa9 context", async () => {
361
+ await t.step("preloaded FEP-7aa9 context", async () => {
338
362
  const url = "https://w3id.org/fep/7aa9";
339
363
  deepStrictEqual(await fetchDocumentLoader(url), {
340
364
  contextUrl: null,
@@ -371,7 +395,7 @@ test("getDocumentLoader()", async (t) => {
371
395
  // A Codeberg Pages outage must not prevent loading or expanding FEP-6757.
372
396
  // See: https://github.com/fedify-dev/fedify/issues/1211
373
397
  fetchMock.get("https://w3id.org/fep/6757", { status: 502 });
374
- await t.test("preloaded FEP-6757 context", async () => {
398
+ await t.step("preloaded FEP-6757 context", async () => {
375
399
  const url = "https://w3id.org/fep/6757";
376
400
  deepStrictEqual(await fetchDocumentLoader(url), {
377
401
  contextUrl: null,
@@ -412,7 +436,7 @@ test("getDocumentLoader()", async (t) => {
412
436
  // A Codeberg Pages outage must not prevent expanding translation metadata.
413
437
  // See: https://github.com/fedify-dev/fedify/issues/1214
414
438
  fetchMock.get("https://w3id.org/fep/22cd", { status: 502 });
415
- await t.test("preloaded FEP-22cd context", async () => {
439
+ await t.step("preloaded FEP-22cd context", async () => {
416
440
  const url = "https://w3id.org/fep/22cd";
417
441
  const articleId = "https://example.com/articles/1";
418
442
  const translatorId = "https://example.com/users/alice";
@@ -491,7 +515,7 @@ test("getDocumentLoader()", async (t) => {
491
515
  );
492
516
  });
493
517
 
494
- await t.test("deny non-HTTP/HTTPS", async () => {
518
+ await t.step("deny non-HTTP/HTTPS", async () => {
495
519
  await rejects(
496
520
  () => fetchDocumentLoader("ftp://localhost"),
497
521
  UrlError,
@@ -525,7 +549,7 @@ test("getDocumentLoader()", async (t) => {
525
549
  },
526
550
  });
527
551
 
528
- await t.test("allowPrivateAddress: false", async () => {
552
+ await t.step("allowPrivateAddress: false", async () => {
529
553
  await rejects(
530
554
  () => fetchDocumentLoader("https://localhost/object"),
531
555
  UrlError,
@@ -542,7 +566,7 @@ test("getDocumentLoader()", async (t) => {
542
566
 
543
567
  const fetchDocumentLoader2 = getDocumentLoader({ allowPrivateAddress: true });
544
568
 
545
- await t.test("allowPrivateAddress: true", async () => {
569
+ await t.step("allowPrivateAddress: true", async () => {
546
570
  const expected = {
547
571
  contextUrl: null,
548
572
  documentUrl: "https://localhost/object",
@@ -579,7 +603,7 @@ test("getDocumentLoader()", async (t) => {
579
603
  };
580
604
  });
581
605
 
582
- await t.test("too many redirects", async () => {
606
+ await t.step("too many redirects", async () => {
583
607
  redirectAttempts = 0;
584
608
  await rejects(
585
609
  () => fetchDocumentLoader("https://example.com/too-many-redirects/0"),
@@ -605,7 +629,7 @@ test("getDocumentLoader()", async (t) => {
605
629
  };
606
630
  });
607
631
 
608
- await t.test("redirect loop", async () => {
632
+ await t.step("redirect loop", async () => {
609
633
  loopAttempts = 0;
610
634
  await rejects(
611
635
  () => fetchDocumentLoader("https://example.com/redirect-loop-a"),
@@ -624,7 +648,7 @@ test("getDocumentLoader()", async (t) => {
624
648
  };
625
649
  });
626
650
 
627
- await t.test("redirect loop with relative location", async () => {
651
+ await t.step("redirect loop with relative location", async () => {
628
652
  relativeLoopAttempts = 0;
629
653
  await rejects(
630
654
  () => fetchDocumentLoader("https://example.com/redirect-loop-relative"),
@@ -644,7 +668,7 @@ test("getDocumentLoader()", async (t) => {
644
668
  headers: { "Content-Type": "text/html; charset=utf-8" },
645
669
  });
646
670
 
647
- await t.test("ReDoS resistance (CVE-2025-68475)", async () => {
671
+ await t.step("ReDoS resistance (CVE-2025-68475)", async () => {
648
672
  const start = performance.now();
649
673
  // The malicious HTML will fail JSON parsing, but the important thing is
650
674
  // that it should complete quickly (not hang due to ReDoS)
@@ -793,12 +817,13 @@ test("getDocumentLoader() logs DNS failures as such", async (t) => {
793
817
  "Deno" in globalThis &&
794
818
  (await Deno.permissions.query({ name: "net" })).state !== "granted"
795
819
  ) {
796
- t.skip("requires the net permission");
797
- return;
820
+ throw new Error(
821
+ "This DNS test requires Deno network permission (--allow-net).",
822
+ );
798
823
  }
799
824
  const loader = getDocumentLoader();
800
825
  for (const result of ["throws", "empty", "private"] as const) {
801
- await t.test(result, async () => {
826
+ await t.step(result, async () => {
802
827
  // Stubbing works only because url.ts uses the default node:dns/promises
803
828
  // import; see the FIXME there.
804
829
  const originalLookup = dns.lookup;
@@ -854,7 +879,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
854
879
  const loader = getDocumentLoader({ allowPrivateAddress: true });
855
880
  for (const mode of ["header", "html", "mixed"] as const) {
856
881
  for (const hops of [20, 21]) {
857
- await t.test(`${mode}: ${hops} hops`, async () => {
882
+ await t.step(`${mode}: ${hops} hops`, async () => {
858
883
  fetchMock.mockGlobal();
859
884
  let requests = 0;
860
885
  fetchMock.get(`begin:${base}`, ({ url }) => {
@@ -899,7 +924,7 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
899
924
  "redirect-intermediate",
900
925
  ]
901
926
  ) {
902
- await t.test(`${mode}: cycle`, async () => {
927
+ await t.step(`${mode}: cycle`, async () => {
903
928
  fetchMock.mockGlobal();
904
929
  let requests = 0;
905
930
  fetchMock.get(`begin:${base}`, ({ url }) => {
@@ -933,49 +958,55 @@ test("getDocumentLoader() bounds alternate document chains", async (t) => {
933
958
  });
934
959
  }
935
960
 
936
- await t.test("alternate followed by 20 redirects shares the limit", async () => {
937
- fetchMock.mockGlobal();
938
- let requests = 0;
939
- fetchMock.get(`begin:${base}`, ({ url }) => {
940
- requests++;
941
- const index = Number(new URL(url).pathname.split("/").at(-1));
942
- if (index === 21) return Response.json({ done: true });
943
- return index === 0
944
- ? alternate(`${base}1`, false)
945
- : Response.redirect(`${base}${index + 1}`, 302);
946
- });
947
- try {
948
- await rejects(loader(`${base}0`), (error: unknown) => {
949
- ok(error instanceof FetchError);
950
- ok(error.message.includes("Too many redirections (21)"));
951
- return true;
961
+ await t.step(
962
+ "alternate followed by 20 redirects shares the limit",
963
+ async () => {
964
+ fetchMock.mockGlobal();
965
+ let requests = 0;
966
+ fetchMock.get(`begin:${base}`, ({ url }) => {
967
+ requests++;
968
+ const index = Number(new URL(url).pathname.split("/").at(-1));
969
+ if (index === 21) return Response.json({ done: true });
970
+ return index === 0
971
+ ? alternate(`${base}1`, false)
972
+ : Response.redirect(`${base}${index + 1}`, 302);
952
973
  });
953
- deepStrictEqual(requests, 21);
954
- } finally {
955
- fetchMock.hardReset();
956
- }
957
- });
974
+ try {
975
+ await rejects(loader(`${base}0`), (error: unknown) => {
976
+ ok(error instanceof FetchError);
977
+ ok(error.message.includes("Too many redirections (21)"));
978
+ return true;
979
+ });
980
+ deepStrictEqual(requests, 21);
981
+ } finally {
982
+ fetchMock.hardReset();
983
+ }
984
+ },
985
+ );
958
986
 
959
- await t.test("relative alternate after redirect and isolated calls", async () => {
960
- fetchMock.mockGlobal();
961
- fetchMock.get(`${base}start`, Response.redirect(`${base}html`, 302));
962
- fetchMock.get(`${base}html`, alternate("./document", true));
963
- fetchMock.get(`${base}document`, Response.json({ done: true }));
964
- try {
965
- for (let i = 0; i < 2; i++) {
966
- const results = await Promise.all([
967
- loader(`${base}start`),
968
- loader(`${base}start`),
969
- ]);
970
- for (const result of results) {
971
- deepStrictEqual(result.document, { done: true });
972
- deepStrictEqual(result.documentUrl, `${base}document`);
987
+ await t.step(
988
+ "relative alternate after redirect and isolated calls",
989
+ async () => {
990
+ fetchMock.mockGlobal();
991
+ fetchMock.get(`${base}start`, Response.redirect(`${base}html`, 302));
992
+ fetchMock.get(`${base}html`, alternate("./document", true));
993
+ fetchMock.get(`${base}document`, Response.json({ done: true }));
994
+ try {
995
+ for (let i = 0; i < 2; i++) {
996
+ const results = await Promise.all([
997
+ loader(`${base}start`),
998
+ loader(`${base}start`),
999
+ ]);
1000
+ for (const result of results) {
1001
+ deepStrictEqual(result.document, { done: true });
1002
+ deepStrictEqual(result.documentUrl, `${base}document`);
1003
+ }
973
1004
  }
1005
+ } finally {
1006
+ fetchMock.hardReset();
974
1007
  }
975
- } finally {
976
- fetchMock.hardReset();
977
- }
978
- });
1008
+ },
1009
+ );
979
1010
 
980
1011
  function alternate(next: string, html: boolean): Response {
981
1012
  return html
@@ -998,7 +1029,7 @@ test("getDocumentLoader() preserves cancellation across alternates", async (t) =
998
1029
  const base = "https://example.com/alternate-abort/";
999
1030
  const loader = getDocumentLoader({ allowPrivateAddress: true });
1000
1031
  for (const html of [false, true]) {
1001
- await t.test(html ? "HTML" : "Link header", async () => {
1032
+ await t.step(html ? "HTML" : "Link header", async () => {
1002
1033
  fetchMock.mockGlobal();
1003
1034
  const controller = new AbortController();
1004
1035
  let requests = 0;
@@ -1,5 +1,5 @@
1
1
  import { deepStrictEqual } from "node:assert";
2
- import { test } from "node:test";
2
+ import { test } from "@fedify/fixture";
3
3
  import * as constants from "./constants.ts";
4
4
  import { decodeMultibase, encodeMultibase } from "./mod.ts";
5
5
  import type { BaseName } from "./types.d.ts";
@@ -76,7 +76,7 @@ test("multibase.encode and decode", async (t) => {
76
76
  ];
77
77
 
78
78
  for (const [name, input, expectedOutput] of testCases) {
79
- await t.test(`Encoding/Decoding ${name} with ${input}`, () => {
79
+ await t.step(`Encoding/Decoding ${name} with ${input}`, () => {
80
80
  const encoded = encodeMultibase(name, encodeText(input));
81
81
  deepStrictEqual(
82
82
  decodeText(encoded),
@@ -96,7 +96,7 @@ test("multibase.encode and decode", async (t) => {
96
96
  });
97
97
  }
98
98
 
99
- await t.test("should allow base32pad full alphabet", () => {
99
+ await t.step("should allow base32pad full alphabet", () => {
100
100
  const encodedStr = "ctimaq4ygg2iegci7";
101
101
  const decoded = decodeMultibase(encodedStr);
102
102
  const encoded = encodeMultibase("c", decoded);
@@ -105,12 +105,12 @@ test("multibase.encode and decode", async (t) => {
105
105
  });
106
106
 
107
107
  test("constants", async (t) => {
108
- await t.test("constants indexed by name", () => {
108
+ await t.step("constants indexed by name", () => {
109
109
  const names = constants.names;
110
110
  deepStrictEqual(Object.keys(names).length, 23);
111
111
  });
112
112
 
113
- await t.test("constants indexed by code", () => {
113
+ await t.step("constants indexed by code", () => {
114
114
  const codes = constants.codes;
115
115
  deepStrictEqual(Object.keys(codes).length, 23);
116
116
  });