@fedify/testing
Version:
Testing utilities for Fedify applications
274 lines (272 loc) • 12 kB
TypeScript
import { TracerProvider } from "@opentelemetry/api";
import { ActorCallbackSetters, ActorDispatcher, ActorKeyPair, CollectionCallbackSetters, CollectionDispatcher, Context, Federation, FederationFetchOptions, FederationStartQueueOptions, InboxListenerSetters, Message, NodeInfoDispatcher, ObjectCallbackSetters, ObjectDispatcher, ParseUriResult, RequestContext, RouteActivityOptions, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair } from "@fedify/fedify/federation";
import { Activity, Actor, Collection, Hashtag, LookupObjectOptions, Object as Object$1, Recipient, TraverseCollectionOptions } from "@fedify/fedify/vocab";
import { ResourceDescriptor } from "@fedify/fedify/webfinger";
import { JsonValue, NodeInfo } from "@fedify/fedify/nodeinfo";
import { DocumentLoader } from "@fedify/fedify/runtime";
//#region mock.d.ts
/**
* Represents a sent activity with metadata about how it was sent.
* @since 1.8.0
*/
interface SentActivity {
/** Whether the activity was queued or sent immediately. */
queued: boolean;
/** Which queue was used (if queued). */
queue?: "inbox" | "outbox" | "fanout";
/** The activity that was sent. */
activity: Activity;
/** The order in which the activity was sent (auto-incrementing counter). */
sentOrder: number;
}
/**
* A mock implementation of the {@link Federation} interface for unit testing.
* This class provides a way to test Fedify applications without needing
* a real federation setup.
*
* @example
* ```typescript
* import { Create } from "@fedify/fedify/vocab";
* import { MockFederation } from "@fedify/testing";
*
* // Create a mock federation with contextData
* const federation = new MockFederation<{ userId: string }>({
* contextData: { userId: "test-user" }
* });
*
* // Set up inbox listeners
* federation
* .setInboxListeners("/users/{identifier}/inbox")
* .on(Create, async (ctx, activity) => {
* console.log("Received:", activity);
* });
*
* // Simulate receiving an activity
* const createActivity = new Create({
* id: new URL("https://example.com/create/1"),
* actor: new URL("https://example.com/users/alice")
* });
* await federation.receiveActivity(createActivity);
* ```
*
* @typeParam TContextData The context data to pass to the {@link Context}.
* @since 1.8.0
*/
declare class MockFederation<TContextData> implements Federation<TContextData> {
private options;
sentActivities: SentActivity[];
queueStarted: boolean;
private activeQueues;
sentCounter: number;
private nodeInfoDispatcher?;
private actorDispatchers;
actorPath?: string;
inboxPath?: string;
outboxPath?: string;
followingPath?: string;
followersPath?: string;
likedPath?: string;
featuredPath?: string;
featuredTagsPath?: string;
nodeInfoPath?: string;
sharedInboxPath?: string;
objectPaths: Map<string, string>;
private objectDispatchers;
private inboxDispatcher?;
private outboxDispatcher?;
private followingDispatcher?;
private followersDispatcher?;
private likedDispatcher?;
private featuredDispatcher?;
private featuredTagsDispatcher?;
private inboxListeners;
private contextData?;
private receivedActivities;
constructor(options?: {
contextData?: TContextData;
origin?: string;
tracerProvider?: TracerProvider;
});
setNodeInfoDispatcher(path: string, dispatcher: NodeInfoDispatcher<TContextData>): void;
setActorDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: ActorDispatcher<TContextData>): ActorCallbackSetters<TContextData>;
setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: (new (...args: any[]) => TObject) & {
typeId: URL;
}, path: string, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
setInboxDispatcher(_path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Activity, RequestContext<TContextData>, TContextData, void>): CollectionCallbackSetters<RequestContext<TContextData>, TContextData, void>;
setOutboxDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Activity, RequestContext<TContextData>, TContextData, void>): CollectionCallbackSetters<RequestContext<TContextData>, TContextData, void>;
setFollowingDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Actor | URL, RequestContext<TContextData>, TContextData, void>): CollectionCallbackSetters<RequestContext<TContextData>, TContextData, void>;
setFollowersDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Recipient, Context<TContextData>, TContextData, URL>): CollectionCallbackSetters<Context<TContextData>, TContextData, URL>;
setLikedDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Object$1 | URL, RequestContext<TContextData>, TContextData, void>): CollectionCallbackSetters<RequestContext<TContextData>, TContextData, void>;
setFeaturedDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Object$1, RequestContext<TContextData>, TContextData, void>): CollectionCallbackSetters<RequestContext<TContextData>, TContextData, void>;
setFeaturedTagsDispatcher(path: `${string}{identifier}${string}` | `${string}{handle}${string}`, dispatcher: CollectionDispatcher<Hashtag, RequestContext<TContextData>, TContextData, void>): CollectionCallbackSetters<RequestContext<TContextData>, TContextData, void>;
setInboxListeners(inboxPath: `${string}{identifier}${string}` | `${string}{handle}${string}`, sharedInboxPath?: string): InboxListenerSetters<TContextData>;
startQueue(contextData: TContextData, options?: FederationStartQueueOptions): Promise<void>;
processQueuedTask(contextData: TContextData, _message: Message): Promise<void>;
createContext(baseUrl: URL, contextData: TContextData): Context<TContextData>;
createContext(request: Request, contextData: TContextData): RequestContext<TContextData>;
fetch(request: Request, options: FederationFetchOptions<TContextData>): Promise<Response>;
/**
* Simulates receiving an activity. This method is specific to the mock
* implementation and is used for testing purposes.
*
* @param activity The activity to receive.
* @returns A promise that resolves when the activity has been processed.
* @since 1.8.0
*/
receiveActivity(activity: Activity): Promise<void>;
/**
* Clears all sent activities from the mock federation.
* This method is specific to the mock implementation and is used for
* testing purposes.
*
* @since 1.8.0
*/
reset(): void;
}
/**
* A mock implementation of the {@link Context} interface for unit testing.
* This class provides a way to test Fedify applications without needing
* a real federation context.
*
* @example
* ```typescript
* import { Person, Create } from "@fedify/fedify/vocab";
* import { MockContext, MockFederation } from "@fedify/testing";
*
* // Create a mock context
* const mockFederation = new MockFederation<{ userId: string }>();
* const context = new MockContext({
* url: new URL("https://example.com"),
* data: { userId: "test-user" },
* federation: mockFederation
* });
*
* // Send an activity
* const recipient = new Person({ id: new URL("https://example.com/users/bob") });
* const activity = new Create({
* id: new URL("https://example.com/create/1"),
* actor: new URL("https://example.com/users/alice")
* });
* await context.sendActivity(
* { identifier: "alice" },
* recipient,
* activity
* );
*
* // Check sent activities
* const sent = context.getSentActivities();
* console.log(sent[0].activity);
* ```
*
* @typeParam TContextData The context data to pass to the {@link Context}.
* @since 1.8.0
*/
declare class MockContext<TContextData> implements Context<TContextData> {
readonly origin: string;
readonly canonicalOrigin: string;
readonly host: string;
readonly hostname: string;
readonly data: TContextData;
readonly federation: Federation<TContextData>;
readonly documentLoader: DocumentLoader;
readonly contextLoader: DocumentLoader;
readonly tracerProvider: TracerProvider;
private sentActivities;
constructor(options: {
url?: URL;
data: TContextData;
federation: Federation<TContextData>;
documentLoader?: DocumentLoader;
contextLoader?: DocumentLoader;
tracerProvider?: TracerProvider;
});
clone(data: TContextData): Context<TContextData>;
getNodeInfoUri(): URL;
getActorUri(identifier: string): URL;
getObjectUri<TObject extends Object$1>(cls: (new (...args: any[]) => TObject) & {
typeId: URL;
}, values: Record<string, string>): URL;
getOutboxUri(identifier: string): URL;
getInboxUri(identifier: string): URL;
getInboxUri(): URL;
getFollowingUri(identifier: string): URL;
getFollowersUri(identifier: string): URL;
getLikedUri(identifier: string): URL;
getFeaturedUri(identifier: string): URL;
getFeaturedTagsUri(identifier: string): URL;
parseUri(uri: URL): ParseUriResult | null;
getActorKeyPairs(_identifier: string): Promise<ActorKeyPair[]>;
getDocumentLoader(params: {
handle: string;
} | {
identifier: string;
}): Promise<DocumentLoader>;
getDocumentLoader(params: {
keyId: URL;
privateKey: CryptoKey;
}): DocumentLoader;
lookupObject(_uri: URL | string, _options?: LookupObjectOptions): Promise<Object$1 | null>;
traverseCollection<TItem, TContext extends Context<TContextData>>(_collection: Collection | URL | null, _options?: TraverseCollectionOptions): AsyncIterable<TItem>;
lookupNodeInfo(url: URL | string, options?: {
parse?: "strict" | "best-effort";
} & any): Promise<NodeInfo | undefined>;
lookupNodeInfo(url: URL | string, options?: {
parse: "none";
} & any): Promise<JsonValue | undefined>;
lookupWebFinger(_resource: URL | `acct:${string}@${string}` | string, _options?: any): Promise<ResourceDescriptor | null>;
sendActivity(sender: SenderKeyPair | SenderKeyPair[] | {
identifier: string;
} | {
username: string;
} | {
handle: string;
}, recipients: Recipient | Recipient[], activity: Activity, options?: SendActivityOptions): Promise<void>;
sendActivity(sender: {
identifier: string;
} | {
username: string;
} | {
handle: string;
}, recipients: "followers", activity: Activity, options?: SendActivityOptionsForCollection): Promise<void>;
sendActivity(sender: SenderKeyPair | SenderKeyPair[] | {
identifier: string;
} | {
username: string;
} | {
handle: string;
}, recipients: Recipient | Recipient[], activity: Activity, options?: SendActivityOptions): Promise<void>;
sendActivity(sender: {
identifier: string;
} | {
username: string;
} | {
handle: string;
}, recipients: "followers", activity: Activity, options?: SendActivityOptionsForCollection): Promise<void>;
routeActivity(_recipient: string | null, _activity: Activity, _options?: RouteActivityOptions): Promise<boolean>;
/**
* Gets all activities that have been sent through this mock context.
* This method is specific to the mock implementation and is used for
* testing purposes.
*
* @returns An array of sent activity records.
*/
getSentActivities(): Array<{
sender: SenderKeyPair | SenderKeyPair[] | {
identifier: string;
} | {
username: string;
} | {
handle: string;
};
recipients: Recipient | Recipient[] | "followers";
activity: Activity;
}>;
/**
* Clears all sent activities from the mock context.
* This method is specific to the mock implementation and is used for
* testing purposes.
*/
reset(): void;
}
//#endregion
export { MockContext, MockFederation, SentActivity };