@contrail/flexplm 1.4.0 → 1.5.0-alpha.0d65410

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.
@@ -454,6 +454,393 @@ describe('getObjectReferenceValue bad value', () => {
454
454
  }
455
455
  });
456
456
  });
457
+ describe('setObjectReferenceValue - identity service', () => {
458
+ const baseConfig = () => ({
459
+ apiHost: 'host',
460
+ userName: () => 'user',
461
+ password: () => 'pass',
462
+ urlContext: 'xxx',
463
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
464
+ csrfEndpoint: '/servlet/rest/security/csrf',
465
+ itemPreDevelopmentLifecycleStages: ['concept']
466
+ });
467
+ const refProp = {
468
+ id: 'cJoZQvoj7dkfCBJq',
469
+ propertyType: 'object_reference',
470
+ slug: 'material',
471
+ label: 'Material',
472
+ referencedTypeRootSlug: 'item',
473
+ referencedTypePath: 'item:material'
474
+ };
475
+ let identifierSpy;
476
+ let poolKeySpy;
477
+ let getByRootAndPathSpy;
478
+ let filterTypePropertiesSpy;
479
+ const setupTypeUtilsSpies = (dc, rootPropSlugs = ['itemNumber']) => {
480
+ getByRootAndPathSpy = jest.spyOn(dc['typeUtils'], 'getByRootAndPath')
481
+ .mockImplementation(async () => ({ typePath: 'item', typeProperties: [] }));
482
+ filterTypePropertiesSpy = jest.spyOn(dc['typeUtils'], 'filterTypeProperties')
483
+ .mockImplementation(() => rootPropSlugs.map(slug => ({ slug })));
484
+ };
485
+ afterEach(() => {
486
+ identifierSpy?.mockRestore();
487
+ poolKeySpy?.mockRestore();
488
+ getByRootAndPathSpy?.mockRestore();
489
+ filterTypePropertiesSpy?.mockRestore();
490
+ mockGetFunction.mockReset();
491
+ mockGetFunction.mockImplementation((options) => ((options?.criteria?.id === 1234) ? [mockObj1234] : [mockObj2222]));
492
+ });
493
+ it('flag off - uses getAllObjectReferences path', async () => {
494
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
495
+ setupTypeUtilsSpies(dc, ['itemNumber', 'roles']);
496
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
497
+ .mockImplementation(async () => ['itemNumber', 'roles']);
498
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
499
+ .mockImplementation(async () => 'item:material');
500
+ mockGetFunction.mockClear();
501
+ mockGetFunction.mockImplementation(() => [{ id: 'abc123', typePath: 'item:material' }]);
502
+ const nd = { itemNumber: 'MAT-100', roles: ['family'] };
503
+ const result = await dc.setObjectReferenceValue(refProp, nd);
504
+ expect(mockGetFunction).toHaveBeenCalled();
505
+ const callArg = mockGetFunction.mock.calls[0][0];
506
+ expect(callArg.entityName).toEqual('item');
507
+ expect(callArg.entityName).not.toEqual('identity');
508
+ expect(result).toEqual('abc123');
509
+ expect(poolKeySpy).not.toHaveBeenCalled();
510
+ });
511
+ it('flag on, single identity match - returns parsed id', async () => {
512
+ const config = baseConfig();
513
+ config.search = { item: { useIdentityServiceForInboundData: true } };
514
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
515
+ setupTypeUtilsSpies(dc);
516
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
517
+ .mockImplementation(async () => ['itemNumber', 'roles']);
518
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
519
+ .mockImplementation(async () => 'item:material');
520
+ mockGetFunction.mockClear();
521
+ mockGetFunction.mockImplementation(() => [{ entityReference: 'item:abc123' }]);
522
+ const nd = { itemNumber: 'MAT-100', roles: ['family'] };
523
+ const result = await dc.setObjectReferenceValue(refProp, nd);
524
+ expect(mockGetFunction).toHaveBeenCalledTimes(1);
525
+ const callArg = mockGetFunction.mock.calls[0][0];
526
+ expect(callArg.entityName).toEqual('identity');
527
+ expect(callArg.criteria).toEqual({
528
+ poolKey: 'item:material',
529
+ propertyName: 'itemNumber',
530
+ propertyValue: 'MAT-100'
531
+ });
532
+ expect(result).toEqual('abc123');
533
+ });
534
+ it('flag on, no identity match - returns empty and warns', async () => {
535
+ const config = baseConfig();
536
+ config.search = { item: { useIdentityServiceForInboundData: true } };
537
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
538
+ setupTypeUtilsSpies(dc);
539
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
540
+ .mockImplementation(async () => ['itemNumber', 'roles']);
541
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
542
+ .mockImplementation(async () => 'item:material');
543
+ mockGetFunction.mockClear();
544
+ mockGetFunction.mockImplementation(() => []);
545
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
546
+ const getAllSpy = jest.spyOn(dc, 'getAllObjectReferences');
547
+ try {
548
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', roles: ['family'] });
549
+ expect(result).toEqual('');
550
+ expect(warnSpy).toHaveBeenCalled();
551
+ expect(getAllSpy).not.toHaveBeenCalled();
552
+ }
553
+ finally {
554
+ warnSpy.mockRestore();
555
+ getAllSpy.mockRestore();
556
+ }
557
+ });
558
+ it('flag on, multiple identity matches - returns empty and does not throw', async () => {
559
+ const config = baseConfig();
560
+ config.search = { item: { useIdentityServiceForInboundData: true } };
561
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
562
+ setupTypeUtilsSpies(dc);
563
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
564
+ .mockImplementation(async () => ['itemNumber', 'roles']);
565
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
566
+ .mockImplementation(async () => 'item:material');
567
+ mockGetFunction.mockClear();
568
+ mockGetFunction.mockImplementation(() => [
569
+ { entityReference: 'item:abc123' },
570
+ { entityReference: 'item:def456' }
571
+ ]);
572
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
573
+ try {
574
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', roles: ['family'] });
575
+ expect(result).toEqual('');
576
+ expect(warnSpy).toHaveBeenCalled();
577
+ }
578
+ finally {
579
+ warnSpy.mockRestore();
580
+ }
581
+ });
582
+ it('flag on, ambiguous identifier set - falls back to getAllObjectReferences', async () => {
583
+ const config = baseConfig();
584
+ config.search = { item: { useIdentityServiceForInboundData: true } };
585
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
586
+ setupTypeUtilsSpies(dc, ['itemNumber', 'season', 'roles']);
587
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
588
+ .mockImplementation(async () => ['itemNumber', 'season', 'roles']);
589
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
590
+ .mockImplementation(async () => 'item:material');
591
+ mockGetFunction.mockClear();
592
+ mockGetFunction.mockImplementation(() => [{ id: 'abc123', typePath: 'item:material' }]);
593
+ const nd = { itemNumber: 'MAT-100', season: 'SS24', roles: ['family'] };
594
+ const result = await dc.setObjectReferenceValue(refProp, nd);
595
+ expect(mockGetFunction).toHaveBeenCalled();
596
+ const callArg = mockGetFunction.mock.calls[0][0];
597
+ expect(callArg.entityName).toEqual('item');
598
+ expect(callArg.entityName).not.toEqual('identity');
599
+ expect(result).toEqual('abc123');
600
+ expect(poolKeySpy).not.toHaveBeenCalled();
601
+ });
602
+ it('flag on, cache hit - second call short-circuits', async () => {
603
+ const config = baseConfig();
604
+ config.search = { item: { useIdentityServiceForInboundData: true } };
605
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
606
+ setupTypeUtilsSpies(dc);
607
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
608
+ .mockImplementation(async () => ['itemNumber', 'roles']);
609
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
610
+ .mockImplementation(async () => 'item:material');
611
+ mockGetFunction.mockClear();
612
+ mockGetFunction.mockImplementation(() => [{ entityReference: 'item:abc123' }]);
613
+ const nd = { itemNumber: 'MAT-100', roles: ['family'] };
614
+ const r1 = await dc.setObjectReferenceValue(refProp, nd);
615
+ const r2 = await dc.setObjectReferenceValue(refProp, nd);
616
+ expect(r1).toEqual('abc123');
617
+ expect(r2).toEqual('abc123');
618
+ expect(mockGetFunction).toHaveBeenCalledTimes(1);
619
+ });
620
+ it('flag on, project-item entityType - roles is filtered, identity path used', async () => {
621
+ const projectItemProp = {
622
+ ...refProp,
623
+ referencedTypeRootSlug: 'project-item',
624
+ referencedTypePath: 'project-item'
625
+ };
626
+ const config = baseConfig();
627
+ config.search = { 'project-item': { useIdentityServiceForInboundData: true } };
628
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
629
+ setupTypeUtilsSpies(dc, ['itemNumber', 'roles']);
630
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
631
+ .mockImplementation(async () => ['itemNumber', 'roles']);
632
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
633
+ .mockImplementation(async () => 'project-item');
634
+ mockGetFunction.mockClear();
635
+ mockGetFunction.mockImplementation(() => [{ entityReference: 'project-item:pi-789' }]);
636
+ const result = await dc.setObjectReferenceValue(projectItemProp, { itemNumber: 'X1', roles: ['family'] });
637
+ expect(mockGetFunction).toHaveBeenCalledTimes(1);
638
+ const callArg = mockGetFunction.mock.calls[0][0];
639
+ expect(callArg.entityName).toEqual('identity');
640
+ expect(callArg.criteria.propertyName).toEqual('itemNumber');
641
+ expect(result).toEqual('pi-789');
642
+ });
643
+ it('flag on, non-item entityType - roles is NOT filtered, falls back to query path', async () => {
644
+ const colorProp = {
645
+ ...refProp,
646
+ referencedTypeRootSlug: 'color',
647
+ referencedTypePath: 'color'
648
+ };
649
+ const config = baseConfig();
650
+ config.search = { color: { useIdentityServiceForInboundData: true } };
651
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
652
+ setupTypeUtilsSpies(dc, ['colorNumber', 'roles']);
653
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
654
+ .mockImplementation(async () => ['colorNumber', 'roles']);
655
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
656
+ .mockImplementation(async () => 'color');
657
+ mockGetFunction.mockClear();
658
+ mockGetFunction.mockImplementation(() => [{ id: 'col-1', typePath: 'color' }]);
659
+ const result = await dc.setObjectReferenceValue(colorProp, { colorNumber: 'C1', roles: ['family'] });
660
+ expect(mockGetFunction).toHaveBeenCalled();
661
+ const callArg = mockGetFunction.mock.calls[0][0];
662
+ expect(callArg.entityName).toEqual('color');
663
+ expect(callArg.entityName).not.toEqual('identity');
664
+ expect(result).toEqual('col-1');
665
+ expect(poolKeySpy).not.toHaveBeenCalled();
666
+ });
667
+ it('query path, multiple matches - returns empty with single warn (no double warn)', async () => {
668
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
669
+ setupTypeUtilsSpies(dc, ['itemNumber']);
670
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
671
+ .mockImplementation(async () => ['itemNumber']);
672
+ mockGetFunction.mockClear();
673
+ mockGetFunction.mockImplementation(() => [
674
+ { id: 'a1', typePath: 'item:material' },
675
+ { id: 'a2', typePath: 'item:material' }
676
+ ]);
677
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
678
+ try {
679
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100' });
680
+ expect(result).toEqual('');
681
+ expect(warnSpy).toHaveBeenCalledTimes(1);
682
+ expect(warnSpy.mock.calls[0][0]).toMatch(/duplicate records/);
683
+ }
684
+ finally {
685
+ warnSpy.mockRestore();
686
+ }
687
+ });
688
+ it('flag on, identity returns null - normalized to empty array, returns empty', async () => {
689
+ const config = baseConfig();
690
+ config.search = { item: { useIdentityServiceForInboundData: true } };
691
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
692
+ setupTypeUtilsSpies(dc);
693
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
694
+ .mockImplementation(async () => ['itemNumber', 'roles']);
695
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
696
+ .mockImplementation(async () => 'item:material');
697
+ mockGetFunction.mockClear();
698
+ mockGetFunction.mockImplementation(() => null);
699
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
700
+ try {
701
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', roles: ['family'] });
702
+ expect(result).toEqual('');
703
+ expect(warnSpy).toHaveBeenCalled();
704
+ }
705
+ finally {
706
+ warnSpy.mockRestore();
707
+ }
708
+ });
709
+ it('null nd - returns empty string and does not query', async () => {
710
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
711
+ mockGetFunction.mockClear();
712
+ const result = await dc.setObjectReferenceValue(refProp, null);
713
+ expect(result).toEqual('');
714
+ expect(mockGetFunction).not.toHaveBeenCalled();
715
+ });
716
+ it('zero identifier keys - warns and returns empty string', async () => {
717
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
718
+ setupTypeUtilsSpies(dc);
719
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
720
+ .mockImplementation(async () => []);
721
+ mockGetFunction.mockClear();
722
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
723
+ try {
724
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100' });
725
+ expect(result).toEqual('');
726
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringMatching(/doesnt have all "identifier" properties/));
727
+ expect(mockGetFunction).not.toHaveBeenCalled();
728
+ }
729
+ finally {
730
+ warnSpy.mockRestore();
731
+ }
732
+ });
733
+ it('missing identifier keys on nd - warns and returns empty string', async () => {
734
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
735
+ setupTypeUtilsSpies(dc);
736
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
737
+ .mockImplementation(async () => ['itemNumber', 'season']);
738
+ mockGetFunction.mockClear();
739
+ const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
740
+ try {
741
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100' });
742
+ expect(result).toEqual('');
743
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringMatching(/doesnt have all "identifier" properties/));
744
+ expect(mockGetFunction).not.toHaveBeenCalled();
745
+ }
746
+ finally {
747
+ warnSpy.mockRestore();
748
+ }
749
+ });
750
+ it('transformMapFile set - applyInboundTransformMap is invoked before context build', async () => {
751
+ const config = baseConfig();
752
+ config['transformMapFile'] = 'file1';
753
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
754
+ setupTypeUtilsSpies(dc, ['itemNumber', 'roles']);
755
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
756
+ .mockImplementation(async () => ['itemNumber', 'roles']);
757
+ const mapKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKeyFromObject')
758
+ .mockImplementation(async () => 'LCSMaterial');
759
+ const applyMapSpy = jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap')
760
+ .mockImplementation(async (...args) => args[2]);
761
+ mockGetFunction.mockClear();
762
+ mockGetFunction.mockImplementation(() => [{ id: 'q-1', typePath: 'item:material' }]);
763
+ try {
764
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', roles: ['family'] });
765
+ expect(mapKeySpy).toHaveBeenCalledTimes(1);
766
+ expect(applyMapSpy).toHaveBeenCalledTimes(1);
767
+ expect(applyMapSpy.mock.calls[0][3]).toEqual('LCSMaterial');
768
+ expect(result).toEqual('q-1');
769
+ }
770
+ finally {
771
+ mapKeySpy.mockRestore();
772
+ applyMapSpy.mockRestore();
773
+ }
774
+ });
775
+ it('transformMapFile unset - applyInboundTransformMap does not invoke map utilities', async () => {
776
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
777
+ setupTypeUtilsSpies(dc, ['itemNumber', 'roles']);
778
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
779
+ .mockImplementation(async () => ['itemNumber', 'roles']);
780
+ const mapKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getMapKeyFromObject');
781
+ const applyMapSpy = jest.spyOn(map_utils_1.MapUtil, 'applyTransformMap');
782
+ mockGetFunction.mockClear();
783
+ mockGetFunction.mockImplementation(() => [{ id: 'q-2', typePath: 'item:material' }]);
784
+ try {
785
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', roles: ['family'] });
786
+ expect(mapKeySpy).not.toHaveBeenCalled();
787
+ expect(applyMapSpy).not.toHaveBeenCalled();
788
+ expect(result).toEqual('q-2');
789
+ }
790
+ finally {
791
+ mapKeySpy.mockRestore();
792
+ applyMapSpy.mockRestore();
793
+ }
794
+ });
795
+ it('query path success - writes id to cache (second call short-circuits)', async () => {
796
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
797
+ setupTypeUtilsSpies(dc, ['itemNumber', 'roles']);
798
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
799
+ .mockImplementation(async () => ['itemNumber', 'roles']);
800
+ mockGetFunction.mockClear();
801
+ mockGetFunction.mockImplementation(() => [{ id: 'cached-q', typePath: 'item:material' }]);
802
+ const nd = { itemNumber: 'MAT-100', roles: ['family'] };
803
+ const r1 = await dc.setObjectReferenceValue(refProp, nd);
804
+ const callsAfterFirst = mockGetFunction.mock.calls.length;
805
+ const r2 = await dc.setObjectReferenceValue(refProp, nd);
806
+ expect(r1).toEqual('cached-q');
807
+ expect(r2).toEqual('cached-q');
808
+ expect(mockGetFunction.mock.calls.length).toEqual(callsAfterFirst);
809
+ });
810
+ it('query path with subtype filter - applies checkKeysAndValues when entityType !== entityTypePath', async () => {
811
+ const dc = new data_converter_1.DataConverter(baseConfig(), new transform_data_1.MapFileUtil(new sdk_1.Entities()));
812
+ setupTypeUtilsSpies(dc, ['itemNumber']);
813
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
814
+ .mockImplementation(async () => ['itemNumber', 'season']);
815
+ const checkSpy = jest.spyOn(dc, 'checkKeysAndValues')
816
+ .mockImplementation((_criteria, arr) => arr);
817
+ mockGetFunction.mockClear();
818
+ mockGetFunction.mockImplementation(() => [{ id: 'sub-1', typePath: 'item:material' }]);
819
+ try {
820
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', season: 'SS24' });
821
+ expect(checkSpy).toHaveBeenCalledTimes(1);
822
+ expect(checkSpy.mock.calls[0][2]).toEqual('item:material');
823
+ expect(result).toEqual('sub-1');
824
+ }
825
+ finally {
826
+ checkSpy.mockRestore();
827
+ }
828
+ });
829
+ it('pickSingleResult single result - identity branch parses id from entityReference', async () => {
830
+ const config = baseConfig();
831
+ config.search = { item: { useIdentityServiceForInboundData: true } };
832
+ const dc = new data_converter_1.DataConverter(config, new transform_data_1.MapFileUtil(new sdk_1.Entities()));
833
+ setupTypeUtilsSpies(dc);
834
+ identifierSpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getIdentifierPropertiesFromObject')
835
+ .mockImplementation(async () => ['itemNumber', 'roles']);
836
+ poolKeySpy = jest.spyOn(type_conversion_utils_1.TypeConversionUtils, 'getUniquenessPoolKeyFromObject')
837
+ .mockImplementation(async () => 'item:material');
838
+ mockGetFunction.mockClear();
839
+ mockGetFunction.mockImplementation(() => [{ entityReference: 'item:parsed-id-from-ref' }]);
840
+ const result = await dc.setObjectReferenceValue(refProp, { itemNumber: 'MAT-100', roles: ['family'] });
841
+ expect(result).toEqual('parsed-id-from-ref');
842
+ });
843
+ });
457
844
  describe('getObjectReferenceValue - use mapping', () => {
458
845
  const maps = require('./data-converter-spec-mockData');
459
846
  const mapping = maps['mapping'];
@@ -525,6 +912,47 @@ describe('getObjectReferenceValue - use mapping', () => {
525
912
  }
526
913
  });
527
914
  });
