From bf8f9ad98d57f8635cd93c0e512daeba99504b2c Mon Sep 17 00:00:00 2001 From: Philipp Muens Date: Wed, 19 Oct 2016 10:57:04 +0200 Subject: [PATCH] Add additional API Gateway tests for lambda proxy integration --- tests/integration/all.js | 3 + .../api-keys/service/handler.js | 13 +++ .../api-keys/service/serverless.yml | 16 ++++ .../api-keys/tests.js | 85 +++++++++++++++++++ .../cors/service/handler.js | 16 ++++ .../cors/service/serverless.yml | 26 ++++++ .../integration-lambda-proxy/cors/tests.js | 62 ++++++++++++++ .../custom-authorizers/service/handler.js | 45 ++++++++++ .../custom-authorizers/service/serverless.yml | 16 ++++ .../custom-authorizers/tests.js | 63 ++++++++++++++ .../simple-api/service/handler.js | 3 - 11 files changed, 345 insertions(+), 3 deletions(-) create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml create mode 100644 tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js diff --git a/tests/integration/all.js b/tests/integration/all.js index cc8afd030..7baa2f81b 100644 --- a/tests/integration/all.js +++ b/tests/integration/all.js @@ -13,6 +13,9 @@ require('./aws/api-gateway/integration-lambda/cors/tests'); require('./aws/api-gateway/integration-lambda/api-keys/tests'); // Integration: Lambda Proxy require('./aws/api-gateway/integration-lambda-proxy/simple-api/tests'); +require('./aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests'); +require('./aws/api-gateway/integration-lambda-proxy/cors/tests'); +require('./aws/api-gateway/integration-lambda-proxy/api-keys/tests'); // Schedule require('./aws/schedule/multiple-schedules-multiple-functions/tests'); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js new file mode 100644 index 000000000..a33dec9e5 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/handler.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports.hello = (event, context, callback) => { + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'Hello from API Gateway!', + event, + }), + }; + + callback(null, response); +}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml new file mode 100644 index 000000000..1b0e532a2 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/service/serverless.yml @@ -0,0 +1,16 @@ +service: aws-nodejs # NOTE: update this with your service name + +provider: + name: aws + runtime: nodejs4.3 + apiKeys: + - WillBeReplacedBeforeDeployment + +functions: + hello: + handler: handler.hello + events: + - http: + path: hello + method: GET + private: true diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js new file mode 100644 index 000000000..42b645560 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/api-keys/tests.js @@ -0,0 +1,85 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const BbPromise = require('bluebird'); +const execSync = require('child_process').execSync; +const AWS = require('aws-sdk'); +const _ = require('lodash'); +const fetch = require('node-fetch'); +const fse = require('fs-extra'); +const crypto = require('crypto'); + +const Utils = require('../../../../../utils/index'); + +const CF = new AWS.CloudFormation({ region: 'us-east-1' }); +const APIG = new AWS.APIGateway({ region: 'us-east-1' }); +BbPromise.promisifyAll(CF, { suffix: 'Promised' }); +BbPromise.promisifyAll(APIG, { suffix: 'Promised' }); + +describe('AWS - API Gateway (Integration: Lambda Proxy): API keys test', function () { + this.timeout(0); + + let stackName; + let endpoint; + let apiKey; + + before(() => { + stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + + // replace name of the API key with something unique + const serverlessYmlFilePath = path.join(process.cwd(), 'serverless.yml'); + let serverlessYmlFileContent = fse.readFileSync(serverlessYmlFilePath).toString(); + + const apiKeyName = crypto.randomBytes(8).toString('hex'); + + serverlessYmlFileContent = serverlessYmlFileContent + .replace(/WillBeReplacedBeforeDeployment/, apiKeyName); + + fse.writeFileSync(serverlessYmlFilePath, serverlessYmlFileContent); + + Utils.deployService(); + }); + + it('should expose the endpoint(s) in the CloudFormation Outputs', () => + CF.describeStacksPromised({ StackName: stackName }) + .then((result) => _.find(result.Stacks[0].Outputs, + { OutputKey: 'ServiceEndpoint' }).OutputValue) + .then((endpointOutput) => { + endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; + endpoint = `${endpoint}/hello`; + }) + ); + + it('should expose the API key(s) with its values when running the info command', () => { + const info = execSync(`${Utils.serverlessExec} info`); + + const stringifiedOutput = (new Buffer(info, 'base64').toString()); + + // some regex magic to extract the first API key value from the info output + apiKey = stringifiedOutput.match(/(api keys:\n)(\s*)(.+):(\s*)(.+)/)[5]; + + expect(apiKey.length).to.be.above(0); + }); + + it('should reject a request with an invalid API Key', () => + fetch(endpoint) + .then((response) => { + expect(response.status).to.equal(403); + }) + ); + + it('should succeed if correct API key is given', () => + fetch(endpoint, { headers: { 'x-api-key': apiKey } }) + .then(response => response.json()) + .then((json) => { + expect(json.message).to.equal('Hello from API Gateway!'); + expect(json.event.requestContext.identity.apiKey).to.equal(apiKey); + expect(json.event.headers['x-api-key']).to.equal(apiKey); + }) + ); + + after(() => { + Utils.removeService(); + }); +}); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js new file mode 100644 index 000000000..af4dfe714 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/handler.js @@ -0,0 +1,16 @@ +'use strict'; + +module.exports.hello = (event, context, callback) => { + const response = { + statusCode: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + body: JSON.stringify({ + message: 'Hello from API Gateway!', + input: event, + }), + }; + + callback(null, response); +}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml new file mode 100644 index 000000000..785bb7ea3 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/service/serverless.yml @@ -0,0 +1,26 @@ +service: aws-nodejs # NOTE: update this with your service name + +provider: + name: aws + runtime: nodejs4.3 + +functions: + hello: + handler: handler.hello + events: + - http: + method: GET + path: simple-cors + cors: true + - http: + method: GET + path: complex-cors + cors: + origins: + - '*' + headers: + - Content-Type + - X-Amz-Date + - Authorization + - X-Api-Key + - X-Amz-Security-Token diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js new file mode 100644 index 000000000..5781519f0 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/cors/tests.js @@ -0,0 +1,62 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const BbPromise = require('bluebird'); +const AWS = require('aws-sdk'); +const _ = require('lodash'); +const fetch = require('node-fetch'); + +const Utils = require('../../../../../utils/index'); + +const CF = new AWS.CloudFormation({ region: 'us-east-1' }); +BbPromise.promisifyAll(CF, { suffix: 'Promised' }); + +describe('AWS - API Gateway (Integration: Lambda Proxy): CORS test', function () { + this.timeout(0); + + let stackName; + let endpointBase; + + before(() => { + stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + Utils.deployService(); + }); + + it('should expose the endpoint(s) in the CloudFormation Outputs', () => + CF.describeStacksPromised({ StackName: stackName }) + .then((result) => _.find(result.Stacks[0].Outputs, + { OutputKey: 'ServiceEndpoint' }).OutputValue) + .then((endpointOutput) => { + endpointBase = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; + }) + ); + + it('should setup CORS support with simple string config', () => + fetch(`${endpointBase}/simple-cors`, { method: 'OPTIONS' }) + .then((response) => { + const headers = response.headers; + + expect(headers.get('access-control-allow-headers')) + .to.equal('Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'); + expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); + expect(headers.get('access-control-allow-origin')).to.equal('*'); + }) + ); + + it('should setup CORS support with complex object config', () => + fetch(`${endpointBase}/complex-cors`, { method: 'OPTIONS' }) + .then((response) => { + const headers = response.headers; + + expect(headers.get('access-control-allow-headers')) + .to.equal('Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'); + expect(headers.get('access-control-allow-methods')).to.equal('OPTIONS,GET'); + expect(headers.get('access-control-allow-origin')).to.equal('*'); + }) + ); + + after(() => { + Utils.removeService(); + }); +}); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js new file mode 100644 index 000000000..08f981b24 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/handler.js @@ -0,0 +1,45 @@ +'use strict'; + +const generatePolicy = (principalId, effect, resource) => { + const authResponse = {}; + authResponse.principalId = principalId; + + if (effect && resource) { + const policyDocument = {}; + policyDocument.Version = '2012-10-17'; + policyDocument.Statement = []; + + const statementOne = {}; + statementOne.Action = 'execute-api:Invoke'; + statementOne.Effect = effect; + statementOne.Resource = resource; + policyDocument.Statement[0] = statementOne; + authResponse.policyDocument = policyDocument; + } + + return authResponse; +}; + +// protected function +module.exports.hello = (event, context, callback) => { + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'Successfully authorized!', + event, + }), + }; + + callback(null, response); +}; + +// auth function +module.exports.auth = (event, context) => { + const token = event.authorizationToken.split(' '); + + if (token[0] === 'Bearer' && token[1] === 'ShouldBeAuthorized') { + context.succeed(generatePolicy('SomeRandomId', 'Allow', '*')); + } + + context.fail('Unauthorized'); +}; diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml new file mode 100644 index 000000000..2661daecc --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/service/serverless.yml @@ -0,0 +1,16 @@ +service: aws-nodejs # NOTE: update this with your service name + +provider: + name: aws + runtime: nodejs4.3 + +functions: + hello: + handler: handler.hello + events: + - http: + path: hello + method: GET + authorizer: auth + auth: + handler: handler.auth diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js new file mode 100644 index 000000000..ce9676147 --- /dev/null +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/custom-authorizers/tests.js @@ -0,0 +1,63 @@ +'use strict'; + +const path = require('path'); +const expect = require('chai').expect; +const BbPromise = require('bluebird'); +const AWS = require('aws-sdk'); +const _ = require('lodash'); +const fetch = require('node-fetch'); + +const Utils = require('../../../../../utils/index'); + +const CF = new AWS.CloudFormation({ region: 'us-east-1' }); +BbPromise.promisifyAll(CF, { suffix: 'Promised' }); + +describe('AWS - API Gateway (Integration: Lambda Proxy): Custom authorizers test', function () { + this.timeout(0); + + let stackName; + let endpoint; + + before(() => { + stackName = Utils.createTestService('aws-nodejs', path.join(__dirname, 'service')); + Utils.deployService(); + }); + + it('should expose the endpoint(s) in the CloudFormation Outputs', () => + CF.describeStacksPromised({ StackName: stackName }) + .then((result) => _.find(result.Stacks[0].Outputs, + { OutputKey: 'ServiceEndpoint' }).OutputValue) + .then((endpointOutput) => { + endpoint = endpointOutput.match(/https:\/\/.+\.execute-api\..+\.amazonaws\.com.+/)[0]; + endpoint = `${endpoint}/hello`; + }) + ); + + it('should reject requests without authorization', () => + fetch(endpoint) + .then((response) => { + expect(response.status).to.equal(401); + }) + ); + + it('should reject requests with wrong authorization', () => + fetch(endpoint, { headers: { Authorization: 'Bearer ShouldNotBeAuthorized' } }) + .then((response) => { + expect(response.status).to.equal(401); + }) + ); + + it('should authorize requests with correct authorization', () => + fetch(endpoint, { headers: { Authorization: 'Bearer ShouldBeAuthorized' } }) + .then(response => response.json()) + .then((json) => { + expect(json.message).to.equal('Successfully authorized!'); + expect(json.event.requestContext.authorizer.principalId).to.equal('SomeRandomId'); + expect(json.event.headers.Authorization).to.equal('Bearer ShouldBeAuthorized'); + }) + ); + + after(() => { + Utils.removeService(); + }); +}); diff --git a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js index 6b8444ddf..60924d588 100644 --- a/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js +++ b/tests/integration/aws/api-gateway/integration-lambda-proxy/simple-api/service/handler.js @@ -10,7 +10,4 @@ module.exports.hello = (event, context, callback) => { }; callback(null, response); - - // Use this code if you don't use the http event with the LAMBDA-PROXY integration - // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); };