diff --git a/extensions/arc/package.json b/extensions/arc/package.json index d30f9657d6..879dc7ac1a 100644 --- a/extensions/arc/package.json +++ b/extensions/arc/package.json @@ -926,10 +926,12 @@ "@types/mocha": "^5.2.5", "@types/node": "^12.11.7", "@types/request": "^2.48.3", + "@types/sinon": "^9.0.4", "mocha": "^5.2.0", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "should": "^13.2.3", + "sinon": "^9.0.2", "typemoq": "2.1.0", "vscodetestcover": "^1.0.9" } diff --git a/extensions/arc/src/test/ui/dialogs/connectControllerDialog.test.ts b/extensions/arc/src/test/ui/dialogs/connectControllerDialog.test.ts new file mode 100644 index 0000000000..117458f3c8 --- /dev/null +++ b/extensions/arc/src/test/ui/dialogs/connectControllerDialog.test.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as should from 'should'; +import * as sinon from 'sinon'; +import { ConnectToControllerDialog } from '../../../ui/dialogs/connectControllerDialog'; +import { ControllerInfo, ControllerModel } from '../../../models/controllerModel'; + +describe('ConnectControllerDialog', function (): void { + afterEach(function (): void { + sinon.restore(); + }); + + (<{ info: ControllerInfo | undefined, description: string }[]>[ + { info: undefined, description: 'all input' }, + { info: { url: '127.0.0.1' }, description: 'all but URL' }, + { info: { url: '127.0.0.1', username: 'sa' }, description: 'all but URL and password' }]).forEach(test => { + it(`Validate returns false when ${test.description} is empty`, async function (): Promise { + const connectControllerDialog = new ConnectToControllerDialog(undefined!); + connectControllerDialog.showDialog(test.info, undefined); + await connectControllerDialog.isInitialized; + const validateResult = await connectControllerDialog.validate(); + should(validateResult).be.false(); + }); + }); + + it('validate returns false if controller refresh fails', async function (): Promise { + sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.reject('Controller refresh failed')); + const connectControllerDialog = new ConnectToControllerDialog(undefined!); + const info = { url: 'https://127.0.0.1:30080', username: 'sa', rememberPassword: true, resources: [] }; + connectControllerDialog.showDialog(info, 'pwd'); + await connectControllerDialog.isInitialized; + const validateResult = await connectControllerDialog.validate(); + should(validateResult).be.false('Validation should have returned false'); + }); + + it('validate replaces http with https', async function (): Promise { + await validateConnectControllerDialog( + { url: 'http://127.0.0.1:30081', username: 'sa', rememberPassword: true, resources: [] }, + 'https://127.0.0.1:30081'); + }); + + it('validate appends https if missing', async function (): Promise { + await validateConnectControllerDialog({ url: '127.0.0.1:30080', username: 'sa', rememberPassword: true, resources: [] }, + 'https://127.0.0.1:30080'); + }); + + it('validate appends default port if missing', async function (): Promise { + await validateConnectControllerDialog({ url: 'https://127.0.0.1', username: 'sa', rememberPassword: true, resources: [] }, + 'https://127.0.0.1:30080'); + }); + + it('validate appends both port and https if missing', async function (): Promise { + await validateConnectControllerDialog({ url: '127.0.0.1', username: 'sa', rememberPassword: true, resources: [] }, + 'https://127.0.0.1:30080'); + }); +}); + +async function validateConnectControllerDialog(info: ControllerInfo, expectedUrl: string): Promise { + // For first set of tests just stub out refresh calls - we'll test that separately + sinon.stub(ControllerModel.prototype, 'refresh').returns(Promise.resolve()); + const connectControllerDialog = new ConnectToControllerDialog(undefined!); + connectControllerDialog.showDialog(info, 'pwd'); + await connectControllerDialog.isInitialized; + const validateResult = await connectControllerDialog.validate(); + should(validateResult).be.true('Validation should have returned true'); + const model = await connectControllerDialog.waitForClose(); + should(model?.controllerModel.info.url).equal(expectedUrl); +} diff --git a/extensions/arc/src/ui/components/initializingComponent.ts b/extensions/arc/src/ui/components/initializingComponent.ts index c8db964f5d..15d4abfc79 100644 --- a/extensions/arc/src/ui/components/initializingComponent.ts +++ b/extensions/arc/src/ui/components/initializingComponent.ts @@ -17,6 +17,10 @@ export abstract class InitializingComponent { return this._initialized; } + public get isInitialized(): Promise { + return this.onInitializedPromise.promise; + } + protected set initialized(value: boolean) { if (!this._initialized && value) { this._initialized = true; diff --git a/extensions/arc/src/ui/dialogs/connectControllerDialog.ts b/extensions/arc/src/ui/dialogs/connectControllerDialog.ts index 41275b0a6b..e466ca620f 100644 --- a/extensions/arc/src/ui/dialogs/connectControllerDialog.ts +++ b/extensions/arc/src/ui/dialogs/connectControllerDialog.ts @@ -9,10 +9,11 @@ import * as loc from '../../localizedConstants'; import { AzureArcTreeDataProvider } from '../tree/azureArcTreeDataProvider'; import { ControllerModel, ControllerInfo } from '../../models/controllerModel'; import { Deferred } from '../../common/promise'; +import { InitializingComponent } from '../components/initializingComponent'; export type ConnectToControllerDialogModel = { controllerModel: ControllerModel, password: string }; -export class ConnectToControllerDialog { +export class ConnectToControllerDialog extends InitializingComponent { private modelBuilder!: azdata.ModelBuilder; private urlInputBox!: azdata.InputBoxComponent; @@ -22,9 +23,11 @@ export class ConnectToControllerDialog { private _completionPromise = new Deferred(); - constructor(private _treeDataProvider: AzureArcTreeDataProvider) { } + constructor(private _treeDataProvider: AzureArcTreeDataProvider) { + super(); + } - public showDialog(controllerInfo?: ControllerInfo, password?: string): void { + public showDialog(controllerInfo?: ControllerInfo, password?: string): azdata.window.Dialog { const dialog = azdata.window.createModelViewDialog(loc.connectToController); dialog.cancelButton.onClick(() => this.handleCancel()); dialog.registerContent(async view => { @@ -76,15 +79,17 @@ export class ConnectToControllerDialog { }]).withLayout({ width: '100%' }).component(); await view.initializeModel(formModel); this.urlInputBox.focus(); + this.initialized = true; }); dialog.registerCloseValidator(async () => await this.validate()); dialog.okButton.label = loc.connect; dialog.cancelButton.label = loc.cancel; azdata.window.openDialog(dialog); + return dialog; } - private async validate(): Promise { + public async validate(): Promise { if (!this.urlInputBox.value || !this.usernameInputBox.value || !this.passwordInputBox.value) { return false; } diff --git a/extensions/arc/yarn.lock b/extensions/arc/yarn.lock index ba09549d6b..88520528e8 100644 --- a/extensions/arc/yarn.lock +++ b/extensions/arc/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.7.2": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" + integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q== + 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.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938" + integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ== + 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/caseless@*": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" @@ -212,6 +248,18 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/sinon@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1" + integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw== + dependencies: + "@types/sinonjs__fake-timers" "*" + +"@types/sinonjs__fake-timers@*": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e" + integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA== + "@types/tough-cookie@*": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.0.tgz#fef1904e4668b6e5ecee60c52cc6a078ffa6697d" @@ -432,6 +480,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== + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -609,6 +662,11 @@ is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +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= + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -716,6 +774,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +just-extend@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4" + integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA== + +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" @@ -834,6 +902,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.1: + 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" + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -856,6 +935,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" + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -995,6 +1081,19 @@ should@^13.2.3: should-type-adaptors "^1.0.1" should-util "^1.0.0" +sinon@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d" + integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A== + dependencies: + "@sinonjs/commons" "^1.7.2" + "@sinonjs/fake-timers" "^6.0.1" + "@sinonjs/formatio" "^5.0.1" + "@sinonjs/samsam" "^5.0.3" + diff "^4.0.2" + nise "^4.0.1" + 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" @@ -1078,6 +1177,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +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"