Skip to content

Commit 2656bf7

Browse files
committed
fix bugs in proxyEventToWebRequest function
1 parent 17ae585 commit 2656bf7

File tree

2 files changed

+69
-36
lines changed

2 files changed

+69
-36
lines changed

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,20 @@ const createBody = (body: string | null, isBase64Encoded: boolean) => {
2727
export const proxyEventToWebRequest = (
2828
event: APIGatewayProxyEvent
2929
): Request => {
30-
const { httpMethod, path, domainName } = event.requestContext;
30+
const { httpMethod, path } = event;
31+
const { domainName } = event.requestContext;
3132

3233
const headers = new Headers();
3334
for (const [name, value] of Object.entries(event.headers ?? {})) {
34-
if (value != null) headers.append(name, value);
35+
if (value != null) headers.set(name, value);
3536
}
3637

3738
for (const [name, values] of Object.entries(event.multiValueHeaders ?? {})) {
3839
for (const value of values ?? []) {
39-
headers.append(name, value);
40+
const headerValue = headers.get(name);
41+
if (!headerValue?.includes(value)) {
42+
headers.append(name, value);
43+
}
4044
}
4145
}
4246
const hostname = headers.get('Host') ?? domainName;

packages/event-handler/tests/unit/rest/converters.test.ts

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('Converters', () => {
3636
body: null,
3737
};
3838

39-
it('should convert basic GET request', () => {
39+
it('converts basic GET request', () => {
4040
const request = proxyEventToWebRequest(baseEvent);
4141

4242
expect(request).toBeInstanceOf(Request);
@@ -45,7 +45,7 @@ describe('Converters', () => {
4545
expect(request.body).toBe(null);
4646
});
4747

48-
it('should use Host header over domainName', () => {
48+
it('uses Host header over domainName', () => {
4949
const event = {
5050
...baseEvent,
5151
headers: { Host: 'custom.example.com' },
@@ -56,7 +56,7 @@ describe('Converters', () => {
5656
expect(request.url).toBe('https://custom.example.com/test');
5757
});
5858

59-
it('should use X-Forwarded-Proto header for protocol', () => {
59+
it('uses X-Forwarded-Proto header for protocol', () => {
6060
const event = {
6161
...baseEvent,
6262
headers: { 'X-Forwarded-Proto': 'https' },
@@ -67,7 +67,7 @@ describe('Converters', () => {
6767
expect(request.url).toBe('https://api.example.com/test');
6868
});
6969

70-
it('should handle null values in multiValueHeaders arrays', () => {
70+
it('handles null values in multiValueHeaders arrays', () => {
7171
const event = {
7272
...baseEvent,
7373
multiValueHeaders: {
@@ -82,7 +82,7 @@ describe('Converters', () => {
8282
expect(request.headers.get('Custom-Header')).toBe('value1');
8383
});
8484

85-
it('should handle null values in multiValueQueryStringParameters arrays', () => {
85+
it('handles null values in multiValueQueryStringParameters arrays', () => {
8686
const event = {
8787
...baseEvent,
8888
multiValueQueryStringParameters: {
@@ -98,14 +98,10 @@ describe('Converters', () => {
9898
expect(url.searchParams.get('sort')).toBe('desc');
9999
});
100100

101-
it('should handle POST request with string body', async () => {
101+
it('handles POST request with string body', async () => {
102102
const event = {
103103
...baseEvent,
104104
httpMethod: 'POST',
105-
requestContext: {
106-
...baseEvent.requestContext,
107-
httpMethod: 'POST',
108-
},
109105
body: '{"key":"value"}',
110106
headers: { 'Content-Type': 'application/json' },
111107
};
@@ -117,17 +113,13 @@ describe('Converters', () => {
117113
expect(request.headers.get('Content-Type')).toBe('application/json');
118114
});
119115

120-
it('should decode base64 encoded body', async () => {
116+
it('decodes base64 encoded body', async () => {
121117
const originalText = 'Hello World';
122118
const base64Text = Buffer.from(originalText).toString('base64');
123119

124120
const event = {
125121
...baseEvent,
126122
httpMethod: 'POST',
127-
requestContext: {
128-
...baseEvent.requestContext,
129-
httpMethod: 'POST',
130-
},
131123
body: base64Text,
132124
isBase64Encoded: true,
133125
};
@@ -137,7 +129,7 @@ describe('Converters', () => {
137129
expect(await request.text()).toBe(originalText);
138130
});
139131

140-
it('should handle single-value headers', () => {
132+
it('handles single-value headers', () => {
141133
const event = {
142134
...baseEvent,
143135
headers: {
@@ -152,7 +144,7 @@ describe('Converters', () => {
152144
expect(request.headers.get('User-Agent')).toBe('test-agent');
153145
});
154146

155-
it('should handle multiValueHeaders', () => {
147+
it('handles multiValueHeaders', () => {
156148
const event = {
157149
...baseEvent,
158150
multiValueHeaders: {
@@ -167,7 +159,7 @@ describe('Converters', () => {
167159
expect(request.headers.get('Custom-Header')).toBe('value1, value2');
168160
});
169161

170-
it('should handle both single and multi-value headers', () => {
162+
it('handles both single and multi-value headers', () => {
171163
const event = {
172164
...baseEvent,
173165
headers: {
@@ -184,7 +176,44 @@ describe('Converters', () => {
184176
expect(request.headers.get('Accept')).toBe('application/json, text/html');
185177
});
186178

187-
it('should handle queryStringParameters', () => {
179+
it('deduplicates headers when same header exists in both headers and multiValueHeaders', () => {
180+
const event = {
181+
...baseEvent,
182+
headers: {
183+
Host: 'abcd1234.execute-api.eu-west-1.amazonaws.com',
184+
'X-Forwarded-Proto': 'https',
185+
},
186+
multiValueHeaders: {
187+
Host: ['abcd1234.execute-api.eu-west-1.amazonaws.com'],
188+
'X-Forwarded-Proto': ['https'],
189+
},
190+
};
191+
192+
const request = proxyEventToWebRequest(event);
193+
expect(request).toBeInstanceOf(Request);
194+
expect(request.headers.get('Host')).toBe(
195+
'abcd1234.execute-api.eu-west-1.amazonaws.com'
196+
);
197+
expect(request.headers.get('X-Forwarded-Proto')).toBe('https');
198+
});
199+
200+
it('appends unique values from multiValueHeaders when header already exists', () => {
201+
const event = {
202+
...baseEvent,
203+
headers: {
204+
Accept: 'application/json',
205+
},
206+
multiValueHeaders: {
207+
Accept: ['application/json', 'text/html'],
208+
},
209+
};
210+
211+
const request = proxyEventToWebRequest(event);
212+
expect(request).toBeInstanceOf(Request);
213+
expect(request.headers.get('Accept')).toBe('application/json, text/html');
214+
});
215+
216+
it('handles queryStringParameters', () => {
188217
const event = {
189218
...baseEvent,
190219
queryStringParameters: {
@@ -200,7 +229,7 @@ describe('Converters', () => {
200229
expect(url.searchParams.get('age')).toBe('25');
201230
});
202231

203-
it('should handle multiValueQueryStringParameters', () => {
232+
it('handles multiValueQueryStringParameters', () => {
204233
const event = {
205234
...baseEvent,
206235
multiValueQueryStringParameters: {
@@ -216,7 +245,7 @@ describe('Converters', () => {
216245
expect(url.searchParams.get('sort')).toBe('desc');
217246
});
218247

219-
it('should handle both queryStringParameters and multiValueQueryStringParameters', () => {
248+
it('handles both queryStringParameters and multiValueQueryStringParameters', () => {
220249
const event = {
221250
...baseEvent,
222251
queryStringParameters: {
@@ -234,7 +263,7 @@ describe('Converters', () => {
234263
expect(url.searchParams.getAll('multi')).toEqual(['value1', 'value2']);
235264
});
236265

237-
it('should skip null queryStringParameter values', () => {
266+
it('skips null queryStringParameter values', () => {
238267
const event = {
239268
...baseEvent,
240269
queryStringParameters: {
@@ -250,7 +279,7 @@ describe('Converters', () => {
250279
expect(url.searchParams.has('null')).toBe(false);
251280
});
252281

253-
it('should skip null header values', () => {
282+
it('skips null header values', () => {
254283
const event = {
255284
...baseEvent,
256285
headers: {
@@ -265,7 +294,7 @@ describe('Converters', () => {
265294
expect(request.headers.get('Null-Header')).toBe(null);
266295
});
267296

268-
it('should handle null/undefined collections', () => {
297+
it('handles null/undefined collections', () => {
269298
const event = {
270299
...baseEvent,
271300
headers: null as any,
@@ -282,7 +311,7 @@ describe('Converters', () => {
282311
});
283312

284313
describe('responseToProxyResult', () => {
285-
it('should convert basic Response to API Gateway result', async () => {
314+
it('converts basic Response to API Gateway result', async () => {
286315
const response = new Response('Hello World', {
287316
status: 200,
288317
headers: {
@@ -299,7 +328,7 @@ describe('Converters', () => {
299328
expect(result.multiValueHeaders).toEqual({});
300329
});
301330

302-
it('should handle single-value headers', async () => {
331+
it('handles single-value headers', async () => {
303332
const response = new Response('Hello', {
304333
status: 201,
305334
headers: { 'content-type': 'text/plain', 'x-custom': 'value' },
@@ -315,7 +344,7 @@ describe('Converters', () => {
315344
expect(result.multiValueHeaders).toEqual({});
316345
});
317346

318-
it('should handle multi-value headers', async () => {
347+
it('handles multi-value headers', async () => {
319348
const response = new Response('Hello', {
320349
status: 200,
321350
headers: {
@@ -332,7 +361,7 @@ describe('Converters', () => {
332361
});
333362
});
334363

335-
it('should handle mixed single and multi-value headers', async () => {
364+
it('handles mixed single and multi-value headers', async () => {
336365
const response = new Response('Hello', {
337366
status: 200,
338367
headers: {
@@ -351,7 +380,7 @@ describe('Converters', () => {
351380
});
352381
});
353382

354-
it('should handle different status codes', async () => {
383+
it('handles different status codes', async () => {
355384
const response = new Response('Not Found', { status: 404 });
356385

357386
const result = await responseToProxyResult(response);
@@ -360,7 +389,7 @@ describe('Converters', () => {
360389
expect(result.body).toBe('Not Found');
361390
});
362391

363-
it('should handle empty response body', async () => {
392+
it('handles empty response body', async () => {
364393
const response = new Response(null, { status: 204 });
365394

366395
const result = await responseToProxyResult(response);
@@ -371,7 +400,7 @@ describe('Converters', () => {
371400
});
372401

373402
describe('handlerResultToProxyResult', () => {
374-
it('should return APIGatewayProxyResult as-is', async () => {
403+
it('returns APIGatewayProxyResult as-is', async () => {
375404
const proxyResult = {
376405
statusCode: 200,
377406
body: 'test',
@@ -384,7 +413,7 @@ describe('Converters', () => {
384413
expect(result).toBe(proxyResult);
385414
});
386415

387-
it('should convert Response object', async () => {
416+
it('converts Response object', async () => {
388417
const response = new Response('Hello', { status: 201 });
389418

390419
const result = await handlerResultToProxyResult(response);
@@ -394,7 +423,7 @@ describe('Converters', () => {
394423
expect(result.isBase64Encoded).toBe(false);
395424
});
396425

397-
it('should convert plain object to JSON', async () => {
426+
it('converts plain object to JSON', async () => {
398427
const obj = { message: 'success', data: [1, 2, 3] };
399428

400429
const result = await handlerResultToProxyResult(obj);

0 commit comments

Comments
 (0)