@contrail/flexplm 1.7.4-alpha.b9acb47 → 1.7.4
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/lib/interfaces/item-family-changes.d.ts +0 -6
- package/lib/interfaces/item-family-changes.js +0 -8
- package/lib/publish/base-process-publish-assortment.d.ts +0 -3
- package/lib/publish/base-process-publish-assortment.js +2 -58
- package/lib/publish/base-process-publish-assortment.spec.js +0 -231
- package/lib/util/flexplm-connect.js +4 -2
- package/lib/util/flexplm-connect.spec.js +34 -0
- package/package.json +1 -1
|
@@ -12,12 +12,6 @@ export declare class ItemFamilyChanges {
|
|
|
12
12
|
colorDeletes: string[];
|
|
13
13
|
colorUpdates: string[];
|
|
14
14
|
colorUnchanged: string[];
|
|
15
|
-
/** Options that changed but are still pre-development. They do NOT produce
|
|
16
|
-
* LCSSKUSeasonLink events - they only signal that the family needs republishing. */
|
|
17
|
-
preDevelopmentOptionChanges: string[];
|
|
18
|
-
/** True when at least one option row was tracked at family level only, because the option is
|
|
19
|
-
* pre-development while the family itself is released. */
|
|
20
|
-
hasPreDevelopmentOnlyOptions: boolean;
|
|
21
15
|
assortmentItemFullChangeMap: Map<string, any>;
|
|
22
16
|
assortmentItemDeleteMap: Map<string, any>;
|
|
23
17
|
itemToFederatedIdMapping: Map<string, string>;
|
|
@@ -14,12 +14,6 @@ class ItemFamilyChanges {
|
|
|
14
14
|
this.colorDeletes = [];
|
|
15
15
|
this.colorUpdates = [];
|
|
16
16
|
this.colorUnchanged = [];
|
|
17
|
-
/** Options that changed but are still pre-development. They do NOT produce
|
|
18
|
-
* LCSSKUSeasonLink events - they only signal that the family needs republishing. */
|
|
19
|
-
this.preDevelopmentOptionChanges = [];
|
|
20
|
-
/** True when at least one option row was tracked at family level only, because the option is
|
|
21
|
-
* pre-development while the family itself is released. */
|
|
22
|
-
this.hasPreDevelopmentOnlyOptions = false;
|
|
23
17
|
this.assortmentItemFullChangeMap = new Map();
|
|
24
18
|
this.assortmentItemDeleteMap = new Map();
|
|
25
19
|
this.itemToFederatedIdMapping = new Map();
|
|
@@ -38,8 +32,6 @@ class ItemFamilyChanges {
|
|
|
38
32
|
s += ' colorDeletes: [' + this.colorDeletes + ']\n';
|
|
39
33
|
s += ' colorUpdates: [' + this.colorUpdates + ']\n';
|
|
40
34
|
s += ' colorUnchanged: [' + this.colorUnchanged + ']\n';
|
|
41
|
-
s += ' preDevelopmentOptionChanges: [' + this.preDevelopmentOptionChanges + ']\n';
|
|
42
|
-
s += ' hasPreDevelopmentOnlyOptions: ' + this.hasPreDevelopmentOnlyOptions + '\n';
|
|
43
35
|
s += 'assortmentItemFullChangeMap:\n';
|
|
44
36
|
s += 'size: ' + this.assortmentItemFullChangeMap.size + '\n[';
|
|
45
37
|
for (const key of this.assortmentItemFullChangeMap.keys()) {
|
|
@@ -62,9 +62,6 @@ export declare class BaseProcessPublishAssortment {
|
|
|
62
62
|
getFullChangeAssortmentMap(fullChange: any): Map<string, object>;
|
|
63
63
|
getDeleteChangesAssortmentMap(deleteChanges: any[]): Map<string, object>;
|
|
64
64
|
getReleasedForDevelopmentItemAndFamilyIds(fullChange: any, deleteChanges: any): string[];
|
|
65
|
-
/** Adds item families that pass meetsCriteria() but whose option rows are all pre-development.
|
|
66
|
-
* Only ever ADDS family ids - never option ids, and never removes anything. */
|
|
67
|
-
protected addReleasedFamiliesOfPreDevelopmentOptions(aItems: any[], releasedForDevelopmentItemIds: string[]): void;
|
|
68
65
|
meetsCriteria(aItem: any): boolean;
|
|
69
66
|
processPublish(pcd: PublishChangeData, changeDetail: any, fullChange: any, deleteChanges: any): Promise<{
|
|
70
67
|
results: {
|
|
@@ -527,34 +527,9 @@ class BaseProcessPublishAssortment {
|
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
529
|
}
|
|
530
|
-
this.addReleasedFamiliesOfPreDevelopmentOptions(assortmentItemsArray, releasedForDevelopmentItemIds);
|
|
531
|
-
this.addReleasedFamiliesOfPreDevelopmentOptions(deleteChanges, releasedForDevelopmentItemIds);
|
|
532
530
|
console.info('releasedForDevelopmentItemIds: ' + releasedForDevelopmentItemIds);
|
|
533
531
|
return releasedForDevelopmentItemIds;
|
|
534
532
|
}
|
|
535
|
-
/** Adds item families that pass meetsCriteria() but whose option rows are all pre-development.
|
|
536
|
-
* Only ever ADDS family ids - never option ids, and never removes anything. */
|
|
537
|
-
addReleasedFamiliesOfPreDevelopmentOptions(aItems, releasedForDevelopmentItemIds) {
|
|
538
|
-
for (const aItem of (aItems || [])) {
|
|
539
|
-
const item = aItem?.item;
|
|
540
|
-
const itemId = item?.id;
|
|
541
|
-
const itemFamilyId = item?.itemFamilyId;
|
|
542
|
-
if (!itemFamilyId || itemId === itemFamilyId) {
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
if (releasedForDevelopmentItemIds.includes(itemFamilyId)) {
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
if (this.meetsCriteria(aItem)) {
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
const familyItem = item?.itemFamily;
|
|
552
|
-
if (familyItem && this.meetsCriteria({ item: familyItem })) {
|
|
553
|
-
console.info('adding released family with pre-development options: ' + itemFamilyId);
|
|
554
|
-
releasedForDevelopmentItemIds.push(itemFamilyId);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
533
|
meetsCriteria(aItem) {
|
|
559
534
|
const item = aItem?.item;
|
|
560
535
|
const lifecycleStage = item?.lifecycleStage;
|
|
@@ -755,11 +730,7 @@ class BaseProcessPublishAssortment {
|
|
|
755
730
|
for (const [itemId, aItem] of assortmentItemFullChangeMap) {
|
|
756
731
|
const projectItem = aItem?.projectItem;
|
|
757
732
|
const itemFamilyId = aItem?.item?.itemFamilyId;
|
|
758
|
-
|
|
759
|
-
const itemInScope = pcd.releasedForDevelopmentItemIds.includes(itemId);
|
|
760
|
-
// Released family + pre-development option: track at family level only (no SKU events).
|
|
761
|
-
const familyOnly = !itemInScope && familyInScope && itemId !== itemFamilyId;
|
|
762
|
-
if (!familyInScope || (!itemInScope && !familyOnly)) {
|
|
733
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId) || !pcd.releasedForDevelopmentItemIds.includes(itemId)) {
|
|
763
734
|
continue;
|
|
764
735
|
}
|
|
765
736
|
const ifc = itemFamilyChanges.get(itemFamilyId) || new item_family_changes_1.ItemFamilyChanges(itemFamilyId, pcd.sinceDate);
|
|
@@ -767,29 +738,10 @@ class BaseProcessPublishAssortment {
|
|
|
767
738
|
ifc.itemFamilyObject = itemId === itemFamilyId ? aItem?.item : aItem?.item?.itemFamily;
|
|
768
739
|
itemFamilyChanges.set(itemFamilyId, ifc);
|
|
769
740
|
}
|
|
770
|
-
else if (itemId === itemFamilyId) {
|
|
771
|
-
// A pre-development option row may have created this bucket with the partial
|
|
772
|
-
// item.itemFamily; the family's own row carries the full item, so prefer it.
|
|
773
|
-
ifc.itemFamilyObject = aItem?.item;
|
|
774
|
-
}
|
|
775
741
|
ifc.assortmentItemFullChangeMap.set(itemId, aItem);
|
|
776
742
|
if (pcd.itemToFederatedIdMapping.has(itemId)) {
|
|
777
743
|
ifc.itemToFederatedIdMapping.set(itemId, pcd.itemToFederatedIdMapping.get(itemId));
|
|
778
744
|
}
|
|
779
|
-
if (familyOnly) {
|
|
780
|
-
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
781
|
-
if (addIds.includes(itemId) || deleteIds.includes(itemId) || updateIds.includes(itemId)
|
|
782
|
-
|| (projectItem && new Date(projectItem.updatedOn) > pcd.sinceDate)) {
|
|
783
|
-
ifc.preDevelopmentOptionChanges.push(itemId);
|
|
784
|
-
}
|
|
785
|
-
else {
|
|
786
|
-
ifc.colorUnchanged.push(itemId);
|
|
787
|
-
}
|
|
788
|
-
if (projectItem && !projectItem?.roles && aItem?.item?.roles) {
|
|
789
|
-
projectItem.roles = aItem?.item?.roles;
|
|
790
|
-
}
|
|
791
|
-
continue;
|
|
792
|
-
}
|
|
793
745
|
if (itemId === itemFamilyId) {
|
|
794
746
|
if (addIds.includes(itemId))
|
|
795
747
|
ifc.familyAdd = true;
|
|
@@ -828,10 +780,7 @@ class BaseProcessPublishAssortment {
|
|
|
828
780
|
for (const [itemId, delEntity] of assortmentItemDeleteMap) {
|
|
829
781
|
const itemFamilyId = delEntity?.item?.itemFamilyId;
|
|
830
782
|
const item = delEntity?.item;
|
|
831
|
-
|
|
832
|
-
const itemInScope = pcd.releasedForDevelopmentItemIds.includes(itemId);
|
|
833
|
-
const familyOnly = !itemInScope && familyInScope && itemId !== itemFamilyId;
|
|
834
|
-
if (!familyInScope || (!itemInScope && !familyOnly)) {
|
|
783
|
+
if (!pcd.releasedForDevelopmentItemIds.includes(itemFamilyId) || !pcd.releasedForDevelopmentItemIds.includes(itemId)) {
|
|
835
784
|
continue;
|
|
836
785
|
}
|
|
837
786
|
else {
|
|
@@ -861,10 +810,6 @@ class BaseProcessPublishAssortment {
|
|
|
861
810
|
if (itemId === itemFamilyId) {
|
|
862
811
|
ifc.familyDelete = true;
|
|
863
812
|
}
|
|
864
|
-
else if (familyOnly) {
|
|
865
|
-
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
866
|
-
ifc.preDevelopmentOptionChanges.push(itemId);
|
|
867
|
-
}
|
|
868
813
|
else {
|
|
869
814
|
ifc.colorDeletes.push(itemId);
|
|
870
815
|
}
|
|
@@ -912,7 +857,6 @@ class BaseProcessPublishAssortment {
|
|
|
912
857
|
const projectItem = this.getProjectItem(itemFamilyChanges, itemFamilyChanges.itemFamilyId);
|
|
913
858
|
const familyAssortmentItem = itemFamilyChanges.familyAdd || itemFamilyChanges.familyUpdate || itemFamilyChanges.familyDelete
|
|
914
859
|
|| itemFamilyChanges.colorAdds.length > 0 || itemFamilyChanges.colorUpdates.length > 0 || itemFamilyChanges.colorDeletes.length > 0
|
|
915
|
-
|| itemFamilyChanges.preDevelopmentOptionChanges.length > 0
|
|
916
860
|
|| (projectItem && this.updatedSinceDate(projectItem, itemFamilyChanges.sinceDate));
|
|
917
861
|
//familyItemRemoved is used when adding the first option to an assortment
|
|
918
862
|
//and will have updates for the family item.
|
|
@@ -1874,234 +1874,3 @@ describe('sendToFlexPLM / handleVibeIQFile / sendPublishPayloadEvent', () => {
|
|
|
1874
1874
|
});
|
|
1875
1875
|
});
|
|
1876
1876
|
});
|
|
1877
|
-
describe('released family with pre-development options', () => {
|
|
1878
|
-
const FAMILY_ID = 'AERfdvdAt1HHEVvQ';
|
|
1879
|
-
const LIME_ID = 'limeGreenItemId__';
|
|
1880
|
-
const MAROON_ID = 'maroonItemId_____';
|
|
1881
|
-
const SINCE = new Date('2026-09-03T16:54:20.778Z');
|
|
1882
|
-
const BEFORE_SINCE = '2026-09-01T00:00:00.000Z';
|
|
1883
|
-
const AFTER_SINCE = '2026-09-03T17:00:00.000Z';
|
|
1884
|
-
/** The full family item, as it appears on the family's own assortment row. */
|
|
1885
|
-
const fullFamilyItem = {
|
|
1886
|
-
id: FAMILY_ID, itemFamilyId: FAMILY_ID, name: 'Sweatpant', itemNumber: 11,
|
|
1887
|
-
lifecycleStage: 'released', roles: ['family'], richFamilyOnlyField: 'PRESENT'
|
|
1888
|
-
};
|
|
1889
|
-
/** The partial family item, as hydrated onto an option row. */
|
|
1890
|
-
const partialFamilyItem = {
|
|
1891
|
-
id: FAMILY_ID, itemFamilyId: FAMILY_ID, name: 'Sweatpant', itemNumber: 11,
|
|
1892
|
-
lifecycleStage: 'released', roles: ['family']
|
|
1893
|
-
};
|
|
1894
|
-
const familyRow = () => ({
|
|
1895
|
-
entityType: 'assortment-item',
|
|
1896
|
-
id: 'ai-' + FAMILY_ID,
|
|
1897
|
-
itemId: FAMILY_ID,
|
|
1898
|
-
item: { ...fullFamilyItem },
|
|
1899
|
-
projectItem: { id: 'proj:' + FAMILY_ID, itemId: FAMILY_ID, updatedOn: BEFORE_SINCE, roles: ['family'] }
|
|
1900
|
-
});
|
|
1901
|
-
const optionRow = (itemId, optionName, projectItemUpdatedOn) => ({
|
|
1902
|
-
entityType: 'assortment-item',
|
|
1903
|
-
id: 'ai-' + itemId,
|
|
1904
|
-
itemId,
|
|
1905
|
-
item: {
|
|
1906
|
-
id: itemId, itemFamilyId: FAMILY_ID, name: 'Sweatpant', optionName,
|
|
1907
|
-
optionGroup: 'color', lifecycleStage: 'concept', roles: ['color', 'option'],
|
|
1908
|
-
itemFamily: { ...partialFamilyItem }
|
|
1909
|
-
},
|
|
1910
|
-
projectItem: { id: 'proj:' + itemId, itemId, updatedOn: projectItemUpdatedOn, roles: ['color', 'option'] },
|
|
1911
|
-
familyProjectItem: { id: 'proj:' + FAMILY_ID, itemId: FAMILY_ID, updatedOn: BEFORE_SINCE, roles: ['family'] }
|
|
1912
|
-
});
|
|
1913
|
-
const emptyDetail = { adds: [], deletes: [], updates: [], familyItemsRemoved: [] };
|
|
1914
|
-
const baseConfig = { identifierAtts: { LCSProduct: ['itemNumber'] }, itemPreDevelopmentLifecycleStages: ['concept'] };
|
|
1915
|
-
const makePpa = () => {
|
|
1916
|
-
const cfg = { ...baseConfig };
|
|
1917
|
-
const mfu = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
1918
|
-
return new base_process_publish_assortment_1.BaseProcessPublishAssortment(cfg, new data_converter_1.DataConverter(cfg, mfu), mfu);
|
|
1919
|
-
};
|
|
1920
|
-
const makePcd = (releasedIds) => {
|
|
1921
|
-
const sf = { entityReference: 'assortment:11EZML5dMn3tREuT', objectClass: 'LCSSeason' };
|
|
1922
|
-
const pcd = new publish_change_data_1.PublishChangeData('11EZML5dMn3tREuT', sf, 'apc1', SINCE);
|
|
1923
|
-
pcd.itemToFederatedIdMapping = new Map();
|
|
1924
|
-
pcd.releasedForDevelopmentItemIds = releasedIds;
|
|
1925
|
-
return pcd;
|
|
1926
|
-
};
|
|
1927
|
-
const eventSeasonFed = { entityReference: 'assortment:a1', objectClass: 'LCSSeason' };
|
|
1928
|
-
const stubEventDeps = (ppa) => {
|
|
1929
|
-
jest.spyOn(ppa, 'getAssortment').mockResolvedValue({ id: 'a1', publishToFlexPLM: true });
|
|
1930
|
-
jest.spyOn(ppa, 'getSeasonalData').mockResolvedValue({});
|
|
1931
|
-
jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap').mockImplementation(async (...args) => args[2]);
|
|
1932
|
-
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierProperties').mockResolvedValue(['itemNumber']);
|
|
1933
|
-
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getInformationalProperties').mockResolvedValue([]);
|
|
1934
|
-
jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'isOutboundCreatableFromEntity').mockResolvedValue(true);
|
|
1935
|
-
};
|
|
1936
|
-
afterEach(() => { jest.restoreAllMocks(); });
|
|
1937
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
1938
|
-
// getReleasedForDevelopmentItemAndFamilyIds
|
|
1939
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
1940
|
-
it('released family enters scope; concept options do NOT', () => {
|
|
1941
|
-
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1942
|
-
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1943
|
-
expect(makePpa().getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toEqual([FAMILY_ID]);
|
|
1944
|
-
});
|
|
1945
|
-
it('released family enters scope; concept options do NOT', () => {
|
|
1946
|
-
const row = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1947
|
-
row.item.itemFamily = { ...partialFamilyItem, lifecycleStage: 'concept' };
|
|
1948
|
-
expect(makePpa().getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [row] }, [])).toEqual([]);
|
|
1949
|
-
});
|
|
1950
|
-
it('family reached only through a delete record', () => {
|
|
1951
|
-
const deleted = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1952
|
-
expect(makePpa().getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [] }, [deleted]))
|
|
1953
|
-
.toEqual([FAMILY_ID]);
|
|
1954
|
-
});
|
|
1955
|
-
it('two concept options of the same family add the family only once', () => {
|
|
1956
|
-
const fullChange = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1957
|
-
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
1958
|
-
expect(makePpa().getReleasedForDevelopmentItemAndFamilyIds(fullChange, [])).toHaveLength(1);
|
|
1959
|
-
});
|
|
1960
|
-
it('option row with no hydrated itemFamily is skipped without throwing', () => {
|
|
1961
|
-
const bare = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1962
|
-
delete bare.item.itemFamily;
|
|
1963
|
-
expect(makePpa().getReleasedForDevelopmentItemAndFamilyIds({ assortmentItems: [bare] }, [])).toEqual([]);
|
|
1964
|
-
});
|
|
1965
|
-
it('released family WITH its own assortment row is in scope with or without the flag', () => {
|
|
1966
|
-
const rows = { assortmentItems: [familyRow(), optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE)] };
|
|
1967
|
-
expect(makePpa().getReleasedForDevelopmentItemAndFamilyIds(rows, [])).toEqual([FAMILY_ID]);
|
|
1968
|
-
});
|
|
1969
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
1970
|
-
// getItemFamilyChanges
|
|
1971
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
1972
|
-
it('ACTION 2: deleting a concept option marks the released family for republish', () => {
|
|
1973
|
-
const ppa = makePpa();
|
|
1974
|
-
const remaining = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
1975
|
-
const deleted = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1976
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
1977
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap({ assortmentItems: [remaining] }), ppa.getDeleteChangesAssortmentMap([deleted]));
|
|
1978
|
-
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
1979
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
1980
|
-
expect(ifc.preDevelopmentOptionChanges).toEqual([MAROON_ID]);
|
|
1981
|
-
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(true);
|
|
1982
|
-
expect(ifc.colorDeletes).toEqual([]);
|
|
1983
|
-
expect(ifc.colorAdds).toEqual([]);
|
|
1984
|
-
expect(ifc.itemFamilyObject.id).toEqual(FAMILY_ID);
|
|
1985
|
-
});
|
|
1986
|
-
it('adding a concept option marks the family, not a colorAdd', () => {
|
|
1987
|
-
const ppa = makePpa();
|
|
1988
|
-
const added = optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE);
|
|
1989
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
1990
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, { ...emptyDetail, adds: [{ id: MAROON_ID }] }, ppa.getFullChangeAssortmentMap({ assortmentItems: [added] }), new Map());
|
|
1991
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
1992
|
-
expect(ifc.preDevelopmentOptionChanges).toEqual([MAROON_ID]);
|
|
1993
|
-
expect(ifc.colorAdds).toEqual([]);
|
|
1994
|
-
});
|
|
1995
|
-
it('ACTION 1: nothing changed - family bucket exists but signals no change', () => {
|
|
1996
|
-
const ppa = makePpa();
|
|
1997
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
1998
|
-
const rows = { assortmentItems: [optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE),
|
|
1999
|
-
optionRow(MAROON_ID, 'Maroon', BEFORE_SINCE)] };
|
|
2000
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap(rows), new Map());
|
|
2001
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2002
|
-
expect(ifc.preDevelopmentOptionChanges).toEqual([]);
|
|
2003
|
-
expect(ifc.colorUnchanged.sort()).toEqual([LIME_ID, MAROON_ID].sort());
|
|
2004
|
-
});
|
|
2005
|
-
it('a concept option updated after sinceDate becomes a preDevelopmentOptionChange', () => {
|
|
2006
|
-
const ppa = makePpa();
|
|
2007
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
2008
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, ppa.getFullChangeAssortmentMap({ assortmentItems: [optionRow(LIME_ID, 'Lime Green', AFTER_SINCE)] }), new Map());
|
|
2009
|
-
expect(pcd.itemFamilyChanges.size).toEqual(1);
|
|
2010
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2011
|
-
expect(ifc.preDevelopmentOptionChanges).toEqual([LIME_ID]);
|
|
2012
|
-
expect(ifc.colorUnchanged).toEqual([]);
|
|
2013
|
-
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(true);
|
|
2014
|
-
});
|
|
2015
|
-
it('REGRESSION: a released option is still a colorAdd, never a preDevelopmentOptionChange', () => {
|
|
2016
|
-
const ppa = makePpa();
|
|
2017
|
-
const released = optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE);
|
|
2018
|
-
released.item.lifecycleStage = 'development';
|
|
2019
|
-
const pcd = makePcd([FAMILY_ID, LIME_ID]);
|
|
2020
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, { ...emptyDetail, adds: [{ id: LIME_ID }] }, ppa.getFullChangeAssortmentMap({ assortmentItems: [released] }), new Map());
|
|
2021
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2022
|
-
expect(ifc.colorAdds).toEqual([LIME_ID]);
|
|
2023
|
-
expect(ifc.preDevelopmentOptionChanges).toEqual([]);
|
|
2024
|
-
expect(ifc.hasPreDevelopmentOnlyOptions).toBe(false);
|
|
2025
|
-
});
|
|
2026
|
-
it('itemFamilyObject prefers the full family item when the OPTION row is iterated first', () => {
|
|
2027
|
-
const ppa = makePpa();
|
|
2028
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
2029
|
-
const map = new Map();
|
|
2030
|
-
map.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2031
|
-
map.set(FAMILY_ID, familyRow());
|
|
2032
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, map, new Map());
|
|
2033
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2034
|
-
expect(ifc.itemFamilyObject.richFamilyOnlyField).toEqual('PRESENT');
|
|
2035
|
-
});
|
|
2036
|
-
it('itemFamilyObject is the full family item when the FAMILY row is iterated first', () => {
|
|
2037
|
-
const ppa = makePpa();
|
|
2038
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
2039
|
-
const map = new Map();
|
|
2040
|
-
map.set(FAMILY_ID, familyRow());
|
|
2041
|
-
map.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2042
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, map, new Map());
|
|
2043
|
-
const ifc = pcd.itemFamilyChanges.get(FAMILY_ID);
|
|
2044
|
-
expect(ifc.itemFamilyObject.richFamilyOnlyField).toEqual('PRESENT');
|
|
2045
|
-
});
|
|
2046
|
-
it('delete record with a missing item entity is skipped safely', () => {
|
|
2047
|
-
const ppa = makePpa();
|
|
2048
|
-
const pcd = makePcd([FAMILY_ID]);
|
|
2049
|
-
const delMap = new Map();
|
|
2050
|
-
delMap.set('GHOST', { itemId: 'GHOST', item: undefined });
|
|
2051
|
-
pcd.itemFamilyChanges = ppa.getItemFamilyChanges(pcd, emptyDetail, new Map(), delMap);
|
|
2052
|
-
expect(pcd.itemFamilyChanges.size).toEqual(0);
|
|
2053
|
-
});
|
|
2054
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
2055
|
-
// getEventsForItemFamilyChanges
|
|
2056
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
2057
|
-
it('emits exactly one LCSProductSeasonLink and zero LCSSKUSeasonLink', async () => {
|
|
2058
|
-
const ppa = makePpa();
|
|
2059
|
-
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2060
|
-
ifc.itemFamilyObject = fullFamilyItem;
|
|
2061
|
-
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
2062
|
-
ifc.preDevelopmentOptionChanges.push(MAROON_ID);
|
|
2063
|
-
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2064
|
-
stubEventDeps(ppa);
|
|
2065
|
-
const events = await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map());
|
|
2066
|
-
expect(events).toHaveLength(1);
|
|
2067
|
-
expect(events[0].objectClass).toBe('LCSProductSeasonLink');
|
|
2068
|
-
expect(events[0].eventType).toBe('UPSERT_ON_SEASON');
|
|
2069
|
-
expect(events[0].entityReference).toBe('item:' + FAMILY_ID);
|
|
2070
|
-
expect(events[0].LCSProduct['itemNumber']).toBe(11);
|
|
2071
|
-
});
|
|
2072
|
-
it('REGRESSION: an ItemFamilyChanges with no changes still emits nothing', async () => {
|
|
2073
|
-
const ppa = makePpa();
|
|
2074
|
-
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2075
|
-
ifc.itemFamilyObject = fullFamilyItem;
|
|
2076
|
-
ifc.colorUnchanged.push(LIME_ID, MAROON_ID);
|
|
2077
|
-
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2078
|
-
stubEventDeps(ppa);
|
|
2079
|
-
expect(await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map())).toHaveLength(0);
|
|
2080
|
-
});
|
|
2081
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
2082
|
-
// republish of an unchanged released family
|
|
2083
|
-
/////////////////////////////////////////////////////////////////////////////
|
|
2084
|
-
const unchangedFamilyIfc = () => {
|
|
2085
|
-
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2086
|
-
ifc.itemFamilyObject = fullFamilyItem;
|
|
2087
|
-
ifc.hasPreDevelopmentOnlyOptions = true;
|
|
2088
|
-
ifc.colorUnchanged.push(LIME_ID, MAROON_ID);
|
|
2089
|
-
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2090
|
-
return ifc;
|
|
2091
|
-
};
|
|
2092
|
-
it('ACTION 1: unchanged family with pre-development-only options emits nothing', async () => {
|
|
2093
|
-
const ppa = makePpa();
|
|
2094
|
-
stubEventDeps(ppa);
|
|
2095
|
-
expect(await ppa.getEventsForItemFamilyChanges(unchangedFamilyIfc(), 'a1', eventSeasonFed, new Map()))
|
|
2096
|
-
.toHaveLength(0);
|
|
2097
|
-
});
|
|
2098
|
-
it('REGRESSION: an ordinary unchanged released family is not republished', async () => {
|
|
2099
|
-
const ppa = makePpa();
|
|
2100
|
-
stubEventDeps(ppa);
|
|
2101
|
-
const ifc = new item_family_changes_1.ItemFamilyChanges(FAMILY_ID, SINCE);
|
|
2102
|
-
ifc.itemFamilyObject = fullFamilyItem;
|
|
2103
|
-
ifc.colorUnchanged.push(LIME_ID);
|
|
2104
|
-
ifc.assortmentItemFullChangeMap.set(LIME_ID, optionRow(LIME_ID, 'Lime Green', BEFORE_SINCE));
|
|
2105
|
-
expect(await ppa.getEventsForItemFamilyChanges(ifc, 'a1', eventSeasonFed, new Map())).toHaveLength(0);
|
|
2106
|
-
});
|
|
2107
|
-
});
|
|
@@ -63,7 +63,6 @@ class FlexPLMConnect {
|
|
|
63
63
|
const nonce_key = body.items[0].attributes.nonce_key;
|
|
64
64
|
const nonce = body.items[0].attributes.nonce;
|
|
65
65
|
console.info('nonce_key: ' + nonce_key);
|
|
66
|
-
console.info('nonce: ' + nonce);
|
|
67
66
|
return {
|
|
68
67
|
nonce_key,
|
|
69
68
|
nonce
|
|
@@ -89,7 +88,10 @@ class FlexPLMConnect {
|
|
|
89
88
|
console.info('Request:');
|
|
90
89
|
console.info('vibeEventsURL: ' + vibeEventsURL);
|
|
91
90
|
const logOptions = JSON.parse(JSON.stringify(xferOptions));
|
|
92
|
-
|
|
91
|
+
if (logOptions?.headers?.Authorization)
|
|
92
|
+
logOptions.headers.Authorization = logOptions?.headers?.Authorization.substring(0, 9);
|
|
93
|
+
if (csrf?.nonce_key)
|
|
94
|
+
logOptions.headers[csrf.nonce_key] = csrf.nonce ? `***masked(length:${csrf.nonce.length})***` : '***masked(empty)***';
|
|
93
95
|
console.info('csrfOptions: ' + JSON.stringify(logOptions));
|
|
94
96
|
console.info('Making call to xfer data to FlexPLM');
|
|
95
97
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const flexplm_connect_1 = require("./flexplm-connect");
|
|
4
|
+
const app_framework_1 = require("@contrail/app-framework");
|
|
4
5
|
const mockJsonData = { items: [{ id: '123', name: 'test' }] };
|
|
5
6
|
const mockResponse = (status, json = mockJsonData) => ({
|
|
6
7
|
status,
|
|
@@ -87,3 +88,36 @@ describe('FlexPLMConnect.getRequest', () => {
|
|
|
87
88
|
await expect(connect.getRequest()).rejects.toThrow('Error getting json data from FlexPLM: Invalid JSON');
|
|
88
89
|
});
|
|
89
90
|
});
|
|
91
|
+
describe('FlexPLMConnect.sendRequest', () => {
|
|
92
|
+
it('should mask csrf nonce value in logged request options', async () => {
|
|
93
|
+
const loggerIsInfoOnSpy = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
|
|
94
|
+
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
95
|
+
const csrf = { nonce_key: 'CSRF_NONCE', nonce: 'raw-secret-nonce' };
|
|
96
|
+
const payload = { id: '123' };
|
|
97
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
98
|
+
await connect.sendRequest(csrf, payload);
|
|
99
|
+
const csrfOptionsLogMessages = consoleInfoSpy.mock.calls
|
|
100
|
+
.map(([message]) => String(message ?? ''))
|
|
101
|
+
.filter(message => message.startsWith('csrfOptions: '));
|
|
102
|
+
const sendRequestOptionsLogMessage = csrfOptionsLogMessages.find(message => message.includes('"CSRF_NONCE":'));
|
|
103
|
+
expect(sendRequestOptionsLogMessage).toBeDefined();
|
|
104
|
+
expect(sendRequestOptionsLogMessage ?? '').not.toContain('raw-secret-nonce');
|
|
105
|
+
expect(sendRequestOptionsLogMessage ?? '').toContain(`"CSRF_NONCE":"***masked(length:${'raw-secret-nonce'.length})***"`);
|
|
106
|
+
loggerIsInfoOnSpy.mockRestore();
|
|
107
|
+
consoleInfoSpy.mockRestore();
|
|
108
|
+
});
|
|
109
|
+
it('should log masked(empty) when csrf nonce is empty', async () => {
|
|
110
|
+
const loggerIsInfoOnSpy = jest.spyOn(app_framework_1.Logger, 'isInfoOn').mockReturnValue(true);
|
|
111
|
+
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
|
|
112
|
+
const csrf = { nonce_key: 'CSRF_NONCE', nonce: '' };
|
|
113
|
+
const connect = new flexplm_connect_1.FlexPLMConnect(createConfig());
|
|
114
|
+
await connect.sendRequest(csrf, { id: '123' });
|
|
115
|
+
const csrfOptionsLog = consoleInfoSpy.mock.calls
|
|
116
|
+
.map(([message]) => String(message ?? ''))
|
|
117
|
+
.find(message => message.includes('"CSRF_NONCE":'));
|
|
118
|
+
expect(csrfOptionsLog).toBeDefined();
|
|
119
|
+
expect(csrfOptionsLog ?? '').toContain('"CSRF_NONCE":"***masked(empty)***"');
|
|
120
|
+
loggerIsInfoOnSpy.mockRestore();
|
|
121
|
+
consoleInfoSpy.mockRestore();
|
|
122
|
+
});
|
|
123
|
+
});
|