Add test coverage for dacpac wizard import flow (#11483)

* Adding importConfig onPageEnter() test

* Removing redundancy from dacpac wizard pages

* promisifying file selection so it can be awaited in the test

* removing debug prints

* PR feedback
This commit is contained in:
Benjin Dubishar
2020-07-23 02:15:05 -07:00
committed by GitHub
parent 21cf89fb7e
commit c963e6ec08
13 changed files with 119 additions and 89 deletions

View File

@@ -5,10 +5,17 @@
import 'mocha'; import 'mocha';
import * as should from 'should'; import * as should from 'should';
import * as sinon from 'sinon';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as os from 'os';
import * as path from 'path';
import { DataTierApplicationWizard, PageName } from '../wizard/dataTierApplicationWizard'; import { DataTierApplicationWizard, PageName } from '../wizard/dataTierApplicationWizard';
import { DacFxDataModel } from '../wizard/api/models'; import { DacFxDataModel } from '../wizard/api/models';
import { TestContext, createContext } from './testContext'; import { TestContext, createContext } from './testContext';
import { TestDeployConfigPage, TestExtractConfigPage } from './testDacFxConfigPages'; import { TestDeployConfigPage, TestExtractConfigPage, TestImportConfigPage } from './testDacFxConfigPages';
import { mockConnectionProfile } from './testUtils';
let wizard: DataTierApplicationWizard; let wizard: DataTierApplicationWizard;
let testContext: TestContext; let testContext: TestContext;
@@ -20,6 +27,10 @@ describe('Dacfx Wizard Pages', function (): void {
wizard.model.server = undefined; wizard.model.server = undefined;
}); });
afterEach(function (): void {
sinon.restore();
});
it('Should open and edit deploy config page correctly', async () => { it('Should open and edit deploy config page correctly', async () => {
testContext = createContext(); testContext = createContext();
wizard.setPages(); wizard.setPages();
@@ -74,4 +85,28 @@ describe('Dacfx Wizard Pages', function (): void {
await extractConfigPage.onPageLeave(); await extractConfigPage.onPageLeave();
should.equal(extractConfigPage.Model.filePath, 'DummyPath.dacpac'); should.equal(extractConfigPage.Model.filePath, 'DummyPath.dacpac');
}); });
it('Should open and edit import config page correctly', async () => {
const dacpacPath = vscode.Uri.file(path.join(os.tmpdir(), 'myDatabase.dacpac')).fsPath;
testContext = createContext();
wizard.setPages();
sinon.stub(azdata.connection, 'getConnections').resolves([mockConnectionProfile]);
sinon.stub(azdata.connection, 'listDatabases').resolves(['fakeDatabaseName']);
sinon.stub(vscode.window, 'showOpenDialog').resolves([vscode.Uri.file(dacpacPath)]);
let importConfigPage = new TestImportConfigPage(wizard, wizard.pages.get(PageName.importConfig).wizardPage, wizard.model, testContext.viewContext.view);
await importConfigPage.start();
let result = await importConfigPage.onPageEnter();
should(result).equal(true, 'onPageEnter() should successfullly load connection profiles');
testContext.viewContext.fileButtonOnClick.fire(undefined);
await importConfigPage.selectionPromise;
should(importConfigPage.Model.filePath).equal(dacpacPath);
should(importConfigPage.Model.database).equal('myDatabase');
});
}); });

View File

