@izara_project/izara-core-generate-service-code 1.0.77 → 1.0.79

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 (63) hide show
  1. package/package.json +2 -2
  2. package/src/compile/flowValidator.js +163 -0
  3. package/src/compile/loadSchemas.js +15 -5
  4. package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +137 -7
  5. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +132 -17
  6. package/src/emit/crud/crudActionPlan.js +82 -0
  7. package/src/emit/crud/emitCrudApplicationArtifacts.js +162 -112
  8. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +43 -65
  9. package/src/emit/crud/generateDynamicCrudFlowSchemas.js +4 -1
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +84 -512
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +536 -0
  12. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +87 -202
  13. package/src/emit/crud/templates/DeleteLogicalLib.ejs +213 -0
  14. package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +217 -0
  15. package/src/emit/crud/templates/GetEndpoint_Main.ejs +1 -1
  16. package/src/emit/crud/templates/GetRangeEndpoint_Main.ejs +9 -2
  17. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +87 -444
  18. package/src/emit/crud/templates/UpdateLogicalLib.ejs +472 -0
  19. package/src/emit/custom/customFlows.js +27 -2
  20. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +1 -2
  21. package/src/emit/custom/templates/FlowEndpoint_Main.ejs +10 -3
  22. package/src/emit/custom/templates/FlowStep_Main.ejs +1 -1
  23. package/src/emit/custom/templates/s3/CreateObject_Main.ejs +1 -1
  24. package/src/emit/custom/templates/triggerCache/CheckTriggerCache_HdrSqs.ejs +2 -2
  25. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +3 -9
  26. package/src/emit/rbac/emitRbacApplicationArtifacts.js +21 -16
  27. package/src/emit/rbac/rbacFlowTag.js +2 -7
  28. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +148 -21
  29. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +4 -2
  30. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +1 -1
  31. package/src/emit/relationship/templates/CreateRelationship_Main.ejs +1 -1
  32. package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +1 -1
  33. package/src/emit/relationship/templates/GetRelationship_Main.ejs +1 -1
  34. package/src/emit/relationship/templates/MoveRelationship_Main.ejs +1 -1
  35. package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +1 -1
  36. package/src/emit/resource/emitResourceArtifacts.js +85 -70
  37. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +31 -5
  38. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +21 -0
  39. package/src/emit/resource/templates/WebSocketSubscription_Yaml.ejs +7 -0
  40. package/src/emit/shared/templates/FlowHandler_HdrApi.ejs +1 -1
  41. package/src/emit/shared/templates/FlowHandler_HdrDsq.ejs +2 -2
  42. package/src/emit/shared/templates/FlowHandler_HdrInv.ejs +1 -1
  43. package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +2 -2
  44. package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -2
  45. package/src/emit/system/templates/RegisterComplete_Yaml.ejs +4 -4
  46. package/src/emit/systemText/emitSystemTextSchemas.js +246 -0
  47. package/src/emit/systemText/templates/SystemTextSchemas.ejs +10 -0
  48. package/src/generate.js +55 -8
  49. package/test/crudApplicationArtifacts.test.js +147 -19
  50. package/test/customFlowApplicationArtifacts.test.js +308 -16
  51. package/test/dynamicCrudFlowSchemas.test.js +150 -14
  52. package/test/dynamicRbacFlowSchemas.test.js +2 -2
  53. package/test/dynamicRelationshipFlowSchemas.test.js +1 -1
  54. package/test/graphDrivenFlowSchema.test.js +257 -0
  55. package/test/publicApi.test.js +15 -6
  56. package/test/resourceArtifacts.test.js +25 -19
  57. package/test/systemTextSchemas.test.js +154 -0
  58. package/src/emit/crud/templates/CreateComplete_HdrSqs.ejs +0 -99
  59. package/src/emit/crud/templates/DeleteComplete_HdrSqs.ejs +0 -99
  60. package/src/emit/crud/templates/FlowEndpointBeforeLogical_Main.ejs +0 -174
  61. package/src/emit/crud/templates/FlowEndpointComplete_Main.ejs +0 -187
  62. package/src/emit/crud/templates/UpdateComplete_HdrSqs.ejs +0 -99
  63. package/src/emit/resource/templates/ResourceBeforeLogicalYaml.ejs +0 -47
