@cloud-copilot/iam-shrink 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 (85) hide show
  1. package/LICENSE.txt +674 -0
  2. package/README.md +187 -0
  3. package/dist/cjs/cli.d.ts +2 -0
  4. package/dist/cjs/cli.d.ts.map +1 -0
  5. package/dist/cjs/cli.js +78 -0
  6. package/dist/cjs/cli.js.map +1 -0
  7. package/dist/cjs/cli_utils.d.ts +30 -0
  8. package/dist/cjs/cli_utils.d.ts.map +1 -0
  9. package/dist/cjs/cli_utils.js +75 -0
  10. package/dist/cjs/cli_utils.js.map +1 -0
  11. package/dist/cjs/errors.d.ts +13 -0
  12. package/dist/cjs/errors.d.ts.map +1 -0
  13. package/dist/cjs/errors.js +56 -0
  14. package/dist/cjs/errors.js.map +1 -0
  15. package/dist/cjs/index.d.ts +3 -0
  16. package/dist/cjs/index.d.ts.map +1 -0
  17. package/dist/cjs/index.js +8 -0
  18. package/dist/cjs/index.js.map +1 -0
  19. package/dist/cjs/package.json +3 -0
  20. package/dist/cjs/shrink.d.ts +131 -0
  21. package/dist/cjs/shrink.d.ts.map +1 -0
  22. package/dist/cjs/shrink.js +358 -0
  23. package/dist/cjs/shrink.js.map +1 -0
  24. package/dist/cjs/shrink_file.d.ts +12 -0
  25. package/dist/cjs/shrink_file.d.ts.map +1 -0
  26. package/dist/cjs/shrink_file.js +38 -0
  27. package/dist/cjs/shrink_file.js.map +1 -0
  28. package/dist/cjs/stdin.d.ts +7 -0
  29. package/dist/cjs/stdin.d.ts.map +1 -0
  30. package/dist/cjs/stdin.js +34 -0
  31. package/dist/cjs/stdin.js.map +1 -0
  32. package/dist/cjs/validate.d.ts +11 -0
  33. package/dist/cjs/validate.d.ts.map +1 -0
  34. package/dist/cjs/validate.js +30 -0
  35. package/dist/cjs/validate.js.map +1 -0
  36. package/dist/esm/cli.d.ts +2 -0
  37. package/dist/esm/cli.d.ts.map +1 -0
  38. package/dist/esm/cli.js +76 -0
  39. package/dist/esm/cli.js.map +1 -0
  40. package/dist/esm/cli_utils.d.ts +30 -0
  41. package/dist/esm/cli_utils.d.ts.map +1 -0
  42. package/dist/esm/cli_utils.js +69 -0
  43. package/dist/esm/cli_utils.js.map +1 -0
  44. package/dist/esm/errors.d.ts +13 -0
  45. package/dist/esm/errors.d.ts.map +1 -0
  46. package/dist/esm/errors.js +50 -0
  47. package/dist/esm/errors.js.map +1 -0
  48. package/dist/esm/index.d.ts +3 -0
  49. package/dist/esm/index.d.ts.map +1 -0
  50. package/dist/esm/index.js +3 -0
  51. package/dist/esm/index.js.map +1 -0
  52. package/dist/esm/package.json +3 -0
  53. package/dist/esm/shrink.d.ts +131 -0
  54. package/dist/esm/shrink.d.ts.map +1 -0
  55. package/dist/esm/shrink.js +343 -0
  56. package/dist/esm/shrink.js.map +1 -0
  57. package/dist/esm/shrink_file.d.ts +12 -0
  58. package/dist/esm/shrink_file.d.ts.map +1 -0
  59. package/dist/esm/shrink_file.js +35 -0
  60. package/dist/esm/shrink_file.js.map +1 -0
  61. package/dist/esm/stdin.d.ts +7 -0
  62. package/dist/esm/stdin.d.ts.map +1 -0
  63. package/dist/esm/stdin.js +31 -0
  64. package/dist/esm/stdin.js.map +1 -0
  65. package/dist/esm/validate.d.ts +11 -0
  66. package/dist/esm/validate.d.ts.map +1 -0
  67. package/dist/esm/validate.js +27 -0
  68. package/dist/esm/validate.js.map +1 -0
  69. package/package.json +43 -0
  70. package/postbuild.sh +13 -0
  71. package/src/cli.ts +83 -0
  72. package/src/cli_utils.test.ts +117 -0
  73. package/src/cli_utils.ts +82 -0
  74. package/src/errors.ts +52 -0
  75. package/src/index.ts +3 -0
  76. package/src/shrink.test.ts +594 -0
  77. package/src/shrink.ts +385 -0
  78. package/src/shrink_file.test.ts +72 -0
  79. package/src/shrink_file.ts +38 -0
  80. package/src/stdin.ts +34 -0
  81. package/src/validate.test.ts +55 -0
  82. package/src/validate.ts +29 -0
  83. package/tsconfig.cjs.json +12 -0
  84. package/tsconfig.esm.json +15 -0
  85. package/tsconfig.json +23 -0
