'use strict' const { expect } = require('chai') const log = require('log').get('serverless:test') const awsRequest = require('@serverless/test/aws-request') const CloudFormationService = require('aws-sdk').CloudFormation const fixtures = require('../../fixtures/programmatic') const { confirmCloudWatchLogs } = require('../../utils/misc') const { deployService, removeService, fetch, } = require('../../utils/integration') describe('AWS - API Gateway Integration Test', function () { this.timeout(1000 * 60 * 10) // Involves time-taking deploys let serviceName let endpoint let stackName let serviceDir let updateConfig let apiKey let isDeployed = false const stage = 'dev' const resolveEndpoint = async () => { const result = await awsRequest(CloudFormationService, 'describeStacks', { StackName: stackName, }) const endpointOutput = result.Stacks[0].Outputs.find( (output) => output.OutputKey === 'ServiceEndpoint', ).OutputValue endpoint = endpointOutput.match( /https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/, )[0] } before(async () => { const serviceData = await fixtures.setup('api-gateway-extended') ;({ servicePath: serviceDir, updateConfig } = serviceData) serviceName = serviceData.serviceConfig.service apiKey = `${serviceName}-api-key-1` stackName = `${serviceName}-${stage}` await deployService(serviceDir) isDeployed = true return resolveEndpoint() }) after(async () => { if (!isDeployed) return log.notice('Removing service...') await removeService(serviceDir) }) describe('Minimal Setup', () => { const expectedMessage = 'Hello from API Gateway! - (minimal)' it('should expose an accessible GET HTTP endpoint', async () => { const testEndpoint = `${endpoint}` return fetch(testEndpoint, { method: 'GET' }) .then((response) => response.json()) .then((json) => expect(json.message).to.equal(expectedMessage)) }) it('should expose an accessible POST HTTP endpoint', async () => { const testEndpoint = `${endpoint}/minimal-1` return fetch(testEndpoint, { method: 'POST' }) .then((response) => response.json()) .then((json) => expect(json.message).to.equal(expectedMessage)) }) it('should expose an accessible PUT HTTP endpoint', async () => { const testEndpoint = `${endpoint}/minimal-2` return fetch(testEndpoint, { method: 'PUT' }) .then((response) => response.json()) .then((json) => expect(json.message).to.equal(expectedMessage)) }) it('should expose an accessible DELETE HTTP endpoint', async () => { const testEndpoint = `${endpoint}/minimal-3` return fetch(testEndpoint, { method: 'DELETE' }) .then((response) => response.json()) .then((json) => expect(json.message).to.equal(expectedMessage)) }) }) describe('CORS', () => { it('should setup simple CORS support via cors: true config', async () => { const testEndpoint = `${endpoint}/simple-cors` return fetch(testEndpoint, { method: 'OPTIONS' }).then((response) => { const headers = response.headers const allowHeaders = [ 'Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token', 'X-Amz-User-Agent', 'X-Amzn-Trace-Id', ].join(',') expect(headers.get('access-control-allow-headers')).to.equal( allowHeaders, ) expect(headers.get('access-control-allow-methods')).to.equal( 'OPTIONS,GET', ) expect(headers.get('access-control-allow-credentials')).to.equal(null) expect(headers.get('access-control-allow-origin')).to.equal('*') }) }) it('should setup CORS support with complex object config', async () => { const testEndpoint = `${endpoint}/complex-cors` return fetch(testEndpoint, { method: 'OPTIONS' }).then((response) => { const headers = response.headers const allowHeaders = [ 'Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token', 'X-Amz-User-Agent', 'X-Amzn-Trace-Id', ].join(',') expect(headers.get('access-control-allow-headers')).to.equal( allowHeaders, ) expect(headers.get('access-control-allow-methods')).to.equal( 'OPTIONS,GET', ) expect(headers.get('access-control-allow-credentials')).to.equal('true') expect(headers.get('access-control-allow-origin')).to.equal('*') }) }) }) describe('Custom Authorizers', () => { let testEndpoint before(() => { testEndpoint = `${endpoint}/custom-auth` }) it('should reject requests without authorization', async () => { return fetch(testEndpoint).then((response) => { expect(response.status).to.equal(401) }) }) it('should reject requests with wrong authorization', async () => { return fetch(testEndpoint, { headers: { Authorization: 'Bearer ShouldNotBeAuthorized' }, }).then((response) => { expect(response.status).to.equal(401) }) }) it('should authorize requests with correct authorization', async () => { return fetch(testEndpoint, { headers: { Authorization: 'Bearer ShouldBeAuthorized' }, }) .then((response) => response.json()) .then((json) => { expect(json.message).to.equal( 'Hello from API Gateway! - (customAuthorizers)', ) expect(json.event.requestContext.authorizer.principalId).to.equal( 'SomeRandomId', ) expect(json.event.headers.Authorization).to.equal( 'Bearer ShouldBeAuthorized', ) }) }) }) describe('API Keys', () => { let testEndpoint let startTime before(() => { testEndpoint = `${endpoint}/api-keys` startTime = Date.now() }) it('should succeed if correct API key is given', async function self() { const response = await fetch(testEndpoint, { headers: { 'X-API-Key': apiKey }, }) const result = await response.json() // API Key may take a moment to propagate, retry if (response.status === 403 && startTime > Date.now() - 1000 * 60 * 3) { log.notice('API Key rejected, retry') return self() } expect(response.status).to.equal(200) expect(result.message).to.equal('Hello from API Gateway! - (apiKeys)') return null }) it('should reject a request with an invalid API Key', async () => { return fetch(testEndpoint).then((response) => { expect(response.status).to.equal(403) }) }) }) describe('Using stage specific configuration', () => { before(async () => { await updateConfig({ provider: { tags: { foo: 'bar', baz: 'qux', }, tracing: { apiGateway: true, }, logs: { restApi: true, }, }, }) await deployService(serviceDir) }) it('should update the stage without service interruptions', async () => { // re-using the endpoint from the "minimal" test case const testEndpoint = `${endpoint}` return confirmCloudWatchLogs( `/aws/api-gateway/${stackName}`, () => fetch(`${testEndpoint}`, { method: 'GET' }) .then((response) => response.json()) // Confirm that APIGW responds as expected .then((json) => expect(json.message).to.equal( 'Hello from API Gateway! - (minimal)', ), ), // Confirm that CloudWatch logs for APIGW are written ).then((events) => expect(events.length > 0).to.equal(true)) }) }) describe('Integration Lambda Timeout', () => { it('should result with 504 status code', async () => fetch(`${endpoint}/integration-lambda-timeout`).then((response) => expect(response.status).to.equal(504), )) }) })