Schema Compare test coverage (#11042)

* Few tests for Schema Compare

* Addressed comment- removed wait in test

* Split setEndpointInfo for database and dacpac
This commit is contained in:
Sakshi Sharma
2020-06-24 14:03:02 -07:00
committed by GitHub
parent 561b881200
commit de263eacd1
5 changed files with 331 additions and 162 deletions

View File

@@ -30,7 +30,7 @@ export default class MainController implements Disposable {
}
private initializeSchemaCompareDialog(): void {
this.apiWrapper.registerCommand('schemaCompare.start', (context: any) => this.schemaCompareMainWindow.start(context));
this.apiWrapper.registerCommand('schemaCompare.start', async (context: any) => { await this.schemaCompareMainWindow.start(context); });
}
public dispose(): void {

View File

@@ -82,12 +82,12 @@ export class SchemaCompareMainWindow {
// 1. undefined
// 2. connection profile
// 3. dacpac
public async start(context: any) {
public async start(context: any): Promise<void> {
// if schema compare was launched from a db, set that as the source
let profile = context ? <azdata.IConnectionProfile>context.connectionProfile : undefined;
let sourceDacpac = context as string;
if (profile) {
let ownerUri = await azdata.connection.getUriForConnection((profile.id));
let ownerUri = await this.apiWrapper.getUriForConnection((profile.id));
this.sourceEndpointInfo = {
endpointType: mssql.SchemaCompareEndpointType.Database,
serverDisplayName: `${profile.serverName} ${profile.userName}`,
@@ -109,138 +109,146 @@ export class SchemaCompareMainWindow {
};
}
this.editor.registerContent(async view => {
this.differencesTable = view.modelBuilder.table().withProperties({
data: [],
title: loc.differencesTableTitle
}).component();
this.diffEditor = view.modelBuilder.diffeditor().withProperties({
contentLeft: os.EOL,
contentRight: os.EOL,
height: 500,
title: loc.diffEditorTitle
}).component();
this.splitView = view.modelBuilder.splitViewContainer().component();
let sourceTargetLabels = view.modelBuilder.flexContainer()
.withProperties({
alignItems: 'stretch',
horizontal: true
}).component();
this.sourceTargetFlexLayout = view.modelBuilder.flexContainer()
.withProperties({
alignItems: 'stretch',
horizontal: true
}).component();
this.createSwitchButton(view);
this.createCompareButton(view);
this.createCancelButton(view);
this.createGenerateScriptButton(view);
this.createApplyButton(view);
this.createOptionsButton(view);
this.createOpenScmpButton(view);
this.createSaveScmpButton(view);
this.createSourceAndTargetButtons(view);
this.resetButtons(ResetButtonState.noSourceTarget);
let toolBar = view.modelBuilder.toolbarContainer();
toolBar.addToolbarItems([{
component: this.compareButton
}, {
component: this.cancelCompareButton
}, {
component: this.generateScriptButton
}, {
component: this.applyButton
}, {
component: this.optionsButton,
toolbarSeparatorAfter: true
}, {
component: this.switchButton,
toolbarSeparatorAfter: true
}, {
component: this.openScmpButton
}, {
component: this.saveScmpButton
}]);
let sourceLabel = view.modelBuilder.text().withProperties({
value: loc.sourceTitle,
CSSStyles: { 'margin-bottom': '0px' }
}).component();
let targetLabel = view.modelBuilder.text().withProperties({
value: loc.targetTitle,
CSSStyles: { 'margin-bottom': '0px' }
}).component();
let arrowLabel = view.modelBuilder.text().withProperties({
value: '➔'
}).component();
this.sourceName = getEndpointName(this.sourceEndpointInfo);
this.targetName = ' ';
this.sourceNameComponent = view.modelBuilder.table().withProperties({
columns: [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]
}).component();
this.targetNameComponent = view.modelBuilder.table().withProperties({
columns: [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]
}).component();
sourceTargetLabels.addItem(sourceLabel, { CSSStyles: { 'width': '55%', 'margin-left': '15px', 'font-size': 'larger', 'font-weight': 'bold' } });
sourceTargetLabels.addItem(targetLabel, { CSSStyles: { 'width': '45%', 'font-size': 'larger', 'font-weight': 'bold' } });
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectSourceButton, { CSSStyles: { 'margin-top': '10px' } });
this.sourceTargetFlexLayout.addItem(arrowLabel, { CSSStyles: { 'width': '10%', 'font-size': 'larger', 'text-align-last': 'center' } });
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectTargetButton, { CSSStyles: { 'margin-top': '10px' } });
this.loader = view.modelBuilder.loadingComponent().component();
this.waitText = view.modelBuilder.text().withProperties({
value: loc.waitText
}).component();
this.startText = view.modelBuilder.text().withProperties({
value: loc.startText
}).component();
this.noDifferencesLabel = view.modelBuilder.text().withProperties({
value: loc.noDifferencesText
}).component();
this.flexModel = view.modelBuilder.flexContainer().component();
this.flexModel.addItem(toolBar.component(), { flex: 'none' });
this.flexModel.addItem(sourceTargetLabels, { flex: 'none' });
this.flexModel.addItem(this.sourceTargetFlexLayout, { flex: 'none' });
this.flexModel.addItem(this.startText, { CSSStyles: { 'margin': 'auto' } });
this.flexModel.setLayout({
flexFlow: 'column',
height: '100%'
});
await view.initializeModel(this.flexModel);
});
await this.GetDefaultDeploymentOptions();
this.editor.openEditor();
await Promise.all([
this.registerContent(),
this.editor.openEditor()
]);
}
private async registerContent(): Promise<void> {
return new Promise<void>((resolve) => {
this.editor.registerContent(async view => {
this.differencesTable = view.modelBuilder.table().withProperties({
data: [],
title: loc.differencesTableTitle
}).component();
this.diffEditor = view.modelBuilder.diffeditor().withProperties({
contentLeft: os.EOL,
contentRight: os.EOL,
height: 500,
title: loc.diffEditorTitle
}).component();
this.splitView = view.modelBuilder.splitViewContainer().component();
let sourceTargetLabels = view.modelBuilder.flexContainer()
.withProperties({
alignItems: 'stretch',
horizontal: true
}).component();
this.sourceTargetFlexLayout = view.modelBuilder.flexContainer()
.withProperties({
alignItems: 'stretch',
horizontal: true
}).component();
this.createSwitchButton(view);
this.createCompareButton(view);
this.createCancelButton(view);
this.createGenerateScriptButton(view);
this.createApplyButton(view);
this.createOptionsButton(view);
this.createOpenScmpButton(view);
this.createSaveScmpButton(view);
this.createSourceAndTargetButtons(view);
this.resetButtons(ResetButtonState.noSourceTarget);
let toolBar = view.modelBuilder.toolbarContainer();
toolBar.addToolbarItems([{
component: this.compareButton
}, {
component: this.cancelCompareButton
}, {
component: this.generateScriptButton
}, {
component: this.applyButton
}, {
component: this.optionsButton,
toolbarSeparatorAfter: true
}, {
component: this.switchButton,
toolbarSeparatorAfter: true
}, {
component: this.openScmpButton
}, {
component: this.saveScmpButton
}]);
let sourceLabel = view.modelBuilder.text().withProperties({
value: loc.sourceTitle,
CSSStyles: { 'margin-bottom': '0px' }
}).component();
let targetLabel = view.modelBuilder.text().withProperties({
value: loc.targetTitle,
CSSStyles: { 'margin-bottom': '0px' }
}).component();
let arrowLabel = view.modelBuilder.text().withProperties({
value: '➔'
}).component();
this.sourceName = getEndpointName(this.sourceEndpointInfo);
this.targetName = ' ';
this.sourceNameComponent = view.modelBuilder.table().withProperties({
columns: [
{
value: this.sourceName,
headerCssClass: 'no-borders',
toolTip: this.sourceName
},
]
}).component();
this.targetNameComponent = view.modelBuilder.table().withProperties({
columns: [
{
value: this.targetName,
headerCssClass: 'no-borders',
toolTip: this.targetName
},
]
}).component();
sourceTargetLabels.addItem(sourceLabel, { CSSStyles: { 'width': '55%', 'margin-left': '15px', 'font-size': 'larger', 'font-weight': 'bold' } });
sourceTargetLabels.addItem(targetLabel, { CSSStyles: { 'width': '45%', 'font-size': 'larger', 'font-weight': 'bold' } });
this.sourceTargetFlexLayout.addItem(this.sourceNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectSourceButton, { CSSStyles: { 'margin-top': '10px' } });
this.sourceTargetFlexLayout.addItem(arrowLabel, { CSSStyles: { 'width': '10%', 'font-size': 'larger', 'text-align-last': 'center' } });
this.sourceTargetFlexLayout.addItem(this.targetNameComponent, { CSSStyles: { 'width': '40%', 'height': '25px', 'margin-top': '10px', 'margin-left': '15px' } });
this.sourceTargetFlexLayout.addItem(this.selectTargetButton, { CSSStyles: { 'margin-top': '10px' } });
this.loader = view.modelBuilder.loadingComponent().component();
this.waitText = view.modelBuilder.text().withProperties({
value: loc.waitText
}).component();
this.startText = view.modelBuilder.text().withProperties({
value: loc.startText
}).component();
this.noDifferencesLabel = view.modelBuilder.text().withProperties({
value: loc.noDifferencesText
}).component();
this.flexModel = view.modelBuilder.flexContainer().component();
this.flexModel.addItem(toolBar.component(), { flex: 'none' });
this.flexModel.addItem(sourceTargetLabels, { flex: 'none' });
this.flexModel.addItem(this.sourceTargetFlexLayout, { flex: 'none' });
this.flexModel.addItem(this.startText, { CSSStyles: { 'margin': 'auto' } });
this.flexModel.setLayout({
flexFlow: 'column',
height: '100%'
});
await view.initializeModel(this.flexModel);
resolve();
});
});
}
// update source and target name to display
@@ -281,6 +289,15 @@ export class SchemaCompareMainWindow {
return this.deploymentOptions;
}
// only for test
public verifyButtonsState(generateScriptButtonState: boolean, applyButtonState: boolean): boolean {
let result: boolean = false;
if (this.generateScriptButton.enabled === generateScriptButtonState && this.applyButton.enabled === applyButtonState) {
result = true;
}
return result;
}
public setDeploymentOptions(deploymentOptions: mssql.DeploymentOptions): void {
this.deploymentOptions = deploymentOptions;
}
@@ -298,7 +315,7 @@ export class SchemaCompareMainWindow {
.withAdditionalProperties({
operationId: this.comparisonResult.operationId
}).send();
vscode.window.showErrorMessage(loc.compareErrorMessage(this.comparisonResult.errorMessage));
this.apiWrapper.showErrorMessage(loc.compareErrorMessage(this.comparisonResult.errorMessage));
return;
}
TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFinished')

View File

@@ -4,16 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as mssql from '../../../mssql';
import * as TypeMoq from 'typemoq';
import * as loc from '../localizedConstants';
import 'mocha';
import { SchemaCompareDialog } from './../dialogs/schemaCompareDialog';
import { SchemaCompareMainWindow } from '../schemaCompareMainWindow';
import { SchemaCompareTestService } from './testSchemaCompareService';
import { SchemaCompareTestService, testStateScmp } from './testSchemaCompareService';
import { createContext, TestContext } from './testContext';
import { mockIConnectionProfile, mockDacpacEndpoint, mockFilePath } from './testUtils';
import { mockIConnectionProfile, mockFilePath, setDacpacEndpointInfo, setDatabaseEndpointInfo, shouldThrowSpecificError } from './testUtils';
// Mock test data
const mocksource: string = 'source.dacpac';
@@ -26,7 +26,7 @@ before(async function (): Promise<void> {
testContext = createContext();
});
describe('SchemaCompareDialog.openDialog', function (): void {
beforeEach(() => {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockExtensionContext.setup(x => x.extensionPath).returns(() => '');
});
@@ -42,27 +42,22 @@ describe('SchemaCompareDialog.openDialog', function (): void {
});
});
describe('SchemaCompareResult.start', function (): void {
beforeEach(() => {
describe('SchemaCompareMainWindow.start', function (): void {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockExtensionContext.setup(x => x.extensionPath).returns(() => '');
});
it('Should be correct when created.', async function (): Promise<void> {
let sc = new SchemaCompareTestService();
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(null);
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;
should(result.getComparisonResult() === undefined);
let sourceEndpointInfo : mssql.SchemaCompareEndpointInfo = {...mockDacpacEndpoint};
let targetEndpointInfo : mssql.SchemaCompareEndpointInfo = {...mockDacpacEndpoint};
result.sourceEndpointInfo = sourceEndpointInfo;
result.sourceEndpointInfo.packageFilePath = mocksource;
result.targetEndpointInfo = targetEndpointInfo;
result.targetEndpointInfo.packageFilePath = mocktarget;
result.sourceEndpointInfo = await setDacpacEndpointInfo(mocksource);
result.targetEndpointInfo = await setDacpacEndpointInfo(mocktarget);
await result.execute();
should(result.getComparisonResult() !== undefined);
@@ -74,8 +69,6 @@ describe('SchemaCompareResult.start', function (): void {
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(undefined);
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;
should.equal(result.sourceEndpointInfo, undefined);
should.equal(result.targetEndpointInfo, undefined);
@@ -86,8 +79,6 @@ describe('SchemaCompareResult.start', function (): void {
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start({connectionProfile: mockIConnectionProfile});
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;
should.notEqual(result.sourceEndpointInfo, undefined);
should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Database);
@@ -102,8 +93,6 @@ describe('SchemaCompareResult.start', function (): void {
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
const dacpacPath = mockFilePath;
await result.start(dacpacPath);
let promise = new Promise(resolve => setTimeout(resolve, 5000)); // to ensure comparison result view is initialized
await promise;
should.notEqual(result.sourceEndpointInfo, undefined);
should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Dacpac);
@@ -111,3 +100,86 @@ describe('SchemaCompareResult.start', function (): void {
should.equal(result.targetEndpointInfo, undefined);
});
});
describe('SchemaCompareMainWindow.execute', function (): void {
before(() => {
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
mockExtensionContext.setup(x => x.extensionPath).returns(() => '');
testContext = createContext();
});
beforeEach(async function (): Promise<void> {
testContext.apiWrapper.reset();
});
it('Should fail for failing Schema Compare service', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.FAILURE);
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); });
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(null);
should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = await setDacpacEndpointInfo(mocksource);
result.targetEndpointInfo = await setDacpacEndpointInfo(mocktarget);
await shouldThrowSpecificError(async () => await result.execute(), loc.compareErrorMessage('Test failure'));
});
it('Should exit for failing Schema Compare service', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.FAILURE);
testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve(''));
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(null);
should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = await setDacpacEndpointInfo(mocksource);
result.targetEndpointInfo = await setDacpacEndpointInfo(mocktarget);
await result.execute();
testContext.apiWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('Should disable script button and apply button for Schema Compare service for dacpac', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL);
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(null);
should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = await setDacpacEndpointInfo(mocksource);
result.targetEndpointInfo = await setDacpacEndpointInfo(mocktarget);
await result.execute();
//Generate script button and apply button should be disabled for dacpac comparison
should(result.verifyButtonsState(false, false)).equal(true);
});
it('Should disable script button and apply button for Schema Compare service for database', async function (): Promise<void> {
let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL);
let result = new SchemaCompareMainWindow(testContext.apiWrapper.object, sc, mockExtensionContext.object);
await result.start(null);
should(result.getComparisonResult() === undefined);
result.sourceEndpointInfo = await setDacpacEndpointInfo(mocksource);
result.targetEndpointInfo = await setDatabaseEndpointInfo();
await result.execute();
//Generate script button and apply button should be enabled for database comparison
should(result.verifyButtonsState(true, true)).equal(true);
});
});

View File

@@ -9,6 +9,16 @@ import * as mssql from '../../../mssql';
export class SchemaCompareTestService implements mssql.ISchemaCompareService {
testOperationId: string = 'Test Operation Id';
testState: testStateScmp;
constructor(state?: testStateScmp) {
if (state) {
this.testState = state;
}
else {
this.testState = testStateScmp.SUCCESS_EQUAL;
}
}
schemaComparePublishChanges(operationId: string, targetServerName: string, targetDatabaseName: string, taskExecutionMode: azdata.TaskExecutionMode): Thenable<azdata.ResultStatus> {
throw new Error('Method not implemented.');
@@ -38,13 +48,56 @@ export class SchemaCompareTestService implements mssql.ISchemaCompareService {
}
schemaCompare(operationId: string, sourceEndpointInfo: mssql.SchemaCompareEndpointInfo, targetEndpointInfo: mssql.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable<mssql.SchemaCompareResult> {
let result: mssql.SchemaCompareResult = {
operationId: this.testOperationId,
areEqual: true,
differences: [],
success: true,
errorMessage: ''
};
let result: mssql.SchemaCompareResult;
if (this.testState === testStateScmp.FAILURE) {
result = {
operationId: this.testOperationId,
areEqual: false,
differences: [],
success: false,
errorMessage: 'Test failure'
};
}
else if (this.testState === testStateScmp.SUCCESS_NOT_EQUAL) {
result = {
operationId: this.testOperationId,
areEqual: false,
differences: [{
updateAction: 2,
differenceType: 0,
name: 'SqlTable',
sourceValue: ['dbo', 'table1'],
targetValue: null,
parent: null,
children: [{
updateAction: 2,
differenceType: 0,
name: 'SqlSimpleColumn',
sourceValue: ['dbo', 'table1', 'id'],
targetValue: null,
parent: null,
children: [],
sourceScript: '',
targetScript: null,
included: false
}],
sourceScript: 'CREATE TABLE [dbo].[table1](id int)',
targetScript: null,
included: true
}],
success: true,
errorMessage: ''
};
}
else {
result = {
operationId: this.testOperationId,
areEqual: true,
differences: [],
success: true,
errorMessage: null
};
}
return Promise.resolve(result);
}
@@ -63,3 +116,9 @@ export class SchemaCompareTestService implements mssql.ISchemaCompareService {
registerOnUpdated(handler: () => any): void {
}
}
export enum testStateScmp {
SUCCESS_EQUAL,
SUCCESS_NOT_EQUAL,
FAILURE
}

View File

@@ -99,3 +99,24 @@ export async function shouldThrowSpecificError(block: Function, expectedMessage:
throw new AssertionError({ message: `Operation succeeded, but expected failure with exception: "${expectedMessage}".${details ? ' ' + details : ''}` });
}
}
export async function setDacpacEndpointInfo(path: string): Promise<mssql.SchemaCompareEndpointInfo> {
let endpointInfo: mssql.SchemaCompareEndpointInfo;
endpointInfo = { ...mockDacpacEndpoint };
endpointInfo.packageFilePath = path;
return endpointInfo;
}
export async function setDatabaseEndpointInfo(): Promise<mssql.SchemaCompareEndpointInfo> {
let endpointInfo: mssql.SchemaCompareEndpointInfo;
let dbName = 'My Database';
let serverName = 'My Server';
endpointInfo = { ...mockDatabaseEndpoint };
endpointInfo.databaseName = dbName;
endpointInfo.serverName = serverName;
return endpointInfo;
}