@fedify/testing 2.4.0-dev.2240 → 2.4.0-dev.2247

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/dist/mod.cjs CHANGED
@@ -629,7 +629,7 @@ var MockFederation = class {
629
629
  async processQueuedTask(contextData, _message) {
630
630
  this.contextData = contextData;
631
631
  }
632
- createContext(baseUrlOrRequest, contextData) {
632
+ createContext(baseUrlOrRequest, contextData, options = {}) {
633
633
  const mockFederation = this;
634
634
  const request = baseUrlOrRequest instanceof Request ? baseUrlOrRequest : null;
635
635
  return new MockContext({
@@ -638,10 +638,63 @@ var MockFederation = class {
638
638
  data: contextData,
639
639
  federation: mockFederation,
640
640
  meterProvider: this.options.meterProvider,
641
- tracerProvider: this.options.tracerProvider
641
+ tracerProvider: this.options.tracerProvider,
642
+ documentLoader: this.options.documentLoader,
643
+ contextLoader: this.options.contextLoader,
644
+ verifyPortableObject: this.options.verifyPortableObject,
645
+ portableRequest: options.portableRequest
642
646
  });
643
647
  }
644
648
  async fetch(request, options) {
649
+ if (this.hashlinkMediaDispatcher != null) {
650
+ const url = new URL(request.url);
651
+ if ((url.protocol === "http:" || url.protocol === "https:") && url.pathname.startsWith("/.well-known/apgateway/")) {
652
+ const encoded = url.pathname.slice(23);
653
+ if (/^hl(?::|%3A)/i.test(encoded)) {
654
+ if (request.method !== "GET" && request.method !== "HEAD") return new Response("Method not allowed.", {
655
+ status: 405,
656
+ headers: {
657
+ Allow: "GET, HEAD",
658
+ "Content-Type": "text/plain; charset=utf-8"
659
+ }
660
+ });
661
+ let media;
662
+ try {
663
+ let hashlink;
664
+ try {
665
+ hashlink = decodeURIComponent(encoded);
666
+ } catch (error) {
667
+ throw new TypeError("Invalid percent-encoding in the hashlink.", { cause: error });
668
+ }
669
+ const { digestMultibase } = (0, _fedify_vocab_runtime.parseHashlink)(hashlink);
670
+ const { digest } = (0, _fedify_vocab_runtime.parseDigestMultibase)(digestMultibase);
671
+ media = globalThis.Object.freeze({
672
+ hashlink: `hl:${digestMultibase}`,
673
+ digestMultibase,
674
+ algorithm: "sha2-256",
675
+ digest,
676
+ multihash: (0, _fedify_vocab_runtime.decodeMultibase)(digestMultibase)
677
+ });
678
+ } catch (error) {
679
+ if (!(error instanceof TypeError)) throw error;
680
+ return new Response(request.method === "HEAD" ? null : "Malformed hashlink.", {
681
+ status: 400,
682
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
683
+ });
684
+ }
685
+ const context = this.createContext(request, options.contextData);
686
+ const response = await this.hashlinkMediaDispatcher(context, media);
687
+ if (response == null) return options.onNotFound == null ? new Response("Not Found", { status: 404 }) : await options.onNotFound(request);
688
+ if (request.method !== "HEAD" || response.body == null) return response;
689
+ response.body.cancel().catch(() => {});
690
+ return new Response(null, {
691
+ status: response.status,
692
+ statusText: response.statusText,
693
+ headers: response.headers
694
+ });
695
+ }
696
+ }
697
+ }
645
698
  if (options.onNotFound) return options.onNotFound(request);
646
699
  return new Response("Not Found", { status: 404 });
647
700
  }
@@ -864,6 +917,10 @@ var MockContext = class MockContext {
864
917
  tracerProvider;
865
918
  request;
866
919
  url;
920
+ portableRequest;
921
+ verifyPortableObject;
922
+ hasDocumentLoader;
923
+ hasContextLoader;
867
924
  sentActivities = [];
868
925
  constructor(options) {
869
926
  const url = options.url ?? new URL("https://example.com");
@@ -873,6 +930,10 @@ var MockContext = class MockContext {
873
930
  this.hostname = url.hostname;
874
931
  this.url = url;
875
932
  this.request = options.request ?? new Request(url);
933
+ this.portableRequest = options.portableRequest;
934
+ this.verifyPortableObject = options.verifyPortableObject;
935
+ this.hasDocumentLoader = options.hasDocumentLoader ?? options.documentLoader != null;
936
+ this.hasContextLoader = options.hasContextLoader ?? options.contextLoader != null;
876
937
  this.data = options.data;
877
938
  this.federation = options.federation;
878
939
  this.documentLoader = options.documentLoader ?? (async (url) => ({
@@ -937,10 +998,15 @@ var MockContext = class MockContext {
937
998
  clone(data) {
938
999
  return new MockContext({
939
1000
  url: this.url,
1001
+ request: this.request,
940
1002
  data,
941
1003
  federation: this.federation,
942
1004
  documentLoader: this.documentLoader,
943
1005
  contextLoader: this.contextLoader,
1006
+ verifyPortableObject: this.verifyPortableObject,
1007
+ portableRequest: this.portableRequest,
1008
+ hasDocumentLoader: this.hasDocumentLoader,
1009
+ hasContextLoader: this.hasContextLoader,
944
1010
  meterProvider: this.meterProvider,
945
1011
  tracerProvider: this.tracerProvider
946
1012
  });
@@ -1106,8 +1172,21 @@ var MockContext = class MockContext {
1106
1172
  if ("keyId" in params) return this.documentLoader;
1107
1173
  return Promise.resolve(this.documentLoader);
1108
1174
  }
1109
- lookupObject(_uri, _options) {
1110
- return Promise.resolve(null);
1175
+ lookupObject(uri, options = {}) {
1176
+ if (!this.hasDocumentLoader && options.documentLoader == null || (options.verifyPortableObject ?? this.verifyPortableObject) == null) return Promise.resolve(null);
1177
+ const value = typeof uri === "string" ? uri : (0, _fedify_vocab_runtime.formatIri)(uri);
1178
+ if (!/^ap(?:\+ef61)?:\/\//i.test(value)) try {
1179
+ if ((0, _fedify_vocab_runtime.fromCompatibleEf61Id)(value) == null) return Promise.resolve(null);
1180
+ } catch (error) {
1181
+ if (error instanceof TypeError) return Promise.resolve(null);
1182
+ throw error;
1183
+ }
1184
+ return (0, _fedify_vocab.lookupObject)(uri, {
1185
+ ...options,
1186
+ documentLoader: options.documentLoader ?? this.documentLoader,
1187
+ contextLoader: options.contextLoader ?? (this.hasContextLoader ? this.contextLoader : options.documentLoader ?? this.contextLoader),
1188
+ verifyPortableObject: options.verifyPortableObject ?? this.verifyPortableObject
1189
+ });
1111
1190
  }
1112
1191
  traverseCollection(_collection, _options) {
1113
1192
  return { async *[Symbol.asyncIterator]() {} };
package/dist/mod.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { Context, Federation, InboxContext, OutboxContext, RequestContext } from "@fedify/fedify/federation";
2
+ import { Context, Federation, InboxContext, OutboxContext, PortableRequest, RequestContext } from "@fedify/fedify/federation";
3
3
  import { Activity } from "@fedify/vocab";
4
+ import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
4
5
  import { KvStore, MessageQueue } from "@fedify/fedify";
5
6
 
6
7
  //#region src/context.d.ts
@@ -86,7 +87,7 @@ interface SentActivity {
86
87
  * Extends the standard Context interface with additional testing utilities.
87
88
  * @since 1.9.1
88
89
  */
89
- interface TestContext<TContextData> extends Omit<Context<TContextData>, "clone">, Pick<RequestContext<TContextData>, "request" | "url" | "getActor" | "getObject" | "getSignedKey" | "getSignedKeyOwner" | "isSignedByAudience" | "sendActivity" | "routeActivity"> {
90
+ interface TestContext<TContextData> extends Omit<Context<TContextData>, "clone">, Pick<RequestContext<TContextData>, "request" | "url" | "portableRequest" | "getActor" | "getObject" | "getSignedKey" | "getSignedKeyOwner" | "isSignedByAudience" | "sendActivity" | "routeActivity"> {
90
91
  clone(data: TContextData): TestContext<TContextData>;
91
92
  getSentActivities(): Array<{
92
93
  sender: any;
@@ -108,7 +109,13 @@ interface TestFederation<TContextData> extends Omit<Federation<TContextData>, "c
108
109
  receiveActivity(activity: Activity): Promise<void>;
109
110
  postOutboxActivity(identifier: string, activity: Activity): Promise<void>;
110
111
  reset(): void;
111
- createContext(baseUrlOrRequest: URL | Request, contextData: TContextData): TestContext<TContextData>;
112
+ createContext(baseUrl: URL, contextData: TContextData): TestContext<TContextData>;
113
+ createContext(request: Request, contextData: TContextData, options?: {
114
+ portableRequest?: PortableRequest;
115
+ }): TestContext<TContextData>;
116
+ createContext(baseUrlOrRequest: URL | Request, contextData: TContextData, options?: {
117
+ portableRequest?: PortableRequest;
118
+ }): TestContext<TContextData>;
112
119
  }
113
120
  /**
114
121
  * Creates a mock Federation instance for testing purposes.
@@ -154,7 +161,15 @@ declare function createFederation<TContextData>(options?: {
154
161
  * @since 2.3.0
155
162
  */
156
163
  meterProvider?: any;
157
- tracerProvider?: any;
164
+ tracerProvider?: any; /** The document loader used by mock context lookups. @since 2.4.0 */
165
+ documentLoader?: DocumentLoader; /** The JSON-LD context loader used by mock context lookups. @since 2.4.0 */
166
+ contextLoader?: DocumentLoader;
167
+ /**
168
+ * The portable object verifier used by mock context lookups when a
169
+ * document loader is provided. Mock collection traversal remains empty.
170
+ * @since 2.4.0
171
+ */
172
+ verifyPortableObject?: PortableObjectVerifier;
158
173
  }): TestFederation<TContextData>;
159
174
  //#endregion
160
175
  //#region src/kv-tester.d.ts
package/dist/mod.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { Activity } from "@fedify/vocab";
3
- import { Context, Federation, InboxContext, OutboxContext, RequestContext } from "@fedify/fedify/federation";
3
+ import { DocumentLoader, PortableObjectVerifier } from "@fedify/vocab-runtime";
4
+ import { Context, Federation, InboxContext, OutboxContext, PortableRequest, RequestContext } from "@fedify/fedify/federation";
4
5
  import { KvStore, MessageQueue } from "@fedify/fedify";
5
6
 
6
7
  //#region src/context.d.ts
@@ -86,7 +87,7 @@ interface SentActivity {
86
87
  * Extends the standard Context interface with additional testing utilities.
87
88
  * @since 1.9.1
88
89
  */
89
- interface TestContext<TContextData> extends Omit<Context<TContextData>, "clone">, Pick<RequestContext<TContextData>, "request" | "url" | "getActor" | "getObject" | "getSignedKey" | "getSignedKeyOwner" | "isSignedByAudience" | "sendActivity" | "routeActivity"> {
90
+ interface TestContext<TContextData> extends Omit<Context<TContextData>, "clone">, Pick<RequestContext<TContextData>, "request" | "url" | "portableRequest" | "getActor" | "getObject" | "getSignedKey" | "getSignedKeyOwner" | "isSignedByAudience" | "sendActivity" | "routeActivity"> {
90
91
  clone(data: TContextData): TestContext<TContextData>;
91
92
  getSentActivities(): Array<{
92
93
  sender: any;
@@ -108,7 +109,13 @@ interface TestFederation<TContextData> extends Omit<Federation<TContextData>, "c
108
109
  receiveActivity(activity: Activity): Promise<void>;
109
110
  postOutboxActivity(identifier: string, activity: Activity): Promise<void>;
110
111
  reset(): void;
111
- createContext(baseUrlOrRequest: URL | Request, contextData: TContextData): TestContext<TContextData>;
112
+ createContext(baseUrl: URL, contextData: TContextData): TestContext<TContextData>;
113
+ createContext(request: Request, contextData: TContextData, options?: {
114
+ portableRequest?: PortableRequest;
115
+ }): TestContext<TContextData>;
116
+ createContext(baseUrlOrRequest: URL | Request, contextData: TContextData, options?: {
117
+ portableRequest?: PortableRequest;
118
+ }): TestContext<TContextData>;
112
119
  }
113
120
  /**
114
121
  * Creates a mock Federation instance for testing purposes.
@@ -154,7 +161,15 @@ declare function createFederation<TContextData>(options?: {
154
161
  * @since 2.3.0
155
162
  */
156
163
  meterProvider?: any;
157
- tracerProvider?: any;
164
+ tracerProvider?: any; /** The document loader used by mock context lookups. @since 2.4.0 */
165
+ documentLoader?: DocumentLoader; /** The JSON-LD context loader used by mock context lookups. @since 2.4.0 */
166
+ contextLoader?: DocumentLoader;
167
+ /**
168
+ * The portable object verifier used by mock context lookups when a
169
+ * document loader is provided. Mock collection traversal remains empty.
170
+ * @since 2.4.0
171
+ */
172
+ verifyPortableObject?: PortableObjectVerifier;
158
173
  }): TestFederation<TContextData>;
159
174
  //#endregion
160
175
  //#region src/kv-tester.d.ts
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, PUBLIC_COLLECTION, Tombstone, lookupObject, traverseCollection } from "@fedify/vocab";
4
- import { arePortableUrisEqual, canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getFe34Origin, parseIri } from "@fedify/vocab-runtime";
4
+ import { arePortableUrisEqual, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getFe34Origin, parseDigestMultibase, parseHashlink, 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";
@@ -628,7 +628,7 @@ var MockFederation = class {
628
628
  async processQueuedTask(contextData, _message) {
629
629
  this.contextData = contextData;
630
630
  }
631
- createContext(baseUrlOrRequest, contextData) {
631
+ createContext(baseUrlOrRequest, contextData, options = {}) {
632
632
  const mockFederation = this;
633
633
  const request = baseUrlOrRequest instanceof Request ? baseUrlOrRequest : null;
634
634
  return new MockContext({
@@ -637,10 +637,63 @@ var MockFederation = class {
637
637
  data: contextData,
638
638
  federation: mockFederation,
639
639
  meterProvider: this.options.meterProvider,
640
- tracerProvider: this.options.tracerProvider
640
+ tracerProvider: this.options.tracerProvider,
641
+ documentLoader: this.options.documentLoader,
642
+ contextLoader: this.options.contextLoader,
643
+ verifyPortableObject: this.options.verifyPortableObject,
644
+ portableRequest: options.portableRequest
641
645
  });
642
646
  }
643
647
  async fetch(request, options) {
648
+ if (this.hashlinkMediaDispatcher != null) {
649
+ const url = new URL(request.url);
650
+ if ((url.protocol === "http:" || url.protocol === "https:") && url.pathname.startsWith("/.well-known/apgateway/")) {
651
+ const encoded = url.pathname.slice(23);
652
+ if (/^hl(?::|%3A)/i.test(encoded)) {
653
+ if (request.method !== "GET" && request.method !== "HEAD") return new Response("Method not allowed.", {
654
+ status: 405,
655
+ headers: {
656
+ Allow: "GET, HEAD",
657
+ "Content-Type": "text/plain; charset=utf-8"
658
+ }
659
+ });
660
+ let media;
661
+ try {
662
+ let hashlink;
663
+ try {
664
+ hashlink = decodeURIComponent(encoded);
665
+ } catch (error) {
666
+ throw new TypeError("Invalid percent-encoding in the hashlink.", { cause: error });
667
+ }
668
+ const { digestMultibase } = parseHashlink(hashlink);
669
+ const { digest } = parseDigestMultibase(digestMultibase);
670
+ media = globalThis.Object.freeze({
671
+ hashlink: `hl:${digestMultibase}`,
672
+ digestMultibase,
673
+ algorithm: "sha2-256",
674
+ digest,
675
+ multihash: decodeMultibase(digestMultibase)
676
+ });
677
+ } catch (error) {
678
+ if (!(error instanceof TypeError)) throw error;
679
+ return new Response(request.method === "HEAD" ? null : "Malformed hashlink.", {
680
+ status: 400,
681
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
682
+ });
683
+ }
684
+ const context = this.createContext(request, options.contextData);
685
+ const response = await this.hashlinkMediaDispatcher(context, media);
686
+ if (response == null) return options.onNotFound == null ? new Response("Not Found", { status: 404 }) : await options.onNotFound(request);
687
+ if (request.method !== "HEAD" || response.body == null) return response;
688
+ response.body.cancel().catch(() => {});
689
+ return new Response(null, {
690
+ status: response.status,
691
+ statusText: response.statusText,
692
+ headers: response.headers
693
+ });
694
+ }
695
+ }
696
+ }
644
697
  if (options.onNotFound) return options.onNotFound(request);
645
698
  return new Response("Not Found", { status: 404 });
646
699
  }
@@ -863,6 +916,10 @@ var MockContext = class MockContext {
863
916
  tracerProvider;
864
917
  request;
865
918
  url;
919
+ portableRequest;
920
+ verifyPortableObject;
921
+ hasDocumentLoader;
922
+ hasContextLoader;
866
923
  sentActivities = [];
867
924
  constructor(options) {
868
925
  const url = options.url ?? new URL("https://example.com");
@@ -872,6 +929,10 @@ var MockContext = class MockContext {
872
929
  this.hostname = url.hostname;
873
930
  this.url = url;
874
931
  this.request = options.request ?? new Request(url);
932
+ this.portableRequest = options.portableRequest;
933
+ this.verifyPortableObject = options.verifyPortableObject;
934
+ this.hasDocumentLoader = options.hasDocumentLoader ?? options.documentLoader != null;
935
+ this.hasContextLoader = options.hasContextLoader ?? options.contextLoader != null;
875
936
  this.data = options.data;
876
937
  this.federation = options.federation;
877
938
  this.documentLoader = options.documentLoader ?? (async (url) => ({
@@ -936,10 +997,15 @@ var MockContext = class MockContext {
936
997
  clone(data) {
937
998
  return new MockContext({
938
999
  url: this.url,
1000
+ request: this.request,
939
1001
  data,
940
1002
  federation: this.federation,
941
1003
  documentLoader: this.documentLoader,
942
1004
  contextLoader: this.contextLoader,
1005
+ verifyPortableObject: this.verifyPortableObject,
1006
+ portableRequest: this.portableRequest,
1007
+ hasDocumentLoader: this.hasDocumentLoader,
1008
+ hasContextLoader: this.hasContextLoader,
943
1009
  meterProvider: this.meterProvider,
944
1010
  tracerProvider: this.tracerProvider
945
1011
  });
@@ -1105,8 +1171,21 @@ var MockContext = class MockContext {
1105
1171
  if ("keyId" in params) return this.documentLoader;
1106
1172
  return Promise.resolve(this.documentLoader);
1107
1173
  }
1108
- lookupObject(_uri, _options) {
1109
- return Promise.resolve(null);
1174
+ lookupObject(uri, options = {}) {
1175
+ if (!this.hasDocumentLoader && options.documentLoader == null || (options.verifyPortableObject ?? this.verifyPortableObject) == null) return Promise.resolve(null);
1176
+ const value = typeof uri === "string" ? uri : formatIri(uri);
1177
+ if (!/^ap(?:\+ef61)?:\/\//i.test(value)) try {
1178
+ if (fromCompatibleEf61Id(value) == null) return Promise.resolve(null);
1179
+ } catch (error) {
1180
+ if (error instanceof TypeError) return Promise.resolve(null);
1181
+ throw error;
1182
+ }
1183
+ return lookupObject(uri, {
1184
+ ...options,
1185
+ documentLoader: options.documentLoader ?? this.documentLoader,
1186
+ contextLoader: options.contextLoader ?? (this.hasContextLoader ? this.contextLoader : options.documentLoader ?? this.contextLoader),
1187
+ verifyPortableObject: options.verifyPortableObject ?? this.verifyPortableObject
1188
+ });
1110
1189
  }
1111
1190
  traverseCollection(_collection, _options) {
1112
1191
  return { async *[Symbol.asyncIterator]() {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/testing",
3
- "version": "2.4.0-dev.2240+79613fa6",
3
+ "version": "2.4.0-dev.2247+4fe1b8c3",
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.2240+79613fa6"
50
+ "@fedify/fedify": "^2.4.0-dev.2247+4fe1b8c3"
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.2240+79613fa6",
56
- "@fedify/vocab-runtime": "^2.4.0-dev.2240+79613fa6"
55
+ "@fedify/vocab": "2.4.0-dev.2247+4fe1b8c3",
56
+ "@fedify/vocab-runtime": "^2.4.0-dev.2247+4fe1b8c3"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@standard-schema/spec": "^1.1.0",