@celerity-sdk/testing 0.8.0

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.
@@ -0,0 +1,323 @@
1
+ import { Type, Provider, InjectionToken, HttpRequest, HttpResponse, WebSocketMessage, ConsumerEventInput, EventResult, ScheduleEventInput } from '@celerity-sdk/types';
2
+ import { TestingApplication, Container, HandlerRegistry } from '@celerity-sdk/core';
3
+ export { mockConsumerEvent, mockRequest, mockScheduleEvent, mockWebSocketMessage } from '@celerity-sdk/core';
4
+
5
+ /**
6
+ * Parsed resource token info extracted from a Symbol description
7
+ * like "celerity:datastore:usersDatastore".
8
+ */
9
+ type ResourceTokenInfo = {
10
+ token: symbol;
11
+ type: string;
12
+ name: string;
13
+ };
14
+ /**
15
+ * Walks the module graph for the given root module and extracts all resource
16
+ * layer tokens from provider, controller, and guard constructor parameters.
17
+ *
18
+ * This discovers which resources the module (and its imports) depend on
19
+ * without instantiating anything.
20
+ */
21
+ declare function discoverResourceTokens(rootModule: Type): ResourceTokenInfo[];
22
+
23
+ type MockFn = (...args: any[]) => any;
24
+ type MockFnCreator = () => MockFn;
25
+ /**
26
+ * Creates a mock object for a resource type with all interface methods stubbed.
27
+ * Returns null for resource types that cannot be meaningfully mocked (e.g. sqlDatabase).
28
+ */
29
+ declare function createResourceMock(resourceType: string, mockFn?: MockFnCreator): Record<string, MockFn> | null;
30
+ /**
31
+ * Creates mock objects for all discovered resource tokens.
32
+ * Returns a map of token → mock object. Tokens for unmockable resource types
33
+ * (like sqlDatabase) are omitted.
34
+ */
35
+ declare function createMocksForTokens(tokens: ResourceTokenInfo[], mockFn?: MockFnCreator): Map<symbol, Record<string, MockFn>>;
36
+
37
+ type CreateTestAppOptions = {
38
+ /** The module under test. Its providers, controllers, guards, and imports are bootstrapped. */
39
+ module: Type;
40
+ /** When true, creates real resource clients from env vars. When false (default), creates mocks. */
41
+ integration?: boolean;
42
+ /** Additional providers to register in the test module. */
43
+ providers?: Array<Type | (Provider & {
44
+ provide: InjectionToken;
45
+ })>;
46
+ /** Additional controllers to register. */
47
+ controllers?: Type[];
48
+ /** Additional guards to register. */
49
+ guards?: Type[];
50
+ /** Explicit overrides registered last — takes precedence over auto-discovery. */
51
+ overrides?: Record<symbol, unknown>;
52
+ /** Path to the blueprint file. Auto-detected if omitted. */
53
+ blueprintPath?: string;
54
+ };
55
+ /**
56
+ * TestApp wraps TestingApplication with mock access and lifecycle management.
57
+ */
58
+ declare class TestApp {
59
+ private mocks;
60
+ private closeables;
61
+ private inner;
62
+ constructor(inner: TestingApplication, mocks: Map<symbol, Record<string, MockFn>>, closeables: Array<{
63
+ close?(): void | Promise<void>;
64
+ }>);
65
+ injectHttp(request: HttpRequest): Promise<HttpResponse>;
66
+ injectWebSocket(route: string, message: WebSocketMessage): Promise<void>;
67
+ injectConsumer(handlerTag: string, event: ConsumerEventInput): Promise<EventResult>;
68
+ injectSchedule(handlerTag: string, event: ScheduleEventInput): Promise<EventResult>;
69
+ injectCustom(name: string, payload?: unknown): Promise<unknown>;
70
+ getContainer(): Container;
71
+ getRegistry(): HandlerRegistry;
72
+ /** Retrieve the auto-generated mock for a resource token. */
73
+ getMock<T>(token: symbol): T;
74
+ /** Get the mock Datastore for a named resource (e.g., "usersDatastore"). */
75
+ getDatastoreMock(name: string): Record<string, MockFn>;
76
+ /** Get the mock Topic for a named resource. */
77
+ getTopicMock(name: string): Record<string, MockFn>;
78
+ /** Get the mock Queue for a named resource. */
79
+ getQueueMock(name: string): Record<string, MockFn>;
80
+ /** Get the mock Cache for a named resource. */
81
+ getCacheMock(name: string): Record<string, MockFn>;
82
+ /** Get the mock Bucket for a named resource. */
83
+ getBucketMock(name: string): Record<string, MockFn>;
84
+ /** Get the mock Config namespace for a named resource. */
85
+ getConfigMock(name: string): Record<string, MockFn>;
86
+ /** Close all real resource clients (integration mode). No-op in unit mode. */
87
+ close(): Promise<void>;
88
+ }
89
+ /**
90
+ * Create a test application for unit or integration testing.
91
+ *
92
+ * - `integration: false` (default): Auto-discovers resource dependencies from the
93
+ * module graph and creates mock objects for each. Access mocks via `app.getDatastoreMock()`, etc.
94
+ *
95
+ * - `integration: true`: Creates real resource clients from env vars provided by
96
+ * `celerity dev test`. Physical resource names are resolved from the blueprint.
97
+ *
98
+ * In both modes, custom providers/controllers/guards can be added, and explicit
99
+ * overrides take precedence over auto-discovered resources.
100
+ */
101
+ declare function createTestApp(options: CreateTestAppOptions): Promise<TestApp>;
102
+
103
+ type GenerateTestTokenOptions = {
104
+ /** Subject claim (required by the dev auth server). Default: "test-user" */
105
+ sub?: string;
106
+ /** Arbitrary claims spread into the JWT payload (roles, permissions, org_id, etc.). */
107
+ claims?: Record<string, unknown>;
108
+ /** Token lifetime as a Go-style duration string. Default: "1h" */
109
+ expiresIn?: string;
110
+ };
111
+ /**
112
+ * Generate an RS256-signed JWT by calling the local dev auth server.
113
+ *
114
+ * The dev auth server runs as a sidecar in `celerity dev test` / `celerity dev run`
115
+ * and is accessible via the `CELERITY_DEV_AUTH_BASE_URL` env var.
116
+ *
117
+ * @returns The signed access token string.
118
+ * @throws If the dev auth server is unreachable or returns an error.
119
+ */
120
+ declare function generateTestToken(options?: GenerateTestTokenOptions): Promise<string>;
121
+
122
+ /**
123
+ * Asserting HTTP client for API tests against a running application.
124
+ * Reads `CELERITY_TEST_BASE_URL` (default http://localhost:8081).
125
+ */
126
+ type TestResponse<TBody = unknown> = {
127
+ status: number;
128
+ headers: Headers;
129
+ body: TBody;
130
+ text: string;
131
+ };
132
+ type ExpectFn = (body: unknown) => void;
133
+ declare class TestRequest<TBody = unknown> {
134
+ private baseUrl;
135
+ private method;
136
+ private path;
137
+ private _headers;
138
+ private _body?;
139
+ private _expectations;
140
+ constructor(baseUrl: string, method: string, path: string);
141
+ /** Set an authorization bearer token. */
142
+ auth(token: string): this;
143
+ /** Set a request header. */
144
+ set(key: string, value: string): this;
145
+ /** Set the request JSON body. */
146
+ send(body: unknown): this;
147
+ /** Add an expectation. Overloaded: status code, body object/fn, or header. */
148
+ expect(statusOrBodyOrFn: number | unknown | ExpectFn): this;
149
+ expect(header: string, value: string | RegExp): this;
150
+ /** Execute the request and run all expectations. Returns the response. */
151
+ end(): Promise<TestResponse<TBody>>;
152
+ private assertExpectation;
153
+ private assertHeader;
154
+ /** Implements PromiseLike so the request chain can be awaited directly. */
155
+ then<T>(// intentional thenable for fluent await syntax
156
+ resolve: (value: TestResponse<TBody>) => T | PromiseLike<T>, reject?: (reason: unknown) => T | PromiseLike<T>): Promise<T>;
157
+ }
158
+ declare class TestHttpClient {
159
+ private baseUrl;
160
+ constructor(baseUrl: string);
161
+ get<TBody = Record<string, unknown>>(path: string): TestRequest<TBody>;
162
+ post<TBody = Record<string, unknown>>(path: string): TestRequest<TBody>;
163
+ put<TBody = Record<string, unknown>>(path: string): TestRequest<TBody>;
164
+ patch<TBody = Record<string, unknown>>(path: string): TestRequest<TBody>;
165
+ delete<TBody = Record<string, unknown>>(path: string): TestRequest<TBody>;
166
+ }
167
+ /**
168
+ * Create a test HTTP client for API tests.
169
+ * Reads `CELERITY_TEST_BASE_URL` env var (default: http://localhost:8081).
170
+ */
171
+ declare function createTestClient(options?: {
172
+ baseUrl?: string;
173
+ }): TestHttpClient;
174
+
175
+ /**
176
+ * Polls a predicate function until it returns true or the timeout expires.
177
+ *
178
+ * @param predicate - Function that returns true when the condition is met.
179
+ * @param options - Timeout (ms, default 5000) and poll interval (ms, default 100).
180
+ */
181
+ declare function waitFor(predicate: () => Promise<boolean> | boolean, options?: {
182
+ timeout?: number;
183
+ interval?: number;
184
+ }): Promise<void>;
185
+
186
+ /**
187
+ * Sequential WebSocket test client for Celerity applications.
188
+ *
189
+ * Wraps `@celerity-sdk/ws-client` to provide an `await`-based message flow.
190
+ * All runtime protocol concerns (heartbeat, capabilities negotiation, ack,
191
+ * dedup) are handled by the underlying client — only application messages
192
+ * surface to tests.
193
+ *
194
+ * Configuration is derived from the blueprint and `celerity dev test` env vars.
195
+ */
196
+
197
+ type Unsubscribe = () => void;
198
+ /** Structural type for the subset of CelerityWsClient used by TestWsClient. */
199
+ type WsClient = {
200
+ on(event: string, handler: (...args: never[]) => void): Unsubscribe;
201
+ connect(): Promise<void>;
202
+ send(route: string, data: unknown): string;
203
+ disconnect(): Promise<void>;
204
+ destroy(): void;
205
+ state: string;
206
+ };
207
+ type CreateTestWsClientOptions = {
208
+ /** Override the WebSocket URL. Derived from CELERITY_TEST_BASE_URL + blueprint basePath if omitted. */
209
+ url?: string;
210
+ /** Token for auth. String = use directly. Object = options for generateTestToken(). Omit to skip auth entirely. */
211
+ token?: string | GenerateTestTokenOptions | null;
212
+ /** Path to the blueprint file (auto-detected if omitted). */
213
+ blueprintPath?: string;
214
+ /** Additional config passed through to createWsClient. */
215
+ clientConfig?: Record<string, unknown>;
216
+ };
217
+ type ReceivedMessage = {
218
+ route: string;
219
+ data: unknown;
220
+ };
221
+ /**
222
+ * Create a sequential WebSocket test client.
223
+ *
224
+ * Config is derived from the blueprint (base path, route key, auth strategy)
225
+ * and `CELERITY_TEST_BASE_URL`. Pass `token: null` to skip authentication
226
+ * (useful for testing unauthenticated rejection).
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * const ws = await createTestWsClient();
231
+ * await ws.connect();
232
+ *
233
+ * ws.send("notifications", { action: "subscribe", channels: ["alerts"] });
234
+ * const msg = await ws.nextMessage();
235
+ * expect(msg.data).toEqual({ subscribed: ["alerts"] });
236
+ *
237
+ * await ws.disconnect();
238
+ * ```
239
+ *
240
+ * @example Skip auth for testing unauthenticated scenarios:
241
+ * ```typescript
242
+ * const ws = await createTestWsClient({ token: null });
243
+ * ```
244
+ */
245
+ declare function createTestWsClient(options?: CreateTestWsClientOptions): Promise<TestWsClient>;
246
+ /**
247
+ * Test-oriented WebSocket client with sequential `await`-based message flow.
248
+ *
249
+ * The underlying `CelerityWsClient` handles all runtime protocol messages
250
+ * (heartbeat, capabilities, auth, ack, dedup) transparently.
251
+ * Only application-level messages are surfaced via `nextMessage()`.
252
+ */
253
+ declare class TestWsClient {
254
+ private inner;
255
+ private messageBuffer;
256
+ private waiters;
257
+ private cleanups;
258
+ private connectionError;
259
+ constructor(inner: WsClient);
260
+ /**
261
+ * Connect and complete the full handshake (including auth if configured).
262
+ *
263
+ * After connect resolves, `nextMessage()` will receive application messages.
264
+ * If auth fails or connection is rejected, the promise rejects with the error.
265
+ */
266
+ connect(): Promise<void>;
267
+ /** Send a routed application message. Returns the message ID. */
268
+ send(route: string, data: unknown): string;
269
+ /**
270
+ * Wait for the next application message from the server.
271
+ *
272
+ * If a message is already buffered, resolves immediately.
273
+ * Otherwise blocks until a message arrives, the connection closes, or timeout expires.
274
+ *
275
+ * @param timeout - Maximum wait time in ms. Default: 5000.
276
+ */
277
+ nextMessage(timeout?: number): Promise<ReceivedMessage>;
278
+ /** Disconnect and clean up all listeners and pending waiters. */
279
+ disconnect(): Promise<void>;
280
+ /** Force destroy without waiting for close handshake. */
281
+ destroy(): void;
282
+ /** Current connection state. */
283
+ get state(): string;
284
+ /** Access the underlying CelerityWsClient for advanced usage. */
285
+ get raw(): WsClient;
286
+ private cleanup;
287
+ private rejectAllWaiters;
288
+ }
289
+
290
+ /**
291
+ * Physical resource info extracted from the blueprint.
292
+ */
293
+ type BlueprintResource = {
294
+ resourceId: string;
295
+ type: string;
296
+ physicalName: string;
297
+ };
298
+ /**
299
+ * Parse the app blueprint and extract resource definitions with their
300
+ * physical names (spec.name). This maps resource IDs used in decorators
301
+ * (e.g., "usersDatastore") to the actual infrastructure names (e.g., "users").
302
+ */
303
+ declare function loadBlueprintResources(blueprintPath?: string): Map<string, BlueprintResource>;
304
+ /**
305
+ * WebSocket configuration extracted from the blueprint's API resource.
306
+ */
307
+ type WebSocketConfig = {
308
+ /** The base path for WebSocket connections (e.g., "/ws"). */
309
+ basePath: string;
310
+ /** The route key used for message routing (e.g., "action"). */
311
+ routeKey: string;
312
+ /** The auth strategy: "authMessage" or "connect". */
313
+ authStrategy: "authMessage" | "connect";
314
+ };
315
+ /**
316
+ * Extract WebSocket configuration from the first celerity/api resource
317
+ * in the blueprint that has WebSocket protocol configured.
318
+ *
319
+ * Returns null if no WebSocket configuration is found.
320
+ */
321
+ declare function loadWebSocketConfig(blueprintPath?: string): WebSocketConfig | null;
322
+
323
+ export { type BlueprintResource, type CreateTestAppOptions, type CreateTestWsClientOptions, type GenerateTestTokenOptions, type MockFn, type MockFnCreator, type ReceivedMessage, type ResourceTokenInfo, TestApp, TestHttpClient, TestRequest, type TestResponse, TestWsClient, type WebSocketConfig, createMocksForTokens, createResourceMock, createTestApp, createTestClient, createTestWsClient, discoverResourceTokens, generateTestToken, loadBlueprintResources, loadWebSocketConfig, waitFor };