@contrail/flexplm 1.8.0-alpha.f09632d → 1.8.0
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/README.md +3 -8
- package/lib/interfaces/mapping-file.d.ts +6 -26
- package/lib/util/type-conversion-utils.d.ts +0 -16
- package/lib/util/type-conversion-utils.js +0 -31
- package/lib/util/type-conversion-utils.spec.js +13 -75
- package/lib/util/type-defaults.d.ts +1 -1
- package/lib/util/type-defaults.js +2 -2
- package/lib/util/type-defaults.spec.js +5 -2
- package/lib/util/type-utils.js +5 -0
- package/lib/util/type-utils.spec.js +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
|
-
Core model for contrail, which establishes the base
|
|
1
|
+
Core model for contrail, which establishes the base persistence services (without dependency on a persistence implementaiton) and core NestJS elements such as the InvocationContext.
|
|
2
2
|
|
|
3
3
|
## LCSCountry defaults
|
|
4
4
|
`LCSCountry` <-> VibeIQ `custom-entity:country` works with built-in defaults for the type mapping. The built-in conventions are:
|
|
5
5
|
|
|
6
6
|
| Item | Default |
|
|
7
7
|
|---|---|
|
|
8
|
-
| VibeIQ type path | `custom-entity:country` (case-
|
|
8
|
+
| VibeIQ type path | `custom-entity:country` (exact, lowercase; matched case-sensitively in both directions) |
|
|
9
9
|
| FlexPLM class / type path | `LCSCountry` / `Country` |
|
|
10
10
|
| Identifier property | `vibeIQIdentifier` (same key in both systems; no rekey runs without a mapping) |
|
|
11
11
|
| Informational property | `name` |
|
|
12
12
|
|
|
13
|
-
A mapping section for `LCSCountry` overrides
|
|
13
|
+
A mapping section for `LCSCountry` overrides these defaults when selected by the type-conversion routing. If your mapping defines `typeConversion.vibe2flex['custom-entity'].getMapKey`, make it return `'LCSCountry'` for Country entities to apply that section's outbound overrides; an empty key bypasses the section.
|
|
14
14
|
|
|
15
|
-
```ts
|
|
16
|
-
LCSCountry: { originSystem: 'flexplm' }, // FlexPLM creates Countries
|
|
17
|
-
LCSCountry: { originSystem: 'vibeiq' }, // VibeIQ creates Countries
|
|
18
|
-
```
|
|
19
|
-
Read the declared origin with `TypeConversionUtils.getOriginSystem(fileId, mapFileUtil, 'LCSCountry')`.
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { FunctionTransformers, RekeyTransformers, TransformDefinition, TransformTask } from "@contrail/transform-data";
|
|
2
|
-
/** The system that originates (creates) an entity type: 'flexplm' or 'vibeiq'. */
|
|
3
|
-
export type OriginSystem = 'flexplm' | 'vibeiq';
|
|
4
2
|
/**
|
|
5
3
|
* One direction of a {@link MappingSection} (either `vibe2flex` or `flex2vibe`).
|
|
6
4
|
*
|
|
@@ -364,6 +362,10 @@ export interface MappingSection {
|
|
|
364
362
|
* }
|
|
365
363
|
*
|
|
366
364
|
* @example
|
|
365
|
+
* // FlexPLM creates Countries (VibeIQ creates them by default).
|
|
366
|
+
* LCSCountry: { isInboundCreatable: () => true }
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
367
369
|
* // add-to-project flow — Flex drives project-item creation.
|
|
368
370
|
* // Paired with isOutboundCreatable: () => false to make
|
|
369
371
|
* // outbound update-only.
|
|
@@ -517,28 +519,6 @@ export interface MappingSection {
|
|
|
517
519
|
* getInformationalProperties: () => ['itemNumber', 'skuName']
|
|
518
520
|
*/
|
|
519
521
|
getInformationalProperties?: (entity: Record<string, unknown>) => string[];
|
|
520
|
-
/**
|
|
521
|
-
* Which system originates (creates) this entity type: `'flexplm'` or `'vibeiq'`.
|
|
522
|
-
*
|
|
523
|
-
* Currently read only for the **`LCSCountry`** section, where it is
|
|
524
|
-
* **required**: the connector creates Countries in one direction only
|
|
525
|
-
* (`'flexplm'`: FlexPLM creates them and VibeIQ never sends them to
|
|
526
|
-
* FlexPLM; `'vibeiq'`: VibeIQ creates them and inbound events only update
|
|
527
|
-
* existing Countries). There is no default. When it is missing, or is not
|
|
528
|
-
* one of those two values (case and surrounding whitespace are ignored),
|
|
529
|
-
* the origin is undeclared and Country is read-only on both sides, with an
|
|
530
|
-
* error logged. See `TypeConversionUtils.getOriginSystem`.
|
|
531
|
-
*
|
|
532
|
-
* It is a static, type-level value. It does not read the record, and it is
|
|
533
|
-
* independent of {@link MappingSection.isInboundCreatable} and
|
|
534
|
-
* {@link MappingSection.isOutboundCreatable}, which the Country origin
|
|
535
|
-
* check does not consult.
|
|
536
|
-
*
|
|
537
|
-
* @example
|
|
538
|
-
* // FlexPLM creates Countries.
|
|
539
|
-
* LCSCountry: { originSystem: 'flexplm' }
|
|
540
|
-
*/
|
|
541
|
-
originSystem?: OriginSystem;
|
|
542
522
|
/**
|
|
543
523
|
* Transform configuration applied when sending data from VibeIQ to FlexPLM.
|
|
544
524
|
* Pipeline runs on rows in their VibeIQ shape and produces rows in their
|
|
@@ -731,7 +711,7 @@ interface MappingFileBase {
|
|
|
731
711
|
* | `item` | `LCSProduct` / `LCSSKU` | Syncs once `lifecycleStage` is out of `concept`. Sends Primary Viewable as the thumbnail (no other images). Creates `LCSProduct` / `LCSSKU` if missing; auto-retries `LCSSKU` creation when the parent `LCSProduct` isn't ready yet. |
|
|
732
712
|
* | `color` | `LCSColor` | Syncs on create / update. Sends Primary Viewable as the thumbnail. Creates `LCSColor` if missing. |
|
|
733
713
|
* | `custom-entity` | `LCSRevisableEntity` | Syncs on create / update. Creates `LCSRevisableEntity` if missing. Workflows should gate on the custom-entity subtype so only the intended subtypes publish. |
|
|
734
|
-
* | `custom-entity` (`custom-entity:country`) | `LCSCountry` | Built-in defaults for type path (`Country`), identifier (`vibeIQIdentifier`, same key in both systems) and name; the mapping section overrides each default
|
|
714
|
+
* | `custom-entity` (`custom-entity:country`) | `LCSCountry` | Built-in defaults for type path (`Country`), identifier (`vibeIQIdentifier`, same key in both systems) and name; the mapping section overrides each default. Syncs on create / update. Creates `LCSCountry` if missing. Workflows should gate on the custom-entity subtype so only the intended subtypes publish. |
|
|
735
715
|
* | `project-item` | `LCSProductSeasonLink` / `LCSSKUSeasonLink` | Syncs on plan publish. Requires the assortment to have `publishToFlexPLM=true` and `flexPLMSeasonName` set. Adds the Product / SKU to the `LCSSeason` if not already linked. Only project-items changed since the last publish are sent (configurable to reach further back). Processing on the FlexPLM side is async via a Windchill Queue. |
|
|
736
716
|
* | `item:material` | `LCSMaterial` | Supported via custom mapping section + `typeConversion`. Route `item:material` typePath to a mapping section that declares `getClass: () => 'LCSMaterial'` and `getSoftType: () => 'Material'`. See {@link TypeConversion} for the `LCSMaterial.processAsItem` config flag that pairs with this. |
|
|
737
717
|
* | `custom-entity` | `LCSLast` / `LCSLifecycleManaged` | **Not yet supported.** |
|
|
@@ -749,7 +729,7 @@ interface MappingFileBase {
|
|
|
749
729
|
* | `LCSLifecycleManaged` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
750
730
|
* | `LCSMaterial` | `custom-entity` (default) or `item:material` | Default: custom-entity. To route to `item:material` instead, set `{ "LCSMaterial": { "processAsItem": true } }` in the connector app config and write a mapping section with `getClass: () => 'item'` + `getSoftType: () => 'item:material'`. Wire it via `flex2vibe.LCSMaterial.getMapKey` returning that section's name. |
|
|
751
731
|
* | `LCSRevisableEntity` | `custom-entity` | Configurable to create new custom-entities; updates existing. |
|
|
752
|
-
* | `LCSCountry` | `custom-entity` (`custom-entity:country`) | Built-in defaults for type path and identifier
|
|
732
|
+
* | `LCSCountry` | `custom-entity` (`custom-entity:country`) | Built-in defaults for type path and identifier (`vibeIQIdentifier`, same key in both systems). Updates existing Countries; configurable to create new ones with `isInboundCreatable` (for example `LCSCountry: { isInboundCreatable: () => true }`). |
|
|
753
733
|
* | (any) | `item` (direct, no link) | **Not yet supported.** |
|
|
754
734
|
*
|
|
755
735
|
* @example
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { MapFileUtil } from '@contrail/transform-data';
|
|
2
|
-
import { OriginSystem } from '../interfaces/mapping-file';
|
|
3
2
|
/** This class is helper functions to get the data for converting
|
|
4
3
|
* VibeIQ entities to / from FlexPLM Objects
|
|
5
4
|
*/
|
|
@@ -148,21 +147,6 @@ export declare class TypeConversionUtils {
|
|
|
148
147
|
static getMapKeyFromObject(fileId: any, mapFileUtil: MapFileUtil, object: any, direction: string): Promise<string | undefined>;
|
|
149
148
|
static isInboundCreatableFromObject(fileId: string, mapFileUtil: MapFileUtil, object: any, context?: any): Promise<boolean>;
|
|
150
149
|
static isOutboundCreatableFromEntity(fileId: string, mapFileUtil: MapFileUtil, entity: any, context?: any): Promise<boolean>;
|
|
151
|
-
/** Returns which system originates (creates) an entity type, declared in its mapping section
|
|
152
|
-
* with the `originSystem` field: 'flexplm' (FlexPLM creates) or 'vibeiq' (VibeIQ creates).
|
|
153
|
-
* Case and surrounding whitespace are ignored. Anything else is undeclared and returns undefined:
|
|
154
|
-
* no mapping file, no section, no field, or a value that is not 'flexplm' / 'vibeiq'. There is no
|
|
155
|
-
* default, so callers can fail safe (treat the entity as read-only) when it is not declared.
|
|
156
|
-
*
|
|
157
|
-
* The value is static and type-level: it does not read the record, and it does not consult the
|
|
158
|
-
* isInboundCreatable / isOutboundCreatable gates.
|
|
159
|
-
*
|
|
160
|
-
* @param fileId id for mapFile
|
|
161
|
-
* @param mapFileUtil class to get mapfile
|
|
162
|
-
* @param mapSectionKey key of the map section that declares the origin. Ex: 'LCSCountry'
|
|
163
|
-
* @returns Promise<'flexplm' | 'vibeiq' | undefined>
|
|
164
|
-
*/
|
|
165
|
-
static getOriginSystem(fileId: string, mapFileUtil: MapFileUtil, mapSectionKey: string): Promise<OriginSystem | undefined>;
|
|
166
150
|
/** Takes in a FlexPLM object and determines whether inbound
|
|
167
151
|
* images should be synced. In most cases, the creation owning system
|
|
168
152
|
* will also control image syncing. Defaults to false if no mapping exists.
|
|
@@ -360,37 +360,6 @@ class TypeConversionUtils {
|
|
|
360
360
|
}
|
|
361
361
|
return isOutboundCreatable;
|
|
362
362
|
}
|
|
363
|
-
/** Returns which system originates (creates) an entity type, declared in its mapping section
|
|
364
|
-
* with the `originSystem` field: 'flexplm' (FlexPLM creates) or 'vibeiq' (VibeIQ creates).
|
|
365
|
-
* Case and surrounding whitespace are ignored. Anything else is undeclared and returns undefined:
|
|
366
|
-
* no mapping file, no section, no field, or a value that is not 'flexplm' / 'vibeiq'. There is no
|
|
367
|
-
* default, so callers can fail safe (treat the entity as read-only) when it is not declared.
|
|
368
|
-
*
|
|
369
|
-
* The value is static and type-level: it does not read the record, and it does not consult the
|
|
370
|
-
* isInboundCreatable / isOutboundCreatable gates.
|
|
371
|
-
*
|
|
372
|
-
* @param fileId id for mapFile
|
|
373
|
-
* @param mapFileUtil class to get mapfile
|
|
374
|
-
* @param mapSectionKey key of the map section that declares the origin. Ex: 'LCSCountry'
|
|
375
|
-
* @returns Promise<'flexplm' | 'vibeiq' | undefined>
|
|
376
|
-
*/
|
|
377
|
-
static async getOriginSystem(fileId, mapFileUtil, mapSectionKey) {
|
|
378
|
-
if (!fileId || !mapSectionKey) {
|
|
379
|
-
return undefined;
|
|
380
|
-
}
|
|
381
|
-
try {
|
|
382
|
-
const mapData = await map_utils_1.MapUtil.getFullMapSection(fileId, mapFileUtil, mapSectionKey);
|
|
383
|
-
const value = mapData?.['originSystem'];
|
|
384
|
-
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
385
|
-
if (normalized === 'flexplm' || normalized === 'vibeiq') {
|
|
386
|
-
return normalized;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
catch (e) {
|
|
390
|
-
console.error('TypeConversionUtils.getOriginSystem: unable to read the origin from the mapping file: ' + e?.message);
|
|
391
|
-
}
|
|
392
|
-
return undefined;
|
|
393
|
-
}
|
|
394
363
|
/** Takes in a FlexPLM object and determines whether inbound
|
|
395
364
|
* images should be synced. In most cases, the creation owning system
|
|
396
365
|
* will also control image syncing. Defaults to false if no mapping exists.
|
|
@@ -963,84 +963,22 @@ describe('conversion-utils', () => {
|
|
|
963
963
|
const entity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
|
|
964
964
|
expect(await type_conversion_utils_1.TypeConversionUtils.getIdentifierProperties(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toEqual(['isoCode']);
|
|
965
965
|
});
|
|
966
|
-
});
|
|
967
|
-
describe('getOriginSystem (originSystem field on the LCSCountry mapping section)', () => {
|
|
968
|
-
const mapFileUtil = new transform_data_1.MapFileUtil(new sdk_1.Entities());
|
|
969
|
-
let spy;
|
|
970
|
-
function useMap(countrySection) {
|
|
971
|
-
const map = { typeConversion: { vibe2flex: { 'custom-entity': { getMapKey: () => '' } }, flex2vibe: {} } };
|
|
972
|
-
if (countrySection) {
|
|
973
|
-
map.LCSCountry = countrySection;
|
|
974
|
-
}
|
|
975
|
-
spy.mockImplementation(async () => map);
|
|
976
|
-
}
|
|
977
|
-
beforeEach(() => {
|
|
978
|
-
spy = jest.spyOn(mapFileUtil, 'getMapFile');
|
|
979
|
-
});
|
|
980
|
-
afterEach(() => {
|
|
981
|
-
spy.mockRestore();
|
|
982
|
-
});
|
|
983
|
-
it('should return flexplm when the section declares originSystem flexplm', async () => {
|
|
984
|
-
useMap({ originSystem: 'flexplm' });
|
|
985
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBe('flexplm');
|
|
986
|
-
});
|
|
987
|
-
it('should return vibeiq when the section declares originSystem vibeiq', async () => {
|
|
988
|
-
useMap({ originSystem: 'vibeiq' });
|
|
989
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBe('vibeiq');
|
|
990
|
-
});
|
|
991
|
-
it('should ignore case and surrounding whitespace in the declared value', async () => {
|
|
992
|
-
useMap({ originSystem: ' FlexPLM ' });
|
|
993
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBe('flexplm');
|
|
994
|
-
useMap({ originSystem: 'VibeIQ' });
|
|
995
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBe('vibeiq');
|
|
996
|
-
});
|
|
997
|
-
it('should return undefined when the value is not flexplm or vibeiq', async () => {
|
|
998
|
-
for (const value of ['sap', '', ' ', 5, null, {}, ['flexplm']]) {
|
|
999
|
-
useMap({ originSystem: value });
|
|
1000
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBeUndefined();
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
it('should return undefined when the section does not declare originSystem', async () => {
|
|
1004
|
-
useMap({ getIdentifierProperties: () => ['vibeIQIdentifier'] });
|
|
1005
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBeUndefined();
|
|
1006
|
-
});
|
|
1007
|
-
it('should return undefined when there is no LCSCountry section', async () => {
|
|
1008
|
-
useMap();
|
|
1009
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBeUndefined();
|
|
1010
|
-
});
|
|
1011
|
-
it('should return undefined when there is no mapping file id or section key', async () => {
|
|
1012
|
-
useMap({ originSystem: 'flexplm' });
|
|
1013
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem('', mapFileUtil, 'LCSCountry')).toBeUndefined();
|
|
1014
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, '')).toBeUndefined();
|
|
1015
|
-
});
|
|
1016
|
-
it('should not consult the creation gates, only the originSystem field', async () => {
|
|
1017
|
-
useMap({ isInboundCreatable: () => true, isOutboundCreatable: () => false });
|
|
1018
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBeUndefined();
|
|
1019
|
-
useMap({ originSystem: 'vibeiq', isInboundCreatable: () => true, isOutboundCreatable: () => false });
|
|
1020
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBe('vibeiq');
|
|
1021
|
-
});
|
|
1022
|
-
it('should read the section named by the key argument', async () => {
|
|
1023
|
-
const map = { typeConversion: {}, countryMapping: { originSystem: 'vibeiq' } };
|
|
1024
|
-
spy.mockImplementation(async () => map);
|
|
1025
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'countryMapping')).toBe('vibeiq');
|
|
1026
|
-
});
|
|
1027
|
-
it('should return undefined and log when the mapping file cannot be read', async () => {
|
|
1028
|
-
spy.mockImplementation(async () => {
|
|
1029
|
-
throw new Error('boom');
|
|
1030
|
-
});
|
|
1031
|
-
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
1032
|
-
try {
|
|
1033
|
-
expect(await type_conversion_utils_1.TypeConversionUtils.getOriginSystem(TRANSFORM_MAP_FILE, mapFileUtil, 'LCSCountry')).toBeUndefined();
|
|
1034
|
-
expect(errorSpy).toHaveBeenCalled();
|
|
1035
|
-
}
|
|
1036
|
-
finally {
|
|
1037
|
-
errorSpy.mockRestore();
|
|
1038
|
-
}
|
|
1039
|
-
});
|
|
1040
966
|
it('should leave the section key of a Country unchanged when the org custom entity routing returns an empty key', async () => {
|
|
1041
|
-
useMap({ originSystem: 'flexplm' });
|
|
1042
967
|
const country = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
|
|
1043
968
|
expect(await type_conversion_utils_1.TypeConversionUtils.getMapKey(TRANSFORM_MAP_FILE, mapFileUtil, country, type_conversion_utils_1.TypeConversionUtils.VIBE2FLEX_DIRECTION)).toEqual('');
|
|
1044
969
|
});
|
|
970
|
+
it('should not allow inbound creation of a Country when the mapping has no isInboundCreatable gate', async () => {
|
|
971
|
+
const object = { flexPLMObjectClass: 'LCSCountry', flexPLMTypePath: 'Country' };
|
|
972
|
+
expect(await type_conversion_utils_1.TypeConversionUtils.isInboundCreatableFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toBe(false);
|
|
973
|
+
});
|
|
974
|
+
it('should allow inbound creation of a Country when the LCSCountry section sets isInboundCreatable', async () => {
|
|
975
|
+
spy.mockImplementation(async () => ({ ...mapWithoutCountry, LCSCountry: { isInboundCreatable: () => true } }));
|
|
976
|
+
const object = { flexPLMObjectClass: 'LCSCountry', flexPLMTypePath: 'Country' };
|
|
977
|
+
expect(await type_conversion_utils_1.TypeConversionUtils.isInboundCreatableFromObject(TRANSFORM_MAP_FILE, mapFileUtil, object)).toBe(true);
|
|
978
|
+
});
|
|
979
|
+
it('should allow outbound creation of a Country by default', async () => {
|
|
980
|
+
const entity = { entityType: 'custom-entity', typePath: 'custom-entity:country' };
|
|
981
|
+
expect(await type_conversion_utils_1.TypeConversionUtils.isOutboundCreatableFromEntity(TRANSFORM_MAP_FILE, mapFileUtil, entity)).toBe(true);
|
|
982
|
+
});
|
|
1045
983
|
});
|
|
1046
984
|
});
|
|
@@ -20,7 +20,7 @@ export declare class TypeDefaults {
|
|
|
20
20
|
static applyConfig(config: any): void;
|
|
21
21
|
static isPropertyTrue(value: any): boolean;
|
|
22
22
|
/** True when the VibeIQ entity is a Country: a custom-entity whose typePath is
|
|
23
|
-
* 'custom-entity:country'
|
|
23
|
+
* exactly 'custom-entity:country'.
|
|
24
24
|
*
|
|
25
25
|
* @param entity VibeIQ entity
|
|
26
26
|
* @returns boolean
|
|
@@ -19,14 +19,14 @@ class TypeDefaults {
|
|
|
19
19
|
return value === true || (typeof value === 'string' && value.toLowerCase() === 'true');
|
|
20
20
|
}
|
|
21
21
|
/** True when the VibeIQ entity is a Country: a custom-entity whose typePath is
|
|
22
|
-
* 'custom-entity:country'
|
|
22
|
+
* exactly 'custom-entity:country'.
|
|
23
23
|
*
|
|
24
24
|
* @param entity VibeIQ entity
|
|
25
25
|
* @returns boolean
|
|
26
26
|
*/
|
|
27
27
|
static isCountryEntity(entity) {
|
|
28
28
|
const typePath = entity?.['typePath'];
|
|
29
|
-
return typeof typePath === 'string' && typePath
|
|
29
|
+
return typeof typePath === 'string' && typePath === TypeDefaults.COUNTRY_VIBE_TYPE_PATH;
|
|
30
30
|
}
|
|
31
31
|
/**Takes in full entity and returs the default FlexPLM
|
|
32
32
|
* object class.
|
|
@@ -50,12 +50,12 @@ describe('Type Defaults', () => {
|
|
|
50
50
|
const objectClass = type_defaults_1.TypeDefaults.getDefaultObjectClass(newData);
|
|
51
51
|
expect(objectClass).toEqual('LCSCountry');
|
|
52
52
|
});
|
|
53
|
-
it('should return
|
|
53
|
+
it('should return LCSRevisableEntity when the Country typePath uses different casing', () => {
|
|
54
54
|
const newData = {
|
|
55
55
|
entityType: 'custom-entity',
|
|
56
56
|
typePath: 'custom-entity:Country'
|
|
57
57
|
};
|
|
58
|
-
expect(type_defaults_1.TypeDefaults.getDefaultObjectClass(newData)).toEqual('
|
|
58
|
+
expect(type_defaults_1.TypeDefaults.getDefaultObjectClass(newData)).toEqual('LCSRevisableEntity');
|
|
59
59
|
});
|
|
60
60
|
it('should return LCSRevisableEntity for a custom entity that is not a Country', () => {
|
|
61
61
|
const newData = {
|
|
@@ -347,6 +347,9 @@ describe('Type Defaults', () => {
|
|
|
347
347
|
it('should return true for the custom-entity:country type path', () => {
|
|
348
348
|
expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:country' })).toBe(true);
|
|
349
349
|
});
|
|
350
|
+
it('should return false when the typePath differs only in casing', () => {
|
|
351
|
+
expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:Country' })).toBe(false);
|
|
352
|
+
});
|
|
350
353
|
it('should return false for another subtype, a missing typePath, or an undefined entity', () => {
|
|
351
354
|
expect(type_defaults_1.TypeDefaults.isCountryEntity({ typePath: 'custom-entity:other' })).toBe(false);
|
|
352
355
|
expect(type_defaults_1.TypeDefaults.isCountryEntity({})).toBe(false);
|
package/lib/util/type-utils.js
CHANGED
|
@@ -15,6 +15,11 @@ class TypeUtils {
|
|
|
15
15
|
id,
|
|
16
16
|
relations: ['typeProperties', 'typeInterfaces']
|
|
17
17
|
});
|
|
18
|
+
// The API answers 200 with an empty body for a missing/deleted type, which the SDK returns as null.
|
|
19
|
+
if (!type) {
|
|
20
|
+
throw new Error('getTypeById: type not found for typeId: ' + id
|
|
21
|
+
+ '. The entity references a type that no longer exists.');
|
|
22
|
+
}
|
|
18
23
|
return type;
|
|
19
24
|
}
|
|
20
25
|
async getByRootAndPath(options) {
|
|
@@ -53,9 +53,9 @@ describe('TypeUtils.getEventObjectClass() tests', () => {
|
|
|
53
53
|
const objectClass = tu.getEventObjectClass(entityType, newData);
|
|
54
54
|
expect(objectClass).toEqual('LCSCountry');
|
|
55
55
|
});
|
|
56
|
-
it('should return
|
|
56
|
+
it('should return LCSRevisableEntity when the Country typePath uses different casing', () => {
|
|
57
57
|
const objectClass = tu.getEventObjectClass('custom-entity', { typePath: 'custom-entity:Country' });
|
|
58
|
-
expect(objectClass).toEqual('
|
|
58
|
+
expect(objectClass).toEqual('LCSRevisableEntity');
|
|
59
59
|
});
|
|
60
60
|
it('should return LCSRevisableEntity for a custom entity that is not a Country', () => {
|
|
61
61
|
const objectClass = tu.getEventObjectClass('custom-entity', { typePath: 'custom-entity:exchangeRate' });
|
|
@@ -215,3 +215,16 @@ describe('filterProperties-color', () => {
|
|
|
215
215
|
expect(filteredProps.length).toEqual(len);
|
|
216
216
|
});
|
|
217
217
|
});
|
|
218
|
+
describe('TypeUtils.getTypeById() tests', () => {
|
|
219
|
+
it('should throw an error containing the missing type id when the type does not exist', async () => {
|
|
220
|
+
const typeUtils = new type_utils_1.TypeUtils();
|
|
221
|
+
typeUtils['typesObj'] = { getType: jest.fn().mockResolvedValue(null) };
|
|
222
|
+
await expect(typeUtils.getTypeById('missingTypeId')).rejects.toThrow('type not found for typeId: missingTypeId');
|
|
223
|
+
});
|
|
224
|
+
it('should return the same type object when the type exists', async () => {
|
|
225
|
+
const typeUtils = new type_utils_1.TypeUtils();
|
|
226
|
+
const type = { id: 'typeId', typeProperties: [] };
|
|
227
|
+
typeUtils['typesObj'] = { getType: jest.fn().mockResolvedValue(type) };
|
|
228
|
+
expect(await typeUtils.getTypeById('typeId')).toBe(type);
|
|
229
|
+
});
|
|
230
|
+
});
|