@@ -13,6 +13,7 @@ import { dirname, join, resolve } from 'node:path';
13
13
  import { promisify } from 'node:util';
14
14
  import test, { after } from 'node:test';
15
15
 
16
+ import { generate } from '../src/generate.js';
16
17
  import { generateCustomFlowApplicationArtifacts } from '../src/emit/custom/generateCustomFlowApplicationArtifacts.js';
17
18
  import { generateResourceArtifacts } from '../src/emit/resource/generateResourceArtifacts.js';
18
19
  import { renderEventBridgeFunctionYaml } from '../src/emit/function/functionYamlArtifact.js';
@@ -107,16 +108,15 @@ test('emits handlers, function YAML, and IAM roles for supported custom flow eve
107
108
  ),
108
109
  /Own_Main/
109
110
  );
110
- assert.match(
111
- await readFile(
112
- join(
113
- rootPath,
114
- 'app/src/generatedCode/FlowSchemas/Own/source/Own_Main.js'
115
- ),
116
- 'utf8'
111
+ const ownMain = await readFile(
112
+ join(
113
+ rootPath,
114
+ 'app/src/generatedCode/FlowSchemas/Own/source/Own_Main.js'
117
115
  ),
118
- /asyncFlowSharedLib/
116
+ 'utf8'
119
117
  );
118
+ assert.match(ownMain, /asyncFlowSharedLib/);
119
+ assert.match(ownMain, /callingFlowConfig = {},\s*\/\/\(<additionalParams>\)/);
120
120
  assert.match(
121
121
  await readFile(
122
122
  join(
@@ -302,15 +302,17 @@ test('emits and removes ext-topic handlers declared by custom flow steps', async
302
302
  'app/src/generatedCode/FlowSchemas/DsqStep/source/DsqStepRetryDsq_HdrDsq.js'
303
303
  )
304
304
  );
305
- assert.match(
306
- await readFile(
307
- join(
308
- rootPath,
309
- 'app/src/generatedCode/FlowSchemas/Stepped/source/SteppedComplete_Main.js'
310
- ),
311
- 'utf8'
305
+ const steppedCompleteMain = await readFile(
306
+ join(
307
+ rootPath,
308
+ 'app/src/generatedCode/FlowSchemas/Stepped/source/SteppedComplete_Main.js'
312
309
  ),
313
- /identifierUuid/
310
+ 'utf8'
311
+ );
312
+ assert.match(steppedCompleteMain, /identifierUuid/);
313
+ assert.match(
314
+ steppedCompleteMain,
315
+ /callingFlowConfig = {},\s*\/\/\(<additionalParams>\)/
314
316
  );
315
317
  assert.match(
316
318
  await readFile(
@@ -819,3 +821,293 @@ test('renders nextSteps IAM permissions in InitialSetupConfig', async () => {
819
821
  );
820
822
  });
821
823
 
824
+ test('registers SQS queue, SNS subscription, and SNS out topic for custom flowSchema with outputTopic set to true', async () => {
825
+ const { generate } = await import('../src/generate.js');
826
+ const rootPath = await mkdtemp(
827
+ join(tmpdir(), 'generate-service-code-v2-outputtopic-')
828
+ );
829
+ roots.add(rootPath);
830
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
831
+ await mkdir(schemaRoot, { recursive: true });
832
+
833
+ await writeFile(
834
+ join(schemaRoot, 'MyCustomFlow.js'),
835
+ `export default [
836
+ {
837
+ flowTag: 'MyCustomFlow',
838
+ initiateBy: 'user',
839
+ handleObj: 'one',
840
+ statusType: 'none',
841
+ objType: {
842
+ objectType: 'myObj',
843
+ serviceTag: 'TST'
844
+ },
845
+ event: ['ownTopic'],
846
+ outputTopic: true
847
+ }
848
+ ];\n`
849
+ );
850
+
851
+ const result = await generate(rootPath, {
852
+ serviceTag: 'TestService'
853
+ });
854
+ assert.ok(result);
855
+
856
+ const snsInSqs = await readFile(
857
+ join(
858
+ rootPath,
859
+ 'resource/sls_yaml/generatedCode/source/generated-sns-in-sqs.yml'
860
+ ),
861
+ 'utf8'
862
+ );
863
+ assert.match(snsInSqs, /^ MyCustomFlowCompleteHdrSqs:/m);
864
+
865
+ const snsOut = await readFile(
866
+ join(
867
+ rootPath,
868
+ 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml'
869
+ ),
870
+ 'utf8'
871
+ );
872
+ assert.match(snsOut, /^\s*OutMyCustomFlow:/m);
873
+ assert.match(snsOut, /MyCustomFlow_Out/);
874
+
875
+ const initialSetupConfigContent = await readFile(
876
+ join(
877
+ rootPath,
878
+ 'app/src/generatedCode/CoreFunctions/InitialSetupConfig.js'
879
+ ),
880
+ 'utf8'
881
+ );
882
+ assert.match(initialSetupConfigContent, /serviceTag: 'TST'/);
883
+ assert.match(initialSetupConfigContent, /topicName: 'MyCustomFlow_Out'/);
884
+ assert.match(
885
+ initialSetupConfigContent,
886
+ /sqsEndpoint: 'MyCustomFlowCompleteHdrSqs'/
887
+ );
888
+ });
889
+
890
+ test('renders RBAC Validator subscriptions in InitialSetupConfig when object has belongTo to external RBAC service', async () => {
891
+ const rootPath = await createProject();
892
+ await writeFile(
893
+ join(rootPath, 'app/src/schemas/ObjectSchemas/Member.js'),
894
+ `export default {
895
+ objectType: 'member',
896
+ belongTo: {
897
+ objectType: 'user',
898
+ serviceTag: 'UserAccount'
899
+ }
900
+ };\n`
901
+ );
902
+
903
+ const result = await generate(rootPath, {
904
+ serviceTag: 'MembershipService'
905
+ });
906
+ assert.ok(result);
907
+
908
+ const initialSetupConfigContent = await readFile(
909
+ join(
910
+ rootPath,
911
+ 'app/src/generatedCode/CoreFunctions/InitialSetupConfig.js'
912
+ ),
913
+ 'utf8'
914
+ );
915
+ assert.match(initialSetupConfigContent, /serviceTag: 'UserAccount'/);
916
+ assert.match(initialSetupConfigContent, /topicName: 'ValidatorRbac_Out'/);
917
+ assert.match(initialSetupConfigContent, /sqsEndpoint: 'CreateCompleteHdrSqs'/);
918
+ assert.match(initialSetupConfigContent, /sqsEndpoint: 'UpdateCompleteHdrSqs'/);
919
+ assert.match(initialSetupConfigContent, /sqsEndpoint: 'DeleteCompleteHdrSqs'/);
920
+ });
921
+
922
+ test('deduplicates duplicate nextSteps topics across flowSteps in InitialSetupConfig', async () => {
923
+ const { generate } = await import('../src/generate.js');
924
+ const rootPath = await mkdtemp(
925
+ join(tmpdir(), 'generate-service-code-v2-dedupe-nextsteps-')
926
+ );
927
+ roots.add(rootPath);
928
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
929
+ await mkdir(schemaRoot, { recursive: true });
930
+
931
+ await writeFile(
932
+ join(schemaRoot, 'DuplicateTopicFlow.js'),
933
+ `export default {
934
+ flowTag: 'DuplicateTopicFlow',
935
+ event: ['ownTopic'],
936
+ flowSteps: [
937
+ {
938
+ stepName: 'StepOne',
939
+ nextSteps: [
940
+ {
941
+ targetType: 'extTopic',
942
+ serviceTag: 'GraphHandler',
943
+ topic: 'GetDeepPathSearch_In'
944
+ },
945
+ {
946
+ targetType: 'lambdaSyncInv',
947
+ serviceTag: 'SharedService',
948
+ functionName: 'ValidateSomething'
949
+ }
950
+ ]
951
+ },
952
+ {
953
+ stepName: 'StepTwo',
954
+ event: ['extTopic'],
955
+ nextSteps: [
956
+ {
957
+ targetType: 'extTopic',
958
+ serviceTag: 'GraphHandler',
959
+ topic: 'GetDeepPathSearch_In'
960
+ },
961
+ {
962
+ targetType: 'lambdaSyncInv',
963
+ serviceTag: 'SharedService',
964
+ functionName: 'ValidateSomething'
965
+ }
966
+ ]
967
+ }
968
+ ]
969
+ };\n`
970
+ );
971
+
972
+ const result = await generate(rootPath, {
973
+ serviceTag: 'DuplicateTestService'
974
+ });
975
+ assert.ok(result);
976
+
977
+ const initialSetupConfigContent = await readFile(
978
+ join(
979
+ rootPath,
980
+ 'app/src/generatedCode/CoreFunctions/InitialSetupConfig.js'
981
+ ),
982
+ 'utf8'
983
+ );
984
+
985
+ const snsMatches = initialSetupConfigContent.match(
986
+ /GetDeepPathSearch_In/g
987
+ );
988
+ assert.equal(
989
+ snsMatches?.length,
990
+ 1,
991
+ 'SNS topic GetDeepPathSearch_In should appear exactly once in externalLambdaRole'
992
+ );
993
+
994
+ const lambdaMatches = initialSetupConfigContent.match(
995
+ /ValidateSomething/g
996
+ );
997
+ assert.equal(
998
+ lambdaMatches?.length,
999
+ 1,
1000
+ 'Lambda function ValidateSomething should appear exactly once in externalLambdaRole'
1001
+ );
1002
+
1003
+ assert.doesNotMatch(
1004
+ initialSetupConfigContent,
1005
+ /<#|#>/,
1006
+ 'InitialSetupConfig.js should not contain invalid <#...#> hook tags'
1007
+ );
1008
+ assert.match(
1009
+ initialSetupConfigContent,
1010
+ /\/\/\(<InitialSetupConfig_ExternalServices>\)/
1011
+ );
1012
+ assert.match(
1013
+ initialSetupConfigContent,
1014
+ /\/\/\(<\/InitialSetupConfig_ExternalServices>\)/
1015
+ );
1016
+ assert.match(
1017
+ initialSetupConfigContent,
1018
+ /\/\/\(<InitialSetupConfig_Prod_Config_TableConfig>\)/
1019
+ );
1020
+ assert.match(
1021
+ initialSetupConfigContent,
1022
+ /\/\/\(<InitialSetupConfig_Test_Config_TableConfig>\)/
1023
+ );
1024
+ });
1025
+
1026
+ test('renders multi-type array for mixed property and messageAttribute type in handler validator schema', async () => {
1027
+ const rootPath = await mkdtemp(
1028
+ join(tmpdir(), 'generate-service-code-v2-mixed-type-')
1029
+ );
1030
+ roots.add(rootPath);
1031
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
1032
+ await mkdir(schemaRoot, { recursive: true });
1033
+
1034
+ await writeFile(
1035
+ join(schemaRoot, 'MixedFlow.js'),
1036
+ `export default {
1037
+ flowTag: 'MixedFlow',
1038
+ event: ['ownTopic', 'lambdaSyncApi', 'lambdaSyncInv', 'dsq'],
1039
+ stepProperties: {
1040
+ payload: { propertyName: 'payload', type: 'mixed' }
1041
+ },
1042
+ stepMessageAttributes: {
1043
+ attr: { propertyName: 'attr', type: 'mixed' }
1044
+ },
1045
+ flowSteps: [
1046
+ {
1047
+ stepName: 'Start',
1048
+ properties: ['payload'],
1049
+ messageAttributes: ['attr']
1050
+ }
1051
+ ]
1052
+ };\n`
1053
+ );
1054
+
1055
+ const result = await generateCustomFlowApplicationArtifacts(rootPath);
1056
+ assert.ok(result);
1057
+
1058
+ const sqsHdr = await readFile(
1059
+ join(
1060
+ rootPath,
1061
+ 'app/src/generatedCode/FlowSchemas/MixedFlow/source/MixedFlow_HdrSqs.js'
1062
+ ),
1063
+ 'utf8'
1064
+ );
1065
+ assert.match(
1066
+ sqsHdr,
1067
+ /payload:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1068
+ );
1069
+ assert.match(
1070
+ sqsHdr,
1071
+ /Value:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1072
+ );
1073
+
1074
+ const apiHdr = await readFile(
1075
+ join(
1076
+ rootPath,
1077
+ 'app/src/generatedCode/FlowSchemas/MixedFlow/source/MixedFlow_HdrApi.js'
1078
+ ),
1079
+ 'utf8'
1080
+ );
1081
+ assert.match(
1082
+ apiHdr,
1083
+ /payload:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1084
+ );
1085
+
1086
+ const invHdr = await readFile(
1087
+ join(
1088
+ rootPath,
1089
+ 'app/src/generatedCode/FlowSchemas/MixedFlow/source/MixedFlow_HdrInv.js'
1090
+ ),
1091
+ 'utf8'
1092
+ );
1093
+ assert.match(
1094
+ invHdr,
1095
+ /payload:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1096
+ );
1097
+
1098
+ const dsqHdr = await readFile(
1099
+ join(
1100
+ rootPath,
1101
+ 'app/src/generatedCode/FlowSchemas/MixedFlow/source/MixedFlow_HdrDsq.js'
1102
+ ),
1103
+ 'utf8'
1104
+ );
1105
+ assert.match(
1106
+ dsqHdr,
1107
+ /payload:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1108
+ );
1109
+ assert.match(
1110
+ dsqHdr,
1111
+ /Value:\s*\{\s*type:\s*\['string',\s*'number',\s*'integer',\s*'boolean',\s*'null',\s*'object',\s*'array'\]/
1112
+ );
1113
+ });
@@ -9,8 +9,7 @@ import { generateDynamicCrudFlowSchemas } from '../src/emit/crud/generateDynamic
9
9
 
10
10
  test('generates dynamic CRUD schemas from local schema modules', async () => {
11
11
  const rootPath = await createProject({
12
- generatedBy: 'userGenerated',
13
- flow: createHookFlow(['ownTopic'])
12
+ generatedBy: 'userGenerated'
14
13
  });
15
14
 
16
15
  const result = await generateDynamicCrudFlowSchemas(rootPath);
@@ -29,7 +28,7 @@ test('generates dynamic CRUD schemas from local schema modules', async () => {
29
28
  assert.deepEqual(create.event, ['ownTopic']);
30
29
  assert.deepEqual(
31
30
  create.flowSteps.map((step) => step.stepName),
32
- ['start', 'BeforeLogical', 'Complete']
31
+ ['Start', 'Complete']
33
32
  );
34
33
  assert.deepEqual(create.flowSteps[0].returnValues.messageAttributes, [
35
34
  'userId',
@@ -43,7 +42,6 @@ test('generates dynamic CRUD schemas from local schema modules', async () => {
43
42
  test('accepts singleton object exports used by existing schema files', async () => {
44
43
  const rootPath = await createProject({
45
44
  generatedBy: 'userGenerated',
46
- flow: createHookFlow(['ownTopic']),
47
45
  singletonExports: true
48
46
  });
49
47
 
@@ -54,7 +52,7 @@ test('accepts singleton object exports used by existing schema files', async ()
54
52
  (await readGeneratedFlow(rootPath, 'Create')).flowSteps.map(
55
53
  (step) => step.stepName
56
54
  ),
57
- ['start', 'BeforeLogical', 'Complete']
55
+ ['Start', 'Complete']
58
56
  );
59
57
  });
60
58
 
@@ -101,25 +99,163 @@ test('rejects source modules that do not default-export schema objects or arrays
101
99
  );
102
100
  });
103
101
 
104
- test('reloads changed local flow schemas instead of reusing module cache', async () => {
102
+ test('rejects deprecated FlowHook schemas before generating CRUD schemas', async () => {
105
103
  const rootPath = await createProject({
106
104
  generatedBy: 'systemGenerated',
107
105
  flow: createHookFlow(['ownTopic'])
108
106
  });
109
- const flowPath = join(rootPath, 'app/src/schemas/FlowSchemas/CreateHook.js');
107
+
108
+ await assert.rejects(
109
+ generateDynamicCrudFlowSchemas(rootPath),
110
+ /generatedCodeConfig\.codeHookTag is no longer supported/
111
+ );
112
+ });
113
+
114
+ test('generates a direct Complete step for RBAC-only Update flows', async () => {
115
+ const rootPath = await mkdtemp(
116
+ join(tmpdir(), 'generate-service-code-v2-crud-rbac-')
117
+ );
118
+ const objectDirectory = join(rootPath, 'app/src/schemas/ObjectSchemas');
119
+ await mkdir(objectDirectory, { recursive: true });
120
+ await writeFile(
121
+ join(objectDirectory, 'Order.js'),
122
+ `export default [{
123
+ objectType: 'order',
124
+ generatedBy: 'userGenerated',
125
+ belongTo: { serviceTag: 'UserAccount', objectType: 'user' }
126
+ }];\n`
127
+ );
110
128
 
111
129
  await generateDynamicCrudFlowSchemas(rootPath);
112
- assert.match(await readGeneratedFile(rootPath, 'Create'), /BeforeLogical/);
130
+ const update = await readGeneratedFlow(rootPath, 'Update');
131
+ assert.deepEqual(
132
+ update.flowSteps.map((step) => step.stepName),
133
+ ['Start', 'Complete']
134
+ );
135
+ assert.deepEqual(update.flowSteps[0].nextSteps, [
136
+ { targetType: 'ownTopic', stepName: 'Complete' }
137
+ ]);
138
+ assert.deepEqual(update.flowSteps[1].previousSteps, [
139
+ { sourceType: 'ownTopic', stepName: 'Start' }
140
+ ]);
141
+ assert.equal(update.flowSteps[1].properties.length, 0);
142
+ });
113
143
 
144
+ test('ignores RBAC for Create even when Graph storage is present', async () => {
145
+ const rootPath = await createProject({
146
+ generatedBy: 'userGenerated',
147
+ });
114
148
  await writeFile(
115
- flowPath,
116
- `export default [${JSON.stringify(createHookFlow([]))}];\n`
149
+ join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
150
+ `export default [{
151
+ objectType: 'order',
152
+ generatedBy: 'userGenerated',
153
+ belongTo: { serviceTag: 'UserAccount', objectType: 'user' }
154
+ , storageResources: { graph: { storageType: 'graph' } }
155
+ }];\n`
117
156
  );
157
+
118
158
  await generateDynamicCrudFlowSchemas(rootPath);
119
- assert.doesNotMatch(
120
- await readGeneratedFile(rootPath, 'Create'),
121
- /BeforeLogical/
159
+ const create = await readGeneratedFlow(rootPath, 'Create');
160
+ assert.deepEqual(create.flowSteps.map((step) => step.stepName), [
161
+ 'Start',
162
+ 'Complete'
163
+ ]);
164
+ });
165
+
166
+ test('uses the renamed RBAC callback step when Graph storage is present', async () => {
167
+ const rootPath = await mkdtemp(
168
+ join(tmpdir(), 'generate-service-code-v2-crud-rbac-graph-')
169
+ );
170
+ const objectDirectory = join(rootPath, 'app/src/schemas/ObjectSchemas');
171
+ await mkdir(objectDirectory, { recursive: true });
172
+ await writeFile(
173
+ join(objectDirectory, 'Order.js'),
174
+ `export default [{
175
+ objectType: 'order',
176
+ generatedBy: 'userGenerated',
177
+ belongTo: { serviceTag: 'UserAccount', objectType: 'user' },
178
+ storageResources: { graph: { storageType: 'graph' } }
179
+ }];\n`
180
+ );
181
+
182
+ await generateDynamicCrudFlowSchemas(rootPath);
183
+ const create = await readGeneratedFlow(rootPath, 'Create');
184
+ assert.deepEqual(create.flowSteps.map((step) => step.stepName), [
185
+ 'Start',
186
+ 'Complete'
187
+ ]);
188
+ assert.deepEqual(create.flowSteps[1].properties, [
189
+ 'returnValue',
190
+ 'parentFlowId',
191
+ 'status',
192
+ 'errorsFound',
193
+ 'graphServiceTag'
194
+ ]);
195
+ });
196
+
197
+ test('chains RbacCallback and Complete for Update with RBAC and Graph', async () => {
198
+ const rootPath = await mkdtemp(
199
+ join(tmpdir(), 'generate-service-code-v2-crud-update-rbac-graph-')
200
+ );
201
+ const objectDirectory = join(rootPath, 'app/src/schemas/ObjectSchemas');
202
+ await mkdir(objectDirectory, { recursive: true });
203
+ await writeFile(
204
+ join(objectDirectory, 'Order.js'),
205
+ `export default [{
206
+ objectType: 'order',
207
+ generatedBy: 'userGenerated',
208
+ belongTo: { serviceTag: 'UserAccount', objectType: 'user' },
209
+ storageResources: { graph: { storageType: 'graph' } }
210
+ }];\n`
211
+ );
212
+
213
+ await generateDynamicCrudFlowSchemas(rootPath);
214
+ const update = await readGeneratedFlow(rootPath, 'Update');
215
+ assert.deepEqual(update.flowSteps.map((step) => step.stepName), [
216
+ 'Start',
217
+ 'RbacCallback',
218
+ 'Complete'
219
+ ]);
220
+ assert.deepEqual(update.flowSteps[0].nextSteps, [
221
+ { targetType: 'ownTopic', stepName: 'RbacCallback' }
222
+ ]);
223
+ assert.deepEqual(update.flowSteps[1].nextSteps, [
224
+ { targetType: 'ownTopic', stepName: 'Complete' }
225
+ ]);
226
+ assert.deepEqual(update.flowSteps[2].properties, [
227
+ 'returnValue',
228
+ 'parentFlowId',
229
+ 'status',
230
+ 'errorsFound',
231
+ 'graphServiceTag'
232
+ ]);
233
+ });
234
+
235
+ test('omits Complete step when no graph storage resources exist', async () => {
236
+ const rootPath = await mkdtemp(
237
+ join(tmpdir(), 'generate-service-code-v2-crud-no-graph-')
238
+ );
239
+ const objectDirectory = join(rootPath, 'app/src/schemas/ObjectSchemas');
240
+ await mkdir(objectDirectory, { recursive: true });
241
+ await writeFile(
242
+ join(objectDirectory, 'Order.js'),
243
+ `export default [{
244
+ objectType: 'order',
245
+ generatedBy: 'userGenerated',
246
+ storageResources: {
247
+ myTable: { storageType: 'dynamoDB', tableName: 'OrderTable' }
248
+ }
249
+ }];\n`
250
+ );
251
+
252
+ await generateDynamicCrudFlowSchemas(rootPath);
253
+ const create = await readGeneratedFlow(rootPath, 'Create');
254
+ assert.deepEqual(
255
+ create.flowSteps.map((step) => step.stepName),
256
+ ['Start']
122
257
  );
258
+ assert.equal(create.flowSteps[0].nextSteps, undefined);
123
259
  });
124
260
 
125
261
  async function createProject({ generatedBy, flow, singletonExports = false }) {
@@ -134,7 +270,7 @@ async function createProject({ generatedBy, flow, singletonExports = false }) {
134
270
  await mkdir(join(rootPath, 'app/src/schemas/FlowSchemas'), {
135
271
  recursive: true
136
272
  });
137
- const object = `{ objectType: 'order', generatedBy: '${generatedBy}' }`;
273
+ const object = `{ objectType: 'order', generatedBy: '${generatedBy}', storageResources: { graph: { storageType: 'graph' } } }`;
138
274
  await writeFile(
139
275
  objectPath,
140
276
  `export default ${singletonExports ? object : `[${object}]`};\n`
@@ -42,14 +42,14 @@ test('generates shared and object-specific RBAC flow schemas', async () => {
42
42
  (action) =>
43
43
  `app/src/schemas/FlowSchemas/generateCode/FlowRbac/${action}.js`
44
44
  ),
45
- 'app/src/schemas/FlowSchemas/generateCode/FlowRbac/UserRbacFlow.js'
45
+ 'app/src/schemas/FlowSchemas/generateCode/FlowRbac/ValidatorRbac.js'
46
46
  ]
47
47
  );
48
48
  const createTargetRole = await readGeneratedFlow(
49
49
  rootPath,
50
50
  'CreateTargetRole'
51
51
  );
52
- const user = await readGeneratedFlow(rootPath, 'UserRbacFlow');
52
+ const user = await readGeneratedFlow(rootPath, 'ValidatorRbac');
53
53
  assert.deepEqual(createTargetRole.flowSteps[0].returnValues, {
54
54
  properties: ['returnResult'],
55
55
  messageAttributes: []
@@ -54,7 +54,7 @@ test('generates dynamic relationship schemas when declared relationships exist',
54
54
  assert.deepEqual(create.event, ['ownTopic']);
55
55
  assert.deepEqual(
56
56
  create.flowSteps.map((step) => step.stepName),
57
- ['start', 'Complete']
57
+ ['Start', 'Complete']
58
58
  );
59
59
  assert.deepEqual(create.flowSteps[0].returnValues.messageAttributes, [
60
60
  'userId',