@contrail/flexplm 1.2.1 → 1.3.0-alpha.3
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/.claude/settings.local.json +1 -2
- package/.github/pull_request_template.md +31 -31
- package/.github/workflows/flexplm-lib.yml +27 -27
- package/lib/entity-processor/base-entity-processor.js +16 -2
- package/lib/entity-processor/base-entity-processor.spec.js +124 -0
- package/lib/util/flexplm-connect.d.ts +5 -1
- package/lib/util/flexplm-connect.js +8 -3
- package/lib/util/flexplm-connect.spec.d.ts +1 -0
- package/lib/util/flexplm-connect.spec.js +88 -0
- package/lib/util/thumbnail-util.d.ts +9 -0
- package/lib/util/thumbnail-util.js +88 -0
- package/lib/util/thumbnail-util.spec.js +156 -0
- package/lib/util/type-conversion-utils-spec-mockData.js +18 -0
- package/lib/util/type-conversion-utils.d.ts +2 -0
- package/lib/util/type-conversion-utils.js +43 -0
- package/lib/util/type-conversion-utils.spec.js +160 -0
- package/package.json +1 -1
- package/publish.bat +4 -4
- package/publish.sh +4 -4
- package/src/entity-processor/base-entity-processor.spec.ts +157 -0
- package/src/entity-processor/base-entity-processor.ts +21 -2
- package/src/flexplm-request.ts +28 -28
- package/src/flexplm-utils.spec.ts +27 -27
- package/src/flexplm-utils.ts +29 -29
- package/src/index.ts +21 -21
- package/src/interfaces/item-family-changes.ts +66 -66
- package/src/interfaces/publish-change-data.ts +42 -42
- package/src/publish/base-process-publish-assortment-callback.ts +50 -50
- package/src/transform/identifier-conversion-spec-mockData.ts +495 -495
- package/src/transform/identifier-conversion.spec.ts +353 -353
- package/src/transform/identifier-conversion.ts +281 -281
- package/src/util/config-defaults.spec.ts +350 -350
- package/src/util/config-defaults.ts +92 -92
- package/src/util/data-converter-spec-mockData.ts +230 -230
- package/src/util/error-response-object.spec.ts +115 -115
- package/src/util/error-response-object.ts +49 -49
- package/src/util/federation.ts +172 -172
- package/src/util/flexplm-connect.spec.ts +132 -0
- package/src/util/flexplm-connect.ts +14 -5
- package/src/util/logger-config.ts +19 -19
- package/src/util/map-util-spec-mockData.ts +230 -230
- package/src/util/map-utils.spec.ts +102 -102
- package/src/util/map-utils.ts +40 -40
- package/src/util/mockData.ts +97 -97
- package/src/util/thumbnail-util.spec.ts +190 -0
- package/src/util/thumbnail-util.ts +109 -1
- package/src/util/type-conversion-utils-spec-mockData.ts +18 -0
- package/src/util/type-conversion-utils.spec.ts +184 -0
- package/src/util/type-conversion-utils.ts +75 -0
- package/src/util/type-defaults.spec.ts +668 -668
- package/src/util/type-defaults.ts +280 -280
- package/src/util/type-utils.spec.ts +227 -227
- package/src/util/type-utils.ts +144 -144
- package/tsconfig.json +28 -26
- package/tslint.json +57 -57
- package/scripts/output.png +0 -0
- package/scripts/test-get-request.ts +0 -35
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const thumbnail_util_1 = require("./thumbnail-util");
|
|
4
4
|
const mockData_1 = require("./mockData");
|
|
5
|
+
const mockEntitiesGet = jest.fn();
|
|
6
|
+
const mockEntitiesUpdate = jest.fn();
|
|
7
|
+
const mockEntitiesDelete = jest.fn();
|
|
8
|
+
const mockContentCreate = jest.fn();
|
|
9
|
+
jest.mock('@contrail/sdk', () => {
|
|
10
|
+
return {
|
|
11
|
+
Entities: jest.fn().mockImplementation(() => ({
|
|
12
|
+
get: mockEntitiesGet,
|
|
13
|
+
update: mockEntitiesUpdate,
|
|
14
|
+
delete: mockEntitiesDelete,
|
|
15
|
+
})),
|
|
16
|
+
Content: jest.fn().mockImplementation(() => ({
|
|
17
|
+
create: mockContentCreate,
|
|
18
|
+
})),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
const mockGetRequest = jest.fn();
|
|
22
|
+
jest.mock('./flexplm-connect', () => {
|
|
23
|
+
return {
|
|
24
|
+
FlexPLMConnect: jest.fn().mockImplementation(() => ({
|
|
25
|
+
getRequest: mockGetRequest,
|
|
26
|
+
})),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
5
29
|
describe('ThumbnailUtil Tests', () => {
|
|
6
30
|
const config = {};
|
|
7
31
|
describe('setOutboundThumbnail()', () => {
|
|
@@ -239,4 +263,136 @@ describe('ThumbnailUtil Tests', () => {
|
|
|
239
263
|
expect(content.primaryFile.id).toEqual('file123');
|
|
240
264
|
});
|
|
241
265
|
});
|
|
266
|
+
describe('syncThumbnailToVibeIQ', () => {
|
|
267
|
+
let tu;
|
|
268
|
+
beforeEach(() => {
|
|
269
|
+
jest.clearAllMocks();
|
|
270
|
+
tu = new thumbnail_util_1.ThumbnailUtil(config);
|
|
271
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
272
|
+
if (opts.entityName === 'content-custom-size')
|
|
273
|
+
return Promise.resolve([]);
|
|
274
|
+
return Promise.resolve({});
|
|
275
|
+
});
|
|
276
|
+
mockEntitiesUpdate.mockImplementation((opts) => Promise.resolve({ id: opts.id }));
|
|
277
|
+
mockEntitiesDelete.mockImplementation((opts) => Promise.resolve({ id: opts.id }));
|
|
278
|
+
});
|
|
279
|
+
it('returns undefined when no thumbnail IDs in event data', async () => {
|
|
280
|
+
const event = { data: {} };
|
|
281
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
282
|
+
expect(result).toBeUndefined();
|
|
283
|
+
expect(mockEntitiesUpdate).not.toHaveBeenCalled();
|
|
284
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
285
|
+
expect(mockContentCreate).not.toHaveBeenCalled();
|
|
286
|
+
});
|
|
287
|
+
it('returns undefined when event.data is undefined', async () => {
|
|
288
|
+
const event = {};
|
|
289
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
290
|
+
expect(result).toBeUndefined();
|
|
291
|
+
expect(mockEntitiesUpdate).not.toHaveBeenCalled();
|
|
292
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
293
|
+
});
|
|
294
|
+
it('REMOVE_THUMBNAIL with existing primaryViewableId deletes content and returns clear updates', async () => {
|
|
295
|
+
const event = { data: { [thumbnail_util_1.ThumbnailUtil.NEW_THUMBNAIL_ID]: thumbnail_util_1.ThumbnailUtil.REMOVE_THUMBNAIL } };
|
|
296
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', primaryViewableId: 'pv1', event, entityName: 'color' });
|
|
297
|
+
expect(mockEntitiesDelete).toHaveBeenCalledWith({ entityName: 'content', id: 'pv1' });
|
|
298
|
+
expect(result).toEqual(expect.objectContaining({ primaryViewableId: null, primaryFileUrl: null }));
|
|
299
|
+
});
|
|
300
|
+
it('REMOVE_THUMBNAIL with no primaryViewableId returns clear updates without deleting', async () => {
|
|
301
|
+
const event = { data: { [thumbnail_util_1.ThumbnailUtil.NEW_THUMBNAIL_ID]: thumbnail_util_1.ThumbnailUtil.REMOVE_THUMBNAIL } };
|
|
302
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'item' });
|
|
303
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
304
|
+
expect(result).toEqual(expect.objectContaining({ primaryViewableId: null }));
|
|
305
|
+
});
|
|
306
|
+
it('creates new content when no primaryViewableId exists and returns primary viewable updates', async () => {
|
|
307
|
+
const mockResponse = {
|
|
308
|
+
arrayBuffer: jest.fn().mockResolvedValue(new ArrayBuffer(8)),
|
|
309
|
+
headers: { get: jest.fn().mockReturnValue('image/png') },
|
|
310
|
+
};
|
|
311
|
+
mockGetRequest.mockResolvedValue(mockResponse);
|
|
312
|
+
const createdContent = {
|
|
313
|
+
id: 'newContent1',
|
|
314
|
+
contentType: 'image/png',
|
|
315
|
+
fileName: 'thumb.png',
|
|
316
|
+
primaryFileUrl: 'https://files/primary.png',
|
|
317
|
+
largeViewableUrl: 'https://files/large.png',
|
|
318
|
+
mediumLargeViewableUrl: null,
|
|
319
|
+
mediumViewableUrl: null,
|
|
320
|
+
smallViewableUrl: null,
|
|
321
|
+
tinyViewableUrl: null,
|
|
322
|
+
};
|
|
323
|
+
mockContentCreate.mockResolvedValue(createdContent);
|
|
324
|
+
const event = { data: { [thumbnail_util_1.ThumbnailUtil.NEW_THUMBNAIL_ID]: '/rest/thumbnail/thumb.png' } };
|
|
325
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', event, entityName: 'color' });
|
|
326
|
+
expect(mockGetRequest).toHaveBeenCalledWith({ urlPath: '/rest/thumbnail/thumb.png', includeUrlContext: false, returnFullResponse: true });
|
|
327
|
+
expect(mockContentCreate).toHaveBeenCalledWith(expect.objectContaining({
|
|
328
|
+
fileName: 'thumb.png',
|
|
329
|
+
contentType: 'image/png',
|
|
330
|
+
contentHolderReference: 'color:entity1',
|
|
331
|
+
}));
|
|
332
|
+
expect(mockEntitiesUpdate).toHaveBeenCalledWith(expect.objectContaining({
|
|
333
|
+
entityName: 'content',
|
|
334
|
+
id: 'newContent1',
|
|
335
|
+
object: { flexplmThumbnailUrl: '/rest/thumbnail/thumb.png' },
|
|
336
|
+
}));
|
|
337
|
+
expect(result).toEqual(expect.objectContaining({
|
|
338
|
+
primaryViewableId: 'newContent1',
|
|
339
|
+
primaryFileUrl: 'https://files/primary.png',
|
|
340
|
+
largeViewableDownloadUrl: 'https://files/large.png',
|
|
341
|
+
}));
|
|
342
|
+
});
|
|
343
|
+
it('replaces content when primaryViewable.flexplmThumbnailUrl differs and returns updates', async () => {
|
|
344
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
345
|
+
if (opts.entityName === 'content-custom-size')
|
|
346
|
+
return Promise.resolve([]);
|
|
347
|
+
if (opts.entityName === 'content' && opts.id === 'oldPv') {
|
|
348
|
+
return Promise.resolve({ id: 'oldPv', flexplmThumbnailUrl: '/rest/thumbnail/old.png' });
|
|
349
|
+
}
|
|
350
|
+
return Promise.resolve({});
|
|
351
|
+
});
|
|
352
|
+
const mockResponse = {
|
|
353
|
+
arrayBuffer: jest.fn().mockResolvedValue(new ArrayBuffer(8)),
|
|
354
|
+
headers: { get: jest.fn().mockReturnValue('image/jpeg') },
|
|
355
|
+
};
|
|
356
|
+
mockGetRequest.mockResolvedValue(mockResponse);
|
|
357
|
+
const createdContent = {
|
|
358
|
+
id: 'newContent2',
|
|
359
|
+
contentType: 'image/jpeg',
|
|
360
|
+
fileName: 'new.jpg',
|
|
361
|
+
primaryFileUrl: 'https://files/new-primary.jpg',
|
|
362
|
+
largeViewableUrl: null,
|
|
363
|
+
mediumLargeViewableUrl: null,
|
|
364
|
+
mediumViewableUrl: null,
|
|
365
|
+
smallViewableUrl: null,
|
|
366
|
+
tinyViewableUrl: null,
|
|
367
|
+
};
|
|
368
|
+
mockContentCreate.mockResolvedValue(createdContent);
|
|
369
|
+
const event = { data: { [thumbnail_util_1.ThumbnailUtil.NEW_THUMBNAIL_ID]: '/rest/thumbnail/new.jpg' } };
|
|
370
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', primaryViewableId: 'oldPv', event, entityName: 'item' });
|
|
371
|
+
expect(mockContentCreate).toHaveBeenCalled();
|
|
372
|
+
expect(mockEntitiesUpdate).toHaveBeenCalledWith(expect.objectContaining({
|
|
373
|
+
entityName: 'content',
|
|
374
|
+
id: 'newContent2',
|
|
375
|
+
object: { flexplmThumbnailUrl: '/rest/thumbnail/new.jpg' },
|
|
376
|
+
}));
|
|
377
|
+
expect(result).toEqual(expect.objectContaining({ primaryViewableId: 'newContent2' }));
|
|
378
|
+
expect(mockEntitiesDelete).toHaveBeenCalledWith({ entityName: 'content', id: 'oldPv' });
|
|
379
|
+
});
|
|
380
|
+
it('returns undefined when primaryViewable.flexplmThumbnailUrl matches', async () => {
|
|
381
|
+
const thumbnailUrl = '/rest/thumbnail/same.png';
|
|
382
|
+
mockEntitiesGet.mockImplementation((opts) => {
|
|
383
|
+
if (opts.entityName === 'content-custom-size')
|
|
384
|
+
return Promise.resolve([]);
|
|
385
|
+
if (opts.entityName === 'content' && opts.id === 'pv1') {
|
|
386
|
+
return Promise.resolve({ id: 'pv1', flexplmThumbnailUrl: thumbnailUrl });
|
|
387
|
+
}
|
|
388
|
+
return Promise.resolve({});
|
|
389
|
+
});
|
|
390
|
+
const event = { data: { [thumbnail_util_1.ThumbnailUtil.EXISTING_THUMBNAIL_ID]: thumbnailUrl } };
|
|
391
|
+
const result = await tu.syncThumbnailToVibeIQ({ entityId: 'entity1', primaryViewableId: 'pv1', event, entityName: 'color' });
|
|
392
|
+
expect(result).toBeUndefined();
|
|
393
|
+
expect(mockContentCreate).not.toHaveBeenCalled();
|
|
394
|
+
expect(mockEntitiesUpdate).not.toHaveBeenCalled();
|
|
395
|
+
expect(mockEntitiesDelete).not.toHaveBeenCalled();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
242
398
|
});
|
|
@@ -137,6 +137,12 @@ exports.mapping = {
|
|
|
137
137
|
isOutboundCreatable: (entity, context) => {
|
|
138
138
|
return false;
|
|
139
139
|
},
|
|
140
|
+
syncInboundImages: (entity, context) => {
|
|
141
|
+
return true;
|
|
142
|
+
},
|
|
143
|
+
syncOutboundImages: (entity, context) => {
|
|
144
|
+
return false;
|
|
145
|
+
},
|
|
140
146
|
vibe2flex: {
|
|
141
147
|
transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
|
|
142
148
|
rekey: {
|
|
@@ -169,6 +175,18 @@ exports.mapping = {
|
|
|
169
175
|
}
|
|
170
176
|
return true;
|
|
171
177
|
},
|
|
178
|
+
syncInboundImages: (entity, context) => {
|
|
179
|
+
if (context && context.skipImages) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
},
|
|
184
|
+
syncOutboundImages: (entity, context) => {
|
|
185
|
+
if (context && context.skipImages) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
},
|
|
172
190
|
vibe2flex: {
|
|
173
191
|
transformOrder: [{ processor: 'REKEY', rekeyDelete: true, rekeyTransformersKey: 'rekey' }],
|
|
174
192
|
rekey: {
|
|
@@ -17,5 +17,7 @@ export declare class TypeConversionUtils {
|
|
|
17
17
|
static getMapKeyFromObject(fileId: any, mapFileUtil: MapFileUtil, object: any, direction: string): Promise<string>;
|
|
18
18
|
static isInboundCreatableFromObject(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean>;
|
|
19
19
|
static isOutboundCreatableFromEntity(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean>;
|
|
20
|
+
static syncInboundImages(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean>;
|
|
21
|
+
static syncOutboundImages(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean>;
|
|
20
22
|
static getObjectType(object: any): any;
|
|
21
23
|
}
|
|
@@ -179,6 +179,7 @@ class TypeConversionUtils {
|
|
|
179
179
|
}
|
|
180
180
|
return type;
|
|
181
181
|
}
|
|
182
|
+
return '';
|
|
182
183
|
}
|
|
183
184
|
static async isInboundCreatableFromObject(fileId, mapFileUtil, object, context) {
|
|
184
185
|
let isInboundCreatable = false;
|
|
@@ -212,6 +213,48 @@ class TypeConversionUtils {
|
|
|
212
213
|
}
|
|
213
214
|
return isOutboundCreatable;
|
|
214
215
|
}
|
|
216
|
+
static async syncInboundImages(fileId, mapFileUtil, object, context) {
|
|
217
|
+
let syncImages = false;
|
|
218
|
+
if (!fileId) {
|
|
219
|
+
return syncImages;
|
|
220
|
+
}
|
|
221
|
+
let mapKey;
|
|
222
|
+
try {
|
|
223
|
+
mapKey = await this.getMapKeyFromObject(fileId, mapFileUtil, object, TypeConversionUtils.FLEX2VIBE_DIRECTION);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return syncImages;
|
|
227
|
+
}
|
|
228
|
+
if (!mapKey) {
|
|
229
|
+
return syncImages;
|
|
230
|
+
}
|
|
231
|
+
const mapData = await map_utils_1.MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
|
|
232
|
+
if (mapData && mapData['syncInboundImages']) {
|
|
233
|
+
syncImages = await mapData['syncInboundImages'](object, context);
|
|
234
|
+
}
|
|
235
|
+
return syncImages;
|
|
236
|
+
}
|
|
237
|
+
static async syncOutboundImages(fileId, mapFileUtil, entity, context) {
|
|
238
|
+
let syncImages = true;
|
|
239
|
+
if (!fileId) {
|
|
240
|
+
return syncImages;
|
|
241
|
+
}
|
|
242
|
+
let mapKey;
|
|
243
|
+
try {
|
|
244
|
+
mapKey = await this.getMapKey(fileId, mapFileUtil, entity, TypeConversionUtils.VIBE2FLEX_DIRECTION);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return syncImages;
|
|
248
|
+
}
|
|
249
|
+
if (!mapKey) {
|
|
250
|
+
return syncImages;
|
|
251
|
+
}
|
|
252
|
+
const mapData = await map_utils_1.MapUtil.getFullMapSection(fileId, mapFileUtil, mapKey);
|
|
253
|
+
if (mapData && mapData['syncOutboundImages']) {
|
|
254
|
+
syncImages = await mapData['syncOutboundImages'](entity, context);
|
|
255
|
+
}
|
|
256
|
+
return syncImages;
|
|
257
|
+
}
|
|
215
258
|
static getObjectType(object) {
|
|
216
259
|
let objectType = object['flexPLMObjectClass'];
|
|
217
260
|
return objectType;
|
|
@@ -705,4 +705,164 @@ describe('conversion-utils', () => {
|
|
|
705
705
|
}
|
|
706
706
|
});
|
|
707
707
|
});
|
|
708
|
+
describe('syncInboundImages', () => {
|
|
709
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
710
|
+
it('should return true for Revisable Entity\\packaging (mapping entry true)', async () => {
|
|
711
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
712
|
+
.mockImplementation(async () => {
|
|
713
|
+
return mapping;
|
|
714
|
+
});
|
|
715
|
+
const obj = {
|
|
716
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
717
|
+
flexPLMTypePath: 'Revisable Entity\\packaging'
|
|
718
|
+
};
|
|
719
|
+
try {
|
|
720
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
721
|
+
expect(results).toBeTruthy();
|
|
722
|
+
}
|
|
723
|
+
finally {
|
|
724
|
+
spy.mockRestore();
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
it('should return true for Revisable Entity\\prefix (mapping entry true)', async () => {
|
|
728
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
729
|
+
.mockImplementation(async () => {
|
|
730
|
+
return mapping;
|
|
731
|
+
});
|
|
732
|
+
const obj = {
|
|
733
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
734
|
+
flexPLMTypePath: 'Revisable Entity\\prefix'
|
|
735
|
+
};
|
|
736
|
+
try {
|
|
737
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
738
|
+
expect(results).toBeTruthy();
|
|
739
|
+
}
|
|
740
|
+
finally {
|
|
741
|
+
spy.mockRestore();
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
it('should pass context to syncInboundImages function', async () => {
|
|
745
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
746
|
+
.mockImplementation(async () => {
|
|
747
|
+
return mapping;
|
|
748
|
+
});
|
|
749
|
+
const obj = {
|
|
750
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
751
|
+
flexPLMTypePath: 'Revisable Entity\\prefix'
|
|
752
|
+
};
|
|
753
|
+
const context = { skipImages: true };
|
|
754
|
+
try {
|
|
755
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj, context);
|
|
756
|
+
expect(results).toBeFalsy();
|
|
757
|
+
}
|
|
758
|
+
finally {
|
|
759
|
+
spy.mockRestore();
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
it('should default to false when no mapping exists', async () => {
|
|
763
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
764
|
+
.mockImplementation(async () => {
|
|
765
|
+
return mapping;
|
|
766
|
+
});
|
|
767
|
+
const obj = {
|
|
768
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
769
|
+
flexPLMTypePath: 'Revisable Entity\\catName'
|
|
770
|
+
};
|
|
771
|
+
try {
|
|
772
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages(TRANSFORM_MAP_FILE, mapFileUtil, obj);
|
|
773
|
+
expect(results).toBeFalsy();
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
spy.mockRestore();
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
it('should default to false when no fileId', async () => {
|
|
780
|
+
const obj = {
|
|
781
|
+
flexPLMObjectClass: 'LCSRevisableEntity',
|
|
782
|
+
flexPLMTypePath: 'Revisable Entity\\pack'
|
|
783
|
+
};
|
|
784
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncInboundImages('', mapFileUtil, obj);
|
|
785
|
+
expect(results).toBeFalsy();
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
describe('syncOutboundImages', () => {
|
|
789
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
790
|
+
it('should return false for custom-entity:pack (mapping entry false)', async () => {
|
|
791
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
792
|
+
.mockImplementation(async () => {
|
|
793
|
+
return mapping;
|
|
794
|
+
});
|
|
795
|
+
const entity = {
|
|
796
|
+
entityType: 'custom-entity',
|
|
797
|
+
typePath: 'custom-entity:pack'
|
|
798
|
+
};
|
|
799
|
+
try {
|
|
800
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity);
|
|
801
|
+
expect(results).toBeFalsy();
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
spy.mockRestore();
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
it('should return true for custom-entity:prefix (mapping entry true)', async () => {
|
|
808
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
809
|
+
.mockImplementation(async () => {
|
|
810
|
+
return mapping;
|
|
811
|
+
});
|
|
812
|
+
const entity = {
|
|
813
|
+
entityType: 'custom-entity',
|
|
814
|
+
typePath: 'custom-entity:prefix'
|
|
815
|
+
};
|
|
816
|
+
try {
|
|
817
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity);
|
|
818
|
+
expect(results).toBeTruthy();
|
|
819
|
+
}
|
|
820
|
+
finally {
|
|
821
|
+
spy.mockRestore();
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
it('should pass context to syncOutboundImages function', async () => {
|
|
825
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
826
|
+
.mockImplementation(async () => {
|
|
827
|
+
return mapping;
|
|
828
|
+
});
|
|
829
|
+
const entity = {
|
|
830
|
+
entityType: 'custom-entity',
|
|
831
|
+
typePath: 'custom-entity:prefix'
|
|
832
|
+
};
|
|
833
|
+
const context = { skipImages: true };
|
|
834
|
+
try {
|
|
835
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity, context);
|
|
836
|
+
expect(results).toBeFalsy();
|
|
837
|
+
}
|
|
838
|
+
finally {
|
|
839
|
+
spy.mockRestore();
|
|
840
|
+
}
|
|
841
|
+
});
|
|
842
|
+
it('should default to true when no mapping exists', async () => {
|
|
843
|
+
const spy = jest.spyOn(mapFileUtil, 'getMapFile')
|
|
844
|
+
.mockImplementation(async () => {
|
|
845
|
+
return mapping;
|
|
846
|
+
});
|
|
847
|
+
const entity = {
|
|
848
|
+
entityType: 'custom-entity',
|
|
849
|
+
typePath: 'custom-entity:catName'
|
|
850
|
+
};
|
|
851
|
+
try {
|
|
852
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncOutboundImages(TRANSFORM_MAP_FILE, mapFileUtil, entity);
|
|
853
|
+
expect(results).toBeTruthy();
|
|
854
|
+
}
|
|
855
|
+
finally {
|
|
856
|
+
spy.mockRestore();
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
it('should default to true when no fileId', async () => {
|
|
860
|
+
const entity = {
|
|
861
|
+
entityType: 'custom-entity',
|
|
862
|
+
typePath: 'custom-entity:pack'
|
|
863
|
+
};
|
|
864
|
+
const results = await type_conversion_utils_1.TypeConversionUtils.syncOutboundImages('', mapFileUtil, entity);
|
|
865
|
+
expect(results).toBeTruthy();
|
|
866
|
+
});
|
|
867
|
+
});
|
|
708
868
|
});
|
package/package.json
CHANGED
package/publish.bat
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
rd /s /q lib
|
|
2
|
-
call npm install
|
|
3
|
-
call npm run build
|
|
4
|
-
call npm version patch
|
|
1
|
+
rd /s /q lib
|
|
2
|
+
call npm install
|
|
3
|
+
call npm run build
|
|
4
|
+
call npm version patch
|
|
5
5
|
call npm publish
|
package/publish.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
rm -rf lib;
|
|
2
|
-
npm install;
|
|
3
|
-
npm run build;
|
|
4
|
-
npm version patch;
|
|
1
|
+
rm -rf lib;
|
|
2
|
+
npm install;
|
|
3
|
+
npm run build;
|
|
4
|
+
npm version patch;
|
|
5
5
|
npm publish;
|
|
@@ -3,6 +3,8 @@ import { MapFileUtil } from "@contrail/transform-data";
|
|
|
3
3
|
import { EntityPayloadType, FCConfig } from "../interfaces/interfaces";
|
|
4
4
|
import { DataConverter } from "../util/data-converter";
|
|
5
5
|
import { BaseEntityProcessor, IncomingEntityResponse } from "./base-entity-processor";
|
|
6
|
+
import { TypeConversionUtils } from "../util/type-conversion-utils";
|
|
7
|
+
import { ThumbnailUtil } from "../util/thumbnail-util";
|
|
6
8
|
|
|
7
9
|
const mockRootType = {
|
|
8
10
|
typeProperties: [
|
|
@@ -335,4 +337,159 @@ describe('BaseEntityProcessor', () =>{
|
|
|
335
337
|
|
|
336
338
|
});
|
|
337
339
|
|
|
340
|
+
describe('handleIncomingUpsert - inbound image sync', () =>{
|
|
341
|
+
const config = {} as FCConfig;
|
|
342
|
+
const mapFileUtil = new MapFileUtil(new Entities());
|
|
343
|
+
const dc = new DataConverter(config, mapFileUtil);
|
|
344
|
+
const mockEvent = { objectClass: 'TestClass', federatedId: 'fed-123', data: { name: 'test' }, entityReference: 'ref-1', eventType: 'PERSIST' } as EntityPayloadType;
|
|
345
|
+
const mockInboundData = { name: 'transformed-test' };
|
|
346
|
+
const mockCreatedEntity = { id: 'created-1', name: 'created', primaryViewableId: 'pv-created' };
|
|
347
|
+
const mockUpdatedEntity = { id: 'updated-1', name: 'updated', primaryViewableId: 'pv-updated' };
|
|
348
|
+
const mockExistingEntity = { id: 'existing-1', name: 'existing', typeId: 'type-1', roles: [] };
|
|
349
|
+
|
|
350
|
+
let btep: TestBaseEntityProcessor;
|
|
351
|
+
let syncInboundImagesSpy: jest.SpyInstance;
|
|
352
|
+
let syncThumbnailSpy: jest.SpyInstance;
|
|
353
|
+
|
|
354
|
+
beforeEach(() =>{
|
|
355
|
+
jest.clearAllMocks();
|
|
356
|
+
btep = new TestBaseEntityProcessor(config, dc, mapFileUtil, 'test');
|
|
357
|
+
jest.spyOn(btep, 'getTransformedData').mockResolvedValue(mockInboundData);
|
|
358
|
+
syncInboundImagesSpy = jest.spyOn(TypeConversionUtils, 'syncInboundImages');
|
|
359
|
+
syncThumbnailSpy = jest.spyOn(ThumbnailUtil.prototype, 'syncThumbnailToVibeIQ').mockResolvedValue(undefined);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should call updateEntity with thumbnail updates after create when syncInboundImages returns true', async () =>{
|
|
363
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: null });
|
|
364
|
+
jest.spyOn(btep, 'getCreateEntity' as any).mockResolvedValue({ entity: { name: 'new' } });
|
|
365
|
+
jest.spyOn(btep, 'createEntity').mockResolvedValue(mockCreatedEntity);
|
|
366
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockCreatedEntity);
|
|
367
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
368
|
+
const thumbnailUpdates = { primaryViewableId: 'pv-new', primaryFileUrl: 'https://files/new.png' };
|
|
369
|
+
syncThumbnailSpy.mockResolvedValue(thumbnailUpdates);
|
|
370
|
+
|
|
371
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
372
|
+
|
|
373
|
+
expect(syncThumbnailSpy).toBeCalledTimes(1);
|
|
374
|
+
expect(syncThumbnailSpy).toBeCalledWith({ entityId: 'created-1', primaryViewableId: 'pv-created', event: mockEvent, entityName: 'test' });
|
|
375
|
+
expect(updateEntitySpy).toBeCalledWith('test', mockCreatedEntity, thumbnailUpdates);
|
|
376
|
+
expect(result).toBe(mockCreatedEntity);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should not call updateEntity after create when syncThumbnailToVibeIQ returns undefined', async () =>{
|
|
380
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: null });
|
|
381
|
+
jest.spyOn(btep, 'getCreateEntity' as any).mockResolvedValue({ entity: { name: 'new' } });
|
|
382
|
+
jest.spyOn(btep, 'createEntity').mockResolvedValue(mockCreatedEntity);
|
|
383
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity');
|
|
384
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
385
|
+
syncThumbnailSpy.mockResolvedValue(undefined);
|
|
386
|
+
|
|
387
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
388
|
+
|
|
389
|
+
expect(syncThumbnailSpy).toBeCalledTimes(1);
|
|
390
|
+
expect(updateEntitySpy).not.toHaveBeenCalled();
|
|
391
|
+
expect(result).toBe(mockCreatedEntity);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should not sync images after create when syncInboundImages returns false', async () =>{
|
|
395
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: null });
|
|
396
|
+
jest.spyOn(btep, 'getCreateEntity' as any).mockResolvedValue({ entity: { name: 'new' } });
|
|
397
|
+
jest.spyOn(btep, 'createEntity').mockResolvedValue(mockCreatedEntity);
|
|
398
|
+
syncInboundImagesSpy.mockResolvedValue(false);
|
|
399
|
+
|
|
400
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
401
|
+
|
|
402
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(1);
|
|
403
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
404
|
+
expect(result).toBe(mockCreatedEntity);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should merge thumbnail updates with property diffs on update', async () =>{
|
|
408
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: mockExistingEntity });
|
|
409
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({ name: 'changed' });
|
|
410
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockUpdatedEntity);
|
|
411
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
412
|
+
const thumbnailUpdates = { primaryViewableId: 'pv-new' };
|
|
413
|
+
syncThumbnailSpy.mockResolvedValue(thumbnailUpdates);
|
|
414
|
+
|
|
415
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
416
|
+
|
|
417
|
+
expect(syncThumbnailSpy).toBeCalledTimes(1);
|
|
418
|
+
expect(syncThumbnailSpy).toBeCalledWith({ entityId: 'existing-1', primaryViewableId: undefined, event: mockEvent, entityName: 'test' });
|
|
419
|
+
expect(updateEntitySpy).toBeCalledWith('test', mockExistingEntity, { name: 'changed', primaryViewableId: 'pv-new' });
|
|
420
|
+
expect(result).toBe(mockUpdatedEntity);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should update entity with only thumbnail changes when no property diffs', async () =>{
|
|
424
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: mockExistingEntity });
|
|
425
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({});
|
|
426
|
+
const updateEntitySpy = jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockUpdatedEntity);
|
|
427
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
428
|
+
const thumbnailUpdates = { primaryViewableId: 'pv-new' };
|
|
429
|
+
syncThumbnailSpy.mockResolvedValue(thumbnailUpdates);
|
|
430
|
+
|
|
431
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
432
|
+
|
|
433
|
+
expect(updateEntitySpy).toBeCalledWith('test', mockExistingEntity, { primaryViewableId: 'pv-new' });
|
|
434
|
+
expect(result).toBe(mockUpdatedEntity);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('should not sync images on update when syncInboundImages returns false', async () =>{
|
|
438
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: mockExistingEntity });
|
|
439
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({ name: 'changed' });
|
|
440
|
+
jest.spyOn(btep, 'updateEntity').mockResolvedValue(mockUpdatedEntity);
|
|
441
|
+
syncInboundImagesSpy.mockResolvedValue(false);
|
|
442
|
+
|
|
443
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
444
|
+
|
|
445
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(1);
|
|
446
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
447
|
+
expect(result).toBe(mockUpdatedEntity);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should not sync images on early return from getIncomingEntity', async () =>{
|
|
451
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({
|
|
452
|
+
earlyReturn: { status: 400, data: { message: 'error' }, shortStatusMessage: 'FAIL' }
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
await btep.handleIncomingUpsert(mockEvent);
|
|
456
|
+
|
|
457
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(0);
|
|
458
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should not sync images on early return from getCreateEntity', async () =>{
|
|
462
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: null });
|
|
463
|
+
jest.spyOn(btep, 'getCreateEntity' as any).mockResolvedValue({
|
|
464
|
+
earlyReturn: { status: 400, data: { message: 'not creatable' }, shortStatusMessage: 'NOT_CREATABLE' }
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
await btep.handleIncomingUpsert(mockEvent);
|
|
468
|
+
|
|
469
|
+
expect(syncInboundImagesSpy).toBeCalledTimes(0);
|
|
470
|
+
expect(syncThumbnailSpy).toBeCalledTimes(0);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it('should return no changes when no property diffs and no thumbnail updates', async () =>{
|
|
474
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: mockExistingEntity });
|
|
475
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({});
|
|
476
|
+
syncInboundImagesSpy.mockResolvedValue(true);
|
|
477
|
+
syncThumbnailSpy.mockResolvedValue(undefined);
|
|
478
|
+
|
|
479
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
480
|
+
|
|
481
|
+
expect(result).toEqual({ status: 200, data: { message: 'No Changes to persist for entity: existing-1' } });
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should return no changes when syncInboundImages is false and no property diffs', async () =>{
|
|
485
|
+
jest.spyOn(btep, 'getIncomingEntity' as any).mockResolvedValue({ entity: mockExistingEntity });
|
|
486
|
+
jest.spyOn(btep, 'getUpdatesForEntity').mockResolvedValue({});
|
|
487
|
+
syncInboundImagesSpy.mockResolvedValue(false);
|
|
488
|
+
|
|
489
|
+
const result = await btep.handleIncomingUpsert(mockEvent);
|
|
490
|
+
|
|
491
|
+
expect(result).toEqual({ status: 200, data: { message: 'No Changes to persist for entity: existing-1' } });
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
|
|
338
495
|
});
|