@fedify/testing 2.4.0-dev.2155 → 2.4.0-dev.2159

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.
Files changed (3) hide show
  1. package/dist/mod.cjs +106 -50
  2. package/dist/mod.js +107 -51
  3. package/package.json +4 -4
package/dist/mod.cjs CHANGED
@@ -248,9 +248,56 @@ function expandUriTemplate(template, values) {
248
248
  function buildPortableUri(authority, path) {
249
249
  if (typeof authority !== "string" || !/^did:[a-z0-9]+:[^/?#]+$/i.test(authority)) throw new TypeError("The authority of a portable ID must be a DID without a path, query, or fragment.");
250
250
  const did = (0, _fedify_vocab_runtime.getFe34Origin)(authority);
251
- if (did.startsWith("did:key:") && !/^z[1-9A-HJ-NP-Za-km-z]+$/.test(did.slice(8))) throw new TypeError("The did:key authority of a portable ID must be encoded in base58-btc, i.e., start with z.");
251
+ if (!isBase58BtcDidKey(did)) throw new TypeError("The did:key authority of a portable ID must be encoded in base58-btc, i.e., start with z.");
252
252
  return (0, _fedify_vocab_runtime.parseIri)(`ap+ef61://${did}${path}`);
253
253
  }
254
+ /**
255
+ * Checks that a normalized DID, if it is a `did:key` DID, is encoded in
256
+ * base58-btc, as `@fedify/fedify` requires of portable IDs.
257
+ */
258
+ function isBase58BtcDidKey(did) {
259
+ return !did.startsWith("did:key:") || /^z[1-9A-HJ-NP-Za-km-z]+$/.test(did.slice(8));
260
+ }
261
+ /**
262
+ * Recognizes an FEP-ef61 portable ID, i.e., an `ap:` or `ap+ef61:` URI, or
263
+ * a compatible identifier on any gateway, validating it the same way as
264
+ * `Context.parseUri()` of `@fedify/fedify` does.
265
+ * @returns The DID and the path of the portable ID, `null` if it is
266
+ * malformed, or `undefined` if the URI is not a portable ID.
267
+ */
268
+ function parseMockPortableId(uri) {
269
+ const portable = uri.protocol === "ap:" || uri.protocol === "ap+ef61:";
270
+ if (!portable && !((uri.protocol === "http:" || uri.protocol === "https:") && /^\/\.well-known\/apgateway\/did(?::|%3A)/i.test(uri.pathname))) return;
271
+ if (uri.username !== "" || uri.password !== "") return null;
272
+ try {
273
+ const id = portable ? (0, _fedify_vocab_runtime.parseIri)(uri) : (0, _fedify_vocab_runtime.fromCompatibleEf61Id)(uri.origin + uri.pathname);
274
+ if (id == null) return null;
275
+ (0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(id));
276
+ const authority = (0, _fedify_vocab_runtime.getFe34Origin)(id);
277
+ if (!isBase58BtcDidKey(authority)) return null;
278
+ return {
279
+ authority,
280
+ path: id.pathname
281
+ };
282
+ } catch (error) {
283
+ if (error instanceof TypeError) return null;
284
+ throw error;
285
+ }
286
+ }
287
+ /**
288
+ * Parses a path in the way {@link MockContext.parseUri} does, i.e., only
289
+ * `/users/{identifier}` and its subpaths are recognized, as actors.
290
+ */
291
+ function parseMockPath(path) {
292
+ if (path.startsWith("/users/")) {
293
+ const parts = path.split("/");
294
+ if (parts.length >= 3) return {
295
+ type: "actor",
296
+ identifier: parts[2]
297
+ };
298
+ }
299
+ return null;
300
+ }
254
301
  function validateOutboxListenerPath(path, dispatcherPath) {
255
302
  if (!path.startsWith("/")) throw new TypeError("Path must start with a slash.");
256
303
  if (dispatcherPath != null && dispatcherPath !== path) throw new TypeError("Outbox listener path and outbox dispatcher path must match.");
@@ -368,92 +415,100 @@ var MockFederation = class {
368
415
  const setters = {
369
416
  setKeyPairsDispatcher: (keyPairsDispatcher) => {
370
417
  this.actorKeyPairsDispatcher = keyPairsDispatcher;
371
- return this;
418
+ return setters;
372
419
  },
373
- mapHandle: () => this,
374
- mapAlias: () => this,
420
+ mapHandle: () => setters,
421
+ mapAlias: () => setters,
375
422
  mapPortableActorId: () => setters,
376
- authorize: () => this
423
+ authorize: () => setters
377
424
  };
378
425
  return setters;
379
426
  }
380
427
  setObjectDispatcher(cls, path, dispatcher) {
381
428
  this.objectDispatchers.set(path, dispatcher);
382
429
  this.objectPaths.set(cls.typeId.href, path);
383
- return { authorize: () => this };
430
+ const setters = { authorize: () => setters };
431
+ return setters;
384
432
  }
385
433
  setInboxDispatcher(_path, dispatcher) {
386
434
  this.inboxDispatcher = dispatcher;
387
- return {
388
- setCounter: () => this,
389
- setFirstCursor: () => this,
390
- setLastCursor: () => this,
391
- authorize: () => this
435
+ const setters = {
436
+ setCounter: () => setters,
437
+ setFirstCursor: () => setters,
438
+ setLastCursor: () => setters,
439
+ authorize: () => setters
392
440
  };
441
+ return setters;
393
442
  }
394
443
  setOutboxDispatcher(path, dispatcher) {
395
444
  validateOutboxListenerPath(path, this.outboxListenersInitialized ? this.outboxPath : void 0);
396
445
  this.outboxDispatcher = dispatcher;
397
446
  this.outboxPath = path;
398
- return {
399
- setCounter: () => this,
400
- setFirstCursor: () => this,
401
- setLastCursor: () => this,
447
+ const setters = {
448
+ setCounter: () => setters,
449
+ setFirstCursor: () => setters,
450
+ setLastCursor: () => setters,
402
451
  authorize: (predicate) => {
403
452
  this.outboxDispatcherAuthorizePredicate = predicate;
404
- return this;
453
+ return setters;
405
454
  }
406
455
  };
456
+ return setters;
407
457
  }
408
458
  setFollowingDispatcher(path, dispatcher) {
409
459
  this.followingDispatcher = dispatcher;
410
460
  this.followingPath = path;
411
- return {
412
- setCounter: () => this,
413
- setFirstCursor: () => this,
414
- setLastCursor: () => this,
415
- authorize: () => this
461
+ const setters = {
462
+ setCounter: () => setters,
463
+ setFirstCursor: () => setters,
464
+ setLastCursor: () => setters,
465
+ authorize: () => setters
416
466
  };
467
+ return setters;
417
468
  }
418
469
  setFollowersDispatcher(path, dispatcher) {
419
470
  this.followersDispatcher = dispatcher;
420
471
  this.followersPath = path;
421
- return {
422
- setCounter: () => this,
423
- setFirstCursor: () => this,
424
- setLastCursor: () => this,
425
- authorize: () => this
472
+ const setters = {
473
+ setCounter: () => setters,
474
+ setFirstCursor: () => setters,
475
+ setLastCursor: () => setters,
476
+ authorize: () => setters
426
477
  };
478
+ return setters;
427
479
  }
428
480
  setLikedDispatcher(path, dispatcher) {
429
481
  this.likedDispatcher = dispatcher;
430
482
  this.likedPath = path;
431
- return {
432
- setCounter: () => this,
433
- setFirstCursor: () => this,
434
- setLastCursor: () => this,
435
- authorize: () => this
483
+ const setters = {
484
+ setCounter: () => setters,
485
+ setFirstCursor: () => setters,
486
+ setLastCursor: () => setters,
487
+ authorize: () => setters
436
488
  };
489
+ return setters;
437
490
  }
438
491
  setFeaturedDispatcher(path, dispatcher) {
439
492
  this.featuredDispatcher = dispatcher;
440
493
  this.featuredPath = path;
441
- return {
442
- setCounter: () => this,
443
- setFirstCursor: () => this,
444
- setLastCursor: () => this,
445
- authorize: () => this
494
+ const setters = {
495
+ setCounter: () => setters,
496
+ setFirstCursor: () => setters,
497
+ setLastCursor: () => setters,
498
+ authorize: () => setters
446
499
  };
500
+ return setters;
447
501
  }
448
502
  setFeaturedTagsDispatcher(path, dispatcher) {
449
503
  this.featuredTagsDispatcher = dispatcher;
450
504
  this.featuredTagsPath = path;
451
- return {
452
- setCounter: () => this,
453
- setFirstCursor: () => this,
454
- setLastCursor: () => this,
455
- authorize: () => this
505
+ const setters = {
506
+ setCounter: () => setters,
507
+ setFirstCursor: () => setters,
508
+ setLastCursor: () => setters,
509
+ authorize: () => setters
456
510
  };
511
+ return setters;
457
512
  }
458
513
  setInboxListeners(inboxPath, sharedInboxPath) {
459
514
  this.inboxPath = inboxPath;
@@ -970,15 +1025,16 @@ var MockContext = class MockContext {
970
1025
  const path = globalThis.Object.entries(values).map(([key, value]) => `${key}/${value}`).join("/");
971
1026
  return new URL(`/collections/${String(_name)}/${path}`, this.origin);
972
1027
  }
973
- parseUri(uri) {
974
- if (uri.pathname.startsWith("/users/")) {
975
- const parts = uri.pathname.split("/");
976
- if (parts.length >= 3) return {
977
- type: "actor",
978
- identifier: parts[2]
979
- };
980
- }
981
- return null;
1028
+ parseUri(uri, options = {}) {
1029
+ if (uri == null) return null;
1030
+ const portable = parseMockPortableId(uri);
1031
+ if (portable === void 0) return parseMockPath(uri.pathname);
1032
+ if (portable === null || !options.portable) return null;
1033
+ const result = parseMockPath(portable.path);
1034
+ return result == null ? null : {
1035
+ ...result,
1036
+ authority: portable.authority
1037
+ };
982
1038
  }
983
1039
  async getActorKeyPairs(identifier) {
984
1040
  if (this.federation instanceof MockFederation && this.federation.actorKeyPairsDispatcher) {
package/dist/mod.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { hasProofLike, hasSignatureLike } from "@fedify/fedify/sig";
3
3
  import { Activity, CryptographicKey, Multikey, Tombstone, lookupObject, traverseCollection } from "@fedify/vocab";
4
- import { getFe34Origin, parseIri } from "@fedify/vocab-runtime";
4
+ import { canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getFe34Origin, parseIri } from "@fedify/vocab-runtime";
5
5
  import { RouterError } from "@fedify/fedify/federation";
6
6
  import { delay } from "es-toolkit";
7
7
  import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
@@ -247,9 +247,56 @@ function expandUriTemplate(template, values) {
247
247
  function buildPortableUri(authority, path) {
248
248
  if (typeof authority !== "string" || !/^did:[a-z0-9]+:[^/?#]+$/i.test(authority)) throw new TypeError("The authority of a portable ID must be a DID without a path, query, or fragment.");
249
249
  const did = getFe34Origin(authority);
250
- if (did.startsWith("did:key:") && !/^z[1-9A-HJ-NP-Za-km-z]+$/.test(did.slice(8))) throw new TypeError("The did:key authority of a portable ID must be encoded in base58-btc, i.e., start with z.");
250
+ if (!isBase58BtcDidKey(did)) throw new TypeError("The did:key authority of a portable ID must be encoded in base58-btc, i.e., start with z.");
251
251
  return parseIri(`ap+ef61://${did}${path}`);
252
252
  }
253
+ /**
254
+ * Checks that a normalized DID, if it is a `did:key` DID, is encoded in
255
+ * base58-btc, as `@fedify/fedify` requires of portable IDs.
256
+ */
257
+ function isBase58BtcDidKey(did) {
258
+ return !did.startsWith("did:key:") || /^z[1-9A-HJ-NP-Za-km-z]+$/.test(did.slice(8));
259
+ }
260
+ /**
261
+ * Recognizes an FEP-ef61 portable ID, i.e., an `ap:` or `ap+ef61:` URI, or
262
+ * a compatible identifier on any gateway, validating it the same way as
263
+ * `Context.parseUri()` of `@fedify/fedify` does.
264
+ * @returns The DID and the path of the portable ID, `null` if it is
265
+ * malformed, or `undefined` if the URI is not a portable ID.
266
+ */
267
+ function parseMockPortableId(uri) {
268
+ const portable = uri.protocol === "ap:" || uri.protocol === "ap+ef61:";
269
+ if (!portable && !((uri.protocol === "http:" || uri.protocol === "https:") && /^\/\.well-known\/apgateway\/did(?::|%3A)/i.test(uri.pathname))) return;
270
+ if (uri.username !== "" || uri.password !== "") return null;
271
+ try {
272
+ const id = portable ? parseIri(uri) : fromCompatibleEf61Id(uri.origin + uri.pathname);
273
+ if (id == null) return null;
274
+ canonicalizePortableUri(formatIri(id));
275
+ const authority = getFe34Origin(id);
276
+ if (!isBase58BtcDidKey(authority)) return null;
277
+ return {
278
+ authority,
279
+ path: id.pathname
280
+ };
281
+ } catch (error) {
282
+ if (error instanceof TypeError) return null;
283
+ throw error;
284
+ }
285
+ }
286
+ /**
287
+ * Parses a path in the way {@link MockContext.parseUri} does, i.e., only
288
+ * `/users/{identifier}` and its subpaths are recognized, as actors.
289
+ */
290
+ function parseMockPath(path) {
291
+ if (path.startsWith("/users/")) {
292
+ const parts = path.split("/");
293
+ if (parts.length >= 3) return {
294
+ type: "actor",
295
+ identifier: parts[2]
296
+ };
297
+ }
298
+ return null;
299
+ }
253
300
  function validateOutboxListenerPath(path, dispatcherPath) {
254
301
  if (!path.startsWith("/")) throw new TypeError("Path must start with a slash.");
255
302
  if (dispatcherPath != null && dispatcherPath !== path) throw new TypeError("Outbox listener path and outbox dispatcher path must match.");
@@ -367,92 +414,100 @@ var MockFederation = class {
367
414
  const setters = {
368
415
  setKeyPairsDispatcher: (keyPairsDispatcher) => {
369
416
  this.actorKeyPairsDispatcher = keyPairsDispatcher;
370
- return this;
417
+ return setters;
371
418
  },
372
- mapHandle: () => this,
373
- mapAlias: () => this,
419
+ mapHandle: () => setters,
420
+ mapAlias: () => setters,
374
421
  mapPortableActorId: () => setters,
375
- authorize: () => this
422
+ authorize: () => setters
376
423
  };
377
424
  return setters;
378
425
  }
379
426
  setObjectDispatcher(cls, path, dispatcher) {
380
427
  this.objectDispatchers.set(path, dispatcher);
381
428
  this.objectPaths.set(cls.typeId.href, path);
382
- return { authorize: () => this };
429
+ const setters = { authorize: () => setters };
430
+ return setters;
383
431
  }
384
432
  setInboxDispatcher(_path, dispatcher) {
385
433
  this.inboxDispatcher = dispatcher;
386
- return {
387
- setCounter: () => this,
388
- setFirstCursor: () => this,
389
- setLastCursor: () => this,
390
- authorize: () => this
434
+ const setters = {
435
+ setCounter: () => setters,
436
+ setFirstCursor: () => setters,
437
+ setLastCursor: () => setters,
438
+ authorize: () => setters
391
439
  };
440
+ return setters;
392
441
  }
393
442
  setOutboxDispatcher(path, dispatcher) {
394
443
  validateOutboxListenerPath(path, this.outboxListenersInitialized ? this.outboxPath : void 0);
395
444
  this.outboxDispatcher = dispatcher;
396
445
  this.outboxPath = path;
397
- return {
398
- setCounter: () => this,
399
- setFirstCursor: () => this,
400
- setLastCursor: () => this,
446
+ const setters = {
447
+ setCounter: () => setters,
448
+ setFirstCursor: () => setters,
449
+ setLastCursor: () => setters,
401
450
  authorize: (predicate) => {
402
451
  this.outboxDispatcherAuthorizePredicate = predicate;
403
- return this;
452
+ return setters;
404
453
  }
405
454
  };
455
+ return setters;
406
456
  }
407
457
  setFollowingDispatcher(path, dispatcher) {
408
458
  this.followingDispatcher = dispatcher;
409
459
  this.followingPath = path;
410
- return {
411
- setCounter: () => this,
412
- setFirstCursor: () => this,
413
- setLastCursor: () => this,
414
- authorize: () => this
460
+ const setters = {
461
+ setCounter: () => setters,
462
+ setFirstCursor: () => setters,
463
+ setLastCursor: () => setters,
464
+ authorize: () => setters
415
465
  };
466
+ return setters;
416
467
  }
417
468
  setFollowersDispatcher(path, dispatcher) {
418
469
  this.followersDispatcher = dispatcher;
419
470
  this.followersPath = path;
420
- return {
421
- setCounter: () => this,
422
- setFirstCursor: () => this,
423
- setLastCursor: () => this,
424
- authorize: () => this
471
+ const setters = {
472
+ setCounter: () => setters,
473
+ setFirstCursor: () => setters,
474
+ setLastCursor: () => setters,
475
+ authorize: () => setters
425
476
  };
477
+ return setters;
426
478
  }
427
479
  setLikedDispatcher(path, dispatcher) {
428
480
  this.likedDispatcher = dispatcher;
429
481
  this.likedPath = path;
430
- return {
431
- setCounter: () => this,
432
- setFirstCursor: () => this,
433
- setLastCursor: () => this,
434
- authorize: () => this
482
+ const setters = {
483
+ setCounter: () => setters,
484
+ setFirstCursor: () => setters,
485
+ setLastCursor: () => setters,
486
+ authorize: () => setters
435
487
  };
488
+ return setters;
436
489
  }
437
490
  setFeaturedDispatcher(path, dispatcher) {
438
491
  this.featuredDispatcher = dispatcher;
439
492
  this.featuredPath = path;
440
- return {
441
- setCounter: () => this,
442
- setFirstCursor: () => this,
443
- setLastCursor: () => this,
444
- authorize: () => this
493
+ const setters = {
494
+ setCounter: () => setters,
495
+ setFirstCursor: () => setters,
496
+ setLastCursor: () => setters,
497
+ authorize: () => setters
445
498
  };
499
+ return setters;
446
500
  }
447
501
  setFeaturedTagsDispatcher(path, dispatcher) {
448
502
  this.featuredTagsDispatcher = dispatcher;
449
503
  this.featuredTagsPath = path;
450
- return {
451
- setCounter: () => this,
452
- setFirstCursor: () => this,
453
- setLastCursor: () => this,
454
- authorize: () => this
504
+ const setters = {
505
+ setCounter: () => setters,
506
+ setFirstCursor: () => setters,
507
+ setLastCursor: () => setters,
508
+ authorize: () => setters
455
509
  };
510
+ return setters;
456
511
  }
457
512
  setInboxListeners(inboxPath, sharedInboxPath) {
458
513
  this.inboxPath = inboxPath;
@@ -969,15 +1024,16 @@ var MockContext = class MockContext {
969
1024
  const path = globalThis.Object.entries(values).map(([key, value]) => `${key}/${value}`).join("/");
970
1025
  return new URL(`/collections/${String(_name)}/${path}`, this.origin);
971
1026
  }
972
- parseUri(uri) {
973
- if (uri.pathname.startsWith("/users/")) {
974
- const parts = uri.pathname.split("/");
975
- if (parts.length >= 3) return {
976
- type: "actor",
977
- identifier: parts[2]
978
- };
979
- }
980
- return null;
1027
+ parseUri(uri, options = {}) {
1028
+ if (uri == null) return null;
1029
+ const portable = parseMockPortableId(uri);
1030
+ if (portable === void 0) return parseMockPath(uri.pathname);
1031
+ if (portable === null || !options.portable) return null;
1032
+ const result = parseMockPath(portable.path);
1033
+ return result == null ? null : {
1034
+ ...result,
1035
+ authority: portable.authority
1036
+ };
981
1037
  }
982
1038
  async getActorKeyPairs(identifier) {
983
1039
  if (this.federation instanceof MockFederation && this.federation.actorKeyPairsDispatcher) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/testing",
3
- "version": "2.4.0-dev.2155+2ccc5085",
3
+ "version": "2.4.0-dev.2159+bb072eda",
4
4
  "description": "Testing utilities for Fedify applications",
5
5
  "keywords": [
6
6
  "fedify",
@@ -47,13 +47,13 @@
47
47
  "package.json"
48
48
  ],
49
49
  "peerDependencies": {
50
- "@fedify/fedify": "^2.4.0-dev.2155+2ccc5085"
50
+ "@fedify/fedify": "^2.4.0-dev.2159+bb072eda"
51
51
  },
52
52
  "dependencies": {
53
53
  "es-toolkit": "1.46.1",
54
54
  "temporal-polyfill": "^1.0.1",
55
- "@fedify/vocab": "2.4.0-dev.2155+2ccc5085",
56
- "@fedify/vocab-runtime": "^2.4.0-dev.2155+2ccc5085"
55
+ "@fedify/vocab": "2.4.0-dev.2159+bb072eda",
56
+ "@fedify/vocab-runtime": "^2.4.0-dev.2159+bb072eda"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@standard-schema/spec": "^1.1.0",