From 2a8fedc2f3dc6be9ffe656b91a493efedd54898d Mon Sep 17 00:00:00 2001 From: "Eslam A. Hefnawy" Date: Wed, 27 Feb 2019 17:34:29 +0300 Subject: [PATCH] added tests --- lib/plugins/aws/lib/naming.test.js | 7 + .../compile/events/websockets/index.test.js | 7 +- .../events/websockets/lib/authorizers.test.js | 109 ++++++++++ .../events/websockets/lib/permissions.test.js | 76 +++++++ .../events/websockets/lib/routes.test.js | 46 +++++ .../events/websockets/lib/validate.test.js | 186 +++++++++++++++++- 6 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 643981306..f479e5925 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -283,6 +283,13 @@ describe('#naming()', () => { }); }); + describe('#getWebsocketsAuthorizerLogicalId()', () => { + it('should return the websockets authorizer logical id', () => { + expect(sdk.naming.getWebsocketsAuthorizerLogicalId('auth')) + .to.equal('AuthWebsocketsAuthorizer'); + }); + }); + describe('#getApiGatewayName()', () => { it('should return the composition of stage & service name if custom name not provided', () => { serverless.service.service = 'myService'; diff --git a/lib/plugins/aws/package/compile/events/websockets/index.test.js b/lib/plugins/aws/package/compile/events/websockets/index.test.js index edacacb45..ad3826e25 100644 --- a/lib/plugins/aws/package/compile/events/websockets/index.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/index.test.js @@ -35,6 +35,7 @@ describe('AwsCompileWebsocketsEvents', () => { describe('#constructor()', () => { let compileApiStub; let compileIntegrationsStub; + let compileAuthorizersStub; let compilePermissionsStub; let compileRoutesStub; let compileDeploymentStub; @@ -45,6 +46,8 @@ describe('AwsCompileWebsocketsEvents', () => { .stub(awsCompileWebsocketsEvents, 'compileApi').resolves(); compileIntegrationsStub = sinon .stub(awsCompileWebsocketsEvents, 'compileIntegrations').resolves(); + compileAuthorizersStub = sinon + .stub(awsCompileWebsocketsEvents, 'compileAuthorizers').resolves(); compilePermissionsStub = sinon .stub(awsCompileWebsocketsEvents, 'compilePermissions').resolves(); compileRoutesStub = sinon @@ -58,6 +61,7 @@ describe('AwsCompileWebsocketsEvents', () => { afterEach(() => { awsCompileWebsocketsEvents.compileApi.restore(); awsCompileWebsocketsEvents.compileIntegrations.restore(); + awsCompileWebsocketsEvents.compileAuthorizers.restore(); awsCompileWebsocketsEvents.compilePermissions.restore(); awsCompileWebsocketsEvents.compileRoutes.restore(); awsCompileWebsocketsEvents.compileDeployment.restore(); @@ -91,7 +95,8 @@ describe('AwsCompileWebsocketsEvents', () => { expect(validateStub.calledOnce).to.be.equal(true); expect(compileApiStub.calledAfter(validateStub)).to.be.equal(true); expect(compileIntegrationsStub.calledAfter(compileApiStub)).to.be.equal(true); - expect(compilePermissionsStub.calledAfter(compileIntegrationsStub)).to.be.equal(true); + expect(compileAuthorizersStub.calledAfter(compileIntegrationsStub)).to.be.equal(true); + expect(compilePermissionsStub.calledAfter(compileAuthorizersStub)).to.be.equal(true); expect(compileRoutesStub.calledAfter(compilePermissionsStub)).to.be.equal(true); expect(compileDeploymentStub.calledAfter(compileRoutesStub)).to.be.equal(true); expect(compileStageStub.calledAfter(compileDeploymentStub)).to.be.equal(true); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js new file mode 100644 index 000000000..1925ae6ce --- /dev/null +++ b/lib/plugins/aws/package/compile/events/websockets/lib/authorizers.test.js @@ -0,0 +1,109 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsCompileWebsocketsEvents = require('../index'); +const Serverless = require('../../../../../../../Serverless'); +const AwsProvider = require('../../../../../provider/awsProvider'); + +describe('#compileAuthorizers()', () => { + let awsCompileWebsocketsEvents; + + beforeEach(() => { + const serverless = new Serverless(); + serverless.setProvider('aws', new AwsProvider(serverless)); + serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; + + awsCompileWebsocketsEvents = new AwsCompileWebsocketsEvents(serverless); + + awsCompileWebsocketsEvents.websocketsApiLogicalId + = awsCompileWebsocketsEvents.provider.naming.getWebsocketsApiLogicalId(); + }); + + it('should create an authorizer resource for routes with authorizer definition', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + }, + }, + ], + }; + + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + AuthWebsocketsAuthorizer: { + Type: 'AWS::ApiGatewayV2::Authorizer', + Properties: { + ApiId: { + Ref: 'WebsocketsApi', + }, + Name: 'auth', + AuthorizerType: 'REQUEST', + AuthorizerUri: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':apigateway:', + { + Ref: 'AWS::Region', + }, + ':lambda:path/2015-03-31/functions/', + { + 'Fn::GetAtt': [ + 'AuthLambdaFunction', + 'Arn', + ], + }, + '/invocations', + ], + ], + }, + IdentitySource: ['route.request.header.Auth'], + }, + }, + }); + }); + }); + + it('should NOT create an authorizer resource for routes with not authorizer definition', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + }, + ], + }; + + return awsCompileWebsocketsEvents.compileAuthorizers().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({}); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js index 4930cde80..25e6a8663 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/permissions.test.js @@ -94,4 +94,80 @@ describe('#compilePermissions()', () => { }); }); }); + + it('should create a permission resource for authorizer function', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + permission: 'AuthLambdaPermissionWebsockets', + }, + }, + ], + }; + + return awsCompileWebsocketsEvents.compilePermissions().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + FirstLambdaPermissionWebsockets: { + Type: 'AWS::Lambda::Permission', + DependsOn: [ + 'WebsocketsApi', + 'FirstLambdaFunction', + ], + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'FirstLambdaFunction', 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: { + 'Fn::Join': [ + '', + [ + 'apigateway.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + }, + }, + AuthLambdaPermissionWebsockets: { + Type: 'AWS::Lambda::Permission', + DependsOn: [ + 'WebsocketsApi', + 'AuthLambdaPermissionWebsockets', + ], + Properties: { + FunctionName: { + 'Fn::GetAtt': [ + 'AuthLambdaPermissionWebsockets', + 'Arn', + ], + }, + Action: 'lambda:InvokeFunction', + Principal: { + 'Fn::Join': [ + '', + [ + 'apigateway.', + { + Ref: 'AWS::URLSuffix', + }, + ], + ], + }, + }, + }, + }); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js index 2a1f8eea6..5a4fb203a 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/routes.test.js @@ -82,4 +82,50 @@ describe('#compileRoutes()', () => { }); }); }); + + it('should set authorizer property for the connect route', () => { + awsCompileWebsocketsEvents.validated = { + events: [ + { + functionName: 'First', + route: '$connect', + authorizer: { + name: 'auth', + }, + }, + ], + }; + + return awsCompileWebsocketsEvents.compileRoutes().then(() => { + const resources = awsCompileWebsocketsEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources; + + expect(resources).to.deep.equal({ + SconnectWebsocketsRoute: { + Type: 'AWS::ApiGatewayV2::Route', + Properties: { + ApiId: { + Ref: 'WebsocketsApi', + }, + RouteKey: '$connect', + AuthorizationType: 'CUSTOM', + AuthorizerId: { + Ref: awsCompileWebsocketsEvents.provider.naming + .getWebsocketsAuthorizerLogicalId('auth'), + }, + Target: { + 'Fn::Join': [ + '/', + [ + 'integrations', { + Ref: 'FirstWebsocketsIntegration', + }, + ], + ], + }, + }, + }, + }); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js index fbb0dbcf1..7891ac518 100644 --- a/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/websockets/lib/validate.test.js @@ -5,7 +5,7 @@ const AwsCompileWebsocketsEvents = require('../index'); const Serverless = require('../../../../../../../Serverless'); const AwsProvider = require('../../../../../provider/awsProvider'); -describe('#validate()', () => { +describe.only('#validate()', () => { let serverless; let awsCompileWebsocketsEvents; @@ -59,6 +59,172 @@ describe('#validate()', () => { ]); }); + it('should add authorizer config when authorizer is specified as a string', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: 'auth', + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + permission: 'AuthLambdaFunction', + }, + }, + ]); + }); + + it('should add authorizer config when authorizer is specified as a string with arn', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: 'arn:aws:auth', + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + 'arn:aws:auth', + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth'], + permission: 'arn:aws:auth', + }, + }, + ]); + }); + + it('should add authorizer config when authorizer is specified as an object', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: { + name: 'auth', + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + }, + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['AuthLambdaFunction', 'Arn'] }, + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + permission: 'AuthLambdaFunction', + }, + }, + ]); + }); + + it('should add authorizer config when authorizer is specified as an object with arn', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: { + arn: 'arn:aws:auth', + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + }, + }, + }, + ], + }, + }; + const validated = awsCompileWebsocketsEvents.validate(); + expect(validated.events).to.deep.equal([ + { + functionName: 'first', + route: '$connect', + authorizer: { + name: 'auth', + uri: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + 'arn:aws:auth', + '/invocations', + ], + ], + }, + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + permission: 'arn:aws:auth', + }, + }, + ]); + }); + it('should ignore non-websocket events', () => { awsCompileWebsocketsEvents.serverless.service.functions = { first: { @@ -86,6 +252,24 @@ describe('#validate()', () => { expect(() => awsCompileWebsocketsEvents.validate()).to.throw(/set the "route"/); }); + it('should reject an authorizer definition without name nor arn', () => { + awsCompileWebsocketsEvents.serverless.service.functions = { + first: { + events: [ + { + websocket: { + route: '$connect', + authorizer: { + identitySource: ['route.request.header.Auth', 'route.request.querystring.Auth'], + }, + }, + }, + ], + }, + }; + expect(() => awsCompileWebsocketsEvents.validate()).to.throw(/You must specify name or arn/); + }); + it('should reject a usage of both, http and websocket event types', () => { awsCompileWebsocketsEvents.serverless.service.functions = { first: {