@cloud-copilot/iam-expand 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE.txt +674 -0
  2. package/README.md +269 -0
  3. package/dist/cjs/cli.d.ts +3 -0
  4. package/dist/cjs/cli.d.ts.map +1 -0
  5. package/dist/cjs/cli.js +76 -0
  6. package/dist/cjs/cli.js.map +1 -0
  7. package/dist/cjs/cli_utils.d.ts +27 -0
  8. package/dist/cjs/cli_utils.d.ts.map +1 -0
  9. package/dist/cjs/cli_utils.js +69 -0
  10. package/dist/cjs/cli_utils.js.map +1 -0
  11. package/dist/cjs/expand.d.ts +69 -0
  12. package/dist/cjs/expand.d.ts.map +1 -0
  13. package/dist/cjs/expand.js +118 -0
  14. package/dist/cjs/expand.js.map +1 -0
  15. package/dist/cjs/index.d.ts +2 -0
  16. package/dist/cjs/index.d.ts.map +1 -0
  17. package/dist/cjs/index.js +18 -0
  18. package/dist/cjs/index.js.map +1 -0
  19. package/dist/cjs/package.json +3 -0
  20. package/dist/cjs/stdin.d.ts +7 -0
  21. package/dist/cjs/stdin.d.ts.map +1 -0
  22. package/dist/cjs/stdin.js +34 -0
  23. package/dist/cjs/stdin.js.map +1 -0
  24. package/dist/cli.d.ts +3 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +67 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/cli_utils.d.ts +27 -0
  29. package/dist/cli_utils.d.ts.map +1 -0
  30. package/dist/cli_utils.js +57 -0
  31. package/dist/cli_utils.js.map +1 -0
  32. package/dist/cli_utils.test.d.ts +2 -0
  33. package/dist/cli_utils.test.d.ts.map +1 -0
  34. package/dist/cli_utils.test.js +90 -0
  35. package/dist/cli_utils.test.js.map +1 -0
  36. package/dist/esm/cli.d.ts +3 -0
  37. package/dist/esm/cli.d.ts.map +1 -0
  38. package/dist/esm/cli.js +74 -0
  39. package/dist/esm/cli.js.map +1 -0
  40. package/dist/esm/cli_utils.d.ts +27 -0
  41. package/dist/esm/cli_utils.d.ts.map +1 -0
  42. package/dist/esm/cli_utils.js +63 -0
  43. package/dist/esm/cli_utils.js.map +1 -0
  44. package/dist/esm/expand.d.ts +69 -0
  45. package/dist/esm/expand.d.ts.map +1 -0
  46. package/dist/esm/expand.js +114 -0
  47. package/dist/esm/expand.js.map +1 -0
  48. package/dist/esm/index.d.ts +2 -0
  49. package/dist/esm/index.d.ts.map +1 -0
  50. package/dist/esm/index.js +2 -0
  51. package/dist/esm/index.js.map +1 -0
  52. package/dist/esm/package.json +3 -0
  53. package/dist/esm/stdin.d.ts +7 -0
  54. package/dist/esm/stdin.d.ts.map +1 -0
  55. package/dist/esm/stdin.js +31 -0
  56. package/dist/esm/stdin.js.map +1 -0
  57. package/dist/expand.d.ts +55 -0
  58. package/dist/expand.d.ts.map +1 -0
  59. package/dist/expand.js +94 -0
  60. package/dist/expand.js.map +1 -0
  61. package/dist/expand.test.d.ts +2 -0
  62. package/dist/expand.test.d.ts.map +1 -0
  63. package/dist/expand.test.js +382 -0
  64. package/dist/expand.test.js.map +1 -0
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +18 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/stdin.d.ts +7 -0
  70. package/dist/stdin.d.ts.map +1 -0
  71. package/dist/stdin.js +35 -0
  72. package/dist/stdin.js.map +1 -0
  73. package/package.json +33 -0
  74. package/postbuild.sh +13 -0
  75. package/src/cli.ts +82 -0
  76. package/src/cli_utils.test.ts +131 -0
  77. package/src/cli_utils.ts +78 -0
  78. package/src/expand.test.ts +523 -0
  79. package/src/expand.ts +185 -0
  80. package/src/index.ts +1 -0
  81. package/src/stdin.ts +34 -0
  82. package/tsconfig.cjs.json +11 -0
  83. package/tsconfig.esm.json +14 -0
  84. package/tsconfig.json +22 -0
