serverless/test/integration/httpApi.test.js
2020-10-09 14:15:37 +02:00

290 lines
9.9 KiB
JavaScript

'use strict';
const { expect } = require('chai');
const log = require('log').get('serverless:test');
const awsRequest = require('@serverless/test/aws-request');
const fixtures = require('../fixtures');
const { confirmCloudWatchLogs } = require('../utils/misc');
const { deployService, removeService, fetch } = require('../utils/integration');
describe('HTTP API Integration Test', function() {
this.timeout(1000 * 60 * 20); // Involves time-taking deploys
let endpoint;
let stackName;
let servicePath;
const stage = 'dev';
const resolveEndpoint = async () => {
const result = await awsRequest('CloudFormation', 'describeStacks', { StackName: stackName });
const endpointOutput = result.Stacks[0].Outputs.find(
output => output.OutputKey === 'HttpApiUrl'
).OutputValue;
endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com/)[0];
};
describe('Specific endpoints', () => {
let poolId;
let clientId;
const userName = 'test-http-api';
const userPassword = 'razDwa3!';
before(async () => {
poolId = (
await awsRequest('CognitoIdentityServiceProvider', 'createUserPool', {
PoolName: `test-http-api-${process.hrtime()[1]}`,
})
).UserPool.Id;
[clientId] = await Promise.all([
awsRequest('CognitoIdentityServiceProvider', 'createUserPoolClient', {
ClientName: 'test-http-api',
UserPoolId: poolId,
ExplicitAuthFlows: ['ALLOW_USER_PASSWORD_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH'],
PreventUserExistenceErrors: 'ENABLED',
}).then(result => result.UserPoolClient.ClientId),
awsRequest('CognitoIdentityServiceProvider', 'adminCreateUser', {
UserPoolId: poolId,
Username: userName,
}).then(() =>
awsRequest('CognitoIdentityServiceProvider', 'adminSetUserPassword', {
UserPoolId: poolId,
Username: userName,
Password: userPassword,
Permanent: true,
})
),
]);
const serviceData = await fixtures.setup('httpApi', {
configExt: {
provider: {
httpApi: {
cors: { exposedResponseHeaders: 'X-foo' },
authorizers: {
someAuthorizer: {
identitySource: '$request.header.Authorization',
issuerUrl: `https://cognito-idp.us-east-1.amazonaws.com/${poolId}`,
audience: clientId,
},
},
},
logs: { httpApi: true },
},
functions: {
foo: {
events: [
{
httpApi: {
authorizer: 'someAuthorizer',
},
},
],
},
other: {
timeout: 1,
},
},
},
});
({ servicePath } = serviceData);
const serviceName = serviceData.serviceConfig.service;
stackName = `${serviceName}-${stage}`;
await deployService(servicePath);
return resolveEndpoint();
});
after(async () => {
await awsRequest('CognitoIdentityServiceProvider', 'deleteUserPool', { UserPoolId: poolId });
if (!servicePath) return;
await removeService(servicePath);
});
it('should expose an accessible POST HTTP endpoint', async () => {
const testEndpoint = `${endpoint}/some-post`;
const response = await fetch(testEndpoint, { method: 'POST' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'POST', path: '/some-post' });
});
it('should expose an accessible paramed GET HTTP endpoint', async () => {
const testEndpoint = `${endpoint}/bar/whatever`;
const response = await fetch(testEndpoint, { method: 'GET' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'GET', path: '/bar/whatever' });
});
it('should return 404 on not supported method', async () => {
const testEndpoint = `${endpoint}/foo`;
const response = await fetch(testEndpoint, { method: 'POST' });
expect(response.status).to.equal(404);
});
it('should return 404 on not configured path', async () => {
const testEndpoint = `${endpoint}/not-configured`;
const response = await fetch(testEndpoint, { method: 'GET' });
expect(response.status).to.equal(404);
});
it('should respect timeout settings', async () => {
const testEndpoint = `${endpoint}/bar/timeout`;
const response = await fetch(testEndpoint, { method: 'GET' });
expect(response.status).to.equal(500);
});
it('should support CORS when indicated', async () => {
const testEndpoint = `${endpoint}/bar/whatever`;
const response = await fetch(testEndpoint, {
method: 'GET',
headers: { Origin: 'https://serverless.com' },
});
expect(response.headers.get('access-control-allow-origin')).to.equal('*');
expect(response.headers.get('access-control-expose-headers')).to.equal('x-foo');
});
it('should expose a GET HTTP endpoint backed by JWT authorization', async () => {
const testEndpoint = `${endpoint}/foo`;
const responseUnauthorized = await fetch(testEndpoint, {
method: 'GET',
});
expect(responseUnauthorized.status).to.equal(401);
const token = (
await awsRequest('CognitoIdentityServiceProvider', 'initiateAuth', {
AuthFlow: 'USER_PASSWORD_AUTH',
AuthParameters: { USERNAME: userName, PASSWORD: userPassword },
ClientId: clientId,
})
).AuthenticationResult.IdToken;
const responseAuthorized = await fetch(testEndpoint, {
method: 'GET',
headers: { Authorization: token },
});
const json = await responseAuthorized.json();
expect(json).to.deep.equal({ method: 'GET', path: '/foo' });
});
it('should expose access logs when configured to', () =>
confirmCloudWatchLogs(`/aws/http-api/${stackName}`, async () => {
const response = await fetch(`${endpoint}/some-post`, { method: 'POST' });
await response.json();
}).then(events => {
expect(events.length > 0).to.equal(true);
}));
});
describe('Catch-all endpoints', () => {
before(async () => {
const serviceData = await fixtures.setup('httpApiCatchAll');
({ servicePath } = serviceData);
const serviceName = serviceData.serviceConfig.service;
stackName = `${serviceName}-${stage}`;
await deployService(servicePath);
return resolveEndpoint();
});
after(async function() {
// Added temporarily to inspect random fails
// TODO: Remove once properly diagnosed
if (this.test.parent.tests.some(test => test.state === 'failed')) return;
log.notice('Removing service...');
await removeService(servicePath);
});
it('should catch all root endpoint', async () => {
const testEndpoint = `${endpoint}`;
const response = await fetch(testEndpoint, { method: 'GET' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'GET', path: '/' });
});
it('should catch all whatever endpoint', async () => {
const testEndpoint = `${endpoint}/whatever`;
const response = await fetch(testEndpoint, { method: 'PATCH' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'PATCH', path: '/whatever' });
});
it('should catch all methods on method catch all endpoint', async () => {
const testEndpoint = `${endpoint}/foo`;
const response = await fetch(testEndpoint, { method: 'PATCH' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'PATCH', path: '/foo' });
});
});
describe('Shared API', () => {
let exportServicePath;
let serviceName;
before(async () => {
const exportServiceData = await fixtures.setup('httpApiExport');
({ servicePath: exportServicePath } = exportServiceData);
const exportServiceName = exportServiceData.serviceConfig.service;
await deployService(exportServicePath);
const httpApiId = (
await awsRequest('CloudFormation', 'describeStacks', {
StackName: `${exportServiceName}-${stage}`,
})
).Stacks[0].Outputs[0].OutputValue;
endpoint = (await awsRequest('ApiGatewayV2', 'getApi', { ApiId: httpApiId })).ApiEndpoint;
const serviceData = await fixtures.setup('httpApi', {
configExt: {
provider: { httpApi: { id: httpApiId } },
},
});
({ servicePath } = serviceData);
serviceName = serviceData.serviceConfig.service;
stackName = `${serviceName}-${stage}`;
await deployService(servicePath);
});
after(async () => {
if (serviceName) {
await removeService(servicePath);
}
await removeService(exportServicePath);
});
it('should expose an accessible POST HTTP endpoint', async () => {
const testEndpoint = `${endpoint}/some-post`;
const response = await fetch(testEndpoint, { method: 'POST' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'POST', path: '/some-post' });
});
it('should expose an accessible paramed GET HTTP endpoint', async () => {
const testEndpoint = `${endpoint}/bar/whatever`;
const response = await fetch(testEndpoint, { method: 'GET' });
const json = await response.json();
expect(json).to.deep.equal({ method: 'GET', path: '/bar/whatever' });
});
it('should return 404 on not supported method', async () => {
const testEndpoint = `${endpoint}/foo`;
const response = await fetch(testEndpoint, { method: 'POST' });
expect(response.status).to.equal(404);
});
it('should return 404 on not configured path', async () => {
const testEndpoint = `${endpoint}/not-configured`;
const response = await fetch(testEndpoint, { method: 'GET' });
expect(response.status).to.equal(404);
});
});
});