UNPKG

serverless

Version:

Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more

1,369 lines (1,275 loc) • 66.4 kB
'use strict'; const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileStreamEvents = require('./index'); const Serverless = require('../../../../../../Serverless'); describe('AwsCompileStreamEvents', () => { let serverless; let awsCompileStreamEvents; beforeEach(() => { serverless = new Serverless(); serverless.service.provider.compiledCloudFormationTemplate = { Resources: { IamRoleLambdaExecution: { Properties: { Policies: [ { PolicyDocument: { Statement: [], }, }, ], }, }, }, }; serverless.setProvider('aws', new AwsProvider(serverless)); awsCompileStreamEvents = new AwsCompileStreamEvents(serverless); awsCompileStreamEvents.serverless.service.service = 'new-service'; }); describe('#constructor()', () => { it('should set the provider variable to be an instance of AwsProvider', () => expect(awsCompileStreamEvents.provider).to.be.instanceof(AwsProvider)); }); describe('#compileStreamEvents()', () => { it('should throw an error if the "consumer" property is not a string, object, or boolean', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { type: 'kinesis', arn: 'arn:aws:kinesis:us-east-1:123456789012:stream/myStream', consumer: 42, }, }, ], }, }; }); it('should throw an error if stream event type is not a string or an object', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: 42, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('should throw an error if the "arn" property is not given', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: null, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('should throw an error if the "arn" property contains an unsupported stream type', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:NOT-SUPPORTED:us-east-1:123456789012:stream/myStream', }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('should not throw error or merge role statements if default policy is not present', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if custom IAM role is set in function', () => { awsCompileStreamEvents.serverless.service.functions = { first: { role: 'arn:aws:iam::account:role/foo', events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn ).to.be.instanceof(Array); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn.length ).to.equal(0); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if custom IAM role name reference is set in function', () => { const roleLogicalId = 'RoleLogicalId'; awsCompileStreamEvents.serverless.service.functions = { first: { role: roleLogicalId, events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn ).to.equal(roleLogicalId); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if custom IAM role reference is set in function', () => { const roleLogicalId = 'RoleLogicalId'; awsCompileStreamEvents.serverless.service.functions = { first: { role: { 'Fn::GetAtt': [roleLogicalId, 'Arn'] }, events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn ).to.equal(roleLogicalId); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if custom IAM role is set in provider', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; awsCompileStreamEvents.serverless.service.provider.role = 'arn:aws:iam::account:role/foo'; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn ).to.be.instanceof(Array); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn.length ).to.equal(0); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if IAM role is referenced from cloudformation parameters', () => { awsCompileStreamEvents.serverless.service.functions = { first: { role: { Ref: 'MyStreamRoleArn' }, events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn.length ).to.equal(0); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if IAM role is imported', () => { awsCompileStreamEvents.serverless.service.functions = { first: { role: { 'Fn::ImportValue': 'ExportedRoleId' }, events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn.length ).to.equal(0); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if custom IAM role reference is set in provider', () => { const roleLogicalId = 'RoleLogicalId'; awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; awsCompileStreamEvents.serverless.service.provider.role = { 'Fn::GetAtt': [roleLogicalId, 'Arn'], }; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn ).to.equal(roleLogicalId); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); it('should not throw error if custom IAM role name reference is set in provider', () => { const roleLogicalId = 'RoleLogicalId'; awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { // doesn't matter if DynamoDB or Kinesis stream stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, ], }, }; // pretend that the default IamRoleLambdaExecution is not in place awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution = null; awsCompileStreamEvents.serverless.service.provider.role = roleLogicalId; expect(() => { awsCompileStreamEvents.compileStreamEvents(); }).to.not.throw(Error); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .FirstEventSourceMappingDynamodbFoo.DependsOn ).to.equal(roleLogicalId); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources .IamRoleLambdaExecution ).to.equal(null); }); describe('when a DynamoDB stream ARN is given', () => { it('should create event source mappings when a DynamoDB stream ARN is given', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', batchSize: 1, startingPosition: 'STARTING_POSITION_ONE', enabled: false, }, }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/bar/stream/2', batchWindow: 15, maximumRetryAttempts: 4, }, }, { stream: 'arn:aws:dynamodb:region:account:table/baz/stream/3', }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/buzz/stream/4', bisectBatchOnFunctionError: true, batchWindow: 0, maximumRecordAgeInSeconds: 120, }, }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/fizz/stream/5', destinations: { onFailure: 'arn:aws:sns:region:account:snstopic', }, }, }, ], }, }; awsCompileStreamEvents.compileStreamEvents(); // event 1 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.arn); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.Properties.BatchSize ).to.equal( awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.batchSize ); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.Properties.StartingPosition ).to.equal( awsCompileStreamEvents.serverless.service.functions.first.events[0].stream .startingPosition ); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.Properties.Enabled ).to.equal(false); // event 2 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[1].stream.arn); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.BatchSize ).to.equal(10); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.Enabled ).to.equal(true); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.MaximumBatchingWindowInSeconds ).to.equal(15); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.MaximumRetryAttempts ).to.equal(4); // event 3 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[2].stream); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.BatchSize ).to.equal(10); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.Enabled ).to.equal(true); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.BisectBatchOnFunctionError ).to.equal(undefined); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.MaximumRecordAgeInSeconds ).to.equal(undefined); // event 4 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[3].stream.arn); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.BatchSize ).to.equal(10); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.Enabled ).to.equal(true); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.BisectBatchOnFunctionError ).to.equal(true); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.MaximumRecordAgeInSeconds ).to.equal(120); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBuzz.Properties.MaximumBatchingWindowInSeconds ).to.equal(0); // event 5 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[4].stream.arn); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.Properties.BatchSize ).to.equal(10); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.Properties.StartingPosition ).to.equal('TRIM_HORIZON'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.Properties.Enabled ).to.equal(true); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFizz.Properties.DestinationConfig.OnFailure .Destination ).to.equal( awsCompileStreamEvents.serverless.service.functions.first.events[4].stream.destinations .onFailure ); }); it('should allow specifying DynamoDB and Kinesis streams as CFN reference types', () => { awsCompileStreamEvents.serverless.service.resources.Parameters = { SomeDdbTableStreamArn: { Type: 'String', }, ForeignKinesisStreamArn: { Type: 'String', }, }; awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: { 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'] }, type: 'dynamodb', }, }, { stream: { arn: { 'Fn::ImportValue': 'ForeignKinesis' }, type: 'kinesis', }, }, { stream: { arn: { 'Fn::Join': [ ':', [ 'arn', 'aws', 'kinesis', { Ref: 'AWS::Region', }, { Ref: 'AWS::AccountId', }, 'stream/MyStream', ], ], }, type: 'kinesis', }, }, { stream: { arn: { Ref: 'SomeDdbTableStreamArn' }, type: 'dynamodb', }, }, { stream: { arn: { Ref: 'ForeignKinesisStreamArn' }, type: 'kinesis', }, }, ], }, }; awsCompileStreamEvents.compileStreamEvents(); // dynamodb with Fn::GetAtt expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbSomeDdbTable.Properties.EventSourceArn ).to.deep.equal({ 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'] }); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[0] ).to.deep.equal({ Action: [ 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', 'dynamodb:DescribeStream', 'dynamodb:ListStreams', ], Effect: 'Allow', Resource: [ { 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'], }, { Ref: 'SomeDdbTableStreamArn', }, ], }); // kinesis with Fn::ImportValue expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisForeignKinesis.Properties.EventSourceArn ).to.deep.equal({ 'Fn::ImportValue': 'ForeignKinesis' }); // kinesis with Fn::Join expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisMyStream.Properties.EventSourceArn ).to.deep.equal({ 'Fn::Join': [ ':', [ 'arn', 'aws', 'kinesis', { Ref: 'AWS::Region', }, { Ref: 'AWS::AccountId', }, 'stream/MyStream', ], ], }); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[1] ).to.deep.equal({ Effect: 'Allow', Action: [ 'kinesis:GetRecords', 'kinesis:GetShardIterator', 'kinesis:DescribeStream', 'kinesis:ListStreams', ], Resource: [ { 'Fn::ImportValue': 'ForeignKinesis', }, { 'Fn::Join': [ ':', [ 'arn', 'aws', 'kinesis', { Ref: 'AWS::Region', }, { Ref: 'AWS::AccountId', }, 'stream/MyStream', ], ], }, { Ref: 'ForeignKinesisStreamArn', }, ], }); }); it('should allow specifying OnFailure destinations as CFN reference types', () => { awsCompileStreamEvents.serverless.service.resources.Parameters = { SomeSNSArn: { Type: 'String', }, ForeignSQSArn: { Type: 'String', }, }; awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { arn: { 'Fn::GetAtt': ['SomeSNS', 'Arn'] }, type: 'sns', }, }, }, }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/bar/stream/1', destinations: { onFailure: { arn: { 'Fn::ImportValue': 'ForeignSQS' }, type: 'sqs', }, }, }, }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/baz/stream/1', destinations: { onFailure: { arn: { 'Fn::Join': [ ':', [ 'arn', 'aws', 'sqs', { Ref: 'AWS::Region', }, { Ref: 'AWS::AccountId', }, 'MyQueue', ], ], }, type: 'sqs', }, }, }, }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/buzz/stream/1', destinations: { onFailure: { arn: { Ref: 'SomeSNSArn' }, type: 'sns', }, }, }, }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/fizz/stream/1', destinations: { onFailure: { arn: { Ref: 'ForeignSQSArn' }, type: 'sqs', }, }, }, }, ], }, }; awsCompileStreamEvents.compileStreamEvents(); // sns with Fn::GetAtt expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbFoo.Properties.DestinationConfig.OnFailure .Destination ).to.deep.equal({ 'Fn::GetAtt': ['SomeSNS', 'Arn'] }); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[1] ).to.deep.equal({ Action: ['sns:Publish'], Effect: 'Allow', Resource: [ { 'Fn::GetAtt': ['SomeSNS', 'Arn'], }, { Ref: 'SomeSNSArn', }, ], }); // sqs with Fn::ImportValue expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBar.Properties.DestinationConfig.OnFailure .Destination ).to.deep.equal({ 'Fn::ImportValue': 'ForeignSQS' }); // sqs with Fn::Join expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingDynamodbBaz.Properties.DestinationConfig.OnFailure .Destination ).to.deep.equal({ 'Fn::Join': [ ':', [ 'arn', 'aws', 'sqs', { Ref: 'AWS::Region', }, { Ref: 'AWS::AccountId', }, 'MyQueue', ], ], }); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement[2] ).to.deep.equal({ Effect: 'Allow', Action: ['sqs:ListQueues', 'sqs:SendMessage'], Resource: [ { 'Fn::ImportValue': 'ForeignSQS', }, { 'Fn::Join': [ ':', [ 'arn', 'aws', 'sqs', { Ref: 'AWS::Region', }, { Ref: 'AWS::AccountId', }, 'MyQueue', ], ], }, { Ref: 'ForeignSQSArn', }, ], }); }); it('fails if Ref/dynamic stream ARN is used without defining it to the CF parameters', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: { Ref: 'SomeDdbTableStreamArn' }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if Ref/dynamic onFailure ARN is used without defining it to the CF parameters', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/fizz/stream/1', destinations: { onFailure: { arn: { Ref: 'ForeignSQSArn' }, }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if Fn::GetAtt/dynamic stream ARN is used without a type', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: { 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'] }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if Fn::GetAtt/dynamic onFailure ARN is used without a type', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { arn: { 'Fn::GetAtt': ['SomeSNS', 'Arn'] }, }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic stream ARN', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { type: 'dynamodb', arn: { 'Fn::GetAtt': ['SomeDdbTable', 'StreamArn'], 'batchSize': 1, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if keys other than Fn::GetAtt/ImportValue/Join are used for dynamic onFailure ARN', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { arn: { 'Fn::GetAtt': ['SomeSNS', 'Arn'], 'batchSize': 1, }, type: 'sns', }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if Fn::ImportValue is misused for onFailure ARN', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { arn: { 'Fn::ImportValue': { 'Fn::GetAtt': ['SomeSNS', 'Arn'], }, }, type: 'invalidType', }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if onFailure ARN is given as a string that does not start with arn', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: 'invalidARN', }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if onFailure ARN is given as a variable type other than string or object', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: 3, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if nested onFailure ARN is given as a string that does not start with arn', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { arn: 'invalidARN', }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if no arn key is given for a dynamic onFailure ARN', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { notarn: ['SomeSNS', 'Arn'], }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if destinations structure is wrong', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { notOnFailure: { arn: { 'Fn::GetAtt': ['SomeSNS', 'Arn'], 'batchSize': 1, }, }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('fails if invalid onFailure type is given', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:dynamodb:region:account:table/foo/stream/1', destinations: { onFailure: { arn: { 'Fn::GetAtt': ['SomeSNS', 'Arn'] }, type: 'invalidType', }, }, }, }, ], }, }; expect(() => awsCompileStreamEvents.compileStreamEvents()).to.throw(Error); }); it('should add the necessary IAM role statements', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: 'arn:aws:dynamodb:region:account:table/foo/stream/1', }, { stream: { arn: 'arn:aws:dynamodb:region:account:table/bar/stream/2', destinations: { onFailure: 'arn:aws:sns:region:account:snstopic', }, }, }, ], }, }; const iamRoleStatements = [ { Effect: 'Allow', Action: [ 'dynamodb:GetRecords', 'dynamodb:GetShardIterator', 'dynamodb:DescribeStream', 'dynamodb:ListStreams', ], Resource: [ 'arn:aws:dynamodb:region:account:table/foo/stream/1', 'arn:aws:dynamodb:region:account:table/bar/stream/2', ], }, { Effect: 'Allow', Action: ['sns:Publish'], Resource: ['arn:aws:sns:region:account:snstopic'], }, ]; awsCompileStreamEvents.compileStreamEvents(); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement ).to.deep.equal(iamRoleStatements); }); }); describe('when a Kinesis stream ARN is given', () => { it('should create event source mappings when a Kinesis stream ARN is given', () => { awsCompileStreamEvents.serverless.service.functions = { first: { events: [ { stream: { arn: 'arn:aws:kinesis:region:account:stream/foo', batchSize: 1, startingPosition: 'STARTING_POSITION_ONE', enabled: false, parallelizationFactor: 10, }, }, { stream: { arn: 'arn:aws:kinesis:region:account:stream/bar', batchWindow: 15, maximumRetryAttempts: 5, }, }, { stream: 'arn:aws:kinesis:region:account:stream/baz', }, { stream: { arn: 'arn:aws:kinesis:region:account:stream/buzz', bisectBatchOnFunctionError: true, maximumRecordAgeInSeconds: 180, }, }, { stream: { arn: 'arn:aws:kinesis:region:account:table/fizz/stream/5', destinations: { onFailure: 'arn:aws:sns:region:account:snstopic', }, }, }, { stream: { arn: 'arn:aws:kinesis:region:account:stream/abc', consumer: true, }, }, { stream: { arn: 'arn:aws:kinesis:region:account:stream/xyz', consumer: 'arn:aws:kinesis:region:account:stream/xyz/consumer/foobar:1558544531', }, }, { stream: { arn: 'arn:aws:kinesis:region:account:stream/def', consumer: false, }, }, ], }, }; awsCompileStreamEvents.compileStreamEvents(); // event 1 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.arn); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.Properties.BatchSize ).to.equal( awsCompileStreamEvents.serverless.service.functions.first.events[0].stream.batchSize ); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.Properties.StartingPosition ).to.equal( awsCompileStreamEvents.serverless.service.functions.first.events[0].stream .startingPosition ); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.Properties.ParallelizationFactor ).to.equal( awsCompileStreamEvents.serverless.service.functions.first.events[0].stream .parallelizationFactor ); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisFoo.Properties.Enabled ).to.equal(false); // event 2 expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisBar.Type ).to.equal('AWS::Lambda::EventSourceMapping'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisBar.DependsOn ).to.equal('IamRoleLambdaExecution'); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisBar.Properties.EventSourceArn ).to.equal(awsCompileStreamEvents.serverless.service.functions.first.events[1].stream.arn); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisBar.Properties.BatchSize ).to.equal(10); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisBar.Properties.ParallelizationFactor ).to.equal(undefined); expect( awsCompileStreamEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstEventSourceMappingKinesisBar.Properties.StartingPosition ).to.equ