Add azcli extension (#16029)

This commit is contained in:
Charles Gagnon
2021-07-07 13:00:12 -07:00
committed by GitHub
parent 6078e9f459
commit d942799f9d
39 changed files with 4797 additions and 1 deletions

View File

@@ -0,0 +1,121 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdataExt from 'azdata-ext';
import * as childProcess from '../common/childProcess';
import * as sinon from 'sinon';
import * as should from 'should';
import * as vscode from 'vscode';
import * as TypeMoq from 'typemoq';
import { getExtensionApi, throwIfNoAzdataOrEulaNotAccepted } from '../api';
import { AzdataToolService } from '../services/azdataToolService';
import { assertRejected } from './testUtils';
import { AzdataTool, IAzdataTool, AzdataDeployOption } from '../azdata';
describe('api', function (): void {
afterEach(function (): void {
sinon.restore();
});
describe('throwIfNoAzdataOrEulaNotAccepted', function (): void {
it('throws when no azdata', function (): void {
should(() => throwIfNoAzdataOrEulaNotAccepted(undefined, false)).throw();
});
it('throws when EULA not accepted', function (): void {
should(() => throwIfNoAzdataOrEulaNotAccepted(TypeMoq.Mock.ofType<IAzdataTool>().object, false)).throw();
});
it('passes with AzdataTool and EULA accepted', function (): void {
throwIfNoAzdataOrEulaNotAccepted(TypeMoq.Mock.ofType<IAzdataTool>().object, true);
});
});
describe('getExtensionApi', function (): void {
it('throws when no azdata', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const azdataToolService = new AzdataToolService();
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(undefined));
await assertRejected(api.isEulaAccepted(), 'isEulaAccepted');
await assertApiCalls(api, assertRejected);
});
it('throws when EULA not accepted', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => false);
const azdataToolService = new AzdataToolService();
// Not using a mock here because it'll hang when resolving mocked objects
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(new AzdataTool('', '1.0.0')));
should(await api.isEulaAccepted()).be.false('EULA should not be accepted');
await assertApiCalls(api, assertRejected);
});
it('succeed when azdata present and EULA accepted', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
const azdataTool = new AzdataTool('', '99.0.0');
const azdataToolService = new AzdataToolService();
azdataToolService.localAzdata = azdataTool;
// Not using a mock here because it'll hang when resolving mocked objects
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(azdataTool));
should(await api.isEulaAccepted()).be.true('EULA should be accepted');
sinon.stub(childProcess, 'executeCommand').callsFake(async (_command, args) => {
// Version needs to be valid so it can be parsed correctly
if (args[0] === '--version') {
return { stdout: `99.0.0`, stderr: '' };
}
console.log(args[0]);
return { stdout: `{ }`, stderr: '' };
});
await assertApiCalls(api, async (promise, message) => {
try {
await promise;
} catch (err) {
throw new Error(`API call to ${message} should have succeeded. ${err}`);
}
});
});
it('promptForEula', async function (): Promise<void> {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
const azdataToolService = new AzdataToolService();
// Not using a mock here because it'll hang when resolving mocked objects
const api = getExtensionApi(mementoMock.object, azdataToolService, Promise.resolve(new AzdataTool('', '1.0.0')));
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const showErrorMessageStub = sinon.stub(vscode.window, 'showErrorMessage');
should(await api.promptForEula()).be.false();
should(showErrorMessageStub.called).be.true('User should have been prompted to accept');
});
/**
* Asserts that calls to the Azdata API behave as expected
* @param api The API object to test the calls with
* @param assertCallback The function to assert that the results are as expected
*/
async function assertApiCalls(api: azdataExt.IExtension, assertCallback: (promise: Promise<any>, message: string) => Promise<void>): Promise<void> {
await assertCallback(api.azdata.getPath(), 'getPath');
await assertCallback(api.azdata.getSemVersion(), 'getSemVersion');
await assertCallback(api.azdata.login({ endpoint: 'https://127.0.0.1' }, '', ''), 'login');
await assertCallback(api.azdata.login({ namespace: 'namespace' }, '', ''), 'login');
await assertCallback(api.azdata.version(), 'version');
await assertCallback(api.azdata.arc.dc.create('', '', '', '', '', ''), 'arc dc create');
await assertCallback(api.azdata.arc.dc.config.list(), 'arc dc config list');
await assertCallback(api.azdata.arc.dc.config.show(), 'arc dc config show');
await assertCallback(api.azdata.arc.dc.endpoint.list(), 'arc dc endpoint list');
await assertCallback(api.azdata.arc.sql.mi.list(), 'arc sql mi list');
await assertCallback(api.azdata.arc.sql.mi.delete(''), 'arc sql mi delete');
await assertCallback(api.azdata.arc.sql.mi.show(''), 'arc sql mi show');
await assertCallback(api.azdata.arc.sql.mi.edit('', {}), 'arc sql mi edit');
await assertCallback(api.azdata.arc.postgres.server.list(), 'arc sql postgres server list');
await assertCallback(api.azdata.arc.postgres.server.delete(''), 'arc sql postgres server delete');
await assertCallback(api.azdata.arc.postgres.server.show(''), 'arc sql postgres server show');
await assertCallback(api.azdata.arc.postgres.server.edit('', {}), 'arc sql postgres server edit');
}
});
});

