@izara_project/izara-core-generate-service-code 1.0.93 → 1.0.95

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 (27) hide show
  1. package/package.json +1 -1
  2. package/src/compile/compileProject.js +3 -1
  3. package/src/compile/flowValidator.js +182 -18
  4. package/src/compile/loadSchemas.js +144 -9
  5. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +1 -26
  6. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +1 -31
  7. package/src/emit/custom/customFlowPaths.js +3 -16
  8. package/src/emit/custom/customFlowS3.js +9 -4
  9. package/src/emit/custom/customFlowSteps.js +14 -47
  10. package/src/emit/custom/customFlows.js +27 -6
  11. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +1 -47
  12. package/src/emit/custom/templates/FlowEndpoint_Main.ejs +75 -2
  13. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +7 -7
  14. package/src/emit/relationship/templates/CreateRelationship_Main.ejs +5 -5
  15. package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +3 -3
  16. package/src/emit/relationship/templates/GetRelationship_Main.ejs +1 -1
  17. package/src/emit/relationship/templates/MoveRelationship_Main.ejs +5 -5
  18. package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +5 -5
  19. package/src/emit/resource/iamResourceArtifacts.js +6 -6
  20. package/src/emit/resource/resourceState.js +3 -9
  21. package/src/emit/system/emitSystemFindDataArtifacts.js +15 -14
  22. package/test/core.test.js +13 -0
  23. package/test/customFlowApplicationArtifacts.test.js +285 -17
  24. package/test/graphDrivenFlowSchema.test.js +325 -0
  25. package/test/relationshipApplicationArtifacts.test.js +29 -0
  26. package/test/resourceArtifacts.test.js +46 -1
  27. package/test/systemFindDataArtifacts.test.js +73 -0
