Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix onHealthCheck for lambda failing to correctly handle returned promise #4969

Merged
merged 5 commits into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/apollo-server-lambda/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export class ApolloServer extends ApolloServerBase {
},
});
});
return;
} else {
return callback(null, successfulResponse);
}
Expand Down
87 changes: 87 additions & 0 deletions packages/apollo-server-lambda/src/__tests__/ApolloServer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import request from 'supertest';
import {createMockServer} from './mockServer';
import { gql } from 'apollo-server-core';
import {
ApolloServer,
CreateHandlerOptions
} from '../ApolloServer';

const typeDefs = gql`
type Query {
hello: String
}
`;

const resolvers = {
Query: {
hello: () => 'hi',
},
};

describe('apollo-server-lambda', () => {
const createLambda = (
options: Partial<CreateHandlerOptions> = {},
) => {
const server = new ApolloServer({
typeDefs,
resolvers
});
const handler = server.createHandler(options);
return createMockServer(handler);
}

describe('healthchecks', () => {

it('creates a healthcheck endpoint', async () => {
const app = createLambda();

const req = request(app)
.get('/.well-known/apollo/server-health');

return req.then((res: any) => {
expect(res.status).toEqual(200);
expect(res.body).toEqual({ status: 'pass' });
expect(res.headers['content-type']).toEqual('application/json');
});
});

it('provides a callback for the healthcheck', async () => {
const app = createLambda({
onHealthCheck: async () => {
return new Promise((resolve) => {
return resolve("Success!");
});
}
});

const req = request(app)
.get('/.well-known/apollo/server-health');

return req.then((res: any) => {
expect(res.status).toEqual(200);
expect(res.body).toEqual({ status: 'pass' });
expect(res.headers['content-type']).toEqual('application/json');
});
});

it('returns a 503 if healthcheck fails', async () => {
const app = createLambda({
onHealthCheck: async () => {
return new Promise(() => {
throw new Error("Failed to connect!");
});
}
});

const req = request(app)
.get('/.well-known/apollo/server-health');

return req.then((res: any) => {
expect(res.status).toEqual(503);
expect(res.body).toEqual({ status: 'fail' });
expect(res.headers['content-type']).toEqual('application/json');
});
});
});

});
53 changes: 3 additions & 50 deletions packages/apollo-server-lambda/src/__tests__/lambdaApollo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ import testSuite, {
NODE_MAJOR_VERSION,
} from 'apollo-server-integration-testsuite';
import { Config } from 'apollo-server-core';
import { IncomingMessage, ServerResponse } from 'http';

import url from 'url';
import gql from 'graphql-tag';
import request from 'supertest';
import { APIGatewayProxyCallback } from "aws-lambda";
import {createMockServer} from './mockServer';

const createLambda = (options: CreateAppOptions = {}) => {
const server = new ApolloServer(
Expand All @@ -19,52 +16,8 @@ const createLambda = (options: CreateAppOptions = {}) => {

const handler = server.createHandler();

return (req: IncomingMessage, res: ServerResponse) => {
// return 404 if path is /bogus-route to pass the test, lambda doesn't have paths
if (req.url && req.url.includes('/bogus-route')) {
res.statusCode = 404;
return res.end();
}

let body = '';
req.on('data', chunk => (body += chunk));
req.on('end', () => {
const urlObject = url.parse(req.url || '', true);
const event = {
httpMethod: req.method,
body: body,
path: req.url,
queryStringParameters: urlObject.query,
requestContext: {
path: urlObject.pathname,
},
headers: req.headers,
};
const callback: APIGatewayProxyCallback = (error, result) => {
if (error) {
throw error;
} else {
result = result as NonNullable<typeof result>;
}
res.statusCode = result.statusCode;
for (let key in result.headers) {
if (result.headers.hasOwnProperty(key)) {
if (typeof result.headers[key] === 'boolean') {
res.setHeader(key, result.headers[key].toString());
} else {
// Without casting this to `any`, TS still believes `boolean`
// is possible.
res.setHeader(key, result.headers[key] as any);
}
}
}
res.write(result.body);
res.end();
};
handler(event as any, {} as any, callback);
});
};
};
return createMockServer(handler);
}

describe('integration:Lambda', () => {
testSuite(createLambda);
Expand Down
62 changes: 62 additions & 0 deletions packages/apollo-server-lambda/src/__tests__/mockServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import url from 'url';
import { IncomingMessage, ServerResponse } from 'http';
import {
APIGatewayProxyCallback,
APIGatewayProxyEvent,
Context as LambdaContext,
Handler,
APIGatewayProxyResult
} from "aws-lambda";

export function createMockServer(handler: Handler<APIGatewayProxyEvent, APIGatewayProxyResult>) {
return (req: IncomingMessage, res: ServerResponse) => {
// return 404 if path is /bogus-route to pass the test, lambda doesn't have paths
if (req.url && req.url.includes('/bogus-route')) {
res.statusCode = 404;
return res.end();
}

let body = '';
req.on('data', chunk => (body += chunk));
req.on('end', () => {
const urlObject = url.parse(req.url || '', true);
const event = {
httpMethod: req.method,
body: body,
path: req.url,
queryStringParameters: urlObject.query,
requestContext: {
path: urlObject.pathname,
},
headers: req.headers,
};
const callback: APIGatewayProxyCallback = (error, result) => {
if (error) {
throw error;
} else {
result = result as NonNullable<typeof result>;
}
res.statusCode = result.statusCode;
for (let key in result.headers) {
if (result.headers.hasOwnProperty(key)) {
if (typeof result.headers[key] === 'boolean') {
res.setHeader(key, result.headers[key].toString());
} else {
// Without casting this to `any`, TS still believes `boolean`
// is possible.
res.setHeader(key, result.headers[key] as any);
}
}
}
res.write(result.body);
res.end();
};

handler(
event as APIGatewayProxyEvent,
{} as LambdaContext,
callback as APIGatewayProxyCallback
);
});
};
};