@@ -51,6 +51,7 @@ export interface ViewTestContext {
extractOnClick: vscode.EventEmitter<any>, extractOnClick: vscode.EventEmitter<any>,
exportOnClick: vscode.EventEmitter<any>, exportOnClick: vscode.EventEmitter<any>,
importOnClick: vscode.EventEmitter<any>, importOnClick: vscode.EventEmitter<any>,
fileButtonOnClick: vscode.EventEmitter<any>;
} }
export function createViewContext(): ViewTestContext { export function createViewContext(): ViewTestContext {
@@ -63,6 +64,7 @@ export function createViewContext(): ViewTestContext {
let extractOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>(); let extractOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
let exportOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>(); let exportOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
let importOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>(); let importOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
let fileButtonOnClick: vscode.EventEmitter<any> = new vscode.EventEmitter<any>();
let componentBase: azdata.Component = { let componentBase: azdata.Component = {
id: '', id: '',
@@ -110,7 +112,13 @@ export function createViewContext(): ViewTestContext {
let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = { let buttonBuilder: azdata.ComponentBuilder<azdata.ButtonComponent> = {
component: () => button, component: () => button,
withProperties: () => buttonBuilder, withProperties: (properties) => {
if ((properties as any).label === '•••') {
button.label = '•••';
button.onDidClick = fileButtonOnClick.event;
}
return buttonBuilder;
},
withValidation: () => buttonBuilder withValidation: () => buttonBuilder
}; };
@@ -295,5 +303,6 @@ export function createViewContext(): ViewTestContext {
extractOnClick: extractOnClick, extractOnClick: extractOnClick,
exportOnClick: exportOnClick, exportOnClick: exportOnClick,
importOnClick: importOnClick, importOnClick: importOnClick,
fileButtonOnClick: fileButtonOnClick
}; };
} }

View File

@@ -9,9 +9,9 @@ import { DeployConfigPage } from '../wizard/pages/deployConfigPage';
import { ExtractConfigPage } from '../wizard/pages/extractConfigPage'; import { ExtractConfigPage } from '../wizard/pages/extractConfigPage';
import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard'; import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard';
import { SelectOperationPage } from '../wizard/pages/selectOperationpage'; import { SelectOperationPage } from '../wizard/pages/selectOperationpage';
import { ImportConfigPage } from '../wizard/pages/importConfigPage';
export class TestDeployConfigPage extends DeployConfigPage { export class TestDeployConfigPage extends DeployConfigPage {
constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view); super(instance, wizardPage, model, view);
} }
@@ -30,7 +30,6 @@ export class TestDeployConfigPage extends DeployConfigPage {
} }
export class TestExtractConfigPage extends ExtractConfigPage { export class TestExtractConfigPage extends ExtractConfigPage {
constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view); super(instance, wizardPage, model, view);
} }
@@ -41,7 +40,6 @@ export class TestExtractConfigPage extends ExtractConfigPage {
} }
export class TestSelectOperationPage extends SelectOperationPage { export class TestSelectOperationPage extends SelectOperationPage {
constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view); super(instance, wizardPage, model, view);
} }
@@ -50,3 +48,13 @@ export class TestSelectOperationPage extends SelectOperationPage {
return this.instance; return this.instance;
} }
} }
export class TestImportConfigPage extends ImportConfigPage {
constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view);
}
get Model(): DacFxDataModel {
return this.model;
}
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
export const mockConnectionProfile: azdata.connection.ConnectionProfile = {
providerId: 'MSSQL',
connectionId: 'My Connection ID',
connectionName: 'My Connection',
serverName: 'My Server',
databaseName: 'My Database',
userName: 'My User',
password: 'My Pwd',
authenticationType: 'SqlLogin',
savePassword: false,
groupFullName: 'My groupName',
groupId: 'My GroupId',
saveProfile: true,
options: {}
};

View File

