@fedify/testing 1.8.1-dev.1237 → 1.8.1-dev.1238

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/mock.ts DELETED
@@ -1,1000 +0,0 @@
1
- // deno-lint-ignore-file no-explicit-any
2
- import type {
3
- ActorCallbackSetters,
4
- ActorDispatcher,
5
- ActorKeyPair,
6
- CollectionCallbackSetters,
7
- CollectionDispatcher,
8
- Context,
9
- Federation,
10
- FederationFetchOptions,
11
- FederationStartQueueOptions,
12
- InboxContext,
13
- InboxListenerSetters,
14
- Message,
15
- NodeInfoDispatcher,
16
- ObjectCallbackSetters,
17
- ObjectDispatcher,
18
- ParseUriResult,
19
- RequestContext,
20
- RouteActivityOptions,
21
- SendActivityOptions,
22
- SendActivityOptionsForCollection,
23
- SenderKeyPair,
24
- } from "@fedify/fedify/federation";
25
- import type { JsonValue, NodeInfo } from "@fedify/fedify/nodeinfo";
26
- import type { DocumentLoader } from "@fedify/fedify/runtime";
27
- import type {
28
- Activity,
29
- Actor,
30
- Collection,
31
- Hashtag,
32
- LookupObjectOptions,
33
- Object,
34
- Recipient,
35
- TraverseCollectionOptions,
36
- } from "@fedify/fedify/vocab";
37
- import type { ResourceDescriptor } from "@fedify/fedify/webfinger";
38
- import { trace, type TracerProvider } from "@opentelemetry/api";
39
- import { createInboxContext, createRequestContext } from "./context.ts";
40
-
41
- /**
42
- * Helper function to expand URI templates with values.
43
- * Supports simple placeholders like {identifier}, {handle}, etc.
44
- * @param template The URI template pattern
45
- * @param values The values to substitute
46
- * @returns The expanded URI path
47
- */
48
- function expandUriTemplate(
49
- template: string,
50
- values: Record<string, string>,
51
- ): string {
52
- return template.replace(/{([^}]+)}/g, (match, key) => {
53
- return values[key] || match;
54
- });
55
- }
56
-
57
- /**
58
- * Represents a sent activity with metadata about how it was sent.
59
- * @since 1.8.0
60
- */
61
- export interface SentActivity {
62
- /** Whether the activity was queued or sent immediately. */
63
- queued: boolean;
64
- /** Which queue was used (if queued). */
65
- queue?: "inbox" | "outbox" | "fanout";
66
- /** The activity that was sent. */
67
- activity: Activity;
68
- /** The order in which the activity was sent (auto-incrementing counter). */
69
- sentOrder: number;
70
- }
71
-
72
- /**
73
- * A mock implementation of the {@link Federation} interface for unit testing.
74
- * This class provides a way to test Fedify applications without needing
75
- * a real federation setup.
76
- *
77
- * @example
78
- * ```typescript
79
- * import { Create } from "@fedify/fedify/vocab";
80
- * import { MockFederation } from "@fedify/testing";
81
- *
82
- * // Create a mock federation with contextData
83
- * const federation = new MockFederation<{ userId: string }>({
84
- * contextData: { userId: "test-user" }
85
- * });
86
- *
87
- * // Set up inbox listeners
88
- * federation
89
- * .setInboxListeners("/users/{identifier}/inbox")
90
- * .on(Create, async (ctx, activity) => {
91
- * console.log("Received:", activity);
92
- * });
93
- *
94
- * // Simulate receiving an activity
95
- * const createActivity = new Create({
96
- * id: new URL("https://example.com/create/1"),
97
- * actor: new URL("https://example.com/users/alice")
98
- * });
99
- * await federation.receiveActivity(createActivity);
100
- * ```
101
- *
102
- * @template TContextData The context data to pass to the {@link Context}.
103
- * @since 1.8.0
104
- */
105
- export class MockFederation<TContextData> implements Federation<TContextData> {
106
- public sentActivities: SentActivity[] = [];
107
- public queueStarted = false;
108
- private activeQueues: Set<"inbox" | "outbox" | "fanout"> = new Set();
109
- public sentCounter = 0;
110
- private nodeInfoDispatcher?: NodeInfoDispatcher<TContextData>;
111
- private actorDispatchers: Map<string, ActorDispatcher<TContextData>> =
112
- new Map();
113
- public actorPath?: string;
114
- public inboxPath?: string;
115
- public outboxPath?: string;
116
- public followingPath?: string;
117
- public followersPath?: string;
118
- public likedPath?: string;
119
- public featuredPath?: string;
120
- public featuredTagsPath?: string;
121
- public nodeInfoPath?: string;
122
- public sharedInboxPath?: string;
123
- public objectPaths: Map<string, string> = new Map();
124
- private objectDispatchers: Map<
125
- string,
126
- ObjectDispatcher<TContextData, Object, string>
127
- > = new Map();
128
- private inboxDispatcher?: CollectionDispatcher<
129
- Activity,
130
- RequestContext<TContextData>,
131
- TContextData,
132
- void
133
- >;
134
- private outboxDispatcher?: CollectionDispatcher<
135
- Activity,
136
- RequestContext<TContextData>,
137
- TContextData,
138
- void
139
- >;
140
- private followingDispatcher?: CollectionDispatcher<
141
- Actor | URL,
142
- RequestContext<TContextData>,
143
- TContextData,
144
- void
145
- >;
146
- private followersDispatcher?: CollectionDispatcher<
147
- Recipient,
148
- Context<TContextData>,
149
- TContextData,
150
- URL
151
- >;
152
- private likedDispatcher?: CollectionDispatcher<
153
- Object | URL,
154
- RequestContext<TContextData>,
155
- TContextData,
156
- void
157
- >;
158
- private featuredDispatcher?: CollectionDispatcher<
159
- Object,
160
- RequestContext<TContextData>,
161
- TContextData,
162
- void
163
- >;
164
- private featuredTagsDispatcher?: CollectionDispatcher<
165
- Hashtag,
166
- RequestContext<TContextData>,
167
- TContextData,
168
- void
169
- >;
170
- private inboxListeners: Map<string, InboxListener<TContextData, Activity>[]> =
171
- new Map();
172
- private contextData?: TContextData;
173
- private receivedActivities: Activity[] = [];
174
-
175
- constructor(
176
- private options: {
177
- contextData?: TContextData;
178
- origin?: string;
179
- tracerProvider?: TracerProvider;
180
- } = {},
181
- ) {
182
- this.contextData = options.contextData;
183
- }
184
-
185
- setNodeInfoDispatcher(
186
- path: string,
187
- dispatcher: NodeInfoDispatcher<TContextData>,
188
- ): void {
189
- this.nodeInfoDispatcher = dispatcher;
190
- this.nodeInfoPath = path;
191
- }
192
-
193
- setActorDispatcher(
194
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
195
- dispatcher: ActorDispatcher<TContextData>,
196
- ): ActorCallbackSetters<TContextData> {
197
- this.actorDispatchers.set(path, dispatcher);
198
- this.actorPath = path;
199
- return {
200
- setKeyPairsDispatcher: () => this as any,
201
- mapHandle: () => this as any,
202
- mapAlias: () => this as any,
203
- authorize: () => this as any,
204
- };
205
- }
206
-
207
- setObjectDispatcher<TObject extends Object, TParam extends string>(
208
- cls: (new (...args: any[]) => TObject) & { typeId: URL },
209
- path: string,
210
- dispatcher: ObjectDispatcher<TContextData, TObject, TParam>,
211
- ): ObjectCallbackSetters<TContextData, TObject, TParam> {
212
- this.objectDispatchers.set(path, dispatcher);
213
- this.objectPaths.set(cls.typeId.href, path);
214
- return {
215
- authorize: () => this as any,
216
- };
217
- }
218
-
219
- setInboxDispatcher(
220
- _path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
221
- dispatcher: CollectionDispatcher<
222
- Activity,
223
- RequestContext<TContextData>,
224
- TContextData,
225
- void
226
- >,
227
- ): CollectionCallbackSetters<
228
- RequestContext<TContextData>,
229
- TContextData,
230
- void
231
- > {
232
- this.inboxDispatcher = dispatcher;
233
- // Note: inboxPath is set in setInboxListeners
234
- return {
235
- setCounter: () => this as any,
236
- setFirstCursor: () => this as any,
237
- setLastCursor: () => this as any,
238
- authorize: () => this as any,
239
- };
240
- }
241
-
242
- setOutboxDispatcher(
243
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
244
- dispatcher: CollectionDispatcher<
245
- Activity,
246
- RequestContext<TContextData>,
247
- TContextData,
248
- void
249
- >,
250
- ): CollectionCallbackSetters<
251
- RequestContext<TContextData>,
252
- TContextData,
253
- void
254
- > {
255
- this.outboxDispatcher = dispatcher;
256
- this.outboxPath = path;
257
- return {
258
- setCounter: () => this as any,
259
- setFirstCursor: () => this as any,
260
- setLastCursor: () => this as any,
261
- authorize: () => this as any,
262
- };
263
- }
264
-
265
- setFollowingDispatcher(
266
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
267
- dispatcher: CollectionDispatcher<
268
- Actor | URL,
269
- RequestContext<TContextData>,
270
- TContextData,
271
- void
272
- >,
273
- ): CollectionCallbackSetters<
274
- RequestContext<TContextData>,
275
- TContextData,
276
- void
277
- > {
278
- this.followingDispatcher = dispatcher;
279
- this.followingPath = path;
280
- return {
281
- setCounter: () => this as any,
282
- setFirstCursor: () => this as any,
283
- setLastCursor: () => this as any,
284
- authorize: () => this as any,
285
- };
286
- }
287
-
288
- setFollowersDispatcher(
289
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
290
- dispatcher: CollectionDispatcher<
291
- Recipient,
292
- Context<TContextData>,
293
- TContextData,
294
- URL
295
- >,
296
- ): CollectionCallbackSetters<Context<TContextData>, TContextData, URL> {
297
- this.followersDispatcher = dispatcher;
298
- this.followersPath = path;
299
- return {
300
- setCounter: () => this as any,
301
- setFirstCursor: () => this as any,
302
- setLastCursor: () => this as any,
303
- authorize: () => this as any,
304
- };
305
- }
306
-
307
- setLikedDispatcher(
308
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
309
- dispatcher: CollectionDispatcher<
310
- Object | URL,
311
- RequestContext<TContextData>,
312
- TContextData,
313
- void
314
- >,
315
- ): CollectionCallbackSetters<
316
- RequestContext<TContextData>,
317
- TContextData,
318
- void
319
- > {
320
- this.likedDispatcher = dispatcher;
321
- this.likedPath = path;
322
- return {
323
- setCounter: () => this as any,
324
- setFirstCursor: () => this as any,
325
- setLastCursor: () => this as any,
326
- authorize: () => this as any,
327
- };
328
- }
329
-
330
- setFeaturedDispatcher(
331
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
332
- dispatcher: CollectionDispatcher<
333
- Object,
334
- RequestContext<TContextData>,
335
- TContextData,
336
- void
337
- >,
338
- ): CollectionCallbackSetters<
339
- RequestContext<TContextData>,
340
- TContextData,
341
- void
342
- > {
343
- this.featuredDispatcher = dispatcher;
344
- this.featuredPath = path;
345
- return {
346
- setCounter: () => this as any,
347
- setFirstCursor: () => this as any,
348
- setLastCursor: () => this as any,
349
- authorize: () => this as any,
350
- };
351
- }
352
-
353
- setFeaturedTagsDispatcher(
354
- path: `${string}{identifier}${string}` | `${string}{handle}${string}`,
355
- dispatcher: CollectionDispatcher<
356
- Hashtag,
357
- RequestContext<TContextData>,
358
- TContextData,
359
- void
360
- >,
361
- ): CollectionCallbackSetters<
362
- RequestContext<TContextData>,
363
- TContextData,
364
- void
365
- > {
366
- this.featuredTagsDispatcher = dispatcher;
367
- this.featuredTagsPath = path;
368
- return {
369
- setCounter: () => this as any,
370
- setFirstCursor: () => this as any,
371
- setLastCursor: () => this as any,
372
- authorize: () => this as any,
373
- };
374
- }
375
-
376
- setInboxListeners(
377
- inboxPath: `${string}{identifier}${string}` | `${string}{handle}${string}`,
378
- sharedInboxPath?: string,
379
- ): InboxListenerSetters<TContextData> {
380
- this.inboxPath = inboxPath;
381
- this.sharedInboxPath = sharedInboxPath;
382
- // deno-lint-ignore no-this-alias
383
- const self = this;
384
- return {
385
- on<TActivity extends Activity>(
386
- type: new (...args: any[]) => TActivity,
387
- listener: InboxListener<TContextData, TActivity>,
388
- ): InboxListenerSetters<TContextData> {
389
- const typeName = type.name;
390
- if (!self.inboxListeners.has(typeName)) {
391
- self.inboxListeners.set(typeName, []);
392
- }
393
- self.inboxListeners.get(typeName)!.push(
394
- listener as InboxListener<TContextData, Activity>,
395
- );
396
- return this;
397
- },
398
- onError(): InboxListenerSetters<TContextData> {
399
- return this;
400
- },
401
- setSharedKeyDispatcher(): InboxListenerSetters<TContextData> {
402
- return this;
403
- },
404
- };
405
- }
406
-
407
- // deno-lint-ignore require-await
408
- async startQueue(
409
- contextData: TContextData,
410
- options?: FederationStartQueueOptions,
411
- ): Promise<void> {
412
- this.contextData = contextData;
413
- this.queueStarted = true;
414
-
415
- // If a specific queue is specified, only activate that one
416
- if (options?.queue) {
417
- this.activeQueues.add(options.queue);
418
- } else {
419
- // If no specific queue, activate all three
420
- this.activeQueues.add("inbox");
421
- this.activeQueues.add("outbox");
422
- this.activeQueues.add("fanout");
423
- }
424
- }
425
-
426
- // deno-lint-ignore require-await
427
- async processQueuedTask(
428
- contextData: TContextData,
429
- _message: Message,
430
- ): Promise<void> {
431
- this.contextData = contextData;
432
- // no queue in mock type. process immediately
433
- }
434
-
435
- createContext(baseUrl: URL, contextData: TContextData): Context<TContextData>;
436
- createContext(
437
- request: Request,
438
- contextData: TContextData,
439
- ): RequestContext<TContextData>;
440
- createContext(
441
- baseUrlOrRequest: URL | Request,
442
- contextData: TContextData,
443
- ): Context<TContextData> | RequestContext<TContextData> {
444
- // deno-lint-ignore no-this-alias
445
- const mockFederation = this;
446
-
447
- if (baseUrlOrRequest instanceof Request) {
448
- // For now, we'll use createRequestContext since MockContext doesn't support Request
449
- // But we need to ensure the sendActivity behavior is consistent
450
- return createRequestContext({
451
- url: new URL(baseUrlOrRequest.url),
452
- request: baseUrlOrRequest,
453
- data: contextData,
454
- federation: mockFederation as any,
455
- sendActivity: (async (
456
- sender: any,
457
- recipients: any,
458
- activity: any,
459
- options: any,
460
- ) => {
461
- // Create a temporary MockContext to use its sendActivity logic
462
- const tempContext = new MockContext({
463
- url: new URL(baseUrlOrRequest.url),
464
- data: contextData,
465
- federation: mockFederation as any,
466
- });
467
- await tempContext.sendActivity(
468
- sender,
469
- recipients,
470
- activity,
471
- options,
472
- );
473
- }) as any,
474
- });
475
- } else {
476
- return new MockContext({
477
- url: baseUrlOrRequest,
478
- data: contextData,
479
- federation: mockFederation as any,
480
- });
481
- }
482
- }
483
-
484
- // deno-lint-ignore require-await
485
- async fetch(
486
- request: Request,
487
- options: FederationFetchOptions<TContextData>,
488
- ): Promise<Response> {
489
- // returning 404 by default
490
- if (options.onNotFound) {
491
- return options.onNotFound(request);
492
- }
493
- return new Response("Not Found", { status: 404 });
494
- }
495
-
496
- /**
497
- * Simulates receiving an activity. This method is specific to the mock
498
- * implementation and is used for testing purposes.
499
- *
500
- * @param activity The activity to receive.
501
- * @returns A promise that resolves when the activity has been processed.
502
- * @since 1.8.0
503
- */
504
- async receiveActivity(activity: Activity): Promise<void> {
505
- this.receivedActivities.push(activity);
506
-
507
- // Find and execute appropriate inbox listeners
508
- const typeName = activity.constructor.name;
509
- const listeners = this.inboxListeners.get(typeName) || [];
510
-
511
- // Check if we have listeners but no context data
512
- if (listeners.length > 0 && this.contextData === undefined) {
513
- throw new Error(
514
- "MockFederation.receiveActivity(): contextData is not initialized. " +
515
- "Please provide contextData through the constructor or call startQueue() before receiving activities.",
516
- );
517
- }
518
-
519
- for (const listener of listeners) {
520
- const context = createInboxContext({
521
- data: this.contextData as TContextData,
522
- federation: this as any,
523
- });
524
- await listener(context, activity);
525
- }
526
- }
527
-
528
- /**
529
- * Clears all sent activities from the mock federation.
530
- * This method is specific to the mock implementation and is used for
531
- * testing purposes.
532
- *
533
- * @since 1.8.0
534
- */
535
- reset(): void {
536
- this.sentActivities = [];
537
- }
538
-
539
- setCollectionDispatcher<
540
- TObject extends Object,
541
- TParams extends Record<string, string>,
542
- >(
543
- _name: string | symbol,
544
- _itemType: any,
545
- _path: any,
546
- _dispatcher: any,
547
- ): any {
548
- // Mock implementation - just return a mock callback setters object
549
- return {
550
- setCounter: () => this as any,
551
- setFirstCursor: () => this as any,
552
- setLastCursor: () => this as any,
553
- authorize: () => this as any,
554
- };
555
- }
556
-
557
- setOrderedCollectionDispatcher<
558
- TObject extends Object,
559
- TParams extends Record<string, string>,
560
- >(
561
- _name: string | symbol,
562
- _itemType: any,
563
- _path: any,
564
- _dispatcher: any,
565
- ): any {
566
- // Mock implementation - just return a mock callback setters object
567
- return {
568
- setCounter: () => this as any,
569
- setFirstCursor: () => this as any,
570
- setLastCursor: () => this as any,
571
- authorize: () => this as any,
572
- };
573
- }
574
- }
575
-
576
- // Type definitions for inbox listeners
577
- interface InboxListener<TContextData, TActivity extends Activity> {
578
- (
579
- context: InboxContext<TContextData>,
580
- activity: TActivity,
581
- ): void | Promise<void>;
582
- }
583
-
584
- /**
585
- * A mock implementation of the {@link Context} interface for unit testing.
586
- * This class provides a way to test Fedify applications without needing
587
- * a real federation context.
588
- *
589
- * @example
590
- * ```typescript
591
- * import { Person, Create } from "@fedify/fedify/vocab";
592
- * import { MockContext, MockFederation } from "@fedify/testing";
593
- *
594
- * // Create a mock context
595
- * const mockFederation = new MockFederation<{ userId: string }>();
596
- * const context = new MockContext({
597
- * url: new URL("https://example.com"),
598
- * data: { userId: "test-user" },
599
- * federation: mockFederation
600
- * });
601
- *
602
- * // Send an activity
603
- * const recipient = new Person({ id: new URL("https://example.com/users/bob") });
604
- * const activity = new Create({
605
- * id: new URL("https://example.com/create/1"),
606
- * actor: new URL("https://example.com/users/alice")
607
- * });
608
- * await context.sendActivity(
609
- * { identifier: "alice" },
610
- * recipient,
611
- * activity
612
- * );
613
- *
614
- * // Check sent activities
615
- * const sent = context.getSentActivities();
616
- * console.log(sent[0].activity);
617
- * ```
618
- *
619
- * @template TContextData The context data to pass to the {@link Context}.
620
- * @since 1.8.0
621
- */
622
- export class MockContext<TContextData> implements Context<TContextData> {
623
- readonly origin: string;
624
- readonly canonicalOrigin: string;
625
- readonly host: string;
626
- readonly hostname: string;
627
- readonly data: TContextData;
628
- readonly federation: Federation<TContextData>;
629
- readonly documentLoader: DocumentLoader;
630
- readonly contextLoader: DocumentLoader;
631
- readonly tracerProvider: TracerProvider;
632
-
633
- private sentActivities: Array<{
634
- sender: any;
635
- recipients: Recipient | Recipient[] | "followers";
636
- activity: Activity;
637
- }> = [];
638
-
639
- constructor(
640
- options: {
641
- url?: URL;
642
- data: TContextData;
643
- federation: Federation<TContextData>;
644
- documentLoader?: DocumentLoader;
645
- contextLoader?: DocumentLoader;
646
- tracerProvider?: TracerProvider;
647
- },
648
- ) {
649
- const url = options.url ?? new URL("https://example.com");
650
- this.origin = url.origin;
651
- this.canonicalOrigin = url.origin;
652
- this.host = url.host;
653
- this.hostname = url.hostname;
654
- this.data = options.data;
655
- this.federation = options.federation;
656
- // deno-lint-ignore require-await
657
- this.documentLoader = options.documentLoader ?? (async (url: string) => ({
658
- contextUrl: null,
659
- document: {},
660
- documentUrl: url,
661
- }));
662
- this.contextLoader = options.contextLoader ?? this.documentLoader;
663
- this.tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
664
- }
665
-
666
- clone(data: TContextData): Context<TContextData> {
667
- return new MockContext({
668
- url: new URL(this.origin),
669
- data,
670
- federation: this.federation,
671
- documentLoader: this.documentLoader,
672
- contextLoader: this.contextLoader,
673
- tracerProvider: this.tracerProvider,
674
- });
675
- }
676
-
677
- getNodeInfoUri(): URL {
678
- if (
679
- this.federation instanceof MockFederation && this.federation.nodeInfoPath
680
- ) {
681
- return new URL(this.federation.nodeInfoPath, this.origin);
682
- }
683
- return new URL("/nodeinfo/2.0", this.origin);
684
- }
685
-
686
- getActorUri(identifier: string): URL {
687
- if (
688
- this.federation instanceof MockFederation && this.federation.actorPath
689
- ) {
690
- const path = expandUriTemplate(this.federation.actorPath, {
691
- identifier,
692
- handle: identifier,
693
- });
694
- return new URL(path, this.origin);
695
- }
696
- return new URL(`/users/${identifier}`, this.origin);
697
- }
698
-
699
- getObjectUri<TObject extends Object>(
700
- cls: (new (...args: any[]) => TObject) & { typeId: URL },
701
- values: Record<string, string>,
702
- ): URL {
703
- if (this.federation instanceof MockFederation) {
704
- const pathTemplate = this.federation.objectPaths.get(cls.typeId.href);
705
- if (pathTemplate) {
706
- const path = expandUriTemplate(pathTemplate, values);
707
- return new URL(path, this.origin);
708
- }
709
- }
710
- const path = globalThis.Object.entries(values)
711
- .map(([key, value]) => `${key}/${value}`)
712
- .join("/");
713
- return new URL(`/objects/${cls.name.toLowerCase()}/${path}`, this.origin);
714
- }
715
-
716
- getOutboxUri(identifier: string): URL {
717
- if (
718
- this.federation instanceof MockFederation && this.federation.outboxPath
719
- ) {
720
- const path = expandUriTemplate(this.federation.outboxPath, {
721
- identifier,
722
- handle: identifier,
723
- });
724
- return new URL(path, this.origin);
725
- }
726
- return new URL(`/users/${identifier}/outbox`, this.origin);
727
- }
728
-
729
- getInboxUri(identifier: string): URL;
730
- getInboxUri(): URL;
731
- getInboxUri(identifier?: string): URL {
732
- if (identifier) {
733
- if (
734
- this.federation instanceof MockFederation && this.federation.inboxPath
735
- ) {
736
- const path = expandUriTemplate(this.federation.inboxPath, {
737
- identifier,
738
- handle: identifier,
739
- });
740
- return new URL(path, this.origin);
741
- }
742
- return new URL(`/users/${identifier}/inbox`, this.origin);
743
- }
744
- if (
745
- this.federation instanceof MockFederation &&
746
- this.federation.sharedInboxPath
747
- ) {
748
- return new URL(this.federation.sharedInboxPath, this.origin);
749
- }
750
- return new URL("/inbox", this.origin);
751
- }
752
-
753
- getFollowingUri(identifier: string): URL {
754
- if (
755
- this.federation instanceof MockFederation && this.federation.followingPath
756
- ) {
757
- const path = expandUriTemplate(this.federation.followingPath, {
758
- identifier,
759
- handle: identifier,
760
- });
761
- return new URL(path, this.origin);
762
- }
763
- return new URL(`/users/${identifier}/following`, this.origin);
764
- }
765
-
766
- getFollowersUri(identifier: string): URL {
767
- if (
768
- this.federation instanceof MockFederation && this.federation.followersPath
769
- ) {
770
- const path = expandUriTemplate(this.federation.followersPath, {
771
- identifier,
772
- handle: identifier,
773
- });
774
- return new URL(path, this.origin);
775
- }
776
- return new URL(`/users/${identifier}/followers`, this.origin);
777
- }
778
-
779
- getLikedUri(identifier: string): URL {
780
- if (
781
- this.federation instanceof MockFederation && this.federation.likedPath
782
- ) {
783
- const path = expandUriTemplate(this.federation.likedPath, {
784
- identifier,
785
- handle: identifier,
786
- });
787
- return new URL(path, this.origin);
788
- }
789
- return new URL(`/users/${identifier}/liked`, this.origin);
790
- }
791
-
792
- getFeaturedUri(identifier: string): URL {
793
- if (
794
- this.federation instanceof MockFederation && this.federation.featuredPath
795
- ) {
796
- const path = expandUriTemplate(this.federation.featuredPath, {
797
- identifier,
798
- handle: identifier,
799
- });
800
- return new URL(path, this.origin);
801
- }
802
- return new URL(`/users/${identifier}/featured`, this.origin);
803
- }
804
-
805
- getFeaturedTagsUri(identifier: string): URL {
806
- if (
807
- this.federation instanceof MockFederation &&
808
- this.federation.featuredTagsPath
809
- ) {
810
- const path = expandUriTemplate(this.federation.featuredTagsPath, {
811
- identifier,
812
- handle: identifier,
813
- });
814
- return new URL(path, this.origin);
815
- }
816
- return new URL(`/users/${identifier}/tags`, this.origin);
817
- }
818
-
819
- getCollectionUri<TParam extends Record<string, string>>(
820
- _name: string | symbol,
821
- values: TParam,
822
- ): URL {
823
- // Mock implementation - construct a generic collection URI
824
- const path = globalThis.Object.entries(values)
825
- .map(([key, value]) => `${key}/${value}`)
826
- .join("/");
827
- return new URL(`/collections/${String(_name)}/${path}`, this.origin);
828
- }
829
-
830
- parseUri(uri: URL): ParseUriResult | null {
831
- if (uri.pathname.startsWith("/users/")) {
832
- const parts = uri.pathname.split("/");
833
- if (parts.length >= 3) {
834
- return {
835
- type: "actor",
836
- identifier: parts[2],
837
- handle: parts[2],
838
- };
839
- }
840
- }
841
- return null;
842
- }
843
-
844
- getActorKeyPairs(_identifier: string): Promise<ActorKeyPair[]> {
845
- return Promise.resolve([]);
846
- }
847
-
848
- getDocumentLoader(
849
- params: { handle: string } | { identifier: string },
850
- ): Promise<DocumentLoader>;
851
- getDocumentLoader(
852
- params: { keyId: URL; privateKey: CryptoKey },
853
- ): DocumentLoader;
854
- getDocumentLoader(params: any): DocumentLoader | Promise<DocumentLoader> {
855
- // return the same document loader
856
- if ("keyId" in params) {
857
- return this.documentLoader;
858
- }
859
- return Promise.resolve(this.documentLoader);
860
- }
861
-
862
- lookupObject(
863
- _uri: URL | string,
864
- _options?: LookupObjectOptions,
865
- ): Promise<Object | null> {
866
- return Promise.resolve(null);
867
- }
868
-
869
- traverseCollection<TItem, TContext extends Context<TContextData>>(
870
- _collection: Collection | URL | null,
871
- _options?: TraverseCollectionOptions,
872
- ): AsyncIterable<TItem> {
873
- // just returning empty async iterable
874
- return {
875
- async *[Symbol.asyncIterator]() {
876
- // yield nothing
877
- },
878
- };
879
- }
880
-
881
- lookupNodeInfo(
882
- url: URL | string,
883
- options?: { parse?: "strict" | "best-effort" } & any,
884
- ): Promise<NodeInfo | undefined>;
885
- lookupNodeInfo(
886
- url: URL | string,
887
- options?: { parse: "none" } & any,
888
- ): Promise<JsonValue | undefined>;
889
- lookupNodeInfo(
890
- _url: URL | string,
891
- _options?: any,
892
- ): Promise<NodeInfo | JsonValue | undefined> {
893
- return Promise.resolve(undefined);
894
- }
895
-
896
- lookupWebFinger(
897
- _resource: URL | `acct:${string}@${string}` | string,
898
- _options?: any,
899
- ): Promise<ResourceDescriptor | null> {
900
- return Promise.resolve(null);
901
- }
902
-
903
- sendActivity(
904
- sender:
905
- | SenderKeyPair
906
- | SenderKeyPair[]
907
- | { identifier: string }
908
- | { username: string }
909
- | { handle: string },
910
- recipients: Recipient | Recipient[],
911
- activity: Activity,
912
- options?: SendActivityOptions,
913
- ): Promise<void>;
914
- sendActivity(
915
- sender: { identifier: string } | { username: string } | { handle: string },
916
- recipients: "followers",
917
- activity: Activity,
918
- options?: SendActivityOptionsForCollection,
919
- ): Promise<void>;
920
- sendActivity(
921
- sender:
922
- | SenderKeyPair
923
- | SenderKeyPair[]
924
- | { identifier: string }
925
- | { username: string }
926
- | { handle: string },
927
- recipients: Recipient | Recipient[],
928
- activity: Activity,
929
- options?: SendActivityOptions,
930
- ): Promise<void>;
931
- sendActivity(
932
- sender: { identifier: string } | { username: string } | { handle: string },
933
- recipients: "followers",
934
- activity: Activity,
935
- options?: SendActivityOptionsForCollection,
936
- ): Promise<void>;
937
- sendActivity(
938
- sender:
939
- | SenderKeyPair
940
- | SenderKeyPair[]
941
- | { identifier: string }
942
- | { username: string }
943
- | { handle: string },
944
- recipients: Recipient | Recipient[] | "followers",
945
- activity: Activity,
946
- _options?: SendActivityOptions | SendActivityOptionsForCollection,
947
- ): Promise<void> {
948
- this.sentActivities.push({ sender, recipients, activity });
949
-
950
- // If this is a MockFederation, also record it there
951
- if (this.federation instanceof MockFederation) {
952
- const queued = this.federation.queueStarted;
953
- this.federation.sentActivities.push({
954
- queued,
955
- queue: queued ? "outbox" : undefined,
956
- activity,
957
- sentOrder: ++this.federation.sentCounter,
958
- });
959
- }
960
-
961
- return Promise.resolve();
962
- }
963
-
964
- routeActivity(
965
- _recipient: string | null,
966
- _activity: Activity,
967
- _options?: RouteActivityOptions,
968
- ): Promise<boolean> {
969
- return Promise.resolve(true);
970
- }
971
-
972
- /**
973
- * Gets all activities that have been sent through this mock context.
974
- * This method is specific to the mock implementation and is used for
975
- * testing purposes.
976
- *
977
- * @returns An array of sent activity records.
978
- */
979
- getSentActivities(): Array<{
980
- sender:
981
- | SenderKeyPair
982
- | SenderKeyPair[]
983
- | { identifier: string }
984
- | { username: string }
985
- | { handle: string };
986
- recipients: Recipient | Recipient[] | "followers";
987
- activity: Activity;
988
- }> {
989
- return [...this.sentActivities];
990
- }
991
-
992
- /**
993
- * Clears all sent activities from the mock context.
994
- * This method is specific to the mock implementation and is used for
995
- * testing purposes.
996
- */
997
- reset(): void {
998
- this.sentActivities = [];
999
- }
1000
- }