serverless/test/integration/aws/api-gateway.test.js
2024-05-29 11:51:04 -04:00

261 lines
8.0 KiB
JavaScript

'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),
))
})
})