Skip to content

Commit 7431b5f

Browse files
committed
test(maintenance): expose ResponseStream object to simplify testing in event handler
1 parent d7303e8 commit 7431b5f

File tree

5 files changed

+73
-72
lines changed

5 files changed

+73
-72
lines changed

packages/event-handler/src/rest/utils.ts

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ import type {
1818
HandlerResponse,
1919
HttpMethod,
2020
HttpStatusCode,
21+
ResponseStream as IResponseStream,
2122
Middleware,
2223
Path,
23-
ResponseStream,
2424
ValidationResult,
2525
} from '../types/rest.js';
2626
import {
@@ -306,19 +306,44 @@ export const resolvePrefixedPath = (path: Path, prefix?: Path): Path => {
306306
return `${prefix}${path}`.replace(/\/$/, '') as Path;
307307
};
308308

309-
export const HttpResponseStream =
310-
globalThis.awslambda?.HttpResponseStream ??
311-
class LocalHttpResponseStream extends Writable {
312-
#contentType: string | undefined;
309+
export class ResponseStream extends Writable implements IResponseStream {
310+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: This is how the Lambda RIC implements it
311+
#contentType: string | undefined;
312+
readonly #chunks: Buffer[] = [];
313+
public _onBeforeFirstWrite?: (
314+
write: (data: Uint8Array | string) => void
315+
) => void;
316+
#firstWrite = true;
317+
318+
setContentType(contentType: string) {
319+
this.#contentType = contentType;
320+
}
313321

314-
setContentType(contentType: string) {
315-
this.#contentType = contentType;
322+
_write(chunk: Buffer, _encoding: string, callback: () => void): void {
323+
/* v8 ignore else -- @preserve */
324+
if (this.#firstWrite && this._onBeforeFirstWrite) {
325+
this._onBeforeFirstWrite((data: Uint8Array | string) => {
326+
this.#chunks.push(Buffer.from(data));
327+
});
328+
this.#firstWrite = false;
316329
}
330+
this.#chunks.push(chunk);
331+
callback();
332+
}
333+
334+
public getBuffer(): Buffer {
335+
return Buffer.concat(this.#chunks);
336+
}
337+
}
317338

339+
export const HttpResponseStream =
340+
globalThis.awslambda?.HttpResponseStream ??
341+
// biome-ignore lint/complexity/noStaticOnlyClass: This is how the Lambda RIC implements it
342+
class LocalHttpResponseStream {
318343
static from(
319344
underlyingStream: ResponseStream,
320345
prelude: Record<string, string>
321-
) {
346+
): ResponseStream {
322347
underlyingStream.setContentType(
323348
"'application/vnd.awslambda.http-integration-response'"
324349
);
@@ -401,22 +426,21 @@ const streamifyResponse =
401426
return (async (event, responseStream, context) => {
402427
await handler(event, responseStream, context);
403428

404-
/* v8 ignore else -- @preserve */
405-
if ('chunks' in responseStream && Array.isArray(responseStream.chunks)) {
406-
const output = Buffer.concat(responseStream.chunks as Buffer[]);
407-
const nullBytes = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]);
408-
const separatorIndex = output.indexOf(nullBytes);
409-
410-
const preludeBuffer = output.subarray(0, separatorIndex);
411-
const bodyBuffer = output.subarray(separatorIndex + 8);
412-
const prelude = JSON.parse(preludeBuffer.toString());
413-
414-
return {
415-
body: bodyBuffer.toString(),
416-
headers: prelude.headers,
417-
statusCode: prelude.statusCode,
418-
} as TResult;
419-
}
429+
/* v8 ignore next -- @preserve */
430+
const output: Buffer =
431+
(responseStream as IResponseStream).getBuffer?.() ?? Buffer.from([]);
432+
const nullBytes = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]);
433+
const separatorIndex = output.indexOf(nullBytes);
434+
435+
const preludeBuffer = output.subarray(0, separatorIndex);
436+
const bodyBuffer = output.subarray(separatorIndex + 8);
437+
const prelude = JSON.parse(preludeBuffer.toString());
438+
439+
return {
440+
body: bodyBuffer.toString(),
441+
headers: prelude.headers,
442+
statusCode: prelude.statusCode,
443+
} as TResult;
420444
}) as StreamifyHandler<TEvent, TResult>;
421445
});
422446