@@ -0,0 +1,523 @@
1
+ import { iamActionExists, iamActionsForService, iamServiceExists, iamServiceKeys } from '@cloud-copilot/iam-data'
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
3
+ import { expandIamActions, InvalidActionBehavior } from "./expand.js"
4
+
5
+ vi.mock('@cloud-copilot/iam-data')
6
+
7
+ beforeEach(() => {
8
+ vi.resetAllMocks()
9
+ // jest.resetAllMocks()
10
+ })
11
+
12
+ describe("expand", () => {
13
+ it("should return an empty array when actionString is null", () => {
14
+ //Given actionString is null
15
+ const actionString = null
16
+ //When expand is called with actionString
17
+ const result = expandIamActions(actionString as any)
18
+ //Then result should be an empty array
19
+ expect(result).toEqual([])
20
+ })
21
+
22
+ it("should return '*' when actionString is '*' and expandAsterik is false", () => {
23
+ //Given actionString is '*'
24
+ const actionString = '*'
25
+ //When expand is called with actionString
26
+ const result = expandIamActions(actionString)
27
+ //Then result should be '*'
28
+ expect(result).toEqual(['*'])
29
+ })
30
+
31
+ it('should return "*" when action string multiple asteriks and expandAsterik is false', () => {
32
+ //Given actionString is multiple asteriks
33
+ const actionString = '***'
34
+
35
+ //And expandAsterik is false
36
+ const options = { expandAsterik: false }
37
+
38
+ //When expand is called with actionString and options
39
+ const result = expandIamActions(actionString, options)
40
+
41
+ //Then result should be '*'
42
+ expect(result).toEqual(['*'])
43
+ })
44
+
45
+ it("should expand all actions for all services when actionString is '*' and expandAsterik is true", () => {
46
+ //Given actionString is '*'
47
+ const actionString = '*'
48
+ //And expandAsterik is true
49
+ const options = { expandAsterik: true }
50
+ //And there are services
51
+ vi.mocked(iamServiceKeys).mockReturnValue(['s3', 'ec2'])
52
+
53
+ //And there are actions for the services
54
+ vi.mocked(iamActionsForService).mockImplementation(service => {
55
+ if(service === 's3') {
56
+ return ['action1', 'action2']
57
+ }
58
+ if(service === 'ec2') {
59
+ return ['action3', 'action4']
60
+ }
61
+ return []
62
+ })
63
+
64
+
65
+
66
+ //When expand is called with actionString and options
67
+ const result = expandIamActions(actionString, options)
68
+ //Then result should be an array of all actions for all services
69
+ expect(result.sort()).toEqual([
70
+ 'ec2:action3',
71
+ 'ec2:action4',
72
+ 's3:action1',
73
+ 's3:action2'
74
+ ])
75
+ })
76
+
77
+ it("should do a case insensitive match for the service in the action string", () => {
78
+ //Given actionString is 'S3:GetObject'
79
+ const actionString = 'S3:get*'
80
+ //And s3 service exists
81
+ vi.mocked(iamServiceExists).mockImplementation((s) => s === 's3')
82
+ //And there are matching actions
83
+ vi.mocked(iamActionsForService).mockReturnValue(['GetObject'])
84
+
85
+ //When expand is called with actionString
86
+ const result = expandIamActions(actionString)
87
+
88
+ //Then result should be an array with the actionString
89
+ expect(result).toEqual(['s3:GetObject'])
90
+ })
91
+
92
+ describe("invalid action name", () => {
93
+ it('should return an action without wildcards if it is a valid action', () => {
94
+ //Given actionString contains a valid action
95
+ const actionString = 's3:GetObject'
96
+ //And s3 the service exists
97
+ vi.mocked(iamServiceExists).mockReturnValue(true)
98
+ //And the action does not
99
+ vi.mocked(iamActionExists).mockReturnValue(true)
100
+
101
+ //When expand is called with actionString
102
+ const result = expandIamActions(actionString)
103
+
104
+ //Then result should be an array with the actionString
105
+ expect(result).toEqual([actionString])
106
+ })
107
+
108
+ it("should remove an invalid action if invalidActionBehavior is Remove", () => {
109
+ //Given actionString contains an invalid action
110
+ const actionString = 's3:DoSomethingDumb'
111
+ //And invalidActionBehavior is Remove
112
+ const options = { invalidActionBehavior: InvalidActionBehavior.Remove }
113
+ //And s3 the service exists
114
+ vi.mocked(iamServiceExists).mockReturnValue(true)
115
+ //And the action does not
116
+ vi.mocked(iamActionExists).mockReturnValue(false)
117
+
118
+ //When expand is called with actionString
119
+ const result = expandIamActions(actionString, options)
120
+
121
+ //Then result should be an array with the valid action
122
+ expect(result).toEqual([])
123
+ })
124
+
125
+ it("should include an invalid action if invalidActionBehavior is Include", () => {
126
+ //Given actionString contains an invalid action
127
+ const actionString = 's3:DoSomethingSilly'
128
+ //And invalidActionBehavior is Include
129
+ const options = { invalidActionBehavior: InvalidActionBehavior.Include }
130
+ //And s3 the service exists
131
+ vi.mocked(iamServiceExists).mockReturnValue(true)
132
+ //And the action does not
133
+ vi.mocked(iamActionExists).mockReturnValue(false)
134
+
135
+ //When expand is called with actionString
136
+ const result = expandIamActions(actionString, options)
137
+
138
+ //Then result should be an array with the invalid action
139
+ expect(result).toEqual([actionString])
140
+ })
141
+
142
+ it('should throw an error if the action is invalid and invalidActionBehavior is Error', () => {
143
+ //Given actionString contains an invalid action
144
+ const actionString = 's3:AbsurdlyInvalidAction'
145
+ //And invalidActionBehavior is Error
146
+ const options = { invalidActionBehavior: InvalidActionBehavior.Error }
147
+ //And s3 the service exists
148
+ vi.mocked(iamServiceExists).mockReturnValue(true)
149
+ //And the action does not
150
+ vi.mocked(iamActionExists).mockReturnValue(false)
151
+
152
+ //When expand is called with actionString
153
+ //Then an error should be thrown
154
+ expect(() => expandIamActions(actionString, options)).toThrowError('Invalid action')
155
+ })
156
+ })
157
+
158
+ describe("when the actions string is in the wrong format", () => {
159
+ it("should return an empty array when there are too many parts and errorOnInvalidFormat is false", () => {
160
+ //Given actionString is in the wrong format
161
+ const actionString = 's3:GetObject:Extra*'
162
+ //And errorOnInvalidFormat is false
163
+ const options = { errorOnInvalidFormat: false }
164
+
165
+ //When expand is called with actionString
166
+ const result = expandIamActions(actionString, options)
167
+
168
+ //Then result should be an empty array
169
+ expect(result).toEqual([])
170
+ })
171
+
172
+ it("should return an empty array when there are too few parts and errorOnInvalidFormat is false", () => {
173
+ //Given actionString has no :
174
+ const actionString = 's3GetObject*'
175
+ //And errorOnInvalidFormat is false
176
+ const options = { errorOnInvalidFormat: false }
177
+
178
+ //When expand is called with actionString
179
+ const result = expandIamActions(actionString, options)
180
+
181
+ //Then result should be an empty array
182
+ expect(result).toEqual([])
183
+ })
184
+
185
+ it("should throw an error when there are too many parts and errorOnInvalidFormat is true", () => {
186
+ //Given actionString is in the wrong format
187
+ const actionString = 's3:GetObject:Extra*'
188
+ //And errorOnInvalidFormat is true
189
+ const options = { errorOnInvalidFormat: true }
190
+
191
+ //When expand is called with actionString
192
+ //Then an error should be thrown
193
+ expect(() => expandIamActions(actionString, options)).toThrowError('Invalid action format')
194
+ })
195
+
196
+ it("should throw an error when there are too few parts and errorOnInvalidFormat is true", () => {
197
+ //Given actionString has no :
198
+ const actionString = 's3GetObject*'
199
+ //And errorOnInvalidFormat is true
200
+ const options = { errorOnInvalidFormat: true }
201
+
202
+ //When expand is called with actionString
203
+ //Then an error should be thrown
204
+ expect(() => expandIamActions(actionString, options)).toThrowError('Invalid action format')
205
+ })
206
+ })
207
+
208
+ describe("when the service in the action string does not exist", () => {
209
+ it("should return an empty array when errorOnMissingService is false", () => {
210
+ //Given actionString contains a service that does not exist
211
+ const actionString = 'fake:GetObject*'
212
+ //And errorOnMissingService is false
213
+ const options = { errorOnMissingService: false }
214
+
215
+ //When expand is called with actionString
216
+ const result = expandIamActions(actionString, options)
217
+
218
+ //Then result should be an empty array
219
+ expect(result).toEqual([])
220
+ })
221
+
222
+ it("should throw an error when errorOnMissingService is true", () => {
223
+ //Given actionString contains a service that does not exist
224
+ const actionString = 'fake:GetObject*'
225
+ //And errorOnMissingService is true
226
+ const options = { errorOnMissingService: true }
227
+
228
+ //When expand is called with actionString
229
+ //Then an error should be thrown
230
+ expect(() => expandIamActions(actionString, options)).toThrowError('Service not found')
231
+ })
232
+ })
233
+
234
+ describe("when the action string contains a wildcard for a service", () => {
235
+ it("should expand not expand the wildcard when expandServiceAsterik is false", () => {
236
+ //Given actionString is 's3:*'
237
+ const actionString = 's3:*'
238
+ //And expandServiceAsterik is false
239
+ const options = { expandServiceAsterik: false }
240
+ //And s3 service exists
241
+ vi.mocked(iamServiceExists).mockReturnValue(true)
242
+ //And there are matching actions
243
+ vi.mocked(iamActionsForService).mockReturnValue(['GetObject', 'PutObject'])
244
+
245
+ //When expand is called with actionString
246
+ const result = expandIamActions(actionString, options)
247
+
248
+ //Then result should be an array with the original string
249
+ expect(result).toEqual([actionString])
250
+ })
251
+
252
+ it("should expand not expand the wildcard when there are multiple asteriks and expandServiceAsterik is false", () => {
253
+ //Given actionString has multiple asteriks for the actions
254
+ const actionString = 's3:****'
255
+ //And expandServiceAsterik is false
256
+ const options = { expandServiceAsterik: false }
257
+ //And s3 service exists
258
+ vi.mocked(iamServiceExists).mockReturnValue(true)
259
+ //And there are matching actions
260
+ vi.mocked(iamActionsForService).mockReturnValue(['GetObject', 'PutObject'])
261
+
262
+ //When expand is called with actionString
263
+ const result = expandIamActions(actionString, options)
264
+
265
+ //Then result should be an array with the original string
266
+ expect(result).toEqual(['s3:*'])
267
+ })
268
+
269
+ it("should expand the wildcard when expandServiceAsterik is true", () => {
270
+ //Given actionString is 's3:*'
271
+ const actionString = 's3:*'
272
+ //And expandServiceAsterik is true
273
+ const options = { expandServiceAsterik: true }
274
+ //And s3 service exists
275
+ vi.mocked(iamServiceExists).mockReturnValue(true)
276
+ //And there are matching actions
277
+ vi.mocked(iamActionsForService).mockReturnValue(['GetObject', 'PutObject'])
278
+
279
+ //When expand is called with actionString
280
+ const result = expandIamActions(actionString, options)
281
+
282
+ //Then result should be an array of actions
283
+ expect(result).toEqual([
284
+ 's3:GetObject',
285
+ 's3:PutObject'
286
+ ])
287
+ })
288
+ })
289
+
290
+
291
+ describe("when the action string contains wildcards", () => {
292
+ it('should expand the wildcard actions at the end', () => {
293
+ //Given actionString is 's3:Get*'
294
+ const actionString = 's3:Get*'
295
+ //And s3 service exists
296
+ vi.mocked(iamServiceExists).mockReturnValue(true)
297
+ //And there are matching actions
298
+ vi.mocked(iamActionsForService).mockReturnValue([
299
+ 'GetObject',
300
+ 'GetObjectAcl',
301
+ 'GetObjectTagging',
302
+ 'GetObjectTorrent',
303
+ 'PutObject',
304
+ 'PutObjectAcl',
305
+ 'SomethingGetSomething'
306
+ ])
307
+
308
+ //When expand is called with actionString
309
+ const result = expandIamActions(actionString)
310
+ //Then result should be an array of actions
311
+ expect(result).toEqual([
312
+ 's3:GetObject',
313
+ 's3:GetObjectAcl',
314
+ 's3:GetObjectTagging',
315
+ 's3:GetObjectTorrent'
316
+ ])
317
+ })
318
+
319
+ it('should expand the wildcard actions at the beginning', () => {
320
+ //Given actionString is 's3:*Object'
321
+ const actionString = 's3:*Object'
322
+ //And s3 service exists
323
+ vi.mocked(iamServiceExists).mockReturnValue(true)
324
+ //And there are matching actions
325
+ vi.mocked(iamActionsForService).mockReturnValue([
326
+ 'GetObject',
327
+ 'GetObjectAcl',
328
+ 'GetObjectTagging',
329
+ 'GetObjectTorrent',
330
+ 'PutObject',
331
+ 'PutObjectAcl',
332
+ 'SomethingGetSomething'
333
+ ])
334
+
335
+ //When expand is called with actionString
336
+ const result = expandIamActions(actionString)
337
+ //Then result should be an array of actions
338
+ expect(result).toEqual([
339
+ 's3:GetObject',
340
+ 's3:PutObject'
341
+ ])
342
+ })
343
+
344
+ it('should expand the wildcard actions in the middle', () => {
345
+ //Given actionString is 's3:Get*Tagging'
346
+ const actionString = 's3:Get*Tagging'
347
+ //And s3 service exists
348
+ vi.mocked(iamServiceExists).mockReturnValue(true)
349
+ //And there are matching actions
350
+ vi.mocked(iamActionsForService).mockReturnValue([
351
+ 'GetObject',
352
+ 'GetObjectAcl',
353
+ 'GetObjectTagging',
354
+ 'GetBanskyTagging',
355
+ 'GetObjectTorrent',
356
+ 'PutObject',
357
+ 'PutObjectAcl',
358
+ 'SomethingGetSomething'
359
+ ])
360
+
361
+ //When expand is called with actionString
362
+ const result = expandIamActions(actionString)
363
+ //Then result should be an array of actions
364
+ expect(result).toEqual([
365
+ 's3:GetObjectTagging',
366
+ 's3:GetBanskyTagging'
367
+ ])
368
+ })
369
+
370
+ it('should expand multiple wildcards', () => {
371
+ //Given actionString is 's3:Get*Tagging*'
372
+ const actionString = 's3:Get*Tagging*'
373
+ //And s3 service exists
374
+ vi.mocked(iamServiceExists).mockReturnValue(true)
375
+ //And there are matching actions
376
+ vi.mocked(iamActionsForService).mockReturnValue([
377
+ 'GetObject',
378
+ 'GetObjectAcl',
379
+ 'GetObjectTagging',
380
+ 'GetBanskyTagging',
381
+ 'GetTagging',
382
+ 'GetObjectTorrent',
383
+ 'GetSomethingTaggingSomething',
384
+ 'PutObject',
385
+ 'PutObjectAcl',
386
+ 'SomethingGetSomething'
387
+ ])
388
+
389
+ //When expand is called with actionString
390
+ const result = expandIamActions(actionString)
391
+ //Then result should be an array of actions
392
+ expect(result).toEqual([
393
+ 's3:GetObjectTagging',
394
+ 's3:GetBanskyTagging',
395
+ 's3:GetTagging',
396
+ 's3:GetSomethingTaggingSomething'
397
+ ])
398
+ })
399
+ })
400
+
401
+ describe("when actionStrings is an array", () => {
402
+ it("should return an empty array when actionStrings is an empty array", () => {
403
+ //Given actionStrings is an empty array
404
+ const actionStrings: string[] = []
405
+
406
+ //When expand is called with actionStrings
407
+ const result = expandIamActions(actionStrings)
408
+
409
+ //Then result should be an empty array
410
+ expect(result).toEqual([])
411
+ })
412
+
413
+ it("should return an array of expanded actions when actionStrings is an array of action strings", () => {
414
+ //Given actionStrings is an array of action strings
415
+ const actionStrings = [
416
+ 's3:Get*',
417
+ 'ec2:*Instances'
418
+ ]
419
+ //And s3 and ec2 services exist
420
+ vi.mocked(iamServiceExists).mockReturnValue(true)
421
+ //And there are actions for the services
422
+ vi.mocked(iamActionsForService).mockImplementation(service => {
423
+ if(service === 's3') {
424
+ return ['GetObject', 'GetObjectTagging', 'PutObject', 'PutObjectTagging']
425
+ }
426
+ if(service === 'ec2') {
427
+ return ['RunInstances', 'TerminateInstances']
428
+ }
429
+ return []
430
+ })
431
+
432
+ //When expand is called with actionStrings
433
+ const result = expandIamActions(actionStrings)
434
+
435
+ //Then result should be an array of expanded actions
436
+ expect(result.sort()).toEqual([
437
+ 'ec2:RunInstances',
438
+ 'ec2:TerminateInstances',
439
+ 's3:GetObject',
440
+ 's3:GetObjectTagging',
441
+ ])
442
+ })
443
+ })
444
+
445
+ describe("distinct option", () => {
446
+ it('should return all values when distinct is false', () => {
447
+ //Given two action strings
448
+ const actionString = ['s3:Get*','s3:*Object']
449
+ //And s3 service exists
450
+ vi.mocked(iamServiceExists).mockReturnValue(true)
451
+ //And there are matching actions
452
+ vi.mocked(iamActionsForService).mockReturnValue(['GetObject', 'PutObject', 'GetOtherObject'])
453
+
454
+ //When expand is called with actionString and distinct is false
455
+ const result = expandIamActions(actionString, { distinct: false })
456
+
457
+ //Then result should be an array of actions, even if they are duplicates
458
+ expect(result).toEqual(['s3:GetObject', 's3:GetOtherObject', 's3:GetObject', 's3:PutObject', 's3:GetOtherObject'])
459
+ })
460
+
461
+ it('should return only unique values when distinct is true, and maintain order', () => {
462
+ //Given two action strings
463
+ const actionString = ['s3:Get*','s3:*Object']
464
+ //And s3 service exists
465
+ vi.mocked(iamServiceExists).mockReturnValue(true)
466
+ //And there are matching actions
467
+ vi.mocked(iamActionsForService).mockReturnValue(['GetObject', 'PutObject', 'GetOtherObject'])
468
+
469
+ //When expand is called with actionStrings and distinct is true
470
+ const result = expandIamActions(actionString, { distinct: true })
471
+ //Then result should be an array of unique actions
472
+ expect(result).toEqual(['s3:GetObject', 's3:GetOtherObject', 's3:PutObject'])
473
+ })
474
+ })
475
+
476
+ describe("sort option", () => {
477
+ it('should return values in the order they were expanded when sort is false', () => {
478
+ //Given two action strings
479
+ const actionString = ['s3:Get*','ec2:Describe*']
480
+ //And s3 service exists
481
+ vi.mocked(iamServiceExists).mockReturnValue(true)
482
+ //And there are matching actions
483
+ vi.mocked(iamActionsForService).mockImplementation(service => {
484
+ if(service === 's3') {
485
+ return ['GetObject', 'GetBucket']
486
+ }
487
+ if(service === 'ec2') {
488
+ return ['DescribeInstances', 'DescribeVolumes']
489
+ }
490
+ return []
491
+ })
492
+
493
+ //When expand is called with actionStrings and sort is false
494
+ const result = expandIamActions(actionString, { sort: false })
495
+ //Then result should be an array of actions in the order they were expanded
496
+ expect(result).toEqual(['s3:GetObject', 's3:GetBucket', 'ec2:DescribeInstances', 'ec2:DescribeVolumes'])
497
+ })
498
+
499
+ it('should return values sorted when sort is true', () => {
500
+ //Given two action strings
501
+ const actionString = ['s3:Get*','ec2:Describe*']
502
+ //And s3 service exists
503
+ vi.mocked(iamServiceExists).mockReturnValue(true)
504
+ //And there are matching actions
505
+ vi.mocked(iamActionsForService).mockImplementation(service => {
506
+ if(service === 's3') {
507
+ return ['GetObject', 'GetBucket']
508
+ }
509
+ if(service === 'ec2') {
510
+ return ['DescribeInstances', 'DescribeVolumes']
511
+ }
512
+ return []
513
+ })
514
+
515
+ //When expand is called with actionStrings and sort is false
516
+ const result = expandIamActions(actionString, { sort: true })
517
+ //Then result should be an array of actions in the order they were expanded
518
+ expect(result).toEqual(['ec2:DescribeInstances', 'ec2:DescribeVolumes', 's3:GetBucket', 's3:GetObject'])
519
+ })
520
+ })
521
+
522
+
523
+ })