Skip to content

Commit fec6189

Browse files
authored
Merge pull request #66 from apollo-server-integrations/bug/base64-payloads
Add base64 encoding support. Refactored tests for coverage
2 parents 0f228e3 + 888759a commit fec6189

9 files changed

+197
-239
lines changed

.changeset/twenty-owls-march.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@as-integrations/aws-lambda': minor
3+
---
4+
5+
Added support for base64 encoding
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { ApolloServer, ApolloServerOptions, BaseContext } from '@apollo/server';
2+
import {
3+
CreateServerForIntegrationTestsOptions,
4+
defineIntegrationTestSuite,
5+
} from '@apollo/server-integration-testsuite';
6+
import type { Handler } from 'aws-lambda';
7+
import { createServer, IncomingMessage, ServerResponse } from 'http';
8+
import { startServerAndCreateLambdaHandler } from '..';
9+
import { urlForHttpServer } from './mockServer';
10+
11+
export function defineLambdaTestSuite<Event, Response>(
12+
mockServerFactory: (
13+
handler: Handler<Event, Response>,
14+
shouldBase64Encode: boolean,
15+
) => (req: IncomingMessage, res: ServerResponse) => void,
16+
) {
17+
describe.each([true, false])(
18+
'With base64 encoding set to %s',
19+
(shouldBase64Encode) => {
20+
defineIntegrationTestSuite(
21+
async function (
22+
serverOptions: ApolloServerOptions<BaseContext>,
23+
testOptions?: CreateServerForIntegrationTestsOptions,
24+
) {
25+
const httpServer = createServer();
26+
const server = new ApolloServer({
27+
...serverOptions,
28+
});
29+
30+
const handler = startServerAndCreateLambdaHandler(
31+
server,
32+
testOptions,
33+
);
34+
35+
httpServer.addListener(
36+
'request',
37+
mockServerFactory(
38+
handler as Handler<Event, Response>,
39+
shouldBase64Encode,
40+
),
41+
);
42+
43+
await new Promise<void>((resolve) => {
44+
httpServer.listen({ port: 0 }, resolve);
45+
});
46+
47+
return {
48+
server,
49+
url: urlForHttpServer(httpServer),
50+
async extraCleanup() {
51+
await new Promise<void>((resolve) => {
52+
httpServer.close(() => resolve());
53+
});
54+
},
55+
};
56+
},
57+
{
58+
serverIsStartedInBackground: true,
59+
noIncrementalDelivery: true,
60+
},
61+
);
62+
},
63+
);
64+
}

src/__tests__/integrationALB.test.ts

+2-47
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,6 @@
1-
import { ApolloServer, ApolloServerOptions, BaseContext } from '@apollo/server';
2-
import {
3-
CreateServerForIntegrationTestsOptions,
4-
defineIntegrationTestSuite,
5-
} from '@apollo/server-integration-testsuite';
6-
import type { ALBEvent, ALBResult, Handler } from 'aws-lambda';
7-
import { createServer } from 'http';
8-
import { startServerAndCreateLambdaHandler } from '..';
91
import { createMockALBServer } from './mockALBServer';
10-
import { urlForHttpServer } from './mockServer';
2+
import { defineLambdaTestSuite } from './defineLambdaTestSuite';
113

124
describe('lambdaHandlerALB', () => {
13-
defineIntegrationTestSuite(
14-
async function (
15-
serverOptions: ApolloServerOptions<BaseContext>,
16-
testOptions?: CreateServerForIntegrationTestsOptions,
17-
) {
18-
const httpServer = createServer();
19-
const server = new ApolloServer({
20-
...serverOptions,
21-
});
22-
23-
const handler = testOptions
24-
? startServerAndCreateLambdaHandler(server, testOptions)
25-
: startServerAndCreateLambdaHandler(server);
26-
27-
httpServer.addListener(
28-
'request',
29-
createMockALBServer(handler as Handler<ALBEvent, ALBResult>),
30-
);
31-
32-
await new Promise<void>((resolve) => {
33-
httpServer.listen({ port: 0 }, resolve);
34-
});
35-
36-
return {
37-
server,
38-
url: urlForHttpServer(httpServer),
39-
async extraCleanup() {
40-
await new Promise<void>((resolve) => {
41-
httpServer.close(() => resolve());
42-
});
43-
},
44-
};
45-
},
46-
{
47-
serverIsStartedInBackground: true,
48-
noIncrementalDelivery: true,
49-
},
50-
);
5+
defineLambdaTestSuite(createMockALBServer);
516
});

src/__tests__/integrationV1.test.ts

+2-44
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,6 @@
1-
import { ApolloServer, ApolloServerOptions, BaseContext } from '@apollo/server';
2-
import {
3-
CreateServerForIntegrationTestsOptions,
4-
defineIntegrationTestSuite,
5-
} from '@apollo/server-integration-testsuite';
6-
import type { Handler } from 'aws-lambda';
7-
import { createServer } from 'http';
8-
import { startServerAndCreateLambdaHandler } from '..';
1+
import { defineLambdaTestSuite } from './defineLambdaTestSuite';
92
import { createMockV1Server } from './mockAPIGatewayV1Server';
10-
import { urlForHttpServer } from './mockServer';
113

