Hook up add database references dialog (#12002)

* initial dialog

* got enabling working

* add tests

* cleanup

* add test coverage for systemDbRadioButtonClick()

* change DAC to .dacpac

* remove isEmptyOrUndefined

* hook up add database reference dialog

* cleanup

* Addressing comments
This commit is contained in:
Kim Santiago
2020-09-01 11:06:04 -07:00
committed by GitHub
parent 8f8d01cee2
commit 177d9bef39
5 changed files with 195 additions and 266 deletions

View File

@@ -17,7 +17,7 @@ import * as azdata from 'azdata';
import { promises as fs } from 'fs';
import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog';
import { Project, DatabaseReferenceLocation, SystemDatabase, TargetPlatform, ProjectEntry, reservedProjectFolders, SqlProjectReferenceProjectEntry } from '../models/project';
import { Project, ProjectEntry, reservedProjectFolders, SqlProjectReferenceProjectEntry } from '../models/project';
import { SqlDatabaseProjectTreeViewProvider } from './databaseProjectTreeViewProvider';
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem';
import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings';
@@ -27,6 +27,8 @@ import { ImportDataModel } from '../models/api/import';
import { NetCoreTool, DotNetCommandOptions } from '../tools/netcoreTool';
import { BuildHelper } from '../tools/buildHelper';
import { PublishProfile, load } from '../models/publishProfile/publishProfile';
import { AddDatabaseReferenceDialog } from '../dialogs/addDatabaseReferenceDialog';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings } from '../models/IDatabaseReferenceSettings';
/**
* Controller for managing project lifecycle
@@ -429,28 +431,23 @@ export class ProjectsController {
* Adds a database reference to the project
* @param context a treeItem in a project's hierarchy, to be used to obtain a Project
*/
public async addDatabaseReference(context: Project | BaseProjectTreeItem): Promise<void> {
public async addDatabaseReference(context: Project | BaseProjectTreeItem): Promise<AddDatabaseReferenceDialog> {
const project = this.getProjectFromContext(context);
const addDatabaseReferenceDialog = this.getAddDatabaseReferenceDialog(project);
addDatabaseReferenceDialog.addReference = async (proj, prof) => await this.addDatabaseReferenceCallback(proj, prof);
addDatabaseReferenceDialog.openDialog();
return addDatabaseReferenceDialog;
}
public async addDatabaseReferenceCallback(project: Project, settings: ISystemDatabaseReferenceSettings | IDacpacReferenceSettings): Promise<void> {
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.systemDatabase) {
const systemDatabase = await this.getSystemDatabaseName(project);
await project.addSystemDatabaseReference(systemDatabase);
if ((<ISystemDatabaseReferenceSettings>settings).systemDb !== undefined) {
await project.addSystemDatabaseReference(<ISystemDatabaseReferenceSettings>settings);
} 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);
}
await project.addDatabaseReference(<IDacpacReferenceSettings>settings);
}
this.refreshProjectsTree();
@@ -459,120 +456,16 @@ export class ProjectsController {
}
}
private async getDatabaseReferenceType(): Promise<string> {
let databaseReferenceOptions: vscode.QuickPickItem[] = [
{
label: constants.systemDatabase
},
{
label: constants.dacpac
}
];
let input = await vscode.window.showQuickPick(databaseReferenceOptions, {
canPickMany: false,
placeHolder: constants.addDatabaseReferenceInput
});
if (!input) {
throw new Error(constants.databaseReferenceTypeRequired);
}
return input.label;
}
public async getSystemDatabaseName(project: Project): Promise<SystemDatabase> {
let databaseReferenceOptions: vscode.QuickPickItem[] = [
{
label: constants.master
}
];
// Azure dbs can only reference master
if (project.getProjectTargetPlatform() !== TargetPlatform.SqlAzureV12) {
databaseReferenceOptions.push(
{
label: constants.msdb
});
}
let input = await vscode.window.showQuickPick(databaseReferenceOptions, {
canPickMany: false,
placeHolder: constants.systemDatabaseReferenceInput
});
if (!input) {
throw new Error(constants.systemDatabaseReferenceRequired);
}
return input.label === constants.master ? SystemDatabase.master : SystemDatabase.msdb;
}
private async getDacpacFileLocation(): Promise<vscode.Uri> {
let fileUris = await vscode.window.showOpenDialog(
{
canSelectFiles: true,
canSelectFolders: false,
canSelectMany: false,
defaultUri: vscode.workspace.workspaceFolders ? (vscode.workspace.workspaceFolders as vscode.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<DatabaseReferenceLocation> {
let databaseReferenceOptions: vscode.QuickPickItem[] = [
{
label: constants.databaseReferenceSameDatabase
},
{
label: constants.databaseReferenceDifferentDabaseSameServer
}
];
let input = await vscode.window.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: vscode.Uri): Promise<string | undefined> {
const dacpacName = path.parse(dacpac.toString()).name;
let databaseName = await vscode.window.showInputBox({
prompt: constants.databaseReferenceDatabaseName,
value: `${dacpacName}`
});
if (!databaseName) {
throw new Error(constants.databaseNameRequired);
}
databaseName = databaseName?.trim();
return databaseName;
}
//#region Helper methods
public getPublishDialog(project: Project): PublishDatabaseDialog {
return new PublishDatabaseDialog(project);
}
public getAddDatabaseReferenceDialog(project: Project): AddDatabaseReferenceDialog {
return new AddDatabaseReferenceDialog(project);
}
public async updateProjectForRoundTrip(project: Project) {
if (project.importedTargets.includes(constants.NetCoreTargets) && !project.containsSSDTOnlySystemDatabaseReferences()) {
return;

View File

@@ -7,7 +7,7 @@ import { DatabaseReferenceLocation, SystemDatabase } from './project';
import { Uri } from 'vscode';
export interface IDatabaseReferenceSettings {
databaseName: string;
databaseName?: string;
}
export interface ISystemDatabaseReferenceSettings extends IDatabaseReferenceSettings {

View File

@@ -14,6 +14,7 @@ import * as templates from '../templates/templates';
import { Uri, window } from 'vscode';
import { promises as fs } from 'fs';
import { DataSource } from './dataSources/dataSources';
import { ISystemDatabaseReferenceSettings, IDacpacReferenceSettings } from './IDatabaseReferenceSettings';
/**
* Class representing a Project, and providing functions for operating on it
@@ -123,7 +124,12 @@ export class Project {
const nameNodes = references[r].getElementsByTagName(constants.DatabaseVariableLiteralValue);
const name = nameNodes.length === 1 ? nameNodes[0].childNodes[0].nodeValue : undefined;
this.databaseReferences.push(new DacpacReferenceProjectEntry(Uri.file(filepath), name ? DatabaseReferenceLocation.differentDatabaseSameServer : DatabaseReferenceLocation.sameDatabase, name));
this.databaseReferences.push(new DacpacReferenceProjectEntry({
dacpacFileLocation: Uri.file(utils.getPlatformSafeFileEntryPath(filepath)),
databaseLocation: name ? DatabaseReferenceLocation.differentDatabaseSameServer : DatabaseReferenceLocation.sameDatabase,
databaseName: name
}));
}
}
@@ -285,21 +291,19 @@ export class Project {
/**
* Adds reference to the appropriate system database dacpac to the project
*/
public async addSystemDatabaseReference(name: SystemDatabase): Promise<void> {
public async addSystemDatabaseReference(settings: ISystemDatabaseReferenceSettings): Promise<void> {
let uri: Uri;
let ssdtUri: Uri;
let dbName: string;
if (name === SystemDatabase.master) {
if (settings.systemDb === SystemDatabase.master) {
uri = this.getSystemDacpacUri(constants.masterDacpac);
ssdtUri = this.getSystemDacpacSsdtUri(constants.masterDacpac);
dbName = constants.master;
} else {
uri = this.getSystemDacpacUri(constants.msdbDacpac);
ssdtUri = this.getSystemDacpacSsdtUri(constants.msdbDacpac);
dbName = constants.msdb;
}
let systemDatabaseReferenceProjectEntry = new SystemDatabaseReferenceProjectEntry(uri, ssdtUri, dbName);
const systemDatabaseReferenceProjectEntry = new SystemDatabaseReferenceProjectEntry(uri, ssdtUri, <string>settings.databaseName);
await this.addToProjFile(systemDatabaseReferenceProjectEntry);
}
@@ -340,8 +344,8 @@ export class Project {
* @param uri Uri of the dacpac
* @param databaseName name of the database
*/
public async addDatabaseReference(uri: Uri, databaseLocation: DatabaseReferenceLocation, databaseName?: string): Promise<void> {
let databaseReferenceEntry = new DacpacReferenceProjectEntry(uri, databaseLocation, databaseName);
public async addDatabaseReference(settings: IDacpacReferenceSettings): Promise<void> {
const databaseReferenceEntry = new DacpacReferenceProjectEntry(settings);
await this.addToProjFile(databaseReferenceEntry);
}
@@ -432,32 +436,41 @@ export class Project {
throw new Error(constants.unableToFindObject(path, constants.folderObject));
}
private addSystemDatabaseReferenceToProjFile(entry: SystemDatabaseReferenceProjectEntry): void {
const systemDbReferenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
// if it's a system database reference, we'll add an additional node with the SSDT location of the dacpac later
systemDbReferenceNode.setAttribute(constants.Condition, constants.NetCoreCondition);
systemDbReferenceNode.setAttribute(constants.Include, entry.pathForSqlProj());
this.addDatabaseReferenceChildren(systemDbReferenceNode, entry);
this.findOrCreateItemGroup(constants.ArtifactReference).appendChild(systemDbReferenceNode);
this.databaseReferences.push(entry);
// add a reference to the system dacpac in SSDT if it's a system db
const ssdtReferenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
ssdtReferenceNode.setAttribute(constants.Condition, constants.NotNetCoreCondition);
ssdtReferenceNode.setAttribute(constants.Include, entry.ssdtPathForSqlProj());
this.addDatabaseReferenceChildren(ssdtReferenceNode, entry);
this.findOrCreateItemGroup(constants.ArtifactReference).appendChild(ssdtReferenceNode);
}
private addDatabaseReferenceToProjFile(entry: IDatabaseReferenceProjectEntry): void {
// check if reference to this database already exists
if (this.databaseReferenceExists(entry)) {
throw new Error(constants.databaseReferenceAlreadyExists);
}
let referenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
const isSystemDatabaseProjectEntry = (<SystemDatabaseReferenceProjectEntry>entry).ssdtUri;
// if it's a system database reference, we'll add an additional node with the SSDT location of the dacpac later
if (isSystemDatabaseProjectEntry) {
referenceNode.setAttribute(constants.Condition, constants.NetCoreCondition);
}
this.addSystemDatabaseReferenceToProjFile(<SystemDatabaseReferenceProjectEntry>entry);
} else {
referenceNode.setAttribute(constants.Include, entry.pathForSqlProj());
this.addDatabaseReferenceChildren(referenceNode, entry.sqlCmdName);
this.findOrCreateItemGroup(constants.ArtifactReference).appendChild(referenceNode);
this.databaseReferences.push(entry);
// add a reference to the system dacpac in SSDT if it's a system db
if (isSystemDatabaseProjectEntry) {
let ssdtReferenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
ssdtReferenceNode.setAttribute(constants.Condition, constants.NotNetCoreCondition);
ssdtReferenceNode.setAttribute(constants.Include, (<SystemDatabaseReferenceProjectEntry>entry).ssdtPathForSqlProj());
this.addDatabaseReferenceChildren(ssdtReferenceNode, entry.sqlCmdName);
this.findOrCreateItemGroup(constants.ArtifactReference).appendChild(ssdtReferenceNode);
const referenceNode = this.projFileXmlDoc.createElement(constants.ArtifactReference);
referenceNode.setAttribute(constants.Include, entry.pathForSqlProj());
this.addDatabaseReferenceChildren(referenceNode, entry);
this.findOrCreateItemGroup(constants.ArtifactReference).appendChild(referenceNode);
this.databaseReferences.push(entry);
}
}
@@ -466,17 +479,19 @@ export class Project {
return found;
}
private addDatabaseReferenceChildren(referenceNode: any, name?: string): void {
let suppressMissingDependenciesErrorNode = this.projFileXmlDoc.createElement(constants.SuppressMissingDependenciesErrors);
let falseTextNode = this.projFileXmlDoc.createTextNode('False');
private addDatabaseReferenceChildren(referenceNode: any, entry: IDatabaseReferenceProjectEntry): void {
// TODO: create checkbox for this setting
const suppressMissingDependenciesErrorNode = this.projFileXmlDoc.createElement(constants.SuppressMissingDependenciesErrors);
const falseTextNode = this.projFileXmlDoc.createTextNode('False');
suppressMissingDependenciesErrorNode.appendChild(falseTextNode);
referenceNode.appendChild(suppressMissingDependenciesErrorNode);
if (name) {
let databaseVariableLiteralValue = this.projFileXmlDoc.createElement(constants.DatabaseVariableLiteralValue);
let databaseTextNode = this.projFileXmlDoc.createTextNode(name);
databaseVariableLiteralValue.appendChild(databaseTextNode);
referenceNode.appendChild(databaseVariableLiteralValue);
// TODO: add support for sqlcmd vars and server https://github.com/microsoft/azuredatastudio/issues/12036
if (entry.databaseVariableLiteralValue) {
const databaseVariableLiteralValueElement = this.projFileXmlDoc.createElement(constants.DatabaseVariableLiteralValue);
const databaseTextNode = this.projFileXmlDoc.createTextNode(entry.databaseVariableLiteralValue);
databaseVariableLiteralValueElement.appendChild(databaseTextNode);
referenceNode.appendChild(databaseVariableLiteralValueElement);
}
}
@@ -521,13 +536,22 @@ export class Project {
return false;
}
/**
* Update system db references to have the ADS and SSDT paths to the system dacpacs
*/
public async updateSystemDatabaseReferencesInProjFile(): Promise<void> {
// find all system database references
for (let r = 0; r < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ArtifactReference).length; r++) {
const currentNode = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ArtifactReference)[r];
if (!currentNode.getAttribute(constants.Condition) && currentNode.getAttribute(constants.Include).includes(constants.DacpacRootPath)) {
// get name of system database
const name = currentNode.getAttribute(constants.Include).includes(constants.master) ? SystemDatabase.master : SystemDatabase.msdb;
const systemDb = currentNode.getAttribute(constants.Include).includes(constants.master) ? SystemDatabase.master : SystemDatabase.msdb;
// get name
const nameNodes = currentNode.getElementsByTagName(constants.DatabaseVariableLiteralValue);
const databaseVariableName = nameNodes[0].childNodes[0]?.nodeValue;
// remove this node
this.projFileXmlDoc.documentElement.removeChild(currentNode);
// delete ItemGroup if there aren't any other children
@@ -536,9 +560,9 @@ export class Project {
}
// remove from database references because it'll get added again later
this.databaseReferences.splice(this.databaseReferences.findIndex(n => n.databaseName === (name === SystemDatabase.master ? constants.master : constants.msdb)), 1);
this.databaseReferences.splice(this.databaseReferences.findIndex(n => n.databaseName === (systemDb === SystemDatabase.master ? constants.master : constants.msdb)), 1);
await this.addSystemDatabaseReference(name);
await this.addSystemDatabaseReference({ databaseName: databaseVariableName, systemDb: systemDb });
}
}
}
@@ -643,26 +667,37 @@ export class ProjectEntry {
export interface IDatabaseReferenceProjectEntry extends ProjectEntry {
databaseName: string;
sqlCmdName?: string | undefined;
databaseVariableLiteralValue?: string;
}
export class DacpacReferenceProjectEntry extends ProjectEntry implements IDatabaseReferenceProjectEntry {
sqlCmdName: string | undefined;
databaseLocation: DatabaseReferenceLocation;
databaseVariableLiteralValue?: string;
constructor(uri: Uri, public databaseLocation: DatabaseReferenceLocation, name?: string) {
super(uri, '', EntryType.DatabaseReference);
this.sqlCmdName = name;
constructor(settings: IDacpacReferenceSettings) {
super(settings.dacpacFileLocation, '', EntryType.DatabaseReference);
this.databaseLocation = settings.databaseLocation;
this.databaseVariableLiteralValue = settings.databaseName;
}
/**
* File name that gets displayed in the project tree
*/
public get databaseName(): string {
return path.parse(utils.getPlatformSafeFileEntryPath(this.fsUri.fsPath)).name;
}
}
class SystemDatabaseReferenceProjectEntry extends DacpacReferenceProjectEntry {
constructor(uri: Uri, public ssdtUri: Uri, name: string) {
super(uri, DatabaseReferenceLocation.differentDatabaseSameServer, name);
this.sqlCmdName = name;
class SystemDatabaseReferenceProjectEntry extends ProjectEntry implements IDatabaseReferenceProjectEntry {
constructor(uri: Uri, public ssdtUri: Uri, public databaseVariableLiteralValue: string) {
super(uri, '', EntryType.DatabaseReference);
}
/**
* File name that gets displayed in the project tree
*/
public get databaseName(): string {
return path.parse(utils.getPlatformSafeFileEntryPath(this.fsUri.fsPath)).name;
}
public pathForSqlProj(): string {

View File

@@ -192,23 +192,28 @@ describe('Project: sqlproj content operations', function (): void {
const project = await Project.openProject(projFilePath);
should(project.databaseReferences.length).equal(0, 'There should be no datbase references to start with');
await project.addSystemDatabaseReference(SystemDatabase.master);
await project.addSystemDatabaseReference({ databaseName: 'master', systemDb: SystemDatabase.master });
should(project.databaseReferences.length).equal(1, 'There should be one database reference after adding a reference to master');
should(project.databaseReferences[0].databaseName).equal(constants.master, 'The database reference should be master');
// make sure reference to SSDT master dacpac was added
// make sure reference to ADS master dacpac and SSDT master dacpac was added
let projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql(convertSlashesForSqlProj(project.getSystemDacpacUri(constants.master).fsPath.substring(1)));
should(projFileText).containEql(convertSlashesForSqlProj(project.getSystemDacpacSsdtUri(constants.master).fsPath.substring(1)));
await project.addSystemDatabaseReference(SystemDatabase.msdb);
await project.addSystemDatabaseReference({ databaseName: 'msdb', systemDb: SystemDatabase.msdb });
should(project.databaseReferences.length).equal(2, 'There should be two database references after adding a reference to msdb');
should(project.databaseReferences[1].databaseName).equal(constants.msdb, 'The database reference should be msdb');
// make sure reference to SSDT msdb dacpac was added
// make sure reference to ADS msdb dacpac and SSDT msdb dacpac was added
projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql(convertSlashesForSqlProj(project.getSystemDacpacUri(constants.msdb).fsPath.substring(1)));
should(projFileText).containEql(convertSlashesForSqlProj(project.getSystemDacpacSsdtUri(constants.msdb).fsPath.substring(1)));
await project.addDatabaseReference(Uri.parse('test.dacpac'), DatabaseReferenceLocation.sameDatabase);
await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test.dacpac'), databaseLocation: DatabaseReferenceLocation.sameDatabase });
should(project.databaseReferences.length).equal(3, 'There should be three database references after adding a reference to test');
should(project.databaseReferences[2].databaseName).equal('test', 'The database reference should be test');
// make sure reference to test.dacpac was added
projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql('test.dacpac');
});
it('Should not allow adding duplicate database references', async function (): Promise<void> {
@@ -216,20 +221,20 @@ describe('Project: sqlproj content operations', function (): void {
const project = await Project.openProject(projFilePath);
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
await project.addSystemDatabaseReference(SystemDatabase.master);
await project.addSystemDatabaseReference({ databaseName: 'master', systemDb: SystemDatabase.master });
should(project.databaseReferences.length).equal(1, 'There should be one database reference after adding a reference to master');
should(project.databaseReferences[0].databaseName).equal(constants.master, 'project.databaseReferences[0].databaseName should be master');
// try to add reference to master again
await testUtils.shouldThrowSpecificError(async () => await project.addSystemDatabaseReference(SystemDatabase.master), constants.databaseReferenceAlreadyExists);
await testUtils.shouldThrowSpecificError(async () => await project.addSystemDatabaseReference({ databaseName: 'master', systemDb: SystemDatabase.master }), constants.databaseReferenceAlreadyExists);
should(project.databaseReferences.length).equal(1, 'There should only be one database reference after trying to add a reference to master again');
await project.addDatabaseReference(Uri.parse('test.dacpac'), DatabaseReferenceLocation.sameDatabase);
await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test.dacpac'), databaseLocation: DatabaseReferenceLocation.sameDatabase });
should(project.databaseReferences.length).equal(2, 'There should be two database references after adding a reference to test.dacpac');
should(project.databaseReferences[1].databaseName).equal('test', 'project.databaseReferences[1].databaseName should be test');
// try to add reference to test.dacpac again
await testUtils.shouldThrowSpecificError(async () => await project.addDatabaseReference(Uri.parse('test.dacpac'), DatabaseReferenceLocation.sameDatabase), constants.databaseReferenceAlreadyExists);
await testUtils.shouldThrowSpecificError(async () => await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test.dacpac'), databaseLocation: DatabaseReferenceLocation.sameDatabase }), constants.databaseReferenceAlreadyExists);
should(project.databaseReferences.length).equal(2, 'There should be two database references after trying to add a reference to test.dacpac again');
});

View File

@@ -19,13 +19,15 @@ import { SqlDatabaseProjectTreeViewProvider } from '../controllers/databaseProje
import { ProjectsController } from '../controllers/projectController';
import { promises as fs } from 'fs';
import { createContext, TestContext, mockDacFxResult } from './testContext';
import { Project, SystemDatabase, ProjectEntry, reservedProjectFolders } from '../models/project';
import { Project, ProjectEntry, reservedProjectFolders, SystemDatabase } from '../models/project';
import { PublishDatabaseDialog } from '../dialogs/publishDatabaseDialog';
import { IPublishSettings, IGenerateScriptSettings } from '../models/IPublishSettings';
import { exists } from '../common/utils';
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
import { FolderNode, FileNode } from '../models/tree/fileFolderTreeItem';
import { BaseProjectTreeItem } from '../models/tree/baseTreeItem';
import { AddDatabaseReferenceDialog } from '../dialogs/addDatabaseReferenceDialog';
import { IDacpacReferenceSettings } from '../models/IDatabaseReferenceSettings';
let testContext: TestContext;
@@ -473,117 +475,111 @@ describe('ProjectsController', function (): void {
});
});
describe('add database reference operations', function (): void {
it('Should show error when no reference type is selected', async function (): Promise<void> {
sinon.stub(vscode.window, 'showQuickPick').resolves(undefined);
const spy = sinon.spy(vscode.window, 'showErrorMessage');
describe('Add database reference', function (): void {
it('Add database reference dialog should open from ProjectController', async function (): Promise<void> {
let opened = false;
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
await projController.addDatabaseReference(new Project('FakePath'));
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once');
should(spy.calledWith(constants.databaseReferenceTypeRequired)).be.true(`showErrorMessage not called with expected message '${constants.databaseReferenceTypeRequired}' Actual '${spy.getCall(0).args[0]}'`);
let addDbReferenceDialog = TypeMoq.Mock.ofType(AddDatabaseReferenceDialog);
addDbReferenceDialog.setup(x => x.openDialog()).returns(() => { opened = true; return Promise.resolve(undefined) });
let projController = TypeMoq.Mock.ofType(ProjectsController);
projController.callBase = true;
projController.setup(x => x.getAddDatabaseReferenceDialog(TypeMoq.It.isAny())).returns(() => addDbReferenceDialog.object);
await projController.object.addDatabaseReference(new Project('FakePath'));
should(opened).equal(true);
});
it('Should show error when no file is selected', async function (): Promise<void> {
sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.dacpac });
sinon.stub(vscode.window, 'showOpenDialog').resolves(undefined);
const spy = sinon.spy(vscode.window, 'showErrorMessage');
it('Callbacks are hooked up and called from Add database reference dialog', async function (): Promise<void> {
const projPath = path.dirname(await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline));
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, projPath);
const proj = new Project(projPath);
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
await projController.addDatabaseReference(new Project('FakePath'));
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once');
should(spy.calledWith(constants.dacpacFileLocationRequired)).be.true(`showErrorMessage not called with expected message '${constants.dacpacFileLocationRequired}' Actual '${spy.getCall(0).args[0]}'`);
});
const addDbRefHoller = 'hello from callback for addDatabaseReference()';
it('Should show error when no database name is provided', async function (): Promise<void> {
sinon.stub(vscode.window, 'showInputBox').resolves(undefined);
sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.dacpac });
sinon.stub(vscode.window, 'showOpenDialog').resolves([vscode.Uri.file('FakePath')]);
const spy = sinon.spy(vscode.window, 'showErrorMessage');
let holler = 'nothing';
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
await projController.addDatabaseReference(new Project('FakePath'));
should(spy.calledOnce).be.true('showErrorMessage should have been called exactly once');
should(spy.calledWith(constants.databaseNameRequired)).be.true(`showErrorMessage not called with expected message '${constants.databaseNameRequired}' Actual '${spy.getCall(0).args[0]}'`);
});
const addDbReferenceDialog = TypeMoq.Mock.ofType(AddDatabaseReferenceDialog, undefined, undefined, proj);
addDbReferenceDialog.callBase = true;
addDbReferenceDialog.setup(x => x.addReferenceClick()).returns(() => {
projController.object.addDatabaseReferenceCallback(proj, { systemDb: SystemDatabase.master, databaseName: 'master' });
return Promise.resolve(undefined);
})
it('Should return the correct system database', async function (): Promise<void> {
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const projFilePath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
const project = await Project.openProject(projFilePath);
const projController = TypeMoq.Mock.ofType(ProjectsController);
projController.callBase = true;
projController.setup(x => x.getAddDatabaseReferenceDialog(TypeMoq.It.isAny())).returns(() => addDbReferenceDialog.object);
projController.setup(x => x.addDatabaseReferenceCallback(TypeMoq.It.isAny(), TypeMoq.It.is((_): _ is IDacpacReferenceSettings => true))).returns(() => {
holler = addDbRefHoller;
return Promise.resolve(undefined);
});
const stub = sinon.stub(vscode.window, 'showQuickPick').resolves({ label: constants.master });
let systemDb = await projController.getSystemDatabaseName(project);
should.equal(systemDb, SystemDatabase.master);
let dialog = await projController.object.addDatabaseReference(proj);
await dialog.addReferenceClick();
stub.resolves({ label: constants.msdb });
systemDb = await projController.getSystemDatabaseName(project);
should.equal(systemDb, SystemDatabase.msdb);
stub.resolves(undefined);
await testUtils.shouldThrowSpecificError(async () => await projController.getSystemDatabaseName(project), constants.systemDatabaseReferenceRequired);
should(holler).equal(addDbRefHoller, 'executionCallback() is supposed to have been setup and called for add database reference scenario');
});
});
});
describe.skip('ProjectsController: round trip feature with SSDT', function (): void {
it('Should show warning message for SSDT project opened in Azure Data Studio', async function (): Promise<void> {
const stub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.noString));
describe.skip('ProjectsController: round trip feature with SSDT', function (): void {
it('Should show warning message for SSDT project opened in Azure Data Studio', async function (): Promise<void> {
const stub = sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.noString));
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
await projController.openProject(vscode.Uri.file(sqlProjPath));
should(stub.calledOnce).be.true('showWarningMessage should have been called exactly once');
should(stub.calledWith(constants.updateProjectForRoundTrip)).be.true(`showWarningMessage not called with expected message '${constants.updateProjectForRoundTrip}' Actual '${stub.getCall(0).args[0]}'`);
});
await projController.openProject(vscode.Uri.file(sqlProjPath));
should(stub.calledOnce).be.true('showWarningMessage should have been called exactly once');
should(stub.calledWith(constants.updateProjectForRoundTrip)).be.true(`showWarningMessage not called with expected message '${constants.updateProjectForRoundTrip}' Actual '${stub.getCall(0).args[0]}'`);
});
it('Should not show warning message for non-SSDT projects that have the additional information for Build', async function (): Promise<void> {
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
it('Should not show warning message for non-SSDT projects that have the additional information for Build', async function (): Promise<void> {
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); // no error thrown
const project = await projController.openProject(vscode.Uri.file(sqlProjPath)); // no error thrown
should(project.importedTargets.length).equal(3); // additional target should exist by default
});
should(project.importedTargets.length).equal(3); // additional target should exist by default
});
it('Should not update project and no backup file should be created when update to project is rejected', async function (): Promise<void> {
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.noString));
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
it('Should not update project and no backup file should be created when update to project is rejected', async function (): Promise<void> {
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.noString));
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const project = await projController.openProject(vscode.Uri.file(sqlProjPath));
const project = await projController.openProject(vscode.Uri.file(sqlProjPath));
should(await exists(sqlProjPath + '_backup')).equal(false); // backup file should not be generated
should(project.importedTargets.length).equal(2); // additional target should not be added by updateProjectForRoundTrip method
});
should(await exists(sqlProjPath + '_backup')).equal(false); // backup file should not be generated
should(project.importedTargets.length).equal(2); // additional target should not be added by updateProjectForRoundTrip method
});
it('Should load Project and associated import targets when update to project is accepted', async function (): Promise<void> {
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.yesString));
it('Should load Project and associated import targets when update to project is accepted', async function (): Promise<void> {
sinon.stub(vscode.window, 'showWarningMessage').returns(<any>Promise.resolve(constants.yesString));
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
// setup test files
const folderPath = await testUtils.generateTestFolderPath();
const sqlProjPath = await testUtils.createTestSqlProjFile(baselines.SSDTProjectFileBaseline, folderPath);
await testUtils.createTestDataSources(baselines.openDataSourcesBaseline, folderPath);
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const project = await projController.openProject(vscode.Uri.file(sqlProjPath));
const project = await projController.openProject(vscode.Uri.file(sqlProjPath));
should(await exists(sqlProjPath + '_backup')).equal(true); // backup file should be generated before the project is updated
should(project.importedTargets.length).equal(3); // additional target added by updateProjectForRoundTrip method
});
should(await exists(sqlProjPath + '_backup')).equal(true); // backup file should be generated before the project is updated
should(project.importedTargets.length).equal(3); // additional target added by updateProjectForRoundTrip method
});
});