@@ -0,0 +1,594 @@
1
+ import { expandIamActions } from '@cloud-copilot/iam-expand';
2
+ import { beforeEach } from 'node:test';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import { consolidateWildcardPatterns, countSubstrings, findCommonSequences, groupActionsByService, mapActions, reduceAction, regexForWildcardAction, shrink, shrinkIteration, shrinkResolvedList, splitActionIntoParts, wildcardActionMatchesAnyString } from './shrink.js';
5
+ import { validateShrinkResults } from './validate.js';
6
+
7
+ vi.mock('@cloud-copilot/iam-expand')
8
+ vi.mock('./validate.js')
9
+
10
+ const mockExpandIamActions = vi.mocked(expandIamActions);
11
+ const mockValidateShrinkResults = vi.mocked(validateShrinkResults);
12
+
13
+ beforeEach(() => {
14
+ vi.resetAllMocks()
15
+ })
16
+
17
+ describe('shrink.ts', () => {
18
+ describe('splitActionIntoParts', () => {
19
+ it('should split by capital letters', () => {
20
+ //Given an action with capital letters
21
+ const input = "CreateAccessPointForObjectLambda";
22
+
23
+ //When we split the action into parts
24
+ const result = splitActionIntoParts(input);
25
+
26
+ //Then we should get an array of parts
27
+ expect(result).toEqual([
28
+ "Create",
29
+ "Access",
30
+ "Point",
31
+ "For",
32
+ "Object",
33
+ "Lambda"
34
+ ]);
35
+ })
36
+
37
+ it('should split by asterisks', () => {
38
+ //Given an action with asterisks
39
+ const input = "*ObjectTagging*";
40
+
41
+ //When we split the action into parts
42
+ const result = splitActionIntoParts(input);
43
+
44
+ //Then we should get an array of parts
45
+ expect(result).toEqual([
46
+ "*",
47
+ "Object",
48
+ "Tagging",
49
+ "*"
50
+ ]);
51
+ })
52
+
53
+ it('should not split on consecutive capital letters', () => {
54
+ //Given an action with only capital letters
55
+ const input = "GET";
56
+
57
+ //When we split the action into parts
58
+ const result = splitActionIntoParts(input);
59
+
60
+ //Then we should back the original string
61
+ expect(result).toEqual([input]);
62
+ })
63
+ })
64
+
65
+ describe('consolidateWildcardPatterns', () => {
66
+ it('should consolidate wildcard patterns', () => {
67
+ //Given wildcard actions
68
+ const actions = ['*Object', 'Object*', '*Object*']
69
+
70
+ //When we consolidate the actions
71
+ const result = consolidateWildcardPatterns(actions);
72
+
73
+ //Then we should get an array of consolidated actions
74
+ expect(result).toEqual(['*Object*']);
75
+ })
76
+
77
+ it('should not overconsolidate in the middle', () => {
78
+ //Given the wildcard actions
79
+ const actions = [
80
+ 'Delete*Tagging',
81
+ 'GetJobTagging',
82
+ 'GetObjectTagging',
83
+ 'GetObject*Tagging',
84
+ 'GetStorage*Tagging',
85
+ 'Put*Tagging'
86
+ ]
87
+
88
+ //When we consolidate the actions
89
+ const result = consolidateWildcardPatterns(actions);
90
+
91
+ //Then we should get an array of consolidated actions
92
+ expect(result.sort()).toEqual([
93
+ 'Delete*Tagging',
94
+ 'GetJobTagging',
95
+ 'GetObject*Tagging',
96
+ 'GetStorage*Tagging',
97
+ 'Put*Tagging'
98
+ ].sort()
99
+ );
100
+ })
101
+ })
102
+
103
+ describe('countSubstrings', () => {
104
+ it('will count strings that appear in the substrings', () => {
105
+ //Given a set of substrings
106
+ const substrings = [
107
+ "Get",
108
+ "Object",
109
+ "Tagging",
110
+ "Put"
111
+ ]
112
+ const actionStrings = [
113
+ "GetObjectTagging",
114
+ "PutObjectTagging"
115
+ ]
116
+
117
+
118
+ //When we count the substrings
119
+ const result = countSubstrings(substrings, actionStrings);
120
+
121
+ //Then we should get the count of substrings
122
+ expect(result.size).toEqual(4);
123
+ expect(result.get("Get")).toBe(1);
124
+ expect(result.get("Object")).toBe(2);
125
+ expect(result.get("Tagging")).toBe(2);
126
+ expect(result.get("Put")).toBe(1);
127
+ })
128
+
129
+ it('does not count substrings that are not in the action strings', () => {
130
+ //Given a set of substrings
131
+ const substrings = [
132
+ "Silliness",
133
+ "Get",
134
+ ]
135
+ const actionStrings = [
136
+ "GetObjectTagging",
137
+ "PutObjectTagging"
138
+ ]
139
+
140
+ //When we count the substrings
141
+ const result = countSubstrings(substrings, actionStrings);
142
+
143
+ //Then we should get the count of substrings
144
+ expect(result.size).toEqual(1);
145
+ expect(result.get("Get")).toBe(1);
146
+ })
147
+ })
148
+
149
+ describe('findCommonSequences', () => {
150
+ it('counts up the common sequences', () => {
151
+ //Given a set of actions
152
+ const actions = [
153
+ "GetObjectTagging",
154
+ "PutObjectTagging",
155
+ "GetBucketTagging",
156
+ "GetObjectVersionAcl",
157
+ "Get*"
158
+ ]
159
+
160
+ //When we find the common sequences
161
+ const result = findCommonSequences(actions);
162
+
163
+ //And sort them
164
+ result.sort((a, b) => {
165
+ return a.sequence.localeCompare(b.sequence);
166
+ });
167
+
168
+ //Then we should get the common sequences
169
+ expect(result).toEqual([
170
+ {sequence: "*", frequency: 1, length: 1},
171
+ {sequence: "Acl", frequency: 1, length: 3},
172
+ {sequence: "Bucket", frequency: 1, length: 6},
173
+ {sequence: "Get", frequency: 4, length: 3},
174
+ {sequence: "Object", frequency: 3, length: 6},
175
+ {sequence: "Put", frequency: 1, length: 3},
176
+ {sequence: "Tagging", frequency: 3, length: 7},
177
+ {sequence: "Version", frequency: 1, length: 7},
178
+ ])
179
+ })
180
+ })
181
+
182
+ describe('regexForWildcardAction', () => {
183
+ it('should create a regex for a wildcard action', () => {
184
+ //Given a wildcard action
185
+ const action = "*ObjectTagging*";
186
+
187
+ //When we create a regex for the action
188
+ const result = regexForWildcardAction(action);
189
+
190
+ //Then we should get a regex that matches the action
191
+ expect(result.source).toBe("^.*?ObjectTagging.*?$");
192
+ expect(result.flags).toBe("i");
193
+ })
194
+
195
+ it('should collapse consecutive asterisks', () => {
196
+ //Given a wildcard action with consecutive asterisks
197
+ const action = "*Object****Tagging*";
198
+
199
+ //When we create a regex for the action
200
+ const result = regexForWildcardAction(action);
201
+
202
+ //Then we should get a regex that matches the action
203
+ expect(result.source).toBe("^.*?Object.*?Tagging.*?$");
204
+ })
205
+ })
206
+
207
+ describe('wildcardActionMatchesAnyString', () => {
208
+ it('should match a string that matches the wildcard action', () => {
209
+ //Given a wildcard action
210
+ const wildcardAction = "*ObjectTagging*";
211
+ //And a list of strings with one that matches the wildcard action
212
+ const strings = [
213
+ "GetObjectTagging",
214
+ "PutObjectTagging",
215
+ "GetObjectVersionAcl"
216
+ ]
217
+
218
+ //When we match the strings against the action
219
+ const result = wildcardActionMatchesAnyString(wildcardAction, strings);
220
+
221
+ //Then we should get a match
222
+ expect(result).toBe(true);
223
+ })
224
+
225
+ it('should return false if there are no matches', () => {
226
+ //Given a wildcard action
227
+ const wildcardAction = "*ObjectTagging*";
228
+ //And a list of strings with none that match the wildcard action
229
+ const strings = [
230
+ "GetObjectVersionAcl",
231
+ "PutBucketTagging",
232
+ ]
233
+
234
+ //When we match the strings against the action
235
+ const result = wildcardActionMatchesAnyString(wildcardAction, strings);
236
+
237
+ //Then we should not get a match
238
+ expect(result).toBe(false);
239
+ })
240
+
241
+ it('should return false if for an empty list of strings', () => {
242
+ //Given a wildcard action
243
+ const wildcardAction = "*ObjectTagging*";
244
+ //And an empty list of strings
245
+ const strings: string[] = []
246
+
247
+ //When we match the strings against the action
248
+ const result = wildcardActionMatchesAnyString(wildcardAction, strings);
249
+
250
+ //Then the result should be false
251
+ expect(result).toBe(false);
252
+ })
253
+ })
254
+
255
+ const reduceActionCases: {action: string, sequence: string, undesiredActions: string[], expected: string, name?: string}[] = [
256
+ //Sequence at the beginning
257
+ {action: "GetObjectTagging", sequence: "Get", undesiredActions: ["GetObjectAcl"], expected: "Get*Tagging"},
258
+ {action: "GetObjectTagging", sequence: "Get", undesiredActions: ["PutObjectTagging"], expected: "Get*"},
259
+
260
+ //Sequence in the middle, remove beginning
261
+ {action: "GetObjectTagging", sequence: "Object", undesiredActions: ["PutObjectVersion", "GetObjectVersion"], expected: "*ObjectTagging"},
262
+ {action: "GetIntelligentTieringConfiguration", sequence: "Tiering", undesiredActions: ["GetIntelligentTieringStructure"], expected: "*TieringConfiguration"},
263
+
264
+ //Sequence in the middle, remove end
265
+ {action: "GetObjectTagging", sequence: "Object", undesiredActions: ["PutObjectTagging"], expected: "GetObject*"},
266
+ {action: "GetObjectTaggingVersion", sequence: "Object", undesiredActions: ["PutObjectTagging"], expected: "GetObject*"},
267
+
268
+ //Sequence in the middle, remove beginning and end
269
+ {action: "GetObjectTagging", sequence: "Object", undesiredActions: ["ListBucketVersions"], expected: "*Object*"},
270
+
271
+ //Sequence at the end
272
+ {action: "GetObjectTagging", sequence: "Tagging", undesiredActions: ["PutObjectTagging"], expected: "Get*Tagging"},
273
+ {action: "GetObjectTagging", sequence: "Tagging", undesiredActions: ["PutObjectTagging"], expected: "Get*Tagging"},
274
+ {action: "GetObjectTagging", sequence: "Tagging", undesiredActions: ["GetObjectAcl"], expected: "*Tagging"},
275
+
276
+ //Action only Has One Part
277
+ {action: "GET", sequence: "GET", undesiredActions: ["PUT"], expected: "GET"},
278
+ ]
279
+
280
+ describe('reduceAction', () => {
281
+ for(const {action, sequence, undesiredActions, expected, name} of reduceActionCases) {
282
+ it(name ?? `should reduce ${action} to ${expected}`, () => {
283
+ //When we reduce the action
284
+ const result = reduceAction(action, sequence, undesiredActions);
285
+
286
+ //Then we should get the reduced action
287
+ expect(result).toBe(expected);
288
+ })
289
+ }
290
+
291
+ it('should replace parts after the sequence if it occurs at the beginning', () => {
292
+
293
+ })
294
+ })
295
+
296
+ describe('shrinkIteration', () => {
297
+ it('should shrink the actions, shallow', () => {
298
+ //Given a list of actions
299
+ const actions = [
300
+ "GetObjectTagging",
301
+ "PutObjectTagging",
302
+ "GetBucketTagging",
303
+ "GetObjectVersionAcl",
304
+ ]
305
+ //And a set of undesired actions
306
+ const undesiredActions = [
307
+ "GetObjectAcl",
308
+ "PutObjectTagging"
309
+ ]
310
+
311
+ //When we shrink the actions
312
+ const result = shrinkIteration(actions, undesiredActions, false);
313
+
314
+ //Then we should get the reduced actions
315
+ expect(result).toEqual([
316
+ // "*ObjectTagging",
317
+ "PutObjectTagging",
318
+ "Get*VersionAcl",
319
+ "Get*Tagging",
320
+ ])
321
+ })
322
+
323
+ it('should shrink the actions, deep', () => {
324
+ //Given a list of actions
325
+ const actions = [
326
+ "GetObjectTagging",
327
+ "PutObjectTagging",
328
+ "GetBucketTagging",
329
+ "GetObjectVersionAcl",
330
+ ]
331
+ //And a set of undesired actions
332
+ const undesiredActions = [
333
+ "GetObjectAcl",
334
+ "PutObjectTagging"
335
+ ]
336
+
337
+ //When we shrink the actions
338
+ const result = shrinkIteration(actions, undesiredActions, true);
339
+
340
+ //Then we should get the reduced actions
341
+ expect(result).toEqual([
342
+ "PutObjectTagging",
343
+ "Get*Tagging",
344
+ "*Version*",
345
+ ])
346
+ })
347
+ })
348
+
349
+ describe('shrinkResolvedList', () => {
350
+ it('should return a wildcard if all actions are desired', () => {
351
+ //Given a list of actions
352
+ const actions = [
353
+ "GetObjectTagging",
354
+ "PutObjectTagging",
355
+ "GetBucketTagging",
356
+ "GetObjectVersionAcl",
357
+ ]
358
+ //And an exact same list of possible actions
359
+ const possibleActions = actions.slice(0);
360
+
361
+ //When we shrink the actions
362
+ const result = shrinkResolvedList(actions, possibleActions, Infinity);
363
+
364
+ //Then we should get a wildcard
365
+ expect(result).toEqual(["*"]);
366
+ })
367
+
368
+ it('should shrink the list with two iterations', () => {
369
+ //Given a list of actions
370
+ const actions = [
371
+ "GetObjectTagging",
372
+ "PutObjectTagging",
373
+ "GetBucketTagging",
374
+ "GetObjectVersionAcl",
375
+ ]
376
+ //And a set of undesired actions
377
+ const possibleActions = [
378
+ "GetObjectTagging",
379
+ "PutObjectTagging",
380
+ "GetBucketTagging",
381
+ "GetObjectVersionAcl",
382
+ "GetObjectAcl",
383
+ "GetObjectVersion",
384
+ ]
385
+
386
+ //When we shrink the actions
387
+ const result = shrinkResolvedList(actions, possibleActions, 2);
388
+
389
+ //Then we should get the reduced actions
390
+ expect(result.sort()).toEqual([
391
+ "*Tagging",
392
+ "Get*VersionAcl"
393
+ ])
394
+ })
395
+
396
+ it('should shrink the list of actions aggressively', () => {
397
+ //Given a list of actions
398
+ const actions = [
399
+ "GetObjectTagging",
400
+ "PutObjectTagging",
401
+ "GetBucketTagging",
402
+ "GetObjectVersionAcl",
403
+ ]
404
+ //And a set of undesired actions
405
+ const possibleActions = [
406
+ "GetObjectTagging",
407
+ "PutObjectTagging",
408
+ "GetBucketTagging",
409
+ "GetObjectVersionAcl",
410
+ "GetObjectAcl",
411
+ "GetObjectVersion",
412
+ ]
413
+
414
+ //When we shrink the actions
415
+ const result = shrinkResolvedList(actions, possibleActions, Infinity);
416
+
417
+ //Then we should get the reduced actions
418
+ expect(result.sort()).toEqual([
419
+ "*Tagging",
420
+ "*VersionAcl"
421
+ ])
422
+ })
423
+ })
424
+
425
+ describe('groupActionsByService', () => {
426
+ it('should group the actions by service', () => {
427
+ //Given a list of actions
428
+ const actions = [
429
+ "s3:GetObjectTagging",
430
+ "s3:PutObjectTagging",
431
+ "s3:GetBucketTagging",
432
+ "s3:GetObjectVersionAcl",
433
+ "ec2:GetObjectTagging",
434
+ "ec2:PutObjectTagging",
435
+ "ec2:GetBucketTagging"
436
+ ]
437
+
438
+ //When we group the actions by service
439
+ const result = groupActionsByService(actions);
440
+
441
+ //Then we should get the actions grouped by service
442
+ expect([...result.keys()]).toEqual(["s3", "ec2"]);
443
+
444
+ expect(result.get("s3")).toEqual({
445
+ withService: [
446
+ "s3:GetObjectTagging",
447
+ "s3:PutObjectTagging",
448
+ "s3:GetBucketTagging",
449
+ "s3:GetObjectVersionAcl"
450
+ ],
451
+ withoutService: [
452
+ "GetObjectTagging",
453
+ "PutObjectTagging",
454
+ "GetBucketTagging",
455
+ "GetObjectVersionAcl"
456
+ ]
457
+ })
458
+
459
+ expect(result.get("ec2")).toEqual({
460
+ withService: [
461
+ "ec2:GetObjectTagging",
462
+ "ec2:PutObjectTagging",
463
+ "ec2:GetBucketTagging"
464
+ ],
465
+ withoutService: [
466
+ "GetObjectTagging",
467
+ "PutObjectTagging",
468
+ "GetBucketTagging"
469
+ ]
470
+ })
471
+ })
472
+ })
473
+
474
+ describe('mapActions', () => {
475
+ it('should map the actions with the service', () => {
476
+ //Given a list of actions
477
+ const actions = [
478
+ "s3:GetObjectTagging",
479
+ "s3:PutObjectTagging",
480
+ "s3:GetBucketTagging",
481
+ "s3:GetObjectVersionAcl",
482
+ "ec2:GetObjectTagging",
483
+ "ec2:PutObjectTagging",
484
+ "ec2:GetBucketTagging"
485
+ ]
486
+
487
+ //When we map the actions to services
488
+ const result = mapActions(actions);
489
+
490
+ //Then we should get the actions grouped by service
491
+ expect(result).toEqual([
492
+ "GetObjectTagging",
493
+ "PutObjectTagging",
494
+ "GetBucketTagging",
495
+ "GetObjectVersionAcl",
496
+ "GetObjectTagging",
497
+ "PutObjectTagging",
498
+ "GetBucketTagging"
499
+ ])
500
+ })
501
+ })
502
+
503
+ describe('shrink', () => {
504
+ it('should shrink the actions', async () => {
505
+ //Given a list of actions
506
+ const actions = [
507
+ "s3:GetObjectTagging",
508
+ "s3:PutObjectTagging",
509
+ "s3:GetBucketTagging",
510
+ "s3:GetObjectVersionAcl"
511
+ ]
512
+
513
+ //And a set of available actions
514
+ mockExpandIamActions.mockImplementation(async (actions: string | string[]) => {
515
+ if(actions == "s3:*") {
516
+ return [
517
+ "s3:GetObjectTagging",
518
+ "s3:PutObjectTagging",
519
+ "s3:GetBucketTagging",
520
+ "s3:GetObjectVersionAcl",
521
+ "s3:GetObjectAcl",
522
+ "s3:GetObjectVersion"
523
+ ]
524
+ }
525
+
526
+ return [
527
+ "s3:GetObjectTagging",
528
+ "s3:PutObjectTagging",
529
+ "s3:GetBucketTagging",
530
+ "s3:GetObjectVersionAcl"
531
+ ];
532
+
533
+ })
534
+
535
+ //When shrink is called
536
+ const result = await shrink(actions, {});
537
+
538
+ //Then we should get the reduced actions
539
+ expect(result).toEqual([
540
+ "s3:Get*VersionAcl",
541
+ "s3:*Tagging"
542
+ ])
543
+ })
544
+
545
+ it('should throw an error if the shrink does not validate', async () => {
546
+ //Given a list of actions
547
+ const actions = [
548
+ "s3:GetObjectTagging",
549
+ "s3:PutObjectTagging",
550
+ "s3:GetBucketTagging",
551
+ "s3:GetObjectVersionAcl"
552
+ ]
553
+
554
+ //And the validation fails
555
+ mockValidateShrinkResults.mockResolvedValueOnce("Undesired action: s3:DeleteObject")
556
+
557
+ //When shrink is called
558
+ const result = shrink(actions, {});
559
+
560
+ //Then we should get the reduced actions
561
+ await expect(result).rejects.toThrow(/@cloud-copilot\/iam-shrink has failed validation and this is a bug\./)
562
+ })
563
+
564
+ it('should return an all actions wildcard if one is provided', async () => {
565
+ //Given a list of actions that includes a global wildcard
566
+ const actions = [
567
+ "*",
568
+ "s3:GetObjectTagging",
569
+ "s3:PutObjectTagging",
570
+ ]
571
+
572
+ //When shrink is called
573
+ const result = await shrink(actions, {});
574
+
575
+ //Then we should get back the single wildcard
576
+ expect(result).toEqual(["*"])
577
+ })
578
+
579
+ it('should return an all actions wildcard if a string of multiple asterisks is included', async () => {
580
+ //Given a list of actions that includes a global wildcard
581
+ const actions = [
582
+ "***",
583
+ "s3:GetObjectTagging",
584
+ "s3:PutObjectTagging",
585
+ ]
586
+
587
+ //When shrink is called
588
+ const result = await shrink(actions);
589
+
590
+ //Then we should get back the single wildcard
591
+ expect(result).toEqual(["*"])
592
+ })
593
+ })
594
+ })