124
describe('lambdaHandlerV1', () => {
13-
defineIntegrationTestSuite(
14-
async function (
15-
serverOptions: ApolloServerOptions<BaseContext>,
16-
testOptions?: CreateServerForIntegrationTestsOptions,
17-
) {
18-
const httpServer = createServer();
19-
const server = new ApolloServer({
20-
...serverOptions,
21-
});
22-
23-
const handler: Handler = testOptions
24-
? startServerAndCreateLambdaHandler(server, testOptions)
25-
: startServerAndCreateLambdaHandler(server);
26-
27-
httpServer.addListener('request', createMockV1Server(handler));
28-
29-
await new Promise<void>((resolve) => {
30-
httpServer.listen({ port: 0 }, resolve);
31-
});
32-
33-
return {
34-
server,
35-
url: urlForHttpServer(httpServer),
36-
async extraCleanup() {
37-
await new Promise<void>((resolve) => {
38-
httpServer.close(() => resolve());
39-
});
40-
},
41-
};
42-
},
43-
{
44-
serverIsStartedInBackground: true,
45-
noIncrementalDelivery: true,
46-
},
47-
);
5+
defineLambdaTestSuite(createMockV1Server);
486
});

src/__tests__/integrationV2.test.ts

+2-43
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,6 @@
1-
import { ApolloServer, ApolloServerOptions, BaseContext } from '@apollo/server';
2-
import {
3-
CreateServerForIntegrationTestsOptions,
4-
defineIntegrationTestSuite,
5-
} from '@apollo/server-integration-testsuite';
6-
import { createServer } from 'http';
7-
import { startServerAndCreateLambdaHandler } from '..';
1+
import { defineLambdaTestSuite } from './defineLambdaTestSuite';
82
import { createMockV2Server } from './mockAPIGatewayV2Server';
9-
import { urlForHttpServer } from './mockServer';
103

114
describe('lambdaHandlerV2', () => {
12-
defineIntegrationTestSuite(
13-
async function (
14-
serverOptions: ApolloServerOptions<BaseContext>,
15-
testOptions?: CreateServerForIntegrationTestsOptions,
16-
) {
17-
const httpServer = createServer();
18-
const server = new ApolloServer({
19-
...serverOptions,
20-
});
21-
22-
const handler = testOptions
23-
? startServerAndCreateLambdaHandler(server, testOptions)
24-
: startServerAndCreateLambdaHandler(server);
25-
26-
httpServer.addListener('request', createMockV2Server(handler));
27-
28-
await new Promise<void>((resolve) => {
29-
httpServer.listen({ port: 0 }, resolve);
30-
});
31-
32-
return {
33-
server,
34-
url: urlForHttpServer(httpServer),
35-
async extraCleanup() {
36-
await new Promise<void>((resolve) => {
37-
httpServer.close(() => resolve());
38-
});
39-
},
40-
};
41-
},
42-
{
43-
serverIsStartedInBackground: true,
44-
noIncrementalDelivery: true,
45-
},
46-
);
5+
defineLambdaTestSuite(createMockV2Server);
476
});

src/__tests__/mockALBServer.ts

+39-32
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,51 @@ import type { IncomingMessage } from 'http';
33
import type { ALBEvent, ALBResult, Handler } from 'aws-lambda';
44
import { createMockServer } from './mockServer';
55

