From 8b52e7200c4a69bab7d5ad7f7c35185477b2b35c Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 25 Aug 2020 13:18:35 -0700 Subject: [PATCH] Add arc controllerModel tests (#11947) * Add arc controllerModel tests * Fix compile --- extensions/arc/src/models/controllerModel.ts | 4 +- .../src/test/models/controllerModel.test.ts | 144 ++++++++++++++++++ 2 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 extensions/arc/src/test/models/controllerModel.test.ts diff --git a/extensions/arc/src/models/controllerModel.ts b/extensions/arc/src/models/controllerModel.ts index 9ab267c2bb..514dcca85d 100644 --- a/extensions/arc/src/models/controllerModel.ts +++ b/extensions/arc/src/models/controllerModel.ts @@ -66,13 +66,13 @@ export class ControllerModel { // It should be in the credentials store, get it from there this._password = await this.treeDataProvider.getPassword(this.info); } - if (promptReconnect) { + if (promptReconnect || !this._password) { // No password yet or we want to re-prompt for credentials so prompt for it from the user const dialog = new ConnectToControllerDialog(this.treeDataProvider); dialog.showDialog(this.info, this._password); const model = await dialog.waitForClose(); if (model) { - this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false); + await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false); this._password = model.password; } else { throw new UserCancelledError(); diff --git a/extensions/arc/src/test/models/controllerModel.test.ts b/extensions/arc/src/test/models/controllerModel.test.ts new file mode 100644 index 0000000000..8abb65a720 --- /dev/null +++ b/extensions/arc/src/test/models/controllerModel.test.ts @@ -0,0 +1,144 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as azdata from 'azdata'; +import * as azdataExt from 'azdata-ext'; +import * as sinon from 'sinon'; +import * as TypeMoq from 'typemoq'; +import * as vscode from 'vscode'; +import * as should from 'should'; +import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog'; +import { ControllerModel } from '../../models/controllerModel'; +import { AzureArcTreeDataProvider } from '../../ui/tree/azureArcTreeDataProvider'; +import { UserCancelledError } from '../../common/utils'; + +describe('ControllerModel', function (): void { + afterEach(function (): void { + sinon.restore(); + }); + + describe('azdataLogin', function (): void { + let mockExtensionContext: TypeMoq.IMock; + let mockGlobalState: TypeMoq.IMock; + + before(function (): void { + mockExtensionContext = TypeMoq.Mock.ofType(); + mockGlobalState = TypeMoq.Mock.ofType(); + mockExtensionContext.setup(x => x.globalState).returns(() => mockGlobalState.object); + }); + + beforeEach(function (): void { + sinon.stub(ConnectToControllerDialog.prototype, 'showDialog'); + }); + + it('Rejected with expected error when user cancels', async function (): Promise { + // Returning an undefined model here indicates that the dialog closed without clicking "Ok" - usually through the user clicking "Cancel" + sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve(undefined)); + const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); + await should(model.azdataLogin()).be.rejectedWith(new UserCancelledError()); + }); + + it('Reads password from cred store', async function (): Promise { + const password = 'password123'; + + // Set up cred store to return our password + const credProviderMock = TypeMoq.Mock.ofType(); + credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: password })); + // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66 + credProviderMock.setup((x: any) => x.then).returns(() => undefined); + sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object)); + + const azdataExtApiMock = TypeMoq.Mock.ofType(); + const azdataMock = TypeMoq.Mock.ofType(); + azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); + sinon.stub(vscode.extensions, 'getExtension').returns({ exports: azdataExtApiMock.object }); + const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); + + await model.azdataLogin(); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + }); + + it('Prompt for password when not in cred store', async function (): Promise { + const password = 'password123'; + + // Set up cred store to return empty password + const credProviderMock = TypeMoq.Mock.ofType(); + credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: '' })); + // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66 + credProviderMock.setup((x: any) => x.then).returns(() => undefined); + sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object)); + + const azdataExtApiMock = TypeMoq.Mock.ofType(); + const azdataMock = TypeMoq.Mock.ofType(); + azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); + sinon.stub(vscode.extensions, 'getExtension').returns({ exports: azdataExtApiMock.object }); + + // Set up dialog to return new model with our password + const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password); + sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password })); + + const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); + + await model.azdataLogin(); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + }); + + it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise { + const password = 'password123'; + // Set up cred store to return a password to start with + const credProviderMock = TypeMoq.Mock.ofType(); + credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' })); + // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66 + credProviderMock.setup((x: any) => x.then).returns(() => undefined); + sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object)); + + const azdataExtApiMock = TypeMoq.Mock.ofType(); + const azdataMock = TypeMoq.Mock.ofType(); + azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); + sinon.stub(vscode.extensions, 'getExtension').returns({ exports: azdataExtApiMock.object }); + + // Set up dialog to return new model with our new password from the reprompt + const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password); + const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password })); + + const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); + + await model.azdataLogin(true); + should(waitForCloseStub.called).be.true('waitForClose should have been called'); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + }); + + it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise { + const password = 'password123'; + // Set up cred store to return a password to start with + const credProviderMock = TypeMoq.Mock.ofType(); + credProviderMock.setup(x => x.readCredential(TypeMoq.It.isAny())).returns(() => Promise.resolve({ credentialId: 'id', password: 'originalPassword' })); + // Need to setup then when Promise.resolving a mocked object : https://github.com/florinn/typemoq/issues/66 + credProviderMock.setup((x: any) => x.then).returns(() => undefined); + sinon.stub(azdata.credentials, 'getProvider').returns(Promise.resolve(credProviderMock.object)); + + const azdataExtApiMock = TypeMoq.Mock.ofType(); + const azdataMock = TypeMoq.Mock.ofType(); + azdataMock.setup(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + azdataExtApiMock.setup(x => x.azdata).returns(() => azdataMock.object); + sinon.stub(vscode.extensions, 'getExtension').returns({ exports: azdataExtApiMock.object }); + + // Set up dialog to return new model with our new password from the reprompt + const newModel = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, password); + const waitForCloseStub = sinon.stub(ConnectToControllerDialog.prototype, 'waitForClose').returns(Promise.resolve({ controllerModel: newModel, password: password })); + + // Set up original model with a password + const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { url: '127.0.0.1', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }, 'originalPassword'); + + await model.azdataLogin(true); + should(waitForCloseStub.called).be.true('waitForClose should have been called'); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + }); + }); + +});