@izara_project/izara-core-generate-service-code 1.0.87 → 1.0.89

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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +25 -0
  3. package/src/compile/loadSchemas.js +199 -10
  4. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +3 -7
  5. package/src/emit/crud/crudActionPlan.js +105 -6
  6. package/src/emit/crud/emitCrudApplicationArtifacts.js +63 -3
  7. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +15 -32
  8. package/src/emit/crud/templates/CreateAfterHookCallback_Main.ejs +121 -0
  9. package/src/emit/crud/templates/CreateBeforeHookCallback_Main.ejs +108 -0
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +140 -340
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +338 -82
  12. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +90 -0
  13. package/src/emit/crud/templates/DeleteAfterHookCallback_Main.ejs +118 -0
  14. package/src/emit/crud/templates/DeleteBeforeHookCallback_Main.ejs +99 -0
  15. package/src/emit/crud/templates/DeleteComplete_Main.ejs +124 -10
  16. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +151 -179
  17. package/src/emit/crud/templates/DeleteLogicalLib.ejs +244 -27
  18. package/src/emit/crud/templates/UpdateAfterHookCallback_Main.ejs +120 -0
  19. package/src/emit/crud/templates/UpdateBeforeHookCallback_Main.ejs +112 -0
  20. package/src/emit/crud/templates/UpdateComplete_Main.ejs +104 -10
  21. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +166 -185
  22. package/src/emit/crud/templates/UpdateLogicalLib.ejs +123 -3
  23. package/src/emit/custom/customFlows.js +3 -1
  24. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +8 -2
  25. package/src/emit/rbac/templates/CancelUsage_Main.ejs +34 -14
  26. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +34 -14
  27. package/src/emit/resource/emitResourceArtifacts.js +119 -59
  28. package/test/crudApplicationArtifacts.test.js +876 -0
  29. package/test/dynamicCrudFlowSchemas.test.js +10 -5
  30. package/test/dynamicRbacFlowSchemas.test.js +24 -2
  31. package/test/graphDrivenFlowSchema.test.js +36 -0
  32. package/test/resourceArtifacts.test.js +271 -12
@@ -142,7 +142,7 @@ test('generates a direct Complete step for RBAC-only Update flows', async () =>
142
142
  assert.equal(update.flowSteps[1].properties.length, 0);
143
143
  });
144
144
 
