UNPKG

@fedify/testing

Version:

Testing utilities for Fedify applications

221 lines (220 loc) 7.86 kB
/// <reference lib="esnext.temporal" /> import { Context, Federation, InboxContext, OutboxContext, RequestContext } from "@fedify/fedify/federation"; import { Activity } from "@fedify/vocab"; import { MessageQueue } from "@fedify/fedify"; //#region src/context.d.ts declare function createContext<TContextData>(values: Partial<Context<TContextData>> & { url?: URL; data: TContextData; federation: Federation<TContextData>; }): Context<TContextData>; /** * Creates a RequestContext for testing purposes. * Not exported - used internally only. Public API is in mock.ts * @param args Partial RequestContext properties * @returns A RequestContext instance * @since 1.8.0 */ declare function createRequestContext<TContextData>(args: Partial<RequestContext<TContextData>> & { url: URL; data: TContextData; federation: Federation<TContextData>; }): RequestContext<TContextData>; /** * Test-specific InboxContext type alias. * This indirection helps avoid JSR type analyzer issues. * @since 1.9.1 */ type TestInboxContext<TContextData> = InboxContext<TContextData>; /** * Test-specific OutboxContext type alias. * This indirection helps avoid JSR type analyzer issues. * @since 2.2.0 */ type TestOutboxContext<TContextData> = OutboxContext<TContextData>; /** * Creates an InboxContext for testing purposes. * Not exported - used internally only. Public API is in mock.ts * @param args Partial InboxContext properties * @returns An InboxContext instance * @since 1.8.0 */ declare function createInboxContext<TContextData>(args: Partial<InboxContext<TContextData>> & { url?: URL; data: TContextData; recipient?: string | null; federation: Federation<TContextData>; }): TestInboxContext<TContextData>; /** * Creates an OutboxContext for testing purposes. * Not exported - used internally only. Public API is in mock.ts * @param args Partial OutboxContext properties * @returns An OutboxContext instance * @since 2.2.0 */ declare function createOutboxContext<TContextData>(args: Partial<OutboxContext<TContextData>> & { url?: URL; data: TContextData; identifier: string; federation: Federation<TContextData>; }): TestOutboxContext<TContextData>; //#endregion //#region src/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 raw forwarded payload, if preserved by the caller. */ rawActivity?: unknown; /** The order in which the activity was sent (auto-incrementing counter). */ sentOrder: number; } /** * A mock Context interface for testing purposes. * Extends the standard Context interface with additional testing utilities. * @since 1.9.1 */ interface TestContext<TContextData> extends Omit<Context<TContextData>, "clone">, Pick<RequestContext<TContextData>, "request" | "url" | "getActor" | "getObject" | "getSignedKey" | "getSignedKeyOwner" | "sendActivity" | "routeActivity"> { clone(data: TContextData): TestContext<TContextData>; getSentActivities(): Array<{ sender: any; recipients: any; activity: Activity; rawActivity?: unknown; }>; reset(): void; } /** * A mock Federation interface for testing purposes. * Extends the standard Federation interface with additional testing utilities. * @since 1.9.1 */ interface TestFederation<TContextData> extends Omit<Federation<TContextData>, "createContext"> { sentActivities: SentActivity[]; queueStarted: boolean; sentCounter: number; receiveActivity(activity: Activity): Promise<void>; postOutboxActivity(identifier: string, activity: Activity): Promise<void>; reset(): void; createContext(baseUrlOrRequest: URL | Request, contextData: TContextData): TestContext<TContextData>; } /** * Creates a mock Federation instance for testing purposes. * * @template TContextData The type of context data to use * @param options Optional configuration for the mock federation * @returns A Federation instance that can be used for testing * @since 1.9.1 * * @example * ```typescript * import { Create } from "@fedify/vocab"; * import { createFederation } from "@fedify/testing"; * * // Create a mock federation with contextData * const federation = createFederation<{ 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); * * // Check sent activities * console.log(federation.sentActivities); * ``` */ declare function createFederation<TContextData>(options?: { contextData?: TContextData; origin?: string; tracerProvider?: any; }): TestFederation<TContextData>; //#endregion //#region src/mq-tester.d.ts /** * Options for {@link testMessageQueue}. */ interface TestMessageQueueOptions { /** * Whether to test ordering key support. If `true`, tests will verify that * messages with the same ordering key are processed in order, while messages * with different ordering keys can be processed in parallel. * * Set this to `true` only if your message queue implementation supports * the `orderingKey` option. * * @default false */ readonly testOrderingKey?: boolean; } /** * Tests a {@link MessageQueue} implementation with a standard set of tests. * * This function runs tests for: * - `enqueue()`: Basic message enqueueing * - `enqueue()` with delay: Delayed message enqueueing * - `enqueueMany()`: Bulk message enqueueing * - `enqueueMany()` with delay: Delayed bulk message enqueueing * - Multiple listeners: Ensures messages are processed by only one listener * - Ordering key support (optional): Ensures messages with the same ordering * key are processed in order * * @example * ```typescript ignore * import { test } from "@fedify/fixture"; * import { testMessageQueue } from "@fedify/testing"; * import { MyMessageQueue } from "./my-mq.ts"; * * test("MyMessageQueue", () => * testMessageQueue( * () => new MyMessageQueue(), * async ({ mq1, mq2, controller }) => { * controller.abort(); * await mq1.close(); * await mq2.close(); * }, * { testOrderingKey: true }, // Enable ordering key tests * ) * ); * ``` * * @param getMessageQueue A factory function that creates a new message queue * instance. It should return a new instance each time * to ensure test isolation, but both instances should * share the same underlying storage/channel. * @param onFinally A cleanup function called after all tests complete. * It receives both message queue instances and the abort * controller used for the listeners. * @param options Optional configuration for the test suite. * @returns A promise that resolves when all tests pass. */ declare function testMessageQueue<MQ extends MessageQueue>(getMessageQueue: () => MQ | Promise<MQ>, onFinally: ({ mq1, mq2, controller }: { mq1: MQ; mq2: MQ; controller: AbortController; }) => Promise<void> | void, options?: TestMessageQueueOptions): Promise<void>; declare function waitFor(predicate: () => boolean, timeoutMs: number): Promise<void>; declare const getRandomKey: (prefix: string) => string; //#endregion export { type TestMessageQueueOptions, createContext, createFederation, createInboxContext, createOutboxContext, createRequestContext, getRandomKey, testMessageQueue, waitFor };