@izara_project/izara-core-generate-service-code 1.0.102 → 1.0.104
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/compile/loadSchemas.js +47 -0
- package/src/compile/loadSchemasRemote.js +38 -4
- package/src/compile/schemaValidatorSetup.js +3 -0
- package/src/emit/application/coreFunction/templates/InitialSetupRunner_uploadSchema.ejs +7 -0
- package/src/emit/application/coreFunction/templates/UploadSchema_Main.ejs +7 -0
- package/src/emit/application/crud/emitCrudApplicationArtifacts.js +13 -0
- package/src/emit/application/crud/emitDynamicCrudFlowSchemas.js +13 -0
- package/src/emit/application/crud/templates/CreateEndpoint_Main.ejs +21 -0
- package/src/emit/application/rbac/templates/CancelUsage_Main.ejs +7 -6
- package/src/emit/application/rbac/templates/ConfirmUsage_Main.ejs +7 -6
- package/src/emit/application/rbac/templates/CreateTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/DeleteTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/GetMyPermissions_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/GetTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/ListRbac_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/RbacMutation_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +7 -4
- package/src/emit/application/rbac/templates/UpdateTargetRole_Main.ejs +2 -2
- package/src/emit/application/rbac/templates/UserRoleMutation_Main.ejs +2 -2
- package/src/emit/infrastructure/resources/sqsResourceArtifacts.js +2 -1
- package/src/emit/infrastructure/resources/templates/FlowOut_Yaml.ejs +2 -0
- package/src/emit/schemas/ledger/emitLedgerSchemas.js +10 -8
- package/src/emit/schemas/ledger/templates/LedgerObjectSchema.ejs +17 -1
- package/src/emit/schemas/ledger/templates/LedgerRelationshipSchema.ejs +11 -1
- package/src/generate.js +50 -3
- package/src/output/commitArtifacts.js +42 -3
- package/src/plan/buildGenerationPlan.js +3 -2
- package/test/integration/core.test.js +31 -0
- package/test/unit/application/crudApplicationArtifacts.test.js +10 -0
- package/test/unit/infrastructure/resourceArtifacts.test.js +20 -0
- package/test/unit/output/mergeHooks.test.js +49 -0
- package/test/unit/schemas/dynamicCrudFlowSchemas.test.js +37 -0
- package/test/unit/schemas/ledgerSchemas.test.js +142 -10
- package/test/unit/schemas/resolveExtendedObjectSchemas.test.js +136 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izara_project/izara-core-generate-service-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.104",
|
|
4
4
|
"description": "Code for locally generating per service files",
|
|
5
5
|
"homepage": "https://bitbucket.org/izara-core-support-services/izara-core-support-services-generate-service-code#readme",
|
|
6
6
|
"repository": {
|
|
@@ -99,6 +99,53 @@ export async function loadLocalSchemaDirectory(
|
|
|
99
99
|
);
|
|
100
100
|
if (loadedSchemas.length === 0) continue;
|
|
101
101
|
|
|
102
|
+
if (directoryName === 'ObjectSchemas') {
|
|
103
|
+
for (let i = 0; i < loadedSchemas.length; i++) {
|
|
104
|
+
let schema = loadedSchemas[i];
|
|
105
|
+
if (
|
|
106
|
+
schema &&
|
|
107
|
+
typeof schema === 'object' &&
|
|
108
|
+
'result' in schema &&
|
|
109
|
+
schema.result &&
|
|
110
|
+
typeof schema.result === 'object'
|
|
111
|
+
) {
|
|
112
|
+
schema = schema.result;
|
|
113
|
+
loadedSchemas[i] = schema;
|
|
114
|
+
}
|
|
115
|
+
if (!Object.prototype.hasOwnProperty.call(schema, '_sourceFile')) {
|
|
116
|
+
Object.defineProperty(schema, '_sourceFile', {
|
|
117
|
+
value: entry.name,
|
|
118
|
+
enumerable: false,
|
|
119
|
+
writable: true,
|
|
120
|
+
configurable: true
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (
|
|
124
|
+
!schema.objectType ||
|
|
125
|
+
typeof schema.objectType !== 'string' ||
|
|
126
|
+
schema.objectType.trim() === ''
|
|
127
|
+
) {
|
|
128
|
+
const keys = Object.keys(schema || {});
|
|
129
|
+
let hint = '';
|
|
130
|
+
if (
|
|
131
|
+
keys.length === 1 &&
|
|
132
|
+
typeof schema[keys[0]] === 'object' &&
|
|
133
|
+
schema[keys[0]] !== null
|
|
134
|
+
) {
|
|
135
|
+
const outerKey = keys[0];
|
|
136
|
+
hint =
|
|
137
|
+
` Schema appears to be wrapped in key '${outerKey}'. ` +
|
|
138
|
+
`ObjectSchemas must not wrap with the object name; ` +
|
|
139
|
+
`export [{ objectType: '${outerKey}', ... }] directly.`;
|
|
140
|
+
}
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Invalid ObjectSchema in ${entry.name}: 'objectType' is required ` +
|
|
143
|
+
`and must be a non-empty string.${hint}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
102
149
|
if (directoryName === 'ErrorSchemas') {
|
|
103
150
|
for (const schema of loadedSchemas) {
|
|
104
151
|
if (!schema._sourceFile) {
|
|
@@ -20,11 +20,13 @@ const consoleLogger = {
|
|
|
20
20
|
*/
|
|
21
21
|
export async function resolveExtendedObjectSchemas(
|
|
22
22
|
objectSchemas,
|
|
23
|
-
bucketName = process.env.iz_serviceSchemaBucketName
|
|
23
|
+
bucketName = process.env.iz_serviceSchemaBucketName,
|
|
24
|
+
dependencies = {}
|
|
24
25
|
) {
|
|
25
26
|
if (!Array.isArray(objectSchemas)) {
|
|
26
27
|
throw new TypeError('objectSchemas must be an array');
|
|
27
28
|
}
|
|
29
|
+
const getObjectSchemaLib = dependencies.getObjectSchema || getObjectSchema;
|
|
28
30
|
const resolvedSchemas = [];
|
|
29
31
|
for (const schema of objectSchemas) {
|
|
30
32
|
let currentSchema = schema;
|
|
@@ -36,15 +38,31 @@ export async function resolveExtendedObjectSchemas(
|
|
|
36
38
|
{ extendObjType: currentSchema.extendObjType }
|
|
37
39
|
);
|
|
38
40
|
const initialResources = currentSchema.storageResources || {};
|
|
39
|
-
const
|
|
41
|
+
const initialResourceKeys = Object.keys(initialResources);
|
|
42
|
+
const childStorageResourceKeys = new Set(initialResourceKeys);
|
|
40
43
|
const parentServiceTag = currentSchema.extendObjType.serviceTag;
|
|
41
44
|
|
|
42
|
-
const
|
|
45
|
+
const mergeResult = await getObjectSchemaLib.mergeExtendObjSchema(
|
|
43
46
|
{ logger: consoleLogger },
|
|
44
47
|
currentSchema,
|
|
45
48
|
bucketName
|
|
46
49
|
);
|
|
47
50
|
|
|
51
|
+
if (
|
|
52
|
+
mergeResult &&
|
|
53
|
+
typeof mergeResult === 'object' &&
|
|
54
|
+
Array.isArray(mergeResult.errorsFound) &&
|
|
55
|
+
mergeResult.errorsFound.length > 0
|
|
56
|
+
) {
|
|
57
|
+
const errMsg = mergeResult.errorsFound.join('; ');
|
|
58
|
+
throw new Error(
|
|
59
|
+
`[resolveExtendedObjectSchemas] Failed to merge extendObjType ` +
|
|
60
|
+
`for objectType=${currentSchema.objectType}: ${errMsg}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const mergedSchema = _unwrapSchemaResult(mergeResult);
|
|
65
|
+
|
|
48
66
|
const externalDynamoResources = [];
|
|
49
67
|
const ownStorageResources = {};
|
|
50
68
|
const mergedStorageResources = mergedSchema.storageResources || {};
|
|
@@ -102,12 +120,13 @@ export async function resolveExtendedObjectSchemas(
|
|
|
102
120
|
serviceTag: belongToTarget.serviceTag,
|
|
103
121
|
objectType: belongToTarget.objectType
|
|
104
122
|
};
|
|
105
|
-
const
|
|
123
|
+
const belongToResult = await getObjectSchemaLib.getObjSchemaS3(
|
|
106
124
|
{ logger: consoleLogger },
|
|
107
125
|
belongToObjType,
|
|
108
126
|
false,
|
|
109
127
|
bucketName
|
|
110
128
|
);
|
|
129
|
+
const belongToSchema = _unwrapSchemaResult(belongToResult);
|
|
111
130
|
currentSchema.belongToSchema = belongToSchema;
|
|
112
131
|
console.log(
|
|
113
132
|
`[DEBUG] [resolveExtendedObjectSchemas] Successfully fetched ` +
|
|
@@ -124,11 +143,26 @@ export async function resolveExtendedObjectSchemas(
|
|
|
124
143
|
}
|
|
125
144
|
}
|
|
126
145
|
|
|
146
|
+
currentSchema = _unwrapSchemaResult(currentSchema);
|
|
127
147
|
resolvedSchemas.push(currentSchema);
|
|
128
148
|
}
|
|
129
149
|
return resolvedSchemas;
|
|
130
150
|
}
|
|
131
151
|
|
|
152
|
+
function _unwrapSchemaResult(schemaOrResult) {
|
|
153
|
+
if (!schemaOrResult || typeof schemaOrResult !== 'object') {
|
|
154
|
+
return schemaOrResult;
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
'result' in schemaOrResult &&
|
|
158
|
+
schemaOrResult.result &&
|
|
159
|
+
typeof schemaOrResult.result === 'object'
|
|
160
|
+
) {
|
|
161
|
+
return schemaOrResult.result;
|
|
162
|
+
}
|
|
163
|
+
return schemaOrResult;
|
|
164
|
+
}
|
|
165
|
+
|
|
132
166
|
export async function resolveGraphServiceTags(
|
|
133
167
|
schemas,
|
|
134
168
|
bucketName = process.env.iz_serviceSchemaBucketName
|
|
@@ -144,6 +144,9 @@ export function setupBasicValidatorSchema(targetSchema = basicValidatorSchema) {
|
|
|
144
144
|
item.required = item.required.filter((r) => r !== 'billalble');
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
if (item?.properties?.type?.enum?.includes('ledger')) {
|
|
148
|
+
item.properties.entryObject = { type: 'boolean' };
|
|
149
|
+
}
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import initialSetupLib from '@izara_project/izara-core-library-initial-setup';
|
|
2
|
+
import { basicValidatorSchema } from '@izara_project/izara-shared-service-schemas';
|
|
3
|
+
|
|
4
|
+
if (basicValidatorSchema?.basicFlowSchema?.properties?.event?.items?.enum) {
|
|
5
|
+
if (!basicValidatorSchema.basicFlowSchema.properties.event.items.enum.includes('dss')) {
|
|
6
|
+
basicValidatorSchema.basicFlowSchema.properties.event.items.enum.push('dss');
|
|
7
|
+
}
|
|
8
|
+
}
|
|
2
9
|
|
|
3
10
|
const SERVICE_TAG = {
|
|
4
11
|
TRANSLATIONS: 'Translations',
|
|
@@ -16,6 +16,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import initialSetupSharedLib from '@izara_project/izara-core-library-initial-setup';
|
|
19
|
+
import { basicValidatorSchema } from '@izara_project/izara-shared-service-schemas';
|
|
20
|
+
|
|
21
|
+
if (basicValidatorSchema?.basicFlowSchema?.properties?.event?.items?.enum) {
|
|
22
|
+
if (!basicValidatorSchema.basicFlowSchema.properties.event.items.enum.includes('dss')) {
|
|
23
|
+
basicValidatorSchema.basicFlowSchema.properties.event.items.enum.push('dss');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
const SERVICE_TAG = {
|
|
21
28
|
TRANSLATIONS: 'Translations',
|
|
@@ -29,6 +29,19 @@ export async function emitCrudApplicationArtifacts({
|
|
|
29
29
|
if (!Array.isArray(objects) || !Array.isArray(flows)) {
|
|
30
30
|
throw new TypeError('objects and flows must be arrays');
|
|
31
31
|
}
|
|
32
|
+
const malformedObj = objects.find(
|
|
33
|
+
(obj) =>
|
|
34
|
+
!obj ||
|
|
35
|
+
typeof obj.objectType !== 'string' ||
|
|
36
|
+
obj.objectType.trim() === ''
|
|
37
|
+
);
|
|
38
|
+
if (malformedObj) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`[CRUD Application] Malformed ObjectSchema: 'objectType' is required ` +
|
|
41
|
+
`and must be a non-empty string.`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
if (!objects.some(isCrudObject)) {
|
|
33
46
|
if (objects.length > 0) {
|
|
34
47
|
const summary = objects.map((obj) => ({
|
|
@@ -23,6 +23,19 @@ export function emitDynamicCrudFlowSchemas({
|
|
|
23
23
|
if (!Array.isArray(objects) || !Array.isArray(flows)) {
|
|
24
24
|
throw new TypeError('objects and flows must be arrays');
|
|
25
25
|
}
|
|
26
|
+
const malformedObj = objects.find(
|
|
27
|
+
(obj) =>
|
|
28
|
+
!obj ||
|
|
29
|
+
typeof obj.objectType !== 'string' ||
|
|
30
|
+
obj.objectType.trim() === ''
|
|
31
|
+
);
|
|
32
|
+
if (malformedObj) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`[CRUD FlowSchemas] Malformed ObjectSchema: 'objectType' is required ` +
|
|
35
|
+
`and must be a non-empty string.`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
if (!objects.some(isCrudObject)) {
|
|
27
40
|
if (objects.length > 0) {
|
|
28
41
|
const summary = objects.map((obj) => ({
|
|
@@ -153,6 +153,10 @@ export default async function <%- flowTag %>(
|
|
|
153
153
|
requestParams.billable = true;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
//(<rbacUsageValue_<%- flowTag %>>)
|
|
157
|
+
let usageValue = requestParams.value || requestParams.usageValue || 1;
|
|
158
|
+
//(</rbacUsageValue_<%- flowTag %>>)
|
|
159
|
+
|
|
156
160
|
const resultPermissionCheck = await checkRbacPermission(
|
|
157
161
|
_izContext,
|
|
158
162
|
{
|
|
@@ -168,6 +172,7 @@ export default async function <%- flowTag %>(
|
|
|
168
172
|
serviceTag: <%- rbacServiceTags.length === 1 ? rbacServiceTags[0] : `{ ${Object.entries(rbacServiceTagMap).map(([k, v]) => `'${k}': ${v}`).join(', ')} }[requestParams.objectType] || process.env.iz_serviceTag` %>
|
|
169
173
|
},
|
|
170
174
|
requestParams: requestParams,
|
|
175
|
+
value: usageValue,
|
|
171
176
|
sync: true
|
|
172
177
|
}
|
|
173
178
|
);
|
|
@@ -187,6 +192,14 @@ export default async function <%- flowTag %>(
|
|
|
187
192
|
);
|
|
188
193
|
}
|
|
189
194
|
|
|
195
|
+
if (resultPermissionCheck?.uniqueIdentify) {
|
|
196
|
+
requestParams.reservedUsageId = resultPermissionCheck.uniqueIdentify;
|
|
197
|
+
}
|
|
198
|
+
if (resultPermissionCheck?.runningTotalReservedId) {
|
|
199
|
+
requestParams.runningTotalReservedId =
|
|
200
|
+
resultPermissionCheck.runningTotalReservedId;
|
|
201
|
+
}
|
|
202
|
+
|
|
190
203
|
//(<rbacPublish_<%- flowTag %>>)
|
|
191
204
|
//(</rbacPublish_<%- flowTag %>>)
|
|
192
205
|
}
|
|
@@ -275,6 +288,7 @@ export default async function <%- flowTag %>(
|
|
|
275
288
|
//(</afterBeforeLogical_<%= objectType %>>)
|
|
276
289
|
|
|
277
290
|
<%_ if (hasOwnTopic) { _%>
|
|
291
|
+
/*
|
|
278
292
|
// ── ownTopic (async — dispatch to before-hook and await callback) ──────────
|
|
279
293
|
<%_ configs.filter(c => c?.generatedCodeConfig?.invocationType === 'ownTopic' || c?.event?.includes('ownTopic')).forEach(config => { _%>
|
|
280
294
|
let hookPayload_<%= config.flowTag %> = {
|
|
@@ -341,6 +355,13 @@ export default async function <%- flowTag %>(
|
|
|
341
355
|
status: 'pending',
|
|
342
356
|
message: 'Dispatched to BeforeHook'
|
|
343
357
|
};
|
|
358
|
+
*/
|
|
359
|
+
return await <%- flowTag %>LogicalLib(
|
|
360
|
+
_izContext,
|
|
361
|
+
requestParams,
|
|
362
|
+
callingFlowConfig,
|
|
363
|
+
foundErrors
|
|
364
|
+
);
|
|
344
365
|
<%_ } else { _%>
|
|
345
366
|
return await <%- flowTag %>LogicalLib(
|
|
346
367
|
_izContext,
|
|
@@ -154,7 +154,7 @@ export default async function CancelUsage(_izContext, requestParams) {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
const eventSourceTag =
|
|
157
|
-
_izContext
|
|
157
|
+
_izContext?.izEventSource?.eventSourceTag ||
|
|
158
158
|
requestParams?.izEventSource?.eventSourceTag;
|
|
159
159
|
|
|
160
160
|
let isSync = false;
|
|
@@ -162,16 +162,17 @@ export default async function CancelUsage(_izContext, requestParams) {
|
|
|
162
162
|
eventSourceTag === 'DirectInvoke' ||
|
|
163
163
|
eventSourceTag === 'ApiGateway' ||
|
|
164
164
|
eventSourceTag === 'ApiGatewayV2' ||
|
|
165
|
-
requestParams?.hasOwnProperty('__context__')
|
|
165
|
+
requestParams?.hasOwnProperty('__context__') ||
|
|
166
|
+
_izContext?.functionName?.endsWith('HdrInv') ||
|
|
167
|
+
_izContext?.functionName?.endsWith('HdrApi') ||
|
|
168
|
+
eventSourceTag !== 'Sqs'
|
|
166
169
|
) {
|
|
167
170
|
isSync = true;
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
if (!isSync) {
|
|
171
|
-
await sns.
|
|
172
|
-
|
|
173
|
-
returnResult
|
|
174
|
-
},
|
|
174
|
+
await sns.publishAsync(_izContext, {
|
|
175
|
+
Message: JSON.stringify({ returnResult }),
|
|
175
176
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
176
177
|
_izContext,
|
|
177
178
|
'CancelUsage_Out'
|
|
@@ -178,7 +178,7 @@ export default async function ConfirmUsage(_izContext, requestParams) {
|
|
|
178
178
|
};
|
|
179
179
|
|
|
180
180
|
const eventSourceTag =
|
|
181
|
-
_izContext
|
|
181
|
+
_izContext?.izEventSource?.eventSourceTag ||
|
|
182
182
|
requestParams?.izEventSource?.eventSourceTag;
|
|
183
183
|
|
|
184
184
|
let isSync = false;
|
|
@@ -186,16 +186,17 @@ export default async function ConfirmUsage(_izContext, requestParams) {
|
|
|
186
186
|
eventSourceTag === 'DirectInvoke' ||
|
|
187
187
|
eventSourceTag === 'ApiGateway' ||
|
|
188
188
|
eventSourceTag === 'ApiGatewayV2' ||
|
|
189
|
-
requestParams?.hasOwnProperty('__context__')
|
|
189
|
+
requestParams?.hasOwnProperty('__context__') ||
|
|
190
|
+
_izContext?.functionName?.endsWith('HdrInv') ||
|
|
191
|
+
_izContext?.functionName?.endsWith('HdrApi') ||
|
|
192
|
+
eventSourceTag !== 'Sqs'
|
|
190
193
|
) {
|
|
191
194
|
isSync = true;
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
if (!isSync) {
|
|
195
|
-
await sns.
|
|
196
|
-
|
|
197
|
-
returnResult
|
|
198
|
-
},
|
|
198
|
+
await sns.publishAsync(_izContext, {
|
|
199
|
+
Message: JSON.stringify({ returnResult }),
|
|
199
200
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
200
201
|
_izContext,
|
|
201
202
|
'ConfirmUsage_Out'
|
|
@@ -133,8 +133,8 @@ export default async function CreateTargetRole(_izContext, requestParams) {
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
await sns.
|
|
137
|
-
|
|
136
|
+
await sns.publishAsync(_izContext, {
|
|
137
|
+
Message: JSON.stringify({ returnResult: { roleId, roleIdKey } }),
|
|
138
138
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
139
139
|
_izContext,
|
|
140
140
|
'CreateTargetRole_Out'
|
|
@@ -82,8 +82,8 @@ export default async function DeleteTargetRole(_izContext, requestParams) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
await sns.
|
|
86
|
-
|
|
85
|
+
await sns.publishAsync(_izContext, {
|
|
86
|
+
Message: JSON.stringify({ returnResult: { identifiersConcat, roleIdKey, status: 'deleted' } }),
|
|
87
87
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
88
88
|
_izContext,
|
|
89
89
|
'DeleteTargetRole_Out'
|
|
@@ -95,8 +95,8 @@ export default async function GetMyPermissions(_izContext, requestParams) {
|
|
|
95
95
|
permissions: permissionsMap
|
|
96
96
|
};
|
|
97
97
|
|
|
98
|
-
await sns.
|
|
99
|
-
|
|
98
|
+
await sns.publishAsync(_izContext, {
|
|
99
|
+
Message: JSON.stringify({ returnResult: result }),
|
|
100
100
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
101
101
|
_izContext,
|
|
102
102
|
'GetMyPermissions_Out'
|
|
@@ -47,8 +47,8 @@ export default async function GetTargetRole(_izContext, requestParams) {
|
|
|
47
47
|
users: usersResult?.Items || []
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
await sns.
|
|
51
|
-
|
|
50
|
+
await sns.publishAsync(_izContext, {
|
|
51
|
+
Message: JSON.stringify({ returnResult: result }),
|
|
52
52
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
53
53
|
_izContext,
|
|
54
54
|
'GetTargetRole_Out'
|
|
@@ -41,8 +41,8 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
|
|
|
41
41
|
dynamodbSharedLib.tableName(_izContext, '<%- tableName %>'),
|
|
42
42
|
queryParams
|
|
43
43
|
);
|
|
44
|
-
await sns.
|
|
45
|
-
|
|
44
|
+
await sns.publishAsync(_izContext, {
|
|
45
|
+
Message: JSON.stringify({ returnResult: { records: resultQuery.Items || [] } }),
|
|
46
46
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
47
47
|
_izContext,
|
|
48
48
|
'<%- flowTag %>_Out'
|
|
@@ -28,8 +28,8 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
|
|
|
28
28
|
dynamodbSharedLib.tableName(_izContext, '<%- tableName %>'),
|
|
29
29
|
{ <%- fieldList %> }
|
|
30
30
|
);
|
|
31
|
-
await sns.
|
|
32
|
-
|
|
31
|
+
await sns.publishAsync(_izContext, {
|
|
32
|
+
Message: JSON.stringify({ returnResult: { <%- fieldList %> } }),
|
|
33
33
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
34
34
|
_izContext,
|
|
35
35
|
'<%- flowTag %>_Out'
|
|
@@ -197,7 +197,7 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
197
197
|
};
|
|
198
198
|
|
|
199
199
|
const eventSourceTag =
|
|
200
|
-
_izContext
|
|
200
|
+
_izContext?.izEventSource?.eventSourceTag ||
|
|
201
201
|
requestParams?.izEventSource?.eventSourceTag;
|
|
202
202
|
|
|
203
203
|
let isSync = false;
|
|
@@ -205,14 +205,17 @@ export default async function ReservedDynamicUsage(_izContext, requestParams) {
|
|
|
205
205
|
eventSourceTag === 'DirectInvoke' ||
|
|
206
206
|
eventSourceTag === 'ApiGateway' ||
|
|
207
207
|
eventSourceTag === 'ApiGatewayV2' ||
|
|
208
|
-
requestParams?.hasOwnProperty('__context__')
|
|
208
|
+
requestParams?.hasOwnProperty('__context__') ||
|
|
209
|
+
_izContext?.functionName?.endsWith('HdrInv') ||
|
|
210
|
+
_izContext?.functionName?.endsWith('HdrApi') ||
|
|
211
|
+
eventSourceTag !== 'Sqs'
|
|
209
212
|
) {
|
|
210
213
|
isSync = true;
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
if (!isSync) {
|
|
214
|
-
await sns.
|
|
215
|
-
|
|
217
|
+
await sns.publishAsync(_izContext, {
|
|
218
|
+
Message: JSON.stringify({ returnResult }),
|
|
216
219
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
217
220
|
_izContext,
|
|
218
221
|
'ReservedDynamicUsage_Out'
|
|
@@ -93,8 +93,8 @@ export default async function UpdateTargetRole(_izContext, requestParams) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
await sns.
|
|
97
|
-
|
|
96
|
+
await sns.publishAsync(_izContext, {
|
|
97
|
+
Message: JSON.stringify({ returnResult: { roleIdKey, status: 'updated' } }),
|
|
98
98
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
99
99
|
_izContext,
|
|
100
100
|
'UpdateTargetRole_Out'
|
|
@@ -54,8 +54,8 @@ export default async function <%- flowTag %>(_izContext, requestParams) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
]);
|
|
57
|
-
await sns.
|
|
58
|
-
|
|
57
|
+
await sns.publishAsync(_izContext, {
|
|
58
|
+
Message: JSON.stringify({ returnResult: { userId, roleIdKey } }),
|
|
59
59
|
TopicArn: snsSharedLib.snsTopicArnByFlowSchema(
|
|
60
60
|
_izContext,
|
|
61
61
|
'<%- flowTag %>_Out'
|
|
@@ -102,7 +102,8 @@ export async function renderOut(state) {
|
|
|
102
102
|
renderResourceTemplate('FlowOut_Yaml.ejs', { topicName })
|
|
103
103
|
)
|
|
104
104
|
);
|
|
105
|
-
const
|
|
105
|
+
const fileHook = '#<#customTopicOut#>\n#<#/customTopicOut#>';
|
|
106
|
+
const body = [...renderedTopics, fileHook].filter(Boolean).join('\n');
|
|
106
107
|
return `Resources:\n${indentYaml(body, 2)}`;
|
|
107
108
|
}
|
|
108
109
|
|
|
@@ -126,7 +126,6 @@ function _buildParentToEntryLinks({
|
|
|
126
126
|
objectType: parentObjectType
|
|
127
127
|
},
|
|
128
128
|
linkType: 'many',
|
|
129
|
-
requiredOnCreate: false,
|
|
130
129
|
belongTo: true
|
|
131
130
|
},
|
|
132
131
|
to: {
|
|
@@ -134,8 +133,7 @@ function _buildParentToEntryLinks({
|
|
|
134
133
|
serviceTag: effectiveServiceTag,
|
|
135
134
|
objectType: entryObjectType
|
|
136
135
|
},
|
|
137
|
-
linkType: 'one'
|
|
138
|
-
requiredOnCreate: false
|
|
136
|
+
linkType: 'one'
|
|
139
137
|
}
|
|
140
138
|
}
|
|
141
139
|
];
|
|
@@ -145,7 +143,10 @@ export function ledgerObjectSchemaManagedPaths(objects = []) {
|
|
|
145
143
|
const ledgers = _extractLedgers(objects);
|
|
146
144
|
const paths = [];
|
|
147
145
|
|
|
148
|
-
for (const { component } of ledgers) {
|
|
146
|
+
for (const { component, addOn } of ledgers) {
|
|
147
|
+
if (addOn?.entryObject === false) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
149
150
|
paths.push(
|
|
150
151
|
`app/src/schemas/ObjectSchemas/generatedLedger/${component.objectType}.js`
|
|
151
152
|
);
|
|
@@ -198,6 +199,9 @@ export async function emitLedgerObjectSchemas({ objects = [] } = {}) {
|
|
|
198
199
|
const artifacts = [];
|
|
199
200
|
|
|
200
201
|
for (const { parentObject, addOn, component } of ledgers) {
|
|
202
|
+
if (addOn?.entryObject === false) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
201
205
|
const objectType = component.objectType;
|
|
202
206
|
const graphStorageResources = _extractGraphStorageResources(
|
|
203
207
|
parentObject,
|
|
@@ -362,16 +366,14 @@ export async function emitLedgerRelationshipSchemas({
|
|
|
362
366
|
serviceTag: effectiveServiceTag,
|
|
363
367
|
objectType: component.objectType
|
|
364
368
|
},
|
|
365
|
-
linkType: 'many'
|
|
366
|
-
requiredOnCreate: false
|
|
369
|
+
linkType: 'many'
|
|
367
370
|
},
|
|
368
371
|
to: {
|
|
369
372
|
objType: {
|
|
370
373
|
serviceTag: targetServiceTag,
|
|
371
374
|
objectType: target.objectType
|
|
372
375
|
},
|
|
373
|
-
linkType: 'one'
|
|
374
|
-
requiredOnCreate: false
|
|
376
|
+
linkType: 'one'
|
|
375
377
|
}
|
|
376
378
|
});
|
|
377
379
|
}
|
|
@@ -1,3 +1,19 @@
|
|
|
1
1
|
export default [
|
|
2
|
-
|
|
2
|
+
{
|
|
3
|
+
"objectType": "<%= schema.objectType %>",
|
|
4
|
+
"generatedBy": "systemNoUser",
|
|
5
|
+
"canDelete": false,
|
|
6
|
+
"storageResources": <%- JSON.stringify(schema.storageResources, null, 4).replace(/\n/g, '\n ') %>,
|
|
7
|
+
"fieldNames": {
|
|
8
|
+
"<%= schema.objectType %>Id": <%- JSON.stringify(schema.fieldNames[schema.objectType + 'Id'], null, 6).replace(/\n/g, '\n ') %>,
|
|
9
|
+
//(<<%= schema.objectType %>HookCode>)
|
|
10
|
+
//(</<%= schema.objectType %>HookCode>)
|
|
11
|
+
},
|
|
12
|
+
"identifiers": [
|
|
13
|
+
{
|
|
14
|
+
"type": "identifier",
|
|
15
|
+
"fieldName": "<%= schema.objectType %>Id"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
3
19
|
];
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
export default
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
"<%= relationshipTag %>": {
|
|
4
|
+
<% Object.entries(content).forEach(([key, val]) => { -%>
|
|
5
|
+
"<%- key %>": <%- JSON.stringify(val, null, 6).replace(/\n/g, '\n ') %>,
|
|
6
|
+
<% }); -%>
|
|
7
|
+
//(<<%= relationshipTag %>HookCode>)
|
|
8
|
+
//(</<%= relationshipTag %>HookCode>)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
];
|
package/src/generate.js
CHANGED
|
@@ -42,10 +42,22 @@ export async function generate(rootPath, options = {}) {
|
|
|
42
42
|
options.schemaBucketName ||
|
|
43
43
|
project.schemaBucketName ||
|
|
44
44
|
process.env.iz_serviceSchemaBucketName;
|
|
45
|
-
const
|
|
45
|
+
const resolvedObjects = await resolveExtendedObjectSchemas(
|
|
46
46
|
rawObjects,
|
|
47
47
|
schemaBucketName
|
|
48
48
|
);
|
|
49
|
+
const objects = resolvedObjects.map((obj) => {
|
|
50
|
+
if (
|
|
51
|
+
obj &&
|
|
52
|
+
typeof obj === 'object' &&
|
|
53
|
+
'result' in obj &&
|
|
54
|
+
obj.result &&
|
|
55
|
+
typeof obj.result === 'object'
|
|
56
|
+
) {
|
|
57
|
+
return obj.result;
|
|
58
|
+
}
|
|
59
|
+
return obj;
|
|
60
|
+
});
|
|
49
61
|
const objectGraphServiceTags = await resolveGraphServiceTags(
|
|
50
62
|
objects,
|
|
51
63
|
schemaBucketName
|
|
@@ -93,16 +105,35 @@ export async function generate(rootPath, options = {}) {
|
|
|
93
105
|
console.log(
|
|
94
106
|
`[INFO] [Generate] STATUS=ARTIFACTS_EMITTED | ${emittedScopeSummary}`
|
|
95
107
|
);
|
|
108
|
+
const allArtifacts = groups.flatMap((group) => group.artifacts);
|
|
109
|
+
const dirSummary = _summarizeArtifactDirectories(allArtifacts);
|
|
110
|
+
const summaryEntries = Object.entries(dirSummary);
|
|
111
|
+
if (summaryEntries.length > 0) {
|
|
112
|
+
const summaryItems = summaryEntries
|
|
113
|
+
.map(([dir, count]) => `${dir}=${count}`)
|
|
114
|
+
.join(' ');
|
|
115
|
+
console.log(`[INFO] [Generate] OUTPUT_SUMMARY | ${summaryItems}`);
|
|
116
|
+
}
|
|
96
117
|
let files = [];
|
|
97
118
|
if (groups.length > 0) {
|
|
98
119
|
files = await commitArtifacts(
|
|
99
120
|
buildGenerationPlan(
|
|
100
121
|
project,
|
|
101
|
-
|
|
122
|
+
allArtifacts,
|
|
102
123
|
{
|
|
103
124
|
managedScopes: Object.fromEntries(
|
|
104
125
|
groups.map((group) => [group.scope, group.managedPaths])
|
|
105
|
-
)
|
|
126
|
+
),
|
|
127
|
+
scopeGuards: {
|
|
128
|
+
hasMalformedSchemas:
|
|
129
|
+
objects.some(
|
|
130
|
+
(obj) =>
|
|
131
|
+
!obj ||
|
|
132
|
+
typeof obj.objectType !== 'string' ||
|
|
133
|
+
obj.objectType.trim() === ''
|
|
134
|
+
) ||
|
|
135
|
+
relationships.some((rel) => !rel || typeof rel !== 'object')
|
|
136
|
+
}
|
|
106
137
|
}
|
|
107
138
|
)
|
|
108
139
|
);
|
|
@@ -123,3 +154,19 @@ export async function generate(rootPath, options = {}) {
|
|
|
123
154
|
throw error;
|
|
124
155
|
}
|
|
125
156
|
}
|
|
157
|
+
|
|
158
|
+
function _summarizeArtifactDirectories(artifacts) {
|
|
159
|
+
const dirCounts = {};
|
|
160
|
+
for (const artifact of artifacts) {
|
|
161
|
+
const parts = artifact.path.split('/');
|
|
162
|
+
let category = parts.slice(0, 3).join('/');
|
|
163
|
+
if (parts.length < 3) {
|
|
164
|
+
category = parts.slice(0, -1).join('/');
|
|
165
|
+
if (!category) {
|
|
166
|
+
category = '.';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
dirCounts[category] = (dirCounts[category] || 0) + 1;
|
|
170
|
+
}
|
|
171
|
+
return dirCounts;
|
|
172
|
+
}
|
|
@@ -42,8 +42,17 @@ export async function commitArtifacts(plan) {
|
|
|
42
42
|
const stalePaths = _staleManagedPaths(
|
|
43
43
|
managedScopes,
|
|
44
44
|
managedArtifacts,
|
|
45
|
-
artifacts
|
|
45
|
+
artifacts,
|
|
46
|
+
plan.scopeGuards
|
|
46
47
|
);
|
|
48
|
+
if (stalePaths.length > 0) {
|
|
49
|
+
console.log(
|
|
50
|
+
`[WARN] [Commit] REMOVED_STALE | count=${stalePaths.length} files:`
|
|
51
|
+
);
|
|
52
|
+
for (const path of stalePaths) {
|
|
53
|
+
console.log(` - ${path}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
47
56
|
const mergedArtifacts = await mergeHooks(physicalOutputRoot, artifacts);
|
|
48
57
|
const stagedRoot = await mkdtemp(join(physicalOutputRoot, STAGING_PREFIX));
|
|
49
58
|
|
|
@@ -67,6 +76,10 @@ export async function commitArtifacts(plan) {
|
|
|
67
76
|
const snapshots = await _snapshotChanges(changes, stagedRoot);
|
|
68
77
|
try {
|
|
69
78
|
await _applyChanges(changes);
|
|
79
|
+
console.log(
|
|
80
|
+
`[INFO] [Commit] STATUS=APPLIED | written=${artifacts.length} ` +
|
|
81
|
+
`removed=${stalePaths.length}`
|
|
82
|
+
);
|
|
70
83
|
} catch (error) {
|
|
71
84
|
await _rollbackChanges(changes, snapshots);
|
|
72
85
|
throw error;
|
|
@@ -90,7 +103,12 @@ function _normalizeManagedScopes(plan) {
|
|
|
90
103
|
};
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
function _staleManagedPaths(
|
|
106
|
+
function _staleManagedPaths(
|
|
107
|
+
managedScopes,
|
|
108
|
+
managedArtifacts,
|
|
109
|
+
artifacts,
|
|
110
|
+
scopeGuards
|
|
111
|
+
) {
|
|
94
112
|
if (!managedScopes) {
|
|
95
113
|
return [];
|
|
96
114
|
}
|
|
@@ -100,7 +118,28 @@ function _staleManagedPaths(managedScopes, managedArtifacts, artifacts) {
|
|
|
100
118
|
if (!Object.hasOwn(managedArtifacts, scope)) {
|
|
101
119
|
continue;
|
|
102
120
|
}
|
|
103
|
-
|
|
121
|
+
const previousPaths = managedArtifacts[scope] || [];
|
|
122
|
+
if (previousPaths.length > 0) {
|
|
123
|
+
const scopeEmittedCount = declaredPaths.filter((path) =>
|
|
124
|
+
emittedPaths.has(path)
|
|
125
|
+
).length;
|
|
126
|
+
if (scopeEmittedCount === 0) {
|
|
127
|
+
if (scopeGuards?.hasMalformedSchemas) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`[SAFETY_GUARD] Refusing to delete all artifacts in managed ` +
|
|
130
|
+
`scope '${scope}'. Malformed schemas were detected in the ` +
|
|
131
|
+
`project.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (scopeGuards?.protectedScopes?.includes(scope)) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`[SAFETY_GUARD] Refusing to delete all artifacts in protected ` +
|
|
137
|
+
`scope '${scope}'.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const path of [...declaredPaths, ...previousPaths]) {
|
|
104
143
|
if (!emittedPaths.has(path)) {
|
|
105
144
|
stalePaths.add(path);
|
|
106
145
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
export function buildGenerationPlan(
|
|
7
7
|
project,
|
|
8
8
|
artifacts = [],
|
|
9
|
-
{ managedPaths = [], managedScope, managedScopes } = {}
|
|
9
|
+
{ managedPaths = [], managedScope, managedScopes, scopeGuards } = {}
|
|
10
10
|
) {
|
|
11
11
|
if (!project?.rootPath || !project?.outputPath) {
|
|
12
12
|
throw new TypeError('ProjectModel must include rootPath and outputPath');
|
|
@@ -21,7 +21,8 @@ export function buildGenerationPlan(
|
|
|
21
21
|
artifacts: validateArtifacts(artifacts),
|
|
22
22
|
managedPaths: normalizeArtifactPaths(managedPaths),
|
|
23
23
|
managedScope: _normalizeManagedScope(managedScope),
|
|
24
|
-
managedScopes: _normalizeManagedScopes(managedScopes)
|
|
24
|
+
managedScopes: _normalizeManagedScopes(managedScopes),
|
|
25
|
+
scopeGuards: scopeGuards ? Object.freeze({ ...scopeGuards }) : null
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -229,3 +229,34 @@ test('rejects a failed output transaction without partial files, hooks, or stagi
|
|
|
229
229
|
);
|
|
230
230
|
assert.equal((await readdir(outputPath)).includes('.bak'), false);
|
|
231
231
|
});
|
|
232
|
+
|
|
233
|
+
test('commitArtifacts throws SAFETY_GUARD error when hasMalformedSchemas is true and scope would be wiped', async () => {
|
|
234
|
+
const outputPath = await mkdtemp(
|
|
235
|
+
join(tmpdir(), 'generate-service-code-v2-safety-guard-')
|
|
236
|
+
);
|
|
237
|
+
const project = compileProject({ rootPath: outputPath });
|
|
238
|
+
const managedScopes = {
|
|
239
|
+
'crud-application': ['app/sls_yaml/generatedCode/source/flow-objects.yml']
|
|
240
|
+
};
|
|
241
|
+
await commitArtifacts(
|
|
242
|
+
buildGenerationPlan(
|
|
243
|
+
project,
|
|
244
|
+
[{ path: 'app/sls_yaml/generatedCode/source/flow-objects.yml', content: 'content\n' }],
|
|
245
|
+
{ managedScopes }
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
await assert.rejects(
|
|
250
|
+
commitArtifacts(
|
|
251
|
+
buildGenerationPlan(project, [], {
|
|
252
|
+
managedScopes,
|
|
253
|
+
scopeGuards: { hasMalformedSchemas: true }
|
|
254
|
+
})
|
|
255
|
+
),
|
|
256
|
+
/\[SAFETY_GUARD\] Refusing to delete all artifacts in managed scope 'crud-application'/
|
|
257
|
+
);
|
|
258
|
+
assert.equal(
|
|
259
|
+
await readFile(join(outputPath, 'app/sls_yaml/generatedCode/source/flow-objects.yml'), 'utf8'),
|
|
260
|
+
'content\n'
|
|
261
|
+
);
|
|
262
|
+
});
|
|
@@ -8,6 +8,7 @@ import test, { after } from 'node:test';
|
|
|
8
8
|
process.env.NODE_ENV = 'test';
|
|
9
9
|
|
|
10
10
|
import { generateCrudApplicationArtifacts } from '../../../src/emit/application/crud/generateCrudApplicationArtifacts.js';
|
|
11
|
+
import { emitCrudApplicationArtifacts } from '../../../src/emit/application/crud/emitCrudApplicationArtifacts.js';
|
|
11
12
|
|
|
12
13
|
const roots = new Set();
|
|
13
14
|
after(() =>
|
|
@@ -1464,6 +1465,15 @@ test('emits async before-hook callback and SNS publish in Create_Main when event
|
|
|
1464
1465
|
assert.match(callbackHandler, /CreateBeforeHookCallback_Main/);
|
|
1465
1466
|
});
|
|
1466
1467
|
|
|
1468
|
+
test('emitCrudApplicationArtifacts throws error when an object is malformed', async () => {
|
|
1469
|
+
await assert.rejects(
|
|
1470
|
+
emitCrudApplicationArtifacts({
|
|
1471
|
+
objects: [{ generatedBy: 'user' }]
|
|
1472
|
+
}),
|
|
1473
|
+
/Malformed ObjectSchema: 'objectType' is required/
|
|
1474
|
+
);
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1467
1477
|
async function createProject(withCrudObject) {
|
|
1468
1478
|
const rootPath = await mkdtemp(
|
|
1469
1479
|
join(tmpdir(), 'generate-service-code-v2-crud-application-')
|
|
@@ -964,3 +964,23 @@ test('roleStatements emits s3:GetObject for custom S3 flow bucket', async () =>
|
|
|
964
964
|
assert.match(uploadRole, /arn:aws:s3:::UploadBucket\/\*/);
|
|
965
965
|
});
|
|
966
966
|
|
|
967
|
+
test('emits per-topic hook tags in generated-sns-out.yml', async () => {
|
|
968
|
+
const rootPath = await createProject();
|
|
969
|
+
await generateResourceArtifacts(rootPath);
|
|
970
|
+
const snsOut = await readFile(
|
|
971
|
+
join(rootPath, 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml'),
|
|
972
|
+
'utf8'
|
|
973
|
+
);
|
|
974
|
+
assert.match(snsOut, /#<#Out\w+TopicSetting#>\s*#<#\/Out\w+TopicSetting#>/);
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
test('emits customTopicOut file-level hook in generated-sns-out.yml', async () => {
|
|
978
|
+
const rootPath = await createProject();
|
|
979
|
+
await generateResourceArtifacts(rootPath);
|
|
980
|
+
const snsOut = await readFile(
|
|
981
|
+
join(rootPath, 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml'),
|
|
982
|
+
'utf8'
|
|
983
|
+
);
|
|
984
|
+
assert.match(snsOut, /#<#customTopicOut#>\s*#<#\/customTopicOut#>/);
|
|
985
|
+
});
|
|
986
|
+
|
|
@@ -402,4 +402,53 @@ test('warns when source content contains custom code in hook missing from templa
|
|
|
402
402
|
}
|
|
403
403
|
});
|
|
404
404
|
|
|
405
|
+
test('preserves custom settings inside Out<topicName>TopicSetting and customTopicOut in generated-sns-out.yml', async () => {
|
|
406
|
+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
|
|
407
|
+
try {
|
|
408
|
+
const filePath = 'resource/sls_yaml/generatedCode/source/generated-sns-out.yml';
|
|
409
|
+
const diskContent = [
|
|
410
|
+
'Resources:',
|
|
411
|
+
' ##===== [Topic Out]',
|
|
412
|
+
' OutCreate:',
|
|
413
|
+
' Type: AWS::SNS::Topic',
|
|
414
|
+
' Properties:',
|
|
415
|
+
" DisplayName: 'SNS Message out'",
|
|
416
|
+
' TopicName: ${self:custom.iz_serviceTag}_${self:custom.iz_stage}_Create_Out',
|
|
417
|
+
' #<#OutCreateTopicSetting#>',
|
|
418
|
+
' FifoTopic: true',
|
|
419
|
+
' #<#/OutCreateTopicSetting#>',
|
|
420
|
+
'',
|
|
421
|
+
' #<#customTopicOut#>',
|
|
422
|
+
' OutCustomNotification:',
|
|
423
|
+
' Type: AWS::SNS::Topic',
|
|
424
|
+
' #<#/customTopicOut#>'
|
|
425
|
+
].join('\n') + '\n';
|
|
426
|
+
await writeTestFile(tmpDir, filePath, diskContent);
|
|
427
|
+
|
|
428
|
+
const generatedArtifact = {
|
|
429
|
+
path: filePath,
|
|
430
|
+
content: [
|
|
431
|
+
'Resources:',
|
|
432
|
+
' ##===== [Topic Out]',
|
|
433
|
+
' OutCreate:',
|
|
434
|
+
' Type: AWS::SNS::Topic',
|
|
435
|
+
' Properties:',
|
|
436
|
+
" DisplayName: 'SNS Message out'",
|
|
437
|
+
' TopicName: ${self:custom.iz_serviceTag}_${self:custom.iz_stage}_Create_Out',
|
|
438
|
+
' #<#OutCreateTopicSetting#>',
|
|
439
|
+
' #<#/OutCreateTopicSetting#>',
|
|
440
|
+
'',
|
|
441
|
+
' #<#customTopicOut#>',
|
|
442
|
+
' #<#/customTopicOut#>'
|
|
443
|
+
].join('\n') + '\n'
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const [result] = await mergeHooks(tmpDir, [generatedArtifact]);
|
|
447
|
+
assert.match(result.content, /FifoTopic: true/);
|
|
448
|
+
assert.match(result.content, /OutCustomNotification:/);
|
|
449
|
+
} finally {
|
|
450
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
405
454
|
|
|
@@ -6,6 +6,7 @@ import test from 'node:test';
|
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
7
|
|
|
8
8
|
import { generateDynamicCrudFlowSchemas } from '../../../src/emit/application/crud/generateDynamicCrudFlowSchemas.js';
|
|
9
|
+
import { emitDynamicCrudFlowSchemas } from '../../../src/emit/application/crud/emitDynamicCrudFlowSchemas.js';
|
|
9
10
|
import { createCrudActionPlans } from '../../../src/emit/application/crud/crudActionPlan.js';
|
|
10
11
|
|
|
11
12
|
test('generates dynamic CRUD schemas from local schema modules', async () => {
|
|
@@ -422,3 +423,39 @@ test('throws error when ObjectSchema validation fails with validateSchemas: true
|
|
|
422
423
|
);
|
|
423
424
|
});
|
|
424
425
|
|
|
426
|
+
test('throws error when ObjectSchema is missing objectType', async () => {
|
|
427
|
+
const rootPath = await createProject({ generatedBy: 'user' });
|
|
428
|
+
await writeFile(
|
|
429
|
+
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
430
|
+
"export default [{ generatedBy: 'user' }];\n"
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
await assert.rejects(
|
|
434
|
+
generateDynamicCrudFlowSchemas(rootPath),
|
|
435
|
+
/Invalid ObjectSchema in Order\.js: 'objectType' is required/
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test('throws helpful error when ObjectSchema is wrapped in outer key', async () => {
|
|
440
|
+
const rootPath = await createProject({ generatedBy: 'user' });
|
|
441
|
+
await writeFile(
|
|
442
|
+
join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
|
|
443
|
+
"export default [{ order: { objectType: 'order', generatedBy: 'user' } }];\n"
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
await assert.rejects(
|
|
447
|
+
generateDynamicCrudFlowSchemas(rootPath),
|
|
448
|
+
/Schema appears to be wrapped in key 'order'/
|
|
449
|
+
);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('emitDynamicCrudFlowSchemas throws error when an object is malformed', () => {
|
|
453
|
+
assert.throws(
|
|
454
|
+
() =>
|
|
455
|
+
emitDynamicCrudFlowSchemas({
|
|
456
|
+
objects: [{ generatedBy: 'user' }]
|
|
457
|
+
}),
|
|
458
|
+
/Malformed ObjectSchema: 'objectType' is required/
|
|
459
|
+
);
|
|
460
|
+
});
|
|
461
|
+
|
|
@@ -79,6 +79,10 @@ test('emitLedgerObjectSchemas returns 1 object schema per ledger addOn', async (
|
|
|
79
79
|
assert.match(entryArtifact.content, /"generatedBy": "systemNoUser"/);
|
|
80
80
|
assert.match(entryArtifact.content, /"canDelete": false/);
|
|
81
81
|
assert.match(entryArtifact.content, /GraphHandler/);
|
|
82
|
+
assert.match(
|
|
83
|
+
entryArtifact.content,
|
|
84
|
+
/\/\/\(<unitStandardSupplyLocationEntryHookCode>\)[\s\S]*?\/\/\(<\/unitStandardSupplyLocationEntryHookCode>\)/
|
|
85
|
+
);
|
|
82
86
|
});
|
|
83
87
|
|
|
84
88
|
test('emitLedgerRelationshipSchemas returns current, old, and is relationship schemas per ledger addOn', async () => {
|
|
@@ -157,30 +161,46 @@ test('emitLedgerRelationshipSchemas returns current, old, and is relationship sc
|
|
|
157
161
|
assert.match(isRel.content, /"supplyLocation"/);
|
|
158
162
|
assert.match(isRel.content, /"unitPacking"/);
|
|
159
163
|
|
|
160
|
-
// 4. Verify
|
|
164
|
+
// 4. Verify hookCode in relationship schemas
|
|
165
|
+
assert.match(
|
|
166
|
+
currentRel.content,
|
|
167
|
+
/\/\/\(<currentUnitStandardSupplyLocationHookCode>\)[\s\S]*?\/\/\(<\/currentUnitStandardSupplyLocationHookCode>\)/
|
|
168
|
+
);
|
|
169
|
+
assert.match(
|
|
170
|
+
oldRel.content,
|
|
171
|
+
/\/\/\(<oldUnitStandardSupplyLocationHookCode>\)[\s\S]*?\/\/\(<\/oldUnitStandardSupplyLocationHookCode>\)/
|
|
172
|
+
);
|
|
173
|
+
assert.match(
|
|
174
|
+
isRel.content,
|
|
175
|
+
/\/\/\(<isUnitStandardSupplyLocationEntryHookCode>\)[\s\S]*?\/\/\(<\/isUnitStandardSupplyLocationEntryHookCode>\)/
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// 5. Verify requiredOnCreate is removed from links
|
|
161
179
|
const _parseRel = (content) => {
|
|
162
180
|
const raw = content
|
|
163
181
|
.replace(/^export default\s+/, '')
|
|
164
|
-
.replace(/;\s*$/, '')
|
|
182
|
+
.replace(/;\s*$/, '')
|
|
183
|
+
.replace(/\/\/.*/g, '')
|
|
184
|
+
.replace(/,(\s*})/g, '$1');
|
|
165
185
|
return JSON.parse(raw)[0];
|
|
166
186
|
};
|
|
167
187
|
|
|
168
188
|
const currentRelData = Object.values(_parseRel(currentRel.content))[0];
|
|
169
189
|
for (const link of currentRelData.links) {
|
|
170
|
-
assert.equal(link.from.requiredOnCreate,
|
|
171
|
-
assert.equal(link.to.requiredOnCreate,
|
|
190
|
+
assert.equal(link.from.requiredOnCreate, undefined);
|
|
191
|
+
assert.equal(link.to.requiredOnCreate, undefined);
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
const oldRelData = Object.values(_parseRel(oldRel.content))[0];
|
|
175
195
|
for (const link of oldRelData.links) {
|
|
176
|
-
assert.equal(link.from.requiredOnCreate,
|
|
177
|
-
assert.equal(link.to.requiredOnCreate,
|
|
196
|
+
assert.equal(link.from.requiredOnCreate, undefined);
|
|
197
|
+
assert.equal(link.to.requiredOnCreate, undefined);
|
|
178
198
|
}
|
|
179
199
|
|
|
180
200
|
const isRelData = Object.values(_parseRel(isRel.content))[0];
|
|
181
201
|
for (const link of isRelData.links) {
|
|
182
|
-
assert.equal(link.from.requiredOnCreate,
|
|
183
|
-
assert.equal(link.to.requiredOnCreate,
|
|
202
|
+
assert.equal(link.from.requiredOnCreate, undefined);
|
|
203
|
+
assert.equal(link.to.requiredOnCreate, undefined);
|
|
184
204
|
}
|
|
185
205
|
});
|
|
186
206
|
|
|
@@ -221,10 +241,122 @@ test('generate commits and cleans up ledger dynamic schemas', async () => {
|
|
|
221
241
|
});
|
|
222
242
|
});
|
|
223
243
|
|
|
244
|
+
test('generate preserves user hooks in ledger ObjectSchemas and RelationshipSchemas across regeneration', async () => {
|
|
245
|
+
const rootPath = await createProjectWithLedger({ hasAddOn: true });
|
|
246
|
+
await generate(rootPath);
|
|
247
|
+
|
|
248
|
+
const objectRelPath =
|
|
249
|
+
'app/src/schemas/ObjectSchemas/generatedLedger/unitStandardSupplyLocationEntry.js';
|
|
250
|
+
const relRelPath =
|
|
251
|
+
'app/src/schemas/RelationshipSchemas/generatedLedger/currentUnitStandardSupplyLocation.js';
|
|
252
|
+
|
|
253
|
+
// Inject custom code into dynamic hook tags
|
|
254
|
+
let objectContent = await readProjectFile(rootPath, objectRelPath);
|
|
255
|
+
objectContent = objectContent.replace(
|
|
256
|
+
'//(<unitStandardSupplyLocationEntryHookCode>)\n //(</unitStandardSupplyLocationEntryHookCode>)',
|
|
257
|
+
'//(<unitStandardSupplyLocationEntryHookCode>)\n "customField": { "type": "string" },\n //(</unitStandardSupplyLocationEntryHookCode>)'
|
|
258
|
+
);
|
|
259
|
+
await writeProjectFile(rootPath, objectRelPath, objectContent);
|
|
260
|
+
|
|
261
|
+
let relContent = await readProjectFile(rootPath, relRelPath);
|
|
262
|
+
relContent = relContent.replace(
|
|
263
|
+
'//(<currentUnitStandardSupplyLocationHookCode>)\n //(</currentUnitStandardSupplyLocationHookCode>)',
|
|
264
|
+
'//(<currentUnitStandardSupplyLocationHookCode>)\n "customRelProp": true,\n //(</currentUnitStandardSupplyLocationHookCode>)'
|
|
265
|
+
);
|
|
266
|
+
await writeProjectFile(rootPath, relRelPath, relContent);
|
|
267
|
+
|
|
268
|
+
// Re-run generate
|
|
269
|
+
await generate(rootPath);
|
|
270
|
+
|
|
271
|
+
const preservedObjectContent = await readProjectFile(rootPath, objectRelPath);
|
|
272
|
+
assert.match(preservedObjectContent, /"customField": \{ "type": "string" \}/);
|
|
273
|
+
|
|
274
|
+
const preservedRelContent = await readProjectFile(rootPath, relRelPath);
|
|
275
|
+
assert.match(preservedRelContent, /"customRelProp": true/);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('emitLedgerObjectSchemas and managedPaths skip when entryObject is false', async () => {
|
|
279
|
+
const objects = [
|
|
280
|
+
{
|
|
281
|
+
objectType: 'unitStandard',
|
|
282
|
+
serviceTag: 'UnitStandard',
|
|
283
|
+
storageResources: {
|
|
284
|
+
graph: {
|
|
285
|
+
storageType: 'graph',
|
|
286
|
+
graphServerTag: 'GraphHandler'
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
addOnDataStructure: [
|
|
290
|
+
{
|
|
291
|
+
type: 'ledger',
|
|
292
|
+
prefix: 'unitStandardSupplyLocation',
|
|
293
|
+
storageResourceTag: 'graph',
|
|
294
|
+
ledgerType: 'movable',
|
|
295
|
+
entryObject: false,
|
|
296
|
+
targetObjType: {
|
|
297
|
+
serviceTag: 'SupplyLocationStandard',
|
|
298
|
+
objectType: 'supplyLocation'
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
];
|
|
304
|
+
|
|
305
|
+
const artifacts = await emitLedgerObjectSchemas({ objects });
|
|
306
|
+
assert.equal(artifacts.length, 0);
|
|
307
|
+
|
|
308
|
+
const managedPaths = ledgerObjectSchemaManagedPaths(objects);
|
|
309
|
+
assert.deepEqual(managedPaths, []);
|
|
310
|
+
|
|
311
|
+
const relArtifacts = await emitLedgerRelationshipSchemas({
|
|
312
|
+
objects,
|
|
313
|
+
serviceTag: 'UnitStandard'
|
|
314
|
+
});
|
|
315
|
+
assert.equal(relArtifacts.length, 3);
|
|
316
|
+
const relPaths = ledgerRelationshipSchemaManagedPaths(objects);
|
|
317
|
+
assert.equal(relPaths.length, 3);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('generate cleans up entry ObjectSchema when entryObject is set to false', async () => {
|
|
321
|
+
const rootPath = await createProjectWithLedger({
|
|
322
|
+
hasAddOn: true,
|
|
323
|
+
entryObject: true
|
|
324
|
+
});
|
|
325
|
+
await generate(rootPath);
|
|
326
|
+
|
|
327
|
+
const objectRelPath =
|
|
328
|
+
'app/src/schemas/ObjectSchemas/generatedLedger/unitStandardSupplyLocationEntry.js';
|
|
329
|
+
const relRelPath =
|
|
330
|
+
'app/src/schemas/RelationshipSchemas/generatedLedger/currentUnitStandardSupplyLocation.js';
|
|
331
|
+
|
|
332
|
+
const initialObj = await readProjectFile(rootPath, objectRelPath);
|
|
333
|
+
assert.ok(initialObj);
|
|
334
|
+
const initialRel = await readProjectFile(rootPath, relRelPath);
|
|
335
|
+
assert.ok(initialRel);
|
|
336
|
+
|
|
337
|
+
await createProjectWithLedger({
|
|
338
|
+
rootPath,
|
|
339
|
+
hasAddOn: true,
|
|
340
|
+
entryObject: false
|
|
341
|
+
});
|
|
342
|
+
await generate(rootPath);
|
|
343
|
+
|
|
344
|
+
await assert.rejects(readProjectFile(rootPath, objectRelPath), {
|
|
345
|
+
code: 'ENOENT'
|
|
346
|
+
});
|
|
347
|
+
const preservedRel = await readProjectFile(rootPath, relRelPath);
|
|
348
|
+
assert.ok(preservedRel);
|
|
349
|
+
});
|
|
350
|
+
|
|
224
351
|
async function createProjectWithLedger({
|
|
225
352
|
rootPath: existingRoot,
|
|
226
|
-
hasAddOn = true
|
|
353
|
+
hasAddOn = true,
|
|
354
|
+
entryObject = true
|
|
227
355
|
} = {}) {
|
|
356
|
+
let entryObjectLine = '';
|
|
357
|
+
if (entryObject === false) {
|
|
358
|
+
entryObjectLine = '\n entryObject: false,';
|
|
359
|
+
}
|
|
228
360
|
const addOnContent = hasAddOn
|
|
229
361
|
? `
|
|
230
362
|
addOnDataStructure: [
|
|
@@ -232,7 +364,7 @@ async function createProjectWithLedger({
|
|
|
232
364
|
type: 'ledger',
|
|
233
365
|
prefix: 'unitStandardSupplyLocation',
|
|
234
366
|
storageResourceTag: 'graph',
|
|
235
|
-
ledgerType: 'movable'
|
|
367
|
+
ledgerType: 'movable',${entryObjectLine}
|
|
236
368
|
targetObjType: {
|
|
237
369
|
serviceTag: 'SupplyLocationStandard',
|
|
238
370
|
objectType: 'supplyLocation'
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
resolveExtendedObjectSchemas
|
|
6
|
+
} from '../../../src/compile/loadSchemasRemote.js';
|
|
7
|
+
|
|
8
|
+
test(
|
|
9
|
+
'resolveExtendedObjectSchemas unwraps result from mergeExtendObjSchema',
|
|
10
|
+
async () => {
|
|
11
|
+
const mockServiceSchemas = {
|
|
12
|
+
mergeExtendObjSchema: async () => {
|
|
13
|
+
return {
|
|
14
|
+
errorsFound: [],
|
|
15
|
+
result: {
|
|
16
|
+
objectType: 'unitStandard',
|
|
17
|
+
generatedBy: 'user',
|
|
18
|
+
storageResources: {
|
|
19
|
+
dynamo1: {
|
|
20
|
+
tableName: 'UnitStandards',
|
|
21
|
+
storageType: 'dynamo'
|
|
22
|
+
},
|
|
23
|
+
childDynamo: {
|
|
24
|
+
tableName: 'ChildTable',
|
|
25
|
+
storageType: 'dynamo'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const inputSchemas = [
|
|
34
|
+
{
|
|
35
|
+
objectType: 'unitStandard',
|
|
36
|
+
generatedBy: 'user',
|
|
37
|
+
extendObjType: {
|
|
38
|
+
serviceTag: 'CoreService',
|
|
39
|
+
objectType: 'standard'
|
|
40
|
+
},
|
|
41
|
+
storageResources: {
|
|
42
|
+
childDynamo: {
|
|
43
|
+
tableName: 'ChildTable',
|
|
44
|
+
storageType: 'dynamo'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const resolved = await resolveExtendedObjectSchemas(
|
|
51
|
+
inputSchemas,
|
|
52
|
+
'bucket',
|
|
53
|
+
{ getObjectSchema: mockServiceSchemas }
|
|
54
|
+
);
|
|
55
|
+
assert.equal(resolved.length, 1);
|
|
56
|
+
const resolvedObject = resolved[0];
|
|
57
|
+
assert.equal(resolvedObject.objectType, 'unitStandard');
|
|
58
|
+
assert.equal(resolvedObject.result, undefined);
|
|
59
|
+
assert.equal(resolvedObject.errorsFound, undefined);
|
|
60
|
+
assert.ok(resolvedObject.storageResources);
|
|
61
|
+
assert.ok(resolvedObject.storageResources.childDynamo);
|
|
62
|
+
assert.ok(resolvedObject.externalDynamoResources);
|
|
63
|
+
assert.equal(resolvedObject.externalDynamoResources.length, 1);
|
|
64
|
+
assert.equal(
|
|
65
|
+
resolvedObject.externalDynamoResources[0].tableName,
|
|
66
|
+
'UnitStandards'
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
test(
|
|
72
|
+
'resolveExtendedObjectSchemas throws if merge returns errorsFound',
|
|
73
|
+
async () => {
|
|
74
|
+
const mockServiceSchemas = {
|
|
75
|
+
mergeExtendObjSchema: async () => {
|
|
76
|
+
return {
|
|
77
|
+
errorsFound: ['Parent schema not found in S3'],
|
|
78
|
+
result: null
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const inputSchemas = [
|
|
84
|
+
{
|
|
85
|
+
objectType: 'unitStandard',
|
|
86
|
+
extendObjType: {
|
|
87
|
+
serviceTag: 'CoreService',
|
|
88
|
+
objectType: 'missing'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
await assert.rejects(
|
|
94
|
+
resolveExtendedObjectSchemas(inputSchemas, 'bucket', {
|
|
95
|
+
getObjectSchema: mockServiceSchemas
|
|
96
|
+
}),
|
|
97
|
+
/Parent schema not found in S3/
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
test(
|
|
103
|
+
'resolveExtendedObjectSchemas unwraps belongToSchema from getObjSchemaS3',
|
|
104
|
+
async () => {
|
|
105
|
+
const mockServiceSchemas = {
|
|
106
|
+
getObjSchemaS3: async () => {
|
|
107
|
+
return {
|
|
108
|
+
errorsFound: [],
|
|
109
|
+
result: {
|
|
110
|
+
objectType: 'organization',
|
|
111
|
+
generatedBy: 'user'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const inputSchemas = [
|
|
118
|
+
{
|
|
119
|
+
objectType: 'member',
|
|
120
|
+
belongTo: {
|
|
121
|
+
serviceTag: 'OrgService',
|
|
122
|
+
objectType: 'organization'
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const resolved = await resolveExtendedObjectSchemas(
|
|
128
|
+
inputSchemas,
|
|
129
|
+
'bucket',
|
|
130
|
+
{ getObjectSchema: mockServiceSchemas }
|
|
131
|
+
);
|
|
132
|
+
assert.equal(resolved.length, 1);
|
|
133
|
+
assert.equal(resolved[0].belongToSchema?.objectType, 'organization');
|
|
134
|
+
assert.equal(resolved[0].belongToSchema?.result, undefined);
|
|
135
|
+
}
|
|
136
|
+
);
|