diff --git a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts index 4382d65504..98789142c0 100644 --- a/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts +++ b/extensions/schema-compare/src/dialogs/schemaCompareDialog.ts @@ -7,22 +7,16 @@ import * as azdata from 'azdata'; import * as vscode from 'vscode'; import * as loc from '../localizedConstants'; import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; -import { promises as fs } from 'fs'; import { TelemetryReporter, TelemetryViews } from '../telemetry'; -import { getEndpointName, getRootPath } from '../utils'; +import { getEndpointName, getRootPath, exists } from '../utils'; import * as mssql from '../../../mssql'; const titleFontSize: number = 13; -async function exists(path: string): Promise { - try { - await fs.access(path); - return true; - } catch (e) { - return false; - } +interface Deferred { + resolve: (result: T | Promise) => void; + reject: (reason: any) => void; } - export class SchemaCompareDialog { public dialog: azdata.window.Dialog; public dialogName: string; @@ -53,15 +47,17 @@ export class SchemaCompareDialog { private targetDbEditable: string; private previousSource: mssql.SchemaCompareEndpointInfo; private previousTarget: mssql.SchemaCompareEndpointInfo; + private initDialogComplete: Deferred; + private initDialogPromise: Promise = new Promise((resolve, reject) => this.initDialogComplete = { resolve, reject }); - constructor(private schemaCompareResult: SchemaCompareMainWindow) { - this.previousSource = schemaCompareResult.sourceEndpointInfo; - this.previousTarget = schemaCompareResult.targetEndpointInfo; + constructor(private schemaCompareMainWindow: SchemaCompareMainWindow, private view?: azdata.ModelView) { + this.previousSource = schemaCompareMainWindow.sourceEndpointInfo; + this.previousTarget = schemaCompareMainWindow.targetEndpointInfo; } - protected initializeDialog(): void { + protected async initializeDialog(): Promise { this.schemaCompareTab = azdata.window.createTab(loc.SchemaCompareLabel); - this.initializeSchemaCompareTab(); + await this.initializeSchemaCompareTab(); this.dialog.content = [this.schemaCompareTab]; } @@ -73,7 +69,7 @@ export class SchemaCompareDialog { } this.dialog = azdata.window.createModelViewDialog(loc.SchemaCompareLabel); - this.initializeDialog(); + await this.initializeDialog(); this.dialog.okButton.label = loc.OkButtonText; this.dialog.okButton.enabled = false; @@ -83,11 +79,12 @@ export class SchemaCompareDialog { this.dialog.cancelButton.onClick(async () => await this.cancel()); azdata.window.openDialog(this.dialog); + await this.initDialogPromise; } - protected async execute(): Promise { + public async execute(): Promise { if (this.sourceIsDacpac) { - this.schemaCompareResult.sourceEndpointInfo = { + this.schemaCompareMainWindow.sourceEndpointInfo = { endpointType: mssql.SchemaCompareEndpointType.Dacpac, serverDisplayName: '', serverName: '', @@ -99,7 +96,7 @@ export class SchemaCompareDialog { } else { let ownerUri = await azdata.connection.getUriForConnection((this.sourceServerDropdown.value as ConnectionDropdownValue).connection.connectionId); - this.schemaCompareResult.sourceEndpointInfo = { + this.schemaCompareMainWindow.sourceEndpointInfo = { endpointType: mssql.SchemaCompareEndpointType.Database, serverDisplayName: (this.sourceServerDropdown.value as ConnectionDropdownValue).displayName, serverName: (this.sourceServerDropdown.value as ConnectionDropdownValue).name, @@ -111,7 +108,7 @@ export class SchemaCompareDialog { } if (this.targetIsDacpac) { - this.schemaCompareResult.targetEndpointInfo = { + this.schemaCompareMainWindow.targetEndpointInfo = { endpointType: mssql.SchemaCompareEndpointType.Dacpac, serverDisplayName: '', serverName: '', @@ -123,7 +120,7 @@ export class SchemaCompareDialog { } else { let ownerUri = await azdata.connection.getUriForConnection((this.targetServerDropdown.value as ConnectionDropdownValue).connection.connectionId); - this.schemaCompareResult.targetEndpointInfo = { + this.schemaCompareMainWindow.targetEndpointInfo = { endpointType: mssql.SchemaCompareEndpointType.Database, serverDisplayName: (this.targetServerDropdown.value as ConnectionDropdownValue).displayName, serverName: (this.targetServerDropdown.value as ConnectionDropdownValue).name, @@ -141,15 +138,15 @@ export class SchemaCompareDialog { }).send(); // update source and target values that are displayed - this.schemaCompareResult.updateSourceAndTarget(); + this.schemaCompareMainWindow.updateSourceAndTarget(); - const sourceEndpointChanged = this.endpointChanged(this.previousSource, this.schemaCompareResult.sourceEndpointInfo); - const targetEndpointChanged = this.endpointChanged(this.previousTarget, this.schemaCompareResult.targetEndpointInfo); + const sourceEndpointChanged = this.endpointChanged(this.previousSource, this.schemaCompareMainWindow.sourceEndpointInfo); + const targetEndpointChanged = this.endpointChanged(this.previousTarget, this.schemaCompareMainWindow.targetEndpointInfo); // show recompare message if it isn't the initial population of source and target if (this.previousSource && this.previousTarget && (sourceEndpointChanged || targetEndpointChanged)) { - this.schemaCompareResult.setButtonsForRecompare(); + this.schemaCompareMainWindow.setButtonsForRecompare(); let message = loc.differentSourceMessage; if (sourceEndpointChanged && targetEndpointChanged) { @@ -160,7 +157,7 @@ export class SchemaCompareDialog { vscode.window.showWarningMessage(message, loc.YesButtonText, loc.NoButtonText).then((result) => { if (result === loc.YesButtonText) { - this.schemaCompareResult.startCompare(); + this.schemaCompareMainWindow.startCompare(); } }); } @@ -177,10 +174,14 @@ export class SchemaCompareDialog { protected async cancel(): Promise { } - private initializeSchemaCompareTab(): void { + private async initializeSchemaCompareTab(): Promise { this.schemaCompareTab.registerContent(async view => { - this.sourceTextBox = view.modelBuilder.inputBox().withProperties({ - value: this.schemaCompareResult.sourceEndpointInfo ? this.schemaCompareResult.sourceEndpointInfo.packageFilePath : '', + if (isNullOrUndefined(this.view)) { + this.view = view; + } + + this.sourceTextBox = this.view.modelBuilder.inputBox().withProperties({ + value: this.schemaCompareMainWindow.sourceEndpointInfo ? this.schemaCompareMainWindow.sourceEndpointInfo.packageFilePath : '', width: 275, ariaLabel: loc.sourceFile }).component(); @@ -189,8 +190,8 @@ export class SchemaCompareDialog { this.dialog.okButton.enabled = await this.shouldEnableOkayButton(); }); - this.targetTextBox = view.modelBuilder.inputBox().withProperties({ - value: this.schemaCompareResult.targetEndpointInfo ? this.schemaCompareResult.targetEndpointInfo.packageFilePath : '', + this.targetTextBox = this.view.modelBuilder.inputBox().withProperties({ + value: this.schemaCompareMainWindow.targetEndpointInfo ? this.schemaCompareMainWindow.targetEndpointInfo.packageFilePath : '', width: 275, ariaLabel: loc.targetFile }).component(); @@ -199,36 +200,36 @@ export class SchemaCompareDialog { this.dialog.okButton.enabled = await this.shouldEnableOkayButton(); }); - this.sourceServerComponent = await this.createSourceServerDropdown(view); + this.sourceServerComponent = await this.createSourceServerDropdown(); await this.populateServerDropdown(false); - this.sourceDatabaseComponent = await this.createSourceDatabaseDropdown(view); + this.sourceDatabaseComponent = await this.createSourceDatabaseDropdown(); if ((this.sourceServerDropdown.value as ConnectionDropdownValue)) { await this.populateDatabaseDropdown((this.sourceServerDropdown.value as ConnectionDropdownValue).connection, false); } - this.targetServerComponent = await this.createTargetServerDropdown(view); + this.targetServerComponent = await this.createTargetServerDropdown(); await this.populateServerDropdown(true); - this.targetDatabaseComponent = await this.createTargetDatabaseDropdown(view); + this.targetDatabaseComponent = await this.createTargetDatabaseDropdown(); if ((this.targetServerDropdown.value as ConnectionDropdownValue)) { await this.populateDatabaseDropdown((this.targetServerDropdown.value as ConnectionDropdownValue).connection, true); } - this.sourceDacpacComponent = await this.createFileBrowser(view, false, this.schemaCompareResult.sourceEndpointInfo); - this.targetDacpacComponent = await this.createFileBrowser(view, true, this.schemaCompareResult.targetEndpointInfo); + this.sourceDacpacComponent = await this.createFileBrowser(false, this.schemaCompareMainWindow.sourceEndpointInfo); + this.targetDacpacComponent = await this.createFileBrowser(true, this.schemaCompareMainWindow.targetEndpointInfo); - let sourceRadioButtons = await this.createSourceRadiobuttons(view); - let targetRadioButtons = await this.createTargetRadiobuttons(view); + let sourceRadioButtons = await this.createSourceRadiobuttons(); + let targetRadioButtons = await this.createTargetRadiobuttons(); - this.sourceNoActiveConnectionsText = await this.createNoActiveConnectionsText(view); - this.targetNoActiveConnectionsText = await this.createNoActiveConnectionsText(view); + this.sourceNoActiveConnectionsText = await this.createNoActiveConnectionsText(); + this.targetNoActiveConnectionsText = await this.createNoActiveConnectionsText(); let sourceComponents = []; let targetComponents = []; // start source and target with either dacpac or database selection based on what the previous value was - if (this.schemaCompareResult.sourceEndpointInfo && this.schemaCompareResult.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { + if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { sourceComponents = [ sourceRadioButtons, this.sourceServerComponent, @@ -241,7 +242,7 @@ export class SchemaCompareDialog { ]; } - if (this.schemaCompareResult.targetEndpointInfo && this.schemaCompareResult.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { + if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { targetComponents = [ targetRadioButtons, this.targetServerComponent, @@ -254,7 +255,7 @@ export class SchemaCompareDialog { ]; } - this.formBuilder = view.modelBuilder.formContainer() + this.formBuilder = this.view.modelBuilder.formContainer() .withFormItems([ { title: loc.SourceTitle, @@ -273,25 +274,26 @@ export class SchemaCompareDialog { }); let formModel = this.formBuilder.component(); - await view.initializeModel(formModel); + await this.view.initializeModel(formModel); if (this.sourceIsDacpac) { await this.sourceDacpacRadioButton.focus(); } else { await this.sourceDatabaseRadioButton.focus(); } + this.initDialogComplete.resolve(); }); } - private createFileBrowser(view: azdata.ModelView, isTarget: boolean, endpoint: mssql.SchemaCompareEndpointInfo): azdata.FormComponent { + private createFileBrowser(isTarget: boolean, endpoint: mssql.SchemaCompareEndpointInfo): azdata.FormComponent { let currentTextbox = isTarget ? this.targetTextBox : this.sourceTextBox; if (isTarget) { - this.targetFileButton = view.modelBuilder.button().withProperties({ + this.targetFileButton = this.view.modelBuilder.button().withProperties({ label: '•••', title: loc.selectTargetFile, ariaLabel: loc.selectTargetFile }).component(); } else { - this.sourceFileButton = view.modelBuilder.button().withProperties({ + this.sourceFileButton = this.view.modelBuilder.button().withProperties({ label: '•••', title: loc.selectSourceFile, ariaLabel: loc.selectSourceFile @@ -333,14 +335,14 @@ export class SchemaCompareDialog { }; } - private createSourceRadiobuttons(view: azdata.ModelView): azdata.FormComponent { - this.sourceDacpacRadioButton = view.modelBuilder.radioButton() + private createSourceRadiobuttons(): azdata.FormComponent { + this.sourceDacpacRadioButton = this.view.modelBuilder.radioButton() .withProperties({ name: 'source', label: loc.DacpacRadioButtonLabel }).component(); - this.sourceDatabaseRadioButton = view.modelBuilder.radioButton() + this.sourceDatabaseRadioButton = this.view.modelBuilder.radioButton() .withProperties({ name: 'source', label: loc.DatabaseRadioButtonLabel @@ -370,14 +372,14 @@ export class SchemaCompareDialog { }); // if source is currently a db, show it in the server and db dropdowns - if (this.schemaCompareResult.sourceEndpointInfo && this.schemaCompareResult.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { + if (this.schemaCompareMainWindow.sourceEndpointInfo && this.schemaCompareMainWindow.sourceEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { this.sourceDatabaseRadioButton.checked = true; this.sourceIsDacpac = false; } else { this.sourceDacpacRadioButton.checked = true; this.sourceIsDacpac = true; } - let flexRadioButtonsModel = view.modelBuilder.flexContainer() + let flexRadioButtonsModel = this.view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'column' }) .withItems([this.sourceDacpacRadioButton, this.sourceDatabaseRadioButton]) .withProperties({ ariaRole: 'radiogroup' }) @@ -389,14 +391,14 @@ export class SchemaCompareDialog { }; } - private createTargetRadiobuttons(view: azdata.ModelView): azdata.FormComponent { - let dacpacRadioButton = view.modelBuilder.radioButton() + private createTargetRadiobuttons(): azdata.FormComponent { + let dacpacRadioButton = this.view.modelBuilder.radioButton() .withProperties({ name: 'target', label: loc.DacpacRadioButtonLabel }).component(); - let databaseRadioButton = view.modelBuilder.radioButton() + let databaseRadioButton = this.view.modelBuilder.radioButton() .withProperties({ name: 'target', label: loc.DatabaseRadioButtonLabel @@ -426,7 +428,7 @@ export class SchemaCompareDialog { }); // if target is currently a db, show it in the server and db dropdowns - if (this.schemaCompareResult.targetEndpointInfo && this.schemaCompareResult.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { + if (this.schemaCompareMainWindow.targetEndpointInfo && this.schemaCompareMainWindow.targetEndpointInfo.endpointType === mssql.SchemaCompareEndpointType.Database) { databaseRadioButton.checked = true; this.targetIsDacpac = false; } else { @@ -434,7 +436,7 @@ export class SchemaCompareDialog { this.targetIsDacpac = true; } - let flexRadioButtonsModel = view.modelBuilder.flexContainer() + let flexRadioButtonsModel = this.view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'column' }) .withItems([dacpacRadioButton, databaseRadioButton] ) @@ -461,8 +463,8 @@ export class SchemaCompareDialog { return !isNullOrUndefined(filename) && await exists(filename) && (filename.toLocaleLowerCase().endsWith('.dacpac')); } - protected createSourceServerDropdown(view: azdata.ModelView): azdata.FormComponent { - this.sourceServerDropdown = view.modelBuilder.dropDown().withProperties( + protected createSourceServerDropdown(): azdata.FormComponent { + this.sourceServerDropdown = this.view.modelBuilder.dropDown().withProperties( { editable: true, fireOnTextChange: true, @@ -487,8 +489,8 @@ export class SchemaCompareDialog { }; } - protected createTargetServerDropdown(view: azdata.ModelView): azdata.FormComponent { - this.targetServerDropdown = view.modelBuilder.dropDown().withProperties( + protected createTargetServerDropdown(): azdata.FormComponent { + this.targetServerDropdown = this.view.modelBuilder.dropDown().withProperties( { editable: true, fireOnTextChange: true, @@ -532,7 +534,7 @@ export class SchemaCompareDialog { return undefined; } - let endpointInfo = isTarget ? this.schemaCompareResult.targetEndpointInfo : this.schemaCompareResult.sourceEndpointInfo; + let endpointInfo = isTarget ? this.schemaCompareMainWindow.targetEndpointInfo : this.schemaCompareMainWindow.sourceEndpointInfo; // reverse list so that most recent connections are first cons.reverse(); @@ -585,8 +587,8 @@ export class SchemaCompareDialog { return values; } - protected createSourceDatabaseDropdown(view: azdata.ModelView): azdata.FormComponent { - this.sourceDatabaseDropdown = view.modelBuilder.dropDown().withProperties( + protected createSourceDatabaseDropdown(): azdata.FormComponent { + this.sourceDatabaseDropdown = this.view.modelBuilder.dropDown().withProperties( { editable: true, fireOnTextChange: true, @@ -604,8 +606,8 @@ export class SchemaCompareDialog { }; } - protected createTargetDatabaseDropdown(view: azdata.ModelView): azdata.FormComponent { - this.targetDatabaseDropdown = view.modelBuilder.dropDown().withProperties( + protected createTargetDatabaseDropdown(): azdata.FormComponent { + this.targetDatabaseDropdown = this.view.modelBuilder.dropDown().withProperties( { editable: true, fireOnTextChange: true, @@ -648,7 +650,7 @@ export class SchemaCompareDialog { } protected async getDatabaseValues(connectionId: string, isTarget: boolean): Promise { - let endpointInfo = isTarget ? this.schemaCompareResult.targetEndpointInfo : this.schemaCompareResult.sourceEndpointInfo; + let endpointInfo = isTarget ? this.schemaCompareMainWindow.targetEndpointInfo : this.schemaCompareMainWindow.sourceEndpointInfo; let idx = -1; let count = -1; @@ -672,8 +674,8 @@ export class SchemaCompareDialog { return values; } - protected createNoActiveConnectionsText(view: azdata.ModelView): azdata.FormComponent { - let noActiveConnectionsText = view.modelBuilder.text().withProperties({ value: loc.NoActiveConnectionsLabel }).component(); + protected createNoActiveConnectionsText(): azdata.FormComponent { + let noActiveConnectionsText = this.view.modelBuilder.text().withProperties({ value: loc.NoActiveConnectionsLabel }).component(); return { component: noActiveConnectionsText, diff --git a/extensions/schema-compare/src/test/schemaCompare.test.ts b/extensions/schema-compare/src/test/schemaCompare.test.ts index 2fc5432e23..7c9d96423f 100644 --- a/extensions/schema-compare/src/test/schemaCompare.test.ts +++ b/extensions/schema-compare/src/test/schemaCompare.test.ts @@ -28,23 +28,6 @@ before(function (): void { testContext = createContext(); }); -describe('SchemaCompareDialog.openDialog', function (): void { - before(() => { - mockExtensionContext = TypeMoq.Mock.ofType(); - mockExtensionContext.setup(x => x.extensionPath).returns(() => ''); - }); - - it('Should be correct when created.', async function (): Promise { - let schemaCompareResult = new SchemaCompareMainWindow(testContext.apiWrapper.object, undefined, mockExtensionContext.object); - let dialog = new SchemaCompareDialog(schemaCompareResult); - await dialog.openDialog(); - - should(dialog.dialog.title).equal('Schema Compare'); - should(dialog.dialog.okButton.label).equal('OK'); - should(dialog.dialog.okButton.enabled).equal(false); // Should be false when open - }); -}); - describe('SchemaCompareMainWindow.start', function (): void { before(() => { mockExtensionContext = TypeMoq.Mock.ofType(); @@ -162,10 +145,18 @@ describe('SchemaCompareMainWindow.execute', function (): void { await result.execute(); //Generate script button and apply button should be disabled for dacpac comparison - should(result.verifyButtonsState( {compareButtonState: true, optionsButtonState: true, switchButtonState: true, - openScmpButtonState: true, saveScmpButtonState: true, cancelCompareButtonState: false, - selectSourceButtonState: true, selectTargetButtonState: true, generateScriptButtonState: false, - applyButtonState: false} )).equal(true); + result.verifyButtonsState( { + compareButtonState: true, + optionsButtonState: true, + switchButtonState: true, + openScmpButtonState: true, + saveScmpButtonState: true, + cancelCompareButtonState: false, + selectSourceButtonState: true, + selectTargetButtonState: true, + generateScriptButtonState: false, + applyButtonState: false + } ); }); it('Should disable script button and apply button for Schema Compare service for database', async function (): Promise { @@ -183,10 +174,18 @@ describe('SchemaCompareMainWindow.execute', function (): void { await result.execute(); //Generate script button and apply button should be enabled for database comparison - should(result.verifyButtonsState( {compareButtonState: true, optionsButtonState: true, switchButtonState: true, - openScmpButtonState: true, saveScmpButtonState: true, cancelCompareButtonState: false, - selectSourceButtonState: true, selectTargetButtonState: true, generateScriptButtonState: true, - applyButtonState: true} )).equal(true); + result.verifyButtonsState( { + compareButtonState: true, + optionsButtonState: true, + switchButtonState: true, + openScmpButtonState: true, + saveScmpButtonState: true, + cancelCompareButtonState: false, + selectSourceButtonState: true, + selectTargetButtonState: true, + generateScriptButtonState: true, + applyButtonState: true + } ); }); }); @@ -213,10 +212,18 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { result.updateSourceAndTarget(); - should(result.verifyButtonsState( {compareButtonState: false, optionsButtonState: false, switchButtonState: false, - openScmpButtonState: true, saveScmpButtonState: false, cancelCompareButtonState: false, - selectSourceButtonState: true, selectTargetButtonState: true, generateScriptButtonState: false, - applyButtonState: false} )).equal(true); + result.verifyButtonsState( { + compareButtonState: false, + optionsButtonState: false, + switchButtonState: false, + openScmpButtonState: true, + saveScmpButtonState: false, + cancelCompareButtonState: false, + selectSourceButtonState: true, + selectTargetButtonState: true, + generateScriptButtonState: false, + applyButtonState: false + } ); }); it('Should set buttons appropriately when source endpoint is empty and target endpoint is populated', async function (): Promise { @@ -234,10 +241,18 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { result.updateSourceAndTarget(); - should(result.verifyButtonsState( {compareButtonState: false, optionsButtonState: false, switchButtonState: true, - openScmpButtonState: true, saveScmpButtonState: false, cancelCompareButtonState: false, - selectSourceButtonState: true, selectTargetButtonState: true, generateScriptButtonState: false, - applyButtonState: false} )).equal(true); + result.verifyButtonsState( { + compareButtonState: false, + optionsButtonState: false, + switchButtonState: true, + openScmpButtonState: true, + saveScmpButtonState: false, + cancelCompareButtonState: false, + selectSourceButtonState: true, + selectTargetButtonState: true, + generateScriptButtonState: false, + applyButtonState: false + } ); }); it('Should set buttons appropriately when source and target endpoints are populated', async function (): Promise { @@ -255,10 +270,18 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { result.updateSourceAndTarget(); - should(result.verifyButtonsState( {compareButtonState: true, optionsButtonState: true, switchButtonState: true, - openScmpButtonState: true, saveScmpButtonState: true, cancelCompareButtonState: false, - selectSourceButtonState: true, selectTargetButtonState: true, generateScriptButtonState: false, - applyButtonState: false} )).equal(true); + result.verifyButtonsState( { + compareButtonState: true, + optionsButtonState: true, + switchButtonState: true, + openScmpButtonState: true, + saveScmpButtonState: true, + cancelCompareButtonState: false, + selectSourceButtonState: true, + selectTargetButtonState: true, + generateScriptButtonState: false, + applyButtonState: false + } ); }); }); diff --git a/extensions/schema-compare/src/test/schemaCompareDialog.test.ts b/extensions/schema-compare/src/test/schemaCompareDialog.test.ts new file mode 100644 index 0000000000..7dc3de3307 --- /dev/null +++ b/extensions/schema-compare/src/test/schemaCompareDialog.test.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as should from 'should'; +import * as vscode from 'vscode'; +import * as TypeMoq from 'typemoq'; +import * as loc from '../localizedConstants'; +import 'mocha'; +import { SchemaCompareDialog } from './../dialogs/schemaCompareDialog'; +import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; +import { createContext, TestContext } from './testContext'; +import { setDacpacEndpointInfo } from './testUtils'; +import { SchemaCompareMainWindowTest } from './testSchemaCompareMainWindow'; + +// Mock test data +const mocksource: string = 'source.dacpac'; +const mocktarget: string = 'target.dacpac'; + +let mockExtensionContext: TypeMoq.IMock; +let testContext: TestContext; + +before(function (): void { + testContext = createContext(); +}); + +describe('SchemaCompareDialog.openDialog', function (): void { + before(() => { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockExtensionContext.setup(x => x.extensionPath).returns(() => ''); + }); + + it('Should be correct when created.', async function (): Promise { + let schemaCompareResult = new SchemaCompareMainWindow(testContext.apiWrapper.object, undefined, mockExtensionContext.object); + let dialog = new SchemaCompareDialog(schemaCompareResult); + await dialog.openDialog(); + + should(dialog.dialog.title).equal(loc.SchemaCompareLabel); + should(dialog.dialog.okButton.label).equal(loc.OkButtonText); + should(dialog.dialog.okButton.enabled).equal(false); // Should be false when open + }); + + it('Simulate ok button- with both endpoints set to dacpac', async function (): Promise { + let schemaCompareResult = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, undefined, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + + let dialog = new SchemaCompareDialog(schemaCompareResult); + await dialog.openDialog(); + + await dialog.execute(); + + // Confirm that ok button got clicked + schemaCompareResult.verifyButtonsState( { + compareButtonState: true, + optionsButtonState: true, + switchButtonState: true, + openScmpButtonState: true, + saveScmpButtonState: true, + cancelCompareButtonState: false, + selectSourceButtonState: true, + selectTargetButtonState: true, + generateScriptButtonState: false, + applyButtonState: false + } ); + }); +}); diff --git a/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts b/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts index 184d0943b0..275fdd169f 100644 --- a/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts +++ b/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import * as mssql from '../../../mssql'; +import * as should from 'should'; import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; import { ApiWrapper } from '../common/apiWrapper'; @@ -34,26 +35,7 @@ export class SchemaCompareMainWindowTest extends SchemaCompareMainWindow { return this.comparisonResult; } - // only for test - public getButtonsState(): ButtonState { - - let buttonObject: ButtonState = { - compareButtonState: this.compareButton.enabled, - optionsButtonState: this.optionsButton.enabled, - switchButtonState: this.switchButton.enabled, - openScmpButtonState: this.openScmpButton.enabled, - saveScmpButtonState: this.saveScmpButton.enabled, - cancelCompareButtonState: this.cancelCompareButton.enabled, - selectSourceButtonState: this.selectSourceButton.enabled, - selectTargetButtonState: this.selectTargetButton.enabled, - generateScriptButtonState: this.generateScriptButton.enabled, - applyButtonState: this.applyButton.enabled - }; - - return buttonObject; - } - - public verifyButtonsState(buttonState: ButtonState): boolean { + public verifyButtonsState(buttonState: ButtonState): void { let result: boolean = false; if (this.compareButton.enabled === buttonState.compareButtonState && @@ -69,6 +51,15 @@ export class SchemaCompareMainWindowTest extends SchemaCompareMainWindow { result = true; } - return result; + should(result).equal(true, `CompareButton: (Actual) ${this.compareButton.enabled} (Expected) ${buttonState.compareButtonState} + OptionsButton: (Actual) ${this.optionsButton.enabled} (Expected) ${buttonState.optionsButtonState} + SwitchButton: (Actual) ${this.switchButton.enabled} (Expected) ${buttonState.switchButtonState} + OpenScmpButton: (Actual) ${this.openScmpButton.enabled} (Expected) ${buttonState.openScmpButtonState} + SaveScmpButton: (Actual) ${this.saveScmpButton.enabled} (Expected) ${buttonState.saveScmpButtonState} + CancelCompareButton: (Actual) ${this.cancelCompareButton.enabled} (Expected) ${buttonState.cancelCompareButtonState} + SelectSourceButton: (Actual) ${this.selectSourceButton.enabled} (Expected) ${buttonState.selectSourceButtonState} + SelectTargetButton: (Actual) ${this.selectTargetButton.enabled} (Expected) ${buttonState.selectTargetButtonState} + GenerateScriptButton: (Actual) ${this.generateScriptButton.enabled} (Expected) ${buttonState.generateScriptButtonState} + ApplyButton: (Actual) ${this.applyButton.enabled} (Expected) ${buttonState.applyButtonState}`); } } diff --git a/extensions/schema-compare/src/test/utils.test.ts b/extensions/schema-compare/src/test/utils.test.ts index 0d448aaa7d..8ee3a99097 100644 --- a/extensions/schema-compare/src/test/utils.test.ts +++ b/extensions/schema-compare/src/test/utils.test.ts @@ -8,8 +8,12 @@ import * as azdata from 'azdata'; import * as mssql from '../../../mssql'; import * as loc from '../localizedConstants'; import * as TypeMoq from 'typemoq'; -import {getEndpointName, verifyConnectionAndGetOwnerUri } from '../utils'; -import {mockDacpacEndpoint, mockDatabaseEndpoint, mockFilePath, mockConnectionInfo, shouldThrowSpecificError, mockConnectionResult, mockConnectionProfile} from './testUtils'; +import * as path from 'path'; +import * as uuid from 'uuid'; +import * as os from 'os'; +import { promises as fs } from 'fs'; +import { getEndpointName, verifyConnectionAndGetOwnerUri, exists } from '../utils'; +import { mockDacpacEndpoint, mockDatabaseEndpoint, mockFilePath, mockConnectionInfo, shouldThrowSpecificError, mockConnectionResult, mockConnectionProfile } from './testUtils'; import { createContext, TestContext } from './testContext'; let testContext: TestContext; @@ -24,8 +28,8 @@ describe('utils: Tests to verify getEndpointName', function (): void { }); it('Should get endpoint information from ConnectionInfo', () => { - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; - testDatabaseEndpoint.connectionDetails = {...mockConnectionInfo}; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; + testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo }; should(getEndpointName(testDatabaseEndpoint)).equal('My Server.My Database'); }); @@ -33,7 +37,7 @@ describe('utils: Tests to verify getEndpointName', function (): void { it('Should get correct endpoint information from SchemaCompareEndpointInfo', () => { let dbName = 'My Database'; let serverName = 'My Server'; - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; testDatabaseEndpoint.databaseName = dbName; testDatabaseEndpoint.serverName = serverName; @@ -55,7 +59,7 @@ describe('utils: Basic tests to verify verifyConnectionAndGetOwnerUri', function it('Should return undefined for endpoint as database and no ConnectionInfo', async function (): Promise { let ownerUri = undefined; - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; testDatabaseEndpoint.connectionDetails = undefined; ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object); @@ -71,9 +75,9 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct it('Should throw an error asking to make a connection', async function (): Promise { let getConnectionsResults: azdata.connection.ConnectionProfile[] = []; - let connection = {...mockConnectionResult}; - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; - testDatabaseEndpoint.connectionDetails = {...mockConnectionInfo}; + let connection = { ...mockConnectionResult }; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; + testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo }; const getConnectionString = loc.getConnectionString('test'); testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); }); @@ -85,10 +89,10 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct }); it('Should throw an error for login failure', async function (): Promise { - let getConnectionsResults: azdata.connection.ConnectionProfile[] = [{...mockConnectionProfile}]; - let connection = {...mockConnectionResult}; - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; - testDatabaseEndpoint.connectionDetails = {...mockConnectionInfo}; + let getConnectionsResults: azdata.connection.ConnectionProfile[] = [{ ...mockConnectionProfile }]; + let connection = { ...mockConnectionResult }; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; + testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo }; testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); }); testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); }); @@ -100,9 +104,9 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct it('Should throw an error for login failure with openConnectionDialog but no ownerUri', async function (): Promise { let getConnectionsResults: azdata.connection.ConnectionProfile[] = []; - let connection = {...mockConnectionResult}; - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; - testDatabaseEndpoint.connectionDetails = {...mockConnectionInfo}; + let connection = { ...mockConnectionResult }; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; + testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo }; testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); }); testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); }); @@ -115,10 +119,10 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct it('Should not throw an error and set ownerUri appropriately', async function (): Promise { let ownerUri = undefined; - let connection = {...mockConnectionResult}; - let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = {...mockDatabaseEndpoint}; + let connection = { ...mockConnectionResult }; + let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; let expectedOwnerUri: string = 'providerName:MSSQL|authenticationType:SqlLogin|database:My Database|server:My Server|user:My User|databaseDisplayName:My Database'; - testDatabaseEndpoint.connectionDetails = {...mockConnectionInfo}; + testDatabaseEndpoint.connectionDetails = { ...mockConnectionInfo }; testContext.apiWrapper.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(connection); }); testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(expectedOwnerUri); }); @@ -128,3 +132,18 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct should(ownerUri).equal(expectedOwnerUri); }); }); + +describe('utils: Test to verify exists method', () => { + it('Should run as expected', async () => { + const filename = path.join(os.tmpdir(), `SchemaCompareUtilsTest_${uuid.v4()}`); + try { + should(await exists(filename)).be.false(); + await fs.writeFile(filename, ''); + should(await exists(filename)).be.true(); + } finally { + try { + await fs.unlink(filename); + } catch { /* no-op */ } + } + }); +}); diff --git a/extensions/schema-compare/src/utils.ts b/extensions/schema-compare/src/utils.ts index d3c0f9ff39..974c2705ea 100644 --- a/extensions/schema-compare/src/utils.ts +++ b/extensions/schema-compare/src/utils.ts @@ -9,6 +9,7 @@ import * as mssql from '../../mssql'; import * as os from 'os'; import * as loc from './localizedConstants'; import { ApiWrapper } from './common/apiWrapper'; +import { promises as fs } from 'fs'; export interface IPackageInfo { name: string; @@ -84,7 +85,7 @@ function connectionInfoToConnectionProfile(details: azdata.ConnectionInfo): azda }; } -export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise { +export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise { let ownerUri = undefined; if (endpoint.endpointType === mssql.SchemaCompareEndpointType.Database && endpoint.connectionDetails) { @@ -132,3 +133,12 @@ export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompa export function getRootPath(): string { return vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : os.homedir(); } + +export async function exists(path: string): Promise { + try { + await fs.access(path); + return true; + } catch (e) { + return false; + } +}