From 9977e83380b8dd8cf10830fda21a725e0750bcaa Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Mon, 7 Dec 2020 14:42:38 -0800 Subject: [PATCH] Adding unit tests for schema compare service (#13642) --- extensions/schema-compare/package.json | 4 +- .../schema-compare/src/common/apiWrapper.ts | 48 --- extensions/schema-compare/src/extension.ts | 3 +- .../src/schemaCompareMainWindow.ts | 408 +++++++++--------- .../src/test/schemaCompare.test.ts | 356 +++++++++++++-- .../src/test/schemaCompareDialog.test.ts | 8 +- .../schema-compare/src/test/testContext.ts | 4 - .../src/test/testSchemaCompareMainWindow.ts | 4 +- .../src/test/testSchemaCompareService.ts | 2 +- .../schema-compare/src/test/utils.test.ts | 62 ++- extensions/schema-compare/src/utils.ts | 19 +- extensions/schema-compare/yarn.lock | 104 +++++ 12 files changed, 703 insertions(+), 319 deletions(-) delete mode 100644 extensions/schema-compare/src/common/apiWrapper.ts diff --git a/extensions/schema-compare/package.json b/extensions/schema-compare/package.json index c7a803597d..170c376e1a 100644 --- a/extensions/schema-compare/package.json +++ b/extensions/schema-compare/package.json @@ -87,13 +87,15 @@ }, "devDependencies": { "@types/mocha": "^5.2.5", + "@types/sinon": "^9.0.4", "@types/node": "^12.11.7", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "should": "^13.2.1", "typemoq": "^2.1.0", - "vscodetestcover": "^1.1.0" + "vscodetestcover": "^1.1.0", + "sinon": "^9.0.2" }, "__metadata": { "id": "37", diff --git a/extensions/schema-compare/src/common/apiWrapper.ts b/extensions/schema-compare/src/common/apiWrapper.ts deleted file mode 100644 index 89ed85f1c5..0000000000 --- a/extensions/schema-compare/src/common/apiWrapper.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import * as azdata from 'azdata'; - -/** - * Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into - * this API from our code - */ -export class ApiWrapper { - public openConnectionDialog(providers?: string[], - initialConnectionProfile?: azdata.IConnectionProfile, - connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable { - return azdata.connection.openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions); - } - - public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable { - return vscode.commands.registerCommand(command, callback, thisArg); - } - - public getUriForConnection(connectionId: string): Thenable { - return azdata.connection.getUriForConnection(connectionId); - } - - public getConnections(activeConnectionsOnly?: boolean): Thenable { - return azdata.connection.getConnections(activeConnectionsOnly); - } - - public connect(connectionProfile: azdata.IConnectionProfile, saveConnection?: boolean, showDashboard?: boolean): Thenable { - return azdata.connection.connect(connectionProfile, saveConnection, showDashboard); - } - - public showErrorMessage(message: string, ...items: string[]): Thenable { - return vscode.window.showErrorMessage(message, ...items); - } - - public showWarningMessage(message: string, options?: vscode.MessageOptions, ...items: string[]): Thenable { - if (options) { - return vscode.window.showWarningMessage(message, options, ...items); - } - else { - return vscode.window.showWarningMessage(message, ...items); - } - } -} diff --git a/extensions/schema-compare/src/extension.ts b/extensions/schema-compare/src/extension.ts index a22de88891..57a379dfb1 100644 --- a/extensions/schema-compare/src/extension.ts +++ b/extensions/schema-compare/src/extension.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ApiWrapper } from './common/apiWrapper'; import { SchemaCompareMainWindow } from './schemaCompareMainWindow'; export async function activate(extensionContext: vscode.ExtensionContext): Promise { - vscode.commands.registerCommand('schemaCompare.start', async (context: any) => { await new SchemaCompareMainWindow(new ApiWrapper(), undefined, extensionContext).start(context); }); + vscode.commands.registerCommand('schemaCompare.start', async (context: any) => { await new SchemaCompareMainWindow(undefined, extensionContext).start(context); }); } export function deactivate(): void { diff --git a/extensions/schema-compare/src/schemaCompareMainWindow.ts b/extensions/schema-compare/src/schemaCompareMainWindow.ts index dac33a7150..28ffbb34aa 100644 --- a/extensions/schema-compare/src/schemaCompareMainWindow.ts +++ b/extensions/schema-compare/src/schemaCompareMainWindow.ts @@ -14,7 +14,6 @@ import { TelemetryReporter, TelemetryViews } from './telemetry'; import { getTelemetryErrorType, getEndpointName, verifyConnectionAndGetOwnerUri, getRootPath } from './utils'; import { SchemaCompareDialog } from './dialogs/schemaCompareDialog'; import { isNullOrUndefined } from 'util'; -import { ApiWrapper } from './common/apiWrapper'; // Do not localize this, this is used to decide the icon for the editor. // TODO : In future icon should be decided based on language id (scmp) and not resource name @@ -69,7 +68,7 @@ export class SchemaCompareMainWindow { public sourceEndpointInfo: mssql.SchemaCompareEndpointInfo; public targetEndpointInfo: mssql.SchemaCompareEndpointInfo; - constructor(private apiWrapper: ApiWrapper, private schemaCompareService?: mssql.ISchemaCompareService, private extensionContext?: vscode.ExtensionContext) { + constructor(private schemaCompareService?: mssql.ISchemaCompareService, private extensionContext?: vscode.ExtensionContext) { this.SchemaCompareActionMap = new Map(); this.SchemaCompareActionMap[mssql.SchemaUpdateAction.Delete] = loc.deleteAction; this.SchemaCompareActionMap[mssql.SchemaUpdateAction.Change] = loc.changeAction; @@ -87,7 +86,7 @@ export class SchemaCompareMainWindow { let profile = context ? context.connectionProfile : undefined; let sourceDacpac = context as string; if (profile) { - let ownerUri = await this.apiWrapper.getUriForConnection((profile.id)); + let ownerUri = await azdata.connection.getUriForConnection((profile.id)); this.sourceEndpointInfo = { endpointType: mssql.SchemaCompareEndpointType.Database, serverDisplayName: `${profile.serverName} ${profile.userName}`, @@ -295,11 +294,11 @@ export class SchemaCompareMainWindow { } this.comparisonResult = await service.schemaCompare(this.operationId, this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions); if (!this.comparisonResult || !this.comparisonResult.success) { - TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFailed', undefined, getTelemetryErrorType(this.comparisonResult.errorMessage)) + TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFailed', undefined, getTelemetryErrorType(this.comparisonResult?.errorMessage)) .withAdditionalProperties({ operationId: this.comparisonResult.operationId }).send(); - this.apiWrapper.showErrorMessage(loc.compareErrorMessage(this.comparisonResult.errorMessage)); + vscode.window.showErrorMessage(loc.compareErrorMessage(this.comparisonResult?.errorMessage)); return; } TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaComparisonFinished') @@ -407,69 +406,76 @@ export class SchemaCompareMainWindow { this.tablelistenersToDispose.push(this.differencesTable.onCellAction(async (rowState) => { let checkboxState = rowState; if (checkboxState) { - // show an info notification the first time when trying to exclude to notify the user that it may take some time to calculate affected dependencies - if (this.showIncludeExcludeWaitingMessage) { - this.showIncludeExcludeWaitingMessage = false; - vscode.window.showInformationMessage(loc.includeExcludeInfoMessage); - } - - let diff = this.comparisonResult.differences[checkboxState.row]; - const result = await service.schemaCompareIncludeExcludeNode(this.comparisonResult.operationId, diff, checkboxState.checked, azdata.TaskExecutionMode.execute); - let checkboxesToChange = []; - if (result.success) { - this.saveExcludeState(checkboxState); - - // dependencies could have been included or excluded as a result, so save their exclude states - result.affectedDependencies.forEach(difference => { - // find the row of the difference and set its checkbox - const diffEntryKey = this.createDiffEntryKey(difference); - if (this.diffEntryRowMap.has(diffEntryKey)) { - const row = this.diffEntryRowMap.get(diffEntryKey); - checkboxesToChange.push({ row: row, column: 2, columnName: 'Include', checked: difference.included }); - const dependencyCheckBoxState: azdata.ICheckboxCellActionEventArgs = { - checked: difference.included, - row: row, - column: 2, - columnName: undefined - }; - this.saveExcludeState(dependencyCheckBoxState); - } - }); - } else { - // failed because of dependencies - if (result.blockingDependencies) { - // show the first dependent that caused this to fail in the warning message - const diffEntryName = this.createName(diff.sourceValue ? diff.sourceValue : diff.targetValue); - const firstDependentName = this.createName(result.blockingDependencies[0].sourceValue ? result.blockingDependencies[0].sourceValue : result.blockingDependencies[0].targetValue); - let cannotExcludeMessage: string; - let cannotIncludeMessage: string; - if (firstDependentName) { - cannotExcludeMessage = loc.cannotExcludeMessageDependent(diffEntryName, firstDependentName); - cannotIncludeMessage = loc.cannotIncludeMessageDependent(diffEntryName, firstDependentName); - } else { - cannotExcludeMessage = loc.cannotExcludeMessage(diffEntryName); - cannotIncludeMessage = loc.cannotIncludeMessage(diffEntryName); - } - vscode.window.showWarningMessage(checkboxState.checked ? cannotIncludeMessage : cannotExcludeMessage); - } else { - vscode.window.showWarningMessage(result.errorMessage); - } - - // set checkbox back to previous state - checkboxesToChange.push({ row: checkboxState.row, column: checkboxState.column, columnName: 'Include', checked: !checkboxState.checked }); - } - - if (checkboxesToChange.length > 0) { - this.differencesTable.updateCells = checkboxesToChange; - } + await this.applyIncludeExclude(checkboxState); } })); } + public async applyIncludeExclude(checkboxState: azdata.ICheckboxCellActionEventArgs): Promise { + const service = await this.getService(); + // show an info notification the first time when trying to exclude to notify the user that it may take some time to calculate affected dependencies + if (this.showIncludeExcludeWaitingMessage) { + this.showIncludeExcludeWaitingMessage = false; + vscode.window.showInformationMessage(loc.includeExcludeInfoMessage); + } + + let diff = this.comparisonResult.differences[checkboxState.row]; + const result = await service.schemaCompareIncludeExcludeNode(this.comparisonResult.operationId, diff, checkboxState.checked, azdata.TaskExecutionMode.execute); + let checkboxesToChange = []; + if (result.success) { + this.saveExcludeState(checkboxState); + + // dependencies could have been included or excluded as a result, so save their exclude states + result.affectedDependencies.forEach(difference => { + // find the row of the difference and set its checkbox + const diffEntryKey = this.createDiffEntryKey(difference); + if (this.diffEntryRowMap.has(diffEntryKey)) { + const row = this.diffEntryRowMap.get(diffEntryKey); + checkboxesToChange.push({ row: row, column: 2, columnName: 'Include', checked: difference.included }); + const dependencyCheckBoxState: azdata.ICheckboxCellActionEventArgs = { + checked: difference.included, + row: row, + column: 2, + columnName: undefined + }; + this.saveExcludeState(dependencyCheckBoxState); + } + }); + } else { + // failed because of dependencies + if (result.blockingDependencies) { + // show the first dependent that caused this to fail in the warning message + const diffEntryName = this.createName(diff.sourceValue ? diff.sourceValue : diff.targetValue); + const firstDependentName = this.createName(result.blockingDependencies[0].sourceValue ? result.blockingDependencies[0].sourceValue : result.blockingDependencies[0].targetValue); + let cannotExcludeMessage: string; + let cannotIncludeMessage: string; + if (firstDependentName) { + cannotExcludeMessage = loc.cannotExcludeMessageDependent(diffEntryName, firstDependentName); + cannotIncludeMessage = loc.cannotIncludeMessageDependent(diffEntryName, firstDependentName); + } else { + cannotExcludeMessage = loc.cannotExcludeMessage(diffEntryName); + cannotIncludeMessage = loc.cannotIncludeMessage(diffEntryName); + } + vscode.window.showWarningMessage(checkboxState.checked ? cannotIncludeMessage : cannotExcludeMessage); + } else { + vscode.window.showWarningMessage(result.errorMessage); + } + + // set checkbox back to previous state + checkboxesToChange.push({ row: checkboxState.row, column: checkboxState.column, columnName: 'Include', checked: !checkboxState.checked }); + } + + if (checkboxesToChange.length > 0) { + this.differencesTable.updateCells = checkboxesToChange; + } + } + // save state based on source name if present otherwise target name (parity with SSDT) private saveExcludeState(rowState: azdata.ICheckboxCellActionEventArgs) { if (rowState) { - this.differencesTable.data[rowState.row][2] = rowState.checked; + if (this.differencesTable.data[rowState.row]?.length > 2) { + this.differencesTable.data[rowState.row][2] = rowState.checked; + } let diff = this.comparisonResult.differences[rowState.row]; let key = (diff.sourceValue && diff.sourceValue.length > 0) ? this.createName(diff.sourceValue) : this.createName(diff.targetValue); if (key) { @@ -647,7 +653,7 @@ export class SchemaCompareMainWindow { }); } - private async cancelCompare() { + public async cancelCompare() { TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareCancelStarted') .withAdditionalProperties({ @@ -691,28 +697,32 @@ export class SchemaCompareMainWindow { }).component(); this.generateScriptButton.onDidClick(async (click) => { - TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptStarted') - .withAdditionalProperties({ - 'startTime': Date.now().toString(), - 'operationId': this.comparisonResult.operationId - }).send(); - const service = await this.getService(); - const result = await service.schemaCompareGenerateScript(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.script); - if (!result || !result.success) { - TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptFailed', undefined, getTelemetryErrorType(result.errorMessage)) - .withAdditionalProperties({ - 'operationId': this.comparisonResult.operationId - }).send(); - vscode.window.showErrorMessage(loc.generateScriptErrorMessage(result.errorMessage)); - } - TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptEnded') - .withAdditionalProperties({ - 'endTime': Date.now().toString(), - 'operationId': this.comparisonResult.operationId - }).send(); + await this.generateScript(); }); } + public async generateScript(): Promise { + TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptStarted') + .withAdditionalProperties({ + 'startTime': Date.now().toString(), + 'operationId': this.comparisonResult.operationId + }).send(); + const service = await this.getService(); + const result = await service.schemaCompareGenerateScript(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.script); + if (!result || !result.success) { + TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptFailed', undefined, getTelemetryErrorType(result.errorMessage)) + .withAdditionalProperties({ + 'operationId': this.comparisonResult.operationId + }).send(); + vscode.window.showErrorMessage(loc.generateScriptErrorMessage(result.errorMessage)); + } + TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareGenerateScriptEnded') + .withAdditionalProperties({ + 'endTime': Date.now().toString(), + 'operationId': this.comparisonResult.operationId + }).send(); + } + private createOptionsButton(view: azdata.ModelView) { this.optionsButton = view.modelBuilder.button().withProperties({ label: loc.options, @@ -741,43 +751,47 @@ export class SchemaCompareMainWindow { }, }).component(); + this.applyButton.onDidClick(async (click) => { + await this.publishChanges(); + }); + } + + public async publishChanges(): Promise { + // need only yes button - since the modal dialog has a default cancel const yesString = loc.YesButtonText; - this.applyButton.onDidClick(async (click) => { + await vscode.window.showWarningMessage(loc.applyConfirmation, { modal: true }, yesString).then(async (result) => { + if (result === yesString) { + TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyStarted') + .withAdditionalProperties({ + 'startTime': Date.now().toString(), + 'operationId': this.comparisonResult.operationId + }).send(); - vscode.window.showWarningMessage(loc.applyConfirmation, { modal: true }, yesString).then(async (result) => { - if (result === yesString) { - TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyStarted') + // disable apply and generate script buttons because the results are no longer valid after applying the changes + this.setButtonsForRecompare(); + + const service = await this.getService(); + const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute); + if (!result || !result.success) { + TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyFailed', undefined, getTelemetryErrorType(result.errorMessage)) .withAdditionalProperties({ - 'startTime': Date.now().toString(), 'operationId': this.comparisonResult.operationId }).send(); + vscode.window.showErrorMessage(loc.applyErrorMessage(result.errorMessage)); - // disable apply and generate script buttons because the results are no longer valid after applying the changes - this.setButtonsForRecompare(); - - const service = await this.getService(); - const result = await service.schemaComparePublishChanges(this.comparisonResult.operationId, this.targetEndpointInfo.serverName, this.targetEndpointInfo.databaseName, azdata.TaskExecutionMode.execute); - if (!result || !result.success) { - TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyFailed', undefined, getTelemetryErrorType(result.errorMessage)) - .withAdditionalProperties({ - 'operationId': this.comparisonResult.operationId - }).send(); - vscode.window.showErrorMessage(loc.applyErrorMessage(result.errorMessage)); - - // reenable generate script and apply buttons if apply failed - this.generateScriptButton.enabled = true; - this.generateScriptButton.title = loc.generateScriptEnabledMessage; - this.applyButton.enabled = true; - this.applyButton.title = loc.applyEnabledMessage; - } - TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyEnded') - .withAdditionalProperties({ - 'endTime': Date.now().toString(), - 'operationId': this.comparisonResult.operationId - }).send(); + // reenable generate script and apply buttons if apply failed + this.generateScriptButton.enabled = true; + this.generateScriptButton.title = loc.generateScriptEnabledMessage; + this.applyButton.enabled = true; + this.applyButton.title = loc.applyEnabledMessage; } - }); + TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareApplyEnded') + .withAdditionalProperties({ + 'endTime': Date.now().toString(), + 'operationId': this.comparisonResult.operationId + }).send(); + } }); } @@ -922,60 +936,64 @@ export class SchemaCompareMainWindow { }).component(); this.openScmpButton.onDidClick(async (click) => { - TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpStarted'); - const rootPath = getRootPath(); - let fileUris = await vscode.window.showOpenDialog( - { - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - defaultUri: vscode.Uri.file(rootPath), - openLabel: loc.open, - filters: { - 'scmp Files': ['scmp'], - } - } - ); - - if (!fileUris || fileUris.length === 0) { - return; - } - - let fileUri = fileUris[0]; - const service = await this.getService(); - let startTime = Date.now(); - const result = await service.schemaCompareOpenScmp(fileUri.fsPath); - if (!result || !result.success) { - TelemetryReporter.sendErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpFailed', undefined, getTelemetryErrorType(result.errorMessage)); - vscode.window.showErrorMessage(loc.openScmpErrorMessage(result.errorMessage)); - return; - } - - this.sourceEndpointInfo = await this.constructEndpointInfo(result.sourceEndpointInfo, loc.sourceTitle, this.apiWrapper); - this.targetEndpointInfo = await this.constructEndpointInfo(result.targetEndpointInfo, loc.targetTitle, this.apiWrapper); - - this.updateSourceAndTarget(); - this.setDeploymentOptions(result.deploymentOptions); - this.scmpSourceExcludes = result.excludedSourceElements; - this.scmpTargetExcludes = result.excludedTargetElements; - this.sourceTargetSwitched = result.originalTargetName !== this.targetEndpointInfo.databaseName; - - // clear out any old results - this.resetForNewCompare(); - - TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpEnded') - .withAdditionalProperties({ - elapsedTime: (Date.now() - startTime).toString() - }).send(); + await this.openScmp(); }); } - private async constructEndpointInfo(endpoint: mssql.SchemaCompareEndpointInfo, caller: string, apiWrapper: ApiWrapper): Promise { + public async openScmp(): Promise { + TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpStarted'); + const rootPath = getRootPath(); + let fileUris = await vscode.window.showOpenDialog( + { + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + defaultUri: vscode.Uri.file(rootPath), + openLabel: loc.open, + filters: { + 'scmp Files': ['scmp'], + } + } + ); + + if (!fileUris || fileUris.length === 0) { + return; + } + + let fileUri = fileUris[0]; + const service = await this.getService(); + let startTime = Date.now(); + const result = await service.schemaCompareOpenScmp(fileUri.fsPath); + if (!result || !result.success) { + TelemetryReporter.sendErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpFailed', undefined, getTelemetryErrorType(result.errorMessage)); + vscode.window.showErrorMessage(loc.openScmpErrorMessage(result.errorMessage)); + return; + } + + this.sourceEndpointInfo = await this.constructEndpointInfo(result.sourceEndpointInfo, loc.sourceTitle); + this.targetEndpointInfo = await this.constructEndpointInfo(result.targetEndpointInfo, loc.targetTitle); + + this.updateSourceAndTarget(); + this.setDeploymentOptions(result.deploymentOptions); + this.scmpSourceExcludes = result.excludedSourceElements; + this.scmpTargetExcludes = result.excludedTargetElements; + this.sourceTargetSwitched = result.originalTargetName !== this.targetEndpointInfo.databaseName; + + // clear out any old results + this.resetForNewCompare(); + + TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareOpenScmpEnded') + .withAdditionalProperties({ + elapsedTime: (Date.now() - startTime).toString() + }).send(); + } + + private async constructEndpointInfo(endpoint: mssql.SchemaCompareEndpointInfo, caller: string): Promise { let ownerUri; let endpointInfo; if (endpoint && endpoint.endpointType === mssql.SchemaCompareEndpointType.Database) { // only set endpoint info if able to connect to the database - ownerUri = await verifyConnectionAndGetOwnerUri(endpoint, caller, apiWrapper); + ownerUri = await verifyConnectionAndGetOwnerUri(endpoint, caller); } if (ownerUri) { endpointInfo = endpoint; @@ -1007,44 +1025,48 @@ export class SchemaCompareMainWindow { }).component(); this.saveScmpButton.onDidClick(async (click) => { - const rootPath = getRootPath(); - const filePath = await vscode.window.showSaveDialog( - { - defaultUri: vscode.Uri.file(rootPath), - saveLabel: loc.save, - filters: { - 'scmp Files': ['scmp'], - } - } - ); - - if (!filePath) { - return; - } - - // convert include/exclude maps to arrays of object ids - let sourceExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalSourceExcludes); - let targetExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalTargetExcludes); - - let startTime = Date.now(); - TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmp'); - const service = await this.getService(); - const result = await service.schemaCompareSaveScmp(this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions, filePath.fsPath, sourceExcludes, targetExcludes); - if (!result || !result.success) { - TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpFailed', undefined, getTelemetryErrorType(result.errorMessage)) - .withAdditionalProperties({ - operationId: this.comparisonResult.operationId - }).send(); - vscode.window.showErrorMessage(loc.saveScmpErrorMessage(result.errorMessage)); - } - TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpEnded') - .withAdditionalProperties({ - elapsedTime: (Date.now() - startTime).toString(), - operationId: this.comparisonResult.operationId - }); + await this.saveScmp(); }); } + public async saveScmp(): Promise { + const rootPath = getRootPath(); + const filePath = await vscode.window.showSaveDialog( + { + defaultUri: vscode.Uri.file(rootPath), + saveLabel: loc.save, + filters: { + 'scmp Files': ['scmp'], + } + } + ); + + if (!filePath) { + return; + } + + // convert include/exclude maps to arrays of object ids + let sourceExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalSourceExcludes); + let targetExcludes: mssql.SchemaCompareObjectId[] = this.convertExcludesToObjectIds(this.originalTargetExcludes); + + let startTime = Date.now(); + TelemetryReporter.sendActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmp'); + const service = await this.getService(); + const result = await service.schemaCompareSaveScmp(this.sourceEndpointInfo, this.targetEndpointInfo, azdata.TaskExecutionMode.execute, this.deploymentOptions, filePath.fsPath, sourceExcludes, targetExcludes); + if (!result || !result.success) { + TelemetryReporter.createErrorEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpFailed', undefined, getTelemetryErrorType(result.errorMessage)) + .withAdditionalProperties({ + operationId: this.comparisonResult.operationId + }).send(); + vscode.window.showErrorMessage(loc.saveScmpErrorMessage(result.errorMessage)); + } + TelemetryReporter.createActionEvent(TelemetryViews.SchemaCompareMainWindow, 'SchemaCompareSaveScmpEnded') + .withAdditionalProperties({ + elapsedTime: (Date.now() - startTime).toString(), + operationId: this.comparisonResult.operationId + }); + } + /** * Converts excluded diff entries into object ids which are needed to save them in an scmp */ @@ -1071,7 +1093,7 @@ export class SchemaCompareMainWindow { } private async getService(): Promise { - if (isNullOrUndefined(this.schemaCompareService)) { + if (this.schemaCompareService === null || this.schemaCompareService === undefined) { this.schemaCompareService = (vscode.extensions.getExtension(mssql.extension.name).exports as mssql.IExtension).schemaCompare; } return this.schemaCompareService; diff --git a/extensions/schema-compare/src/test/schemaCompare.test.ts b/extensions/schema-compare/src/test/schemaCompare.test.ts index 7c9d96423f..62c150a74d 100644 --- a/extensions/schema-compare/src/test/schemaCompare.test.ts +++ b/extensions/schema-compare/src/test/schemaCompare.test.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ 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 * as sinon from 'sinon'; import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; import { SchemaCompareTestService, testStateScmp } from './testSchemaCompareService'; import { createContext, TestContext } from './testContext'; @@ -28,15 +27,24 @@ before(function (): void { testContext = createContext(); }); +afterEach(function (): void { + sinon.restore(); +}); + describe('SchemaCompareMainWindow.start', function (): void { before(() => { mockExtensionContext = TypeMoq.Mock.ofType(); mockExtensionContext.setup(x => x.extensionPath).returns(() => ''); }); + + this.afterEach(() => { + sinon.restore(); + }); + it('Should be correct when created.', async function (): Promise { let sc = new SchemaCompareTestService(); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); should(result.getComparisonResult() === undefined); @@ -52,7 +60,7 @@ describe('SchemaCompareMainWindow.start', function (): void { it('Should start with the source as undefined', async function (): Promise { let sc = new SchemaCompareTestService(); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); should.equal(result.sourceEndpointInfo, undefined); @@ -62,8 +70,8 @@ describe('SchemaCompareMainWindow.start', function (): void { it('Should start with the source as database', async function (): Promise { let sc = new SchemaCompareTestService(); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); - await result.start({connectionProfile: mockIConnectionProfile}); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); + await result.start({ connectionProfile: mockIConnectionProfile }); should.notEqual(result.sourceEndpointInfo, undefined); should.equal(result.sourceEndpointInfo.endpointType, mssql.SchemaCompareEndpointType.Database); @@ -75,7 +83,7 @@ describe('SchemaCompareMainWindow.start', function (): void { it('Should start with the source as dacpac.', async function (): Promise { let sc = new SchemaCompareTestService(); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); const dacpacPath = mockFilePath; await result.start(dacpacPath); @@ -85,7 +93,287 @@ describe('SchemaCompareMainWindow.start', function (): void { should.equal(result.targetEndpointInfo, undefined); }); }); +let showErrorMessageSpy: any; +let showWarningMessageStub: any; +let showOpenDialogStub: any; +describe('SchemaCompareMainWindow.results', function (): void { + before(() => { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockExtensionContext.setup(x => x.extensionPath).returns(() => ''); + }); + + this.afterEach(() => { + sinon.restore(); + }); + + this.beforeEach(() => { + sinon.restore(); + showErrorMessageSpy = sinon.spy(vscode.window, 'showErrorMessage'); + }); + + it('Should show error if publish changes fails', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: false, + errorMessage: 'error1' + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('Yes')); + + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.publishChanges(); + + should(showErrorMessageSpy.calledOnce).be.true(); + should.equal(showErrorMessageSpy.getCall(0).args[0], loc.applyErrorMessage('error1')); + }); + + it('Should show not error if publish changes succeed', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: true, + errorMessage: '' + })); + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('Yes')); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.publishChanges(); + should(showErrorMessageSpy.notCalled).be.true(); + }); + + it('Should show error if openScmp fails', async function (): Promise { + let service = createServiceMock(); + let files: vscode.Uri[] = [vscode.Uri.parse('file:///test')]; + service.setup(x => x.schemaCompareOpenScmp(TypeMoq.It.isAny())).returns(() => Promise.resolve({ + sourceEndpointInfo: undefined, + targetEndpointInfo: undefined, + originalTargetName: 'string', + originalConnectionString: '', + deploymentOptions: undefined, + excludedSourceElements: [], + excludedTargetElements: [], + success: false, + errorMessage: 'error1' + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('Yes')); + showOpenDialogStub = sinon.stub(vscode.window, 'showOpenDialog').returns(Promise.resolve(files)); + + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.openScmp(); + + should(showErrorMessageSpy.calledOnce).be.true(); + should.equal(showErrorMessageSpy.getCall(0).args[0], loc.openScmpErrorMessage('error1')); + }); + + it('Should show error if saveScmp fails', async function (): Promise { + let service = createServiceMock(); + let file: vscode.Uri = vscode.Uri.parse('file:///test'); + service.setup(x => x.schemaCompareSaveScmp(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + sourceEndpointInfo: undefined, + targetEndpointInfo: undefined, + originalTargetName: 'string', + originalConnectionString: '', + deploymentOptions: undefined, + excludedSourceElements: [], + excludedTargetElements: [], + success: false, + errorMessage: 'error1' + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('Yes')); + showOpenDialogStub = sinon.stub(vscode.window, 'showSaveDialog').returns(Promise.resolve(file)); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.saveScmp(); + + should(showErrorMessageSpy.calledOnce).be.true(); + should.equal(showErrorMessageSpy.getCall(0).args[0], loc.saveScmpErrorMessage('error1')); + }); + + + it('Should show error if generateScript fails', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaCompareGenerateScript(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: false, + errorMessage: 'error1' + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('Yes')); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.generateScript(); + + should(showErrorMessageSpy.calledOnce).be.true(); + should.equal(showErrorMessageSpy.getCall(0).args[0], loc.generateScriptErrorMessage('error1')); + }); + + it('Should show error if cancel fails', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaCompareCancel(TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: false, + errorMessage: 'error1' + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('Yes')); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.cancelCompare(); + + should(showErrorMessageSpy.calledOnce).be.true(); + should.equal(showErrorMessageSpy.getCall(0).args[0], loc.cancelErrorMessage('error1')); + }); + + it('Should show error if IncludeExcludeNode fails', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaCompareIncludeExcludeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: false, + errorMessage: '', + affectedDependencies: [], + blockingDependencies: [{ + 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 + }] + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('')); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.applyIncludeExclude({ + row: 0, + column: 0, + columnName: 1, + checked: true + }); + + should(showWarningMessageStub.calledOnce).be.true(); + }); + + it('Should not show warning if IncludeExcludeNode succeed', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaCompareIncludeExcludeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: true, + errorMessage: '', + affectedDependencies: [], + blockingDependencies: [{ + 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 + }] + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('')); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.applyIncludeExclude({ + row: 0, + column: 0, + columnName: 1, + checked: false + }); + + should(showWarningMessageStub.notCalled).be.true(); + }); + + it('Should not show error if user does not want to publish', async function (): Promise { + let service = createServiceMock(); + service.setup(x => x.schemaComparePublishChanges(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ + success: true, + errorMessage: '' + })); + + showWarningMessageStub = sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve('No')); + let schemaCompareResult = new SchemaCompareMainWindow(service.object, mockExtensionContext.object); + await schemaCompareResult.start(undefined); + + schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); + schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); + await schemaCompareResult.execute(); + await schemaCompareResult.publishChanges(); + + should(showErrorMessageSpy.notCalled).be.true(); + }); + + function createServiceMock() { + let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL); + let service = TypeMoq.Mock.ofInstance(new SchemaCompareTestService()); + service.setup(x => x.schemaCompareGetDefaultOptions()).returns(x => sc.schemaCompareGetDefaultOptions()); + service.setup(x => x.schemaCompare(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => sc.schemaCompare('', undefined, undefined, undefined, undefined)); + return service; + } +}); + +let showErrorMessageStub: any; describe('SchemaCompareMainWindow.execute', function (): void { before(() => { mockExtensionContext = TypeMoq.Mock.ofType(); @@ -93,16 +381,22 @@ describe('SchemaCompareMainWindow.execute', function (): void { testContext = createContext(); }); - beforeEach(function (): void { - testContext.apiWrapper.reset(); + this.afterEach(() => { + sinon.restore(); + }); + + this.beforeEach(() => { + sinon.restore(); }); it('Should fail for failing Schema Compare service', async function (): Promise { let sc = new SchemaCompareTestService(testStateScmp.FAILURE); - testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => { + throw new Error(message); + }); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); should(result.getComparisonResult() === undefined); @@ -116,8 +410,7 @@ describe('SchemaCompareMainWindow.execute', function (): void { it('Should exit for failing Schema Compare service', async function (): Promise { let sc = new SchemaCompareTestService(testStateScmp.FAILURE); - testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns(() => Promise.resolve('')); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); @@ -127,13 +420,12 @@ describe('SchemaCompareMainWindow.execute', function (): void { result.targetEndpointInfo = 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 { let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); @@ -145,7 +437,7 @@ describe('SchemaCompareMainWindow.execute', function (): void { await result.execute(); //Generate script button and apply button should be disabled for dacpac comparison - result.verifyButtonsState( { + result.verifyButtonsState({ compareButtonState: true, optionsButtonState: true, switchButtonState: true, @@ -156,13 +448,13 @@ describe('SchemaCompareMainWindow.execute', function (): void { selectTargetButtonState: true, generateScriptButtonState: false, applyButtonState: false - } ); + }); }); it('Should disable script button and apply button for Schema Compare service for database', async function (): Promise { let sc = new SchemaCompareTestService(testStateScmp.SUCCESS_NOT_EQUAL); - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); @@ -174,7 +466,7 @@ describe('SchemaCompareMainWindow.execute', function (): void { await result.execute(); //Generate script button and apply button should be enabled for database comparison - result.verifyButtonsState( { + result.verifyButtonsState({ compareButtonState: true, optionsButtonState: true, switchButtonState: true, @@ -185,7 +477,7 @@ describe('SchemaCompareMainWindow.execute', function (): void { selectTargetButtonState: true, generateScriptButtonState: true, applyButtonState: true - } ); + }); }); }); @@ -201,18 +493,18 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { let sc = new SchemaCompareTestService(); let endpointInfo: mssql.SchemaCompareEndpointInfo; - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); should(result.getComparisonResult() === undefined); - result.sourceEndpointInfo = {...endpointInfo}; - result.targetEndpointInfo = {...endpointInfo}; + result.sourceEndpointInfo = { ...endpointInfo }; + result.targetEndpointInfo = { ...endpointInfo }; result.updateSourceAndTarget(); - result.verifyButtonsState( { + result.verifyButtonsState({ compareButtonState: false, optionsButtonState: false, switchButtonState: false, @@ -223,25 +515,25 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { selectTargetButtonState: true, generateScriptButtonState: false, applyButtonState: false - } ); + }); }); it('Should set buttons appropriately when source endpoint is empty and target endpoint is populated', async function (): Promise { let sc = new SchemaCompareTestService(); let endpointInfo: mssql.SchemaCompareEndpointInfo; - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); should(result.getComparisonResult() === undefined); - result.sourceEndpointInfo = {...endpointInfo}; + result.sourceEndpointInfo = { ...endpointInfo }; result.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); result.updateSourceAndTarget(); - result.verifyButtonsState( { + result.verifyButtonsState({ compareButtonState: false, optionsButtonState: false, switchButtonState: true, @@ -252,14 +544,14 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { selectTargetButtonState: true, generateScriptButtonState: false, applyButtonState: false - } ); + }); }); it('Should set buttons appropriately when source and target endpoints are populated', async function (): Promise { let sc = new SchemaCompareTestService(); let endpointInfo: mssql.SchemaCompareEndpointInfo; - let result = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, sc, mockExtensionContext.object); + let result = new SchemaCompareMainWindowTest(sc, mockExtensionContext.object); await result.start(undefined); @@ -270,7 +562,7 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { result.updateSourceAndTarget(); - result.verifyButtonsState( { + result.verifyButtonsState({ compareButtonState: true, optionsButtonState: true, switchButtonState: true, @@ -281,7 +573,7 @@ describe('SchemaCompareMainWindow.updateSourceAndTarget', function (): void { 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 index 7dc3de3307..91e662aeed 100644 --- a/extensions/schema-compare/src/test/schemaCompareDialog.test.ts +++ b/extensions/schema-compare/src/test/schemaCompareDialog.test.ts @@ -32,7 +32,7 @@ describe('SchemaCompareDialog.openDialog', function (): void { }); it('Should be correct when created.', async function (): Promise { - let schemaCompareResult = new SchemaCompareMainWindow(testContext.apiWrapper.object, undefined, mockExtensionContext.object); + let schemaCompareResult = new SchemaCompareMainWindow(undefined, mockExtensionContext.object); let dialog = new SchemaCompareDialog(schemaCompareResult); await dialog.openDialog(); @@ -42,7 +42,7 @@ describe('SchemaCompareDialog.openDialog', function (): void { }); it('Simulate ok button- with both endpoints set to dacpac', async function (): Promise { - let schemaCompareResult = new SchemaCompareMainWindowTest(testContext.apiWrapper.object, undefined, mockExtensionContext.object); + let schemaCompareResult = new SchemaCompareMainWindowTest(undefined, mockExtensionContext.object); await schemaCompareResult.start(undefined); schemaCompareResult.sourceEndpointInfo = setDacpacEndpointInfo(mocksource); schemaCompareResult.targetEndpointInfo = setDacpacEndpointInfo(mocktarget); @@ -53,7 +53,7 @@ describe('SchemaCompareDialog.openDialog', function (): void { await dialog.execute(); // Confirm that ok button got clicked - schemaCompareResult.verifyButtonsState( { + schemaCompareResult.verifyButtonsState({ compareButtonState: true, optionsButtonState: true, switchButtonState: true, @@ -64,6 +64,6 @@ describe('SchemaCompareDialog.openDialog', function (): void { selectTargetButtonState: true, generateScriptButtonState: false, applyButtonState: false - } ); + }); }); }); diff --git a/extensions/schema-compare/src/test/testContext.ts b/extensions/schema-compare/src/test/testContext.ts index 7e59247811..b4d0261011 100644 --- a/extensions/schema-compare/src/test/testContext.ts +++ b/extensions/schema-compare/src/test/testContext.ts @@ -5,11 +5,8 @@ import * as vscode from 'vscode'; import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { ApiWrapper } from '../common/apiWrapper'; export interface TestContext { - apiWrapper: TypeMoq.IMock; context: vscode.ExtensionContext; } @@ -17,7 +14,6 @@ export function createContext(): TestContext { let extensionPath = path.join(__dirname, '..', '..'); return { - apiWrapper: TypeMoq.Mock.ofType(ApiWrapper), context: { subscriptions: [], workspaceState: { diff --git a/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts b/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts index 275fdd169f..5488f3a217 100644 --- a/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts +++ b/extensions/schema-compare/src/test/testSchemaCompareMainWindow.ts @@ -7,7 +7,6 @@ import * as vscode from 'vscode'; import * as mssql from '../../../mssql'; import * as should from 'should'; import { SchemaCompareMainWindow } from '../schemaCompareMainWindow'; -import { ApiWrapper } from '../common/apiWrapper'; export interface ButtonState { compareButtonState: boolean; @@ -24,10 +23,9 @@ export interface ButtonState { export class SchemaCompareMainWindowTest extends SchemaCompareMainWindow { constructor( - apiWrapper: ApiWrapper, schemaCompareService: mssql.ISchemaCompareService, extensionContext: vscode.ExtensionContext) { - super(apiWrapper, schemaCompareService, extensionContext); + super(schemaCompareService, extensionContext); } // only for test diff --git a/extensions/schema-compare/src/test/testSchemaCompareService.ts b/extensions/schema-compare/src/test/testSchemaCompareService.ts index 12366e90b3..5da1da9a3e 100644 --- a/extensions/schema-compare/src/test/testSchemaCompareService.ts +++ b/extensions/schema-compare/src/test/testSchemaCompareService.ts @@ -47,7 +47,7 @@ export class SchemaCompareTestService implements mssql.ISchemaCompareService { throw new Error('Method not implemented.'); } - schemaCompare(operationId: string, sourceEndpointInfo: mssql.SchemaCompareEndpointInfo, targetEndpointInfo: mssql.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode): Thenable { + schemaCompare(operationId: string, sourceEndpointInfo: mssql.SchemaCompareEndpointInfo, targetEndpointInfo: mssql.SchemaCompareEndpointInfo, taskExecutionMode: azdata.TaskExecutionMode, deploymentOptions: mssql.DeploymentOptions): Thenable { let result: mssql.SchemaCompareResult; if (this.testState === testStateScmp.FAILURE) { result = { diff --git a/extensions/schema-compare/src/test/utils.test.ts b/extensions/schema-compare/src/test/utils.test.ts index 8ee3a99097..2f6cc4aa80 100644 --- a/extensions/schema-compare/src/test/utils.test.ts +++ b/extensions/schema-compare/src/test/utils.test.ts @@ -5,6 +5,7 @@ import * as should from 'should'; import * as azdata from 'azdata'; +import * as vscode from 'vscode'; import * as mssql from '../../../mssql'; import * as loc from '../localizedConstants'; import * as TypeMoq from 'typemoq'; @@ -15,10 +16,15 @@ 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'; +import * as sinon from 'sinon'; let testContext: TestContext; describe('utils: Tests to verify getEndpointName', function (): void { + afterEach(() => { + sinon.restore(); + }); + it('Should generate correct endpoint information', () => { let endpointInfo: mssql.SchemaCompareEndpointInfo; @@ -52,7 +58,7 @@ describe('utils: Basic tests to verify verifyConnectionAndGetOwnerUri', function it('Should return undefined for endpoint as dacpac', async function (): Promise { let ownerUri = undefined; - ownerUri = await verifyConnectionAndGetOwnerUri(mockDacpacEndpoint, 'test', testContext.apiWrapper.object); + ownerUri = await verifyConnectionAndGetOwnerUri(mockDacpacEndpoint, 'test'); should(ownerUri).equal(undefined); }); @@ -62,7 +68,7 @@ describe('utils: Basic tests to verify verifyConnectionAndGetOwnerUri', function let testDatabaseEndpoint: mssql.SchemaCompareEndpointInfo = { ...mockDatabaseEndpoint }; testDatabaseEndpoint.connectionDetails = undefined; - ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object); + ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'); should(ownerUri).equal(undefined); }); @@ -73,6 +79,10 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct testContext = createContext(); }); + afterEach(() => { + sinon.restore(); + }); + it('Should throw an error asking to make a connection', async function (): Promise { let getConnectionsResults: azdata.connection.ConnectionProfile[] = []; let connection = { ...mockConnectionResult }; @@ -80,12 +90,14 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct 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); }); - testContext.apiWrapper.setup(x => x.getUriForConnection(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); }); - testContext.apiWrapper.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(getConnectionsResults); }); - testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connection)); + sinon.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve(undefined)); + sinon.stub(azdata.connection, 'getConnections').returns(Promise.resolve(getConnectionsResults)); + sinon.stub(vscode.window, 'showWarningMessage').callsFake((message) => { + throw new Error(message); + }); - await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), getConnectionString); + await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), getConnectionString); }); it('Should throw an error for login failure', async function (): Promise { @@ -94,12 +106,15 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct 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); }); - testContext.apiWrapper.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(getConnectionsResults); }); - testContext.apiWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAny())).returns((s) => { throw new Error(s); }); + sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connection)); + sinon.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve(undefined)); + sinon.stub(azdata.connection, 'getConnections').returns(Promise.resolve(getConnectionsResults)); + sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve(loc.YesButtonText)); + sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => { + throw new Error(message); + }); - await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), connection.errorMessage); + await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), connection.errorMessage); }); it('Should throw an error for login failure with openConnectionDialog but no ownerUri', async function (): Promise { @@ -108,13 +123,18 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct 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); }); - testContext.apiWrapper.setup(x => x.getConnections(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(getConnectionsResults); }); - testContext.apiWrapper.setup(x => x.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(loc.YesButtonText); }); - testContext.apiWrapper.setup(x => x.openConnectionDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { return Promise.resolve(undefined); }); + sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connection)); + sinon.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve(undefined)); + sinon.stub(azdata.connection, 'openConnectionDialog').returns(Promise.resolve({ + connectionId: 'id' + })); + sinon.stub(azdata.connection, 'getConnections').returns(Promise.resolve(getConnectionsResults)); + sinon.stub(vscode.window, 'showWarningMessage').returns(Promise.resolve(loc.YesButtonText)); + sinon.stub(vscode.window, 'showErrorMessage').callsFake((message) => { + throw new Error(message); + }); - await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object), connection.errorMessage); + await shouldThrowSpecificError(async () => await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'), connection.errorMessage); }); it('Should not throw an error and set ownerUri appropriately', async function (): Promise { @@ -124,10 +144,10 @@ describe('utils: In-depth tests to verify verifyConnectionAndGetOwnerUri', funct let expectedOwnerUri: string = 'providerName:MSSQL|authenticationType:SqlLogin|database:My Database|server:My Server|user:My User|databaseDisplayName:My Database'; 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); }); + sinon.stub(azdata.connection, 'connect').returns(Promise.resolve(connection)); + sinon.stub(azdata.connection, 'getUriForConnection').returns(Promise.resolve(expectedOwnerUri)); - ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test', testContext.apiWrapper.object); + ownerUri = await verifyConnectionAndGetOwnerUri(testDatabaseEndpoint, 'test'); should(ownerUri).equal(expectedOwnerUri); }); diff --git a/extensions/schema-compare/src/utils.ts b/extensions/schema-compare/src/utils.ts index 974c2705ea..4dc6e52268 100644 --- a/extensions/schema-compare/src/utils.ts +++ b/extensions/schema-compare/src/utils.ts @@ -8,7 +8,6 @@ import * as vscode from 'vscode'; 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 { @@ -32,7 +31,7 @@ export function getPackageInfo(packageJson: any): IPackageInfo { * @param msg The error message to map */ export function getTelemetryErrorType(msg: string): string { - if (msg.indexOf('Object reference not set to an instance of an object') !== -1) { + if (msg && msg.indexOf('Object reference not set to an instance of an object') !== -1) { return 'ObjectReferenceNotSet'; } else { @@ -85,18 +84,18 @@ 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): Promise { let ownerUri = undefined; if (endpoint.endpointType === mssql.SchemaCompareEndpointType.Database && endpoint.connectionDetails) { let connectionProfile = await connectionInfoToConnectionProfile(endpoint.connectionDetails); - let connection = await apiWrapper.connect(connectionProfile, false, false); + let connection = await azdata.connection.connect(connectionProfile, false, false); if (connection) { - ownerUri = await apiWrapper.getUriForConnection(connection.connectionId); + ownerUri = await azdata.connection.getUriForConnection(connection.connectionId); if (!ownerUri) { - let connectionList = await apiWrapper.getConnections(true); + let connectionList = await azdata.connection.getConnections(true); let userConnection; userConnection = connectionList.find(connection => @@ -109,18 +108,18 @@ export async function verifyConnectionAndGetOwnerUri(endpoint: mssql.SchemaCompa if (userConnection === undefined) { const getConnectionString = loc.getConnectionString(caller); // need only yes button - since the modal dialog has a default cancel - let result = await apiWrapper.showWarningMessage(getConnectionString, { modal: true }, loc.YesButtonText); + let result = await vscode.window.showWarningMessage(getConnectionString, { modal: true }, loc.YesButtonText); if (result === loc.YesButtonText) { - userConnection = await apiWrapper.openConnectionDialog(undefined, connectionProfile); + userConnection = await azdata.connection.openConnectionDialog(undefined, connectionProfile); } } if (userConnection !== undefined) { - ownerUri = await apiWrapper.getUriForConnection(userConnection.connectionId); + ownerUri = await azdata.connection.getUriForConnection(userConnection.connectionId); } } if (!ownerUri && connection.errorMessage) { - apiWrapper.showErrorMessage(connection.errorMessage); + vscode.window.showErrorMessage(connection.errorMessage); } } } diff --git a/extensions/schema-compare/yarn.lock b/extensions/schema-compare/yarn.lock index a1e00eac5a..a0f9561394 100644 --- a/extensions/schema-compare/yarn.lock +++ b/extensions/schema-compare/yarn.lock @@ -182,6 +182,42 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.8.1": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" + integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" + integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@sinonjs/formatio@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089" + integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ== + dependencies: + "@sinonjs/commons" "^1" + "@sinonjs/samsam" "^5.0.2" + +"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.2.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.3.0.tgz#1d2f0743dc54bf13fe9d508baefacdffa25d4329" + integrity sha512-hXpcfx3aq+ETVBwPlRFICld5EnrkexXuXDwqUNhDdr5L8VjvMeSRwyOa0qL7XFmR+jVWR4rUZtnxlG7RX72sBg== + dependencies: + "@sinonjs/commons" "^1.6.0" + lodash.get "^4.4.2" + type-detect "^4.0.8" + +"@sinonjs/text-encoding@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" + integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== + "@types/mocha@^5.2.5": version "5.2.6" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" @@ -192,6 +228,18 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.7.tgz#01e4ea724d9e3bd50d90c11fd5980ba317d8fa11" integrity sha512-E6Zn0rffhgd130zbCbAr/JdXfXkoOUFAKNs/rF8qnafSJ8KYaA/j3oz7dcwal+lYjLA7xvdd5J4wdYpCTlP8+w== +"@types/sinon@^9.0.4": + version "9.0.9" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.9.tgz#115843b491583f924080f684b6d0d7438344f73c" + integrity sha512-z/y8maYOQyYLyqaOB+dYQ6i0pxKLOsfwCmHmn4T7jS/SDHicIslr37oE3Dg8SCqKrKeBy6Lemu7do2yy+unLrw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" + integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== + ads-extension-telemetry@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ads-extension-telemetry/-/ads-extension-telemetry-1.0.0.tgz#840b363a6ad958447819b9bc59fdad3e49de31a9" @@ -395,6 +443,11 @@ diff@3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + emitter-listener@^1.0.1, emitter-listener@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" @@ -501,6 +554,11 @@ is-buffer@~1.1.1: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + istanbul-lib-coverage@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49" @@ -576,6 +634,16 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +just-extend@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.1.tgz#158f1fdb01f128c411dc8b286a7b4837b3545282" + integrity sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + lodash@^4.16.4, lodash@^4.17.13, lodash@^4.17.4: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" @@ -675,6 +743,17 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +nise@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd" + integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A== + dependencies: + "@sinonjs/commons" "^1.7.0" + "@sinonjs/fake-timers" "^6.0.0" + "@sinonjs/text-encoding" "^0.7.1" + just-extend "^4.0.2" + path-to-regexp "^1.7.0" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -692,6 +771,13 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -785,6 +871,19 @@ should@^13.2.1: should-type-adaptors "^1.0.1" should-util "^1.0.0" +sinon@^9.0.2: + version "9.2.1" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.2.1.tgz#64cc88beac718557055bd8caa526b34a2231be6d" + integrity sha512-naPfsamB5KEE1aiioaoqJ6MEhdUs/2vtI5w1hPAXX/UwvoPjXcwh1m5HiKx0HGgKR8lQSoFIgY5jM6KK8VrS9w== + dependencies: + "@sinonjs/commons" "^1.8.1" + "@sinonjs/fake-timers" "^6.0.1" + "@sinonjs/formatio" "^5.0.1" + "@sinonjs/samsam" "^5.2.0" + diff "^4.0.2" + nise "^4.0.4" + supports-color "^7.1.0" + source-map@^0.5.0: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -838,6 +937,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +type-detect@4.0.8, type-detect@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + typemoq@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8"