opinionated-machine 10.3.0 → 10.5.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.
- package/CHANGELOG.md +17 -0
- package/README.md +107 -3
- package/dist/lib/api-contracts/apiRouteBuilder.d.ts +1 -1
- package/dist/lib/api-contracts/apiRouteBuilder.js +36 -1
- package/dist/lib/api-contracts/apiRouteBuilder.js.map +1 -1
- package/dist/lib/sse/index.d.ts +1 -0
- package/dist/lib/sse/index.js +1 -0
- package/dist/lib/sse/index.js.map +1 -1
- package/dist/lib/sse/sseSendDiagnostics.d.ts +134 -0
- package/dist/lib/sse/sseSendDiagnostics.js +277 -0
- package/dist/lib/sse/sseSendDiagnostics.js.map +1 -0
- package/dist/lib/testing/apiSseEventValidation.d.ts +40 -0
- package/dist/lib/testing/apiSseEventValidation.js +78 -0
- package/dist/lib/testing/apiSseEventValidation.js.map +1 -0
- package/dist/lib/testing/apiSseHttpHelpers.d.ts +168 -0
- package/dist/lib/testing/apiSseHttpHelpers.js +214 -0
- package/dist/lib/testing/apiSseHttpHelpers.js.map +1 -0
- package/dist/lib/testing/apiSseInjectHelpers.d.ts +17 -2
- package/dist/lib/testing/apiSseInjectHelpers.js +227 -56
- package/dist/lib/testing/apiSseInjectHelpers.js.map +1 -1
- package/dist/lib/testing/apiSseTestTypes.d.ts +83 -3
- package/dist/lib/testing/index.d.ts +3 -2
- package/dist/lib/testing/index.js +1 -0
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/sseHttpClient.d.ts +52 -0
- package/dist/lib/testing/sseHttpClient.js +77 -0
- package/dist/lib/testing/sseHttpClient.js.map +1 -1
- package/dist/lib/testing/sseInjectClient.d.ts +17 -0
- package/dist/lib/testing/sseInjectClient.js +31 -0
- package/dist/lib/testing/sseInjectClient.js.map +1 -1
- package/dist/lib/testing/sseTestTypes.d.ts +36 -2
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ApiContract, ClientErrorHttpStatusCode, ExpandStatusRangeKey, HttpStatusCode, HttpStatusCodeRange, InformationalHttpStatusCode, RedirectionHttpStatusCode, ServerErrorHttpStatusCode, SuccessfulHttpStatusCode } from '@lokalise/api-contracts';
|
|
2
2
|
import type { InjectByApiContractParams } from '@lokalise/fastify-api-contracts';
|
|
3
3
|
import type { z } from 'zod';
|
|
4
|
-
import type {
|
|
4
|
+
import type { SSESendFailure } from '../sse/sseSendDiagnostics.ts';
|
|
5
|
+
import type { SSEResponse, SSEResponseHead } from './sseTestTypes.ts';
|
|
5
6
|
/**
|
|
6
7
|
* Request params for {@link injectApiSSE}, derived from a `defineApiContract` contract.
|
|
7
8
|
*
|
|
@@ -140,6 +141,13 @@ type HasApiSSEResponse<Contract extends ApiContract> = [ApiSSEEventSchemas<Contr
|
|
|
140
141
|
* contract declares; the result type below hides it behind {@link HasApiSSEResponse}.
|
|
141
142
|
*/
|
|
142
143
|
export type ApiSSEEventReader<Contract extends ApiContract> = () => Promise<ApiSSEEvent<Contract>[]>;
|
|
144
|
+
/**
|
|
145
|
+
* The callable form of {@link InjectApiSSEResult.stream}.
|
|
146
|
+
*
|
|
147
|
+
* Always a function, for the same reason {@link ApiSSEEventReader} is; the result type below
|
|
148
|
+
* hides it behind {@link HasApiSSEResponse}.
|
|
149
|
+
*/
|
|
150
|
+
export type ApiSSEStreamReader<Contract extends ApiContract> = (signal?: AbortSignal) => AsyncGenerator<ApiSSEEvent<Contract>, void, unknown>;
|
|
143
151
|
/**
|
|
144
152
|
* Result of an {@link injectApiSSE} call.
|
|
145
153
|
*
|
|
@@ -153,6 +161,42 @@ export type InjectApiSSEResult<Contract extends ApiContract> = {
|
|
|
153
161
|
* Parse the body with `parseSSEEvents()` — or use `events()` for typed events.
|
|
154
162
|
*/
|
|
155
163
|
closed: Promise<SSEResponse>;
|
|
164
|
+
/**
|
|
165
|
+
* Resolves as soon as the response head is on the wire — for a streaming response, when
|
|
166
|
+
* the handler calls `sse.start()`, long before it finishes.
|
|
167
|
+
*
|
|
168
|
+
* Lets a test assert the status and headers of a stream while the handler is still
|
|
169
|
+
* producing events, which `closed` cannot: it only settles once the stream ends.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* const { head, stream } = injectApiSSE(app, contract, { body })
|
|
174
|
+
* expect((await head).statusCode).toBe(200) // handler is still working
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
head: Promise<SSEResponseHead>;
|
|
178
|
+
/**
|
|
179
|
+
* The sends the route could not make while serving this request — a payload that failed the
|
|
180
|
+
* contract's schema for its event, say — recorded instead of being left in the server log.
|
|
181
|
+
*
|
|
182
|
+
* `events()` and `stream()` already fail on the failures that truncated the response, so
|
|
183
|
+
* this is for the ones they deliberately let pass: a handler that catches its own failed
|
|
184
|
+
* send and streams a fallback produced the response it meant to, and a test asserting on
|
|
185
|
+
* that response should not fail — but may still want to assert the send was attempted, and
|
|
186
|
+
* rejected, exactly once.
|
|
187
|
+
*
|
|
188
|
+
* Only meaningful once the response completed (`await closed`), and only for routes built
|
|
189
|
+
* with this package's `buildApiRoute`; anything else records nothing.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* const { closed, events, sendFailures } = injectApiSSE(app, contract, { body })
|
|
194
|
+
* await closed
|
|
195
|
+
* expect(await events()).toHaveLength(2) // the fallback stream is intact
|
|
196
|
+
* expect(sendFailures()).toMatchObject([{ eventName: 'issue', handled: true }])
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
sendFailures(): SSESendFailure[];
|
|
156
200
|
/**
|
|
157
201
|
* Awaits the response, asserts the status code matches, parses the body against the
|
|
158
202
|
* contract's JSON schema for that status, and returns the parsed object. Useful for
|
|
@@ -184,8 +228,9 @@ export type InjectApiSSEResult<Contract extends ApiContract> = {
|
|
|
184
228
|
* statuses yields the union of both.
|
|
185
229
|
*
|
|
186
230
|
* Throws if the response isn't an SSE stream (use `bodyForStatus` for the documented
|
|
187
|
-
* error statuses), if an event name isn't declared by the contract,
|
|
188
|
-
* doesn't match its schema
|
|
231
|
+
* error statuses), if an event name isn't declared by the contract, if an event payload
|
|
232
|
+
* doesn't match its schema, or if a send the route could not make — and did not recover
|
|
233
|
+
* from — ended the stream early (see `sendFailures()`).
|
|
189
234
|
*
|
|
190
235
|
* A contract that declares no SSE response at all types this as `never`, so calling it is
|
|
191
236
|
* a compile error rather than a guaranteed throw — reach for `injectByApiContract` there.
|
|
@@ -198,5 +243,40 @@ export type InjectApiSSEResult<Contract extends ApiContract> = {
|
|
|
198
243
|
* ```
|
|
199
244
|
*/
|
|
200
245
|
events: HasApiSSEResponse<Contract> extends true ? ApiSSEEventReader<Contract> : never;
|
|
246
|
+
/**
|
|
247
|
+
* Yields the contract's events as the handler writes them, rather than after the response
|
|
248
|
+
* completes — the same discriminated union and the same validation `events()` provides.
|
|
249
|
+
*
|
|
250
|
+
* This is what `events()` cannot show: that event N reached the client while the handler
|
|
251
|
+
* was still working. The request is injected with Fastify's `payloadAsStream`, so no
|
|
252
|
+
* `app.listen()`, base URL or manual connection cleanup is involved.
|
|
253
|
+
*
|
|
254
|
+
* Events are buffered from the moment the request is injected, so a generator started
|
|
255
|
+
* late still yields the stream from its first event, and the handler is never blocked by
|
|
256
|
+
* a slow (or absent) consumer. Breaking out of the loop leaves the rest of the stream
|
|
257
|
+
* readable through `closed` / `events()`; calling `stream()` again replays it from the
|
|
258
|
+
* start.
|
|
259
|
+
*
|
|
260
|
+
* Throws — before yielding anything — if the response isn't an SSE stream, and rethrows
|
|
261
|
+
* the same event-level validation errors `events()` does. If the stream was cut short by a
|
|
262
|
+
* send the route could not make (a payload that didn't match the contract's schema for its
|
|
263
|
+
* event, and that nothing caught), the generator ends by throwing an error naming that
|
|
264
|
+
* event and its Zod issues, instead of leaving the test to explain a missing event on its
|
|
265
|
+
* own. A failure the route did catch and recovered from is left to `sendFailures()`.
|
|
266
|
+
*
|
|
267
|
+
* A contract that declares no SSE response types this as `never`, exactly as `events()`.
|
|
268
|
+
*
|
|
269
|
+
* @param signal - Optional `AbortSignal` to stop the generator early
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```typescript
|
|
273
|
+
* const { stream } = injectApiSSE(app, lqaSegmentContract, { body: { segment } })
|
|
274
|
+
*
|
|
275
|
+
* for await (const event of stream()) {
|
|
276
|
+
* if (event.event === 'issue') expect(handlerFinished).toBe(false) // progressive delivery
|
|
277
|
+
* }
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
stream: HasApiSSEResponse<Contract> extends true ? ApiSSEStreamReader<Contract> : never;
|
|
201
281
|
};
|
|
202
282
|
export {};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
export { ApiSSEHttpClient, type ConnectApiSSEParams, type ConnectApiSSEResult, type ConnectApiSSEWithSpyOptions, connectApiSSE, } from './apiSseHttpHelpers.js';
|
|
1
2
|
export { injectApiSSE } from './apiSseInjectHelpers.js';
|
|
2
|
-
export type { ApiDeclaredResponseBody, ApiDeclaredResponseStatus, ApiSSEEvent, ApiSSEEventReader, InjectApiSSEParams, InjectApiSSEResult, } from './apiSseTestTypes.js';
|
|
3
|
+
export type { ApiDeclaredResponseBody, ApiDeclaredResponseStatus, ApiSSEEvent, ApiSSEEventReader, ApiSSEStreamReader, InjectApiSSEParams, InjectApiSSEResult, } from './apiSseTestTypes.js';
|
|
3
4
|
export { type HasSessionSpy, SSEHttpClient, type SSEHttpConnectOptions, type SSEHttpConnectResult, type SSEHttpConnectWithSessionSpyOptions, type SSEHttpConnectWithSpyOptions, type SSEHttpMethod, } from './sseHttpClient.js';
|
|
4
5
|
export { SSEInjectClient, SSEInjectConnection } from './sseInjectClient.js';
|
|
5
6
|
export { injectPayloadSSE, injectSSE } from './sseInjectHelpers.js';
|
|
6
7
|
export { type CreateSSESessionSpyResult, createSSESessionSpy, type SSESessionSpyHooks, type SSESessionSpyRouteOptions, } from './sseSessionSpyFactory.js';
|
|
7
8
|
export { SSETestServer } from './sseTestServer.js';
|
|
8
|
-
export type { InjectPayloadSSEOptions, InjectSSEOptions, InjectSSEResult, SSEConnectOptions, SSEInjectMethod, SSEResponse, SSETestConnection, } from './sseTestTypes.js';
|
|
9
|
+
export type { InjectPayloadSSEOptions, InjectSSEOptions, InjectSSEResult, SSEConnectOptions, SSEInjectMethod, SSEResponse, SSEResponseHead, SSETestConnection, } from './sseTestTypes.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAIhB,aAAa,GACd,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAUvD,OAAO,EAEL,aAAa,GAMd,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAEL,mBAAmB,GAGpB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { ApiContract } from '@lokalise/api-contracts';
|
|
1
2
|
import type { SSESession } from '../routes/fastifyRouteTypes.ts';
|
|
2
3
|
import type { SpiedSSESession, SSESessionSpy } from '../sse/SSESessionSpy.ts';
|
|
3
4
|
import { type ParsedSSEEvent } from '../sse/sseParser.ts';
|
|
5
|
+
import type { ApiSSEEvent } from './apiSseTestTypes.ts';
|
|
4
6
|
/**
|
|
5
7
|
* Interface for objects that have a sessionSpy (e.g., SSE controllers in test mode).
|
|
6
8
|
*/
|
|
@@ -168,6 +170,11 @@ export declare class SSEHttpClient {
|
|
|
168
170
|
private buffer;
|
|
169
171
|
private closed;
|
|
170
172
|
private constructor();
|
|
173
|
+
/**
|
|
174
|
+
* Whether the stream is over — the server ended it, or {@link SSEHttpClient.close} was
|
|
175
|
+
* called. A client that reports `true` will yield no further events.
|
|
176
|
+
*/
|
|
177
|
+
get isClosed(): boolean;
|
|
171
178
|
/**
|
|
172
179
|
* Lazily acquire the stream reader, locking the response body on first use.
|
|
173
180
|
*
|
|
@@ -289,6 +296,51 @@ export declare class SSEHttpClient {
|
|
|
289
296
|
* ```
|
|
290
297
|
*/
|
|
291
298
|
collectEvents(countOrPredicate: number | ((event: ParsedSSEEvent) => boolean), timeout?: number): Promise<ParsedSSEEvent[]>;
|
|
299
|
+
/**
|
|
300
|
+
* Async generator that yields events typed and validated against an `ApiContract`.
|
|
301
|
+
*
|
|
302
|
+
* The contract-aware counterpart of {@link SSEHttpClient.events}: each event is JSON-parsed,
|
|
303
|
+
* checked against the contract's `sseResponse` / `sseBody` schema for its name, and typed as
|
|
304
|
+
* a member of the contract's discriminated union — the same events `injectApiSSE().events()`
|
|
305
|
+
* returns, so an assertion reads the same on either path.
|
|
306
|
+
*
|
|
307
|
+
* {@link connectApiSSE} is usually the better entry point: it also resolves the path, method
|
|
308
|
+
* and body from the contract. Reach for this method when the connection already exists — one
|
|
309
|
+
* opened with `awaitServerConnection`, say.
|
|
310
|
+
*
|
|
311
|
+
* @param contract - Contract built with `defineApiContract`, declaring the SSE events
|
|
312
|
+
* @param signal - Optional `AbortSignal` to stop the generator early
|
|
313
|
+
*
|
|
314
|
+
* @throws if the contract declares no SSE response, if an event name it doesn't declare
|
|
315
|
+
* arrives, or if a payload fails its schema
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* for await (const event of client.apiEvents(lqaSegmentContract)) {
|
|
320
|
+
* if (event.event === 'issue') expect(event.data.severity).toBe('minor')
|
|
321
|
+
* }
|
|
322
|
+
* ```
|
|
323
|
+
*/
|
|
324
|
+
apiEvents<Contract extends ApiContract>(contract: Contract, signal?: AbortSignal): AsyncGenerator<ApiSSEEvent<Contract>, void, unknown>;
|
|
325
|
+
/**
|
|
326
|
+
* Collect events typed and validated against an `ApiContract`, until a count is reached or
|
|
327
|
+
* a predicate matches.
|
|
328
|
+
*
|
|
329
|
+
* The contract-aware counterpart of {@link SSEHttpClient.collectEvents}; the predicate sees
|
|
330
|
+
* the typed event, so it can narrow on `event.event` and read `event.data` without a cast.
|
|
331
|
+
*
|
|
332
|
+
* @param contract - Contract built with `defineApiContract`, declaring the SSE events
|
|
333
|
+
* @param countOrPredicate - Number of events to collect, or a predicate that ends collection
|
|
334
|
+
* (the matching event is included)
|
|
335
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 5000)
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```typescript
|
|
339
|
+
* const events = await client.collectApiEvents(contract, (e) => e.event === 'review')
|
|
340
|
+
* expect(events.at(-1)?.data).toMatchObject({ score: 5 })
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
collectApiEvents<Contract extends ApiContract>(contract: Contract, countOrPredicate: number | ((event: ApiSSEEvent<Contract>) => boolean), timeout?: number): Promise<ApiSSEEvent<Contract>[]>;
|
|
292
344
|
/**
|
|
293
345
|
* Close the connection from the client side.
|
|
294
346
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stringify } from 'fast-querystring';
|
|
2
2
|
import { parseSSEBuffer } from "../sse/sseParser.js";
|
|
3
|
+
import { resolveApiSseSchemas, validateApiSseEvent } from "./apiSseEventValidation.js";
|
|
3
4
|
/**
|
|
4
5
|
* Whether `fetch()` can send this value as-is. Everything else is JSON-stringified,
|
|
5
6
|
* so binary and form payloads are not silently mangled into `{}` or `{"0":1}`.
|
|
@@ -133,6 +134,13 @@ export class SSEHttpClient {
|
|
|
133
134
|
this.abortController = abortController;
|
|
134
135
|
this.responseBody = response.body;
|
|
135
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether the stream is over — the server ended it, or {@link SSEHttpClient.close} was
|
|
139
|
+
* called. A client that reports `true` will yield no further events.
|
|
140
|
+
*/
|
|
141
|
+
get isClosed() {
|
|
142
|
+
return this.closed;
|
|
143
|
+
}
|
|
136
144
|
/**
|
|
137
145
|
* Lazily acquire the stream reader, locking the response body on first use.
|
|
138
146
|
*
|
|
@@ -351,6 +359,75 @@ export class SSEHttpClient {
|
|
|
351
359
|
}
|
|
352
360
|
return collected;
|
|
353
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Async generator that yields events typed and validated against an `ApiContract`.
|
|
364
|
+
*
|
|
365
|
+
* The contract-aware counterpart of {@link SSEHttpClient.events}: each event is JSON-parsed,
|
|
366
|
+
* checked against the contract's `sseResponse` / `sseBody` schema for its name, and typed as
|
|
367
|
+
* a member of the contract's discriminated union — the same events `injectApiSSE().events()`
|
|
368
|
+
* returns, so an assertion reads the same on either path.
|
|
369
|
+
*
|
|
370
|
+
* {@link connectApiSSE} is usually the better entry point: it also resolves the path, method
|
|
371
|
+
* and body from the contract. Reach for this method when the connection already exists — one
|
|
372
|
+
* opened with `awaitServerConnection`, say.
|
|
373
|
+
*
|
|
374
|
+
* @param contract - Contract built with `defineApiContract`, declaring the SSE events
|
|
375
|
+
* @param signal - Optional `AbortSignal` to stop the generator early
|
|
376
|
+
*
|
|
377
|
+
* @throws if the contract declares no SSE response, if an event name it doesn't declare
|
|
378
|
+
* arrives, or if a payload fails its schema
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* for await (const event of client.apiEvents(lqaSegmentContract)) {
|
|
383
|
+
* if (event.event === 'issue') expect(event.data.severity).toBe('minor')
|
|
384
|
+
* }
|
|
385
|
+
* ```
|
|
386
|
+
*/
|
|
387
|
+
async *apiEvents(contract, signal) {
|
|
388
|
+
const schemaByEventName = resolveApiSseSchemas(contract, 'apiEvents()');
|
|
389
|
+
for await (const event of this.events(signal)) {
|
|
390
|
+
yield validateApiSseEvent(schemaByEventName, event, 'apiEvents()');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Collect events typed and validated against an `ApiContract`, until a count is reached or
|
|
395
|
+
* a predicate matches.
|
|
396
|
+
*
|
|
397
|
+
* The contract-aware counterpart of {@link SSEHttpClient.collectEvents}; the predicate sees
|
|
398
|
+
* the typed event, so it can narrow on `event.event` and read `event.data` without a cast.
|
|
399
|
+
*
|
|
400
|
+
* @param contract - Contract built with `defineApiContract`, declaring the SSE events
|
|
401
|
+
* @param countOrPredicate - Number of events to collect, or a predicate that ends collection
|
|
402
|
+
* (the matching event is included)
|
|
403
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 5000)
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```typescript
|
|
407
|
+
* const events = await client.collectApiEvents(contract, (e) => e.event === 'review')
|
|
408
|
+
* expect(events.at(-1)?.data).toMatchObject({ score: 5 })
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
async collectApiEvents(contract, countOrPredicate, timeout) {
|
|
412
|
+
const schemaByEventName = resolveApiSseSchemas(contract, 'collectApiEvents()');
|
|
413
|
+
// A predicate sees every event as it arrives, and the collected ones are returned typed:
|
|
414
|
+
// memoizing keeps each event validated once instead of twice, and — more importantly —
|
|
415
|
+
// hands the caller the very object its predicate inspected.
|
|
416
|
+
const validated = new Map();
|
|
417
|
+
const validate = (event) => {
|
|
418
|
+
const cached = validated.get(event);
|
|
419
|
+
if (cached) {
|
|
420
|
+
return cached;
|
|
421
|
+
}
|
|
422
|
+
const parsed = validateApiSseEvent(schemaByEventName, event, 'collectApiEvents()');
|
|
423
|
+
validated.set(event, parsed);
|
|
424
|
+
return parsed;
|
|
425
|
+
};
|
|
426
|
+
const collected = await this.collectEvents(typeof countOrPredicate === 'number'
|
|
427
|
+
? countOrPredicate
|
|
428
|
+
: (event) => countOrPredicate(validate(event)), timeout);
|
|
429
|
+
return collected.map(validate);
|
|
430
|
+
}
|
|
354
431
|
/**
|
|
355
432
|
* Close the connection from the client side.
|
|
356
433
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sseHttpClient.js","sourceRoot":"","sources":["../../../lib/testing/sseHttpClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sseHttpClient.js","sourceRoot":"","sources":["../../../lib/testing/sseHttpClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,23 @@ export declare class SSEInjectConnection implements SSETestConnection {
|
|
|
54
54
|
* Get the response headers.
|
|
55
55
|
*/
|
|
56
56
|
getHeaders(): Record<string, string | string[] | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* Get the raw response body as a string.
|
|
59
|
+
*
|
|
60
|
+
* For a streaming response this is the raw `text/event-stream` payload the
|
|
61
|
+
* events were parsed from. For a route that answered with a status code
|
|
62
|
+
* before streaming started (auth failure, validation error, integration
|
|
63
|
+
* unavailable) it is that non-streaming body - typically JSON.
|
|
64
|
+
*/
|
|
65
|
+
getBody(): string;
|
|
66
|
+
/**
|
|
67
|
+
* Parse the raw response body as JSON, mirroring Fastify's inject `json()`.
|
|
68
|
+
*
|
|
69
|
+
* @throws if the body is empty or not valid JSON. A `text/event-stream` body
|
|
70
|
+
* is not JSON, so this only makes sense for responses emitted before
|
|
71
|
+
* streaming started.
|
|
72
|
+
*/
|
|
73
|
+
json<T = unknown>(): T;
|
|
57
74
|
}
|
|
58
75
|
/**
|
|
59
76
|
* SSE client using Fastify's inject() for testing SSE endpoints.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseSSEEvents } from "../sse/sseParser.js";
|
|
2
|
+
import { truncateBody } from "./sseInjectShared.js";
|
|
2
3
|
/**
|
|
3
4
|
* SSE connection object returned by SSEInjectClient.
|
|
4
5
|
*
|
|
@@ -80,6 +81,36 @@ export class SSEInjectConnection {
|
|
|
80
81
|
getHeaders() {
|
|
81
82
|
return this.response.headers;
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Get the raw response body as a string.
|
|
86
|
+
*
|
|
87
|
+
* For a streaming response this is the raw `text/event-stream` payload the
|
|
88
|
+
* events were parsed from. For a route that answered with a status code
|
|
89
|
+
* before streaming started (auth failure, validation error, integration
|
|
90
|
+
* unavailable) it is that non-streaming body - typically JSON.
|
|
91
|
+
*/
|
|
92
|
+
getBody() {
|
|
93
|
+
return this.response.body;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse the raw response body as JSON, mirroring Fastify's inject `json()`.
|
|
97
|
+
*
|
|
98
|
+
* @throws if the body is empty or not valid JSON. A `text/event-stream` body
|
|
99
|
+
* is not JSON, so this only makes sense for responses emitted before
|
|
100
|
+
* streaming started.
|
|
101
|
+
*/
|
|
102
|
+
json() {
|
|
103
|
+
const { body } = this.response;
|
|
104
|
+
if (!body) {
|
|
105
|
+
throw new Error(`json() — response body is empty (status ${this.response.statusCode})`);
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(body);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
throw new Error(`json() — body is not valid JSON: ${err.message}; body: ${truncateBody(body)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
83
114
|
}
|
|
84
115
|
/**
|
|
85
116
|
* SSE client using Fastify's inject() for testing SSE endpoints.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sseInjectClient.js","sourceRoot":"","sources":["../../../lib/testing/sseInjectClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,cAAc,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"sseInjectClient.js","sourceRoot":"","sources":["../../../lib/testing/sseInjectClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,6 +60,34 @@ export interface SSETestConnection {
|
|
|
60
60
|
* Get response headers.
|
|
61
61
|
*/
|
|
62
62
|
getHeaders(): Record<string, string | string[] | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Get the raw response body as a string.
|
|
65
|
+
*
|
|
66
|
+
* For a successful SSE response this is the raw `text/event-stream` payload
|
|
67
|
+
* (already parsed into events, available via `getReceivedEvents()`). It is
|
|
68
|
+
* most useful when the route answered with a status code before streaming
|
|
69
|
+
* started - an auth failure, a validation error, an unavailable integration -
|
|
70
|
+
* and responded with a JSON body instead of events.
|
|
71
|
+
*/
|
|
72
|
+
getBody(): string;
|
|
73
|
+
/**
|
|
74
|
+
* Parse the raw response body as JSON.
|
|
75
|
+
*
|
|
76
|
+
* Mirrors Fastify's own inject response `json()`. Intended for non-streaming
|
|
77
|
+
* responses emitted before streaming starts; calling it on an actual SSE
|
|
78
|
+
* stream body throws, since `text/event-stream` is not JSON.
|
|
79
|
+
*
|
|
80
|
+
* @throws if the body is empty or not valid JSON (the message includes a
|
|
81
|
+
* truncated body snippet).
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const conn = await client.connect('/api/stream')
|
|
86
|
+
* expect(conn.getStatusCode()).toBe(503)
|
|
87
|
+
* expect(conn.json()).toMatchObject({ errorCode: 'INTEGRATION_NOT_AVAILABLE' })
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
json<T = unknown>(): T;
|
|
63
91
|
}
|
|
64
92
|
/**
|
|
65
93
|
* Options for establishing an SSE connection.
|
|
@@ -88,11 +116,17 @@ export type InjectPayloadSSEOptions<Contract extends AnySSEContractDefinition> =
|
|
|
88
116
|
body: InferOptionalSchema<Contract['requestBodySchema'], never>;
|
|
89
117
|
};
|
|
90
118
|
/**
|
|
91
|
-
* SSE response
|
|
119
|
+
* Status code and headers of an SSE response, available as soon as they are on the wire —
|
|
120
|
+
* before the handler has finished streaming.
|
|
92
121
|
*/
|
|
93
|
-
export type
|
|
122
|
+
export type SSEResponseHead = {
|
|
94
123
|
statusCode: number;
|
|
95
124
|
headers: Record<string, string | string[] | undefined>;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* SSE response data.
|
|
128
|
+
*/
|
|
129
|
+
export type SSEResponse = SSEResponseHead & {
|
|
96
130
|
body: string;
|
|
97
131
|
};
|
|
98
132
|
/**
|