From a1f600657a2b33aaa0deda2e6eab1539cfd3bdc1 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Tue, 30 Jun 2020 14:05:27 -0700 Subject: [PATCH] Add more arc tests (#11145) * Add more arc tests * Update comment --- extensions/arc/coverConfig.json | 5 +- extensions/arc/src/common/utils.ts | 15 +- extensions/arc/src/test/common/date.test.ts | 85 +++++++++++ extensions/arc/src/test/common/utils.test.ts | 138 +++++++++++++++++- .../arc/src/test/controller/auth.test.ts | 31 ++++ 5 files changed, 255 insertions(+), 19 deletions(-) create mode 100644 extensions/arc/src/test/common/date.test.ts create mode 100644 extensions/arc/src/test/controller/auth.test.ts diff --git a/extensions/arc/coverConfig.json b/extensions/arc/coverConfig.json index cc192564eb..bf3311a06e 100644 --- a/extensions/arc/coverConfig.json +++ b/extensions/arc/coverConfig.json @@ -5,7 +5,10 @@ "ignorePatterns": [ "**/generated/**", "**/node_modules/**", - "**/test/**" + "**/test/**", + "constants.js", + "localizedConstants.js", + "extension.js" ], "reports": [ "cobertura", diff --git a/extensions/arc/src/common/utils.ts b/extensions/arc/src/common/utils.ts index 97d6b96fa8..5099816fa1 100644 --- a/extensions/arc/src/common/utils.ts +++ b/extensions/arc/src/common/utils.ts @@ -103,13 +103,6 @@ export function getDatabaseStateDisplayText(state: string): string { return state; } -/** - * Opens an input box prompting the user to enter in the name of a resource to delete - * @param namespace The namespace of the resource to delete - * @param name The name of the resource to delete - * @returns Promise resolving to true if the user confirmed the name, false if the input box was closed for any other reason - */ - /** * Opens an input box prompting and validating the user's input. * @param options Options for the input box @@ -173,9 +166,9 @@ export async function promptForResourceDeletion(namespace: string, name: string) * Opens an input box prompting the user to enter and confirm a password * @param validate A function that accepts the password and returns an error message if it's invalid * @returns Promise resolving to the password if it passed validation, - * or false if the input box was closed for any other reason + * or undefined if the input box was closed for any other reason */ -export async function promptAndConfirmPassword(validate: (input: string) => string): Promise { +export async function promptAndConfirmPassword(validate: (input: string) => string): Promise { const title = loc.resetPassword; const options: vscode.InputBoxOptions = { prompt: loc.enterNewPassword, @@ -190,7 +183,7 @@ export async function promptAndConfirmPassword(validate: (input: string) => stri return promptInputBox(title, options); } - return false; + return undefined; } /** @@ -198,7 +191,7 @@ export async function promptAndConfirmPassword(validate: (input: string) => stri * @param error The error object */ export function getErrorMessage(error: any): string { - if (error?.body?.reason) { + if (error.body?.reason) { // For HTTP Errors with a body pull out the reason message since that's usually the most helpful return error.body.reason; } else if (error.message) { diff --git a/extensions/arc/src/test/common/date.test.ts b/extensions/arc/src/test/common/date.test.ts new file mode 100644 index 0000000000..3c894d7502 --- /dev/null +++ b/extensions/arc/src/test/common/date.test.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * 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 'mocha'; +import { fromNow } from '../../common/date'; + +describe('fromNow Method Tests', function () { + it('Future date', function (): void { + should(fromNow(new Date().getTime() + 60000)).startWith('in'); + }); + + it('Now', function (): void { + should(fromNow(new Date())).equal('now'); + }); + + it('< 1 min ago', function (): void { + // 30 sec + should(fromNow(new Date().getTime() - 30000)).endWith('secs'); + should(fromNow(new Date().getTime() - 30000, true)).endWith('secs ago'); + }); + + it('< 1 hr ago', function (): void { + // 1.5 min + should(fromNow(new Date().getTime() - 90 * 1000)).endWith('min'); + should(fromNow(new Date().getTime() - 90 * 1000, true)).endWith('min ago'); + + // 5 min + should(fromNow(new Date().getTime() - 5 * 60 * 1000)).endWith('mins'); + should(fromNow(new Date().getTime() - 5 * 60 * 1000, true)).endWith('mins ago'); + }); + + it('< 1 day ago', function (): void { + // 1.5 hrs + should(fromNow(new Date().getTime() - 90 * 60 * 1000)).endWith('hr'); + should(fromNow(new Date().getTime() - 90 * 60 * 1000, true)).endWith('hr ago'); + + // 5 hrs + should(fromNow(new Date().getTime() - 5 * 60 * 60 * 1000)).endWith('hrs'); + should(fromNow(new Date().getTime() - 5 * 60 * 60 * 1000, true)).endWith('hrs ago'); + }); + + it('< 1 week ago', function (): void { + // 30 hours + should(fromNow(new Date().getTime() - 30 * 60 * 60 * 1000)).endWith('day'); + should(fromNow(new Date().getTime() - 30 * 60 * 60 * 1000, true)).endWith('day ago'); + + // 3 days + should(fromNow(new Date().getTime() - 3 * 24 * 60 * 60 * 1000)).endWith('days'); + should(fromNow(new Date().getTime() - 3 * 24 * 60 * 60 * 1000, true)).endWith('days ago'); + }); + + it('< 1 month ago', function (): void { + // 10 days + should(fromNow(new Date().getTime() - 10 * 24 * 60 * 60 * 1000)).endWith('wk'); + should(fromNow(new Date().getTime() - 10 * 24 * 60 * 60 * 1000, true)).endWith('wk ago'); + + // 20 days + should(fromNow(new Date().getTime() - 20 * 24 * 60 * 60 * 1000)).endWith('wks'); + should(fromNow(new Date().getTime() - 20 * 24 * 60 * 60 * 1000, true)).endWith('wks ago'); + }); + + it('< 1 year ago', function (): void { + // 45 days + should(fromNow(new Date().getTime() - 45 * 24 * 60 * 60 * 1000)).endWith('mo'); + should(fromNow(new Date().getTime() - 45 * 24 * 60 * 60 * 1000, true)).endWith('mo ago'); + + // 90 days + should(fromNow(new Date().getTime() - 90 * 24 * 60 * 60 * 1000)).endWith('mos'); + should(fromNow(new Date().getTime() - 90 * 24 * 60 * 60 * 1000, true)).endWith('mos ago'); + }); + + it('> 1 year ago', function (): void { + // 400 days + should(fromNow(new Date().getTime() - 400 * 24 * 60 * 60 * 1000)).endWith('yr'); + should(fromNow(new Date().getTime() - 400 * 24 * 60 * 60 * 1000, true)).endWith('yr ago'); + + // 1000 + should(fromNow(new Date().getTime() - 1000 * 24 * 60 * 60 * 1000)).endWith('yrs'); + should(fromNow(new Date().getTime() - 1000 * 24 * 60 * 60 * 1000, true)).endWith('yrs ago'); + }); + +}); diff --git a/extensions/arc/src/test/common/utils.test.ts b/extensions/arc/src/test/common/utils.test.ts index 9402a9a9ee..726139f91b 100644 --- a/extensions/arc/src/test/common/utils.test.ts +++ b/extensions/arc/src/test/common/utils.test.ts @@ -6,13 +6,15 @@ import * as vscode from 'vscode'; import * as should from 'should'; import 'mocha'; -import { resourceTypeToDisplayName, parseEndpoint, parseInstanceName, getAzurecoreApi, getResourceTypeIcon, getConnectionModeDisplayText, getDatabaseStateDisplayText, promptForResourceDeletion } from '../../common/utils'; +import { resourceTypeToDisplayName, parseEndpoint, parseInstanceName, getAzurecoreApi, getResourceTypeIcon, getConnectionModeDisplayText, getDatabaseStateDisplayText, promptForResourceDeletion, promptAndConfirmPassword, getErrorMessage } from '../../common/utils'; import * as loc from '../../localizedConstants'; import { ResourceType, IconPathHelper, Connectionmode as ConnectionMode } from '../../constants'; import { MockInputBox } from '../stubs'; +import { HttpError } from '../../controller/generated/v1/api'; +import { IncomingMessage } from 'http'; -describe('resourceTypeToDisplayName Method Tests', function(): void { +describe('resourceTypeToDisplayName Method Tests', function (): void { it('Display Name should be correct for valid ResourceType', function (): void { should(resourceTypeToDisplayName(ResourceType.dataControllers)).equal(loc.dataControllersType); should(resourceTypeToDisplayName(ResourceType.postgresInstances)).equal(loc.pgSqlType); @@ -32,7 +34,7 @@ describe('resourceTypeToDisplayName Method Tests', function(): void { }); }); -describe('parseEndpoint Method Tests', function(): void { +describe('parseEndpoint Method Tests', function (): void { it('Should parse valid endpoint correctly', function (): void { should(parseEndpoint('127.0.0.1:1337')).deepEqual({ ip: '127.0.0.1', port: '1337' }); }); @@ -64,13 +66,13 @@ describe('parseInstanceName Method Tests', () => { }); }); -describe('getAzurecoreApi Method Tests', function() { +describe('getAzurecoreApi Method Tests', function () { it('Should get azurecore API correctly', function (): void { should(getAzurecoreApi()).not.be.undefined(); }); }); -describe('getResourceTypeIcon Method Tests', function() { +describe('getResourceTypeIcon Method Tests', function () { it('Correct icons should be returned for valid ResourceTypes', function (): void { should(getResourceTypeIcon(ResourceType.sqlManagedInstances)).equal(IconPathHelper.miaa, 'Unexpected MIAA icon'); should(getResourceTypeIcon(ResourceType.postgresInstances)).equal(IconPathHelper.postgres, 'Unexpected Postgres icon'); @@ -87,7 +89,7 @@ describe('getResourceTypeIcon Method Tests', function() { }); }); -describe('getConnectionModeDisplayText Method Tests', function() { +describe('getConnectionModeDisplayText Method Tests', function () { it('Display Name should be correct for valid ResourceType', function (): void { should(getConnectionModeDisplayText(ConnectionMode.connected)).equal(loc.connected); should(getConnectionModeDisplayText(ConnectionMode.disconnected)).equal(loc.disconnected); @@ -106,7 +108,7 @@ describe('getConnectionModeDisplayText Method Tests', function() { }); }); -describe('getDatabaseStateDisplayText Method Tests', function() { +describe('getDatabaseStateDisplayText Method Tests', function () { it('State should be correct for valid states', function (): void { should(getDatabaseStateDisplayText('ONLINE')).equal(loc.online); should(getDatabaseStateDisplayText('OFFLINE')).equal(loc.offline); @@ -162,3 +164,125 @@ describe('promptForResourceDeletion Method Tests', function (): void { should(mockInputBox.validationMessage).be.equal('', 'Validation message should be empty after new value entered'); }); }); + +describe('promptAndConfirmPassword Method Tests', function (): void { + let mockInputBox: MockInputBox; + before(function (): void { + vscode.window.createInputBox = () => { + return mockInputBox; + }; + }); + + beforeEach(function (): void { + mockInputBox = new MockInputBox(); + }); + + it('Resolves with expected string when passwords match', function (done): void { + const password = 'MyPassword'; + promptAndConfirmPassword((_: string) => { return ''; }).then(value => { + if (value === password) { + done(); + } else { + done(new Error(`Return value '${value}' did not match expected value '${password}'`)); + } + }); + mockInputBox.value = password; + mockInputBox.triggerAccept().then( () => { + mockInputBox.value = password; + mockInputBox.triggerAccept(); + }); + }); + + it('Resolves with undefined when first input box closed early', function (done): void { + promptAndConfirmPassword((_: string) => { return ''; }).then(value => { + if (value === undefined) { + done(); + } else { + done(new Error('Return value was expected to be undefined')); + } + }); + mockInputBox.hide(); + }); + + it('Resolves with undefined when second input box closed early', function (done): void { + const password = 'MyPassword'; + promptAndConfirmPassword((_: string) => { return ''; }).then(value => { + if (value === undefined) { + done(); + } else { + done(new Error('Return value was expected to be undefined')); + } + }); + mockInputBox.value = password; + mockInputBox.triggerAccept().then( () => { + mockInputBox.hide(); + }); + }); + + it('Error message displayed when validation callback returns error message', function (done): void { + const testError = 'Test Error'; + promptAndConfirmPassword((_: string) => { return testError; }).catch(err => done(err)); + mockInputBox.value = ''; + mockInputBox.triggerAccept().then( () => { + if(mockInputBox.validationMessage === testError) { + done(); + } else { + done(new Error(`Validation message '${mockInputBox.validationMessage}' was expected to be '${testError}'`)); + } + }); + }); + + it('Error message displayed when passwords do not match', function (done): void { + promptAndConfirmPassword((_: string) => { return ''; }).catch(err => done(err)); + mockInputBox.value = 'MyPassword'; + mockInputBox.triggerAccept().then( () => { + mockInputBox.value = 'WrongPassword'; + mockInputBox.triggerAccept().then( () => { + if(mockInputBox.validationMessage === loc.thePasswordsDoNotMatch) { + done(); + } else { + done(new Error(`Validation message '${mockInputBox.validationMessage} was not the expected message`)); + } + }); + }); + }); +}); + +describe('getErrorMessage Method Tests', function () { + it('HttpError with reason', function (): void { + const httpReason = 'Test Reason'; + should(getErrorMessage(new HttpError({ }, { reason: 'Test Reason' }))).equal(httpReason); + }); + + it('HttpError with status message', function (): void { + const httpStatusMessage = 'Test Status Message'; + should(getErrorMessage(new HttpError({ statusMessage: httpStatusMessage}, { }))).containEql(`(${httpStatusMessage})`); + }); + + it('Error with message', function (): void { + const errorMessage = 'Test Message'; + const error = new Error(errorMessage); + should(getErrorMessage(error)).equal(errorMessage); + }); + + it('Error with no message', function (): void { + const error = new Error(); + should(getErrorMessage(error)).equal(error); + }); +}); + +describe('parseInstanceName Method Tests', function () { + it('2 part name', function (): void { + const name = 'MyName'; + should(parseInstanceName(`MyNamespace_${name}`)).equal(name); + }); + + it('1 part name', function (): void { + const name = 'MyName'; + should(parseInstanceName(name)).equal(name); + }); + + it('Invalid name', function (): void { + should(() => parseInstanceName('Some_Invalid_Name')).throwError(); + }); +}); diff --git a/extensions/arc/src/test/controller/auth.test.ts b/extensions/arc/src/test/controller/auth.test.ts new file mode 100644 index 0000000000..4b4aa59a3f --- /dev/null +++ b/extensions/arc/src/test/controller/auth.test.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * 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 should from 'should'; +import 'mocha'; +import { BasicAuth } from '../../controller/auth'; + +describe('BasicAuth Method Tests', function () { + + it('Options applied correctly', async function (): Promise { + const username = 'MyUsername'; + const password = 'MyPassword'; + let ignoreSslVerification = true; + const auth = new BasicAuth(username, password); + const requestOptions = {} as any; + // We don't need this to be actual valid options since we're just checking the ones applied + auth.applyToRequest(requestOptions); + await vscode.workspace.getConfiguration('arc').update('ignoreSslVerification', ignoreSslVerification, vscode.ConfigurationTarget.Global); + should(requestOptions.auth).deepEqual({ username: username, password: password }); + should(requestOptions.agentOptions).deepEqual({ rejectUnauthorized: !ignoreSslVerification }); + + ignoreSslVerification = false; + await vscode.workspace.getConfiguration('arc').update('ignoreSslVerification', ignoreSslVerification, vscode.ConfigurationTarget.Global); + auth.applyToRequest(requestOptions); + should(requestOptions.agentOptions).deepEqual({ rejectUnauthorized: !ignoreSslVerification }); + }); + +});