View File

@@ -0,0 +1,805 @@
/*---------------------------------------------------------------------------------------------
* 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 * as vscode from 'vscode';
import * as azdata from '../azdata';
import * as childProcess from '../common/childProcess';
import { HttpClient } from '../common/httpClient';
import * as utils from '../common/utils';
import * as loc from '../localizedConstants';
import * as os from 'os';
import * as fs from 'fs';
import { AzdataReleaseInfo } from '../azdataReleaseInfo';
import * as TypeMoq from 'typemoq';
import { eulaAccepted } from '../constants';
const oldAzdataMock = new azdata.AzdataTool('/path/to/azdata', azdata.MIN_AZDATA_VERSION.raw);
const currentAzdataMock = new azdata.AzdataTool('/path/to/azdata', '9999.999.999');
/**
* This matches the schema of the JSON file used to determine the current version of
* azdata - do not modify unless also updating the corresponding JSON file
*/
const releaseJson: AzdataReleaseInfo = {
win32: {
'version': '9999.999.999',
'link': 'https://download.com/azdata-20.0.1.msi'
},
darwin: {
'version': '9999.999.999'
},
linux: {
'version': '9999.999.999'
}
};
let executeSudoCommandStub: sinon.SinonStub;
describe('azdata', function () {
afterEach(function (): void {
sinon.restore();
});
describe('azdataTool', function (): void {
const azdataTool = new azdata.AzdataTool(os.tmpdir(), '1.0.0');
let executeCommandStub: sinon.SinonStub;
const namespace = 'myNamespace';
const name = 'myName';
const connectivityMode = 'myConnectivityMode';
const resourceGroup = 'myResourceGroup';
const location = 'myLocation';
const subscription = 'mySubscription';
const profileName = 'myProfileName';
const storageClass = 'myStorageClass';
beforeEach(function (): void {
executeCommandStub = sinon.stub(childProcess, 'executeCommand').resolves({ stdout: '{}', stderr: '' });
});
describe('arc', function (): void {
describe('dc', function (): void {
it('create', async function (): Promise<void> {
await azdataTool.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass);
verifyExecuteCommandCalledWithArgs([
'arc', 'dc', 'create',
namespace,
name,
connectivityMode,
resourceGroup,
location,
subscription,
profileName,
storageClass]);
});
describe('endpoint', async function (): Promise<void> {
it('list', async function (): Promise<void> {
await azdataTool.arc.dc.endpoint.list();
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'endpoint', 'list']);
});
});
describe('config', async function (): Promise<void> {
it('list', async function (): Promise<void> {
await azdataTool.arc.dc.config.list();
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'list']);
});
it('show', async function (): Promise<void> {
await azdataTool.arc.dc.config.show();
verifyExecuteCommandCalledWithArgs(['arc', 'dc', 'config', 'show']);
});
});
});
describe('postgres', function (): void {
describe('server', function (): void {
it('delete', async function (): Promise<void> {
await azdataTool.arc.postgres.server.delete(name);
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'delete', name]);
});
it('list', async function (): Promise<void> {
await azdataTool.arc.postgres.server.list();
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'list']);
});
it('show', async function (): Promise<void> {
await azdataTool.arc.postgres.server.show(name);
verifyExecuteCommandCalledWithArgs(['arc', 'postgres', 'server', 'show', name]);
});
it('edit', async function (): Promise<void> {
const args = {
adminPassword: true,
coresLimit: 'myCoresLimit',
coresRequest: 'myCoresRequest',
engineSettings: 'myEngineSettings',
extensions: 'myExtensions',
memoryLimit: 'myMemoryLimit',
memoryRequest: 'myMemoryRequest',
noWait: true,
port: 1337,
replaceEngineSettings: true,
workers: 2
};
await azdataTool.arc.postgres.server.edit(name, args);
verifyExecuteCommandCalledWithArgs([
'arc', 'postgres', 'server', 'edit',
name,
'--admin-password',
args.coresLimit,
args.coresRequest,
args.engineSettings,
args.extensions,
args.memoryLimit,
args.memoryRequest,
'--no-wait',
args.port.toString(),
'--replace-engine-settings',
args.workers.toString()]);
});
it('edit no optional args', async function (): Promise<void> {
await azdataTool.arc.postgres.server.edit(name, {});
verifyExecuteCommandCalledWithArgs([
'arc', 'postgres', 'server', 'edit',
name]);
verifyExecuteCommandCalledWithoutArgs([
'--admin-password',
'--cores-limit',
'--cores-request',
'--engine-settings',
'--extensions',
'--memory-limit',
'--memory-request',
'--no-wait',
'--port',
'--replace-engine-settings',
'--workers']);
});
});
});
describe('sql', function (): void {
describe('mi', function (): void {
it('delete', async function (): Promise<void> {
await azdataTool.arc.sql.mi.delete(name);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'delete', name]);
});
it('list', async function (): Promise<void> {
await azdataTool.arc.sql.mi.list();
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'list']);
});
it('show', async function (): Promise<void> {
await azdataTool.arc.sql.mi.show(name);
verifyExecuteCommandCalledWithArgs(['arc', 'sql', 'mi', 'show', name]);
});
});
});
it('general error throws', async function (): Promise<void> {
const err = new Error();
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error).equal(err);
}
});
it('ExitCodeError handled and parsed correctly', async function (): Promise<void> {
const errorInnerText = 'my error text';
const err = new childProcess.ExitCodeError(1, `ERROR { "stderr": "${errorInnerText}"}`);
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error).equal(err);
should((error as childProcess.ExitCodeError).stderr).equal(errorInnerText);
}
});
it('ExitCodeError general error with azdata tool existing rethrows original error', async function (): Promise<void> {
sinon.stub(fs.promises, 'access').resolves();
const err = new childProcess.ExitCodeError(1, 'some other error');
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error).equal(err);
}
});
it('ExitCodeError general error with azdata tool not existing throws NoAzdataError', async function (): Promise<void> {
sinon.stub(fs.promises, 'access').throws(new Error('not found'));
const err = new childProcess.ExitCodeError(1, 'some other error');
executeCommandStub.throws(err);
try {
await azdataTool.arc.dc.endpoint.list();
throw new Error('command should have failed');
} catch (error) {
should(error instanceof utils.NoAzdataError).be.true('error should have been instance of NoAzdataError');
}
});
});
it('login', async function (): Promise<void> {
const endpoint = 'myEndpoint';
const username = 'myUsername';
const password = 'myPassword';
await azdataTool.login({ endpoint: endpoint }, username, password);
verifyExecuteCommandCalledWithArgs(['login', endpoint, username]);
});
it('version', async function (): Promise<void> {
executeCommandStub.resolves({ stdout: '1.0.0', stderr: '' });
await azdataTool.version();
verifyExecuteCommandCalledWithArgs(['--version']);
});
/**
* Verifies that the specified args were included in the call to executeCommand
* @param args The args to check were included in the execute command call
*/
function verifyExecuteCommandCalledWithArgs(args: string[], callIndex = 0): void {
const commandArgs = executeCommandStub.args[callIndex][1] as string[];
args.forEach(arg => should(commandArgs).containEql(arg));
}
/**
* Verifies that the specified args weren't included in the call to executeCommand
* @param args The args to check weren't included in the execute command call
*/
function verifyExecuteCommandCalledWithoutArgs(args: string[]): void {
const commandArgs = executeCommandStub.args[0][1] as string[];
args.forEach(arg => should(commandArgs).not.containEql(arg));
}
});
describe('findAzdata', function (): void {
it('successful', async function (): Promise<void> {
// Mock searchForCmd to return a path to azdata.cmd
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
// Mock call to --version to simulate azdata being installed
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '1.0.0', stderr: '' }));
await should(azdata.findAzdata()).not.be.rejected();
});
it('unsuccessful', async function (): Promise<void> {
if (process.platform === 'win32') {
// Mock searchForCmd to return a failure to find azdata.cmd
sinon.stub(utils, 'searchForCmd').returns(Promise.reject(new Error('Could not find azdata')));
} else {
// Mock call to executeCommand to simulate azdata --version returning error
sinon.stub(childProcess, 'executeCommand').returns(Promise.reject({ stdout: '', stderr: 'command not found: azdata' }));
}
await should(azdata.findAzdata()).be.rejected();
});
});
describe('installAzdata', function (): void {
let errorMessageStub: sinon.SinonStub;
beforeEach(function (): void {
errorMessageStub = sinon.stub(vscode.window, 'showErrorMessage').returns(Promise.resolve(<any>loc.yes));
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('/path/to/azdata'));
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
});
it('successful install', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32SuccessfulInstall();
break;
case 'darwin':
await testDarwinSuccessfulInstall();
break;
case 'linux':
await testLinuxSuccessfulInstall();
break;
}
});
it('skipped install - dont prompt config', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => 'dontPrompt');
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
switch (process.platform) {
case 'win32':
await testWin32SkippedInstall();
break;
case 'darwin':
await testDarwinSkippedInstall();
break;
case 'linux':
await testLinuxSkippedInstall();
break;
}
});
it('skipped install - user chose not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
errorMessageStub.resolves(<any>loc.doNotAskAgain);
switch (process.platform) {
case 'win32':
await testWin32SkippedInstall();
break;
case 'darwin':
await testDarwinSkippedInstall();
break;
case 'linux':
await testLinuxSkippedInstall();
break;
}
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
if (process.platform === 'win32') {
it('unsuccessful download - win32', async function (): Promise<void> {
sinon.stub(HttpClient, 'downloadFile').rejects();
sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found')) // First call mock the tool not being found
.resolves({ stdout: '1.0.0', stderr: '' });
const azdataTool = await azdata.checkAndInstallAzdata();
should(azdataTool).be.undefined();
});
}
it('unsuccessful install', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32UnsuccessfulInstall();
break;
case 'darwin':
await testDarwinUnsuccessfulInstall();
break;
case 'linux':
await testLinuxUnsuccessfulInstall();
break;
}
});
});
describe('updateAzdata', function (): void {
let showInformationMessageStub: sinon.SinonStub;
beforeEach(function (): void {
showInformationMessageStub = sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(releaseJson));
});
it('successful update', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32SuccessfulUpdate();
break;
case 'darwin':
await testDarwinSuccessfulUpdate();
break;
case 'linux':
await testLinuxSuccessfulUpdate();
break;
}
});
it('successful update - always prompt if user requested', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
switch (process.platform) {
case 'win32':
await testWin32SuccessfulUpdate(true);
break;
case 'darwin':
await testDarwinSuccessfulUpdate(true);
break;
case 'linux':
await testLinuxSuccessfulUpdate(true);
break;
}
});
it('skipped update - config set not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
switch (process.platform) {
case 'win32':
await testWin32SkippedUpdateDontPrompt();
break;
case 'darwin':
await testDarwinSkippedUpdateDontPrompt();
break;
case 'linux':
await testLinuxSkippedUpdateDontPrompt();
break;
}
});
it('skipped update - user chose to never prompt again', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
showInformationMessageStub.resolves(<any>loc.doNotAskAgain);
switch (process.platform) {
case 'win32':
await testWin32SkippedUpdateDontPrompt();
break;
case 'darwin':
await testDarwinSkippedUpdateDontPrompt();
break;
case 'linux':
await testLinuxSkippedUpdateDontPrompt();
break;
}
// Config should have been updated since user chose never to prompt again
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
});
it('skipped update - no new version', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32SkippedUpdate();
break;
case 'darwin':
await testDarwinSkippedUpdate();
break;
case 'linux':
await testLinuxSkippedUpdate();
break;
}
});
it('skipped update - no azdata', async function (): Promise<void> {
const result = await azdata.checkAndUpdateAzdata();
should(result).be.false();
});
it('unsuccessful update', async function (): Promise<void> {
switch (process.platform) {
case 'win32':
await testWin32UnsuccessfulUpdate();
break;
case 'darwin':
await testDarwinUnsuccessfulUpdate();
break;
case 'linux':
await testLinuxUnsuccessfulUpdate();
}
});
describe('discoverLatestAvailableAzdataVersion', function (): void {
it('finds latest available version of azdata successfully', async function (): Promise<void> {
await azdata.discoverLatestAvailableAzdataVersion();
});
});
});
describe('promptForEula', function (): void {
it('skipped because of config', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const result = await azdata.promptForEula(mementoMock.object);
should(result).be.false();
});
it('always prompt if user requested', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.dontPrompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage');
const result = await azdata.promptForEula(mementoMock.object, true);
should(result).be.false();
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('prompt if config set to do so', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage');
const result = await azdata.promptForEula(mementoMock.object);
should(result).be.false();
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('update config if user chooses not to prompt', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>loc.doNotAskAgain);
const result = await azdata.promptForEula(mementoMock.object);
configMock.verify(x => x.update(TypeMoq.It.isAny(), azdata.AzdataDeployOption.dontPrompt, TypeMoq.It.isAny()), TypeMoq.Times.once());
should(result).be.false('EULA should not have been accepted');
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('user accepted EULA', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showInformationMessage = sinon.stub(vscode.window, 'showInformationMessage').resolves(<any>loc.accept);
const result = await azdata.promptForEula(mementoMock.object);
mementoMock.verify(x => x.update(eulaAccepted, true), TypeMoq.Times.once());
should(result).be.true('EULA should have been accepted');
should(showInformationMessage.calledOnce).be.true('showInformationMessage should have been called to prompt user');
});
it('user accepted EULA - require user action', async function (): Promise<void> {
const configMock = TypeMoq.Mock.ofType<vscode.WorkspaceConfiguration>();
configMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => azdata.AzdataDeployOption.prompt);
sinon.stub(vscode.workspace, 'getConfiguration').returns(configMock.object);
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
const showErrorMessage = sinon.stub(vscode.window, 'showErrorMessage').resolves(<any>loc.accept);
const result = await azdata.promptForEula(mementoMock.object, true, true);
mementoMock.verify(x => x.update(eulaAccepted, true), TypeMoq.Times.once());
should(result).be.true('EULA should have been accepted');
should(showErrorMessage.calledOnce).be.true('showErrorMessage should have been called to prompt user');
});
});
describe('isEulaAccepted', function (): void {
const mementoMock = TypeMoq.Mock.ofType<vscode.Memento>();
mementoMock.setup(x => x.get(TypeMoq.It.isAny())).returns(() => true);
should(azdata.isEulaAccepted(mementoMock.object)).be.true();
});
});
async function testLinuxUnsuccessfulUpdate() {
executeSudoCommandStub.rejects();
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(updateDone).be.false();
should(executeSudoCommandStub.calledOnce).be.true();
}
async function testDarwinUnsuccessfulUpdate() {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.callsFake(async (_command: string, _args: string[]) => {
return Promise.resolve({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
});
})
.onCall(5) //6th call is the first one to do actual update, the call number are 0 indexed
.callsFake(async (_command: string, _args: string[]) => {
return Promise.reject(new Error('not Found'));
})
.callsFake(async (_command: string, _args: string[]) => { // by default return success
return Promise.resolve({ stderr: '', stdout: 'success' });
});
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(updateDone).be.false();
should(executeCommandStub.callCount).equal(6);
}
async function testWin32UnsuccessfulUpdate() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
executeSudoCommandStub.rejects();
const updateDone = await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(updateDone).be.false('Update should not have been successful');
should(executeSudoCommandStub.calledOnce).be.true();
}
async function testLinuxSuccessfulUpdate(userRequested = false) {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
should(executeSudoCommandStub.callCount).be.equal(6);
should(executeCommandStub.calledOnce).be.true();
}
async function testDarwinSuccessfulUpdate(userRequested = false) {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.resolves({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
})
.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
should(executeCommandStub.callCount).be.equal(6);
should(executeCommandStub.getCall(2).args[0]).be.equal('brew', '3rd call should have been to brew');
should(executeCommandStub.getCall(2).args[1]).deepEqual(['info', 'azdata-cli', '--json'], '3rd call did not have expected arguments');
}
async function testWin32SuccessfulUpdate(userRequested = false) {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
await azdata.checkAndUpdateAzdata(oldAzdataMock, userRequested);
should(executeSudoCommandStub.calledOnce).be.true('executeSudoCommand should have been called once');
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
}
async function testLinuxSkippedUpdate() {
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(currentAzdataMock);
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
}
async function testDarwinSkippedUpdateDontPrompt() {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.resolves({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
})
.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(executeCommandStub.callCount).be.equal(6);
should(executeCommandStub.notCalledWith(sinon.match.any, sinon.match.array.contains(['upgrade', 'azdata-cli'])));
}
async function testWin32SkippedUpdateDontPrompt() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(executeSudoCommandStub.notCalled).be.true(`executeSudoCommand should not have been called ${executeSudoCommandStub.getCalls().join(os.EOL)}`);
}
async function testLinuxSkippedUpdateDontPrompt() {
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '0.0.0', stderr: '' }));
executeSudoCommandStub.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(oldAzdataMock);
should(executeSudoCommandStub.callCount).be.equal(0, 'executeSudoCommand was not expected to be called');
}
async function testDarwinSkippedUpdate() {
const brewInfoOutput = [{
name: 'azdata-cli',
full_name: 'microsoft/azdata-cli-release/azdata-cli',
versions: {
'stable': '9999.999.999',
'devel': null,
'head': null,
'bottle': true
}
}];
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onThirdCall() //third call is brew info azdata-cli --json which needs to return json of new available azdata versions.
.resolves({
stderr: '',
stdout: JSON.stringify(brewInfoOutput)
})
.resolves({ stdout: '0.0.0', stderr: '' });
await azdata.checkAndUpdateAzdata(currentAzdataMock);
should(executeCommandStub.callCount).be.equal(6);
should(executeCommandStub.notCalledWith(sinon.match.any, sinon.match.array.contains(['upgrade', 'azdata-cli'])));
}
async function testWin32SkippedUpdate() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
await azdata.checkAndUpdateAzdata(currentAzdataMock);
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
}
async function testDarwinSkippedInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.callsFake(async (_command: string, _args: string[]) => {
return Promise.reject(new Error('not Found'));
})
.callsFake(async (_command: string, _args: string[]) => {
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
});
const result = await azdata.checkAndInstallAzdata();
should(result).equal(undefined, 'result should be undefined');
should(executeCommandStub.callCount).be.equal(0);
}
async function testLinuxSkippedInstall() {
sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found'))
.resolves({ stdout: '0.0.0', stderr: '' });
executeSudoCommandStub
.resolves({ stdout: 'success', stderr: '' });
const result = await azdata.checkAndInstallAzdata();
should(result).equal(undefined, 'result should be undefined');
should(executeSudoCommandStub.callCount).be.equal(0);
}
async function testWin32SkippedInstall() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found')) // First call mock the tool not being found
.resolves({ stdout: '1.0.0', stderr: '' });
executeSudoCommandStub
.returns({ stdout: '', stderr: '' });
const result = await azdata.checkAndInstallAzdata();
should(result).equal(undefined, 'result should be undefined');
should(executeSudoCommandStub.notCalled).be.true('executeSudoCommand should not have been called');
}
async function testWin32SuccessfulInstall() {
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found')) // First call mock the tool not being found
.resolves({ stdout: '1.0.0', stderr: '' });
executeSudoCommandStub
.returns({ stdout: '', stderr: '' });
await azdata.checkAndInstallAzdata();
should(executeCommandStub.calledTwice).be.true(`executeCommand should have been called twice. Actual ${executeCommandStub.getCalls().length}`);
should(executeSudoCommandStub.calledOnce).be.true(`executeSudoCommand should have been called once. Actual ${executeSudoCommandStub.getCalls().length}`);
should(executeSudoCommandStub.getCall(0).args[0]).startWith('msiexec /qn /i');
}
async function testDarwinSuccessfulInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.callsFake(async (_command: string, _args: string[]) => {
return Promise.reject(new Error('not Found'));
})
.callsFake(async (_command: string, _args: string[]) => {
return Promise.resolve({ stdout: '0.0.0', stderr: '' });
});
await azdata.checkAndInstallAzdata();
should(executeCommandStub.callCount).be.equal(5);
}
async function testLinuxSuccessfulInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand')
.onFirstCall()
.rejects(new Error('not Found'))
.resolves({ stdout: '0.0.0', stderr: '' });
executeSudoCommandStub
.resolves({ stdout: 'success', stderr: '' });
await azdata.checkAndInstallAzdata();
should(executeSudoCommandStub.callCount).be.equal(6);
should(executeCommandStub.calledThrice).be.true();
}
async function testLinuxUnsuccessfulInstall() {
executeSudoCommandStub.rejects();
const downloadPromise = azdata.installAzdata();
await should(downloadPromise).be.rejected();
should(executeSudoCommandStub.calledOnce).be.true();
}
async function testDarwinUnsuccessfulInstall() {
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
const downloadPromise = azdata.installAzdata();
await should(downloadPromise).be.rejected();
should(executeCommandStub.calledOnce).be.true();
}
async function testWin32UnsuccessfulInstall() {
executeSudoCommandStub.rejects();
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
const downloadPromise = azdata.installAzdata();
await should(downloadPromise).be.rejected();
should(executeSudoCommandStub.calledOnce).be.true();
}

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* 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 { HttpClient } from '../common/httpClient';
import { getPlatformReleaseVersion, getPlatformDownloadLink, AzdataReleaseInfo } from '../azdataReleaseInfo';
const emptyReleaseJson = {
win32: {},
darwin: {},
linux: {}
};
const releaseVersion = '999.999.999';
const releaseLink = 'https://microsoft.com';
const validReleaseJson: AzdataReleaseInfo = {
win32: {
version: releaseVersion,
link: releaseLink
},
darwin: {
version: releaseVersion,
link: releaseLink
},
linux: {
version: releaseVersion,
link: releaseLink
}
};
describe('azdataReleaseInfo', function (): void {
afterEach(function (): void {
sinon.restore();
});
describe('getPlatformReleaseVersion', function(): void {
it('gets version successfully', async function(): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(validReleaseJson));
const version = await getPlatformReleaseVersion();
should(version.format()).equal(releaseVersion);
});
it('throws with invalid JSON', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves('invalid JSON');
await should(getPlatformReleaseVersion()).be.rejected();
});
it('throws when no version', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(emptyReleaseJson));
await should(getPlatformReleaseVersion()).be.rejected();
});
});
describe('getPlatformDownloadLink', function(): void {
it('gets link successfully', async function(): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(validReleaseJson));
const link = await getPlatformDownloadLink();
should(link).equal(releaseLink);
});
it('throws with invalid JSON', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves('invalid JSON');
await should(getPlatformDownloadLink()).be.rejected();
});
it('throws when no version', async function (): Promise<void> {
sinon.stub(HttpClient, 'getTextContent').resolves(JSON.stringify(emptyReleaseJson));
await should(getPlatformDownloadLink()).be.rejected();
});
});
});

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* 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 sudo from 'sudo-prompt';
import * as sinon from 'sinon';
import Logger from '../../common/logger';
import { executeCommand, executeSudoCommand } from '../../common/childProcess';
describe('ChildProcess', function (): void {
afterEach(function(): void {
sinon.restore();
});
describe('executeCommand', function(): void {
[[], ['test']].forEach(args => {
it(`Output channel is used with ${JSON.stringify(args)} args`, async function (): Promise<void> {
const logStub = sinon.stub(Logger, 'log');
await executeCommand('echo', args);
should(logStub.called).be.true('Log should have been called');
});
});
it('Gets expected output', async function (): Promise<void> {
const echoOutput = 'test';
const output = await executeCommand('echo', [echoOutput]);
should(output.stdout).equal(echoOutput);
});
it('Invalid command errors', async function (): Promise<void> {
await should(executeCommand('invalid_command', [])).be.rejected();
});
});
describe('executeSudoCommand', function(): void {
it('Gets expected stdout output', async function (): Promise<void> {
const stdout = 'stdout output';
sinon.stub(sudo, 'exec').callsFake( (_cmd, _options, callback) => {
callback!(undefined, stdout);
});
const result = await executeSudoCommand('echo');
should(result.stdout).equal(stdout);
should(result.stderr).equal('');
});
it('Gets expected stderr output', async function (): Promise<void> {
const stderr = 'stderr output';
sinon.stub(sudo, 'exec').callsFake( (_cmd, _options, callback) => {
callback!(undefined, undefined, stderr);
});
const result = await executeSudoCommand('echo');
should(result.stdout).equal('');
should(result.stderr).equal(stderr);
});
it('Error rejects', async function (): Promise<void> {
sinon.stub(sudo, 'exec').callsFake( (_cmd, _options, callback) => {
callback!(new Error('error'));
});
await should(executeSudoCommand('invalid_command')).be.rejected();
});
});
});

