Skip to content

Commit 02fb509

Browse files
chore: resolved comments and added test cases
1 parent d213718 commit 02fb509

File tree

4 files changed

+305
-11
lines changed

4 files changed

+305
-11
lines changed

src/commands/omnistudio/migration/migrate.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@ import { YES_SHORT, YES_LONG, NO_SHORT, NO_LONG } from '../../../utils/projectPa
3131
import { PostMigrate } from '../../../migration/postMigrate';
3232
import { PreMigrate } from '../../../migration/premigrate';
3333
import { GlobalAutoNumberMigrationTool } from '../../../migration/globalautonumber';
34-
import { initializeDataModelService, isStandardDataModel } from '../../../utils/dataModelService';
34+
import {
35+
getFieldKeyForOmniscript,
36+
initializeDataModelService,
37+
isStandardDataModel,
38+
} from '../../../utils/dataModelService';
3539
import { NameMappingRegistry } from '../../../migration/NameMappingRegistry';
3640
import { ValidatorService } from '../../../utils/validatorService';
37-
import OmniScriptMappings from '../../../mappings/OmniScript';
3841

3942
// Initialize Messages with the current plugin directory
4043
Messages.importMessagesDirectory(__dirname);
@@ -596,7 +599,7 @@ export default class Migrate extends OmniStudioBaseCommand {
596599
const angular: any[] = [];
597600

598601
for (const omniscript of omniscripts) {
599-
const isLwcEnabled = omniscript[this.getFieldKeyForOmniscript(namespace, 'IsLwcEnabled__c')];
602+
const isLwcEnabled = omniscript[getFieldKeyForOmniscript(namespace, 'IsLwcEnabled__c')];
600603
if (isLwcEnabled) {
601604
lwc.push(omniscript);
602605
} else {
@@ -732,9 +735,4 @@ export default class Migrate extends OmniStudioBaseCommand {
732735
}
733736
});
734737
}
735-
736-
private getFieldKeyForOmniscript(namespacePrefix: string, fieldName: string): string {
737-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
738-
return isStandardDataModel() ? OmniScriptMappings[fieldName] : namespacePrefix + '__' + fieldName;
739-
}
740738
}