@@ -6,14 +6,22 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import * as loc from '../../localizedConstants'; import * as loc from '../../localizedConstants';
import { DacFxDataModel } from './models'; import { DacFxDataModel } from './models';
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
export abstract class BasePage { export abstract class BasePage {
protected readonly instance: DataTierApplicationWizard;
protected readonly wizardPage: azdata.window.WizardPage; protected readonly wizardPage: azdata.window.WizardPage;
protected readonly model: DacFxDataModel; protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView; protected readonly view: azdata.ModelView;
public databaseValues: string[]; public databaseValues: string[];
protected constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
}
/** /**
* This method constructs all the elements of the page. * This method constructs all the elements of the page.
*/ */

View File

@@ -14,11 +14,6 @@ import { BasePage } from './basePage';
import { sanitizeStringForFilename, isValidBasename, isValidBasenameErrorMessage } from './utils'; import { sanitizeStringForFilename, isValidBasename, isValidBasenameErrorMessage } from './utils';
export abstract class DacFxConfigPage extends BasePage { export abstract class DacFxConfigPage extends BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
protected serverDropdown: azdata.DropDownComponent; protected serverDropdown: azdata.DropDownComponent;
protected databaseTextBox: azdata.InputBoxComponent; protected databaseTextBox: azdata.InputBoxComponent;
protected databaseDropdown: azdata.DropDownComponent; protected databaseDropdown: azdata.DropDownComponent;
@@ -28,11 +23,7 @@ export abstract class DacFxConfigPage extends BasePage {
protected fileExtension: string; protected fileExtension: string;
protected constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { protected constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(); super(instance, wizardPage, model, view);
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
} }
public setupNavigationValidator(): void { public setupNavigationValidator(): void {
@@ -67,6 +58,7 @@ export abstract class DacFxConfigPage extends BasePage {
protected async populateServerDropdown(): Promise<boolean> { protected async populateServerDropdown(): Promise<boolean> {
let values = await this.getServerValues(); let values = await this.getServerValues();
if (values === undefined) { if (values === undefined) {
return false; return false;
} }

View File

@@ -10,22 +10,12 @@ import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWiza
import { BasePage } from '../api/basePage'; import { BasePage } from '../api/basePage';
export class DacFxSummaryPage extends BasePage { export class DacFxSummaryPage extends BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer; private form: azdata.FormContainer;
private table: azdata.TableComponent; private table: azdata.TableComponent;
private loader: azdata.LoadingComponent; private loader: azdata.LoadingComponent;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(); super(instance, wizardPage, model, view);
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
} }
async start(): Promise<boolean> { async start(): Promise<boolean> {

View File

@@ -12,11 +12,6 @@ import { DacFxConfigPage } from '../api/dacFxConfigPage';
import { generateDatabaseName } from '../api/utils'; import { generateDatabaseName } from '../api/utils';
export class DeployConfigPage extends DacFxConfigPage { export class DeployConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private databaseDropdownComponent: azdata.FormComponent; private databaseDropdownComponent: azdata.FormComponent;
private databaseComponent: azdata.FormComponent; private databaseComponent: azdata.FormComponent;
private formBuilder: azdata.FormBuilder; private formBuilder: azdata.FormBuilder;

View File

@@ -34,10 +34,6 @@ class DeployPlanResult {
} }
export class DeployPlanPage extends DacFxConfigPage { export class DeployPlanPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private formBuilder: azdata.FormBuilder; private formBuilder: azdata.FormBuilder;
private form: azdata.FormContainer; private form: azdata.FormContainer;
private table: azdata.TableComponent; private table: azdata.TableComponent;

View File

@@ -11,12 +11,6 @@ import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage'; import { DacFxConfigPage } from '../api/dacFxConfigPage';
export class ExportConfigPage extends DacFxConfigPage { export class ExportConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer; private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {

View File

@@ -11,12 +11,6 @@ import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
import { DacFxConfigPage } from '../api/dacFxConfigPage'; import { DacFxConfigPage } from '../api/dacFxConfigPage';
export class ExtractConfigPage extends DacFxConfigPage { export class ExtractConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer; private form: azdata.FormContainer;
private versionTextBox: azdata.InputBoxComponent; private versionTextBox: azdata.InputBoxComponent;

View File

@@ -12,13 +12,8 @@ import { DacFxConfigPage } from '../api/dacFxConfigPage';
import { generateDatabaseName } from '../api/utils'; import { generateDatabaseName } from '../api/utils';
export class ImportConfigPage extends DacFxConfigPage { export class ImportConfigPage extends DacFxConfigPage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private form: azdata.FormContainer; private form: azdata.FormContainer;
public selectionPromise: Promise<void>;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(instance, wizardPage, model, view); super(instance, wizardPage, model, view);
@@ -54,7 +49,22 @@ export class ImportConfigPage extends DacFxConfigPage {
private async createFileBrowser(): Promise<azdata.FormComponent> { private async createFileBrowser(): Promise<azdata.FormComponent> {
this.createFileBrowserParts(); this.createFileBrowserParts();
this.fileButton.onDidClick(async (click) => { this.fileButton.onDidClick(async (click) => { this.selectionPromise = this.handleFileSelection(); });
this.fileTextBox.onTextChanged(async () => {
this.model.filePath = this.fileTextBox.value;
this.model.database = generateDatabaseName(this.model.filePath);
this.databaseTextBox.value = this.model.database;
});
return {
component: this.fileTextBox,
title: loc.fileLocation,
actions: [this.fileButton]
};
}
private async handleFileSelection(): Promise<void> {
let fileUris = await vscode.window.showOpenDialog( let fileUris = await vscode.window.showOpenDialog(
{ {
canSelectFiles: true, canSelectFiles: true,
@@ -77,18 +87,5 @@ export class ImportConfigPage extends DacFxConfigPage {
this.model.filePath = fileUri.fsPath; this.model.filePath = fileUri.fsPath;
this.model.database = generateDatabaseName(this.model.filePath); this.model.database = generateDatabaseName(this.model.filePath);
this.databaseTextBox.value = this.model.database; this.databaseTextBox.value = this.model.database;
});
this.fileTextBox.onTextChanged(async () => {
this.model.filePath = this.fileTextBox.value;
this.model.database = generateDatabaseName(this.model.filePath);
this.databaseTextBox.value = this.model.database;
});
return {
component: this.fileTextBox,
title: loc.fileLocation,
actions: [this.fileButton]
};
} }
} }

View File

@@ -10,12 +10,6 @@ import { DataTierApplicationWizard, Operation, DeployOperationPath, ExtractOpera
import { BasePage } from '../api/basePage'; import { BasePage } from '../api/basePage';
export class SelectOperationPage extends BasePage { export class SelectOperationPage extends BasePage {
protected readonly wizardPage: azdata.window.WizardPage;
protected readonly instance: DataTierApplicationWizard;
protected readonly model: DacFxDataModel;
protected readonly view: azdata.ModelView;
private deployRadioButton: azdata.RadioButtonComponent; private deployRadioButton: azdata.RadioButtonComponent;
private extractRadioButton: azdata.RadioButtonComponent; private extractRadioButton: azdata.RadioButtonComponent;
private importRadioButton: azdata.RadioButtonComponent; private importRadioButton: azdata.RadioButtonComponent;
@@ -23,11 +17,7 @@ export class SelectOperationPage extends BasePage {
private form: azdata.FormContainer; private form: azdata.FormContainer;
public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) { public constructor(instance: DataTierApplicationWizard, wizardPage: azdata.window.WizardPage, model: DacFxDataModel, view: azdata.ModelView) {
super(); super(instance, wizardPage, model, view);
this.instance = instance;
this.wizardPage = wizardPage;
this.model = model;
this.view = view;
} }
async start(): Promise<boolean> { async start(): Promise<boolean> {