915
+ describe('getEntityValues', () => {
916
+ const config = {
917
+ apiHost: 'host',
918
+ userName: () => 'user',
919
+ password: () => 'pass',
920
+ urlContext: 'xxx',
921
+ vibeEventEndpoint: '/rfa/vibeiq/vibeEvents',
922
+ csrfEndpoint: '/servlet/rest/security/csrf',
923
+ itemPreDevelopmentLifecycleStages: ['concept']
924
+ };
925
+ const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
926
+ const dc = new data_converter_1.DataConverter(config, mapFileUtil);
927
+ const runWithStubs = async (typePath, objectClass, data) => {
928
+ const tcoSpy = jest.spyOn(dc['typeUtils'], 'getEntityTypeClientOptionsUsingMapping')
929
+ .mockImplementation(async () => ({ root: 'item' }));
930
+ const typeSpy = jest.spyOn(dc['typeUtils'], 'getByRootAndPath')
931
+ .mockImplementation(async () => ({ typePath, typeProperties: [] }));
932
+ const filterSpy = jest.spyOn(dc['typeUtils'], 'filterTypeProperties')
933
+ .mockImplementation(() => []);
934
+ try {
935
+ return await dc.getEntityValues(objectClass, data);
936
+ }
937
+ finally {
938
+ tcoSpy.mockRestore();
939
+ typeSpy.mockRestore();
940
+ filterSpy.mockRestore();
941
+ }
942
+ };
943
+ it('LCSProduct -> roles family', async () => {
944
+ const result = await runWithStubs('item', 'LCSProduct', { itemNumber: 'X1' });
945
+ expect(result['roles']).toEqual(['family']);
946
+ });
947
+ it('LCSSKU -> roles color, option', async () => {
948
+ const result = await runWithStubs('item', 'LCSSKU', { itemNumber: 'X1' });
949
+ expect(result['roles']).toEqual(['color', 'option']);
950
+ });
951
+ it('LCSMaterial -> roles family', async () => {
952
+ const result = await runWithStubs('item:material', 'LCSMaterial', { itemNumber: 'MAT-100' });
953
+ expect(result['roles']).toEqual(['family']);
954
+ });
955
+ });
528
956
  describe('setEnumerationKeys', () => {
529
957
  const config = {
530
958
  apiHost: 'host',
@@ -2,7 +2,10 @@ export declare class TypeDefaults {
2
2
  static NO_ENTITY_TYPE: string;
3
3
  static NO_OBJECT_CLASS: string;
4
4
  static NO_TYPE_PATH: string;
5
+ static processLCSMaterialAsItem: boolean;
5
6
  constructor();
7
+ static applyConfig(config: any): void;
8
+ static isPropertyTrue(value: any): boolean;
6
9
  static getDefaultObjectClass(entity: any): string;
7
10
  static getDefaultObjectTypePath(entity: any): string;
8
11
  static getDefaultIdentifierProperties(entity: any): string[];
@@ -4,6 +4,12 @@ exports.TypeDefaults = void 0;
4
4
  class TypeDefaults {
5
5
  constructor() {
6
6
  }
7
+ static applyConfig(config) {
8
+ TypeDefaults.processLCSMaterialAsItem = TypeDefaults.isPropertyTrue(config?.LCSMaterial?.processAsItem);
9
+ }
10
+ static isPropertyTrue(value) {
11
+ return value === true || (typeof value === 'string' && value.toLowerCase() === 'true');
12
+ }
7
13
  static getDefaultObjectClass(entity) {
8
14
  const entityType = this.getEntityType(entity);
9
15
  let objectClass = '';
@@ -130,7 +136,13 @@ class TypeDefaults {
130
136
  static getDefaultEntityClass(object) {
131
137
  let entityClass = '';
132
138
  let objectClass = TypeDefaults.getObjectClass(object);
133
- if (['LCSProduct', 'LCSSKU'].includes(objectClass)) {
139
+ const itemClasses = TypeDefaults.processLCSMaterialAsItem
140
+ ? ['LCSProduct', 'LCSSKU', 'LCSMaterial']
141
+ : ['LCSProduct', 'LCSSKU'];
142
+ const customEntityClasses = TypeDefaults.processLCSMaterialAsItem
143
+ ? ['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast']
144
+ : ['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', 'LCSMaterial'];
145
+ if (itemClasses.includes(objectClass)) {
134
146
  entityClass = 'item';
135
147
  }
136
148
  else if (['LCSProductSeasonLink', 'LCSSKUSeasonLink'].includes(objectClass)) {
@@ -142,7 +154,7 @@ class TypeDefaults {
142
154
  else if (['LCSSeason', 'SeasonGroup'].includes(objectClass)) {
143
155
  entityClass = 'assortment';
144
156
  }
145
- else if (['LCSRevisableEntity', 'LCSLifecycleManaged', 'LCSLast', 'LCSMaterial'].includes(objectClass)) {
157
+ else if (customEntityClasses.includes(objectClass)) {
146
158
  entityClass = 'custom-entity';
147
159
  }
148
160
  if (entityClass === '')
@@ -157,6 +169,11 @@ class TypeDefaults {
157
169
  case 'LCSSKU':
158
170
  typePath = 'item';
159
171
  break;
172
+ case 'LCSMaterial':
173
+ if (TypeDefaults.processLCSMaterialAsItem) {
174
+ typePath = 'item:material';
175
+ }
176
+ break;
160
177
  case 'LCSProductSeasonLink':
161
178
  case 'LCSSKUSeasonLink':
162
179
  typePath = 'project-item';
@@ -181,6 +198,14 @@ class TypeDefaults {
181
198
  case 'LCSSKU':
182
199
  identifierProps.push('itemNumber');
183
200
  break;
201
+ case 'LCSMaterial':
202
+ if (TypeDefaults.processLCSMaterialAsItem) {
203
+ identifierProps.push('itemNumber');
204
+ }
205
+ else {
206
+ identifierProps.push('name');
207
+ }
208
+ break;
184
209
  case 'LCSSeason':
185
210
  identifierProps.push('flexPLMSeasonName');
186
211
  break;
@@ -191,7 +216,6 @@ class TypeDefaults {
191
216
  case 'LCSRevisableEntity':
192
217
  case 'LCSLifecycleManaged':
193
218
  case 'LCSLast':
194
- case 'LCSMaterial':
195
219
  identifierProps.push('name');
196
220
  break;
197
221
  }
@@ -200,7 +224,10 @@ class TypeDefaults {
200
224
  static getDefaultInformationalPropertiesFromObject(object) {
201
225
  const objectClass = TypeDefaults.getObjectClass(object);
202
226
  let properties = [];
203
- if ('LCSProduct' === objectClass) {
227
+ const itemClasses = TypeDefaults.processLCSMaterialAsItem
228
+ ? ['LCSProduct', 'LCSMaterial']
229
+ : ['LCSProduct'];
230
+ if (itemClasses.includes(objectClass)) {
204
231
  properties.push('name');
205
232
  }
206
233
  else if ('LCSSKU' === objectClass) {
@@ -219,3 +246,4 @@ exports.TypeDefaults = TypeDefaults;
219
246
  TypeDefaults.NO_ENTITY_TYPE = 'Not able to determine the entity type of the entity object';
220
247
  TypeDefaults.NO_OBJECT_CLASS = 'Please ensure that the flexPLMObjectClass property is provided.';
221
248
  TypeDefaults.NO_TYPE_PATH = 'Please ensure that the flexPLMTypePath property is provided.';
249
+ TypeDefaults.processLCSMaterialAsItem = false;