Add reference to another sql project (#12186)

* add projects to add database reference dialog

* able to add project references

* check for circular dependency

* only allow adding reference to project in the same workspace

* fix location dropdown when project reference is enabled

* add tests

* more tests

* cleanup

* fix flakey test

* addressing comments
This commit is contained in:
Kim Santiago
2020-09-10 17:44:39 -07:00
committed by GitHub
parent 7df132b307
commit 133ff73a43
11 changed files with 380 additions and 70 deletions

View File

@@ -52,6 +52,7 @@ describe('Add Database Reference Dialog', () => {
// change location to different database, different server
dialog.locationDropdown!.value = constants.differentDbDifferentServer;
dialog.updateEnabledInputBoxes();
dialog.tryEnableAddReferenceButton();
should(dialog.dialog.okButton.enabled).equal(false, 'Ok button should not be enabled because server fields are not filled');
@@ -63,9 +64,14 @@ describe('Add Database Reference Dialog', () => {
// change location to same database
dialog.locationDropdown!.value = constants.sameDatabase;
dialog.updateEnabledInputBoxes();
dialog.tryEnableAddReferenceButton();
should(dialog.dialog.okButton.enabled).equal(true, 'Ok button should be enabled because only dacpac location is needed for a reference located on the same database');
// switch to project
dialog.projectRadioButtonClick();
should(dialog.dialog.okButton.enabled).equal(false, 'Ok button should not be enabled because there are no projects in the dropdown');
// change reference type back to system db
dialog.systemDbRadioButtonClick();
should(dialog.databaseNameTextbox?.value).equal('', `Database name textbox should be empty. Actual:${dialog.databaseNameTextbox?.value}`);
@@ -77,7 +83,7 @@ describe('Add Database Reference Dialog', () => {
const dialog = new AddDatabaseReferenceDialog(project);
await dialog.openDialog();
// dialog starts with system db
// dialog starts with system db because there aren't any other projects in the workspace
should(dialog.currentReferenceType).equal(ReferenceType.systemDb);
validateInputBoxEnabledStates(dialog, { databaseNameEnabled: true, databaseVariableEnabled: false, serverNameEnabled: false, serverVariabledEnabled: false});
@@ -96,6 +102,12 @@ describe('Add Database Reference Dialog', () => {
dialog.locationDropdown!.value = constants.sameDatabase;
dialog.updateEnabledInputBoxes();
validateInputBoxEnabledStates(dialog, { databaseNameEnabled: false, databaseVariableEnabled: false, serverNameEnabled: false, serverVariabledEnabled: false});
// change to project reference
dialog.projectRadioButtonClick();
should(dialog.currentReferenceType).equal(ReferenceType.project);
should(dialog.locationDropdown!.value).equal(constants.sameDatabase);
validateInputBoxEnabledStates(dialog, { databaseNameEnabled: false, databaseVariableEnabled: false, serverNameEnabled: false, serverVariabledEnabled: false});
});
});
@@ -107,8 +119,8 @@ interface inputBoxExpectedStates {
}
function validateInputBoxEnabledStates(dialog: AddDatabaseReferenceDialog, expectedStates: inputBoxExpectedStates): void {
should(dialog.databaseNameTextbox?.enabled).equal(expectedStates.databaseNameEnabled);
should(dialog.databaseVariableTextbox?.enabled).equal(expectedStates.databaseVariableEnabled);
should(dialog.serverNameTextbox?.enabled).equal(expectedStates.serverNameEnabled);
should(dialog.serverVariableTextbox?.enabled).equal(expectedStates.serverVariabledEnabled);
should(dialog.databaseNameTextbox?.enabled).equal(expectedStates.databaseNameEnabled, `Database name text box should be ${expectedStates.databaseNameEnabled}. Actual: ${dialog.databaseNameTextbox?.enabled}`);
should(dialog.databaseVariableTextbox?.enabled).equal(expectedStates.databaseVariableEnabled, `Database variable text box should be ${expectedStates.databaseVariableEnabled}. Actual: ${dialog.databaseVariableTextbox?.enabled}`);
should(dialog.serverNameTextbox?.enabled).equal(expectedStates.serverNameEnabled, `Server name text box should be ${expectedStates.serverNameEnabled}. Actual: ${dialog.serverNameTextbox?.enabled}`);
should(dialog.serverVariableTextbox?.enabled).equal(expectedStates.serverVariabledEnabled, `Server variable text box should be ${expectedStates.serverVariabledEnabled}. Actual: ${dialog.serverVariableTextbox?.enabled}`);
}

View File

@@ -12,7 +12,7 @@ import * as testUtils from './testUtils';
import * as constants from '../common/constants';
import { promises as fs } from 'fs';
import { Project, EntryType, TargetPlatform, SystemDatabase, DatabaseReferenceLocation, DacpacReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/project';
import { Project, EntryType, TargetPlatform, SystemDatabase, DacpacReferenceProjectEntry, SqlProjectReferenceProjectEntry } from '../models/project';
import { exists, convertSlashesForSqlProj } from '../common/utils';
import { Uri, window } from 'vscode';
@@ -217,7 +217,7 @@ describe('Project: sqlproj content operations', function (): void {
// add database reference in the same database
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test1.dacpac'), databaseLocation: DatabaseReferenceLocation.sameDatabase, suppressMissingDependenciesErrors: true });
await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test1.dacpac'), suppressMissingDependenciesErrors: true });
should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to test1');
should(project.databaseReferences[0].databaseName).equal('test1', 'The database reference should be test1');
should(project.databaseReferences[0].suppressMissingDependenciesErrors).equal(true, 'project.databaseReferences[0].suppressMissingDependenciesErrors should be true');
@@ -234,7 +234,6 @@ describe('Project: sqlproj content operations', function (): void {
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
await project.addDatabaseReference({
dacpacFileLocation: Uri.file('test2.dacpac'),
databaseLocation: DatabaseReferenceLocation.differentDatabaseSameServer,
databaseName: 'test2DbName',
databaseVariable: 'test2Db',
suppressMissingDependenciesErrors: false
@@ -257,7 +256,6 @@ describe('Project: sqlproj content operations', function (): void {
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
await project.addDatabaseReference({
dacpacFileLocation: Uri.file('test3.dacpac'),
databaseLocation: DatabaseReferenceLocation.differentDatabaseDifferentServer,
databaseName: 'test3DbName',
databaseVariable: 'test3Db',
serverName: 'otherServerName',
@@ -267,7 +265,7 @@ describe('Project: sqlproj content operations', function (): void {
should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to test3');
should(project.databaseReferences[0].databaseName).equal('test3', 'The database reference should be test3');
should(project.databaseReferences[0].suppressMissingDependenciesErrors).equal(false, 'project.databaseReferences[0].suppressMissingDependenciesErrors should be false');
// make sure reference to test2.dacpac and SQLCMD variables were added
// make sure reference to test3.dacpac and SQLCMD variables were added
let projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql('test3.dacpac');
should(projFileText).containEql('<DatabaseSqlCmdVariable>test3Db</DatabaseSqlCmdVariable>');
@@ -276,6 +274,87 @@ describe('Project: sqlproj content operations', function (): void {
should(projFileText).containEql('<SqlCmdVariable Include="otherServer">');
});
it('Should add a project reference to the same database correctly', async function (): Promise<void> {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath);
// add database reference to a different database on a different server
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
should(Object.keys(project.sqlCmdVariables).length).equal(0, `There should be no sqlcmd variables to start with. Actual: ${Object.keys(project.sqlCmdVariables).length}`);
await project.addProjectReference({
projectName: 'project1',
projectGuid: '',
projectRelativePath: Uri.file(path.join('..','project1', 'project1.sqlproj')),
suppressMissingDependenciesErrors: false
});
should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to project1');
should(project.databaseReferences[0].databaseName).equal('project1', 'The database reference should be project1');
should(project.databaseReferences[0].suppressMissingDependenciesErrors).equal(false, 'project.databaseReferences[0].suppressMissingDependenciesErrors should be false');
should(Object.keys(project.sqlCmdVariables).length).equal(0, `There should be no sqlcmd variables added. Actual: ${Object.keys(project.sqlCmdVariables).length}`);
// make sure reference to project1 and SQLCMD variables were added
let projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql('project1');
});
it('Should add a project reference to a different database in the same server correctly', async function (): Promise<void> {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath);
// add database reference to a different database on a different server
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
should(Object.keys(project.sqlCmdVariables).length).equal(0, 'There should be no sqlcmd variables to start with');
await project.addProjectReference({
projectName: 'project1',
projectGuid: '',
projectRelativePath: Uri.file(path.join('..','project1', 'project1.sqlproj')),
databaseName: 'testdbName',
databaseVariable: 'testdb',
suppressMissingDependenciesErrors: false
});
should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to project1');
should(project.databaseReferences[0].databaseName).equal('project1', 'The database reference should be project1');
should(project.databaseReferences[0].suppressMissingDependenciesErrors).equal(false, 'project.databaseReferences[0].suppressMissingDependenciesErrors should be false');
should(Object.keys(project.sqlCmdVariables).length).equal(1, `There should be one new sqlcmd variable added. Actual: ${Object.keys(project.sqlCmdVariables).length}`);
// make sure reference to project1 and SQLCMD variables were added
let projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql('project1');
should(projFileText).containEql('<DatabaseSqlCmdVariable>testdb</DatabaseSqlCmdVariable>');
should(projFileText).containEql('<SqlCmdVariable Include="testdb">');
});
it('Should add a project reference to a different database in a different server correctly', async function (): Promise<void> {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath);
// add database reference to a different database on a different server
should(project.databaseReferences.length).equal(0, 'There should be no database references to start with');
should(Object.keys(project.sqlCmdVariables).length).equal(0, 'There should be no sqlcmd variables to start with');
await project.addProjectReference({
projectName: 'project1',
projectGuid: '',
projectRelativePath: Uri.file(path.join('..','project1', 'project1.sqlproj')),
databaseName: 'testdbName',
databaseVariable: 'testdb',
serverName: 'otherServerName',
serverVariable: 'otherServer',
suppressMissingDependenciesErrors: false
});
should(project.databaseReferences.length).equal(1, 'There should be a database reference after adding a reference to project1');
should(project.databaseReferences[0].databaseName).equal('project1', 'The database reference should be project1');
should(project.databaseReferences[0].suppressMissingDependenciesErrors).equal(false, 'project.databaseReferences[0].suppressMissingDependenciesErrors should be false');
should(Object.keys(project.sqlCmdVariables).length).equal(2, `There should be two new sqlcmd variables added. Actual: ${Object.keys(project.sqlCmdVariables).length}`);
// make sure reference to project1 and SQLCMD variables were added
let projFileText = (await fs.readFile(projFilePath)).toString();
should(projFileText).containEql('project1');
should(projFileText).containEql('<DatabaseSqlCmdVariable>testdb</DatabaseSqlCmdVariable>');
should(projFileText).containEql('<SqlCmdVariable Include="testdb">');
should(projFileText).containEql('<ServerSqlCmdVariable>otherServer</ServerSqlCmdVariable>');
should(projFileText).containEql('<SqlCmdVariable Include="otherServer">');
});
it('Should not allow adding duplicate database references', async function (): Promise<void> {
projFilePath = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const project = await Project.openProject(projFilePath);
@@ -289,12 +368,12 @@ describe('Project: sqlproj content operations', function (): void {
await testUtils.shouldThrowSpecificError(async () => await project.addSystemDatabaseReference({ databaseName: 'master', systemDb: SystemDatabase.master, suppressMissingDependenciesErrors: false }), 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({ dacpacFileLocation: Uri.file('test.dacpac'), databaseLocation: DatabaseReferenceLocation.sameDatabase, suppressMissingDependenciesErrors: false });
await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test.dacpac'), suppressMissingDependenciesErrors: false });
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({ dacpacFileLocation: Uri.file('test.dacpac'), databaseLocation: DatabaseReferenceLocation.sameDatabase, suppressMissingDependenciesErrors: false }), constants.databaseReferenceAlreadyExists);
await testUtils.shouldThrowSpecificError(async () => await project.addDatabaseReference({ dacpacFileLocation: Uri.file('test.dacpac'), suppressMissingDependenciesErrors: false }), 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

@@ -518,7 +518,7 @@ describe('ProjectsController', function (): void {
let opened = false;
let addDbReferenceDialog = TypeMoq.Mock.ofType(AddDatabaseReferenceDialog);
addDbReferenceDialog.setup(x => x.openDialog()).returns(() => { opened = true; return Promise.resolve(undefined) });
addDbReferenceDialog.setup(x => x.openDialog()).returns(() => { opened = true; return Promise.resolve(undefined); });
let projController = TypeMoq.Mock.ofType(ProjectsController);
projController.callBase = true;
@@ -530,7 +530,6 @@ describe('ProjectsController', function (): void {
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 addDbRefHoller = 'hello from callback for addDatabaseReference()';
@@ -557,6 +556,36 @@ describe('ProjectsController', function (): void {
should(holler).equal(addDbRefHoller, 'executionCallback() is supposed to have been setup and called for add database reference scenario');
});
it('Should not allow adding circular project references', async function (): Promise<void> {
const showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage');
const projPath1 = await testUtils.createTestSqlProjFile(baselines.openProjectFileBaseline);
const projPath2 = await testUtils.createTestSqlProjFile(baselines.newProjectFileBaseline);
const projController = new ProjectsController(new SqlDatabaseProjectTreeViewProvider());
const project1 = await projController.openProject(vscode.Uri.file(projPath1));
const project2 = await projController.openProject(vscode.Uri.file(projPath2));
// add project reference from project1 to project2
await projController.addDatabaseReferenceCallback(project1, {
projectGuid: '',
projectName: 'TestProject',
projectRelativePath: undefined,
suppressMissingDependenciesErrors: false
});
should(showErrorMessageSpy.notCalled).be.true('showErrorMessage should not have been called');
// try to add circular reference
await projController.addDatabaseReferenceCallback(project2, {
projectGuid: '',
projectName: 'TestProjectName',
projectRelativePath: undefined,
suppressMissingDependenciesErrors: false
});
should(showErrorMessageSpy.called).be.true('showErrorMessage should have been called');
});
});
});