View File

@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as nock from 'nock';
import * as os from 'os';
import * as should from 'should';
import * as sinon from 'sinon';
import { PassThrough } from 'stream';
import { HttpClient } from '../../common/httpClient';
import { Deferred } from '../../common/promise';
describe('HttpClient', function (): void {
afterEach(function (): void {
nock.cleanAll();
nock.enableNetConnect();
sinon.restore();
});
describe('downloadFile', function (): void {
it('downloads file successfully', async function (): Promise<void> {
nock('https://127.0.0.1')
.get('/README.md')
.replyWithFile(200, __filename);
const downloadFolder = os.tmpdir();
const downloadPath = await HttpClient.downloadFile('https://127.0.0.1/README.md', downloadFolder);
// Verify file was downloaded correctly
await fs.promises.stat(downloadPath);
});
it('errors on response stream error', async function (): Promise<void> {
const downloadFolder = os.tmpdir();
nock('https://127.0.0.1')
.get('/')
.replyWithError('Unexpected Error');
const downloadPromise = HttpClient.downloadFile('https://127.0.0.1', downloadFolder);
await should(downloadPromise).be.rejected();
});
it('rejects on non-OK status code', async function (): Promise<void> {
const downloadFolder = os.tmpdir();
nock('https://127.0.0.1')
.get('/')
.reply(404, '');
const downloadPromise = HttpClient.downloadFile('https://127.0.0.1', downloadFolder);
await should(downloadPromise).be.rejected();
});
it('errors on write stream error', async function (): Promise<void> {
const downloadFolder = os.tmpdir();
const mockWriteStream = new PassThrough();
const deferredPromise = new Deferred();
sinon.stub(fs, 'createWriteStream').callsFake(() => {
deferredPromise.resolve();
return <any>mockWriteStream;
});
nock('https://127.0.0.1')
.get('/')
.reply(200, '');
const downloadPromise = HttpClient.downloadFile('https://127.0.0.1', downloadFolder);
// Wait for the stream to be created before throwing the error or HttpClient will miss the event
await deferredPromise;
try {
// Passthrough streams will throw the error we emit so just no-op and
// let the HttpClient handler handle the error
mockWriteStream.emit('error', 'Unexpected write error');
} catch (err) { }
await should(downloadPromise).be.rejected();
});
});
describe('getTextContent', function (): void {
it('Gets file contents correctly', async function (): Promise<void> {
nock('https://127.0.0.1')
.get('/arbitraryFile')
.replyWithFile(200, __filename);
const receivedContents = await HttpClient.getTextContent(`https://127.0.0.1/arbitraryFile`);
should(receivedContents).equal((await fs.promises.readFile(__filename)).toString());
});
it('rejects on response error', async function (): Promise<void> {
nock('https://127.0.0.1')
.get('/')
.replyWithError('Unexpected Error');
const getFileContentsPromise = HttpClient.getTextContent('https://127.0.0.1/', );
await should(getFileContentsPromise).be.rejected();
});
it('rejects on non-OK status code', async function (): Promise<void> {
nock('https://127.0.0.1')
.get('/')
.reply(404, '');
const getFileContentsPromise = HttpClient.getTextContent('https://127.0.0.1/', );
await should(getFileContentsPromise).be.rejected();
});
});
});

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* 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 { Deferred } from '../../common/promise';
describe('DeferredPromise', function (): void {
it('Resolves correctly', async function(): Promise<void> {
const deferred = new Deferred();
deferred.resolve();
await should(deferred.promise).be.resolved();
});
it('Rejects correctly', async function(): Promise<void> {
const deferred = new Deferred();
deferred.reject();
await should(deferred.promise).be.rejected();
});
it('Chains then correctly', function(done): void {
const deferred = new Deferred();
deferred.then( () => {
done();
});
deferred.resolve();
});
});

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* 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 { NoAzdataError, searchForCmd as searchForExe } from '../../common/utils';
describe('utils', function () {
describe('searchForExe', function (): void {
it('finds exe successfully', async function (): Promise<void> {
await searchForExe('node');
});
it('throws for non-existent exe', async function (): Promise<void> {
await should(searchForExe('someFakeExe')).be.rejected();
});
});
describe('NoAzdataError', function (): void {
it('error contains message with and without links', function (): void {
const error = new NoAzdataError();
should(error.message).not.be.empty();
should(error.messageWithLink).not.be.empty();
should(error.message).not.equal(error.messageWithLink, 'Messages should not be equal');
});
});
});

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
const testRunner = require('vscodetestcover');
const suite = 'azdata Extension Tests';
const mochaOptions: any = {
ui: 'bdd',
useColors: true,
timeout: 10000
};
// set relevant mocha options from the environment
if (process.env.ADS_TEST_GREP) {
mochaOptions.grep = process.env.ADS_TEST_GREP;
console.log(`setting options.grep to: ${mochaOptions.grep}`);
}
if (process.env.ADS_TEST_INVERT_GREP) {
mochaOptions.invert = parseInt(process.env.ADS_TEST_INVERT_GREP);
console.log(`setting options.invert to: ${mochaOptions.invert}`);
}
if (process.env.ADS_TEST_TIMEOUT) {
mochaOptions.timeout = parseInt(process.env.ADS_TEST_TIMEOUT);
console.log(`setting options.timeout to: ${mochaOptions.timeout}`);
}
if (process.env.ADS_TEST_RETRIES) {
mochaOptions.retries = parseInt(process.env.ADS_TEST_RETRIES);
console.log(`setting options.retries to: ${mochaOptions.retries}`);
}
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
mochaOptions.reporter = 'mocha-multi-reporters';
mochaOptions.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(mochaOptions, { coverConfig: '../../coverConfig.json' });
export = testRunner;

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* 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 azdataExt from 'azdata-ext';
import * as should from 'should';
import * as sinon from 'sinon';
import { ArcControllerConfigProfilesOptionsSource } from '../../providers/arcControllerConfigProfilesOptionsSource';
describe('arcControllerConfigProfilesOptionsSource', async function (): Promise<void> {
afterEach(function(): void {
sinon.restore();
});
it('eula accepted returns list', async function (): Promise<void> {
const options = ['option1', 'option2'];
const api = vscode.extensions.getExtension(azdataExt.extension.name)?.exports as azdataExt.IExtension;
sinon.stub(api, 'isEulaAccepted').resolves(true);
sinon.stub(api.azdata.arc.dc.config, 'list').resolves({ stdout: [''], stderr: [''], logs: [''], result: options});
const source = new ArcControllerConfigProfilesOptionsSource(api);
const result = await source.getOptions();
should(result).deepEqual(options);
});
it('eula not accepted prompts for acceptance', async function (): Promise<void> {
const options = ['option1', 'option2'];
const api = vscode.extensions.getExtension(azdataExt.extension.name)?.exports as azdataExt.IExtension;
sinon.stub(api, 'isEulaAccepted').resolves(false);
const promptStub = sinon.stub(api, 'promptForEula').resolves(true);
sinon.stub(api.azdata.arc.dc.config, 'list').resolves({ stdout: [''], stderr: [''], logs: [''], result: options});
const source = new ArcControllerConfigProfilesOptionsSource(api);
const result = await source.getOptions();
should(result).deepEqual(options);
should(promptStub.calledOnce).be.true('promptForEula should have been called');
});
});

View File

@@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* 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 { AzdataTool } from '../../azdata';
import { AzdataToolService } from '../../services/azdataToolService';
describe('azdataToolService', function (): void {
it('Tool should be set correctly', async function (): Promise<void> {
const service = new AzdataToolService();
should(service.localAzdata).be.undefined();
service.localAzdata = new AzdataTool('my path', '1.0.0');
should(service).not.be.undefined();
});
});

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Asserts that the specified promise was rejected. This is similar to should(..).be.rejected but
* allows specifying a message in the thrown Error to add more information to the failure.
* @param promise The promise to verify was rejected
* @param message The message to include in the error if the promise isn't rejected
*/
export async function assertRejected(promise: Promise<any>, message: string): Promise<void> {
try {
await promise;
} catch {
return;
}
throw new Error(message);
}