@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.
- package/deno.json +2 -2
- package/dist/mod.cjs +2 -2
- package/dist/mod.js +2 -2
- package/dist/tests/{body-DGmcZ1Kz.cjs → body-BLtrczC8.cjs} +1 -1
- package/dist/tests/{body-Bx6-vXo_.mjs → body-PONsC2hg.mjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/docloader.test.cjs +57 -45
- package/dist/tests/docloader.test.mjs +48 -36
- package/dist/tests/multibase/multibase.test.cjs +7 -7
- package/dist/tests/multibase/multibase.test.mjs +5 -5
- package/dist/tests/{request-BV7yaWdl.mjs → request-BD0HpfwZ.mjs} +1 -1
- package/dist/tests/{request-Ban3HXuI.cjs → request-BdKDFsbY.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/package.json +6 -3
- package/scripts/test-bun.mjs +18 -0
- package/src/contexts.ts +1 -1
- package/src/docloader.test.ts +99 -68
- package/src/multibase/multibase.test.ts +5 -5
package/src/docloader.test.ts
CHANGED
|
@@ -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 "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
797
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
requests
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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.
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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
|
-
}
|
|
976
|
-
|
|
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.
|
|
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 "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
113
|
+
await t.step("constants indexed by code", () => {
|
|
114
114
|
const codes = constants.codes;
|
|
115
115
|
deepStrictEqual(Object.keys(codes).length, 23);
|
|
116
116
|
});
|