145
- test('generates CreateNodeCallback and Complete for Create when RBAC and Graph are present', async () => {
145
+ test('generates CreateNodeCallback for Create when RBAC and Graph are present', async () => {
146
146
  const rootPath = await createProject({
147
147
  generatedBy: 'user',
148
148
  });
@@ -160,8 +160,14 @@ test('generates CreateNodeCallback and Complete for Create when RBAC and Graph a
160
160
  const create = await readGeneratedFlow(rootPath, 'Create');
161
161
  assert.deepEqual(create.flowSteps.map((step) => step.stepName), [
162
162
  'Start',
163
- 'CreateNodeCallback',
164
- 'Complete'
163
+ 'CreateNodeCallback'
164
+ ]);
165
+ assert.deepEqual(create.flowSteps[1].nextSteps, [
166
+ {
167
+ targetType: 'lambdaSyncInv',
168
+ serviceTag: 'Rbac',
169
+ functionName: 'ConfirmUsageHdrInv'
170
+ }
165
171
  ]);
166
172
  });
167
173
 
@@ -185,8 +191,7 @@ test('uses CreateNodeCallback step when Graph storage is present with sync RBAC'
185
191
  const create = await readGeneratedFlow(rootPath, 'Create');
186
192
  assert.deepEqual(create.flowSteps.map((step) => step.stepName), [
187
193
  'Start',
188
- 'CreateNodeCallback',
189
- 'Complete'
194
+ 'CreateNodeCallback'
190
195
  ]);
191
196
  assert.deepEqual(create.flowSteps[1].properties, [
192
197
  'returnValue',
@@ -69,6 +69,27 @@ test('generates shared and object-specific RBAC flow schemas', async () => {
69
69
  });
70
70
  assert.deepEqual(user.event, ['ownTopic']);
71
71
  assert.equal(user.steps, undefined);
72
+
73
+ const confirmUsage = await readGeneratedFlow(
74
+ rootPath,
75
+ 'ConfirmUsage',
76
+ 'FlowLimit'
77
+ );
78
+ const cancelUsage = await readGeneratedFlow(
79
+ rootPath,
80
+ 'CancelUsage',
81
+ 'FlowLimit'
82
+ );
83
+ assert.deepEqual(confirmUsage.event, [
84
+ 'ownTopic',
85
+ 'lambdaSyncInv',
86
+ 'lambdaSyncApi'
87
+ ]);
88
+ assert.deepEqual(cancelUsage.event, [
89
+ 'ownTopic',
90
+ 'lambdaSyncInv',
91
+ 'lambdaSyncApi'
92
+ ]);
72
93
  });
73
94
 
74
95
  test('removes RBAC schemas when the last RBAC object is removed', async () => {
@@ -144,10 +165,11 @@ async function createProject({ hasRbac = false } = {}) {
144
165
  return rootPath;
145
166
  }
146
167
 
147
- async function readGeneratedFlow(rootPath, flowTag) {
168
+ async function readGeneratedFlow(rootPath, flowTag, folder = 'FlowRbac') {
148
169
  const filePath = join(
149
170
  rootPath,
150
- 'app/src/schemas/FlowSchemas/generateCode/FlowRbac',
171
+ 'app/src/schemas/FlowSchemas/generateCode',
172
+ folder,
151
173
  `${flowTag}.js`
152
174
  );
153
175
  const module = await import(
@@ -255,3 +255,39 @@ test('validateFlowSchema rejects lambdaSyncInv nextSteps missing functionName',
255
255
  /lambdaSyncInv target requires non-empty 'functionName'/
256
256
  );
257
257
  });
258
+
259
+ test('validateFlowSchema passes for mixed sync and async events with single Start step', () => {
260
+ const mixedSingleStepSchema = {
261
+ flowTag: 'ConfirmUsage',
262
+ event: ['ownTopic', 'lambdaSyncInv', 'lambdaSyncApi'],
263
+ flowSteps: [
264
+ {
265
+ stepName: 'Start'
266
+ }
267
+ ]
268
+ };
269
+
270
+ assert.equal(validateFlowSchema(mixedSingleStepSchema), true);
271
+ });
272
+
273
+ test('validateFlowSchema rejects flow with synchronous events having multiple flowSteps', () => {
274
+ const mixedMultiStepSchema = {
275
+ flowTag: 'ConfirmUsage',
276
+ event: ['ownTopic', 'lambdaSyncInv'],
277
+ flowSteps: [
278
+ {
279
+ stepName: 'Start',
280
+ nextSteps: [{ targetType: 'ownTopic', stepName: 'StepTwo' }]
281
+ },
282
+ {
283
+ stepName: 'StepTwo',
284
+ previousSteps: [{ sourceType: 'ownTopic', stepName: 'Start' }]
285
+ }
286
+ ]
287
+ };
288
+
289
+ assert.throws(
290
+ () => validateFlowSchema(mixedMultiStepSchema),
291
+ /Flow 'ConfirmUsage' with synchronous events .* must only define a single entry step 'Start'/
292
+ );
293
+ });
@@ -76,11 +76,23 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
76
76
  assert.match(dynamoYaml, /OrdersTable:/);
77
77
  assert.match(dynamoYaml, /#<#OrdersTableSetting#>/);
78
78
  assert.match(dynamoYaml, /#<#\/OrdersTableSetting#>/);
79
- assert.match(dynamoYaml, /RolesTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}Roles/);
80
- assert.match(dynamoYaml, /TargetRolesTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+AttributeName: identifiersConcat/);
79
+ assert.match(
80
+ dynamoYaml,
81
+ /RolesTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}Roles/
82
+ );
83
+ assert.match(
84
+ dynamoYaml,
85
+ /TargetRolesTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+AttributeName: identifiersConcat/
86
+ );
81
87
  assert.doesNotMatch(dynamoYaml, /RoleLimitTable:/);
82
- assert.match(dynamoYaml, /RunningTotalUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalUsage/);
83
- assert.match(dynamoYaml, /RunningTotalReservedUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsage/);
88
+ assert.match(
89
+ dynamoYaml,
90
+ /RunningTotalUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalUsage/
91
+ );
92
+ assert.match(
93
+ dynamoYaml,
94
+ /RunningTotalReservedUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsage/
95
+ );
84
96
  const iam = await readFile(
85
97
  join(
86
98
  rootPath,
@@ -151,6 +163,7 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
151
163
  const procFindDataRole = roleBlock(iam, 'ProcFindDataRole');
152
164
  assert.match(procFindDataRole, /dynamodb:UpdateItem/);
153
165
  assert.match(procFindDataRole, /dynamodb:GetItem/);
166
+ assert.match(procFindDataRole, /dynamodb:PutItem/);
154
167
  assert.match(procFindDataRole, /dynamodb:Query/);
155
168
  assert.match(procFindDataRole, /sns:Publish/);
156
169
  assert.match(procFindDataRole, /FindData_Out/);
@@ -191,6 +204,38 @@ test('rejects deprecated FlowHook schemas', async () => {
191
204
  );
192
205
  });
193
206
 
207
+ test('rejects invalid generateCodeTag configuration', async () => {
208
+ const rootPath = await createProject();
209
+ const flowDirectory = join(rootPath, 'app/src/schemas/FlowSchemas');
210
+ await writeFile(
211
+ join(flowDirectory, 'InvalidTag.js'),
212
+ "export default { flowTag: 'invalidHook', event: ['ownTopic'], " +
213
+ "generatedCodeConfig: { generateCodeTag: ['invalidAction'], " +
214
+ "codeHookTag: 'before' } };\n"
215
+ );
216
+
217
+ await assert.rejects(
218
+ generateResourceArtifacts(rootPath),
219
+ /generatedCodeConfig\.generateCodeTag contains invalid tag 'invalidAction'/
220
+ );
221
+ });
222
+
223
+ test('rejects empty generateCodeTag array configuration', async () => {
224
+ const rootPath = await createProject();
225
+ const flowDirectory = join(rootPath, 'app/src/schemas/FlowSchemas');
226
+ await writeFile(
227
+ join(flowDirectory, 'EmptyTag.js'),
228
+ "export default { flowTag: 'emptyHook', event: ['ownTopic'], " +
229
+ "generatedCodeConfig: { generateCodeTag: [], " +
230
+ "codeHookTag: 'before' } };\n"
231
+ );
232
+
233
+ await assert.rejects(
234
+ generateResourceArtifacts(rootPath),
235
+ /generatedCodeConfig\.generateCodeTag array must not be empty/
236
+ );
237
+ });
238
+
194
239
  test('emits the shared WebSocket completion queue and output-topic subscriptions', async () => {
195
240
  const rootPath = await createProject();
196
241
  await generateResourceArtifacts(rootPath);
@@ -290,21 +335,21 @@ test('emits DynamoDB table with number sortKey attribute type when field type is
290
335
  const rootPath = await createProject();
291
336
  await writeFile(
292
337
  join(rootPath, 'app/src/schemas/ObjectSchemas/CartOrder.js'),
293
- "export default { " +
338
+ 'export default { ' +
294
339
  "objectType: 'cartOrder', " +
295
340
  "generatedBy: 'user', " +
296
- "fieldNames: { " +
341
+ 'fieldNames: { ' +
297
342
  " cartId: { type: 'string' }, " +
298
343
  " sortId: { type: 'number' } " +
299
- "}, " +
300
- "identifiers: [ " +
344
+ '}, ' +
345
+ 'identifiers: [ ' +
301
346
  " { type: 'partitionKey', fieldName: 'cartId' }, " +
302
347
  " { type: 'sortKey', fieldName: 'sortId' } " +
303
- "], " +
304
- "storageResources: { " +
348
+ '], ' +
349
+ 'storageResources: { ' +
305
350
  " main: { storageType: 'dynamoDB', tableName: 'CartOrders' } " +
306
- "} " +
307
- "};\n"
351
+ '} ' +
352
+ '};\n'
308
353
  );
309
354
 
310
355
  await generateResourceArtifacts(rootPath);
@@ -369,6 +414,220 @@ test('emits CreateNodeCallbackHdrSqs in FlowObjectsRole when object has graph an
369
414
  assert.match(role, /RolePermissions/);
370
415
  assert.match(role, /ValidatorRbac_In/);
371
416
  assert.match(role, /Rbac_\$\{self:custom\.iz_stage\}_ValidatorRbac_In/);
417
+ assert.match(role, /ConfirmUsageHdrInv/);
418
+ assert.match(role, /CancelUsageHdrInv/);
419
+ });
420
+
421
+ test('emits CreateAfterHookCallbackHdrSqs in SQS resources and FlowObjectsRole when flow has after hook with ownTopic', async () => {
422
+ const rootPath = await createProject();
423
+ const schemas = join(rootPath, 'app/src/schemas');
424
+ await writeFile(
425
+ join(schemas, 'FlowSchemas/NotifyAfterCreate.js'),
426
+ "export default { flowTag: 'notifyAfterCreate', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'after', invocationType: 'ownTopic', objType: { objectType: 'order' } } };\n"
427
+ );
428
+ await generateResourceArtifacts(rootPath);
429
+
430
+ const crudResources = await readFile(
431
+ join(
432
+ rootPath,
433
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml'
434
+ ),
435
+ 'utf8'
436
+ );
437
+ assert.match(crudResources, /^ CreateAfterHookCallbackHdrSqs:/m);
438
+ assert.match(crudResources, /^ CreateAfterHookCallbackHdrSqsDLQ:/m);
439
+
440
+ const iam = await readFile(
441
+ join(
442
+ rootPath,
443
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
444
+ ),
445
+ 'utf8'
446
+ );
447
+ const role = roleBlock(iam, 'FlowObjectsRole');
448
+ assert.match(role, /CreateAfterHookCallbackHdrSqs/);
449
+ assert.match(role, /CreateAfterHookCallbackHdrSqsDLQ/);
450
+ assert.match(role, /NotifyAfterCreate_In/);
451
+ });
452
+
453
+ test('emits CreateBeforeHookCallbackHdrSqs in SQS resources and FlowObjectsRole when flow has before hook with ownTopic', async () => {
454
+ const rootPath = await createProject();
455
+ const schemas = join(rootPath, 'app/src/schemas');
456
+ await writeFile(
457
+ join(schemas, 'FlowSchemas/ValidateBeforeCreate.js'),
458
+ "export default { flowTag: 'validateBeforeCreate', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
459
+ );
460
+ await generateResourceArtifacts(rootPath);
461
+
462
+ const crudResources = await readFile(
463
+ join(
464
+ rootPath,
465
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml'
466
+ ),
467
+ 'utf8'
468
+ );
469
+ assert.match(crudResources, /^ CreateBeforeHookCallbackHdrSqs:/m);
470
+ assert.match(crudResources, /^ CreateBeforeHookCallbackHdrSqsDLQ:/m);
471
+
472
+ const iam = await readFile(
473
+ join(
474
+ rootPath,
475
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
476
+ ),
477
+ 'utf8'
478
+ );
479
+ const role = roleBlock(iam, 'FlowObjectsRole');
480
+ assert.match(role, /CreateBeforeHookCallbackHdrSqs/);
481
+ assert.match(role, /CreateBeforeHookCallbackHdrSqsDLQ/);
482
+ assert.match(role, /ValidateBeforeCreate_In/);
483
+ });
484
+
485
+ test('emits UpdateBeforeHookCallbackHdrSqs in SQS resources and FlowObjectsRole when flow has before hook with ownTopic', async () => {
486
+ const rootPath = await createProject();
487
+ const schemas = join(rootPath, 'app/src/schemas');
488
+ await writeFile(
489
+ join(schemas, 'FlowSchemas/ValidateBeforeUpdate.js'),
490
+ "export default { flowTag: 'validateBeforeUpdate', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'update', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
491
+ );
492
+ await generateResourceArtifacts(rootPath);
493
+
494
+ const crudResources = await readFile(
495
+ join(
496
+ rootPath,
497
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml'
498
+ ),
499
+ 'utf8'
500
+ );
501
+ assert.match(crudResources, /^ UpdateBeforeHookCallbackHdrSqs:/m);
502
+ assert.match(crudResources, /^ UpdateBeforeHookCallbackHdrSqsDLQ:/m);
503
+
504
+ const iam = await readFile(
505
+ join(
506
+ rootPath,
507
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
508
+ ),
509
+ 'utf8'
510
+ );
511
+ const role = roleBlock(iam, 'FlowObjectsRole');
512
+ assert.match(role, /UpdateBeforeHookCallbackHdrSqs/);
513
+ assert.match(role, /UpdateBeforeHookCallbackHdrSqsDLQ/);
514
+ assert.match(role, /ValidateBeforeUpdate_In/);
515
+ });
516
+
517
+ test('emits DeleteBeforeHookCallbackHdrSqs in SQS resources and FlowObjectsRole when flow has before hook with ownTopic', async () => {
518
+ const rootPath = await createProject();
519
+ const schemas = join(rootPath, 'app/src/schemas');
520
+ await writeFile(
521
+ join(schemas, 'FlowSchemas/ValidateBeforeDelete.js'),
522
+ "export default { flowTag: 'validateBeforeDelete', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'delete', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
523
+ );
524
+ await generateResourceArtifacts(rootPath);
525
+
526
+ const crudResources = await readFile(
527
+ join(
528
+ rootPath,
529
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml'
530
+ ),
531
+ 'utf8'
532
+ );
533
+ assert.match(crudResources, /^ DeleteBeforeHookCallbackHdrSqs:/m);
534
+ assert.match(crudResources, /^ DeleteBeforeHookCallbackHdrSqsDLQ:/m);
535
+
536
+ const iam = await readFile(
537
+ join(
538
+ rootPath,
539
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
540
+ ),
541
+ 'utf8'
542
+ );
543
+ const role = roleBlock(iam, 'FlowObjectsRole');
544
+ assert.match(role, /DeleteBeforeHookCallbackHdrSqs/);
545
+ assert.match(role, /DeleteBeforeHookCallbackHdrSqsDLQ/);
546
+ assert.match(role, /ValidateBeforeDelete_In/);
547
+ });
548
+
549
+ test('emits lambda:InvokeFunction in FlowObjectsRole when flow has before hook with lambdaSyncInv for update and delete', async () => {
550
+ const rootPath = await createProject();
551
+ const schemas = join(rootPath, 'app/src/schemas');
552
+ await writeFile(
553
+ join(schemas, 'FlowSchemas/CheckBeforeUpdate.js'),
554
+ "export default { flowTag: 'checkBeforeUpdate', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'update', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
555
+ );
556
+ await writeFile(
557
+ join(schemas, 'FlowSchemas/CheckBeforeDelete.js'),
558
+ "export default { flowTag: 'checkBeforeDelete', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'delete', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
559
+ );
560
+ await generateResourceArtifacts(rootPath);
561
+
562
+ const iam = await readFile(
563
+ join(
564
+ rootPath,
565
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
566
+ ),
567
+ 'utf8'
568
+ );
569
+ const role = roleBlock(iam, 'FlowObjectsRole');
570
+ assert.match(role, /lambda:InvokeFunction/);
571
+ assert.match(role, /CheckBeforeUpdate/);
572
+ assert.match(role, /CheckBeforeDelete/);
573
+ });
574
+
575
+ test('emits lambda:InvokeFunction in FlowObjectsRole when flow has after hook with lambdaSyncInv', async () => {
576
+ const rootPath = await createProject();
577
+ const schemas = join(rootPath, 'app/src/schemas');
578
+ await writeFile(
579
+ join(schemas, 'FlowSchemas/AuditAfterCreate.js'),
580
+ "export default { flowTag: 'auditAfterCreate', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'after', invocationType: 'lambdaSyncInv', objType: { objectType: 'order' } } };\n"
581
+ );
582
+ await generateResourceArtifacts(rootPath);
583
+
584
+ const iam = await readFile(
585
+ join(
586
+ rootPath,
587
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
588
+ ),
589
+ 'utf8'
590
+ );
591
+ const role = roleBlock(iam, 'FlowObjectsRole');
592
+ assert.match(role, /lambda:InvokeFunction/);
593
+ assert.match(role, /AuditAfterCreate/);
594
+ });
595
+
596
+ test('emits SQS queues and deduplicated IAM policies when generateCodeTag is an array for multiple actions', async () => {
597
+ const rootPath = await createProject();
598
+ const schemas = join(rootPath, 'app/src/schemas');
599
+ await writeFile(
600
+ join(schemas, 'FlowSchemas/NotifyMultiAfter.js'),
601
+ "export default { flowTag: 'notifyMultiAfter', event: ['ownTopic'], " +
602
+ "generatedCodeConfig: { generateCodeTag: ['create', 'update'], " +
603
+ "codeHookTag: 'after', invocationType: 'ownTopic', " +
604
+ "objType: { objectType: 'order' } } };\n"
605
+ );
606
+ await generateResourceArtifacts(rootPath);
607
+
608
+ const crudResources = await readFile(
609
+ join(
610
+ rootPath,
611
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml'
612
+ ),
613
+ 'utf8'
614
+ );
615
+ assert.match(crudResources, /^ CreateAfterHookCallbackHdrSqs:/m);
616
+ assert.match(crudResources, /^ UpdateAfterHookCallbackHdrSqs:/m);
617
+
618
+ const iam = await readFile(
619
+ join(
620
+ rootPath,
621
+ 'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
622
+ ),
623
+ 'utf8'
624
+ );
625
+ const role = roleBlock(iam, 'FlowObjectsRole');
626
+ assert.match(role, /CreateAfterHookCallbackHdrSqs/);
627
+ assert.match(role, /UpdateAfterHookCallbackHdrSqs/);
628
+
629
+ const notifyTopicMatches = role.match(/NotifyMultiAfter_In/g);
630
+ assert.strictEqual(notifyTopicMatches?.length, 1);
372
631
  });
373
632
 
374
633
  function roleBlock(iam, roleName) {