packages/event-handler/src/types/rest.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Readable } from 'node:stream';
1+
import type { Readable, Writable } from 'node:stream';
22
import type {
33
GenericLogger,
44
JSONValue,
@@ -12,7 +12,6 @@ import type {
1212
} from 'aws-lambda';
1313
import type { HttpStatusCodes, HttpVerbs } from '../rest/constants.js';
1414
import type { Route } from '../rest/Route.js';
15-
import type { HttpResponseStream } from '../rest/utils.js';
1615
import type { ResolveOptions } from './common.js';
1716

1817
type ResponseType = 'ApiGatewayV1' | 'ApiGatewayV2';
@@ -140,9 +139,11 @@ type ValidationResult = {
140139
issues: string[];
141140
};
142141

143-
type ResponseStream = InstanceType<typeof HttpResponseStream> & {
144-
_onBeforeFirstWrite?: (write: (data: Uint8Array | string) => void) => void;
145-
};
142+
interface ResponseStream extends Writable {
143+
setContentType(contentType: string): void;
144+
_onBeforeFirstWrite?: (writeFn: (chunk: unknown) => void) => void;
145+
getBuffer?: () => Buffer;
146+
}
146147

147148
type V1Headers = {
148149
headers: Record<string, string>;

packages/event-handler/tests/unit/rest/Router/decorators.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
streamify,
1010
UnauthorizedError,
1111
} from '../../../../src/rest/index.js';
12+
import { ResponseStream } from '../../../../src/rest/utils.js';
1213
import type { RequestContext } from '../../../../src/types/rest.js';
1314
import {
1415
createHandler,
@@ -17,7 +18,6 @@ import {
1718
createTestEventV2,
1819
createTestLambdaClass,
1920
createTrackingMiddleware,
20-
MockResponseStream,
2121
} from '../helpers.js';
2222

2323
describe.each([
@@ -498,7 +498,7 @@ describe.each([
498498
}
499499

500500
const lambda = new Lambda();
501-
const responseStream = new MockResponseStream();
501+
const responseStream = new ResponseStream();
502502
const handler = lambda.handler.bind(lambda);
503503

504504
// Act
@@ -540,7 +540,7 @@ describe.each([
540540
}
541541

542542
const lambda = new Lambda();
543-
const responseStream = new MockResponseStream();
543+
const responseStream = new ResponseStream();
544544
const handler = lambda.handler.bind(lambda);
545545

546546
// Act

packages/event-handler/tests/unit/rest/Router/streaming.test.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import {
66
streamify,
77
UnauthorizedError,
88
} from '../../../../src/rest/index.js';
9-
import {
10-
createTestEvent,
11-
createTestEventV2,
12-
MockResponseStream,
13-
} from '../helpers.js';
9+
import { ResponseStream } from '../../../../src/rest/utils.js';
10+
import { createTestEvent, createTestEventV2 } from '../helpers.js';
1411

1512
describe.each([
1613
{ version: 'V1', createEvent: createTestEvent },
@@ -22,7 +19,7 @@ describe.each([
2219
app.get('/test', async () => ({ message: 'Hello, World!' }));
2320

2421
const handler = streamify(app);
25-
const responseStream = new MockResponseStream();
22+
const responseStream = new ResponseStream();
2623

2724
// Act
2825
const result = await handler(
@@ -47,7 +44,7 @@ describe.each([
4744
});
4845

4946
const handler = streamify(app);
50-
const responseStream = new MockResponseStream();
47+
const responseStream = new ResponseStream();
5148

5249
// Act
5350
const result = await handler(
@@ -65,7 +62,7 @@ describe.each([
6562
// Prepare
6663
const app = new Router();
6764
const handler = streamify(app);
68-
const responseStream = new MockResponseStream();
65+
const responseStream = new ResponseStream();
6966

7067
// Act
7168
const result = await handler(
@@ -93,7 +90,7 @@ describe.each([
9390
app.get('/test', () => ({ message: 'middleware test' }));
9491

9592
const handler = streamify(app);
96-
const responseStream = new MockResponseStream();
93+
const responseStream = new ResponseStream();
9794

9895
// Act
9996
const result = await handler(
@@ -116,7 +113,7 @@ describe.each([
116113
});
117114

118115
const handler = streamify(app);
119-
const responseStream = new MockResponseStream();
116+
const responseStream = new ResponseStream();
120117

121118
// Act
122119
const result = await handler(
@@ -145,7 +142,7 @@ describe.each([
145142
});
146143

147144
const handler = streamify(app);
148-
const responseStream = new MockResponseStream();
145+
const responseStream = new ResponseStream();
149146

150147
// Act
151148
const result = await handler(
@@ -193,7 +190,7 @@ describe.each([
193190
const app = new Router();
194191
app.get('/test', handlerFn);
195192
const handler = streamify(app);
196-
const responseStream = new MockResponseStream();
193+
const responseStream = new ResponseStream();
197194

198195
// Act
199196
const result = await handler(
@@ -212,7 +209,7 @@ describe.each([
212209
const app = new Router();
213210
app.get('/test', () => new Response(null, { status: 204 }));
214211
const handler = streamify(app);
215-
const responseStream = new MockResponseStream();
212+
const responseStream = new ResponseStream();
216213

217214
// Act
218215
const result = await handler(
@@ -231,7 +228,7 @@ describe.each([
231228
const app = new Router();
232229
app.get('/test', () => new Response(undefined, { status: 200 }));
233230
const handler = streamify(app);
234-
const responseStream = new MockResponseStream();
231+
const responseStream = new ResponseStream();
235232

236233
// Act
237234
const result = await handler(
@@ -256,7 +253,7 @@ describe.each([
256253

257254
app.get('/test', () => new Response(errorStream, { status: 200 }));
258255
const handler = streamify(app);
259-
const responseStream = new MockResponseStream();
256+
const responseStream = new ResponseStream();
260257

261258
// Act & Assess
262259
await expect(
@@ -275,7 +272,7 @@ describe.each([
275272
});
276273

277274
const handler = streamify(app);
278-
const responseStream = new MockResponseStream();
275+
const responseStream = new ResponseStream();
279276

280277
// Act
281278
const result = await handler(
@@ -299,7 +296,7 @@ describe.each([
299296
});
300297

301298
const handler = streamify(app);
302-
const responseStream = new MockResponseStream();
299+
const responseStream = new ResponseStream();
303300

304301
// Act
305302
const result = await handler(
@@ -323,7 +320,7 @@ describe.each([
323320
const app = new Router();
324321
const handler = streamify(app);
325322
const invalidEvent = { invalid: 'event' };
326-
const responseStream = new MockResponseStream();
323+
const responseStream = new ResponseStream();
327324

328325
// Act & Assess
329326
await expect(
@@ -344,7 +341,7 @@ describe.each([
344341
}));
345342

346343
const handler = streamify(app);
347-
const responseStream = new MockResponseStream();
344+
const responseStream = new ResponseStream();
348345

349346
// Act
350347
const result = await handler(

packages/event-handler/tests/unit/rest/helpers.ts

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import type {
66
Context,
77
} from 'aws-lambda';
88
import type { Router } from '../../../src/rest/Router.js';
9-
import { HttpResponseStream } from '../../../src/rest/utils.js';
109
import type { HandlerResponse, Middleware } from '../../../src/types/rest.js';
1110

1211
export const createTestEvent = (
@@ -129,26 +128,6 @@ export const createHeaderCheckMiddleware = (headers: {
129128
};
130129
};
131130

132-
// Mock ResponseStream that extends the actual ResponseStream class
133-
export class MockResponseStream extends HttpResponseStream {
134-
public chunks: Buffer[] = [];
135-
public _onBeforeFirstWrite?: (
136-
write: (data: Uint8Array | string) => void
137-
) => void;
138-
#firstWrite = true;
139-
140-
_write(chunk: Buffer, _encoding: string, callback: () => void): void {
141-
if (this.#firstWrite && this._onBeforeFirstWrite) {
142-
this._onBeforeFirstWrite((data: Uint8Array | string) => {
143-
this.chunks.push(Buffer.from(data));
144-
});
145-
this.#firstWrite = false;
146-
}
147-
this.chunks.push(chunk);
148-
callback();
149-
}
150-
}
151-
152131
// Create a handler function from the Router instance
153132
export const createHandler = (app: Router) => {
154133
function handler(

0 commit comments

Comments
 (0)