@izara_project/izara-core-generate-service-code 1.0.100 → 1.0.102
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 +1 -1
- package/src/emit/application/crud/templates/CreateLogicalLib.ejs +6 -0
- package/src/emit/application/custom/customFlowS3.js +226 -6
- package/src/emit/application/custom/customFlows.js +22 -14
- package/src/emit/application/custom/emitCustomFlowApplicationArtifacts.js +5 -4
- package/src/emit/application/custom/templates/s3/ConfirmComplete_HdrSqs.ejs +1 -0
- package/src/emit/application/custom/templates/s3/ConfirmComplete_Main.ejs +28 -17
- package/src/emit/application/custom/templates/s3/ProcessS3File_Main.ejs +234 -23
- package/src/emit/application/custom/templates/s3/Start_HdrWbs.ejs +65 -0
- package/src/emit/application/custom/templates/s3/Start_Main.ejs +40 -17
- package/src/emit/application/custom/templates/s3/{ConfirmAfterUpload_HdrSqs.ejs → legacy/ConfirmAfterUpload_HdrSqs.ejs} +1 -0
- package/src/emit/application/custom/templates/s3/{ConfirmAfterUpload_Main.ejs → legacy/ConfirmAfterUpload_Main.ejs} +1 -1
- package/src/emit/infrastructure/functions/templates/DssFunction_Yaml.ejs +1 -1
- package/src/emit/infrastructure/resources/dynamoResourceArtifacts.js +6 -2
- package/src/plan/domainRegistry.js +2 -1
- package/test/unit/application/createTargetRoleHandler.test.js +1 -1
- package/test/unit/application/customFlowS3.test.js +284 -2
- package/test/unit/infrastructure/resourceArtifacts.test.js +4 -0
- /package/src/emit/application/custom/templates/s3/{ConfirmReserved_HdrSqs.ejs → legacy/ConfirmReserved_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ConfirmReserved_Main.ejs → legacy/ConfirmReserved_Main.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreateObject_HdrWbs.ejs → legacy/CreateObject_HdrWbs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreateObject_Main.ejs → legacy/CreateObject_Main.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreatePreSignUrl_HdrSqs.ejs → legacy/CreatePreSignUrl_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{CreatePreSignUrl_Main.ejs → legacy/CreatePreSignUrl_Main.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ReservedLimit_HdrSqs.ejs → legacy/ReservedLimit_HdrSqs.ejs} +0 -0
- /package/src/emit/application/custom/templates/s3/{ReservedLimit_Main.ejs → legacy/ReservedLimit_Main.ejs} +0 -0
|
@@ -131,8 +131,8 @@ test('emitS3Flow with ADR-016 deferred-node pipeline generates correct 3 pairs o
|
|
|
131
131
|
'Start_Main should generate S3 presigned post URL'
|
|
132
132
|
);
|
|
133
133
|
assert.ok(
|
|
134
|
-
startMainArtifact.content.includes('
|
|
135
|
-
'Start_Main should generate identifier with
|
|
134
|
+
startMainArtifact.content.includes('identifierUuid'),
|
|
135
|
+
'Start_Main should generate identifier with identifierUuid'
|
|
136
136
|
);
|
|
137
137
|
assert.ok(
|
|
138
138
|
!startMainArtifact.content.includes("snsTopicArnByFlowSchema(_izContext, 'Create_In')"),
|
|
@@ -186,6 +186,10 @@ test('emitS3Flow with ADR-016 deferred-node pipeline generates correct 3 pairs o
|
|
|
186
186
|
procMainArtifact.content.includes('FeedChunkRecordUrlConfirmAfterUploadHdrSqs'),
|
|
187
187
|
'ProcessAfterUpload_Main must target ConfirmAfterUploadHdrSqs for callback'
|
|
188
188
|
);
|
|
189
|
+
assert.ok(
|
|
190
|
+
procMainArtifact.content.includes('feedChunkId: rawId'),
|
|
191
|
+
'ProcessAfterUpload_Main must include objectTypeId: rawId in fieldNames'
|
|
192
|
+
);
|
|
189
193
|
|
|
190
194
|
const confirmMainArtifact = result.artifacts.find(
|
|
191
195
|
(a) => a.path === `${source}/FeedChunkRecordUrlConfirmAfterUpload_Main.js`
|
|
@@ -725,6 +729,284 @@ test('emitS3Flow with ADR-016 pipeline without step-level event enum resolves co
|
|
|
725
729
|
]);
|
|
726
730
|
});
|
|
727
731
|
|
|
732
|
+
test('emitS3Flow generates relationships with direction to in ProcessAfterUpload_Main and fields in Start_Main', async () => {
|
|
733
|
+
const flowWithRel = {
|
|
734
|
+
flowTag: 'FeedChunkRecordUrl',
|
|
735
|
+
initiateBy: 'user',
|
|
736
|
+
handleObj: 'one',
|
|
737
|
+
statusType: 'statusField',
|
|
738
|
+
event: ['s3'],
|
|
739
|
+
objType: {
|
|
740
|
+
objectType: 'feedChunk',
|
|
741
|
+
serviceTag: 'FeedChunk'
|
|
742
|
+
},
|
|
743
|
+
bucketName: 'video-record-temp',
|
|
744
|
+
flowSteps: [
|
|
745
|
+
{ stepName: 'Start', event: ['wbs'] },
|
|
746
|
+
{ stepName: 'ProcessAfterUpload', event: ['s3'] },
|
|
747
|
+
{ stepName: 'ConfirmComplete', event: ['extTopic'] }
|
|
748
|
+
],
|
|
749
|
+
relationships: [
|
|
750
|
+
{
|
|
751
|
+
targetObjType: { objectType: 'videoFeed', serviceTag: 'VideoFeed' },
|
|
752
|
+
targetIdentifierKey: 'videoFeedId',
|
|
753
|
+
relType: { relationshipTag: 'hasFeedChunk', serviceTag: 'VideoFeed' },
|
|
754
|
+
relationshipDirection: 'to'
|
|
755
|
+
}
|
|
756
|
+
]
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
760
|
+
const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
761
|
+
|
|
762
|
+
const result = await emitS3Flow(flowWithRel, source, handlerRoot);
|
|
763
|
+
|
|
764
|
+
const startMain = result.artifacts.find(
|
|
765
|
+
(a) => a.path === `${source}/FeedChunkRecordUrl_Main.js`
|
|
766
|
+
);
|
|
767
|
+
assert.ok(
|
|
768
|
+
startMain.content.includes("requestParams.fieldNames?.videoFeedId"),
|
|
769
|
+
'Start_Main should extract videoFeedId from requestParams'
|
|
770
|
+
);
|
|
771
|
+
assert.ok(
|
|
772
|
+
startMain.content.includes("['starts-with', '$x-amz-meta-videoFeedId', '']"),
|
|
773
|
+
'Start_Main should include videoFeedId condition in Presigned POST'
|
|
774
|
+
);
|
|
775
|
+
assert.ok(
|
|
776
|
+
startMain.content.includes("'x-amz-meta-videoFeedId': videoFeedId || ''"),
|
|
777
|
+
'Start_Main should include videoFeedId field in Presigned POST'
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
const procMain = result.artifacts.find(
|
|
781
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
|
|
782
|
+
);
|
|
783
|
+
assert.ok(
|
|
784
|
+
procMain.content.includes("const metaVideoFeedId = s3Metadata.videofeedid || '';"),
|
|
785
|
+
'ProcessAfterUpload_Main should extract metaVideoFeedId from s3Metadata'
|
|
786
|
+
);
|
|
787
|
+
assert.ok(
|
|
788
|
+
procMain.content.includes("videoFeedId: metaVideoFeedId"),
|
|
789
|
+
'ProcessAfterUpload_Main should include videoFeedId in extracted metadata log'
|
|
790
|
+
);
|
|
791
|
+
assert.ok(
|
|
792
|
+
procMain.content.includes("objectType: 'videoFeed'"),
|
|
793
|
+
'ProcessAfterUpload_Main should include targetObjType objectType'
|
|
794
|
+
);
|
|
795
|
+
assert.ok(
|
|
796
|
+
procMain.content.includes("relationshipTag: 'hasFeedChunk'"),
|
|
797
|
+
'ProcessAfterUpload_Main should include relType relationshipTag'
|
|
798
|
+
);
|
|
799
|
+
assert.ok(
|
|
800
|
+
procMain.content.includes("relationshipDirection: 'to'"),
|
|
801
|
+
'ProcessAfterUpload_Main must generate relationshipDirection to'
|
|
802
|
+
);
|
|
803
|
+
assert.ok(
|
|
804
|
+
procMain.content.includes('Built relationship for videoFeed'),
|
|
805
|
+
'ProcessAfterUpload_Main should log relationship construction'
|
|
806
|
+
);
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test('emitS3Flow resolves relationships from schemas with relationshipDirection to', async () => {
|
|
810
|
+
const flow = {
|
|
811
|
+
flowTag: 'FeedChunkRecordUrl',
|
|
812
|
+
initiateBy: 'user',
|
|
813
|
+
handleObj: 'one',
|
|
814
|
+
statusType: 'statusField',
|
|
815
|
+
event: ['s3'],
|
|
816
|
+
objType: {
|
|
817
|
+
objectType: 'feedChunk',
|
|
818
|
+
serviceTag: 'FeedChunk'
|
|
819
|
+
},
|
|
820
|
+
bucketName: 'video-record-temp',
|
|
821
|
+
flowSteps: [
|
|
822
|
+
{ stepName: 'Start', event: ['wbs'] },
|
|
823
|
+
{ stepName: 'ProcessAfterUpload', event: ['s3'] },
|
|
824
|
+
{ stepName: 'ConfirmComplete', event: ['extTopic'] }
|
|
825
|
+
]
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
const relationshipsSchema = [
|
|
829
|
+
{
|
|
830
|
+
objectType: 'feedChunk',
|
|
831
|
+
relationshipTag: 'hasFeedChunk',
|
|
832
|
+
relationshipServiceTag: 'VideoFeed'
|
|
833
|
+
}
|
|
834
|
+
];
|
|
835
|
+
|
|
836
|
+
const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
837
|
+
const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
838
|
+
|
|
839
|
+
const result = await emitS3Flow(flow, source, handlerRoot, {
|
|
840
|
+
relationships: relationshipsSchema
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const procMain = result.artifacts.find(
|
|
844
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
|
|
845
|
+
);
|
|
846
|
+
assert.ok(
|
|
847
|
+
procMain.content.includes("relationshipDirection: 'to'"),
|
|
848
|
+
'ProcessAfterUpload_Main should infer relationshipDirection to from RefObjectRelationships'
|
|
849
|
+
);
|
|
850
|
+
assert.ok(
|
|
851
|
+
procMain.content.includes("relationshipTag: 'hasFeedChunk'"),
|
|
852
|
+
'ProcessAfterUpload_Main should infer relationshipTag hasFeedChunk'
|
|
853
|
+
);
|
|
854
|
+
assert.ok(
|
|
855
|
+
procMain.content.includes("serviceTag: 'VideoFeed'"),
|
|
856
|
+
'ProcessAfterUpload_Main should infer serviceTag VideoFeed'
|
|
857
|
+
);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
test('emitS3Flow generates WebSocketTask atomic claim in ProcessAfterUpload and connectionId NONE guard in ConfirmComplete', async () => {
|
|
861
|
+
const flow = {
|
|
862
|
+
flowTag: 'FeedChunkRecordUrl',
|
|
863
|
+
initiateBy: 'user',
|
|
864
|
+
handleObj: 'one',
|
|
865
|
+
statusType: 'statusField',
|
|
866
|
+
event: ['s3'],
|
|
867
|
+
objType: {
|
|
868
|
+
objectType: 'feedChunk',
|
|
869
|
+
serviceTag: 'FeedChunk'
|
|
870
|
+
},
|
|
871
|
+
bucketName: 'video-record-temp',
|
|
872
|
+
flowSteps: [
|
|
873
|
+
{ stepName: 'Start', event: ['wbs'] },
|
|
874
|
+
{ stepName: 'ProcessAfterUpload', event: ['s3'] },
|
|
875
|
+
{ stepName: 'ConfirmComplete', event: ['extTopic'] }
|
|
876
|
+
]
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
880
|
+
const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
881
|
+
|
|
882
|
+
const result = await emitS3Flow(flow, source, handlerRoot);
|
|
883
|
+
|
|
884
|
+
const procMain = result.artifacts.find(
|
|
885
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
|
|
886
|
+
);
|
|
887
|
+
assert.ok(
|
|
888
|
+
procMain.content.includes('attribute_exists(taskKey) AND attribute_not_exists(uploadClaimed)'),
|
|
889
|
+
'ProcessAfterUpload_Main must enforce attribute_exists(taskKey) AND attribute_not_exists(uploadClaimed)'
|
|
890
|
+
);
|
|
891
|
+
assert.ok(
|
|
892
|
+
procMain.content.includes("status: 'unauthorized_or_unknown_upload'"),
|
|
893
|
+
'ProcessAfterUpload_Main must abort when task record is missing'
|
|
894
|
+
);
|
|
895
|
+
assert.ok(
|
|
896
|
+
procMain.content.includes('dynamodbSharedLib'),
|
|
897
|
+
'ProcessAfterUpload_Main must import dynamodbSharedLib for simplified S3'
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
assert.ok(
|
|
901
|
+
procMain.content.includes("status: 'skipped_empty_or_dir'"),
|
|
902
|
+
'ProcessAfterUpload_Main must guard against directory markers or 0-byte files'
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
const confirmMain = result.artifacts.find(
|
|
906
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlConfirmComplete_Main.js`
|
|
907
|
+
);
|
|
908
|
+
assert.ok(
|
|
909
|
+
confirmMain.content.includes("taskRecord.connectionId !== 'NONE'"),
|
|
910
|
+
'ConfirmComplete_Main must guard against connectionId NONE'
|
|
911
|
+
);
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test('resolveTargetIdentifierInfo resolves both DynamoDB and Graph identifier formats', async () => {
|
|
915
|
+
const { resolveTargetIdentifierInfo } = await import(
|
|
916
|
+
'../../../src/emit/application/custom/customFlowS3.js'
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// 1. Graph style: { type: 'identifier', fieldName: 'chunkUuid' }
|
|
920
|
+
const graphFlow = { objType: { objectType: 'feedChunk' } };
|
|
921
|
+
const graphObjects = [
|
|
922
|
+
{
|
|
923
|
+
objectType: 'feedChunk',
|
|
924
|
+
identifiers: [{ type: 'identifier', fieldName: 'chunkUuid' }]
|
|
925
|
+
}
|
|
926
|
+
];
|
|
927
|
+
const graphResult = resolveTargetIdentifierInfo(graphFlow, graphObjects);
|
|
928
|
+
assert.equal(graphResult.identifierKey, 'chunkUuid');
|
|
929
|
+
assert.deepEqual(graphResult.identifierKeys, ['chunkUuid']);
|
|
930
|
+
|
|
931
|
+
// 2. DynamoDB style: { type: 'partitionKey', fieldName: 'myFeedId' }
|
|
932
|
+
const dynamoFlow = { objType: { objectType: 'feedChunk' } };
|
|
933
|
+
const dynamoObjects = [
|
|
934
|
+
{
|
|
935
|
+
objectType: 'feedChunk',
|
|
936
|
+
identifiers: [{ type: 'partitionKey', fieldName: 'myFeedId' }]
|
|
937
|
+
}
|
|
938
|
+
];
|
|
939
|
+
const dynamoResult = resolveTargetIdentifierInfo(dynamoFlow, dynamoObjects);
|
|
940
|
+
assert.equal(dynamoResult.identifierKey, 'myFeedId');
|
|
941
|
+
assert.deepEqual(dynamoResult.identifierKeys, ['myFeedId']);
|
|
942
|
+
|
|
943
|
+
// 3. Fallback when no identifiers defined
|
|
944
|
+
const emptyFlow = { objType: { objectType: 'videoFeed' } };
|
|
945
|
+
const emptyResult = resolveTargetIdentifierInfo(emptyFlow, []);
|
|
946
|
+
assert.equal(emptyResult.identifierKey, 'videoFeedId');
|
|
947
|
+
assert.deepEqual(emptyResult.identifierKeys, ['videoFeedId']);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
test('emitS3Flow generates dynamic identifier name for both Start_Main and ProcessAfterUpload_Main', async () => {
|
|
951
|
+
const flow = {
|
|
952
|
+
flowTag: 'FeedChunkRecordUrl',
|
|
953
|
+
flowType: 'custom',
|
|
954
|
+
event: ['s3'],
|
|
955
|
+
bucketName: 'video-record-temp',
|
|
956
|
+
contentTypePrefix: 'video/',
|
|
957
|
+
maxFileSizeMb: 50,
|
|
958
|
+
objType: {
|
|
959
|
+
objectType: 'feedChunk',
|
|
960
|
+
serviceTag: 'FeedChunk'
|
|
961
|
+
},
|
|
962
|
+
flowSteps: [
|
|
963
|
+
{ stepName: 'Start', event: ['wbs'] },
|
|
964
|
+
{ stepName: 'ProcessAfterUpload', event: ['s3'] },
|
|
965
|
+
{ stepName: 'ConfirmComplete', event: ['sqs'] }
|
|
966
|
+
]
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
const customObjects = [
|
|
970
|
+
{
|
|
971
|
+
objectType: 'feedChunk',
|
|
972
|
+
identifiers: [
|
|
973
|
+
{
|
|
974
|
+
type: 'identifier',
|
|
975
|
+
fieldName: 'customChunkId'
|
|
976
|
+
}
|
|
977
|
+
]
|
|
978
|
+
}
|
|
979
|
+
];
|
|
980
|
+
|
|
981
|
+
const source = 'app/src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
982
|
+
const handlerRoot = 'src/generatedCode/FlowSchemas/FeedChunkRecordUrl/source';
|
|
983
|
+
|
|
984
|
+
const result = await emitS3Flow(flow, source, handlerRoot, {
|
|
985
|
+
objects: customObjects
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
const startMain = result.artifacts.find(
|
|
989
|
+
(a) => a.path === `${source}/FeedChunkRecordUrl_Main.js`
|
|
990
|
+
);
|
|
991
|
+
assert.ok(
|
|
992
|
+
startMain.content.includes('const customChunkId = identifierUuid()'),
|
|
993
|
+
'Start_Main must bind custom identifier key'
|
|
994
|
+
);
|
|
995
|
+
assert.ok(
|
|
996
|
+
startMain.content.includes("'x-amz-meta-customChunkId': customChunkId"),
|
|
997
|
+
'Start_Main must use custom identifier key in S3 metadata'
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
const procMain = result.artifacts.find(
|
|
1001
|
+
(a) => a.path === `${source}/FeedChunkRecordUrlProcessAfterUpload_Main.js`
|
|
1002
|
+
);
|
|
1003
|
+
assert.ok(
|
|
1004
|
+
procMain.content.includes('customChunkId: rawId'),
|
|
1005
|
+
'ProcessAfterUpload_Main must use custom identifier key in fieldNames and identifiers'
|
|
1006
|
+
);
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
|
|
728
1010
|
|
|
729
1011
|
|
|
730
1012
|
|
|
@@ -97,6 +97,10 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
97
97
|
dynamoYaml,
|
|
98
98
|
/RunningTotalReservedUsageTable:\s+Type: 'AWS::DynamoDB::Table'[\s\S]+TableName: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsage/
|
|
99
99
|
);
|
|
100
|
+
assert.match(
|
|
101
|
+
dynamoYaml,
|
|
102
|
+
/Outputs:\s+RunningTotalReservedUsageStreamArn:\s+Value:\s+Fn::GetAtt: \[RunningTotalReservedUsageTable, StreamArn\]\s+Export:\s+Name: \${self:custom\.iz_resourcePrefix}RunningTotalReservedUsageStreamArn/
|
|
103
|
+
);
|
|
100
104
|
const iam = await readFile(
|
|
101
105
|
join(
|
|
102
106
|
rootPath,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|