mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 09:35:36 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user