diff --git a/messages/assess.json b/messages/assess.json index 883c5a12..de94e23d 100644 --- a/messages/assess.json +++ b/messages/assess.json @@ -117,6 +117,7 @@ "operationCancelled": "Operation cancelled.", "invalidYesNoResponse": "Invalid response. Please answer y or n.", "notSfdxProjectFolderPath": "Provided folder is not a valid Salesforce DX project. Please select a folder containing sfdx-project.json", + "restrictedFolderName": "Restricted folder name: %s. Do not use 'labels', 'messageChannels', or 'lwc'. Try again with a different name.", "errorRunningAssess": "Assessment process failed reason : %s", "enableVerboseOutput": "Enable verbose output", "apexFileChangesIdentifiedNotApplied": "Changes were identified but not applied for the %s Apex class when the assessment mode was running.", diff --git a/messages/migrate.json b/messages/migrate.json index b02d2db5..11b94942 100644 --- a/messages/migrate.json +++ b/messages/migrate.json @@ -46,6 +46,7 @@ "notEmptyProjectFolderPath": "Provided project folder is not empty. Please provide a valid empty project folder name and path", "invalidYesNoResponse": "Invalid response. Please answer y or n.", "notSfdxProjectFolderPath": "Provided folder is not a valid Salesforce DX project. Please select a folder containing sfdx-project.json", + "restrictedFolderName": "Restricted folder name: %s. Do not use 'labels', 'messageChannels', or 'lwc'. Try again with a different name.", "operationCancelled": "Operation cancelled.", "errorRunningMigrate": "Migration process failed reason : %s", "exceptionSettingDesignersToStandardDataModel": "We've encountered an exception while configuring the Omnistudio standard designers to use the standard data model. Try again later.", diff --git a/src/utils/projectPathUtil.ts b/src/utils/projectPathUtil.ts index ef9377d5..b88c2606 100644 --- a/src/utils/projectPathUtil.ts +++ b/src/utils/projectPathUtil.ts @@ -158,6 +158,14 @@ export class ProjectPathUtil { return false; } + // Check if folder path ends with restricted names (case insensitive) + const restrictedFolderNames = ['labels', 'messagechannels', 'lwc']; + const folderName = path.basename(folderPath); + if (restrictedFolderNames.includes(folderName.toLowerCase())) { + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + return false; + } + if (mode === EMPTY_MODE && fs.readdirSync(folderPath).length > 0) { Logger.error(messages.getMessage('notEmptyProjectFolderPath')); return false; diff --git a/test/utils/projectPathUtil.test.ts b/test/utils/projectPathUtil.test.ts new file mode 100644 index 00000000..65c7ec8f --- /dev/null +++ b/test/utils/projectPathUtil.test.ts @@ -0,0 +1,184 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { Messages } from '@salesforce/core'; +import { Logger } from '../../src/utils/logger'; + +Messages.importMessagesDirectory(__dirname); +const messages = Messages.loadMessages('@salesforce/plugin-omnistudio-migration-tool', 'assess'); + +describe('ProjectPathUtil - Restricted Folder Name Validation', () => { + let loggerErrorStub: sinon.SinonStub; + let fsExistsSyncStub: sinon.SinonStub; + let fsLstatSyncStub: sinon.SinonStub; + + beforeEach(() => { + loggerErrorStub = sinon.stub(Logger, 'error'); + fsExistsSyncStub = sinon.stub(fs, 'existsSync'); + fsLstatSyncStub = sinon.stub(fs, 'lstatSync'); + }); + + afterEach(() => { + loggerErrorStub.restore(); + fsExistsSyncStub.restore(); + fsLstatSyncStub.restore(); + }); + + /** + * Helper function that simulates the isValidFolderPath restricted name check + */ + function isRestrictedFolderName(folderPath: string): boolean { + const restrictedFolderNames = ['labels', 'messagechannels', 'lwc']; + const folderName = path.basename(folderPath); + return restrictedFolderNames.includes(folderName.toLowerCase()); + } + + describe('Restricted folder names', () => { + it('should reject folder path ending with "lwc"', () => { + const folderPath = '/some/path/lwc'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.calledOnce).to.be.true; + expect(loggerErrorStub.firstCall.args[0]).to.include('Restricted folder name: lwc'); + expect(loggerErrorStub.firstCall.args[0]).to.include('Try again with a different name'); + }); + + it('should reject folder path ending with "labels"', () => { + const folderPath = '/some/path/labels'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.calledOnce).to.be.true; + expect(loggerErrorStub.firstCall.args[0]).to.include('Restricted folder name: labels'); + expect(loggerErrorStub.firstCall.args[0]).to.include('Try again with a different name'); + }); + + it('should reject folder path ending with "messageChannels"', () => { + const folderPath = '/some/path/messageChannels'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.calledOnce).to.be.true; + expect(loggerErrorStub.firstCall.args[0]).to.include('Restricted folder name: messageChannels'); + expect(loggerErrorStub.firstCall.args[0]).to.include('Try again with a different name'); + }); + + it('should reject folder path with uppercase "LWC" (case insensitive)', () => { + const folderPath = '/some/path/LWC'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.calledOnce).to.be.true; + expect(loggerErrorStub.firstCall.args[0]).to.include('Restricted folder name: LWC'); + expect(loggerErrorStub.firstCall.args[0]).to.include('Try again with a different name'); + }); + + it('should reject folder path with mixed case "Labels" (case insensitive)', () => { + const folderPath = '/some/path/Labels'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.calledOnce).to.be.true; + expect(loggerErrorStub.firstCall.args[0]).to.include('Restricted folder name: Labels'); + expect(loggerErrorStub.firstCall.args[0]).to.include('Try again with a different name'); + }); + + it('should reject folder path with uppercase "MESSAGECHANNELS" (case insensitive)', () => { + const folderPath = '/some/path/MESSAGECHANNELS'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.calledOnce).to.be.true; + expect(loggerErrorStub.firstCall.args[0]).to.include('Restricted folder name: MESSAGECHANNELS'); + expect(loggerErrorStub.firstCall.args[0]).to.include('Try again with a different name'); + }); + }); + + describe('Valid folder names', () => { + it('should allow folder path ending with "myproject"', () => { + const folderPath = '/some/path/myproject'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.called).to.be.false; + }); + + it('should allow folder path ending with "src"', () => { + const folderPath = '/some/path/src'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.called).to.be.false; + }); + + it('should allow folder path containing "lwc" but not ending with it', () => { + const folderPath = '/some/lwc/myproject'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.called).to.be.false; + }); + + it('should allow folder path containing "labels" but not ending with it', () => { + const folderPath = '/some/labels/customfolder'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.called).to.be.false; + }); + + it('should allow folder name that contains restricted name as substring', () => { + const folderPath = '/some/path/mylwcproject'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.called).to.be.false; + }); + + it('should allow folder name "labelsbackup"', () => { + const folderPath = '/some/path/labelsbackup'; + + if (isRestrictedFolderName(folderPath)) { + const folderName = path.basename(folderPath); + Logger.error(messages.getMessage('restrictedFolderName', [folderName])); + } + + expect(loggerErrorStub.called).to.be.false; + }); + }); +});