diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 5eb2685158..c1dc47a399 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -198,6 +198,8 @@ export function fileOrFolderDoesNotExist(name: string) { return localize('fileOr export function cannotResolvePath(path: string) { return localize('cannotResolvePath', "Cannot resolve path {0}", path); } export function fileAlreadyExists(filename: string) { return localize('fileAlreadyExists', "A file with the name '{0}' already exists on disk at this location. Please choose another name.", filename); } export function folderAlreadyExists(filename: string) { return localize('folderAlreadyExists', "A folder with the name '{0}' already exists on disk at this location. Please choose another name.", filename); } +export function fileAlreadyAddedToProject(filepath: string) { return localize('fileAlreadyAddedToProject', "A file with the path '{0}' has already been added to the project", filepath); } +export function folderAlreadyAddedToProject(folderpath: string) { return localize('folderAlreadyAddedToProject', "A folder with the path '{0}' has already been added to the project", folderpath); } export function invalidInput(input: string) { return localize('invalidInput', "Invalid input: {0}", input); } export function unableToCreatePublishConnection(input: string) { return localize('unableToCreatePublishConnection', "Unable to construct connection: {0}", input); } export function circularProjectReference(project1: string, project2: string) { return localize('cicularProjectReference', "Circular reference from project {0} to project {1}", project1, project2); } diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index ae815814cf..5f22318bc2 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -266,6 +266,11 @@ export class Project { public async addFolderItem(relativeFolderPath: string): Promise { const absoluteFolderPath = path.join(this.projectFolderPath, relativeFolderPath); + // check if folder already has been added to sqlproj + if (this.files.find(f => f.relativePath.toLowerCase() === relativeFolderPath.toLowerCase())) { + throw new Error(constants.folderAlreadyAddedToProject((relativeFolderPath))); + } + //If folder doesn't exist, create it let exists = await utils.exists(absoluteFolderPath); if (!exists) { @@ -292,6 +297,11 @@ export class Project { throw new Error(constants.fileAlreadyExists(path.parse(absoluteFilePath).name)); } + // check if file already has been added to sqlproj + if (this.files.find(f => f.relativePath.toLowerCase() === relativeFilePath.toLowerCase())) { + throw new Error(constants.fileAlreadyAddedToProject((relativeFilePath))); + } + // create the file if contents were passed in if (contents) { await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true }); diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts index 0a4ad9fa72..aa63235054 100644 --- a/extensions/sql-database-projects/src/test/project.test.ts +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -13,7 +13,7 @@ import * as constants from '../common/constants'; import { promises as fs } from 'fs'; import { Project, EntryType, SystemDatabase, SystemDatabaseReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/project'; -import { exists, convertSlashesForSqlProj } from '../common/utils'; +import { exists, convertSlashesForSqlProj, trimChars, trimUri } from '../common/utils'; import { Uri, window } from 'vscode'; import { IDacpacReferenceSettings, IProjectReferenceSettings, ISystemDatabaseReferenceSettings } from '../models/IDatabaseReferenceSettings'; @@ -569,6 +569,27 @@ describe('Project: sqlproj content operations', function (): void { should(newProject.noneDeployScripts.find(f => f.type === EntryType.File && f.relativePath === convertSlashesForSqlProj(postDeploymentScriptFilePath2))).not.equal(undefined, 'File Script.PostDeployment2.sql not read'); }); + + it('Should not allow adding duplicate file/folder entries in sqlproj', async function (): Promise { + projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline); + const project: Project = await Project.openProject(projFilePath); + const fileList = await testUtils.createListOfFiles(path.dirname(projFilePath)); + + // verify first entry in list is a folder + const existingFolderUri = fileList[0]; + const folderStats = await fs.stat(existingFolderUri.fsPath); + should(folderStats.isDirectory()).equal(true, 'First entry in fileList should be a folder'); + await project.addToProject([existingFolderUri]); + const folderRelativePath = trimChars(trimUri(Uri.file(projFilePath), existingFolderUri), ''); + testUtils.shouldThrowSpecificError(async () => await project.addToProject([existingFolderUri]), constants.folderAlreadyAddedToProject(folderRelativePath)); + + // verify duplicate file can't be added + const existingFileUri = fileList[1]; + const fileStats = await fs.stat(existingFileUri.fsPath); + should(fileStats.isFile()).equal(true, 'Second entry in fileList should be a file'); + const fileRelativePath = trimChars(trimUri(Uri.file(projFilePath), existingFileUri), '/'); + testUtils.shouldThrowSpecificError(async () => await project.addToProject([existingFileUri]), constants.fileAlreadyAddedToProject(fileRelativePath)); + }); }); describe('Project: add SQLCMD Variables', function (): void {