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

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.
@@ -66,6 +66,7 @@ test('validateFlowSchema passes for valid Graph-Driven FlowSchema', () => {
66
66
  test('validateFlowSchema rejects flowSteps[0] without stepName Start', () => {
67
67
  const invalidSchema = {
68
68
  flowTag: 'TestFlow',
69
+ event: ['ownTopic'],
69
70
  flowSteps: [
70
71
  {
71
72
  stepName: 'Begin',
@@ -83,6 +84,7 @@ test('validateFlowSchema rejects flowSteps[0] without stepName Start', () => {
83
84
  test('validateFlowSchema rejects entry step with previousSteps or event', () => {
84
85
  const schemaWithPrev = {
85
86
  flowTag: 'TestFlow',
87
+ event: ['ownTopic'],
86
88
  flowSteps: [
87
89
  {
88
90
  stepName: 'Start',
@@ -98,6 +100,7 @@ test('validateFlowSchema rejects entry step with previousSteps or event', () =>
98
100
 
99
101
  const schemaWithEvent = {
100
102
  flowTag: 'TestFlow',
103
+ event: ['ownTopic'],
101
104
  flowSteps: [
102
105
  {
103
106
  stepName: 'Start',
@@ -115,6 +118,7 @@ test('validateFlowSchema rejects entry step with previousSteps or event', () =>
115
118
  test('validateFlowSchema rejects nextSteps item missing targetType', () => {
116
119
  const invalidSchema = {
117
120
  flowTag: 'TestFlow',
121
+ event: ['ownTopic'],
118
122
  flowSteps: [
119
123
  {
120
124
  stepName: 'Start',
@@ -132,6 +136,7 @@ test('validateFlowSchema rejects nextSteps item missing targetType', () => {
132
136
  test('validateFlowSchema rejects previousSteps item missing sourceType', () => {
133
137
  const invalidSchema = {
134
138
  flowTag: 'TestFlow',
139
+ event: ['ownTopic'],
135
140
  flowSteps: [
136
141
  {
137
142
  stepName: 'Start',
@@ -153,6 +158,7 @@ test('validateFlowSchema rejects previousSteps item missing sourceType', () => {
153
158
  test('validateFlowSchema rejects extTopic nextSteps missing toStepNames or serviceTag', () => {
154
159
  const missingToSteps = {
155
160
  flowTag: 'TestFlow',
161
+ event: ['ownTopic'],
156
162
  flowSteps: [
157
163
  {
158
164
  stepName: 'Start',
@@ -176,6 +182,7 @@ test('validateFlowSchema rejects extTopic nextSteps missing toStepNames or servi
176
182
  test('validateFlowSchema rejects extTopic previousSteps missing fromStepNames', () => {
177
183
  const missingFromSteps = {
178
184
  flowTag: 'TestFlow',
185
+ event: ['ownTopic'],
179
186
  flowSteps: [
180
187
  {
181
188
  stepName: 'Start',
@@ -204,6 +211,7 @@ test('validateFlowSchema rejects extTopic previousSteps missing fromStepNames',
204
211
  test('validateFlowSchema allows optional omitted nextSteps and previousSteps', () => {
205
212
  const minimalSchema = {
206
213
  flowTag: 'MinimalFlow',
214
+ event: ['ownTopic'],
207
215
  flowSteps: [
208
216
  {
209
217
  stepName: 'Start'
@@ -217,6 +225,7 @@ test('validateFlowSchema allows optional omitted nextSteps and previousSteps', (
217
225
  test('validateFlowSchema passes for lambdaSyncInv with functionName', () => {
218
226
  const syncInvSchema = {
219
227
  flowTag: 'SyncInvFlow',
228
+ event: ['lambdaSyncInv'],
220
229
  flowSteps: [
221
230
  {
222
231
  stepName: 'Start',
@@ -237,6 +246,7 @@ test('validateFlowSchema passes for lambdaSyncInv with functionName', () => {
237
246
  test('validateFlowSchema rejects lambdaSyncInv nextSteps missing functionName', () => {
238
247
  const missingFnSchema = {
239
248
  flowTag: 'SyncInvFlow',
249
+ event: ['lambdaSyncInv'],
240
250
  flowSteps: [
241
251
  {
242
252
  stepName: 'Start',
@@ -291,3 +301,318 @@ test('validateFlowSchema rejects flow with synchronous events having multiple fl
291
301
  /Flow 'ConfirmUsage' with synchronous events .* must only define a single entry step 'Start'/
292
302
  );
293
303
  });
304
+
305
+ test('validateFlowSchema rejects statusType triggerCache without triggerFlowTypes', () => {
306
+ const invalidSchema = {
307
+ flowTag: 'cart',
308
+ event: ['ownTopic'],
309
+ statusType: 'triggerCache',
310
+ objType: { serviceTag: 'Order' },
311
+ flowSteps: [{ stepName: 'Start' }]
312
+ };
313
+ assert.throws(
314
+ () => validateFlowSchema(invalidSchema),
315
+ /Flow 'cart' with statusType 'triggerCache' requires a non-empty 'triggerFlowTypes' array/
316
+ );
317
+ });
318
+
319
+ test('validateFlowSchema rejects statusType triggerCache with empty triggerFlowTypes', () => {
320
+ const invalidSchema = {
321
+ flowTag: 'cart',
322
+ event: ['ownTopic'],
323
+ statusType: 'triggerCache',
324
+ triggerFlowTypes: [],
325
+ objType: { serviceTag: 'Order' },
326
+ flowSteps: [{ stepName: 'Start' }]
327
+ };
328
+ assert.throws(
329
+ () => validateFlowSchema(invalidSchema),
330
+ /Flow 'cart' with statusType 'triggerCache' requires a non-empty 'triggerFlowTypes' array/
331
+ );
332
+ });
333
+
334
+ test('validateFlowSchema rejects statusType triggerCache without objType', () => {
335
+ const invalidSchema = {
336
+ flowTag: 'cart',
337
+ event: ['ownTopic'],
338
+ statusType: 'triggerCache',
339
+ triggerFlowTypes: ['OrderCheckout'],
340
+ flowSteps: [{ stepName: 'Start' }]
341
+ };
342
+ assert.throws(
343
+ () => validateFlowSchema(invalidSchema),
344
+ /Flow 'cart' with statusType 'triggerCache' requires 'objType'/
345
+ );
346
+ });
347
+
348
+ test('validateFlowSchema rejects statusType storedCache without objType', () => {
349
+ const invalidSchema = {
350
+ flowTag: 'cache',
351
+ event: ['ownTopic'],
352
+ statusType: 'storedCache',
353
+ flowSteps: [{ stepName: 'Start' }]
354
+ };
355
+ assert.throws(
356
+ () => validateFlowSchema(invalidSchema),
357
+ /Flow 'cache' with statusType 'storedCache' requires 'objType'/
358
+ );
359
+ });
360
+
361
+ test('validateFlowSchema rejects checktrigger step without triggerFlowTypes', () => {
362
+ const invalidSchema = {
363
+ flowTag: 'customCheck',
364
+ event: ['ownTopic'],
365
+ flowSteps: [
366
+ { stepName: 'Start' },
367
+ { stepName: 'CheckTriggerFlows', event: ['extTopic'] }
368
+ ]
369
+ };
370
+ assert.throws(
371
+ () => validateFlowSchema(invalidSchema),
372
+ /Flow 'customCheck' defines step 'CheckTriggerFlows' but requires a non-empty 'triggerFlowTypes' array/
373
+ );
374
+ });
375
+
376
+ test('validateFlowSchema passes for valid triggerCache and storedCache schemas', () => {
377
+ const validTriggerCache = {
378
+ flowTag: 'cart',
379
+ event: ['ownTopic'],
380
+ statusType: 'triggerCache',
381
+ triggerFlowTypes: ['OrderCheckout'],
382
+ objType: { serviceTag: 'Order' },
383
+ flowSteps: [{ stepName: 'Start' }]
384
+ };
385
+ assert.equal(validateFlowSchema(validTriggerCache), true);
386
+
387
+ const validStoredCache = {
388
+ flowTag: 'cache',
389
+ event: ['ownTopic'],
390
+ statusType: 'storedCache',
391
+ objType: { objectType: 'order' },
392
+ flowSteps: [{ stepName: 'Start' }]
393
+ };
394
+ assert.equal(validateFlowSchema(validStoredCache), true);
395
+ });
396
+
397
+ test('validateFlowSchema rejects missing or empty flowTag', () => {
398
+ assert.throws(
399
+ () => validateFlowSchema({ event: ['ownTopic'], flowSteps: [{ stepName: 'Start' }] }),
400
+ /flowTag is required and must be a non-empty string/
401
+ );
402
+ assert.throws(
403
+ () => validateFlowSchema({ flowTag: ' ', event: ['ownTopic'], flowSteps: [{ stepName: 'Start' }] }),
404
+ /flowTag is required and must be a non-empty string/
405
+ );
406
+ });
407
+
408
+ test('validateFlowSchema rejects missing, non-array, or empty event', () => {
409
+ assert.throws(
410
+ () => validateFlowSchema({ flowTag: 'testFlow', flowSteps: [{ stepName: 'Start' }] }),
411
+ /Flow 'testFlow' requires a non-empty 'event' array/
412
+ );
413
+ assert.throws(
414
+ () => validateFlowSchema({ flowTag: 'testFlow', event: 'ownTopic', flowSteps: [{ stepName: 'Start' }] }),
415
+ /Flow 'testFlow' requires a non-empty 'event' array/
416
+ );
417
+ assert.throws(
418
+ () => validateFlowSchema({ flowTag: 'testFlow', event: [], flowSteps: [{ stepName: 'Start' }] }),
419
+ /Flow 'testFlow' requires a non-empty 'event' array/
420
+ );
421
+ });
422
+
423
+ test('validateFlowSchema rejects unsupported event in event array', () => {
424
+ assert.throws(
425
+ () => validateFlowSchema({ flowTag: 'testFlow', event: ['invalidEvent'], flowSteps: [{ stepName: 'Start' }] }),
426
+ /event\[0\] 'invalidEvent' is invalid/
427
+ );
428
+ });
429
+
430
+ test('validateFlowSchema rejects generateCode event without generatedCodeConfig', () => {
431
+ assert.throws(
432
+ () => validateFlowSchema({ flowTag: 'testFlow', event: ['generateCode'], flowSteps: [{ stepName: 'Start' }] }),
433
+ /with event 'generateCode' requires 'generatedCodeConfig'/
434
+ );
435
+ });
436
+
437
+ test('validateFlowSchema rejects non-ownTopic hook without codeHookTag', () => {
438
+ assert.throws(
439
+ () =>
440
+ validateFlowSchema({
441
+ flowTag: 'testHook',
442
+ event: ['lambdaSyncInv'],
443
+ generatedCodeConfig: { generateCodeTag: 'create' },
444
+ flowSteps: [{ stepName: 'Start' }]
445
+ }),
446
+ /requires 'codeHookTag' \('before' \| 'after'\)/
447
+ );
448
+ });
449
+
450
+ test('validateFlowSchema rejects ownTopic hook with generateCodeTag without codeHookTag', () => {
451
+ assert.throws(
452
+ () =>
453
+ validateFlowSchema({
454
+ flowTag: 'testOwnHook',
455
+ event: ['ownTopic'],
456
+ generatedCodeConfig: {
457
+ generateCodeTag: 'create',
458
+ invocationType: 'ownTopic'
459
+ },
460
+ flowSteps: [{ stepName: 'Start' }]
461
+ }),
462
+ /requires 'codeHookTag' \('before' \| 'after'\)/
463
+ );
464
+ });
465
+
466
+ test('validateFlowSchema rejects generateCode co-existing with other events', () => {
467
+ assert.throws(
468
+ () =>
469
+ validateFlowSchema({
470
+ flowTag: 'testHook',
471
+ event: ['generateCode', 'ownTopic'],
472
+ generatedCodeConfig: {
473
+ generateCodeTag: 'create',
474
+ codeHookTag: 'before',
475
+ invocationType: 'ownTopic'
476
+ },
477
+ flowSteps: [{ stepName: 'Start' }]
478
+ }),
479
+ /Flow 'testHook' with event 'generateCode' must be a standalone event and cannot co-exist with other events/
480
+ );
481
+ assert.throws(
482
+ () =>
483
+ validateFlowSchema({
484
+ flowTag: 'testHook',
485
+ event: ['extTopic', 'generateCode'],
486
+ generatedCodeConfig: {
487
+ generateCodeTag: 'create',
488
+ codeHookTag: 'after',
489
+ invocationType: 'lambdaSyncInv'
490
+ },
491
+ flowSteps: [{ stepName: 'Start' }]
492
+ }),
493
+ /Flow 'testHook' with event 'generateCode' must be a standalone event and cannot co-exist with other events/
494
+ );
495
+ });
496
+
497
+ test('validateFlowSchema passes when generateCode is the sole event with valid config', () => {
498
+ assert.equal(
499
+ validateFlowSchema({
500
+ flowTag: 'testHook',
501
+ event: ['generateCode'],
502
+ generatedCodeConfig: {
503
+ generateCodeTag: 'create',
504
+ codeHookTag: 'before',
505
+ invocationType: 'lambdaSyncInv'
506
+ },
507
+ flowSteps: [{ stepName: 'Start' }]
508
+ }),
509
+ true
510
+ );
511
+ });
512
+
513
+ test('validateFlowSchema rejects s3 event without bucketName', () => {
514
+ assert.throws(
515
+ () =>
516
+ validateFlowSchema({
517
+ flowTag: 'uploadFlow',
518
+ event: ['s3'],
519
+ flowSteps: [{ stepName: 'Start' }]
520
+ }),
521
+ /Flow 'uploadFlow' with event 's3' requires 'bucketName' as a non-empty string/
522
+ );
523
+ assert.throws(
524
+ () =>
525
+ validateFlowSchema({
526
+ flowTag: 'uploadFlow',
527
+ event: ['s3'],
528
+ bucketName: ' ',
529
+ flowSteps: [{ stepName: 'Start' }]
530
+ }),
531
+ /Flow 'uploadFlow' with event 's3' requires 'bucketName' as a non-empty string/
532
+ );
533
+ });
534
+
535
+ test('validateFlowSchema passes for s3 event with valid bucketName', () => {
536
+ assert.equal(
537
+ validateFlowSchema({
538
+ flowTag: 'uploadFlow',
539
+ event: ['s3'],
540
+ bucketName: 'MyUploadBucket',
541
+ flowSteps: [{ stepName: 'Start' }]
542
+ }),
543
+ true
544
+ );
545
+ });
546
+
547
+ test('validateFlowSchema rejects event with storedCache or triggerCache as invalid event', () => {
548
+ assert.throws(
549
+ () =>
550
+ validateFlowSchema({
551
+ flowTag: 'cacheFlow',
552
+ event: ['storedCache'],
553
+ flowSteps: [{ stepName: 'Start' }]
554
+ }),
555
+ /Flow 'cacheFlow' event\[0\] 'storedCache' is invalid\. Must be one of/
556
+ );
557
+ assert.throws(
558
+ () =>
559
+ validateFlowSchema({
560
+ flowTag: 'triggerFlow',
561
+ event: ['triggerCache'],
562
+ flowSteps: [{ stepName: 'Start' }]
563
+ }),
564
+ /Flow 'triggerFlow' event\[0\] 'triggerCache' is invalid\. Must be one of/
565
+ );
566
+ });
567
+
568
+ test('validateFlowSchema rejects statusType storedCache with invalid objType object', () => {
569
+ assert.throws(
570
+ () =>
571
+ validateFlowSchema({
572
+ flowTag: 'cacheFlow',
573
+ event: ['ownTopic'],
574
+ statusType: 'storedCache',
575
+ objType: {},
576
+ flowSteps: [{ stepName: 'Start' }]
577
+ }),
578
+ /Flow 'cacheFlow' with statusType 'storedCache' requires 'objType' to be a non-empty string or object with string 'objectType'/
579
+ );
580
+ assert.throws(
581
+ () =>
582
+ validateFlowSchema({
583
+ flowTag: 'cacheFlow',
584
+ event: ['ownTopic'],
585
+ statusType: 'storedCache',
586
+ objType: { objectType: ' ' },
587
+ flowSteps: [{ stepName: 'Start' }]
588
+ }),
589
+ /Flow 'cacheFlow' with statusType 'storedCache' requires 'objType' to be a non-empty string or object with string 'objectType'/
590
+ );
591
+ });
592
+
593
+ test('validateFlowSchema passes statusType storedCache with string objType or object objType', () => {
594
+ assert.equal(
595
+ validateFlowSchema({
596
+ flowTag: 'cacheFlow1',
597
+ event: ['ownTopic'],
598
+ statusType: 'storedCache',
599
+ objType: 'order',
600
+ flowSteps: [{ stepName: 'Start' }]
601
+ }),
602
+ true
603
+ );
604
+ assert.equal(
605
+ validateFlowSchema({
606
+ flowTag: 'cacheFlow2',
607
+ event: ['ownTopic'],
608
+ statusType: 'storedCache',
609
+ objType: { objectType: 'order' },
610
+ flowSteps: [{ stepName: 'Start' }]
611
+ }),
612
+ true
613
+ );
614
+ });
615
+
616
+
617
+
618
+
@@ -236,6 +236,51 @@ test('rejects empty generateCodeTag array configuration', async () => {
236
236
  );
237
237
  });
238
238
 
239
+ test('rejects FlowHook schema missing codeHookTag at loader', async () => {
240
+ const rootPath = await createProject();
241
+ const flowDirectory = join(rootPath, 'app/src/schemas/FlowSchemas');
242
+ await writeFile(
243
+ join(flowDirectory, 'MissingHookTag.js'),
244
+ "export default { flowTag: 'missingHookTag', event: ['lambdaSyncInv'], " +
245
+ "generatedCodeConfig: { generateCodeTag: 'create' } };\n"
246
+ );
247
+
248
+ await assert.rejects(
249
+ generateResourceArtifacts(rootPath),
250
+ /Flow 'missingHookTag' with generatedCodeConfig requires 'codeHookTag' \('before' or 'after'\)/
251
+ );
252
+ });
253
+
254
+ test('rejects ownTopic FlowHook schema missing codeHookTag at loader', async () => {
255
+ const rootPath = await createProject();
256
+ const flowDirectory = join(rootPath, 'app/src/schemas/FlowSchemas');
257
+ await writeFile(
258
+ join(flowDirectory, 'MissingOwnHookTag.js'),
259
+ "export default { flowTag: 'missingOwnHookTag', event: ['ownTopic'], " +
260
+ "generatedCodeConfig: { generateCodeTag: 'create', invocationType: 'ownTopic' } };\n"
261
+ );
262
+
263
+ await assert.rejects(
264
+ generateResourceArtifacts(rootPath),
265
+ /Flow 'missingOwnHookTag' with generatedCodeConfig requires 'codeHookTag' \('before' or 'after'\)/
266
+ );
267
+ });
268
+
269
+ test('rejects FlowHook schema with invalid codeHookTag at loader', async () => {
270
+ const rootPath = await createProject();
271
+ const flowDirectory = join(rootPath, 'app/src/schemas/FlowSchemas');
272
+ await writeFile(
273
+ join(flowDirectory, 'InvalidCodeHookTag.js'),
274
+ "export default { flowTag: 'invalidCodeHookTag', event: ['lambdaSyncInv'], " +
275
+ "generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'middle' } };\n"
276
+ );
277
+
278
+ await assert.rejects(
279
+ generateResourceArtifacts(rootPath),
280
+ /generatedCodeConfig\.codeHookTag 'middle' is invalid\. Must be 'before' or 'after'/
281
+ );
282
+ });
283
+
239
284
  test('emits the shared WebSocket completion queue and output-topic subscriptions', async () => {
240
285
  const rootPath = await createProject();
241
286
  await generateResourceArtifacts(rootPath);
@@ -807,7 +852,7 @@ async function createProject() {
807
852
  );
808
853
  await writeFile(
809
854
  join(schemas, 'FlowSchemas/Upload.js'),
810
- "export default { flowTag: 'upload', event: ['s3'], outputTopic: true, s3Subscription: { topicName: 'OutCreate', queueName: 'UploadReservedLimitHdrSqs' } };\n"
855
+ "export default { flowTag: 'upload', event: ['s3'], bucketName: 'UploadBucket', outputTopic: true, s3Subscription: { topicName: 'OutCreate', queueName: 'UploadReservedLimitHdrSqs' } };\n"
811
856
  );
812
857
  return rootPath;
813
858
  }
@@ -0,0 +1,73 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { emitSystemFindDataArtifacts } from '../src/emit/system/emitSystemFindDataArtifacts.js';
5
+
6
+ test('emitSystemFindDataArtifacts returns empty when no objects have storage resources', async () => {
7
+ const result = await emitSystemFindDataArtifacts([
8
+ { objectType: 'plainObject' }
9
+ ]);
10
+ assert.deepEqual(result.artifacts, []);
11
+ assert.deepEqual(result.functions, []);
12
+ });
13
+
14
+ test('emitSystemFindDataArtifacts omits FindData handlers and YAML function when no fields qualify for find data', async () => {
15
+ const objects = [
16
+ {
17
+ objectType: 'item',
18
+ storageResources: {
19
+ main: { storageType: 'dynamoDB', tableName: 'Items' }
20
+ },
21
+ fieldNames: {
22
+ description: { type: 'boolean' }
23
+ }
24
+ }
25
+ ];
26
+
27
+ const result = await emitSystemFindDataArtifacts(objects);
28
+ assert.ok(result.artifacts.length > 0);
29
+ assert.ok(
30
+ !result.artifacts.some((a) => a.path.includes('FindData')),
31
+ 'FindData artifacts must not be emitted when no fields qualify'
32
+ );
33
+ assert.ok(
34
+ !result.functions.some((fn) => fn.includes('FindDataHdrSqs:')),
35
+ 'FindData YAML function must not be emitted when no fields qualify'
36
+ );
37
+ assert.ok(
38
+ result.functions.some((fn) => fn.includes('ProcessLogicalHdrSqs:')),
39
+ 'ProcessLogical functions should still be emitted'
40
+ );
41
+ });
42
+
43
+ test('emitSystemFindDataArtifacts includes FindData handlers and YAML function when fields qualify', async () => {
44
+ const objects = [
45
+ {
46
+ objectType: 'product',
47
+ storageResources: {
48
+ main: { storageType: 'dynamoDB', tableName: 'Products' }
49
+ },
50
+ identifiers: [{ fieldName: 'productId' }],
51
+ fieldNames: {
52
+ productId: {
53
+ type: 'string',
54
+ storageResourceTags: ['main']
55
+ }
56
+ }
57
+ }
58
+ ];
59
+
60
+ const result = await emitSystemFindDataArtifacts(objects);
61
+ assert.ok(
62
+ result.artifacts.some((a) => a.path.includes('FindData_Main.js')),
63
+ 'FindData_Main.js must be emitted'
64
+ );
65
+ assert.ok(
66
+ result.artifacts.some((a) => a.path.includes('FindData_HdrSqs.js')),
67
+ 'FindData_HdrSqs.js must be emitted'
68
+ );
69
+ assert.ok(
70
+ result.functions.some((fn) => fn.includes('FindDataHdrSqs:')),
71
+ 'FindDataHdrSqs YAML function must be emitted'
72
+ );
73
+ });