@izara_project/izara-core-generate-service-code 1.0.82 → 1.0.84
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.
- package/package.json +4 -4
- package/src/compile/flowValidator.js +34 -0
- package/src/compile/loadSchemas.js +121 -7
- package/src/emit/attributeTree/emitAttributeTreeSchemas.js +388 -0
- package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +29 -1
- package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +16 -3
- package/src/emit/crud/crudActionPlan.js +43 -7
- package/src/emit/crud/emitCrudApplicationArtifacts.js +19 -4
- package/src/emit/crud/emitDynamicCrudFlowSchemas.js +27 -6
- package/src/emit/crud/generateCrudApplicationArtifacts.js +3 -2
- package/src/emit/crud/generateDynamicCrudFlowSchemas.js +3 -2
- package/src/emit/crud/loadLocalSchemaArrays.js +3 -3
- package/src/emit/crud/templates/CreateComplete_Main.ejs +1 -1
- package/src/emit/crud/templates/CreateEndpoint_Main.ejs +75 -21
- package/src/emit/crud/templates/CreateLogicalLib.ejs +73 -8
- package/src/emit/crud/templates/DeleteComplete_Main.ejs +1 -1
- package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +39 -11
- package/src/emit/crud/templates/DeleteLogicalLib.ejs +6 -3
- package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +2 -2
- package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +7 -0
- package/src/emit/crud/templates/FlowMain_Wbs.ejs +5 -2
- package/src/emit/crud/templates/GetEndpoint_Main.ejs +13 -0
- package/src/emit/crud/templates/UpdateComplete_Main.ejs +1 -1
- package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +53 -4
- package/src/emit/crud/templates/UpdateLogicalLib.ejs +4 -3
- package/src/emit/custom/customFlows.js +49 -0
- package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +168 -49
- package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +1 -1
- package/src/emit/custom/templates/FlowEndpointRbacCallback_Main.ejs +194 -0
- package/src/emit/custom/templates/FlowEndpoint_Main.ejs +14 -22
- package/src/emit/custom/templates/FlowStep_Main.ejs +1 -1
- package/src/emit/function/templates/WbsFunction_Yaml.ejs +0 -2
- package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +33 -8
- package/src/emit/rbac/emitRbacApplicationArtifacts.js +204 -7
- package/src/emit/rbac/templates/CancelUsage_Main.ejs +103 -0
- package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +71 -0
- package/src/emit/rbac/templates/CreateTargetRole_Main.ejs +49 -6
- package/src/emit/rbac/templates/ListRbac_Main.ejs +9 -3
- package/src/emit/rbac/templates/RbacMutation_Main.ejs +1 -1
- package/src/emit/rbac/templates/ReservedDynamicUsage_Main.ejs +147 -0
- package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +59 -23
- package/src/emit/rbac/templates/UserRoleMutation_Main.ejs +1 -1
- package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -1
- package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +10 -3
- package/src/emit/resource/emitResourceArtifacts.js +350 -112
- package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +53 -2
- package/src/emit/system/emitSystemWebSocketArtifacts.js +6 -4
- package/src/emit/system/templates/WebSocketConnect.ejs +71 -12
- package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +7 -0
- package/src/emit/systemText/emitSystemTextSchemas.js +3 -2
- package/src/generate.js +41 -10
- package/test/attributeTreeSchemas.test.js +219 -0
- package/test/createTargetRoleHandler.test.js +59 -4
- package/test/crudApplicationArtifacts.test.js +23 -6
- package/test/customFlowApplicationArtifacts.test.js +5 -5
- package/test/dynamicCrudFlowSchemas.test.js +110 -13
- package/test/dynamicRbacFlowSchemas.test.js +15 -2
- package/test/publicApi.test.js +7 -7
- package/test/resourceArtifacts.test.js +70 -29
- package/test/systemTextSchemas.test.js +1 -1
|
@@ -184,7 +184,7 @@ test('preserves function-setting hooks across CRUD regeneration', async () => {
|
|
|
184
184
|
assert.equal(await readFile(yamlPath, 'utf8'), yaml);
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
-
test('
|
|
187
|
+
test('emits a single Complete RBAC handler for Create without Graph', async () => {
|
|
188
188
|
const rootPath = await createRbacProject();
|
|
189
189
|
const result = await generateCrudApplicationArtifacts(rootPath, {
|
|
190
190
|
serviceTag: 'orders'
|
|
@@ -196,13 +196,13 @@ test('does not add RBAC handlers to Create', async () => {
|
|
|
196
196
|
assert.ok(createPaths.includes(
|
|
197
197
|
'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
|
|
198
198
|
));
|
|
199
|
-
assert.ok(
|
|
199
|
+
assert.ok(createPaths.includes(
|
|
200
200
|
'app/src/generatedCode/FlowObjects/Create/source/CreateComplete_Main.js'
|
|
201
201
|
));
|
|
202
202
|
assert.ok(
|
|
203
203
|
!createPaths.some((path) => path.includes('CreateRbacCallback'))
|
|
204
204
|
);
|
|
205
|
-
assert.
|
|
205
|
+
assert.match(
|
|
206
206
|
await readFile(
|
|
207
207
|
join(
|
|
208
208
|
rootPath,
|
|
@@ -280,7 +280,7 @@ test('emits GetRangeHdrApi when an object defines a sortKey identifier', async (
|
|
|
280
280
|
const rootPath = await createProject(true);
|
|
281
281
|
await writeFile(
|
|
282
282
|
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
283
|
-
"export default { objectType: 'order', generatedBy: '
|
|
283
|
+
"export default { objectType: 'order', generatedBy: 'user', identifiers: [{ type: 'identifier', fieldName: 'orderId' }, { type: 'sortKey', fieldName: 'createdAt' }], storageResources: { graph: { storageType: 'graph' } } };\n"
|
|
284
284
|
);
|
|
285
285
|
const result = await generateCrudApplicationArtifacts(rootPath, {
|
|
286
286
|
serviceTag: 'orders'
|
|
@@ -341,6 +341,23 @@ test('removes managed CRUD artifacts when no applicable object remains', async (
|
|
|
341
341
|
);
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
+
test('logs warning when ObjectSchema has invalid or non-CRUD generatedBy in crudApplicationArtifacts', async () => {
|
|
345
|
+
const rootPath = await createProject(false);
|
|
346
|
+
const warnings = [];
|
|
347
|
+
const logger = {
|
|
348
|
+
warn: (msg) => warnings.push(msg)
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const result = await generateCrudApplicationArtifacts(rootPath, { logger });
|
|
352
|
+
assert.deepEqual(result.files, []);
|
|
353
|
+
assert.equal(warnings.length, 1);
|
|
354
|
+
assert.match(
|
|
355
|
+
warnings[0],
|
|
356
|
+
/\[WARN\] \[CRUD Application\] Skipped FlowObjects application artifacts generation/
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
|
|
344
361
|
async function createProject(withCrudObject) {
|
|
345
362
|
const rootPath = await mkdtemp(
|
|
346
363
|
join(tmpdir(), 'generate-service-code-v2-crud-application-')
|
|
@@ -352,7 +369,7 @@ async function createProject(withCrudObject) {
|
|
|
352
369
|
await writeFile(
|
|
353
370
|
join(schemaRoot, 'ObjectSchemas/Order.js'),
|
|
354
371
|
withCrudObject
|
|
355
|
-
? "export default { objectType: 'order', generatedBy: '
|
|
372
|
+
? "export default { objectType: 'order', generatedBy: 'user', storageResources: { graph: { storageType: 'graph' } } };\n"
|
|
356
373
|
: "export default { objectType: 'order' };\n"
|
|
357
374
|
);
|
|
358
375
|
return rootPath;
|
|
@@ -368,7 +385,7 @@ async function createRbacProject(withHook = false, withGraph = false) {
|
|
|
368
385
|
await mkdir(join(schemaRoot, 'FlowSchemas'), { recursive: true });
|
|
369
386
|
await writeFile(
|
|
370
387
|
join(schemaRoot, 'ObjectSchemas/Order.js'),
|
|
371
|
-
`export default { objectType: 'order', generatedBy: '
|
|
388
|
+
`export default { objectType: 'order', generatedBy: 'user', belongTo: { serviceTag: 'UserAccount', objectType: 'user' }${withGraph ? ", storageResources: { graph: { storageType: 'graph' } }" : ''} };\n`
|
|
372
389
|
);
|
|
373
390
|
if (withHook) {
|
|
374
391
|
await writeFile(
|
|
@@ -340,7 +340,7 @@ test('emits and removes ext-topic handlers declared by custom flow steps', async
|
|
|
340
340
|
await readFile(
|
|
341
341
|
join(
|
|
342
342
|
rootPath,
|
|
343
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
343
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
344
344
|
),
|
|
345
345
|
'utf8'
|
|
346
346
|
),
|
|
@@ -350,7 +350,7 @@ test('emits and removes ext-topic handlers declared by custom flow steps', async
|
|
|
350
350
|
await readFile(
|
|
351
351
|
join(
|
|
352
352
|
rootPath,
|
|
353
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
353
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
354
354
|
),
|
|
355
355
|
'utf8'
|
|
356
356
|
),
|
|
@@ -398,7 +398,7 @@ test('emits and removes ext-topic handlers declared by custom flow steps', async
|
|
|
398
398
|
await readFile(
|
|
399
399
|
join(
|
|
400
400
|
rootPath,
|
|
401
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
401
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
402
402
|
),
|
|
403
403
|
'utf8'
|
|
404
404
|
),
|
|
@@ -437,7 +437,7 @@ test('emits and removes the trigger-cache artifact suite when triggerFlowTypes i
|
|
|
437
437
|
const resources = await readFile(
|
|
438
438
|
join(
|
|
439
439
|
rootPath,
|
|
440
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
440
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
441
441
|
),
|
|
442
442
|
'utf8'
|
|
443
443
|
);
|
|
@@ -864,7 +864,7 @@ test('registers SQS queue, SNS subscription, and SNS out topic for custom flowSc
|
|
|
864
864
|
const snsInSqs = await readFile(
|
|
865
865
|
join(
|
|
866
866
|
rootPath,
|
|
867
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
867
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
868
868
|
),
|
|
869
869
|
'utf8'
|
|
870
870
|
);
|
|
@@ -6,10 +6,11 @@ import test from 'node:test';
|
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
|
|
8
8
|
import { generateDynamicCrudFlowSchemas } from '../src/emit/crud/generateDynamicCrudFlowSchemas.js';
|
|
9
|
+
import { createCrudActionPlans } from '../src/emit/crud/crudActionPlan.js';
|
|
9
10
|
|
|
10
11
|
test('generates dynamic CRUD schemas from local schema modules', async () => {
|
|
11
12
|
const rootPath = await createProject({
|
|
12
|
-
generatedBy: '
|
|
13
|
+
generatedBy: 'user'
|
|
13
14
|
});
|
|
14
15
|
|
|
15
16
|
const result = await generateDynamicCrudFlowSchemas(rootPath);
|
|
@@ -41,7 +42,7 @@ test('generates dynamic CRUD schemas from local schema modules', async () => {
|
|
|
41
42
|
|
|
42
43
|
test('accepts singleton object exports used by existing schema files', async () => {
|
|
43
44
|
const rootPath = await createProject({
|
|
44
|
-
generatedBy: '
|
|
45
|
+
generatedBy: 'user',
|
|
45
46
|
singletonExports: true
|
|
46
47
|
});
|
|
47
48
|
|
|
@@ -57,7 +58,7 @@ test('accepts singleton object exports used by existing schema files', async ()
|
|
|
57
58
|
});
|
|
58
59
|
|
|
59
60
|
test('does not generate CRUD schemas without an applicable object', async () => {
|
|
60
|
-
const rootPath = await createProject({ generatedBy: '
|
|
61
|
+
const rootPath = await createProject({ generatedBy: 'user' });
|
|
61
62
|
await generateDynamicCrudFlowSchemas(rootPath);
|
|
62
63
|
await writeFile(
|
|
63
64
|
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
@@ -71,7 +72,7 @@ test('does not generate CRUD schemas without an applicable object', async () =>
|
|
|
71
72
|
});
|
|
72
73
|
|
|
73
74
|
test('does not allow options to override the rootPath argument', async () => {
|
|
74
|
-
const rootPath = await createProject({ generatedBy: '
|
|
75
|
+
const rootPath = await createProject({ generatedBy: 'user' });
|
|
75
76
|
const ignoredRootPath = await createProject({ generatedBy: 'external' });
|
|
76
77
|
|
|
77
78
|
const result = await generateDynamicCrudFlowSchemas(rootPath, {
|
|
@@ -101,7 +102,7 @@ test('rejects source modules that do not default-export schema objects or arrays
|
|
|
101
102
|
|
|
102
103
|
test('rejects deprecated FlowHook schemas before generating CRUD schemas', async () => {
|
|
103
104
|
const rootPath = await createProject({
|
|
104
|
-
generatedBy: '
|
|
105
|
+
generatedBy: 'systemWithNoUser',
|
|
105
106
|
flow: createHookFlow(['ownTopic'])
|
|
106
107
|
});
|
|
107
108
|
|
|
@@ -121,7 +122,7 @@ test('generates a direct Complete step for RBAC-only Update flows', async () =>
|
|
|
121
122
|
join(objectDirectory, 'Order.js'),
|
|
122
123
|
`export default [{
|
|
123
124
|
objectType: 'order',
|
|
124
|
-
generatedBy: '
|
|
125
|
+
generatedBy: 'user',
|
|
125
126
|
belongTo: { serviceTag: 'UserAccount', objectType: 'user' }
|
|
126
127
|
}];\n`
|
|
127
128
|
);
|
|
@@ -141,15 +142,15 @@ test('generates a direct Complete step for RBAC-only Update flows', async () =>
|
|
|
141
142
|
assert.equal(update.flowSteps[1].properties.length, 0);
|
|
142
143
|
});
|
|
143
144
|
|
|
144
|
-
test('
|
|
145
|
+
test('generates RbacCallback and Complete for Create when RBAC and Graph are present', async () => {
|
|
145
146
|
const rootPath = await createProject({
|
|
146
|
-
generatedBy: '
|
|
147
|
+
generatedBy: 'user',
|
|
147
148
|
});
|
|
148
149
|
await writeFile(
|
|
149
150
|
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
150
151
|
`export default [{
|
|
151
152
|
objectType: 'order',
|
|
152
|
-
generatedBy: '
|
|
153
|
+
generatedBy: 'user',
|
|
153
154
|
belongTo: { serviceTag: 'UserAccount', objectType: 'user' }
|
|
154
155
|
, storageResources: { graph: { storageType: 'graph' } }
|
|
155
156
|
}];\n`
|
|
@@ -159,6 +160,7 @@ test('ignores RBAC for Create even when Graph storage is present', async () => {
|
|
|
159
160
|
const create = await readGeneratedFlow(rootPath, 'Create');
|
|
160
161
|
assert.deepEqual(create.flowSteps.map((step) => step.stepName), [
|
|
161
162
|
'Start',
|
|
163
|
+
'RbacCallback',
|
|
162
164
|
'Complete'
|
|
163
165
|
]);
|
|
164
166
|
});
|
|
@@ -173,7 +175,7 @@ test('uses the renamed RBAC callback step when Graph storage is present', async
|
|
|
173
175
|
join(objectDirectory, 'Order.js'),
|
|
174
176
|
`export default [{
|
|
175
177
|
objectType: 'order',
|
|
176
|
-
generatedBy: '
|
|
178
|
+
generatedBy: 'user',
|
|
177
179
|
belongTo: { serviceTag: 'UserAccount', objectType: 'user' },
|
|
178
180
|
storageResources: { graph: { storageType: 'graph' } }
|
|
179
181
|
}];\n`
|
|
@@ -183,9 +185,10 @@ test('uses the renamed RBAC callback step when Graph storage is present', async
|
|
|
183
185
|
const create = await readGeneratedFlow(rootPath, 'Create');
|
|
184
186
|
assert.deepEqual(create.flowSteps.map((step) => step.stepName), [
|
|
185
187
|
'Start',
|
|
188
|
+
'RbacCallback',
|
|
186
189
|
'Complete'
|
|
187
190
|
]);
|
|
188
|
-
assert.deepEqual(create.flowSteps[
|
|
191
|
+
assert.deepEqual(create.flowSteps[2].properties, [
|
|
189
192
|
'returnValue',
|
|
190
193
|
'parentFlowId',
|
|
191
194
|
'status',
|
|
@@ -204,7 +207,7 @@ test('chains RbacCallback and Complete for Update with RBAC and Graph', async ()
|
|
|
204
207
|
join(objectDirectory, 'Order.js'),
|
|
205
208
|
`export default [{
|
|
206
209
|
objectType: 'order',
|
|
207
|
-
generatedBy: '
|
|
210
|
+
generatedBy: 'user',
|
|
208
211
|
belongTo: { serviceTag: 'UserAccount', objectType: 'user' },
|
|
209
212
|
storageResources: { graph: { storageType: 'graph' } }
|
|
210
213
|
}];\n`
|
|
@@ -242,7 +245,7 @@ test('omits Complete step when no graph storage resources exist', async () => {
|
|
|
242
245
|
join(objectDirectory, 'Order.js'),
|
|
243
246
|
`export default [{
|
|
244
247
|
objectType: 'order',
|
|
245
|
-
generatedBy: '
|
|
248
|
+
generatedBy: 'user',
|
|
246
249
|
storageResources: {
|
|
247
250
|
myTable: { storageType: 'dynamoDB', tableName: 'OrderTable' }
|
|
248
251
|
}
|
|
@@ -258,6 +261,57 @@ test('omits Complete step when no graph storage resources exist', async () => {
|
|
|
258
261
|
assert.equal(create.flowSteps[0].nextSteps, undefined);
|
|
259
262
|
});
|
|
260
263
|
|
|
264
|
+
test('evaluates hasQuotaReservation based on billable flag in belongTo and addOnDataStructure', () => {
|
|
265
|
+
const billableObj = {
|
|
266
|
+
objectType: 'unitType',
|
|
267
|
+
generatedBy: 'user',
|
|
268
|
+
addOnDataStructure: [{ type: 'rbac', billable: true }]
|
|
269
|
+
};
|
|
270
|
+
const plans1 = createCrudActionPlans([billableObj]);
|
|
271
|
+
assert.equal(plans1.Create.hasQuotaReservation, true);
|
|
272
|
+
|
|
273
|
+
const nonBillableObj1 = {
|
|
274
|
+
objectType: 'internalTag',
|
|
275
|
+
generatedBy: 'user',
|
|
276
|
+
addOnDataStructure: [{ type: 'rbac', billable: false }]
|
|
277
|
+
};
|
|
278
|
+
const plans2 = createCrudActionPlans([nonBillableObj1]);
|
|
279
|
+
assert.equal(plans2.Create.hasQuotaReservation, false);
|
|
280
|
+
|
|
281
|
+
const nonBillableObj2 = {
|
|
282
|
+
objectType: 'deliveryMethod',
|
|
283
|
+
generatedBy: 'user',
|
|
284
|
+
belongTo: {
|
|
285
|
+
objectType: 'supplyNetwork',
|
|
286
|
+
serviceTag: 'SupplyNetwork',
|
|
287
|
+
billable: false
|
|
288
|
+
},
|
|
289
|
+
belongToSchema: {
|
|
290
|
+
objectType: 'supplyNetwork',
|
|
291
|
+
addOnDataStructure: [{ type: 'rbac' }]
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
const plans3 = createCrudActionPlans([nonBillableObj2]);
|
|
295
|
+
assert.equal(plans3.Create.hasQuotaReservation, false);
|
|
296
|
+
assert.equal(plans3.Update.hasRbac, true);
|
|
297
|
+
|
|
298
|
+
const billableObj2 = {
|
|
299
|
+
objectType: 'unitType',
|
|
300
|
+
generatedBy: 'user',
|
|
301
|
+
belongTo: {
|
|
302
|
+
objectType: 'supplyNetwork',
|
|
303
|
+
serviceTag: 'SupplyNetwork',
|
|
304
|
+
billable: true
|
|
305
|
+
},
|
|
306
|
+
belongToSchema: {
|
|
307
|
+
objectType: 'supplyNetwork',
|
|
308
|
+
addOnDataStructure: [{ type: 'rbac' }]
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
const plans4 = createCrudActionPlans([billableObj2]);
|
|
312
|
+
assert.equal(plans4.Create.hasQuotaReservation, true);
|
|
313
|
+
});
|
|
314
|
+
|
|
261
315
|
async function createProject({ generatedBy, flow, singletonExports = false }) {
|
|
262
316
|
const rootPath = await mkdtemp(
|
|
263
317
|
join(tmpdir(), 'generate-service-code-v2-crud-')
|
|
@@ -318,3 +372,46 @@ function readGeneratedFile(rootPath, action) {
|
|
|
318
372
|
'utf8'
|
|
319
373
|
);
|
|
320
374
|
}
|
|
375
|
+
|
|
376
|
+
test('supports standard generatedBy values (user, systemWithUser, systemNoUser)', async () => {
|
|
377
|
+
for (const generatedBy of ['user', 'systemWithUser', 'systemNoUser']) {
|
|
378
|
+
const rootPath = await createProject({ generatedBy });
|
|
379
|
+
await generateDynamicCrudFlowSchemas(rootPath);
|
|
380
|
+
const create = await readGeneratedFlow(rootPath, 'Create');
|
|
381
|
+
assert.ok(create, `Create flow should be generated for generatedBy: ${generatedBy}`);
|
|
382
|
+
assert.deepEqual(create.flowSteps.map((s) => s.stepName), ['Start', 'Complete']);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('logs warning when ObjectSchema has invalid or non-CRUD generatedBy', async () => {
|
|
387
|
+
const rootPath = await createProject({ generatedBy: 'user' });
|
|
388
|
+
await writeFile(
|
|
389
|
+
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
390
|
+
"export default [{ objectType: 'order', generatedBy: 'invalidGeneratedBy' }];\n"
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const warnings = [];
|
|
394
|
+
const logger = {
|
|
395
|
+
warn: (msg) => warnings.push(msg)
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const result = await generateDynamicCrudFlowSchemas(rootPath, { logger });
|
|
399
|
+
assert.deepEqual(result.files, []);
|
|
400
|
+
assert.equal(warnings.length, 1);
|
|
401
|
+
assert.match(warnings[0], /\[WARN\] \[CRUD FlowSchemas\] Skipped FlowObjects FlowSchemas generation/);
|
|
402
|
+
assert.match(warnings[0], /invalidGeneratedBy/);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('throws error when ObjectSchema validation fails with validateSchemas: true', async () => {
|
|
406
|
+
const rootPath = await createProject({ generatedBy: 'user' });
|
|
407
|
+
await writeFile(
|
|
408
|
+
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
409
|
+
"export default [{ objectType: 'order', fieldNames: {}, identifiers: [] }];\n"
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
await assert.rejects(
|
|
413
|
+
generateDynamicCrudFlowSchemas(rootPath, { validateSchemas: true }),
|
|
414
|
+
/\[SCHEMA_VALIDATION_ERROR\] Schema validation failed for ObjectSchemas\/Order\.js/
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
|
|
@@ -8,7 +8,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
8
8
|
import { generateDynamicRbacFlowSchemas } from '../src/emit/rbac/generateDynamicRbacFlowSchemas.js';
|
|
9
9
|
|
|
10
10
|
const projectRoots = new Set();
|
|
11
|
-
const
|
|
11
|
+
const STATIC_RBAC_ACTIONS = [
|
|
12
12
|
'CreateTargetRole',
|
|
13
13
|
'ListTargetRole',
|
|
14
14
|
'DeleteTargetRole',
|
|
@@ -20,6 +20,15 @@ const STATIC_ACTIONS = [
|
|
|
20
20
|
'DeleteUserFromRole'
|
|
21
21
|
];
|
|
22
22
|
|
|
23
|
+
const STATIC_LIMIT_ACTIONS = [
|
|
24
|
+
'CreateRoleLimit',
|
|
25
|
+
'ListRoleLimits',
|
|
26
|
+
'DeleteRoleLimit',
|
|
27
|
+
'ReservedDynamicUsage',
|
|
28
|
+
'ConfirmUsage',
|
|
29
|
+
'CancelUsage'
|
|
30
|
+
];
|
|
31
|
+
|
|
23
32
|
after(async () => {
|
|
24
33
|
await Promise.all(
|
|
25
34
|
[...projectRoots].map((rootPath) =>
|
|
@@ -38,10 +47,14 @@ test('generates shared and object-specific RBAC flow schemas', async () => {
|
|
|
38
47
|
assert.deepEqual(
|
|
39
48
|
result.files.map((file) => file.path),
|
|
40
49
|
[
|
|
41
|
-
...
|
|
50
|
+
...STATIC_RBAC_ACTIONS.map(
|
|
42
51
|
(action) =>
|
|
43
52
|
`app/src/schemas/FlowSchemas/generateCode/FlowRbac/${action}.js`
|
|
44
53
|
),
|
|
54
|
+
...STATIC_LIMIT_ACTIONS.map(
|
|
55
|
+
(action) =>
|
|
56
|
+
`app/src/schemas/FlowSchemas/generateCode/FlowLimit/${action}.js`
|
|
57
|
+
),
|
|
45
58
|
'app/src/schemas/FlowSchemas/generateCode/FlowRbac/ValidatorRbac.js'
|
|
46
59
|
]
|
|
47
60
|
);
|
package/test/publicApi.test.js
CHANGED
|
@@ -23,7 +23,7 @@ test('generate commits schemas, application artifacts, and resources in one publ
|
|
|
23
23
|
const rootPath = await createCrudProject();
|
|
24
24
|
const result = await generate(rootPath);
|
|
25
25
|
|
|
26
|
-
assert.strictEqual(result.files.length,
|
|
26
|
+
assert.strictEqual(result.files.length, 74);
|
|
27
27
|
assert.equal(result.project.rootPath, rootPath);
|
|
28
28
|
assert.match(
|
|
29
29
|
await readFile(
|
|
@@ -155,7 +155,7 @@ test('generate commits the system WebSocket completion path for own-topic flows'
|
|
|
155
155
|
await readFile(
|
|
156
156
|
join(
|
|
157
157
|
rootPath,
|
|
158
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
158
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-system.yml'
|
|
159
159
|
),
|
|
160
160
|
'utf8'
|
|
161
161
|
),
|
|
@@ -165,7 +165,7 @@ test('generate commits the system WebSocket completion path for own-topic flows'
|
|
|
165
165
|
await readFile(
|
|
166
166
|
join(
|
|
167
167
|
rootPath,
|
|
168
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
168
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-system.yml'
|
|
169
169
|
),
|
|
170
170
|
'utf8'
|
|
171
171
|
),
|
|
@@ -235,7 +235,7 @@ test('generate commits the trigger-cache suite with its queues when triggerFlowT
|
|
|
235
235
|
await readFile(
|
|
236
236
|
join(
|
|
237
237
|
rootPath,
|
|
238
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
238
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
239
239
|
),
|
|
240
240
|
'utf8'
|
|
241
241
|
),
|
|
@@ -273,7 +273,7 @@ test('generate removes the system WebSocket path when the last own-topic flow is
|
|
|
273
273
|
await readFile(
|
|
274
274
|
join(
|
|
275
275
|
rootPath,
|
|
276
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
276
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-system.yml'
|
|
277
277
|
),
|
|
278
278
|
'utf8'
|
|
279
279
|
),
|
|
@@ -309,7 +309,7 @@ test('generate removes stale CRUD schemas and application artifacts', async () =
|
|
|
309
309
|
|
|
310
310
|
const result = await generate(rootPath);
|
|
311
311
|
|
|
312
|
-
assert.equal(result.files.length,
|
|
312
|
+
assert.equal(result.files.length, 38);
|
|
313
313
|
await assert.rejects(
|
|
314
314
|
readFile(
|
|
315
315
|
join(
|
|
@@ -346,7 +346,7 @@ async function createCrudProject() {
|
|
|
346
346
|
await mkdir(objectDirectory, { recursive: true });
|
|
347
347
|
await writeFile(
|
|
348
348
|
join(objectDirectory, 'Order.js'),
|
|
349
|
-
"export default { objectType: 'order', generatedBy: '
|
|
349
|
+
"export default { objectType: 'order', generatedBy: 'user', storageResources: { graph: { storageType: 'graph' } } };\n"
|
|
350
350
|
);
|
|
351
351
|
return rootPath;
|
|
352
352
|
}
|
|
@@ -19,28 +19,52 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
19
19
|
const rootPath = await createProject();
|
|
20
20
|
const result = await generateResourceArtifacts(rootPath);
|
|
21
21
|
|
|
22
|
-
assert.equal(result.files.length,
|
|
23
|
-
const
|
|
22
|
+
assert.equal(result.files.length, 10);
|
|
23
|
+
const crudResources = await readFile(
|
|
24
24
|
join(
|
|
25
25
|
rootPath,
|
|
26
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
26
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml'
|
|
27
27
|
),
|
|
28
28
|
'utf8'
|
|
29
29
|
);
|
|
30
|
-
assert.match(
|
|
31
|
-
assert.match(
|
|
30
|
+
assert.match(crudResources, /InCreate:/);
|
|
31
|
+
assert.match(crudResources, /^ CreateCompleteHdrSqs:/m);
|
|
32
|
+
assert.match(crudResources, /^ UpdateCompleteHdrSqs:/m);
|
|
33
|
+
assert.match(crudResources, /^ DeleteCompleteHdrSqs:/m);
|
|
34
|
+
assert.doesNotMatch(crudResources, /RbacCallbackHdrSqs/);
|
|
35
|
+
assert.doesNotMatch(crudResources, /FlowHook|beforeLogical/);
|
|
36
|
+
|
|
37
|
+
const customResources = await readFile(
|
|
38
|
+
join(
|
|
39
|
+
rootPath,
|
|
40
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
|
|
41
|
+
),
|
|
42
|
+
'utf8'
|
|
43
|
+
);
|
|
44
|
+
assert.match(
|
|
45
|
+
customResources,
|
|
46
|
+
/SubscriptionOutCreateToUploadReservedLimitHdrSqs:/
|
|
47
|
+
);
|
|
48
|
+
assert.match(customResources, /TopicArn: !Ref OutCreate/);
|
|
32
49
|
assert.match(
|
|
33
|
-
|
|
50
|
+
customResources,
|
|
34
51
|
/Endpoint: 'arn:aws:sqs:\${self:custom\.iz_region}:\${self:custom\.iz_accountId}:\${self:custom\.iz_resourcePrefix}UploadReservedLimitHdrSqs'/
|
|
35
52
|
);
|
|
36
|
-
|
|
53
|
+
|
|
54
|
+
const systemResources = await readFile(
|
|
55
|
+
join(
|
|
56
|
+
rootPath,
|
|
57
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-system.yml'
|
|
58
|
+
),
|
|
59
|
+
'utf8'
|
|
60
|
+
);
|
|
37
61
|
for (const queueName of [
|
|
38
62
|
'FindDataHdrSqs',
|
|
39
63
|
'ProcessLogicalHdrSqs',
|
|
40
64
|
'PaginateProcessLogicalHdrDsq',
|
|
41
65
|
'PaginateProcessLogicalHdrSqs'
|
|
42
66
|
]) {
|
|
43
|
-
assert.match(
|
|
67
|
+
assert.match(systemResources, new RegExp(`^ ${queueName}:`, 'm'));
|
|
44
68
|
}
|
|
45
69
|
const dynamoYaml = await readFile(
|
|
46
70
|
join(
|
|
@@ -52,6 +76,10 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
52
76
|
assert.match(dynamoYaml, /OrdersTable:/);
|
|
53
77
|
assert.match(dynamoYaml, /#<#OrdersTableSetting#>/);
|
|
54
78
|
assert.match(dynamoYaml, /#<#\/OrdersTableSetting#>/);
|
|
79
|
+
assert.match(dynamoYaml, /TargetRolesTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+AttributeName: identifiersConcat/);
|
|
80
|
+
assert.match(dynamoYaml, /RoleLimitTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RoleLimit/);
|
|
81
|
+
assert.match(dynamoYaml, /RunningTotalUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalUsage/);
|
|
82
|
+
assert.match(dynamoYaml, /RunningTotalReservedUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsage/);
|
|
55
83
|
const iam = await readFile(
|
|
56
84
|
join(
|
|
57
85
|
rootPath,
|
|
@@ -80,22 +108,28 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
80
108
|
roleBlock(iam, 'FlowRbacRole'),
|
|
81
109
|
/RolePermissions|ProcessCreateTargetRoleHdrSqs/
|
|
82
110
|
);
|
|
111
|
+
assert.match(roleBlock(iam, 'FlowRbacRole'), /RoleLimit/);
|
|
112
|
+
assert.match(roleBlock(iam, 'FlowRbacRole'), /RunningTotalUsage/);
|
|
113
|
+
assert.match(roleBlock(iam, 'FlowObjectsRole'), /RoleLimit/);
|
|
83
114
|
// Per-object RBAC flow queue (e.g. ValidatorRbacHdrSqs from Order with rbac add-on)
|
|
84
115
|
assert.match(roleBlock(iam, 'FlowRbacRole'), /ValidatorRbacHdrSqs/);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
|
|
117
|
+
const relResources = await readFile(
|
|
118
|
+
join(
|
|
119
|
+
rootPath,
|
|
120
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-relationship.yml'
|
|
121
|
+
),
|
|
122
|
+
'utf8'
|
|
123
|
+
);
|
|
124
|
+
assert.match(relResources, /^ CreateRelationshipHdrSqs:/m);
|
|
91
125
|
assert.equal(
|
|
92
|
-
(
|
|
126
|
+
(relResources.match(/^ CreateRelationshipHdrSqs:/gm) ?? []).length,
|
|
93
127
|
1
|
|
94
128
|
);
|
|
95
|
-
assert.match(
|
|
96
|
-
assert.match(
|
|
129
|
+
assert.match(relResources, /^ CreateRelationshipCompleteHdrSqs:/m);
|
|
130
|
+
assert.match(relResources, /^ GetRelationshipHdrDsq:/m);
|
|
97
131
|
assert.doesNotMatch(
|
|
98
|
-
|
|
132
|
+
relResources,
|
|
99
133
|
/Process(?:Create|Update|Delete|Get|Change|Move)Relationship/
|
|
100
134
|
);
|
|
101
135
|
const relationshipRole = roleBlock(iam, 'FlowRelationshipRole');
|
|
@@ -107,9 +141,12 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
107
141
|
/Process(?:Create|Update|Delete|Get|Change|Move)Relationship/
|
|
108
142
|
);
|
|
109
143
|
assert.match(roleBlock(iam, 'RegisterRole'), /s3:GetObject/);
|
|
144
|
+
assert.match(roleBlock(iam, 'RegisterRole'), /s3:ListBucket/);
|
|
110
145
|
assert.match(roleBlock(iam, 'RegisterRole'), /RegisterComplete/);
|
|
111
146
|
assert.match(roleBlock(iam, 'RegisterRole'), /dynamodb:/);
|
|
112
147
|
assert.match(roleBlock(iam, 'RegisterRole'), /execute-api:ManageConnections/);
|
|
148
|
+
assert.match(roleBlock(iam, 'FlowObjectsRole'), /s3:GetObject/);
|
|
149
|
+
assert.match(roleBlock(iam, 'FlowObjectsRole'), /s3:ListBucket/);
|
|
113
150
|
const procFindDataRole = roleBlock(iam, 'ProcFindDataRole');
|
|
114
151
|
assert.match(procFindDataRole, /dynamodb:UpdateItem/);
|
|
115
152
|
assert.match(procFindDataRole, /dynamodb:GetItem/);
|
|
@@ -118,12 +155,16 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
118
155
|
assert.match(procFindDataRole, /FindData_Out/);
|
|
119
156
|
assert.doesNotMatch(
|
|
120
157
|
iam,
|
|
121
|
-
/s3:GetObjectVersion|
|
|
158
|
+
/s3:GetObjectVersion|sqs:DeleteMessageBatch|sqs:GetQueueUrl/
|
|
122
159
|
);
|
|
123
160
|
assert.match(iam, /#<#\/\w+StatementHookCode#>\n\n \w+Role:/);
|
|
124
161
|
|
|
125
162
|
for (const filePath of [
|
|
126
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
163
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-crud.yml',
|
|
164
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-relationship.yml',
|
|
165
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-rbac.yml',
|
|
166
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml',
|
|
167
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-system.yml',
|
|
127
168
|
'resource/sls_yaml/generatedCode/source/generated-sns-out.yml',
|
|
128
169
|
'resource/sls_yaml/generatedCode/source/generated-dynamoDB-table.yml',
|
|
129
170
|
'app/sls_yaml/generatedCode/source/generate-shared-resource.yml'
|
|
@@ -153,16 +194,16 @@ test('emits the shared WebSocket completion queue and output-topic subscriptions
|
|
|
153
194
|
const rootPath = await createProject();
|
|
154
195
|
await generateResourceArtifacts(rootPath);
|
|
155
196
|
|
|
156
|
-
const
|
|
197
|
+
const systemResources = await readFile(
|
|
157
198
|
join(
|
|
158
199
|
rootPath,
|
|
159
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-
|
|
200
|
+
'resource/sls_yaml/generatedCode/source/generated-sns-sqs-system.yml'
|
|
160
201
|
),
|
|
161
202
|
'utf8'
|
|
162
203
|
);
|
|
163
|
-
assert.match(
|
|
164
|
-
assert.match(
|
|
165
|
-
assert.match(
|
|
204
|
+
assert.match(systemResources, /^ WebSocketCompleteHdrSqs:/m);
|
|
205
|
+
assert.match(systemResources, /SubscriptionToOutCustomWebSocketComplete:/);
|
|
206
|
+
assert.match(systemResources, /SubscriptionToOutCustomRegisterComplete:/);
|
|
166
207
|
const iam = await readFile(
|
|
167
208
|
join(
|
|
168
209
|
rootPath,
|
|
@@ -186,11 +227,11 @@ test('emits the shared WebSocket completion queue and output-topic subscriptions
|
|
|
186
227
|
);
|
|
187
228
|
}
|
|
188
229
|
assert.match(
|
|
189
|
-
|
|
230
|
+
systemResources,
|
|
190
231
|
/SubscriptionToOutCreateRelationshipWebSocketComplete:/
|
|
191
232
|
);
|
|
192
233
|
assert.match(
|
|
193
|
-
|
|
234
|
+
systemResources,
|
|
194
235
|
/SubscriptionToOutCreateRelationshipRegisterComplete:/
|
|
195
236
|
);
|
|
196
237
|
});
|
|
@@ -250,7 +291,7 @@ test('emits DynamoDB table with number sortKey attribute type when field type is
|
|
|
250
291
|
join(rootPath, 'app/src/schemas/ObjectSchemas/CartOrder.js'),
|
|
251
292
|
"export default { " +
|
|
252
293
|
"objectType: 'cartOrder', " +
|
|
253
|
-
"generatedBy: '
|
|
294
|
+
"generatedBy: 'user', " +
|
|
254
295
|
"fieldNames: { " +
|
|
255
296
|
" cartId: { type: 'string' }, " +
|
|
256
297
|
" sortId: { type: 'number' } " +
|
|
@@ -329,7 +370,7 @@ async function createProject() {
|
|
|
329
370
|
);
|
|
330
371
|
await writeFile(
|
|
331
372
|
join(schemas, 'ObjectSchemas/Order.js'),
|
|
332
|
-
"export default { objectType: 'order', generatedBy: '
|
|
373
|
+
"export default { objectType: 'order', generatedBy: 'user', addOnDataStructure: [{ type: 'rbac' }], identifiers: [{ type: 'partitionKey', fieldName: 'orderId' }], storageResources: { main: { storageType: 'dynamoDB', tableName: 'Orders' } } };\n"
|
|
333
374
|
);
|
|
334
375
|
await writeFile(
|
|
335
376
|
join(schemas, 'RelationshipSchemas/OrderCustomer.js'),
|
|
@@ -123,7 +123,7 @@ test('generate preserves user hooks inside SystemTextSchemas.js across regenerat
|
|
|
123
123
|
);
|
|
124
124
|
await writeFile(
|
|
125
125
|
join(rootPath, 'app/src/schemas/ObjectSchemas/Item.js'),
|
|
126
|
-
"export default { objectType: 'item', fieldNames: { itemId: { type: 'string' } }, generatedBy: '
|
|
126
|
+
"export default { objectType: 'item', fieldNames: { itemId: { type: 'string' } }, generatedBy: 'user' };\n"
|
|
127
127
|
);
|
|
128
128
|
|
|
129
129
|
// First generate
|