src/migration/omniscript.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,7 +1293,7 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
12931293
for (let element of elements) {
12941294
if (element[this.getElementFieldKey('Level__c')] === levelCount) {
12951295
let elementId = element['Id'];
1296-
let elementParentId = element[this.getElementFieldKey['ParentElementId__c']];
1296+
let elementParentId = element[this.getElementFieldKey('ParentElementId__c')];
12971297
if (
12981298
!elementsUploadInfo.has(elementId) &&
12991299
(!elementParentId || (elementParentId && elementsUploadInfo.has(elementParentId)))
@@ -1338,10 +1338,9 @@ export class OmniScriptMigrationTool extends BaseMigrationTool implements Migrat
13381338

13391339
elementRecord['Id'] = standardElementId;
13401340
elementRecord['OmniProcessId'] = standardOmniProcessId;
1341-
elementsUploadResponse[standardElementId] = response;
1341+
elementsUploadResponse.set(standardElementId, response);
13421342
}
13431343
}
1344-
13451344
// Keep appending upload Info for Elements at each level
13461345
elementsUploadInfo = new Map([
13471346
...Array.from(elementsUploadInfo.entries()),

src/utils/dataModelService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import OmniScriptMappings from '../mappings/OmniScript';
12
import { OmnistudioOrgDetails } from './orgUtils';
23
import { Constants } from './constants/stringContants';
34

@@ -51,3 +52,8 @@ export function isCustomDataModel(): boolean {
5152
const dataModel = getDataModelInfo();
5253
return dataModel === Constants.CustomDataModel;
5354
}
55+
56+
export function getFieldKeyForOmniscript(namespacePrefix: string, fieldName: string): string {
57+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
58+
return isStandardDataModel() ? OmniScriptMappings[fieldName] : namespacePrefix + '__' + fieldName;
59+
}

test/migration/omniscript-standard-datamodel.test.ts

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { OmniScriptMigrationTool, OmniScriptExportType } from '../../src/migrati
44
import { NameMappingRegistry } from '../../src/migration/NameMappingRegistry';
55
import { initializeDataModelService } from '../../src/utils/dataModelService';
66
import { OmnistudioOrgDetails } from '../../src/utils/orgUtils';
7+
import { NetUtils } from '../../src/utils/net';
78

89
describe('OmniScript Standard Data Model (Metadata API Disabled) - Assessment and Migration', () => {
910
let omniScriptTool: OmniScriptMigrationTool;
@@ -565,6 +566,246 @@ describe('OmniScript Standard Data Model (Metadata API Disabled) - Assessment an
565566
});
566567
});
567568

569+
describe('Standard Data Model - Multi-Level Element Hierarchy Processing', () => {
570+
it('should properly handle hierarchical elements with parent-child relationships in standard data model', async () => {
571+
// This test specifically catches the Map[property] vs Map.set() bug in uploadAllElements
572+
const mockElements = [
573+
// Level 0: Root Step element
574+
{
575+
Id: 'step001',
576+
Name: 'CustomerInfoStep',
577+
Type: 'Step',
578+
PropertySetConfig: JSON.stringify({
579+
label: 'Customer Information',
580+
}),
581+
Level: 0,
582+
ParentElementId: null,
583+
OmniProcessId: 'op123',
584+
},
585+
// Level 1: Integration Procedure child of Step
586+
{
587+
Id: 'ip001',
588+
Name: 'GetCustomerData',
589+
Type: 'Integration Procedure Action',
590+
PropertySetConfig: JSON.stringify({
591+
integrationProcedureKey: 'API-Gateway_Customer@Info!',
592+
timeout: 30,
593+
}),
594+
Level: 1,
595+
ParentElementId: 'step001',
596+
OmniProcessId: 'op123',
597+
},
598+
// Level 1: DataRaptor child of Step
599+
{
600+
Id: 'dr001',
601+
Name: 'TransformCustomerData',
602+
Type: 'DataRaptor Transform Action',
603+
PropertySetConfig: JSON.stringify({
604+
bundle: 'Customer-Data@Loader!',
605+
inputType: 'JSON',
606+
}),
607+
Level: 1,
608+
ParentElementId: 'step001',
609+
OmniProcessId: 'op123',
610+
},
611+
// Level 0: Another root element (Text)
612+
{
613+
Id: 'text001',
614+
Name: 'CustomerNameInput',
615+
Type: 'Text',
616+
PropertySetConfig: JSON.stringify({
617+
label: 'Customer Name',
618+
required: true,
619+
}),
620+
Level: 0,
621+
ParentElementId: null,
622+
OmniProcessId: 'op123',
623+
},
624+
// Level 1: Child of Text element
625+
{
626+
Id: 'validate001',
627+
Name: 'ValidateCustomerName',
628+
Type: 'Formula',
629+
PropertySetConfig: JSON.stringify({
630+
formula: 'LENGTH({CustomerName}) > 2',
631+
}),
632+
Level: 1,
633+
ParentElementId: 'text001',
634+
OmniProcessId: 'op123',
635+
},
636+
// Level 2: Grandchild element (child of Integration Procedure)
637+
{
638+
Id: 'nested001',
639+
Name: 'NestedValidation',
640+
Type: 'Messaging Framework',
641+
PropertySetConfig: JSON.stringify({
642+
message: 'Validating customer data...',
643+
}),
644+
Level: 2,
645+
ParentElementId: 'ip001',
646+
OmniProcessId: 'op123',
647+
},
648+
];
649+
650+
const mockUploadResult = {
651+
id: 'op123',
652+
success: true,
653+
referenceId: 'originalId',
654+
hasErrors: false,
655+
errors: [],
656+
warnings: [],
657+
newName: 'TestOmniProcess',
658+
skipped: false,
659+
};
660+
661+
// Mock NetUtils.updateOne to simulate successful updates for standard data model
662+
const mockUpdateResponses = new Map([
663+
[
664+
'step001',
665+
{ id: 'step001', success: true, referenceId: 'step001', hasErrors: false, errors: [], warnings: [] },
666+
],
667+
['ip001', { id: 'ip001', success: true, referenceId: 'ip001', hasErrors: false, errors: [], warnings: [] }],
668+
['dr001', { id: 'dr001', success: true, referenceId: 'dr001', hasErrors: false, errors: [], warnings: [] }],
669+
[
670+
'text001',
671+
{ id: 'text001', success: true, referenceId: 'text001', hasErrors: false, errors: [], warnings: [] },
672+
],
673+
[
674+
'validate001',
675+
{ id: 'validate001', success: true, referenceId: 'validate001', hasErrors: false, errors: [], warnings: [] },
676+
],
677+
[
678+
'nested001',
679+
{ id: 'nested001', success: true, referenceId: 'nested001', hasErrors: false, errors: [], warnings: [] },
680+
],
681+
]);
682+
683+
// Mock NetUtils.updateOne method
684+
const originalUpdateOne = NetUtils.updateOne.bind(NetUtils);
685+
NetUtils.updateOne = async (connection, objectName, referenceId, recordId) => {
686+
return (
687+
mockUpdateResponses.get(recordId) || {
688+
success: false,
689+
errors: ['Mock update failed'],
690+
referenceId,
691+
hasErrors: true,
692+
warnings: [],
693+
}
694+
);
695+
};
696+
697+
try {
698+
// Call uploadAllElements directly to test the hierarchical processing
699+
const result = await (omniScriptTool as any).uploadAllElements(mockUploadResult, mockElements);
700+
701+
// Verify that ALL elements were processed and stored correctly
702+
expect(result).to.be.instanceOf(Map);
703+
expect(result.size).to.equal(6, 'Should have processed all 6 elements');
704+
705+
// Verify each element was processed at the correct level
706+
// Level 0 elements should be processed first
707+
expect(result.has('step001')).to.be.true;
708+
expect(result.has('text001')).to.be.true;
709+
710+
// Level 1 elements should be processed after their parents
711+
expect(result.has('ip001')).to.be.true;
712+
expect(result.has('dr001')).to.be.true;
713+
expect(result.has('validate001')).to.be.true;
714+
715+
// Level 2 elements should be processed last
716+
expect(result.has('nested001')).to.be.true;
717+
718+
// Verify the responses are correct UploadRecordResult objects
719+
const stepResult = result.get('step001');
720+
expect(stepResult.success).to.be.true;
721+
expect(stepResult.id).to.equal('step001');
722+
723+
const ipResult = result.get('ip001');
724+
expect(ipResult.success).to.be.true;
725+
expect(ipResult.id).to.equal('ip001');
726+
727+
const nestedResult = result.get('nested001');
728+
expect(nestedResult.success).to.be.true;
729+
expect(nestedResult.id).to.equal('nested001');
730+
731+
// This test would FAIL with the original bug because:
732+
// elementsUploadResponse[standardElementId] = response would add properties to the Map object
733+
// but Array.from(elementsUploadResponse.entries()) would return empty array
734+
// so elementsUploadInfo would only contain elements from previous levels, not current level
735+
} finally {
736+
// Restore original method
737+
NetUtils.updateOne = originalUpdateOne;
738+
}
739+
});
740+
741+
it('should handle dependency mapping in hierarchical elements for standard data model', () => {
742+
const mockElements = [
743+
// Parent Step with nested dependencies
744+
{
745+
Id: 'step002',
746+
Name: 'ProcessingStep',
747+
Type: 'Step',
748+
PropertySetConfig: JSON.stringify({
749+
label: 'Data Processing Step',
750+
}),
751+
Level: 0,
752+
ParentElementId: null,
753+
OmniProcessId: 'op124',
754+
},
755+
// Child IP Action with special character dependencies
756+
{
757+
Id: 'ip002',
758+
Name: 'CustomerProcessing',
759+
Type: 'Integration Procedure Action',
760+
PropertySetConfig: JSON.stringify({
761+
integrationProcedureKey: 'API-Gateway_Customer@Info!',
762+
preTransformBundle: 'Customer-Data@Loader!',
763+
postTransformBundle: 'Product#Info$Extractor',
764+
remoteOptions: {
765+
preTransformBundle: 'Customer-Data@Loader!',
766+
postTransformBundle: 'Product#Info$Extractor',
767+
},
768+
}),
769+
Level: 1,
770+
ParentElementId: 'step002',
771+
OmniProcessId: 'op124',
772+
},
773+
// Child DR Action
774+
{
775+
Id: 'dr002',
776+
Name: 'DataExtraction',
777+
Type: 'DataRaptor Extract Action',
778+
PropertySetConfig: JSON.stringify({
779+
bundle: 'Customer-Data@Loader!',
780+
}),
781+
Level: 1,
782+
ParentElementId: 'step002',
783+
OmniProcessId: 'op124',
784+
},
785+
];
786+
787+
// Test element mapping with dependencies
788+
const ipResult = (omniScriptTool as any).mapElementData(mockElements[1], 'op124', new Map(), new Map());
789+
const drResult = (omniScriptTool as any).mapElementData(mockElements[2], 'op124', new Map(), new Map());
790+
791+
// Verify IP Action dependency mapping
792+
const ipPropertySet = JSON.parse(ipResult.PropertySetConfig);
793+
expect(ipPropertySet.integrationProcedureKey).to.equal('APIGateway_CustomerInfo');
794+
expect(ipPropertySet.preTransformBundle).to.equal('CustomerDataLoader');
795+
expect(ipPropertySet.postTransformBundle).to.equal('ProductInfoExtractor');
796+
expect(ipPropertySet.remoteOptions.preTransformBundle).to.equal('CustomerDataLoader');
797+
expect(ipPropertySet.remoteOptions.postTransformBundle).to.equal('ProductInfoExtractor');
798+
799+
// Verify DR Action dependency mapping
800+
const drPropertySet = JSON.parse(drResult.PropertySetConfig);
801+
expect(drPropertySet.bundle).to.equal('CustomerDataLoader');
802+
803+
// Verify OmniProcessId is set correctly for standard data model
804+
expect(ipResult.OmniProcessId).to.equal('op124');
805+
expect(drResult.OmniProcessId).to.equal('op124');
806+
});
807+
});
808+
568809
describe('Standard Data Model - Error Scenarios and Edge Cases', () => {
569810
it('should handle empty or null dependency references', () => {
570811
const mockElementRecord = {
@@ -585,5 +826,55 @@ describe('OmniScript Standard Data Model (Metadata API Disabled) - Assessment an
585826
expect(propertySet.integrationProcedureKey).to.equal('');
586827
expect(propertySet.preTransformBundle).to.equal(null);
587828
});
829+
830+
it('should handle element processing failure in hierarchical structure', async () => {
831+
const mockElements = [
832+
{
833+
Id: 'step003',
834+
Name: 'FailingStep',
835+
Type: 'Step',
836+
PropertySetConfig: JSON.stringify({}),
837+
Level: 0,
838+
ParentElementId: null,
839+
OmniProcessId: 'op125',
840+
},
841+
];
842+
843+
const mockUploadResult = {
844+
id: 'op125',
845+
success: true,
846+
referenceId: 'originalId',
847+
hasErrors: false,
848+
errors: [],
849+
warnings: [],
850+
newName: 'TestFailingProcess',
851+
skipped: false,
852+
};
853+
854+
// Mock NetUtils.updateOne to simulate failure
855+
const originalUpdateOne = NetUtils.updateOne.bind(NetUtils);
856+
NetUtils.updateOne = async (connection, objectName, referenceId) => ({
857+
success: false,
858+
errors: ['Simulated update failure'],
859+
hasErrors: true,
860+
referenceId,
861+
warnings: [],
862+
});
863+
864+
try {
865+
const result = await (omniScriptTool as any).uploadAllElements(mockUploadResult, mockElements);
866+
867+
// Should still return a Map even if updates fail
868+
expect(result).to.be.instanceOf(Map);
869+
expect(result.size).to.equal(1);
870+
871+
const failedResult = result.get('step003');
872+
expect(failedResult.success).to.be.false;
873+
expect(failedResult.errors).to.include('Simulated update failure');
874+
} finally {
875+
// Restore original method
876+
NetUtils.updateOne = originalUpdateOne;
877+
}
878+
});
588879
});
589880
});

0 commit comments

Comments
 (0)