@contrail/flexplm 1.7.8-alpha.48b253c → 1.7.8-alpha.6f9b7d7
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.
|
@@ -348,6 +348,9 @@ class BaseProcessPublishAssortment {
|
|
|
348
348
|
const apcDeletes = await this.downloadDeleteChanges(assortmentPublishChange);
|
|
349
349
|
let previousApcDate = Date.parse(apcCreatedOn);
|
|
350
350
|
const sinceDateMilliseconds = sinceDate.getTime();
|
|
351
|
+
// sinceDate matches a publish timestamp when it is the default (previous publish) or 'numberOfPublishes:N'.
|
|
352
|
+
// A date-only sinceDate (e.g. 2026-10-08) matches none, so the walk below must stop on the date instead.
|
|
353
|
+
const hasPublishAtSinceDate = apcHistory.some(apc => Date.parse(apc?.createdOn) === sinceDateMilliseconds);
|
|
351
354
|
console.info('getDeleteChanges(): apc: ' + assortmentPublishChange?.id
|
|
352
355
|
+ ', index: ' + currentAPCIndex + ', previousApcCreatedOn: ' + apcCreatedOn);
|
|
353
356
|
//if only 1 apc, no processing needed
|
|
@@ -361,7 +364,11 @@ class BaseProcessPublishAssortment {
|
|
|
361
364
|
for (let i = currentAPCIndex - 1; i > 0; i--) {
|
|
362
365
|
const workingAPC = apcHistory[i];
|
|
363
366
|
previousApcDate = Date.parse(workingAPC?.createdOn);
|
|
364
|
-
|
|
367
|
+
const reachedSinceDate = hasPublishAtSinceDate
|
|
368
|
+
? previousApcDate === sinceDateMilliseconds
|
|
369
|
+
: previousApcDate < sinceDateMilliseconds;
|
|
370
|
+
if (reachedSinceDate) {
|
|
371
|
+
console.info('getDeleteChanges(): reached sinceDate at apc: ' + workingAPC?.id);
|
|
365
372
|
break;
|
|
366
373
|
}
|
|
367
374
|
if (workingAPC?.deletes > 0) {
|
|
@@ -922,6 +922,60 @@ describe('getDeleteChanges', () => {
|
|
|
922
922
|
spy.mockRestore();
|
|
923
923
|
});
|
|
924
924
|
});
|
|
925
|
+
describe('getDeleteChanges - how far back the history walk goes', () => {
|
|
926
|
+
const config = {};
|
|
927
|
+
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
928
|
+
const dc = new data_converter_1.DataConverter(config, mapFileUtil);
|
|
929
|
+
// Oldest -> newest. 'current' is the publish being processed.
|
|
930
|
+
const history = [
|
|
931
|
+
{ id: 'apc0', createdOn: '2025-09-30T00:00:00.000Z', deletes: 5 },
|
|
932
|
+
{ id: 'apc1', createdOn: '2026-09-28T05:48:26.357Z', deletes: 1 },
|
|
933
|
+
{ id: 'apc2', createdOn: '2026-10-08T07:16:43.363Z', deletes: 2 },
|
|
934
|
+
{ id: 'apc3', createdOn: '2026-10-09T02:37:30.292Z', deletes: 3 },
|
|
935
|
+
{ id: 'apc4', createdOn: '2026-10-14T04:23:10.416Z', deletes: 0 },
|
|
936
|
+
{ id: 'current', createdOn: '2026-10-16T15:25:51.965Z', deletes: 0 }
|
|
937
|
+
];
|
|
938
|
+
const current = history[history.length - 1];
|
|
939
|
+
const baseline = { assortmentItems: [{ itemId: 'stillInPlan' }] };
|
|
940
|
+
let ppa;
|
|
941
|
+
let buildSpy;
|
|
942
|
+
beforeEach(() => {
|
|
943
|
+
ppa = new base_process_publish_assortment_1.BaseProcessPublishAssortment(config, dc, mapFileUtil);
|
|
944
|
+
jest.spyOn(ppa, 'downloadDeleteChanges').mockResolvedValue([]);
|
|
945
|
+
buildSpy = jest.spyOn(ppa, 'buildDeleteChanges')
|
|
946
|
+
.mockImplementation(async (apc) => [{ itemId: 'deleted-in-' + apc['id'] }]);
|
|
947
|
+
});
|
|
948
|
+
afterEach(() => {
|
|
949
|
+
jest.restoreAllMocks();
|
|
950
|
+
});
|
|
951
|
+
const walkedApcIds = () => buildSpy.mock.calls.map(call => call[0]['id']);
|
|
952
|
+
it('does not walk older publishes when sinceDate is the previous publish date (default)', async () => {
|
|
953
|
+
const result = await ppa.getDeleteChanges(current, history, baseline, new Date(history[4].createdOn));
|
|
954
|
+
expect(walkedApcIds()).toEqual([]);
|
|
955
|
+
expect(result).toEqual([]);
|
|
956
|
+
});
|
|
957
|
+
it('stops at the first publish before a date-only sinceDate that matches no publish timestamp', async () => {
|
|
958
|
+
const result = await ppa.getDeleteChanges(current, history, baseline, new Date('2026-10-08T00:00:00.000Z'));
|
|
959
|
+
// apc3 and apc2 are after the cutoff; apc1 (Sep 28) is before it, so the walk ends there and apc0 is never read.
|
|
960
|
+
expect(walkedApcIds()).toEqual(['apc3', 'apc2']);
|
|
961
|
+
expect(result.map(item => item['itemId'])).toEqual(['deleted-in-apc3', 'deleted-in-apc2']);
|
|
962
|
+
});
|
|
963
|
+
it('stops at a publish whose timestamp equals sinceDate without reading it (numberOfPublishes)', async () => {
|
|
964
|
+
await ppa.getDeleteChanges(current, history, baseline, new Date(history[1].createdOn));
|
|
965
|
+
expect(walkedApcIds()).toEqual(['apc3', 'apc2']);
|
|
966
|
+
});
|
|
967
|
+
it('does not walk any publish when a date-only sinceDate is later than the previous publish', async () => {
|
|
968
|
+
await ppa.getDeleteChanges(current, history, baseline, new Date('2026-10-15T00:00:00.000Z'));
|
|
969
|
+
expect(walkedApcIds()).toEqual([]);
|
|
970
|
+
});
|
|
971
|
+
it('skips deleted items still in the plan and de-duplicates across publishes', async () => {
|
|
972
|
+
buildSpy.mockImplementation(async (apc) => apc['id'] === 'apc3'
|
|
973
|
+
? [{ itemId: 'stillInPlan' }, { itemId: 'sameItem' }]
|
|
974
|
+
: [{ itemId: 'sameItem' }, { itemId: 'deleted-in-' + apc['id'] }]);
|
|
975
|
+
const result = await ppa.getDeleteChanges(current, history, baseline, new Date('2026-10-08T00:00:00.000Z'));
|
|
976
|
+
expect(result.map(item => item['itemId'])).toEqual(['sameItem', 'deleted-in-apc2']);
|
|
977
|
+
});
|
|
978
|
+
});
|
|
925
979
|
describe('buildDeleteChanges', () => {
|
|
926
980
|
const config = {};
|
|
927
981
|
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
package/lib/util/type-utils.js
CHANGED
|
@@ -14,6 +14,11 @@ class TypeUtils {
|
|
|
14
14
|
id,
|
|
15
15
|
relations: ['typeProperties', 'typeInterfaces']
|
|
16
16
|
});
|
|
17
|
+
// The API answers 200 with an empty body for a missing/deleted type, which the SDK returns as null.
|
|
18
|
+
if (!type) {
|
|
19
|
+
throw new Error('getTypeById: type not found for typeId: ' + id
|
|
20
|
+
+ '. The entity references a type that no longer exists.');
|
|
21
|
+
}
|
|
17
22
|
return type;
|
|
18
23
|
}
|
|
19
24
|
async getByRootAndPath(options) {
|
|
@@ -190,3 +190,16 @@ describe('filterProperties-color', () => {
|
|
|
190
190
|
expect(filteredProps.length).toEqual(len);
|
|
191
191
|
});
|
|
192
192
|
});
|
|
193
|
+
describe('TypeUtils.getTypeById() tests', () => {
|
|
194
|
+
it('should throw an error containing the missing type id when the type does not exist', async () => {
|
|
195
|
+
const typeUtils = new type_utils_1.TypeUtils();
|
|
196
|
+
typeUtils['typesObj'] = { getType: jest.fn().mockResolvedValue(null) };
|
|
197
|
+
await expect(typeUtils.getTypeById('missingTypeId')).rejects.toThrow('type not found for typeId: missingTypeId');
|
|
198
|
+
});
|
|
199
|
+
it('should return the same type object when the type exists', async () => {
|
|
200
|
+
const typeUtils = new type_utils_1.TypeUtils();
|
|
201
|
+
const type = { id: 'typeId', typeProperties: [] };
|
|
202
|
+
typeUtils['typesObj'] = { getType: jest.fn().mockResolvedValue(type) };
|
|
203
|
+
expect(await typeUtils.getTypeById('typeId')).toBe(type);
|
|
204
|
+
});
|
|
205
|
+
});
|