@fedify/testing 2.0.0-dev.221 → 2.0.0-dev.228

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
@@ -1,3 +1,6 @@
1
+
2
+ const { Temporal } = require("@js-temporal/polyfill");
3
+
1
4
  //#region rolldown:runtime
2
5
  var __create = Object.create;
3
6
  var __defProp = Object.defineProperty;
@@ -21,8 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
24
  }) : target, mod));
22
25
 
23
26
  //#endregion
24
- const __fedify_fedify_federation = __toESM(require("@fedify/fedify/federation"));
25
27
  const __fedify_vocab = __toESM(require("@fedify/vocab"));
28
+ const __fedify_fedify_federation = __toESM(require("@fedify/fedify/federation"));
29
+ const es_toolkit = __toESM(require("es-toolkit"));
30
+ const node_assert_strict = __toESM(require("node:assert/strict"));
26
31
 
27
32
  //#region src/docloader.ts
28
33
  const mockDocumentLoader = async (url) => ({
@@ -205,6 +210,7 @@ var MockFederation = class {
205
210
  nodeInfoDispatcher;
206
211
  webFingerDispatcher;
207
212
  actorDispatchers = /* @__PURE__ */ new Map();
213
+ actorKeyPairsDispatcher;
208
214
  actorPath;
209
215
  inboxPath;
210
216
  outboxPath;
@@ -242,7 +248,10 @@ var MockFederation = class {
242
248
  this.actorDispatchers.set(path, dispatcher);
243
249
  this.actorPath = path;
244
250
  return {
245
- setKeyPairsDispatcher: () => this,
251
+ setKeyPairsDispatcher: (keyPairsDispatcher) => {
252
+ this.actorKeyPairsDispatcher = keyPairsDispatcher;
253
+ return this;
254
+ },
246
255
  mapHandle: () => this,
247
256
  mapAlias: () => this,
248
257
  authorize: () => this
@@ -678,8 +687,25 @@ var MockContext = class MockContext {
678
687
  }
679
688
  return null;
680
689
  }
681
- getActorKeyPairs(_identifier) {
682
- return Promise.resolve([]);
690
+ async getActorKeyPairs(identifier) {
691
+ if (this.federation instanceof MockFederation && this.federation.actorKeyPairsDispatcher) {
692
+ const keyPairs = await this.federation.actorKeyPairsDispatcher(this, identifier);
693
+ const owner = this.getActorUri(identifier);
694
+ return keyPairs.map((kp) => ({
695
+ ...kp,
696
+ cryptographicKey: new __fedify_vocab.CryptographicKey({
697
+ id: kp.keyId,
698
+ owner,
699
+ publicKey: kp.publicKey
700
+ }),
701
+ multikey: new __fedify_vocab.Multikey({
702
+ id: kp.keyId,
703
+ controller: owner,
704
+ publicKey: kp.publicKey
705
+ })
706
+ }));
707
+ }
708
+ return [];
683
709
  }
684
710
  getDocumentLoader(params) {
685
711
  if ("keyId" in params) return this.documentLoader;
@@ -737,8 +763,114 @@ var MockContext = class MockContext {
737
763
  }
738
764
  };
739
765
 
766
+ //#endregion
767
+ //#region src/mq-tester.ts
768
+ /**
769
+ * Tests a {@link MessageQueue} implementation with a standard set of tests.
770
+ *
771
+ * This function runs tests for:
772
+ * - `enqueue()`: Basic message enqueueing
773
+ * - `enqueue()` with delay: Delayed message enqueueing
774
+ * - `enqueueMany()`: Bulk message enqueueing
775
+ * - `enqueueMany()` with delay: Delayed bulk message enqueueing
776
+ * - Multiple listeners: Ensures messages are processed by only one listener
777
+ *
778
+ * @example
779
+ * ```typescript ignore
780
+ * import { test } from "@fedify/fixture";
781
+ * import { testMessageQueue } from "@fedify/testing";
782
+ * import { MyMessageQueue } from "./my-mq.ts";
783
+ *
784
+ * test("MyMessageQueue", () =>
785
+ * testMessageQueue(
786
+ * () => new MyMessageQueue(),
787
+ * async ({ mq1, mq2, controller }) => {
788
+ * controller.abort();
789
+ * await mq1.close();
790
+ * await mq2.close();
791
+ * },
792
+ * )
793
+ * );
794
+ * ```
795
+ *
796
+ * @param getMessageQueue A factory function that creates a new message queue
797
+ * instance. It should return a new instance each time
798
+ * to ensure test isolation, but both instances should
799
+ * share the same underlying storage/channel.
800
+ * @param onFinally A cleanup function called after all tests complete.
801
+ * It receives both message queue instances and the abort
802
+ * controller used for the listeners.
803
+ * @returns A promise that resolves when all tests pass.
804
+ */
805
+ async function testMessageQueue(getMessageQueue, onFinally) {
806
+ const mq1 = await getMessageQueue();
807
+ const mq2 = await getMessageQueue();
808
+ const controller = new AbortController();
809
+ try {
810
+ const messages = [];
811
+ const listening1 = mq1.listen((message) => {
812
+ messages.push(message);
813
+ }, { signal: controller.signal });
814
+ const listening2 = mq2.listen((message) => {
815
+ messages.push(message);
816
+ }, { signal: controller.signal });
817
+ await mq1.enqueue("Hello, world!");
818
+ await waitFor(() => messages.length > 0, 15e3);
819
+ (0, node_assert_strict.deepStrictEqual)(messages, ["Hello, world!"]);
820
+ let started = Date.now();
821
+ await mq1.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) });
822
+ await waitFor(() => messages.length > 1, 15e3);
823
+ (0, node_assert_strict.deepStrictEqual)(messages, ["Hello, world!", "Delayed message"]);
824
+ (0, node_assert_strict.ok)(Date.now() - started >= 3e3, "Delayed message should be delivered after at least 3 seconds");
825
+ if (mq1.enqueueMany != null) {
826
+ while (messages.length > 0) messages.pop();
827
+ const batchMessages = [
828
+ "First batch message",
829
+ "Second batch message",
830
+ "Third batch message"
831
+ ];
832
+ await mq1.enqueueMany(batchMessages);
833
+ await waitFor(() => messages.length >= batchMessages.length, 15e3);
834
+ (0, node_assert_strict.deepStrictEqual)(new Set(messages), new Set(batchMessages));
835
+ while (messages.length > 0) messages.pop();
836
+ started = Date.now();
837
+ const delayedBatchMessages = ["Delayed batch 1", "Delayed batch 2"];
838
+ await mq1.enqueueMany(delayedBatchMessages, { delay: Temporal.Duration.from({ seconds: 2 }) });
839
+ await waitFor(() => messages.length >= delayedBatchMessages.length, 15e3);
840
+ (0, node_assert_strict.deepStrictEqual)(new Set(messages), new Set(delayedBatchMessages));
841
+ (0, node_assert_strict.ok)(Date.now() - started >= 2e3, "Delayed batch messages should be delivered after at least 2 seconds");
842
+ }
843
+ while (messages.length > 0) messages.pop();
844
+ const bulkCount = 100;
845
+ for (let i = 0; i < bulkCount; i++) await mq1.enqueue(`message-${i}`);
846
+ await waitFor(() => messages.length >= bulkCount, 3e4);
847
+ const expectedMessages = new Set(Array.from({ length: bulkCount }, (_, i) => `message-${i}`));
848
+ (0, node_assert_strict.deepStrictEqual)(new Set(messages), expectedMessages);
849
+ controller.abort();
850
+ await listening1;
851
+ await listening2;
852
+ } finally {
853
+ await onFinally({
854
+ mq1,
855
+ mq2,
856
+ controller
857
+ });
858
+ }
859
+ }
860
+ async function waitFor(predicate, timeoutMs) {
861
+ const started = Date.now();
862
+ while (!predicate()) {
863
+ await (0, es_toolkit.delay)(500);
864
+ if (Date.now() - started > timeoutMs) throw new Error("Timeout");
865
+ }
866
+ }
867
+ const getRandomKey = (prefix) => `fedify_test_${prefix}_${crypto.randomUUID()}`;
868
+
740
869
  //#endregion
741
870
  exports.createContext = createContext;
742
871
  exports.createFederation = createFederation;
743
872
  exports.createInboxContext = createInboxContext;
744
- exports.createRequestContext = createRequestContext;
873
+ exports.createRequestContext = createRequestContext;
874
+ exports.getRandomKey = getRandomKey;
875
+ exports.testMessageQueue = testMessageQueue;
876
+ exports.waitFor = waitFor;
package/dist/mod.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Context, Federation, InboxContext, RequestContext } from "@fedify/fedify/federation";
2
2
  import { Activity } from "@fedify/vocab";
3
+ import { MessageQueue } from "@fedify/fedify";
3
4
 
4
5
  //#region src/context.d.ts
5
6
  declare function createContext<TContextData>(values: Partial<Context<TContextData>> & {
@@ -123,4 +124,54 @@ declare function createFederation<TContextData>(options?: {
123
124
  tracerProvider?: any;
124
125
  }): TestFederation<TContextData>;
125
126
  //#endregion
126
- export { createContext, createFederation, createInboxContext, createRequestContext };
127
+ //#region src/mq-tester.d.ts
128
+ /**
129
+ * Tests a {@link MessageQueue} implementation with a standard set of tests.
130
+ *
131
+ * This function runs tests for:
132
+ * - `enqueue()`: Basic message enqueueing
133
+ * - `enqueue()` with delay: Delayed message enqueueing
134
+ * - `enqueueMany()`: Bulk message enqueueing
135
+ * - `enqueueMany()` with delay: Delayed bulk message enqueueing
136
+ * - Multiple listeners: Ensures messages are processed by only one listener
137
+ *
138
+ * @example
139
+ * ```typescript ignore
140
+ * import { test } from "@fedify/fixture";
141
+ * import { testMessageQueue } from "@fedify/testing";
142
+ * import { MyMessageQueue } from "./my-mq.ts";
143
+ *
144
+ * test("MyMessageQueue", () =>
145
+ * testMessageQueue(
146
+ * () => new MyMessageQueue(),
147
+ * async ({ mq1, mq2, controller }) => {
148
+ * controller.abort();
149
+ * await mq1.close();
150
+ * await mq2.close();
151
+ * },
152
+ * )
153
+ * );
154
+ * ```
155
+ *
156
+ * @param getMessageQueue A factory function that creates a new message queue
157
+ * instance. It should return a new instance each time
158
+ * to ensure test isolation, but both instances should
159
+ * share the same underlying storage/channel.
160
+ * @param onFinally A cleanup function called after all tests complete.
161
+ * It receives both message queue instances and the abort
162
+ * controller used for the listeners.
163
+ * @returns A promise that resolves when all tests pass.
164
+ */
165
+ declare function testMessageQueue<MQ extends MessageQueue>(getMessageQueue: () => MQ | Promise<MQ>, onFinally: ({
166
+ mq1,
167
+ mq2,
168
+ controller
169
+ }: {
170
+ mq1: MQ;
171
+ mq2: MQ;
172
+ controller: AbortController;
173
+ }) => Promise<void> | void): Promise<void>;
174
+ declare function waitFor(predicate: () => boolean, timeoutMs: number): Promise<void>;
175
+ declare const getRandomKey: (prefix: string) => string;
176
+ //#endregion
177
+ export { createContext, createFederation, createInboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };
package/dist/mod.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { Context, Federation, InboxContext, RequestContext } from "@fedify/fedify/federation";
1
+ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { Activity } from "@fedify/vocab";
3
+ import { Context, Federation, InboxContext, RequestContext } from "@fedify/fedify/federation";
4
+ import { MessageQueue } from "@fedify/fedify";
3
5
 
4
6
  //#region src/context.d.ts
5
7
  declare function createContext<TContextData>(values: Partial<Context<TContextData>> & {
@@ -123,4 +125,54 @@ declare function createFederation<TContextData>(options?: {
123
125
  tracerProvider?: any;
124
126
  }): TestFederation<TContextData>;
125
127
  //#endregion
126
- export { createContext, createFederation, createInboxContext, createRequestContext };
128
+ //#region src/mq-tester.d.ts
129
+ /**
130
+ * Tests a {@link MessageQueue} implementation with a standard set of tests.
131
+ *
132
+ * This function runs tests for:
133
+ * - `enqueue()`: Basic message enqueueing
134
+ * - `enqueue()` with delay: Delayed message enqueueing
135
+ * - `enqueueMany()`: Bulk message enqueueing
136
+ * - `enqueueMany()` with delay: Delayed bulk message enqueueing
137
+ * - Multiple listeners: Ensures messages are processed by only one listener
138
+ *
139
+ * @example
140
+ * ```typescript ignore
141
+ * import { test } from "@fedify/fixture";
142
+ * import { testMessageQueue } from "@fedify/testing";
143
+ * import { MyMessageQueue } from "./my-mq.ts";
144
+ *
145
+ * test("MyMessageQueue", () =>
146
+ * testMessageQueue(
147
+ * () => new MyMessageQueue(),
148
+ * async ({ mq1, mq2, controller }) => {
149
+ * controller.abort();
150
+ * await mq1.close();
151
+ * await mq2.close();
152
+ * },
153
+ * )
154
+ * );
155
+ * ```
156
+ *
157
+ * @param getMessageQueue A factory function that creates a new message queue
158
+ * instance. It should return a new instance each time
159
+ * to ensure test isolation, but both instances should
160
+ * share the same underlying storage/channel.
161
+ * @param onFinally A cleanup function called after all tests complete.
162
+ * It receives both message queue instances and the abort
163
+ * controller used for the listeners.
164
+ * @returns A promise that resolves when all tests pass.
165
+ */
166
+ declare function testMessageQueue<MQ extends MessageQueue>(getMessageQueue: () => MQ | Promise<MQ>, onFinally: ({
167
+ mq1,
168
+ mq2,
169
+ controller
170
+ }: {
171
+ mq1: MQ;
172
+ mq2: MQ;
173
+ controller: AbortController;
174
+ }) => Promise<void> | void): Promise<void>;
175
+ declare function waitFor(predicate: () => boolean, timeoutMs: number): Promise<void>;
176
+ declare const getRandomKey: (prefix: string) => string;
177
+ //#endregion
178
+ export { createContext, createFederation, createInboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };
package/dist/mod.js CHANGED
@@ -1,5 +1,10 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { CryptographicKey, Multikey, lookupObject, traverseCollection } from "@fedify/vocab";
1
5
  import { RouterError } from "@fedify/fedify/federation";
2
- import { lookupObject, traverseCollection } from "@fedify/vocab";
6
+ import { delay } from "es-toolkit";
7
+ import { deepStrictEqual, ok } from "node:assert/strict";
3
8
 
4
9
  //#region src/docloader.ts
5
10
  const mockDocumentLoader = async (url) => ({
@@ -182,6 +187,7 @@ var MockFederation = class {
182
187
  nodeInfoDispatcher;
183
188
  webFingerDispatcher;
184
189
  actorDispatchers = /* @__PURE__ */ new Map();
190
+ actorKeyPairsDispatcher;
185
191
  actorPath;
186
192
  inboxPath;
187
193
  outboxPath;
@@ -219,7 +225,10 @@ var MockFederation = class {
219
225
  this.actorDispatchers.set(path, dispatcher);
220
226
  this.actorPath = path;
221
227
  return {
222
- setKeyPairsDispatcher: () => this,
228
+ setKeyPairsDispatcher: (keyPairsDispatcher) => {
229
+ this.actorKeyPairsDispatcher = keyPairsDispatcher;
230
+ return this;
231
+ },
223
232
  mapHandle: () => this,
224
233
  mapAlias: () => this,
225
234
  authorize: () => this
@@ -655,8 +664,25 @@ var MockContext = class MockContext {
655
664
  }
656
665
  return null;
657
666
  }
658
- getActorKeyPairs(_identifier) {
659
- return Promise.resolve([]);
667
+ async getActorKeyPairs(identifier) {
668
+ if (this.federation instanceof MockFederation && this.federation.actorKeyPairsDispatcher) {
669
+ const keyPairs = await this.federation.actorKeyPairsDispatcher(this, identifier);
670
+ const owner = this.getActorUri(identifier);
671
+ return keyPairs.map((kp) => ({
672
+ ...kp,
673
+ cryptographicKey: new CryptographicKey({
674
+ id: kp.keyId,
675
+ owner,
676
+ publicKey: kp.publicKey
677
+ }),
678
+ multikey: new Multikey({
679
+ id: kp.keyId,
680
+ controller: owner,
681
+ publicKey: kp.publicKey
682
+ })
683
+ }));
684
+ }
685
+ return [];
660
686
  }
661
687
  getDocumentLoader(params) {
662
688
  if ("keyId" in params) return this.documentLoader;
@@ -715,4 +741,107 @@ var MockContext = class MockContext {
715
741
  };
716
742
 
717
743
  //#endregion
718
- export { createContext, createFederation, createInboxContext, createRequestContext };
744
+ //#region src/mq-tester.ts
745
+ /**
746
+ * Tests a {@link MessageQueue} implementation with a standard set of tests.
747
+ *
748
+ * This function runs tests for:
749
+ * - `enqueue()`: Basic message enqueueing
750
+ * - `enqueue()` with delay: Delayed message enqueueing
751
+ * - `enqueueMany()`: Bulk message enqueueing
752
+ * - `enqueueMany()` with delay: Delayed bulk message enqueueing
753
+ * - Multiple listeners: Ensures messages are processed by only one listener
754
+ *
755
+ * @example
756
+ * ```typescript ignore
757
+ * import { test } from "@fedify/fixture";
758
+ * import { testMessageQueue } from "@fedify/testing";
759
+ * import { MyMessageQueue } from "./my-mq.ts";
760
+ *
761
+ * test("MyMessageQueue", () =>
762
+ * testMessageQueue(
763
+ * () => new MyMessageQueue(),
764
+ * async ({ mq1, mq2, controller }) => {
765
+ * controller.abort();
766
+ * await mq1.close();
767
+ * await mq2.close();
768
+ * },
769
+ * )
770
+ * );
771
+ * ```
772
+ *
773
+ * @param getMessageQueue A factory function that creates a new message queue
774
+ * instance. It should return a new instance each time
775
+ * to ensure test isolation, but both instances should
776
+ * share the same underlying storage/channel.
777
+ * @param onFinally A cleanup function called after all tests complete.
778
+ * It receives both message queue instances and the abort
779
+ * controller used for the listeners.
780
+ * @returns A promise that resolves when all tests pass.
781
+ */
782
+ async function testMessageQueue(getMessageQueue, onFinally) {
783
+ const mq1 = await getMessageQueue();
784
+ const mq2 = await getMessageQueue();
785
+ const controller = new AbortController();
786
+ try {
787
+ const messages = [];
788
+ const listening1 = mq1.listen((message) => {
789
+ messages.push(message);
790
+ }, { signal: controller.signal });
791
+ const listening2 = mq2.listen((message) => {
792
+ messages.push(message);
793
+ }, { signal: controller.signal });
794
+ await mq1.enqueue("Hello, world!");
795
+ await waitFor(() => messages.length > 0, 15e3);
796
+ deepStrictEqual(messages, ["Hello, world!"]);
797
+ let started = Date.now();
798
+ await mq1.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) });
799
+ await waitFor(() => messages.length > 1, 15e3);
800
+ deepStrictEqual(messages, ["Hello, world!", "Delayed message"]);
801
+ ok(Date.now() - started >= 3e3, "Delayed message should be delivered after at least 3 seconds");
802
+ if (mq1.enqueueMany != null) {
803
+ while (messages.length > 0) messages.pop();
804
+ const batchMessages = [
805
+ "First batch message",
806
+ "Second batch message",
807
+ "Third batch message"
808
+ ];
809
+ await mq1.enqueueMany(batchMessages);
810
+ await waitFor(() => messages.length >= batchMessages.length, 15e3);
811
+ deepStrictEqual(new Set(messages), new Set(batchMessages));
812
+ while (messages.length > 0) messages.pop();
813
+ started = Date.now();
814
+ const delayedBatchMessages = ["Delayed batch 1", "Delayed batch 2"];
815
+ await mq1.enqueueMany(delayedBatchMessages, { delay: Temporal.Duration.from({ seconds: 2 }) });
816
+ await waitFor(() => messages.length >= delayedBatchMessages.length, 15e3);
817
+ deepStrictEqual(new Set(messages), new Set(delayedBatchMessages));
818
+ ok(Date.now() - started >= 2e3, "Delayed batch messages should be delivered after at least 2 seconds");
819
+ }
820
+ while (messages.length > 0) messages.pop();
821
+ const bulkCount = 100;
822
+ for (let i = 0; i < bulkCount; i++) await mq1.enqueue(`message-${i}`);
823
+ await waitFor(() => messages.length >= bulkCount, 3e4);
824
+ const expectedMessages = new Set(Array.from({ length: bulkCount }, (_, i) => `message-${i}`));
825
+ deepStrictEqual(new Set(messages), expectedMessages);
826
+ controller.abort();
827
+ await listening1;
828
+ await listening2;
829
+ } finally {
830
+ await onFinally({
831
+ mq1,
832
+ mq2,
833
+ controller
834
+ });
835
+ }
836
+ }
837
+ async function waitFor(predicate, timeoutMs) {
838
+ const started = Date.now();
839
+ while (!predicate()) {
840
+ await delay(500);
841
+ if (Date.now() - started > timeoutMs) throw new Error("Timeout");
842
+ }
843
+ }
844
+ const getRandomKey = (prefix) => `fedify_test_${prefix}_${crypto.randomUUID()}`;
845
+
846
+ //#endregion
847
+ export { createContext, createFederation, createInboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/testing",
3
- "version": "2.0.0-dev.221+f54af224",
3
+ "version": "2.0.0-dev.228+5c4cd765",
4
4
  "description": "Testing utilities for Fedify applications",
5
5
  "keywords": [
6
6
  "fedify",
@@ -50,14 +50,18 @@
50
50
  "package.json"
51
51
  ],
52
52
  "peerDependencies": {
53
- "@fedify/fedify": "^2.0.0-dev.221+f54af224"
53
+ "@fedify/fedify": "^2.0.0-dev.228+5c4cd765"
54
+ },
55
+ "dependencies": {
56
+ "es-toolkit": "1.43.0"
54
57
  },
55
58
  "devDependencies": {
56
59
  "@js-temporal/polyfill": "^0.5.1",
57
60
  "@std/assert": "npm:@jsr/std__assert@^1.0.13",
58
61
  "@std/async": "npm:@jsr/std__async@^1.0.13",
59
62
  "tsdown": "^0.12.9",
60
- "typescript": "^5.9.3"
63
+ "typescript": "^5.9.3",
64
+ "@fedify/fixture": "^2.0.0"
61
65
  },
62
66
  "scripts": {
63
67
  "build": "tsdown",