From f10ac10f6deac6f9f88b499745f958912decaac1 Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Tue, 3 Nov 2020 13:34:33 -0800 Subject: [PATCH] platformService tests and move tests from tdd to bdd (#13131) --- .../resource-deployment/src/common/utils.ts | 9 + .../src/services/platformService.ts | 4 +- .../src/test/SemVerProxy.test.ts | 4 +- .../resource-deployment/src/test/index.ts | 2 +- .../test/{ => services}/apiService.test.ts | 6 +- .../test/{ => services}/azdataService.test.ts | 12 +- .../{ => services}/notebookService.test.ts | 16 +- .../src/test/services/platformService.test.ts | 243 ++++++++++++++++++ .../resourceTypeService.test.ts | 22 +- .../src/test/services/toolsService.test.ts | 59 +++++ .../src/test/toolsService.test.ts | 51 ---- .../test/ui/validation/validations.test.ts | 24 +- 12 files changed, 355 insertions(+), 97 deletions(-) rename extensions/resource-deployment/src/test/{ => services}/apiService.test.ts (75%) rename extensions/resource-deployment/src/test/{ => services}/azdataService.test.ts (86%) rename extensions/resource-deployment/src/test/{ => services}/notebookService.test.ts (91%) create mode 100644 extensions/resource-deployment/src/test/services/platformService.test.ts rename extensions/resource-deployment/src/test/{ => services}/resourceTypeService.test.ts (84%) create mode 100644 extensions/resource-deployment/src/test/services/toolsService.test.ts delete mode 100644 extensions/resource-deployment/src/test/toolsService.test.ts diff --git a/extensions/resource-deployment/src/common/utils.ts b/extensions/resource-deployment/src/common/utils.ts index 59c6963506..fce3e7c0ce 100644 --- a/extensions/resource-deployment/src/common/utils.ts +++ b/extensions/resource-deployment/src/common/utils.ts @@ -53,3 +53,12 @@ export function throwUnless(condition: boolean, message?: string): asserts condi throw new Error(message); } } +export async function tryExecuteAction(action: () => T | PromiseLike): Promise<{ result: T | undefined, error: any }> { + let error: any, result: T | undefined; + try { + result = await action(); + } catch (e) { + error = e; + } + return { result, error }; +} diff --git a/extensions/resource-deployment/src/services/platformService.ts b/extensions/resource-deployment/src/services/platformService.ts index f2f46f7c98..eef4807009 100644 --- a/extensions/resource-deployment/src/services/platformService.ts +++ b/extensions/resource-deployment/src/services/platformService.ts @@ -13,7 +13,7 @@ import { OsDistribution, OsRelease } from '../interfaces'; import { getErrorMessage } from '../common/utils'; const localize = nls.loadMessageBundle(); -const extensionOutputChannel = localize('resourceDeployment.outputChannel', "Deployments"); +export const extensionOutputChannel = localize('resourceDeployment.outputChannel', "Deployments"); const sudoPromptTitle = 'AzureDataStudio'; /** * Abstract of platform dependencies @@ -244,7 +244,6 @@ export class PlatformService implements IPlatformService { windowsHide: true }; const child = cp.spawn(command, [], spawnOptions); - // Add listeners to print stdout and stderr and exit code child.on('exit', (code: number | null, signal: string | null) => { if (code !== null) { @@ -258,7 +257,6 @@ export class PlatformService implements IPlatformService { this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stdout', " stdout: ")); }); child.stderr!.on('data', (data: string | Buffer) => { this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stderr', " stderr: ")); }); - await child; return stdoutData.join(''); } diff --git a/extensions/resource-deployment/src/test/SemVerProxy.test.ts b/extensions/resource-deployment/src/test/SemVerProxy.test.ts index d9ba5f202e..2707d28cc7 100644 --- a/extensions/resource-deployment/src/test/SemVerProxy.test.ts +++ b/extensions/resource-deployment/src/test/SemVerProxy.test.ts @@ -85,9 +85,9 @@ function validate(test: TestDefinition, semVerProxy: SymVerProxyTest) { } } -suite('SemVeryProxy Tests', function (): void { +describe('SemVeryProxy Tests', function (): void { testDefinitions.forEach((semVerTest: TestDefinition) => { - test(semVerTest.testName, () => { + it(semVerTest.testName, () => { const semVerProxy = new SymVerProxyTest(semVerTest.inputVersion); validate(semVerTest, semVerProxy); }); diff --git a/extensions/resource-deployment/src/test/index.ts b/extensions/resource-deployment/src/test/index.ts index 465f51175a..59de14c623 100644 --- a/extensions/resource-deployment/src/test/index.ts +++ b/extensions/resource-deployment/src/test/index.ts @@ -9,7 +9,7 @@ const testRunner = require('vscodetestcover'); const suite = 'resource-deployment Extension Tests'; const mochaOptions: any = { - ui: 'tdd', + ui: 'bdd', useColors: true, timeout: 10000 }; diff --git a/extensions/resource-deployment/src/test/apiService.test.ts b/extensions/resource-deployment/src/test/services/apiService.test.ts similarity index 75% rename from extensions/resource-deployment/src/test/apiService.test.ts rename to extensions/resource-deployment/src/test/services/apiService.test.ts index 4b3595189c..f6b82a629c 100644 --- a/extensions/resource-deployment/src/test/apiService.test.ts +++ b/extensions/resource-deployment/src/test/services/apiService.test.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import 'mocha'; +import { apiService } from '../../services/apiService'; import assert = require('assert'); -import { apiService } from '../services/apiService'; -suite('API Service Tests', function (): void { - test('getAzurecoreApi returns azure api', () => { +describe('API Service Tests', function (): void { + it('get azurecoreApi returns azure api', () => { const api = apiService.azurecoreApi; assert(api !== undefined); }); diff --git a/extensions/resource-deployment/src/test/azdataService.test.ts b/extensions/resource-deployment/src/test/services/azdataService.test.ts similarity index 86% rename from extensions/resource-deployment/src/test/azdataService.test.ts rename to extensions/resource-deployment/src/test/services/azdataService.test.ts index 466bc19bcb..2e039fb8a9 100644 --- a/extensions/resource-deployment/src/test/azdataService.test.ts +++ b/extensions/resource-deployment/src/test/services/azdataService.test.ts @@ -6,12 +6,12 @@ import 'mocha'; import * as TypeMoq from 'typemoq'; import * as should from 'should'; -import { IPlatformService, CommandOptions } from '../services/platformService'; -import { AzdataService } from '../services/azdataService'; -import { BdcDeploymentType } from '../interfaces'; +import { IPlatformService, CommandOptions } from '../../services/platformService'; +import { AzdataService } from '../../services/azdataService'; +import { BdcDeploymentType } from '../../interfaces'; -suite('azdata service Tests', function (): void { - test('azdata service handles deployment types properly', async () => { +describe('azdata service Tests', function (): void { + it('azdata service handles deployment types properly', async () => { const mockPlatformService = TypeMoq.Mock.ofType(); const azdataService = new AzdataService(mockPlatformService.object); mockPlatformService.setup((service) => service.runCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns((command: string, options: CommandOptions | undefined) => { @@ -30,7 +30,7 @@ suite('azdata service Tests', function (): void { mockPlatformService.verify((service) => service.runCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(5)); }); - test('azdata service returns correct deployment profiles', async () => { + it('azdata service returns correct deployment profiles', async () => { const mockPlatformService = TypeMoq.Mock.ofType(); const azdataService = new AzdataService(mockPlatformService.object); mockPlatformService.setup((service => service.storagePath())).returns(() => { diff --git a/extensions/resource-deployment/src/test/notebookService.test.ts b/extensions/resource-deployment/src/test/services/notebookService.test.ts similarity index 91% rename from extensions/resource-deployment/src/test/notebookService.test.ts rename to extensions/resource-deployment/src/test/services/notebookService.test.ts index f7e619c15b..35e859147d 100644 --- a/extensions/resource-deployment/src/test/notebookService.test.ts +++ b/extensions/resource-deployment/src/test/services/notebookService.test.ts @@ -5,14 +5,14 @@ import * as TypeMoq from 'typemoq'; import 'mocha'; -import { NotebookService } from '../services/notebookService'; +import { NotebookService } from '../../services/notebookService'; import assert = require('assert'); -import { NotebookPathInfo } from '../interfaces'; -import { IPlatformService } from '../services/platformService'; +import { NotebookPathInfo } from '../../interfaces'; +import { IPlatformService } from '../../services/platformService'; -suite('Notebook Service Tests', function (): void { +describe('Notebook Service Tests', function (): void { - test('getNotebook with string parameter', () => { + it('getNotebook with string parameter', () => { const mockPlatformService = TypeMoq.Mock.ofType(); const notebookService = new NotebookService(mockPlatformService.object, ''); const notebookInput = 'test-notebook.ipynb'; @@ -28,7 +28,7 @@ suite('Notebook Service Tests', function (): void { mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); }); - test('getNotebook with NotebookInfo parameter', () => { + it('getNotebook with NotebookInfo parameter', () => { const mockPlatformService = TypeMoq.Mock.ofType(); const notebookService = new NotebookService(mockPlatformService.object, ''); const notebookWin32 = 'test-notebook-win32.ipynb'; @@ -58,7 +58,7 @@ suite('Notebook Service Tests', function (): void { mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); }); - test('findNextUntitledEditorName with no name conflict', () => { + it('findNextUntitledEditorName with no name conflict', () => { const mockPlatformService = TypeMoq.Mock.ofType(); const notebookService = new NotebookService(mockPlatformService.object, ''); const notebookFileName = 'mynotebook.ipynb'; @@ -72,7 +72,7 @@ suite('Notebook Service Tests', function (): void { assert.equal(actualFileName, expectedTargetFile, 'target file name is not correct'); }); - test('findNextUntitledEditorName with name conflicts', () => { + it('findNextUntitledEditorName with name conflicts', () => { const mockPlatformService = TypeMoq.Mock.ofType(); const notebookService = new NotebookService(mockPlatformService.object, ''); const notebookFileName = 'mynotebook.ipynb'; diff --git a/extensions/resource-deployment/src/test/services/platformService.test.ts b/extensions/resource-deployment/src/test/services/platformService.test.ts new file mode 100644 index 0000000000..e421d0b43f --- /dev/null +++ b/extensions/resource-deployment/src/test/services/platformService.test.ts @@ -0,0 +1,243 @@ +/*--------------------------------------------------------------------------------------------- + * 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 fs from 'fs'; +import 'mocha'; +import * as os from 'os'; +import * as cp from 'promisify-child-process'; +import * as should from 'should'; +import * as sinon from 'sinon'; +import * as sudo from 'sudo-prompt'; +import * as TypeMoq from 'typemoq'; +import * as vscode from 'vscode'; +import { tryExecuteAction } from '../../common/utils'; +import { OsDistribution } from '../../interfaces'; +import { extensionOutputChannel, PlatformService } from '../../services/platformService'; +import { TestChildProcessPromise } from '../stubs'; + +const globalStoragePath = os.tmpdir(); +const platformService = new PlatformService(globalStoragePath); + +describe('PlatformService', () => { + beforeEach('PlatformService setup', async () => { + await platformService.initialize(); + }); + afterEach('PlatformService cleanup', () => { + sinon.restore(); + }); + it('storagePath', () => { + const result = platformService.storagePath(); + result.should.equal(globalStoragePath); + }); + it('platform', () => { + const result = platformService.platform(); + result.should.equal(process.platform); + }); + it('outputChannelName', () => { + const result = platformService.outputChannelName(); + result.should.equal(extensionOutputChannel); + }); + describe('output channel', () => { + let outputChannelStub: TypeMoq.IMock; + beforeEach('output channel setup', () => { + outputChannelStub = TypeMoq.Mock.ofType(); + }); + it('showOutputChannel', () => { + outputChannelStub.setup(c => c.show(TypeMoq.It.isAny())).callback((preserveFocus => { + preserveFocus.should.be.true(); + })); + platformService.showOutputChannel(true); + }); + describe('logToOutputChannel', () => { + ['', undefined, 'header'].forEach(header => { + it(`header = ${header}`, () => { + const data = 'data'; + outputChannelStub.setup(c => c.appendLine(TypeMoq.It.isAny())).callback((line => { + line.should.equal(header + line); + })); + platformService.logToOutputChannel(data, header); + }); + }); + }); + }); + it('osDistribution', () => { + const result = platformService.osDistribution(); + switch (process.platform) { + case 'darwin': result.should.equal(OsDistribution.darwin); break; + case 'win32': result.should.equal(OsDistribution.win32); break; + case 'linux': result.should.equal(OsDistribution.debian); break; + default: result.should.equal(OsDistribution.others); break; + } + }); + describe('file/directory', () => { + const filePath = __filename; + const contents = __dirname; //a known value + [true, false, 'throws'].forEach((fileExists => { + it(`fileExists - ${fileExists}`, async () => { + switch (fileExists) { + case true: (await platformService.fileExists(filePath)).should.be.true(); break; + case false: { + sinon.stub(fs.promises, 'access').rejects({ code: 'ENOENT' }); + (await platformService.fileExists(filePath)).should.be.false(); + break; + } + case 'throws': { + sinon.stub(fs.promises, 'access').rejects({}); + const { error } = await tryExecuteAction(() => platformService.fileExists(filePath)); + should(error).not.be.undefined(); + break; + } + default: throw new Error('unexpected error'); + } + }); + })); + describe('deleteFile', () => { + [true, false].forEach(fileExists => { + it(`fileExists - ${fileExists}`, async () => { + if (fileExists) { + const stub = sinon.stub(fs.promises, 'unlink').resolves(); + await platformService.deleteFile(filePath); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal(filePath); + } else { + sinon.stub(fs.promises, 'access').rejects({ code: 'ENOENT' }); // causes fileExists to return false + const stub = sinon.stub(fs.promises, 'unlink').resolves(); + await platformService.deleteFile(filePath); + stub.callCount.should.equal(0); // verifies that unlink was not called + } + }); + }); + [true, false].forEach(async ignoreError => { + it(`throws with ignoreError: ${ignoreError}`, async () => { + const stub = sinon.stub(fs.promises, 'unlink').throws(); + const { error } = await tryExecuteAction(() => platformService.deleteFile(filePath, ignoreError)); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal(filePath); + if (ignoreError) { + should(error).be.undefined(); + } else { + should(error).not.be.undefined(); + } + }); + }); + }); + it('openFile', () => { + const stub = sinon.stub(vscode.commands, 'executeCommand').resolves(); //resolves with a known string + platformService.openFile(filePath); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal('vscode.open'); + stub.getCall(0).args[1].path.should.equal(filePath); + }); + it('readTextFile', async () => { + sinon.stub(fs.promises, 'readFile').resolves(contents); + const result = await platformService.readTextFile(filePath); + result.should.equal(contents); + }); + it('saveTextFile', async () => { + const stub = sinon.stub(fs.promises, 'writeFile').resolves(); //resolves with a known string + await platformService.saveTextFile(contents, filePath); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal(filePath); + stub.getCall(0).args[1].should.equal(contents); + }); + it('copyFile', async () => { + const target = __dirname; //arbitrary path + const stub = sinon.stub(fs.promises, 'copyFile').resolves(); + await platformService.copyFile(filePath, target); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal(filePath); + stub.getCall(0).args[1].should.equal(target); + }); + it('makeDirectory ', async () => { + const target = __dirname; //arbitrary path + sinon.stub(fs.promises, 'access').rejects({ code: 'ENOENT' }); // this simulates the target directory to not Exist. + const stub = sinon.stub(fs.promises, 'mkdir').resolves(); + await platformService.makeDirectory(target); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal(target); + }); + }); + it('showErrorMessage', () => { + const error = __dirname; //arbitrary known string + const stub = sinon.stub(vscode.window, 'showErrorMessage').resolves(); //resolves with a known string + platformService.showErrorMessage(error); + stub.callCount.should.equal(1); + stub.getCall(0).args[0].should.equal(error); + }); + describe('isNotebookNameUsed', () => { + [true, false].forEach((isUsed => { + it(`return value: ${isUsed}`, () => { + const title = __filename; //arbitrary known string + if (isUsed) { + sinon.stub(azdata.nb, 'notebookDocuments').get(() => [{ isUntitled: true, fileName: title }]); + sinon.stub(vscode.workspace, 'textDocuments').get(() => [{ isUntitled: true, fileName: title }]); + } else { + sinon.stub(azdata.nb, 'notebookDocuments').get(() => [{ isUntitled: true, fileName: '' }]); + sinon.stub(vscode.workspace, 'textDocuments').get(() => [{ isUntitled: true, fileName: '' }]); + } + const result = platformService.isNotebookNameUsed(title); + result.should.equal(isUsed); + }); + })); + }); + describe('runCommand', () => { + [ + { commandSucceeds: true }, + { commandSucceeds: false, ignoreError: true }, + { commandSucceeds: false, ignoreError: false }, + ].forEach(({ commandSucceeds, ignoreError }) => { + if (ignoreError && commandSucceeds) { + return; //exit out of the loop as we do not handle ignoreError when command is successful + } + it(`non-sudo, commandSucceeds: ${commandSucceeds}, ignoreError: ${ignoreError}`, async () => { + const command = __dirname; // arbitrary command string, and success string on successful execution and error string on error + const child = new TestChildProcessPromise(); + const stub = sinon.stub(cp, 'spawn').returns(child); + const runningCommand = platformService.runCommand(command, { commandTitle: 'title', ignoreError: ignoreError }); + // fake runCommand to behave like echo, returning the command back as stdout/stderr/error. + // TestChildProcessPromise object shares the stdout/stderr stream for convenience with the child stream. + if (commandSucceeds) { + child.emit('data', command); + child.emit('exit', 0, null); //resolve with 0 exit code + child.resolve({ stdout: command }); + } else { + child.emit('data', command); + child.emit('exit', 1, null); // resolve with non-zero exit code + child.reject({ stderr: command }); + } + const { result, error } = await tryExecuteAction(() => runningCommand); + verifyCommandExecution(stub, result, error, command, commandSucceeds, ignoreError); + }); + + it(`sudo, commandSucceeds: ${commandSucceeds}, ignoreError: ${ignoreError}`, async () => { + const command = __dirname; // arbitrary command string, and success string on successful execution + const stub = sinon.stub(sudo, 'exec').callsFake((cmd, _options, cb) => { + // behaves like echo, returning the _cmd back as stdout/stderr/error. + if (commandSucceeds) { + cb(''/* error */, cmd/* stdout */, ''/* stderr */); + } else { + cb(cmd/* error */, ''/* stdout */, cmd/* stderr */); + } + }); + const { error, result } = await tryExecuteAction(() => platformService.runCommand(command, { commandTitle: 'title', ignoreError: ignoreError, sudo: true, workingDirectory: __dirname })); + verifyCommandExecution(stub, result, error, command, commandSucceeds, ignoreError); + }); + }); + }); +}); + +function verifyCommandExecution(stub: sinon.SinonStub, result: string | undefined, error: any, command: string, commandSucceeds: boolean | undefined, ignoreError: boolean | undefined) { + stub.callCount.should.equal(1); + if (commandSucceeds) { + result!.should.equal(command); + } else { + if (ignoreError) { + should(error).be.undefined(); + (result === undefined || result.length === 0).should.be.true('result should be an empty string or be undefined when an error occurs'); + } else { + should(error).not.be.undefined(); + } + } +} diff --git a/extensions/resource-deployment/src/test/resourceTypeService.test.ts b/extensions/resource-deployment/src/test/services/resourceTypeService.test.ts similarity index 84% rename from extensions/resource-deployment/src/test/resourceTypeService.test.ts rename to extensions/resource-deployment/src/test/services/resourceTypeService.test.ts index cd0d840387..37a04ef4f2 100644 --- a/extensions/resource-deployment/src/test/resourceTypeService.test.ts +++ b/extensions/resource-deployment/src/test/services/resourceTypeService.test.ts @@ -8,14 +8,14 @@ import * as TypeMoq from 'typemoq'; import assert = require('assert'); import should = require('should'); import { EOL } from 'os'; -import { ResourceTypeService, processWhenClause } from '../services/resourceTypeService'; -import { IPlatformService } from '../services/platformService'; -import { ToolsService } from '../services/toolsService'; -import { NotebookService } from '../services/notebookService'; +import { ResourceTypeService, processWhenClause } from '../../services/resourceTypeService'; +import { IPlatformService } from '../../services/platformService'; +import { ToolsService } from '../../services/toolsService'; +import { NotebookService } from '../../services/notebookService'; -suite('Resource Type Service Tests', function (): void { +describe('Resource Type Service Tests', function (): void { - test('test resource types', () => { + it('test resource types', () => { const mockPlatformService = TypeMoq.Mock.ofType(); const toolsService = new ToolsService(mockPlatformService.object); const notebookService = new NotebookService(mockPlatformService.object, ''); @@ -47,7 +47,7 @@ suite('Resource Type Service Tests', function (): void { assert(validationErrors.length === 0, `Validation errors detected in the package.json: ${validationErrors.join(EOL)}.`); }); - test('Selected options containing all when clauses should return true', () => { + it('Selected options containing all when clauses should return true', () => { const whenSelectedTrue: { when: string; selectedOptions: { option: string, value: string }[] }[] = [ { when: 'resourceType=sql-bdc && newType=sql-windows-setup', selectedOptions: [{ option: 'resourceType', value: 'sql-image' }, { option: 'resourceType', value: 'sql-bdc' }, { option: 'newType', value: 'sql-windows-setup' }] @@ -62,16 +62,16 @@ suite('Resource Type Service Tests', function (): void { }); }); - test('When clause that reads "true" (ignoring case) should always return true', () => { + it('When clause that reads "true" (ignoring case) should always return true', () => { should(processWhenClause(undefined, [])).be.true('undefined when clause should always return true'); should(processWhenClause('TrUe', [])).be.true(`"true" when clause should always return true`); }); - test('No selected options returns false', () => { + it('No selected options returns false', () => { should(processWhenClause('newType=empty', [])).be.false('No selected options should return false'); }); - test('Unfulfilled or partially fulfilled when clauses return false', () => { + it('Unfulfilled or partially fulfilled when clauses return false', () => { const whenSelectedFalse: { when: string; selectedOptions: { option: string, value: string }[] }[] = [ { when: 'resourceType=sql-bdc && dneType=does-not-exist', selectedOptions: [{ option: 'resourceType', value: 'sql-image' }, { option: 'resourceType', value: 'sql-bdc' }, { option: 'newType', value: 'sql-windows-setup' }] @@ -85,7 +85,7 @@ suite('Resource Type Service Tests', function (): void { }); }); - test('An invalid when clause should always return false', () => { + it('An invalid when clause should always return false', () => { should(processWhenClause('badWhenClause', [{ option: 'bad', value: 'WhenClause' }])).be.false(`invalid when clause should return false`); }); }); diff --git a/extensions/resource-deployment/src/test/services/toolsService.test.ts b/extensions/resource-deployment/src/test/services/toolsService.test.ts new file mode 100644 index 0000000000..41b4f45b3d --- /dev/null +++ b/extensions/resource-deployment/src/test/services/toolsService.test.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as TypeMoq from 'typemoq'; +import { ITool, ToolType } from '../../interfaces'; +import { IPlatformService } from '../../services/platformService'; +import { ToolsService } from '../../services/toolsService'; + + +const tools: { name: string; type: ToolType }[] = [ + { name: 'azure-cli', type: ToolType.AzCli }, + { name: 'docker', type: ToolType.Docker }, + { name: 'kubectl', type: ToolType.KubeCtl }, + { name: 'azdata', type: ToolType.Azdata } +]; +const mockPlatformService = TypeMoq.Mock.ofType(); +const toolsService = new ToolsService(mockPlatformService.object); + +describe('Tools Service Tests', function (): void { + + it('run getToolByName with all known values', () => { + const missingTypes: string[] = []; + // Make sure all the enum values are covered + for (const type in ToolType) { + if (typeof ToolType[type] === 'number') { + if (tools.findIndex(element => element.type === parseInt(ToolType[type])) === -1) { + missingTypes.push(type); + } + } + } + (missingTypes.length === 0).should.be.true(`the following enum values are not included in the test:${missingTypes.join(',')}`); + + tools.forEach(toolInfo => { + const tool = toolsService.getToolByName(toolInfo.name); + (!!tool).should.be.true(`The tool: ${toolInfo.name} is not recognized`); + (tool!.type).should.equal(toolInfo.type, 'returned notebook name does not match expected value'); + }); + }); + + it('run getToolByName with a name that is not defined', () => { + const mockPlatformService = TypeMoq.Mock.ofType(); + const toolsService = new ToolsService(mockPlatformService.object); + const tool = toolsService.getToolByName('no-such-tool'); + (tool === undefined).should.be.true('for a not defined tool, expected value is undefined'); + }); + + it('get/set tools for CurrentProvider', () => { + const iTools: ITool[] = tools.map(toolInfo => { + const tool = toolsService.getToolByName(toolInfo.name); + (!!tool).should.be.true(`The tool: ${toolInfo.name} is not recognized`); + tool!.type.should.equal(toolInfo.type, 'returned notebook name does not match expected value'); + return tool!; + }); + toolsService.toolsForCurrentProvider = iTools; + iTools.should.deepEqual(toolsService.toolsForCurrentProvider, 'toolsForCurrentProvider did not return the value we set'); + }); +}); diff --git a/extensions/resource-deployment/src/test/toolsService.test.ts b/extensions/resource-deployment/src/test/toolsService.test.ts deleted file mode 100644 index e8a5b5f142..0000000000 --- a/extensions/resource-deployment/src/test/toolsService.test.ts +++ /dev/null @@ -1,51 +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 'mocha'; -import assert = require('assert'); -import * as TypeMoq from 'typemoq'; -import { ToolsService } from '../services/toolsService'; -import { ToolType } from '../interfaces'; -import { isNumber } from 'util'; -import { IPlatformService } from '../services/platformService'; - -suite('Tools Service Tests', function (): void { - - test('run getToolByName with all known values', () => { - const mockPlatformService = TypeMoq.Mock.ofType(); - const toolsService = new ToolsService(mockPlatformService.object); - - const tools: { name: string; type: ToolType }[] = [ - { name: 'azure-cli', type: ToolType.AzCli }, - { name: 'docker', type: ToolType.Docker }, - { name: 'kubectl', type: ToolType.KubeCtl }, - { name: 'azdata', type: ToolType.Azdata }]; - - const missingTypes: string[] = []; - - // Make sure all the enum values are covered - for (const type in ToolType) { - if (isNumber(ToolType[type])) { - if (tools.findIndex(element => element.type === parseInt(ToolType[type])) === -1) { - missingTypes.push(type); - } - } - } - assert(missingTypes.length === 0, `the following enum values are not included in the test:${missingTypes.join(',')}`); - - tools.forEach(toolInfo => { - const tool = toolsService.getToolByName(toolInfo.name); - assert(!!tool, `The tool: ${toolInfo.name} is not recognized`); - assert.equal(tool!.type, toolInfo.type, 'returned notebook name does not match expected value'); - }); - }); - - test('run getToolByName with a name that is not defined', () => { - const mockPlatformService = TypeMoq.Mock.ofType(); - const toolsService = new ToolsService(mockPlatformService.object); - const tool = toolsService.getToolByName('no-such-tool'); - assert(tool === undefined, 'for a not defined tool, expected value is undefined'); - }); -}); diff --git a/extensions/resource-deployment/src/test/ui/validation/validations.test.ts b/extensions/resource-deployment/src/test/ui/validation/validations.test.ts index 882d6e3016..107a2e627f 100644 --- a/extensions/resource-deployment/src/test/ui/validation/validations.test.ts +++ b/extensions/resource-deployment/src/test/ui/validation/validations.test.ts @@ -35,14 +35,14 @@ const testValidations = [ } ]; -suite('Validation', () => { - suite('createValidation and validate input Box', () => { - setup(() => { +describe('Validation', () => { + describe('createValidation and validate input Box', () => { + beforeEach(() => { sinon.restore(); //cleanup all previously defined sinon mocks inputBoxStub = sinon.stub(inputBox, 'updateProperty').resolves(); }); testValidations.forEach(testObj => { - test(`validationType: ${testObj.type}`, async () => { + it(`validationType: ${testObj.type}`, async () => { const validation = createValidation(testObj, async () => undefined, async (_varName: string) => undefined); switch (testObj.type) { case ValidationType.IsInteger: should(validation).be.instanceOf(IntegerValidation); break; @@ -59,7 +59,7 @@ suite('Validation', () => { }); }); - suite('IntegerValidation', () => { + describe('IntegerValidation', () => { // all the below test values are arbitrary representative values or sentinel values for integer validation [ { value: '342520596781', expected: true }, @@ -72,7 +72,7 @@ suite('Validation', () => { { value: NaN, expected: false }, ].forEach((testObj) => { const displayTestValue = getDisplayString(testObj.value); - test(`testValue:${displayTestValue}`, async () => { + it(`testValue:${displayTestValue}`, async () => { const validationDescription = `value: ${displayTestValue} was not an integer`; const validation = new IntegerValidation( { type: ValidationType.IsInteger, description: validationDescription }, @@ -83,7 +83,7 @@ suite('Validation', () => { }); }); - suite('RegexValidation', () => { + describe('RegexValidation', () => { const testRegex = '^[0-9]+$'; // tests [ @@ -97,7 +97,7 @@ suite('Validation', () => { { value: undefined, expected: false }, ].forEach(testOb => { const displayTestValue = getDisplayString(testOb.value); - test(`regex: /${testRegex}/, testValue:${displayTestValue}, expect result: ${testOb.expected}`, async () => { + it(`regex: /${testRegex}/, testValue:${displayTestValue}, expect result: ${testOb.expected}`, async () => { const validationDescription = `value:${displayTestValue} did not match the regex:/${testRegex}/`; const validation = new RegexValidation( { type: ValidationType.IsInteger, description: validationDescription, regex: testRegex }, @@ -108,7 +108,7 @@ suite('Validation', () => { }); }); - suite('LessThanOrEqualsValidation', () => { + describe('LessThanOrEqualsValidation', () => { const targetVariableName = 'comparisonTarget'; // tests - when operands are mix of string and number then number comparison is performed [ @@ -147,7 +147,7 @@ suite('Validation', () => { ].forEach(testObj => { const displayTestValue = getDisplayString(testObj.value); const displayTargetValue = getDisplayString(testObj.targetValue); - test(`testValue:${displayTestValue}, targetValue:${displayTargetValue}`, async () => { + it(`testValue:${displayTestValue}, targetValue:${displayTargetValue}`, async () => { const validationDescription = `${displayTestValue} did not test as <= ${displayTargetValue}`; const validation = new LessThanOrEqualsValidation( { type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName }, @@ -159,7 +159,7 @@ suite('Validation', () => { }); }); - suite('GreaterThanOrEqualsValidation', () => { + describe('GreaterThanOrEqualsValidation', () => { const targetVariableName = 'comparisonTarget'; // tests - when operands are mix of string and number then number comparison is performed [ @@ -190,7 +190,7 @@ suite('Validation', () => { ].forEach(testObj => { const displayTestValue = getDisplayString(testObj.value); const displayTargetValue = getDisplayString(testObj.targetValue); - test(`testValue:${displayTestValue}, targetValue:${displayTargetValue}`, async () => { + it(`testValue:${displayTestValue}, targetValue:${displayTargetValue}`, async () => { const validationDescription = `${displayTestValue} did not test as >= ${displayTargetValue}`; const validation = new GreaterThanOrEqualsValidation( { type: ValidationType.IsInteger, description: validationDescription, target: targetVariableName },