diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 8666a15cc0..54d4dbd5c8 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -109,6 +109,7 @@ const indentationFilter = [ '!extensions/sql-database-projects/src/test/baselines/*.xml', '!extensions/sql-database-projects/src/test/baselines/*.json', '!extensions/sql-database-projects/src/test/baselines/*.sqlproj', + '!extensions/sql-database-projects/BuildDirectory/SystemDacpacs/**', '!extensions/big-data-cluster/src/bigDataCluster/controller/apiGenerated.ts', '!extensions/big-data-cluster/src/bigDataCluster/controller/clusterApiGenerated2.ts', '!resources/linux/snap/electron-launch' diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/100/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/100/master.dacpac new file mode 100644 index 0000000000..8f1373d960 Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/100/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/110/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/110/master.dacpac new file mode 100644 index 0000000000..217bc9f899 Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/110/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/120/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/120/master.dacpac new file mode 100644 index 0000000000..b5025c8996 Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/120/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/130/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/130/master.dacpac new file mode 100644 index 0000000000..ad828bd4e7 Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/130/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/140/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/140/master.dacpac new file mode 100644 index 0000000000..7460294cef Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/140/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/150/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/150/master.dacpac new file mode 100644 index 0000000000..0b20121cfa Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/150/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/90/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/90/master.dacpac new file mode 100644 index 0000000000..47303bd51c Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/90/master.dacpac differ diff --git a/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/AzureV12/master.dacpac b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/AzureV12/master.dacpac new file mode 100644 index 0000000000..8294601843 Binary files /dev/null and b/extensions/sql-database-projects/BuildDirectory/SystemDacpacs/AzureV12/master.dacpac differ diff --git a/extensions/sql-database-projects/package.json b/extensions/sql-database-projects/package.json index 2eb79baad5..69fe47584c 100644 --- a/extensions/sql-database-projects/package.json +++ b/extensions/sql-database-projects/package.json @@ -113,6 +113,11 @@ "command": "sqlDatabaseProjects.importDatabase", "title": "%sqlDatabaseProjects.importDatabase%", "category": "%sqlDatabaseProjects.displayName%" + }, + { + "command": "sqlDatabaseProjects.addDatabaseReference", + "title": "%sqlDatabaseProjects.addDatabaseReference%", + "category": "%sqlDatabaseProjects.displayName%" } ], "menus": { @@ -226,6 +231,11 @@ "when": "view == sqlDatabaseProjectsView", "group": "3_dbProjects_newItem@9" }, + { + "command": "sqlDatabaseProjects.addDatabaseReference", + "when": "view == sqlDatabaseProjectsView", + "group": "4_dbProjects_addDatabaseReference" + }, { "command": "sqlDatabaseProjects.close", "when": "view == sqlDatabaseProjectsView", diff --git a/extensions/sql-database-projects/package.nls.json b/extensions/sql-database-projects/package.nls.json index 20c88a02b0..1e82ce98e9 100644 --- a/extensions/sql-database-projects/package.nls.json +++ b/extensions/sql-database-projects/package.nls.json @@ -21,6 +21,8 @@ "sqlDatabaseProjects.importDatabase": "Import New Database Project", + "sqlDatabaseProjects.addDatabaseReference": "Add Database Reference", + "sqlDatabaseProjects.Settings": "Database Projects", "sqlDatabaseProjects.netCoreInstallLocation": "Full Path to .Net Core SDK on the machine." } diff --git a/extensions/sql-database-projects/src/common/constants.ts b/extensions/sql-database-projects/src/common/constants.ts index 1af102bd61..6f35dea1a8 100644 --- a/extensions/sql-database-projects/src/common/constants.ts +++ b/extensions/sql-database-projects/src/common/constants.ts @@ -14,6 +14,10 @@ export const sqlFileExtension = '.sql'; export const schemaCompareExtensionId = 'microsoft.schema-compare'; export const sqlDatabaseProjectExtensionId = 'microsoft.sql-database-projects'; export const mssqlExtensionId = 'microsoft.mssql'; +export const dacpac = 'dacpac'; +export const master = 'master'; +export const MicrosoftDatatoolsSchemaSqlSql = 'Microsoft.Data.Tools.Schema.Sql.Sql'; +export const databaseSchemaProvider = 'DatabaseSchemaProvider'; // UI Strings @@ -26,7 +30,13 @@ export const sqlDatabaseProject = localize('sqlDatabaseProject', "SQL database p export const yesString = localize('yesString', "Yes"); export const noString = localize('noString', "No"); export const extractTargetInput = localize('extractTargetInput', "Target for extraction:"); -export const selectFileFolder = localize('selectFileFolder', "Select"); +export const selectString = localize('selectString', "Select"); +export const addDatabaseReferenceInput = localize('addDatabaseReferenceInput', "Add database reference for:"); +export const databaseReferenceLocation = localize('databaseReferenceLocation', "Database location"); +export const databaseReferenceSameDatabase = localize('databaseReferenceSameDatabase', "Same database"); +export const databaseReferenceDifferentDabaseSameServer = localize('databaseReferenceDifferentDabaseSameServer', "Different database, same server"); +export const databaseReferenceDatabaseName = localize('databaseReferenceDatabaseName', "Database name"); +export const dacpacFiles = localize('dacpacFiles', "dacpac Files"); export function newObjectNamePrompt(objectType: string) { return localize('newObjectNamePrompt', 'New {0} name:', objectType); } // Deploy dialog strings @@ -63,6 +73,11 @@ export const extractTargetRequired = localize('extractTargetRequired', "Target i export const schemaCompareNotInstalled = localize('schemaCompareNotInstalled', "Schema compare extension installation is required to run schema compare"); export const buildDacpacNotFound = localize('buildDacpacNotFound', "Dacpac created from build not found"); export const updateProjectForRoundTrip = localize('updateProjectForRoundTrip', "To build this project, Azure Data Studio needs to update targets and references. If the project is created in SSDT, it will continue to work in both tools. Do you want Azure Data Studio to update the project?"); +export const databaseReferenceTypeRequired = localize('databaseReferenceTypeRequired', "Database reference type is required for adding a reference to a database"); +export const dacpacFileLocationRequired = localize('dacpacFileLocationRequired', "Dacpac file location is required for adding a reference to a database"); +export const databaseLocationRequired = localize('databaseLocation', "Database location is required for adding a reference to a database"); +export const databaseNameRequired = localize('databaseNameRequired', "Database name is required for adding a reference to a different database"); +export const invalidDataSchemaProvider = localize('invalidDataSchemaProvider', "Invalid DSP in .sqlproj file"); export function projectAlreadyOpened(path: string) { return localize('projectAlreadyOpened', "Project '{0}' is already opened.", path); } export function projectAlreadyExists(name: string, path: string) { return localize('projectAlreadyExists', "A project named {0} already exists in {1}.", name, path); } export function noFileExist(fileName: string) { return localize('noFileExist', "File {0} doesn't exist", fileName); } @@ -91,6 +106,10 @@ export const Condition = 'Condition'; export const PackageReference = 'PackageReference'; export const Version = 'Version'; export const PrivateAssets = 'PrivateAssets'; +export const ArtifactReference = 'ArtifactReference'; +export const SuppressMissingDependenciesErrors = 'SuppressMissingDependenciesErrors'; +export const DatabaseVariableLiteralValue = 'DatabaseVariableLiteralValue'; +export const DSP = 'DSP'; // SqlProj File targets export const NetCoreTargets = '$(NETCoreTargetsPath)\\Microsoft.Data.Tools.Schema.SqlTasks.targets'; diff --git a/extensions/sql-database-projects/src/controllers/mainController.ts b/extensions/sql-database-projects/src/controllers/mainController.ts index 42841bfa5f..bce84f23ed 100644 --- a/extensions/sql-database-projects/src/controllers/mainController.ts +++ b/extensions/sql-database-projects/src/controllers/mainController.ts @@ -64,6 +64,8 @@ export default class MainController implements Disposable { this.apiWrapper.registerCommand('sqlDatabaseProjects.importDatabase', async (profile: azdata.IConnectionProfile) => { await this.projectsController.importNewDatabaseProject(profile); }); + this.apiWrapper.registerCommand('sqlDatabaseProjects.addDatabaseReference', async (node: BaseProjectTreeItem) => { await this.projectsController.addDatabaseReference(node); }); + // init view this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider(SQL_DATABASE_PROJECTS_VIEW_ID, this.dbProjectTreeViewProvider)); diff --git a/extensions/sql-database-projects/src/controllers/projectController.ts b/extensions/sql-database-projects/src/controllers/projectController.ts index bbb59cf64e..f2eef700b9 100644 --- a/extensions/sql-database-projects/src/controllers/projectController.ts +++ b/extensions/sql-database-projects/src/controllers/projectController.ts @@ -16,7 +16,7 @@ import { IConnectionProfile, TaskExecutionMode } from 'azdata'; import { promises as fs } from 'fs'; import { ApiWrapper } from '../common/apiWrapper'; import { DeployDatabaseDialog } from '../dialogs/deployDatabaseDialog'; -import { Project } from '../models/project'; +import { Project, DatabaseReferenceLocation } from '../models/project'; import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider'; import { FolderNode } from '../models/tree/fileFolderTreeItem'; import { IDeploymentProfile, IGenerateScriptProfile } from '../models/IDeploymentProfile'; @@ -292,6 +292,118 @@ export class ProjectsController { this.refreshProjectsTree(); } + /** + * Adds a database reference to the project + * @param treeNode a treeItem in a project's hierarchy, to be used to obtain a Project + */ + public async addDatabaseReference(context: Project | BaseProjectTreeItem): Promise { + const project = ProjectsController.getProjectFromContext(context); + + try { + // choose if reference is to master or a dacpac + const databaseReferenceType = await this.getDatabaseReferenceType(); + + // if master is selected, we know which dacpac needs to be added + if (databaseReferenceType === constants.master) { + await project.addMasterDatabaseReference(); + } else { + // get other information needed to add a reference to the dacpac + const dacpacFileLocation = await this.getDacpacFileLocation(); + const databaseLocation = await this.getDatabaseLocation(); + + if (databaseLocation === DatabaseReferenceLocation.differentDatabaseSameServer) { + const databaseName = await this.getDatabaseName(dacpacFileLocation); + await project.addDatabaseReference(dacpacFileLocation, databaseLocation, databaseName); + } else { + await project.addDatabaseReference(dacpacFileLocation, databaseLocation); + } + } + } catch (err) { + this.apiWrapper.showErrorMessage(utils.getErrorMessage(err)); + } + } + + private async getDatabaseReferenceType(): Promise { + let databaseReferenceOptions: QuickPickItem[] = [ + { + label: constants.master + }, + { + label: constants.dacpac + } + ]; + + let input = await this.apiWrapper.showQuickPick(databaseReferenceOptions, { + canPickMany: false, + placeHolder: constants.addDatabaseReferenceInput + }); + + if (!input) { + throw new Error(constants.databaseReferenceTypeRequired); + } + + return input.label; + } + + private async getDacpacFileLocation(): Promise { + let fileUris = await this.apiWrapper.showOpenDialog( + { + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + defaultUri: this.apiWrapper.workspaceFolders() ? (this.apiWrapper.workspaceFolders() as WorkspaceFolder[])[0].uri : undefined, + openLabel: constants.selectString, + filters: { + [constants.dacpacFiles]: ['dacpac'], + } + } + ); + + if (!fileUris || fileUris.length === 0) { + throw new Error(constants.dacpacFileLocationRequired); + } + + return fileUris[0]; + } + + private async getDatabaseLocation(): Promise { + let databaseReferenceOptions: QuickPickItem[] = [ + { + label: constants.databaseReferenceSameDatabase + }, + { + label: constants.databaseReferenceDifferentDabaseSameServer + } + ]; + + let input = await this.apiWrapper.showQuickPick(databaseReferenceOptions, { + canPickMany: false, + placeHolder: constants.databaseReferenceLocation + }); + + if (input === undefined) { + throw new Error(constants.databaseLocationRequired); + } + + const location = input?.label === constants.databaseReferenceSameDatabase ? DatabaseReferenceLocation.sameDatabase : DatabaseReferenceLocation.differentDatabaseSameServer; + return location; + } + + private async getDatabaseName(dacpac: Uri): Promise { + const dacpacName = path.parse(dacpac.toString()).name; + let databaseName = await this.apiWrapper.showInputBox({ + prompt: constants.databaseReferenceDatabaseName, + value: `${dacpacName}` + }); + + if (!databaseName) { + throw new Error(constants.databaseNameRequired); + } + + databaseName = databaseName?.trim(); + return databaseName; + } + //#region Helper methods public getDeployDialog(project: Project): DeployDatabaseDialog { @@ -497,7 +609,7 @@ export class ProjectsController { canSelectFiles: false, canSelectFolders: true, canSelectMany: false, - openLabel: constants.selectFileFolder, + openLabel: constants.selectString, defaultUri: this.apiWrapper.workspaceFolders() ? (this.apiWrapper.workspaceFolders() as WorkspaceFolder[])[0].uri : undefined }); if (selectionResult) { @@ -508,7 +620,7 @@ export class ProjectsController { selectionResult = await this.apiWrapper.showSaveDialog( { defaultUri: this.apiWrapper.workspaceFolders() ? (this.apiWrapper.workspaceFolders() as WorkspaceFolder[])[0].uri : undefined, - saveLabel: constants.selectFileFolder, + saveLabel: constants.selectString, filters: { 'SQL files': ['sql'], 'All files': ['*'] diff --git a/extensions/sql-database-projects/src/models/project.ts b/extensions/sql-database-projects/src/models/project.ts index 910c852d19..500593509a 100644 --- a/extensions/sql-database-projects/src/models/project.ts +++ b/extensions/sql-database-projects/src/models/project.ts @@ -133,6 +133,57 @@ export class Project { return fileEntry; } + /** + * Set the compat level of the project + * Just used in tests right now, but can be used later if this functionality is added to the UI + * @param compatLevel compat level of project + */ + public changeDSP(compatLevel: string): void { + const newDSP = `${constants.MicrosoftDatatoolsSchemaSqlSql}${compatLevel}${constants.databaseSchemaProvider}`; + this.projFileXmlDoc.getElementsByTagName(constants.DSP)[0].childNodes[0].nodeValue = newDSP; + } + + /** + * Adds reference to the appropriate master dacpac to the project + */ + public async addMasterDatabaseReference(): Promise { + const uri = this.getMasterDacpac(); + this.addDatabaseReference(uri, DatabaseReferenceLocation.differentDatabaseSameServer, constants.master); + } + + public getMasterDacpac(): Uri { + // check for invalid DSP + if (this.projFileXmlDoc.getElementsByTagName(constants.DSP).length !== 1 || this.projFileXmlDoc.getElementsByTagName(constants.DSP)[0].childNodes.length !== 1) { + throw new Error(constants.invalidDataSchemaProvider); + } + + let dsp: string = this.projFileXmlDoc.getElementsByTagName(constants.DSP)[0].childNodes[0].nodeValue; + + // get version from dsp, which is a string like Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + // remove part before the number + let version: any = dsp.substring(constants.MicrosoftDatatoolsSchemaSqlSql.length); + // remove DatabaseSchemaProvider + version = version.substring(0, version.length - constants.databaseSchemaProvider.length); + + // make sure version is valid + console.error(Object.values(TargetPlatform)); + if (!Object.values(TargetPlatform).includes(version)) { + throw new Error(constants.invalidDataSchemaProvider); + } + + return Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', version, 'master.dacpac')); + } + + /** + * Adds reference to a dacpac to the project + * @param uri Uri of the dacpac + * @param databaseName name of the database + */ + public async addDatabaseReference(uri: Uri, databaseLocation: DatabaseReferenceLocation, databaseName?: string): Promise { + let databaseReferenceEntry = new DatabaseReferenceProjectEntry(uri, databaseLocation, databaseName); + await this.addToProjFile(databaseReferenceEntry); + } + public createProjectEntry(relativePath: string, entryType: EntryType): ProjectEntry { return new ProjectEntry(Uri.file(path.join(this.projectFolderPath, relativePath)), relativePath, entryType); } @@ -177,6 +228,26 @@ export class Project { this.findOrCreateItemGroup(constants.Folder).appendChild(newFolderNode); } + private addDatabaseReferenceToProjFile(entry: DatabaseReferenceProjectEntry): void { + const referenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference); + referenceNode.setAttribute(constants.Condition, constants.NetCoreCondition); + referenceNode.setAttribute(constants.Include, entry.fsUri.fsPath); + + let suppressMissingDependenciesErrorNode = this.projFileXmlDoc.createElement(constants.SuppressMissingDependenciesErrors); + let falseTextNode = this.projFileXmlDoc.createTextNode('False'); + suppressMissingDependenciesErrorNode.appendChild(falseTextNode); + referenceNode.appendChild(suppressMissingDependenciesErrorNode); + + if (entry.databaseLocation === DatabaseReferenceLocation.differentDatabaseSameServer) { + let databaseVariableLiteralValue = this.projFileXmlDoc.createElement(constants.DatabaseVariableLiteralValue); + let databaseTextNode = this.projFileXmlDoc.createTextNode(entry.name); + databaseVariableLiteralValue.appendChild(databaseTextNode); + referenceNode.appendChild(databaseVariableLiteralValue); + } + + this.findOrCreateItemGroup().appendChild(referenceNode); + } + private async updateImportedTargetsToProjFile(condition: string, projectAttributeVal: string, oldImportNode?: any): Promise { const importNode = this.projFileXmlDoc.createElement(constants.Import); importNode.setAttribute(constants.Condition, condition); @@ -213,6 +284,8 @@ export class Project { break; case EntryType.Folder: this.addFolderToProjFile(entry.relativePath); + case EntryType.DatabaseReference: + this.addDatabaseReferenceToProjFile(entry); } await this.serializeToProjFile(this.projFileXmlDoc); @@ -270,7 +343,33 @@ export class ProjectEntry { } } +/** + * Represents a database reference entry in a project file + */ +class DatabaseReferenceProjectEntry extends ProjectEntry { + constructor(uri: Uri, public databaseLocation: DatabaseReferenceLocation, public name?: string) { + super(uri, '', EntryType.DatabaseReference); + } +} + export enum EntryType { File, - Folder + Folder, + DatabaseReference +} + +export enum DatabaseReferenceLocation { + sameDatabase, + differentDatabaseSameServer +} + +export enum TargetPlatform { + Sql90 = '90', + Sql100 = '100', + Sql110 = '110', + Sql120 = '120', + Sql130 = '130', + Sql140 = '140', + Sql150 = '150', + SqlAzureV12 = 'AzureV12' } diff --git a/extensions/sql-database-projects/src/test/baselines/baselines.ts b/extensions/sql-database-projects/src/test/baselines/baselines.ts index b233850422..e75d6b13cb 100644 --- a/extensions/sql-database-projects/src/test/baselines/baselines.ts +++ b/extensions/sql-database-projects/src/test/baselines/baselines.ts @@ -12,6 +12,7 @@ export let openProjectFileBaseline: string; export let openDataSourcesBaseline: string; export let SSDTProjectFileBaseline: string; export let SSDTProjectAfterUpdateBaseline: string; +export let dacpacReferencesProjectFileBaseline: string; const baselineFolderPath = __dirname; @@ -21,6 +22,7 @@ export async function loadBaselines() { openDataSourcesBaseline = await loadBaseline(baselineFolderPath, 'openDataSourcesBaseline.json'); SSDTProjectFileBaseline = await loadBaseline(baselineFolderPath, 'SSDTProjectBaseline.xml'); SSDTProjectAfterUpdateBaseline = await loadBaseline(baselineFolderPath, 'SSDTProjectAfterUpdateBaseline.xml'); + dacpacReferencesProjectFileBaseline = await loadBaseline(baselineFolderPath, 'dacpacReferencesSqlProjectBaseline.xml'); } async function loadBaseline(baselineFolderPath: string, fileName: string): Promise { diff --git a/extensions/sql-database-projects/src/test/project.test.ts b/extensions/sql-database-projects/src/test/project.test.ts index 5e8e14f6d8..178fc8277e 100644 --- a/extensions/sql-database-projects/src/test/project.test.ts +++ b/extensions/sql-database-projects/src/test/project.test.ts @@ -7,10 +7,12 @@ import * as should from 'should'; import * as path from 'path'; import * as baselines from './baselines/baselines'; import * as testUtils from './testUtils'; +import * as constants from '../common/constants'; import { promises as fs } from 'fs'; -import { Project, EntryType } from '../models/project'; +import { Project, EntryType, TargetPlatform } from '../models/project'; import { exists } from '../common/utils'; +import { Uri } from 'vscode'; let projFilePath: string; @@ -82,6 +84,32 @@ describe('Project: sqlproj content operations', function (): void { await testUtils.shouldThrowSpecificError(async () => await project.addToProject(list), `ENOENT: no such file or directory, stat \'${nonexistentFile}\'`); }); + + it('Should choose correct master dacpac', async function(): Promise { + projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline); + const project = new Project(projFilePath); + await project.readProjFile(); + + let uri = project.getMasterDacpac(); + should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '130', 'master.dacpac')).fsPath); + + project.changeDSP(TargetPlatform.Sql150.toString()); + uri = project.getMasterDacpac(); + should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', '150', 'master.dacpac')).fsPath); + + project.changeDSP(TargetPlatform.SqlAzureV12.toString()); + uri = project.getMasterDacpac(); + should.equal(uri.fsPath, Uri.parse(path.join('$(NETCoreTargetsPath)', 'SystemDacpacs', 'AzureV12', 'master.dacpac')).fsPath); + }); + + it('Should throw error when choosing correct master dacpac if invalid DSP', async function(): Promise { + projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline); + const project = new Project(projFilePath); + await project.readProjFile(); + + project.changeDSP('invalidPlatform'); + await testUtils.shouldThrowSpecificError(async () => await project.getMasterDacpac(), constants.invalidDataSchemaProvider); + }); }); describe('Project: round trip updates', function (): void { diff --git a/extensions/sql-database-projects/src/test/projectController.test.ts b/extensions/sql-database-projects/src/test/projectController.test.ts index 0db07856a4..87e96297b6 100644 --- a/extensions/sql-database-projects/src/test/projectController.test.ts +++ b/extensions/sql-database-projects/src/test/projectController.test.ts @@ -227,6 +227,36 @@ describe('ProjectsController: import operations', function (): void { }); }); +describe('ProjectsController: add database reference operations', function (): void { + it('Should show error when no reference type is selected', async function (): Promise { + testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + await testUtils.shouldThrowSpecificError(async () => await projController.addDatabaseReference(new Project('FakePath')), constants.databaseReferenceTypeRequired); + }); + + it('Should show error when no file is selected', async function (): Promise { + testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ label: constants.dacpac })); + testContext.apiWrapper.setup(x => x.showOpenDialog(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + await testUtils.shouldThrowSpecificError(async () => await projController.addDatabaseReference(new Project('FakePath')), constants.dacpacFileLocationRequired); + }); + + it('Should show error when no database name is provided', async function (): Promise { + testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ label: constants.dacpac })); + testContext.apiWrapper.setup(x => x.showOpenDialog(TypeMoq.It.isAny())).returns(() => Promise.resolve([vscode.Uri.file('FakePath')])); + testContext.apiWrapper.setup(x => x.showQuickPick(TypeMoq.It.isAny())).returns(() => Promise.resolve({ label: constants.databaseReferenceDifferentDabaseSameServer })); + testContext.apiWrapper.setup(x => x.showInputBox(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + + const projController = new ProjectsController(testContext.apiWrapper.object, new SqlDatabaseProjectTreeViewProvider()); + await testUtils.shouldThrowSpecificError(async () => await projController.addDatabaseReference(new Project('FakePath')), constants.databaseNameRequired); + }); +}); + describe('ProjectsController: round trip feature with SSDT', function (): void { it('Should show warning message for SSDT project opened in Azure Data Studio', async function (): Promise { testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });