Skip to content

Commit e1f2cbc

Browse files
authored
Merge pull request #384 from shaurabh-tiwari-git/apexDuplicateCall
@W-19363600 - Apex duplicate call
2 parents e58aa55 + 8c9d7c4 commit e1f2cbc

File tree

3 files changed

+233
-2
lines changed

3 files changed

+233
-2
lines changed

messages/assess.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
"experienceSiteException": "We’ve encountered an exception while processing Experience Cloud sites.",
186186
"reservedKeysFoundInPropertySet": "Reserved keys found in any of output response transformation fields: %s.",
187187
"invalidTypeAssessErrorMessage": "We couldn't assess your Omnistudio components in the %s namespace. Select the correct namespace and try again",
188+
"apexFileAlreadyHasCallMethod": "File %s already has call method, thus keeping the existing call method intact",
188189
"errorFetchingCustomLabels": "Error fetching custom labels: %s",
189190
"customLabelAssessmentSummary": "Custom Label with same name and different value is already exist without namespace.",
190191
"generatedCustomLabelAssessmentReportPage": "Generated custom label assessment report page %s of %s with %s labels"

src/migration/related/ApexMigration.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,20 @@ export class ApexMigration extends BaseRelatedObjectMigration {
199199
Logger.logger.info(assessMessages.getMessage('apexFileImplementsVlocityOpenInterface2', [file.name]));
200200
const tokens = implementsInterface.get(this.vlocityOpenInterface2);
201201
tokenUpdates.push(new RangeTokenUpdate(CALLABLE, tokens[0], tokens[1]));
202-
tokenUpdates.push(new InsertAfterTokenUpdate(this.callMethodBody(), parser.classDeclaration));
202+
if (!parser.hasCallMethodImplemented) {
203+
tokenUpdates.push(new InsertAfterTokenUpdate(this.callMethodBody(), parser.classDeclaration));
204+
} else {
205+
Logger.logger.info(assessMessages.getMessage('apexFileAlreadyHasCallMethod', [file.name]));
206+
}
203207
} else if (implementsInterface.has(this.vlocityOpenInterface)) {
204208
Logger.logger.info(assessMessages.getMessage('fileImplementsVlocityOpenInterface', [file.name]));
205209
const tokens = implementsInterface.get(this.vlocityOpenInterface);
206210
tokenUpdates.push(new RangeTokenUpdate(CALLABLE, tokens[0], tokens[1]));
207-
tokenUpdates.push(new InsertAfterTokenUpdate(this.callMethodBody(), parser.classDeclaration));
211+
if (!parser.hasCallMethodImplemented) {
212+
tokenUpdates.push(new InsertAfterTokenUpdate(this.callMethodBody(), parser.classDeclaration));
213+
} else {
214+
Logger.logger.info(assessMessages.getMessage('apexFileAlreadyHasCallMethod', [file.name]));
215+
}
208216
}
209217
return tokenUpdates;
210218
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/no-var-requires */
3+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
4+
/* eslint-disable @typescript-eslint/no-unsafe-call */
5+
/* eslint-disable @typescript-eslint/no-unsafe-return */
6+
import { expect } from '@salesforce/command/lib/test';
7+
import { Org } from '@salesforce/core';
8+
import sinon = require('sinon');
9+
import { ApexMigration } from '../../../src/migration/related/ApexMigration';
10+
import { InterfaceImplements } from '../../../src/utils/apex/parser/apexparser';
11+
12+
interface MockFileUtil {
13+
readFilesSync: sinon.SinonStub;
14+
}
15+
16+
interface MockFs {
17+
readFileSync: sinon.SinonStub;
18+
writeFileSync: sinon.SinonStub;
19+
}
20+
21+
interface MockShell {
22+
pwd: sinon.SinonStub;
23+
cd: sinon.SinonStub;
24+
}
25+
26+
interface MockCreateProgressBar {
27+
start: sinon.SinonStub;
28+
update: sinon.SinonStub;
29+
stop: sinon.SinonStub;
30+
}
31+
32+
interface MockLogger {
33+
info: sinon.SinonStub;
34+
logVerbose: sinon.SinonStub;
35+
error: sinon.SinonStub;
36+
log: sinon.SinonStub;
37+
}
38+
39+
describe('ApexMigration', () => {
40+
let apexMigration: ApexMigration;
41+
let sandbox: sinon.SinonSandbox;
42+
let mockOrg: Org;
43+
let mockFileUtil: MockFileUtil;
44+
let mockFs: MockFs;
45+
let mockShell: MockShell;
46+
let mockCreateProgressBar: MockCreateProgressBar;
47+
let mockLogger: MockLogger;
48+
49+
const testProjectPath = '/test/project/path';
50+
const testNamespace = 'testNamespace';
51+
const testTargetNamespace = 'targetNamespace';
52+
53+
beforeEach(() => {
54+
sandbox = sinon.createSandbox();
55+
56+
// Mock Org
57+
mockOrg = {
58+
getUsername: sandbox.stub().returns('[email protected]'),
59+
} as unknown as Org;
60+
61+
// Mock FileUtil
62+
mockFileUtil = {
63+
readFilesSync: sandbox.stub(),
64+
};
65+
66+
// Mock fs
67+
mockFs = {
68+
readFileSync: sandbox.stub(),
69+
writeFileSync: sandbox.stub(),
70+
};
71+
72+
// Mock shell
73+
mockShell = {
74+
pwd: sandbox.stub().returns('/current/directory'),
75+
cd: sandbox.stub(),
76+
};
77+
78+
// Mock createProgressBar
79+
mockCreateProgressBar = {
80+
start: sandbox.stub(),
81+
update: sandbox.stub(),
82+
stop: sandbox.stub(),
83+
};
84+
85+
// Mock Logger
86+
mockLogger = {
87+
info: sandbox.stub(),
88+
logVerbose: sandbox.stub(),
89+
error: sandbox.stub(),
90+
log: sandbox.stub(),
91+
};
92+
93+
// Stub dependencies
94+
sandbox.stub(require('../../../src/utils/file/fileUtil'), 'FileUtil').value(mockFileUtil);
95+
sandbox.stub(require('fs'), 'readFileSync').value(mockFs.readFileSync);
96+
sandbox.stub(require('fs'), 'writeFileSync').value(mockFs.writeFileSync);
97+
sandbox.stub(require('shelljs'), 'pwd').value(mockShell.pwd);
98+
sandbox.stub(require('shelljs'), 'cd').value(mockShell.cd);
99+
sandbox
100+
.stub(require('../../../src/migration/base'), 'createProgressBar')
101+
.value(sandbox.stub().returns(mockCreateProgressBar));
102+
103+
// Mock Logger class and its static logger property
104+
const MockLogger = {
105+
logger: mockLogger,
106+
info: mockLogger.info,
107+
logVerbose: mockLogger.logVerbose,
108+
error: mockLogger.error,
109+
log: mockLogger.log,
110+
};
111+
sandbox.stub(require('../../../src/utils/logger'), 'Logger').value(MockLogger);
112+
113+
// Mock ApexASTParser constructor and methods
114+
const MockApexASTParser = function () {
115+
return {
116+
parse: sandbox.stub(),
117+
rewrite: sandbox.stub(),
118+
implementsInterfaces: new Map(),
119+
hasCallMethodImplemented: false,
120+
classDeclaration: {},
121+
namespaceChanges: new Map(),
122+
methodParameters: new Map(),
123+
nonReplacableMethodParameters: [],
124+
};
125+
};
126+
sandbox.stub(require('../../../src/utils/apex/parser/apexparser'), 'ApexASTParser').value(MockApexASTParser);
127+
128+
// Mock other dependencies
129+
sandbox.stub(require('../../../src/utils/lwcparser/fileutils/FileDiffUtil'), 'FileDiffUtil').value(function () {
130+
return {
131+
getFileDiff: sandbox.stub().returns('mock-diff'),
132+
};
133+
});
134+
135+
apexMigration = new ApexMigration(testProjectPath, testNamespace, mockOrg, testTargetNamespace);
136+
});
137+
138+
afterEach(() => {
139+
sandbox.restore();
140+
});
141+
142+
describe('replaceAllInterfaces', () => {
143+
it('should replace multiple interfaces with System.Callable when Callable is already implemented', () => {
144+
// Arrange
145+
const mockParser = {
146+
implementsInterfaces: new Map([
147+
[new InterfaceImplements('Callable', 'System'), [{ startIndex: 10, stopIndex: 20, text: 'System.Callable' }]],
148+
[
149+
new InterfaceImplements('VlocityOpenInterface2', 'testNamespace'),
150+
[{ startIndex: 5, stopIndex: 15, text: 'testNamespace.VlocityOpenInterface2' }],
151+
],
152+
]),
153+
hasCallMethodImplemented: false,
154+
classDeclaration: {},
155+
};
156+
157+
// Act
158+
const result = (apexMigration as any).replaceAllInterfaces(
159+
mockParser.implementsInterfaces,
160+
[],
161+
mockParser,
162+
'TestClass.cls'
163+
);
164+
165+
// Assert
166+
expect(result).to.be.an('array').with.length(2);
167+
expect(result[0].constructor.name).to.equal('RangeTokenUpdate');
168+
expect(result[1].constructor.name).to.equal('InsertAfterTokenUpdate');
169+
});
170+
171+
it('should handle case where leftmost and rightmost tokens are found correctly', () => {
172+
// Arrange
173+
const mockParser = {
174+
implementsInterfaces: new Map([
175+
[new InterfaceImplements('Interface1', 'ns1'), [{ startIndex: 100, stopIndex: 110, text: 'ns1.Interface1' }]],
176+
[new InterfaceImplements('Interface2', 'ns2'), [{ startIndex: 50, stopIndex: 60, text: 'ns2.Interface2' }]],
177+
]),
178+
hasCallMethodImplemented: true,
179+
classDeclaration: {},
180+
};
181+
182+
// Act
183+
const result = (apexMigration as any).replaceAllInterfaces(
184+
mockParser.implementsInterfaces,
185+
[],
186+
mockParser,
187+
'TestClass.cls'
188+
);
189+
190+
// Assert
191+
expect(result).to.be.an('array').with.length(1);
192+
expect(result[0].constructor.name).to.equal('RangeTokenUpdate');
193+
});
194+
195+
it('should not add call method when it already exists', () => {
196+
// Arrange
197+
const mockParser = {
198+
implementsInterfaces: new Map([
199+
[new InterfaceImplements('Callable', 'System'), [{ startIndex: 10, stopIndex: 20, text: 'System.Callable' }]],
200+
[
201+
new InterfaceImplements('VlocityOpenInterface2', 'testNamespace'),
202+
[{ startIndex: 5, stopIndex: 15, text: 'testNamespace.VlocityOpenInterface2' }],
203+
],
204+
]),
205+
hasCallMethodImplemented: true,
206+
classDeclaration: {},
207+
};
208+
209+
// Act
210+
const result = (apexMigration as any).replaceAllInterfaces(
211+
mockParser.implementsInterfaces,
212+
[],
213+
mockParser,
214+
'TestClass.cls'
215+
);
216+
217+
// Assert
218+
expect(result).to.be.an('array').with.length(1);
219+
expect(result[0].constructor.name).to.equal('RangeTokenUpdate');
220+
});
221+
});
222+
});

0 commit comments

Comments
 (0)