mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-05 01:25:38 -05:00
Add azcli extension (#16029)
This commit is contained in:
121
extensions/azcli/src/test/api.test.ts
Normal file
121
extensions/azcli/src/test/api.test.ts
Normal 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');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
805
extensions/azcli/src/test/azdata.test.ts
Normal file
805
extensions/azcli/src/test/azdata.test.ts
Normal 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();
|
||||
}
|
||||
75
extensions/azcli/src/test/azdataReleaseInfo.test.ts
Normal file
75
extensions/azcli/src/test/azdataReleaseInfo.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
68
extensions/azcli/src/test/common/childProcess.test.ts
Normal file
68
extensions/azcli/src/test/common/childProcess.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
100
extensions/azcli/src/test/common/httpClient.test.ts
Normal file
100
extensions/azcli/src/test/common/httpClient.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
30
extensions/azcli/src/test/common/promise.test.ts
Normal file
30
extensions/azcli/src/test/common/promise.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
26
extensions/azcli/src/test/common/utils.test.ts
Normal file
26
extensions/azcli/src/test/common/utils.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
48
extensions/azcli/src/test/index.ts
Normal file
48
extensions/azcli/src/test/index.ts
Normal 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;
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
17
extensions/azcli/src/test/services/azdataToolService.test.ts
Normal file
17
extensions/azcli/src/test/services/azdataToolService.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
20
extensions/azcli/src/test/testUtils.ts
Normal file
20
extensions/azcli/src/test/testUtils.ts
Normal 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user