Skip to content

Commit 19ffa35

Browse files
- refactored the IAM role test
- added tests for SNS, SQS, DynamoDB and handling for nested parallel states
1 parent 9b82ef5 commit 19ffa35

File tree

1 file changed

+279
-56
lines changed

1 file changed

+279
-56
lines changed

lib/deploy/stepFunctions/compileIamRole.test.js

Lines changed: 279 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const _ = require('lodash');
34
const expect = require('chai').expect;
45
const Serverless = require('serverless/lib/Serverless');
56
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider');
@@ -91,82 +92,304 @@ describe('#compileIamRole', () => {
9192
const worldLambda = 'arn:aws:lambda:*:*:function:world';
9293
const fooLambda = 'arn:aws:lambda:us-west-2::function:foo_';
9394
const barLambda = 'arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:bar';
94-
const bazLambda = 'arn:aws:lambda:#{AWS::Region}:#{AWS::AccountId}:function:${baz}';
95-
serverless.service.stepFunctions = {
96-
stateMachines: {
97-
myStateMachine1: {
98-
name: 'stateMachineBeta1',
99-
definition: {
100-
StartAt: 'Hello',
101-
States: {
102-
Hello: {
103-
Type: 'Task',
104-
Resource: helloLambda,
105-
End: true,
106-
},
107-
},
95+
96+
const genStateMachine = (name, lambda1, lambda2) => ({
97+
name,
98+
definition: {
99+
StartAt: 'A',
100+
States: {
101+
A: {
102+
Type: 'Task',
103+
Resource: lambda1,
104+
Next: 'B',
105+
},
106+
B: {
107+
Type: 'Task',
108+
Resource: lambda2,
109+
End: true,
108110
},
109111
},
110-
myStateMachine2: {
111-
name: 'stateMachineBeta2',
112-
definition: {
113-
StartAt: 'World',
114-
States: {
115-
World: {
116-
Type: 'Task',
117-
Resource: worldLambda,
118-
End: true,
119-
},
112+
},
113+
});
114+
115+
serverless.service.stepFunctions = {
116+
stateMachines: {
117+
myStateMachine1: genStateMachine('stateMachineBeta1', helloLambda, worldLambda),
118+
myStateMachine2: genStateMachine('stateMachineBeta2', fooLambda, barLambda),
119+
},
120+
};
121+
122+
serverlessStepFunctions.compileIamRole();
123+
const policy = serverlessStepFunctions.serverless.service
124+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
125+
.Properties.Policies[0];
126+
expect(policy.PolicyDocument.Statement[0].Resource)
127+
.to.be.deep.equal([helloLambda, worldLambda, fooLambda, barLambda]);
128+
});
129+
130+
it('should give sns:Publish permission for only SNS topics referenced by state machine', () => {
131+
const helloTopic = 'arn:aws:sns:#{AWS::Region}:#{AWS::AccountId}:hello';
132+
const worldTopic = 'arn:aws:sns:us-east-1:#{AWS::AccountId}:world';
133+
134+
const genStateMachine = (name, snsTopic) => ({
135+
name,
136+
definition: {
137+
StartAt: 'A',
138+
States: {
139+
A: {
140+
Type: 'Task',
141+
Resource: 'arn:aws:states:::sns:publish',
142+
Parameters: {
143+
Message: '42',
144+
TopicArn: snsTopic,
120145
},
146+
End: true,
121147
},
122148
},
123-
myStateMachine3: {
124-
name: 'stateMachineBeta3',
125-
definition: {
126-
StartAt: 'Foo',
127-
States: {
128-
Hello: {
129-
Type: 'Task',
130-
Resource: fooLambda,
131-
End: true,
132-
},
149+
},
150+
});
151+
152+
serverless.service.stepFunctions = {
153+
stateMachines: {
154+
myStateMachine1: genStateMachine('stateMachineBeta1', helloTopic),
155+
myStateMachine2: genStateMachine('stateMachineBeta2', worldTopic),
156+
},
157+
};
158+
159+
serverlessStepFunctions.compileIamRole();
160+
const policy = serverlessStepFunctions.serverless.service
161+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
162+
.Properties.Policies[0];
163+
expect(policy.PolicyDocument.Statement[0].Resource)
164+
.to.be.deep.equal([helloTopic, worldTopic]);
165+
});
166+
167+
it('should give sqs:SendMessage permission for only SQS referenced by state machine', () => {
168+
const helloQueue = 'https://sqs.#{AWS::Region}.amazonaws.com/#{AWS::AccountId}/hello';
169+
const helloQueueArn = 'arn:aws:sqs:#{AWS::Region}:#{AWS::AccountId}:hello';
170+
const worldQueue = 'https://sqs.us-east-1.amazonaws.com/#{AWS::AccountId}/world';
171+
const worldQueueArn = 'arn:aws:sqs:us-east-1:#{AWS::AccountId}:world';
172+
173+
const genStateMachine = (name, queueUrl) => ({
174+
name,
175+
definition: {
176+
StartAt: 'A',
177+
States: {
178+
A: {
179+
Type: 'Task',
180+
Resource: 'arn:aws:states:::sqs:sendMessage',
181+
Parameters: {
182+
QueueUrl: queueUrl,
183+
Message: '42',
133184
},
185+
End: true,
134186
},
135187
},
136-
myStateMachine4: {
137-
name: 'stateMachineBeta4',
138-
definition: {
139-
StartAt: 'Bar',
140-
States: {
141-
Hello: {
142-
Type: 'Task',
143-
Resource: barLambda,
144-
End: true,
145-
},
188+
},
189+
});
190+
191+
serverless.service.stepFunctions = {
192+
stateMachines: {
193+
myStateMachine1: genStateMachine('stateMachineBeta1', helloQueue),
194+
myStateMachine2: genStateMachine('stateMachineBeta2', worldQueue),
195+
},
196+
};
197+
198+
serverlessStepFunctions.compileIamRole();
199+
const policy = serverlessStepFunctions.serverless.service
200+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
201+
.Properties.Policies[0];
202+
expect(policy.PolicyDocument.Statement[0].Resource)
203+
.to.be.deep.equal([helloQueueArn, worldQueueArn]);
204+
});
205+
206+
it('should give dynamodb permission for only tables referenced by state machine', () => {
207+
const helloTable = 'hello';
208+
const helloTableArn = {
209+
'Fn::Join': [
210+
':', ['arn:aws:dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/hello'],
211+
],
212+
};
213+
const worldTable = 'world';
214+
const worldTableArn = {
215+
'Fn::Join': [
216+
':', ['arn:aws:dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/world'],
217+
],
218+
};
219+
220+
const genStateMachine = (name, tableName) => ({
221+
name,
222+
definition: {
223+
StartAt: 'A',
224+
States: {
225+
A: {
226+
Type: 'Task',
227+
Resource: 'arn:aws:states:::dynamodb:updateItem',
228+
Parameters: {
229+
TableName: tableName,
146230
},
231+
Next: 'B',
147232
},
148-
},
149-
myStateMachine5: {
150-
name: 'stateMachineBeta5',
151-
definition: {
152-
StartAt: 'Baz',
153-
States: {
154-
Hello: {
155-
Type: 'Task',
156-
Resource: bazLambda,
157-
End: true,
158-
},
233+
B: {
234+
Type: 'Task',
235+
Resource: 'arn:aws:states:::dynamodb:putItem',
236+
Parameters: {
237+
TableName: tableName,
159238
},
239+
End: true,
160240
},
161241
},
162242
},
243+
});
244+
245+
serverless.service.stepFunctions = {
246+
stateMachines: {
247+
myStateMachine1: genStateMachine('stateMachineBeta1', helloTable),
248+
myStateMachine2: genStateMachine('stateMachineBeta2', worldTable),
249+
},
163250
};
164251

165252
serverlessStepFunctions.compileIamRole();
166253
const policy = serverlessStepFunctions.serverless.service
167254
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
168255
.Properties.Policies[0];
256+
expect(policy.PolicyDocument.Statement[0].Action)
257+
.to.be.deep.equal(['dynamodb:UpdateItem', 'dynamodb:PutItem']);
169258
expect(policy.PolicyDocument.Statement[0].Resource)
170-
.to.be.deep.equal([helloLambda, worldLambda, fooLambda, barLambda, bazLambda]);
259+
.to.be.deep.equal([helloTableArn, worldTableArn]);
260+
});
261+
262+
it('should handle nested parallel states', () => {
263+
const getStateMachine = (name, lambdaArn, snsTopicArn, sqsQueueUrl, dynamodbTable) => ({
264+
name,
265+
definition: {
266+
StartAt: 'A',
267+
States: {
268+
A: {
269+
Type: 'Task',
270+
Resource: 'arn:aws:states:::sns:publish',
271+
Parameters: {
272+
Message: '42',
273+
TopicArn: snsTopicArn,
274+
},
275+
Next: 'Parallel',
276+
},
277+
Parallel: {
278+
Type: 'Parallel',
279+
Branches: [{
280+
StartAt: 'B',
281+
States: {
282+
B: {
283+
Type: 'Task',
284+
Resource: lambdaArn,
285+
End: true,
286+
},
287+
},
288+
}, {
289+
StartAt: 'C',
290+
States: {
291+
C: {
292+
Type: 'Task',
293+
Resource: 'arn:aws:states:::sqs:sendMessage',
294+
Parameters: {
295+
QueueUrl: sqsQueueUrl,
296+
},
297+
},
298+
},
299+
}, {
300+
StartAt: 'NestedParallel',
301+
States: {
302+
NestedParallel: {
303+
Type: 'Parallel',
304+
Branches: [{
305+
StartAt: 'D',
306+
States: {
307+
D: {
308+
Type: 'Task',
309+
Resource: 'arn:aws:states:::dynamodb:updateItem',
310+
Parameters: {
311+
TableName: dynamodbTable,
312+
},
313+
},
314+
},
315+
}, {
316+
StartAt: 'E',
317+
States: {
318+
E: {
319+
Type: 'Task',
320+
Resource: 'arn:aws:states:::dynamodb:putItem',
321+
Parameters: {
322+
TableName: dynamodbTable,
323+
},
324+
},
325+
},
326+
}],
327+
},
328+
},
329+
}],
330+
},
331+
},
332+
},
333+
});
334+
335+
const [lambda1, lambda2] = [
336+
'arn:aws:lambda:us-west-2:1234567890:function:foo',
337+
'arn:aws:lambda:us-west-1:#{AWS::AccountId}:function:bar',
338+
];
339+
340+
const [sns1, sns2] = [
341+
'arn:aws:sns:us-east-1:1234567890:foo',
342+
'arn:aws:sns:us-east-2:#{AWS::AccountId}:bar',
343+
];
344+
345+
const [sqs1, sqs2] = [
346+
'https://sqs.us-east-1.amazonaws.com/1234567890/foo',
347+
'https://sqs.us-east-2.amazonaws.com/#{AWS::AccountId}/bar',
348+
];
349+
const [sqsArn1, sqsArn2] = [
350+
'arn:aws:sqs:us-east-1:1234567890:foo',
351+
'arn:aws:sqs:us-east-2:#{AWS::AccountId}:bar',
352+
];
353+
354+
const [dynamodb1, dynamodb2] = ['foo', 'bar'];
355+
const [dynamodbArn1, dynamodbArn2] = [{
356+
'Fn::Join': [
357+
':', ['arn:aws:dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/foo'],
358+
],
359+
}, {
360+
'Fn::Join': [
361+
':', ['arn:aws:dynamodb', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, 'table/bar'],
362+
],
363+
}];
364+
365+
serverless.service.stepFunctions = {
366+
stateMachines: {
367+
myStateMachine1: getStateMachine('sm1', lambda1, sns1, sqs1, dynamodb1),
368+
myStateMachine2: getStateMachine('sm2', lambda2, sns2, sqs2, dynamodb2),
369+
},
370+
};
371+
372+
serverlessStepFunctions.compileIamRole();
373+
const statements = serverlessStepFunctions.serverless.service
374+
.provider.compiledCloudFormationTemplate.Resources.IamRoleStateMachineExecution
375+
.Properties.Policies[0].PolicyDocument.Statement;
376+
377+
const lambdaPermissions = statements.filter(s =>
378+
_.isEqual(s.Action, ['lambda:InvokeFunction']));
379+
expect(lambdaPermissions).to.have.lengthOf(1);
380+
expect(lambdaPermissions[0].Resource).to.deep.eq([lambda1, lambda2]);
381+
382+
const snsPermissions = statements.filter(s => _.isEqual(s.Action, ['sns:Publish']));
383+
expect(snsPermissions).to.have.lengthOf(1);
384+
expect(snsPermissions[0].Resource).to.deep.eq([sns1, sns2]);
385+
386+
const sqsPermissions = statements.filter(s => _.isEqual(s.Action, ['sqs:SendMessage']));
387+
expect(sqsPermissions).to.have.lengthOf(1);
388+
expect(sqsPermissions[0].Resource).to.deep.eq([sqsArn1, sqsArn2]);
389+
390+
const dynamodbPermissions = statements.filter(s =>
391+
_.isEqual(s.Action, ['dynamodb:UpdateItem', 'dynamodb:PutItem']));
392+
expect(dynamodbPermissions).to.have.lengthOf(1);
393+
expect(dynamodbPermissions[0].Resource).to.deep.eq([dynamodbArn1, dynamodbArn2]);
171394
});
172395
});

0 commit comments

Comments
 (0)