package/test/core.test.js CHANGED
@@ -34,6 +34,19 @@ test('compiles a request and commits only declared relative artifacts', async ()
34
34
  test('rejects invalid generation inputs before writing files', async () => {
35
35
  assert.throws(() => compileProject({}), /rootPath/);
36
36
 
37
+ const corruptPath = await mkdtemp(
38
+ join(tmpdir(), 'generate-service-code-v2-corrupt-yml-')
39
+ );
40
+ await mkdir(join(corruptPath, 'config'), { recursive: true });
41
+ await writeFile(
42
+ join(corruptPath, 'config/serverless.config.yml'),
43
+ 'main_config: [unbalanced brackets:'
44
+ );
45
+ assert.throws(
46
+ () => compileProject({ rootPath: corruptPath }),
47
+ /Failed to read or parse configuration at/
48
+ );
49
+
37
50
  const outputPath = await mkdtemp(
38
51
  join(tmpdir(), 'generate-service-code-v2-invalid-')
39
52
  );
@@ -88,6 +88,15 @@ test('emits handlers, function YAML, and IAM roles for supported custom flow eve
88
88
  assert.match(iam, /ApiRole:/);
89
89
  assert.match(iam, /OwnRole:/);
90
90
  assert.match(iam, /UploadRole:/);
91
+ const customSqsYaml = await readFile(
92
+ join(
93
+ rootPath,
94
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
95
+ ),
96
+ 'utf8'
97
+ );
98
+ assert.match(customSqsYaml, /ExtHdrSqs:/);
99
+ assert.doesNotMatch(customSqsYaml, /ProcessExtHdrSqs/);
91
100
  assert.match(
92
101
  await readFile(
93
102
  join(
@@ -254,7 +263,7 @@ test('removes only V2-owned custom-flow application artifacts when supported eve
254
263
  await generateCustomFlowApplicationArtifacts(rootPath);
255
264
  await writeFile(
256
265
  join(rootPath, 'app/src/schemas/FlowSchemas/Flows.js'),
257
- "export default [{ flowTag: 'later', event: ['unsupported'] }];\n"
266
+ "export default [];\n"
258
267
  );
259
268
 
260
269
  assert.deepEqual(
@@ -469,6 +478,53 @@ test('emits and removes the trigger-cache artifact suite when triggerFlowTypes i
469
478
  );
470
479
  });
471
480
 
481
+ test('triggerCache entrypoint Cart_Main.js contains correct library import and logic', async () => {
482
+ const rootPath = await createTriggerCacheProject(['OrderCheckout']);
483
+ await generateCustomFlowApplicationArtifacts(rootPath);
484
+
485
+ const source = 'app/src/generatedCode/FlowSchemas/Cart/source';
486
+ const mainContent = await readFile(
487
+ join(rootPath, `${source}/Cart_Main.js`),
488
+ 'utf8'
489
+ );
490
+
491
+ assert.match(
492
+ mainContent,
493
+ /import \{ triggeredCacheSharedLibV2 \} from '@izara_project\/izara-core-library-trigger-cache'/,
494
+ 'should import triggeredCacheSharedLibV2'
495
+ );
496
+ assert.match(
497
+ mainContent,
498
+ /triggeredCacheSharedLibV2\.checkTriggerResetedV2/,
499
+ 'should call checkTriggerResetedV2'
500
+ );
501
+ assert.match(
502
+ mainContent,
503
+ /triggeredCacheSharedLibV2\.checkTriggeredCacheV2/,
504
+ 'should call checkTriggeredCacheV2'
505
+ );
506
+ assert.match(
507
+ mainContent,
508
+ /\/\/\(<beforeCheckTriggeredCache>\)/,
509
+ 'should have beforeCheckTriggeredCache hook'
510
+ );
511
+ assert.match(
512
+ mainContent,
513
+ /\/\/\(<dispatchTriggerFlowHook>\)/,
514
+ 'should have dispatchTriggerFlowHook hook'
515
+ );
516
+ assert.match(
517
+ mainContent,
518
+ /if \(triggeredCacheResult\?\.hasPendingTasks\)/,
519
+ 'should guard dispatch with hasPendingTasks check'
520
+ );
521
+ assert.doesNotMatch(
522
+ mainContent,
523
+ /checkStoredCacheStatus/,
524
+ 'should not contain storedCache logic'
525
+ );
526
+ });
527
+
472
528
  test('uses custom CheckTrigger step if provided by developer in flowSteps', async () => {
473
529
  const rootPath = await mkdtemp(
474
530
  join(tmpdir(), 'generate-service-code-v2-trigger-cache-custom-')
@@ -494,28 +550,77 @@ test('uses custom CheckTrigger step if provided by developer in flowSteps', asyn
494
550
  assert.match(flowYaml, /^CartCheckTriggerCustomStepHdrSqs:/m);
495
551
  });
496
552
 
497
- test('does not emit check trigger-cache artifacts when triggerFlowTypes is empty, null, undefined, or missing', async () => {
553
+ test('uses full {flowTag}{stepName} queue name when stepName starts with flowTag', async () => {
554
+ const rootPath = await createTriggerCacheProject(['OrderCheckout']);
555
+ await writeFile(
556
+ join(rootPath, 'app/src/schemas/FlowSchemas/Cart.js'),
557
+ "export default { flowTag: 'cart', event: ['ownTopic'], statusType: 'triggerCache', triggerFlowTypes: ['OrderCheckout'], objType: { serviceTag: 'Order' }, flowSteps: [{ stepName: 'start' }, { stepName: 'CartCheckX', event: ['extTopic'] }] };\n"
558
+ );
559
+ await generateCustomFlowApplicationArtifacts(rootPath);
560
+ await generateResourceArtifacts(rootPath);
561
+
562
+ const source = 'app/src/generatedCode/FlowSchemas/Cart/source';
563
+ for (const fileName of [
564
+ 'CartCartCheckX_Main.js',
565
+ 'CartCartCheckX_HdrSqs.js'
566
+ ]) {
567
+ const path = join(rootPath, source, fileName);
568
+ await access(path);
569
+ await parseAsEsm(path);
570
+ }
571
+
572
+ const flowYaml = await readFile(
573
+ join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
574
+ 'utf8'
575
+ );
576
+ assert.match(flowYaml, /^CartCartCheckXHdrSqs:/m);
577
+ assert.match(
578
+ await readFile(join(rootPath, `${source}/CartCartCheckX_HdrSqs.js`), 'utf8'),
579
+ /CartCartCheckXHdrSqs/
580
+ );
581
+
582
+ const resources = await readFile(
583
+ join(
584
+ rootPath,
585
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
586
+ ),
587
+ 'utf8'
588
+ );
589
+ assert.match(
590
+ resources,
591
+ new RegExp(
592
+ 'QueueName: \\$\\{self:custom\\.iz_resourcePrefix\\}CartCartCheckXHdrSqs'
593
+ )
594
+ );
595
+ });
596
+
597
+ test('rejects when statusType triggerCache has triggerFlowTypes empty, null, undefined, or missing', async () => {
498
598
  for (const emptyTriggerFlowTypes of [[], null, undefined]) {
499
599
  const rootPath = await createTriggerCacheProject(emptyTriggerFlowTypes);
500
- await generateCustomFlowApplicationArtifacts(rootPath);
501
- await generateResourceArtifacts(rootPath);
502
-
503
- const source = 'app/src/generatedCode/FlowSchemas/Cart/source';
504
- await assert.rejects(
505
- readFile(join(rootPath, `${source}/CartCheckTriggerFlows_Main.js`))
506
- );
507
600
  await assert.rejects(
508
- readFile(join(rootPath, `${source}/CartCheckTriggerFlows_HdrSqs.js`))
601
+ generateCustomFlowApplicationArtifacts(rootPath),
602
+ /FlowSchema validation error.*triggerCache.*requires a non-empty 'triggerFlowTypes' array/
509
603
  );
510
-
511
- const flowYaml = await readFile(
512
- join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
513
- 'utf8'
514
- );
515
- assert.doesNotMatch(flowYaml, /CartCheckTriggerFlowsHdrSqs/);
516
604
  }
517
605
  });
518
606
 
607
+ test('rejects when statusType storedCache cannot resolve DynamoDB table from objType', async () => {
608
+ const rootPath = await mkdtemp(
609
+ join(tmpdir(), 'generate-service-code-v2-stored-cache-err-')
610
+ );
611
+ roots.add(rootPath);
612
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
613
+ await mkdir(schemaRoot, { recursive: true });
614
+ await writeFile(
615
+ join(schemaRoot, 'CacheFlow.js'),
616
+ "export default { flowTag: 'cacheFlow', event: ['ownTopic'], statusType: 'storedCache', objType: { objectType: 'missingObj' }, flowSteps: [{ stepName: 'Start' }] };\n"
617
+ );
618
+ await assert.rejects(
619
+ generateCustomFlowApplicationArtifacts(rootPath),
620
+ /FlowSchema validation error.*storedCache.*cannot resolve DynamoDB tableName/
621
+ );
622
+ });
623
+
519
624
  test('rejects an EventBridge schedule without a deployable name and rate', () => {
520
625
  assert.throws(
521
626
  () =>
@@ -565,7 +670,7 @@ export default [
565
670
  { ...base, flowTag: 'cache', event: ['ownTopic'], statusType: 'storedCache', objType: { objectType: 'order', serviceTag: 'Current' } },
566
671
  { ...base, flowTag: 'mixed', event: ['ownTopic', 'extTopic'] },
567
672
  { ...base, flowTag: 'generated', event: ['ownTopic'], generatedCodeConfig: { generatedCodeTag: 'create' } },
568
- { ...base, flowTag: 'upload', event: ['s3'] }
673
+ { ...base, flowTag: 'upload', event: ['s3'], bucketName: 'UploadBucket' }
569
674
  ];
570
675
  `
571
676
  );
@@ -1271,3 +1376,166 @@ test('emits handler and YAML for flow with event generateCode and invocationType
1271
1376
  assert.match(yaml, /GenSyncHdrInv:/);
1272
1377
  });
1273
1378
 
1379
+ test('rejects FlowSchema with event storedCache at loader', async () => {
1380
+ const rootPath = await createProject();
1381
+ await writeFile(
1382
+ join(rootPath, 'app/src/schemas/FlowSchemas/BadEvent.js'),
1383
+ `export default {
1384
+ flowTag: 'badEvent',
1385
+ event: ['storedCache'],
1386
+ statusType: 'storedCache',
1387
+ objType: { objectType: 'order' },
1388
+ flowSteps: [{ stepName: 'Start' }]
1389
+ };\n`
1390
+ );
1391
+
1392
+ await assert.rejects(
1393
+ () => generateCustomFlowApplicationArtifacts(rootPath),
1394
+ /Flow 'badEvent' event\[0\] 'storedCache' is invalid\. Must be one of/
1395
+ );
1396
+ });
1397
+
1398
+ test('emits early return cacheMain on cache hit in FlowEndpoint_Main for storedCache', async () => {
1399
+ const rootPath = await createProject();
1400
+ await writeFile(
1401
+ join(rootPath, 'app/src/schemas/FlowSchemas/CacheFlow.js'),
1402
+ `export default {
1403
+ flowTag: 'cacheOrder',
1404
+ event: ['ownTopic'],
1405
+ statusType: 'storedCache',
1406
+ objType: { objectType: 'order', serviceTag: 'Current' },
1407
+ flowSteps: [{ stepName: 'Start' }]
1408
+ };\n`
1409
+ );
1410
+
1411
+ await generateCustomFlowApplicationArtifacts(rootPath);
1412
+
1413
+ const mainPath = join(
1414
+ rootPath,
1415
+ 'app/src/generatedCode/FlowSchemas/CacheOrder/source/CacheOrder_Main.js'
1416
+ );
1417
+ const mainContent = await readFile(mainPath, 'utf8');
1418
+ assert.match(mainContent, /checkStoredCacheStatus/);
1419
+ assert.match(mainContent, /if \(cacheMainStatus === 'complete'\) \{/);
1420
+ assert.match(mainContent, /return cacheMain;/);
1421
+ await parseAsEsm(mainPath);
1422
+ });
1423
+
1424
+ test('does not emit RbacCallback artifacts or duplicate artifact paths for custom flow with RBAC', async () => {
1425
+ const { generate } = await import('../src/generate.js');
1426
+ const rootPath = await createProject();
1427
+ await writeFile(
1428
+ join(rootPath, 'app/src/schemas/ObjectSchemas/ComAcc.js'),
1429
+ `export default {
1430
+ objectType: 'comAcc',
1431
+ belongTo: {
1432
+ objectType: 'user',
1433
+ serviceTag: 'UserAccount'
1434
+ }
1435
+ };\n`
1436
+ );
1437
+ await writeFile(
1438
+ join(
1439
+ rootPath,
1440
+ 'app/src/schemas/FlowSchemas/CreateBusinessToComAcc.js'
1441
+ ),
1442
+ `export default {
1443
+ flowTag: 'createBusinessToComAcc',
1444
+ event: ['lambdaSyncInv', 'ownTopic'],
1445
+ objType: {
1446
+ objectType: 'comAcc',
1447
+ serviceTag: 'ComAcc'
1448
+ },
1449
+ flowSteps: [{ stepName: 'Start' }]
1450
+ };\n`
1451
+ );
1452
+
1453
+ const result = await generate(rootPath, {
1454
+ serviceTag: 'BusinessService'
1455
+ });
1456
+ assert.ok(result);
1457
+
1458
+ const sourceDir = join(
1459
+ rootPath,
1460
+ 'app/src/generatedCode/FlowSchemas/CreateBusinessToComAcc/source'
1461
+ );
1462
+ const mainPath = join(sourceDir, 'CreateBusinessToComAcc_Main.js');
1463
+ const mainContent = await readFile(mainPath, 'utf8');
1464
+ assert.match(mainContent, /checkRbacPermission/);
1465
+ assert.doesNotMatch(mainContent, /CreateBusinessToComAccRbacCallback/);
1466
+
1467
+ await assert.rejects(
1468
+ () =>
1469
+ readFile(
1470
+ join(sourceDir, 'CreateBusinessToComAccRbacCallback_Main.js'),
1471
+ 'utf8'
1472
+ ),
1473
+ { code: 'ENOENT' }
1474
+ );
1475
+ await assert.rejects(
1476
+ () =>
1477
+ readFile(
1478
+ join(sourceDir, 'CreateBusinessToComAccRbacCallback_HdrSqs.js'),
1479
+ 'utf8'
1480
+ ),
1481
+ { code: 'ENOENT' }
1482
+ );
1483
+
1484
+ const initialSetupConfigContent = await readFile(
1485
+ join(
1486
+ rootPath,
1487
+ 'app/src/generatedCode/CoreFunctions/InitialSetupConfig.js'
1488
+ ),
1489
+ 'utf8'
1490
+ );
1491
+ assert.doesNotMatch(
1492
+ initialSetupConfigContent,
1493
+ /CreateBusinessToComAccRbacCallbackHdrSqs/
1494
+ );
1495
+ });
1496
+
1497
+ test('skips checkRbacPermission in FlowEndpoint_Main when initiateBy is systemNoUser', async () => {
1498
+ const { generate } = await import('../src/generate.js');
1499
+ const rootPath = await createProject();
1500
+ await writeFile(
1501
+ join(rootPath, 'app/src/schemas/ObjectSchemas/ComAcc.js'),
1502
+ `export default {
1503
+ objectType: 'comAcc',
1504
+ belongTo: {
1505
+ objectType: 'user',
1506
+ serviceTag: 'UserAccount'
1507
+ }
1508
+ };\n`
1509
+ );
1510
+ await writeFile(
1511
+ join(
1512
+ rootPath,
1513
+ 'app/src/schemas/FlowSchemas/SystemCronFlow.js'
1514
+ ),
1515
+ `export default {
1516
+ flowTag: 'systemCronFlow',
1517
+ event: ['ownTopic'],
1518
+ initiateBy: 'systemNoUser',
1519
+ objType: {
1520
+ objectType: 'comAcc',
1521
+ serviceTag: 'ComAcc'
1522
+ },
1523
+ flowSteps: [{ stepName: 'Start' }]
1524
+ };\n`
1525
+ );
1526
+
1527
+ const result = await generate(rootPath, {
1528
+ serviceTag: 'BusinessService'
1529
+ });
1530
+ assert.ok(result);
1531
+
1532
+ const mainPath = join(
1533
+ rootPath,
1534
+ 'app/src/generatedCode/FlowSchemas/SystemCronFlow/source/SystemCronFlow_Main.js'
1535
+ );
1536
+ const mainContent = await readFile(mainPath, 'utf8');
1537
+ assert.doesNotMatch(mainContent, /checkRbacPermission/);
1538
+ await parseAsEsm(mainPath);
1539
+ });
1540
+
1541
+