6-
export function createMockALBServer(handler: Handler<ALBEvent, ALBResult>) {
7-
return createMockServer(handler, albEventFromRequest);
6+
export function createMockALBServer(
7+
handler: Handler<ALBEvent, ALBResult>,
8+
shouldBase64Encode: boolean,
9+
) {
10+
return createMockServer(handler, albEventFromRequest(shouldBase64Encode));
811
}
912

10-
function albEventFromRequest(req: IncomingMessage, body: string): ALBEvent {
11-
const urlObject = url.parse(req.url || '', false);
12-
const searchParams = new URLSearchParams(urlObject.search ?? '');
13+
function albEventFromRequest(shouldBase64Encode: boolean) {
14+
return function (req: IncomingMessage, body: string): ALBEvent {
15+
const urlObject = url.parse(req.url || '', false);
16+
const searchParams = new URLSearchParams(urlObject.search ?? '');
1317

14-
const multiValueQueryStringParameters: ALBEvent['multiValueQueryStringParameters'] =
15-
{};
18+
const multiValueQueryStringParameters: ALBEvent['multiValueQueryStringParameters'] =
19+
{};
1620

17-
for (const [key] of searchParams.entries()) {
18-
const all = searchParams.getAll(key);
19-
if (all.length > 1) {
20-
multiValueQueryStringParameters[key] = all;
21+
for (const [key] of searchParams.entries()) {
22+
const all = searchParams.getAll(key);
23+
if (all.length > 1) {
24+
multiValueQueryStringParameters[key] = all;
25+
}
2126
}
22-
}
2327

24-
return {
25-
requestContext: {
26-
elb: {
27-
targetGroupArn: '...',
28+
return {
29+
requestContext: {
30+
elb: {
31+
targetGroupArn: '...',
32+
},
2833
},
29-
},
30-
httpMethod: req.method ?? 'GET',
31-
path: urlObject.pathname ?? '/',
32-
queryStringParameters: Object.fromEntries(searchParams.entries()),
33-
headers: Object.fromEntries(
34-
Object.entries(req.headers).map(([name, value]) => {
35-
if (Array.isArray(value)) {
36-
return [name, value.join(',')];
37-
} else {
38-
return [name, value];
39-
}
40-
}),
41-
),
42-
multiValueQueryStringParameters,
43-
body,
44-
isBase64Encoded: false,
34+
httpMethod: req.method ?? 'GET',
35+
path: urlObject.pathname ?? '/',
36+
queryStringParameters: Object.fromEntries(searchParams.entries()),
37+
headers: Object.fromEntries(
38+
Object.entries(req.headers).map(([name, value]) => {
39+
if (Array.isArray(value)) {
40+
return [name, value.join(',')];
41+
} else {
42+
return [name, value];
43+
}
44+
}),
45+
),
46+
multiValueQueryStringParameters,
47+
body: shouldBase64Encode
48+
? Buffer.from(body, 'utf8').toString('base64')
49+
: body,
50+
isBase64Encoded: shouldBase64Encode,
51+
};
4552
};
4653
}

src/__tests__/mockAPIGatewayV1Server.ts

+36-33
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,47 @@ import { createMockServer } from './mockServer';
99

1010
export function createMockV1Server(
1111
handler: Handler<APIGatewayProxyEvent, APIGatewayProxyResult>,
12+
shouldBase64Encode: boolean,
1213
) {
13-
return createMockServer(handler, v1EventFromRequest);
14+
return createMockServer(handler, v1EventFromRequest(shouldBase64Encode));
1415
}
1516

16-
function v1EventFromRequest(
17-
req: IncomingMessage,
18-
body: string,
19-
): APIGatewayProxyEvent {
20-
const urlObject = url.parse(req.url || '', false);
21-
const searchParams = new URLSearchParams(urlObject.search ?? '');
17+
function v1EventFromRequest(shouldBase64Encode: boolean) {
18+
return function (req: IncomingMessage, body: string): APIGatewayProxyEvent {
19+
const urlObject = url.parse(req.url || '', false);
20+
const searchParams = new URLSearchParams(urlObject.search ?? '');
2221

23-
const multiValueQueryStringParameters: Record<string, string[]> = {};
24-
for (const [key] of searchParams.entries()) {
25-
const all = searchParams.getAll(key);
26-
if (all.length > 1) {
27-
multiValueQueryStringParameters[key] = all;
22+
const multiValueQueryStringParameters: Record<string, string[]> = {};
23+
for (const [key] of searchParams.entries()) {
24+
const all = searchParams.getAll(key);
25+
if (all.length > 1) {
26+
multiValueQueryStringParameters[key] = all;
27+
}
2828
}
29-
}
3029

31-
// simplify the V1 event down to what our integration actually cares about
32-
const event: Partial<APIGatewayProxyEvent> = {
33-
// @ts-expect-error (version actually can exist on v1 events, this seems to be a typing error)
34-
version: '1.0',
35-
httpMethod: req.method!,
36-
headers: Object.fromEntries(
37-
Object.entries(req.headers).map(([name, value]) => {
38-
if (Array.isArray(value)) {
39-
return [name, value.join(',')];
40-
} else {
41-
return [name, value];
42-
}
43-
}),
44-
),
45-
queryStringParameters: Object.fromEntries(searchParams.entries()),
46-
body,
47-
multiValueQueryStringParameters,
48-
multiValueHeaders: {},
49-
};
30+
// simplify the V1 event down to what our integration actually cares about
31+
const event: Partial<APIGatewayProxyEvent> = {
32+
// @ts-expect-error (version actually can exist on v1 events, this seems to be a typing error)
33+
version: '1.0',
34+
httpMethod: req.method!,
35+
headers: Object.fromEntries(
36+
Object.entries(req.headers).map(([name, value]) => {
37+
if (Array.isArray(value)) {
38+
return [name, value.join(',')];
39+
} else {
40+
return [name, value];
41+
}
42+
}),
43+
),
44+
queryStringParameters: Object.fromEntries(searchParams.entries()),
45+
body: shouldBase64Encode
46+
? Buffer.from(body, 'utf8').toString('base64')
47+
: body,
48+
isBase64Encoded: shouldBase64Encode,
49+
multiValueQueryStringParameters,
50+
multiValueHeaders: {},
51+
};
5052

51-
return event as APIGatewayProxyEvent;
53+
return event as APIGatewayProxyEvent;
54+
};
5255
}

0 commit comments

Comments
 (0)