mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Changes to discover and perform azdata update (#11906)
* WIP * first version with working tests * fixes needed after merge from main * Linux untest changes and merge from other changes from mac * after testing getTextContent * rename 2 methods * linux discovery * tested code on linux * using release.json for update discovery on linux * comment added * dead code removed * coomments * revert unrelated change * revert testing changes * PR feedback * remove SendOutputChannelToConsole * cleanup * pr feedback * PR Feedback * pr feedback * pr feedback * merge from main * merge from main * cleanup and pr feedback * pr feedback * pr feedback. * pr feedback Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
"main": "./out/extension",
|
||||
"dependencies": {
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.3.2",
|
||||
"sudo-prompt": "^9.2.1",
|
||||
"vscode-nls": "^4.1.2",
|
||||
"which": "^2.0.2"
|
||||
@@ -29,6 +30,7 @@
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^12.11.7",
|
||||
"@types/request": "^2.48.5",
|
||||
"@types/semver": "^7.3.1",
|
||||
"@types/sinon": "^9.0.4",
|
||||
"@types/uuid": "^8.0.0",
|
||||
"@types/which": "^1.3.2",
|
||||
|
||||
@@ -3,21 +3,27 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as vscode from 'vscode';
|
||||
import { HttpClient } from './common/httpClient';
|
||||
import * as loc from './localizedConstants';
|
||||
import { executeCommand, executeSudoCommand, ExitCodeError } from './common/childProcess';
|
||||
import { searchForCmd } from './common/utils';
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import * as os from 'os';
|
||||
import { SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import { executeCommand, executeSudoCommand, ExitCodeError } from './common/childProcess';
|
||||
import { HttpClient } from './common/httpClient';
|
||||
import Logger from './common/logger';
|
||||
import { getErrorMessage, searchForCmd } from './common/utils';
|
||||
import * as loc from './localizedConstants';
|
||||
|
||||
export const azdataHostname = 'https://aka.ms';
|
||||
export const azdataUri = 'azdata-msi';
|
||||
export const azdataReleaseJson = 'azdata/release.json';
|
||||
|
||||
/**
|
||||
* Interface for an object to interact with the azdata tool installed on the box.
|
||||
*/
|
||||
export interface IAzdataTool extends azdataExt.IAzdataApi {
|
||||
path: string,
|
||||
toolVersion: string,
|
||||
cachedVersion: SemVer
|
||||
|
||||
/**
|
||||
* Executes azdata with the specified arguments (e.g. --version) and returns the result
|
||||
* @param args The args to pass to azdata
|
||||
@@ -26,8 +32,14 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
|
||||
executeCommand<R>(args: string[], additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<R>>
|
||||
}
|
||||
|
||||
class AzdataTool implements IAzdataTool {
|
||||
constructor(public path: string, public toolVersion: string) { }
|
||||
/**
|
||||
* An object to interact with the azdata tool installed on the box.
|
||||
*/
|
||||
export class AzdataTool implements IAzdataTool {
|
||||
public cachedVersion: SemVer;
|
||||
constructor(public path: string, version: string) {
|
||||
this.cachedVersion = new SemVer(version);
|
||||
}
|
||||
|
||||
public arc = {
|
||||
dc: {
|
||||
@@ -90,10 +102,19 @@ class AzdataTool implements IAzdataTool {
|
||||
return this.executeCommand<void>(['login', '-e', endpoint, '-u', username], { 'AZDATA_PASSWORD': password });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output of running '--version' command on the azdata tool.
|
||||
* It also updates the cachedVersion property based on the return value from the tool.
|
||||
*/
|
||||
public async version(): Promise<azdataExt.AzdataOutput<string>> {
|
||||
const output = await this.executeCommand<string>(['--version']);
|
||||
this.toolVersion = parseVersion(output.stdout[0]);
|
||||
return output;
|
||||
const output = await executeCommand(`"${this.path}"`, ['--version']);
|
||||
this.cachedVersion = new SemVer(parseVersion(output.stdout));
|
||||
return {
|
||||
logs: [],
|
||||
stdout: output.stdout.split(os.EOL),
|
||||
stderr: output.stderr.split(os.EOL),
|
||||
result: ''
|
||||
};
|
||||
}
|
||||
|
||||
public async executeCommand<R>(args: string[], additionalEnvVars?: { [key: string]: string }): Promise<azdataExt.AzdataOutput<R>> {
|
||||
@@ -117,22 +138,24 @@ class AzdataTool implements IAzdataTool {
|
||||
}
|
||||
}
|
||||
|
||||
export type AzdataDarwinPackageVersionInfo = {
|
||||
versions: {
|
||||
stable: string,
|
||||
devel: string,
|
||||
head: string,
|
||||
bottle: boolean
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Finds the existing installation of azdata, or throws an error if it couldn't find it
|
||||
* or encountered an unexpected error.
|
||||
* The promise is rejected when Azdata is not found.
|
||||
*/
|
||||
export async function findAzdata(): Promise<IAzdataTool> {
|
||||
Logger.log(loc.searchingForAzdata);
|
||||
try {
|
||||
let azdata: IAzdataTool | undefined = undefined;
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
azdata = await findAzdataWin32();
|
||||
break;
|
||||
default:
|
||||
azdata = await findSpecificAzdata('azdata');
|
||||
}
|
||||
Logger.log(loc.foundExistingAzdata(azdata.path, azdata.toolVersion));
|
||||
const azdata = await findSpecificAzdata();
|
||||
Logger.log(loc.foundExistingAzdata(azdata.path, azdata.cachedVersion.raw));
|
||||
return azdata;
|
||||
} catch (err) {
|
||||
Logger.log(loc.couldNotFindAzdata(err));
|
||||
@@ -141,9 +164,9 @@ export async function findAzdata(): Promise<IAzdataTool> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the appropriate installer and/or runs the command to install azdata
|
||||
* runs the commands to install azdata, downloading the installation package if needed
|
||||
*/
|
||||
export async function downloadAndInstallAzdata(): Promise<void> {
|
||||
export async function installAzdata(): Promise<void> {
|
||||
const statusDisposable = vscode.window.setStatusBarMessage(loc.installingAzdata);
|
||||
Logger.show();
|
||||
Logger.log(loc.installingAzdata);
|
||||
@@ -161,17 +184,64 @@ export async function downloadAndInstallAzdata(): Promise<void> {
|
||||
default:
|
||||
throw new Error(loc.platformUnsupported(process.platform));
|
||||
}
|
||||
Logger.log(loc.azdataInstalled);
|
||||
} finally {
|
||||
statusDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrades the azdata using os appropriate method
|
||||
*/
|
||||
export async function upgradeAzdata(): Promise<void> {
|
||||
const statusDisposable = vscode.window.setStatusBarMessage(loc.upgradingAzdata);
|
||||
Logger.show();
|
||||
Logger.log(loc.upgradingAzdata);
|
||||
try {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await downloadAndInstallAzdataWin32();
|
||||
break;
|
||||
case 'darwin':
|
||||
await upgradeAzdataDarwin();
|
||||
break;
|
||||
case 'linux':
|
||||
await installAzdataLinux();
|
||||
break;
|
||||
default:
|
||||
throw new Error(loc.platformUnsupported(process.platform));
|
||||
}
|
||||
Logger.log(loc.azdataUpgraded);
|
||||
} finally {
|
||||
statusDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a newer version of azdata is available - and if it is prompts the user to download and
|
||||
* install it.
|
||||
* @param currentAzdata The current version of azdata to check against
|
||||
*/
|
||||
export async function checkAndUpgradeAzdata(currentAzdata?: IAzdataTool): Promise<void> {
|
||||
if (currentAzdata === undefined) {
|
||||
currentAzdata = await findAzdata();
|
||||
}
|
||||
const newVersion = await discoverLatestAvailableAzdataVersion();
|
||||
if (newVersion.compare(currentAzdata.cachedVersion) === 1) {
|
||||
const response = await vscode.window.showInformationMessage(loc.promptForAzdataUpgrade(newVersion.raw), loc.yes, loc.no);
|
||||
if (response === loc.yes) {
|
||||
await upgradeAzdata();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Downloads the Windows installer and runs it
|
||||
*/
|
||||
async function downloadAndInstallAzdataWin32(): Promise<void> {
|
||||
const downloadFolder = os.tmpdir();
|
||||
const downloadedFile = await HttpClient.download(`${azdataHostname}/${azdataUri}`, downloadFolder);
|
||||
const downloadedFile = await HttpClient.downloadFile(`${azdataHostname}/${azdataUri}`, downloadFolder);
|
||||
await executeCommand('msiexec', ['/qn', '/i', downloadedFile]);
|
||||
}
|
||||
|
||||
@@ -184,6 +254,15 @@ async function installAzdataDarwin(): Promise<void> {
|
||||
await executeCommand('brew', ['install', 'azdata-cli']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs commands to upgrade azdata on MacOS
|
||||
*/
|
||||
async function upgradeAzdataDarwin(): Promise<void> {
|
||||
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
|
||||
await executeCommand('brew', ['update']);
|
||||
await executeCommand('brew', ['upgrade', 'azdata-cli']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs commands to install azdata on Linux
|
||||
*/
|
||||
@@ -203,20 +282,46 @@ async function installAzdataLinux(): Promise<void> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds azdata specifically on Windows
|
||||
*/
|
||||
async function findAzdataWin32(): Promise<IAzdataTool> {
|
||||
const promise = searchForCmd('azdata.cmd');
|
||||
return findSpecificAzdata(await promise);
|
||||
async function findSpecificAzdata(): Promise<IAzdataTool> {
|
||||
const promise = ((process.platform === 'win32') ? searchForCmd('azdata.cmd') : searchForCmd('azdata'));
|
||||
const path = `"${await promise}"`; // throws if azdata is not found
|
||||
const versionOutput = await executeCommand(`"${path}"`, ['--version']);
|
||||
return new AzdataTool(path, parseVersion(versionOutput.stdout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version using a known azdata path
|
||||
* @param path The path to the azdata executable
|
||||
* Gets the latest azdata version available for a given platform
|
||||
*/
|
||||
async function findSpecificAzdata(path: string): Promise<IAzdataTool> {
|
||||
const versionOutput = await executeCommand(`"${path}"`, ['--version']);
|
||||
return new AzdataTool(path, parseVersion(versionOutput.stdout));
|
||||
export async function discoverLatestAvailableAzdataVersion(): Promise<SemVer> {
|
||||
Logger.log(loc.checkingLatestAzdataVersion);
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return await discoverLatestStableAzdataVersionDarwin();
|
||||
// case 'linux':
|
||||
// ideally we would not to discover linux package availability using the apt/apt-get/apt-cache package manager commands.
|
||||
// However, doing discovery that way required apt update to be performed which requires sudo privileges. At least currently this code path
|
||||
// gets invoked on extension start up and prompt user for sudo privileges is annoying at best. So for now basing linux discovery also on a releaseJson file.
|
||||
default:
|
||||
return await discoverLatestAzdataVersionFromJson();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest azdata version from a json document published by azdata release
|
||||
*/
|
||||
async function discoverLatestAzdataVersionFromJson(): Promise<SemVer> {
|
||||
// get version information for current platform from http://aka.ms/azdata/release.json
|
||||
const fileContents = await HttpClient.getTextContent(`${azdataHostname}/${azdataReleaseJson}`);
|
||||
let azdataReleaseInfo;
|
||||
try {
|
||||
azdataReleaseInfo = JSON.parse(fileContents);
|
||||
} catch (e) {
|
||||
throw Error(`failed to parse the JSON of contents at: ${azdataHostname}/${azdataReleaseJson}, text being parsed: '${fileContents}', error:${getErrorMessage(e)}`);
|
||||
}
|
||||
const version = azdataReleaseInfo[process.platform]['version'];
|
||||
Logger.log(loc.foundAzdataVersionToUpgradeTo(version));
|
||||
return new SemVer(version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,3 +334,38 @@ function parseVersion(raw: string): string {
|
||||
const lines = raw.split(os.EOL);
|
||||
return lines[0].trim();
|
||||
}
|
||||
/**
|
||||
* Gets the latest azdata version for MacOs clients
|
||||
*/
|
||||
async function discoverLatestStableAzdataVersionDarwin(): Promise<SemVer> {
|
||||
// set brew tap to azdata-cli repository
|
||||
await executeCommand('brew', ['tap', 'microsoft/azdata-cli-release']);
|
||||
await executeCommand('brew', ['update']);
|
||||
let brewInfoAzdataCliJson;
|
||||
// Get the package version 'info' about 'azdata-cli' from 'brew' as a json object
|
||||
const brewInfoOutput = (await executeCommand('brew', ['info', 'azdata-cli', '--json'])).stdout;
|
||||
try {
|
||||
brewInfoAzdataCliJson = JSON.parse(brewInfoOutput);
|
||||
} catch (e) {
|
||||
throw Error(`failed to parse the JSON contents output of: 'brew info azdata-cli --json', text being parsed: '${brewInfoOutput}', error:${getErrorMessage(e)}`);
|
||||
}
|
||||
const azdataPackageVersionInfo: AzdataDarwinPackageVersionInfo = brewInfoAzdataCliJson.shift();
|
||||
Logger.log(loc.foundAzdataVersionToUpgradeTo(azdataPackageVersionInfo.versions.stable));
|
||||
return new SemVer(azdataPackageVersionInfo.versions.stable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest azdata version for linux clients
|
||||
* This method requires sudo permission so not suitable to be run during startup.
|
||||
*/
|
||||
// async function discoverLatestStableAzdataVersionLinux(): Promise<SemVer> {
|
||||
// // Update repository information and install azdata
|
||||
// await executeSudoCommand('apt-get update');
|
||||
// const output = (await executeCommand('apt', ['list', 'azdata-cli', '--upgradeable'])).stdout;
|
||||
// // the packageName (with version) string is the second space delimited token on the 2nd line
|
||||
// const packageName = output.split('\n')[1].split(' ')[1];
|
||||
// // the version string is the first part of the package sting before '~'
|
||||
// const version = packageName.split('~')[0];
|
||||
// Logger.log(loc.foundAzdataVersionToUpgradeTo(version));
|
||||
// return new SemVer(version);
|
||||
// }
|
||||
|
||||
@@ -17,13 +17,34 @@ export namespace HttpClient {
|
||||
* Downloads a file from the given URL, resolving to the full path of the downloaded file when complete
|
||||
* @param downloadUrl The URL to download the file from
|
||||
* @param targetFolder The folder to download the file to
|
||||
* @returns Full path to the downloaded file
|
||||
* @returns a promise to a full path to the downloaded file
|
||||
*/
|
||||
export function download(downloadUrl: string, targetFolder: string): Promise<string> {
|
||||
export function downloadFile(downloadUrl: string, targetFolder: string): Promise<string> {
|
||||
return download(downloadUrl, targetFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the text contents of the document at the given URL, resolving to a string containing the text when complete
|
||||
* @param url The URL of the document whose contents need to be fetched
|
||||
* @returns a promise to a string that has the contents of document at the provided url
|
||||
*/
|
||||
export async function getTextContent(url: string): Promise<string> {
|
||||
Logger.log(loc.gettingTextContentsOfUrl(url));
|
||||
return await download(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a file/fileContents at the given URL.
|
||||
* @param downloadUrl The URL to download the file from
|
||||
* @param targetFolder The folder to download the file to. If not defined then return value is the contents of the downloaded file.
|
||||
* @returns Full path to the downloaded file or the contents of the file at the given downloadUrl
|
||||
*/
|
||||
function download(downloadUrl: string, targetFolder?: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let totalMegaBytes: number | undefined = undefined;
|
||||
let receivedBytes = 0;
|
||||
let printThreshold = 0.1;
|
||||
let strings: string[] = [];
|
||||
let downloadRequest = request.get(downloadUrl, { timeout: DownloadTimeout })
|
||||
.on('error', downloadError => {
|
||||
Logger.log(loc.downloadError);
|
||||
@@ -34,28 +55,34 @@ export namespace HttpClient {
|
||||
if (response.statusCode !== 200) {
|
||||
Logger.log(loc.downloadError);
|
||||
Logger.log(response.statusMessage);
|
||||
Logger.log(`response code: ${response.statusCode}`);
|
||||
return reject(response.statusMessage);
|
||||
}
|
||||
const filename = path.basename(response.request.path);
|
||||
const targetPath = path.join(targetFolder, filename);
|
||||
Logger.log(loc.downloadingTo(filename, targetPath));
|
||||
// Wait to create the WriteStream until here so we can use the actual
|
||||
// filename based off of the URI.
|
||||
downloadRequest.pipe(fs.createWriteStream(targetPath))
|
||||
.on('close', async () => {
|
||||
Logger.log(loc.downloadFinished);
|
||||
resolve(targetPath);
|
||||
})
|
||||
.on('error', (downloadError) => {
|
||||
reject(downloadError);
|
||||
downloadRequest.abort();
|
||||
});
|
||||
if (targetFolder !== undefined) {
|
||||
const filename = path.basename(response.request.path);
|
||||
const targetPath = path.join(targetFolder, filename);
|
||||
Logger.log(loc.downloadingTo(filename, targetPath));
|
||||
// Wait to create the WriteStream until here so we can use the actual
|
||||
// filename based off of the URI.
|
||||
downloadRequest.pipe(fs.createWriteStream(targetPath))
|
||||
.on('close', async () => {
|
||||
Logger.log(loc.downloadFinished);
|
||||
resolve(targetPath);
|
||||
})
|
||||
.on('error', (downloadError) => {
|
||||
reject(downloadError);
|
||||
downloadRequest.abort();
|
||||
});
|
||||
}
|
||||
let contentLength = response.headers['content-length'];
|
||||
let totalBytes = parseInt(contentLength || '0');
|
||||
totalMegaBytes = totalBytes / (1024 * 1024);
|
||||
Logger.log(loc.downloadingProgressMb('0', totalMegaBytes.toFixed(2)));
|
||||
})
|
||||
.on('data', (data) => {
|
||||
if (targetFolder === undefined) {
|
||||
strings.push(data.toString('utf-8'));
|
||||
}
|
||||
receivedBytes += data.length;
|
||||
if (totalMegaBytes) {
|
||||
let receivedMegaBytes = receivedBytes / (1024 * 1024);
|
||||
@@ -65,30 +92,13 @@ export namespace HttpClient {
|
||||
printThreshold += 0.1;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filename for the specified URL - following redirects as needed
|
||||
* @param url The URL to get the filename of
|
||||
*/
|
||||
export async function getFilename(url: string): Promise<string> {
|
||||
Logger.log(loc.gettingFilenameOfUrl(url));
|
||||
return new Promise((resolve, reject) => {
|
||||
let httpRequest = request.get(url, { timeout: DownloadTimeout })
|
||||
.on('error', downloadError => {
|
||||
reject(downloadError);
|
||||
})
|
||||
.on('response', (response) => {
|
||||
if (response.statusCode !== 200) {
|
||||
return reject(response.statusMessage);
|
||||
.on('close', async () => {
|
||||
if (targetFolder === undefined) {
|
||||
|
||||
Logger.log(loc.downloadFinished);
|
||||
resolve(strings.join(''));
|
||||
}
|
||||
// We don't want to actually download the file so abort the request now
|
||||
httpRequest.abort();
|
||||
const filename = path.basename(response.request.path);
|
||||
Logger.log(loc.gotFilenameOfUrl(response.request.path, filename));
|
||||
resolve(filename);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,3 +13,11 @@ export function searchForCmd(exe: string): Promise<string> {
|
||||
// Note : This is separated out to allow for easy test stubbing
|
||||
return new Promise<string>((resolve, reject) => which(exe, (err, path) => err ? reject(err) : resolve(path)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message to display for a given error object that may be a variety of types.
|
||||
* @param error The error object
|
||||
*/
|
||||
export function getErrorMessage(error: any): string {
|
||||
return error.message ?? error;
|
||||
}
|
||||
|
||||
@@ -4,13 +4,20 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdataExt from 'azdata-ext';
|
||||
import { findAzdata, IAzdataTool } from './azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { checkAndUpgradeAzdata, findAzdata, IAzdataTool } from './azdata';
|
||||
import * as loc from './localizedConstants';
|
||||
|
||||
let localAzdata: IAzdataTool | undefined = undefined;
|
||||
|
||||
export async function activate(): Promise<azdataExt.IExtension> {
|
||||
localAzdata = await checkForAzdata();
|
||||
// Don't block on this since we want the extension to finish activating without needing user input
|
||||
checkAndUpgradeAzdata(localAzdata)
|
||||
.then(async () => {
|
||||
localAzdata = await findAzdata(); // now again find and return the currently installed azdata
|
||||
})
|
||||
.catch(err => vscode.window.showWarningMessage(loc.updateError(err))); //update if available and user wants it.
|
||||
return {
|
||||
azdata: {
|
||||
arc: {
|
||||
@@ -85,11 +92,11 @@ function throwIfNoAzdata(): void {
|
||||
|
||||
async function checkForAzdata(): Promise<IAzdataTool | undefined> {
|
||||
try {
|
||||
return await findAzdata();
|
||||
return await findAzdata(); // find currently installed Azdata
|
||||
} catch (err) {
|
||||
// Don't block on this since we want the extension to finish activating without needing user input.
|
||||
// Calls will be made to handle azdata not being installed
|
||||
promptToInstallAzdata().catch(e => console.log(`Unexpected error prompting to install azdata ${e}`));
|
||||
await promptToInstallAzdata().catch(e => console.log(`Unexpected error prompting to install azdata ${e}`));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -7,25 +7,30 @@ import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export const searchingForAzdata = localize('azdata.searchingForAzdata', "Searching for existing azdata installation...");
|
||||
export function foundExistingAzdata(path: string, version: string): string { return localize('azdata.foundExistingAzdata', "Found existing azdata installation at {0} (v{1})", path, version); }
|
||||
export function downloadingProgressMb(currentMb: string, totalMb: string): string { return localize('azdata.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb); }
|
||||
export const foundExistingAzdata = (path: string, version: string): string => localize('azdata.foundExistingAzdata', "Found existing azdata installation of version (v{0}) at path:{1}", version, path);
|
||||
export const downloadingProgressMb = (currentMb: string, totalMb: string): string => localize('azdata.downloadingProgressMb', "Downloading ({0} / {1} MB)", currentMb, totalMb);
|
||||
export const downloadFinished = localize('azdata.downloadFinished', "Download finished");
|
||||
export const install = localize('azdata.install', "Install");
|
||||
export const installingAzdata = localize('azdata.installingAzdata', "Installing azdata...");
|
||||
export const upgradingAzdata = localize('azdata.upgradingAzdata', "Upgrading azdata...");
|
||||
export const azdataInstalled = localize('azdata.azdataInstalled', "azdata was successfully installed. Restarting Azure Data Studio is required to complete configuration - features will not be activated until this is done.");
|
||||
export const azdataUpgraded = localize('azdata.azdataUpgraded', "azdata was successfully upgraded.");
|
||||
export const cancel = localize('azdata.cancel', "Cancel");
|
||||
export function downloadingTo(name: string, location: string): string { return localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location); }
|
||||
export function executingCommand(command: string, args: string[]): string { return localize('azdata.executingCommand', "Executing command \"{0} {1}\"", command, args?.join(' ')); }
|
||||
export function stdoutOutput(stdout: string): string { return localize('azdat.stdoutOutput', "stdout : {0}", stdout); }
|
||||
export function stderrOutput(stderr: string): string { return localize('azdat.stderrOutput', "stderr : {0}", stderr); }
|
||||
export function gettingFilenameOfUrl(url: string): string { return localize('azdata.gettingFilenameOfUrl', "Getting filename of resource at URL {0}", url); }
|
||||
export function gotFilenameOfUrl(url: string, filename: string): string { return localize('azdata.gotFilenameOfUrl', "Got filename {0} from URL {1}", filename, url); }
|
||||
|
||||
export function couldNotFindAzdata(err: any): string { return localize('azdata.couldNotFindAzdata', "Could not find azdata. Error : {0}", err.message ?? err); }
|
||||
export const yes = localize('azdata.yes', "Yes");
|
||||
export const no = localize('azdata.no', "No");
|
||||
export const downloadingTo = (name: string, location: string): string => localize('azdata.downloadingTo', "Downloading {0} to {1}", name, location);
|
||||
export const executingCommand = (command: string, args: string[]): string => localize('azdata.executingCommand', "Executing command \"{0} {1}\"", command, args?.join(' '));
|
||||
export const stdoutOutput = (stdout: string): string => localize('azdata.stdoutOutput', "stdout : {0}", stdout);
|
||||
export const stderrOutput = (stderr: string): string => localize('azdata.stderrOutput', "stderr : {0}", stderr);
|
||||
export const checkingLatestAzdataVersion = localize('azdata.checkingLatestAzdataVersion', "Checking for latest version of azdata");
|
||||
export const gettingTextContentsOfUrl = (url: string): string => localize('azdata.gettingTextContentsOfUrl', "Getting text contents of resource at URL {0}", url);
|
||||
export const foundAzdataVersionToUpgradeTo = (version: string): string => localize('azdata.versionForUpgrade', "Found version {0} that azdata-cli can be upgraded to.", version);
|
||||
export const promptForAzdataUpgrade = (version: string): string => localize('azdata.promptForAzdataUpgrade', "An updated version of azdata ( {0} ) is available, do you wish to install it now?", version);
|
||||
export const couldNotFindAzdata = (err: any): string => localize('azdata.couldNotFindAzdata', "Could not find azdata. Error : {0}", err.message ?? err);
|
||||
export const couldNotFindAzdataWithPrompt = localize('azdata.couldNotFindAzdataWithPrompt', "Could not find azdata, install it now? If not then some features will not be able to function.");
|
||||
export const downloadError = localize('azdata.downloadError', "Error while downloading");
|
||||
export function installError(err: any): string { return localize('azdata.installError', "Error installing azdata : {0}", err.message ?? err); }
|
||||
export function platformUnsupported(platform: string): string { return localize('azdata.platformUnsupported', "Platform '{0}' is currently unsupported", platform); }
|
||||
export function unexpectedCommandError(errMsg: string): string { return localize('azdata.unexpectedCommandError', "Unexpected error executing command : {0}", errMsg); }
|
||||
export function unexpectedExitCode(code: number, err: string): string { return localize('azdata.unexpectedExitCode', "Unexpected exit code from command : {1} ({0})", code, err); }
|
||||
export const installError = (err: any): string => localize('azdata.installError', "Error installing azdata : {0}", err.message ?? err);
|
||||
export const platformUnsupported = (platform: string): string => localize('azdata.platformUnsupported', "Platform '{0}' is currently unsupported", platform);
|
||||
export const unexpectedCommandError = (errMsg: string): string => localize('azdata.unexpectedCommandError', "Unexpected error executing command : {0}", errMsg);
|
||||
export const updateError = (err: any): string => localize('azdata.updateError', "Error updating azdata : {0}", err.message ?? err);
|
||||
export const unexpectedExitCode = (code: number, err: string): string => localize('azdata.unexpectedExitCode', "Unexpected exit code from command : {1} ({0})", code, err);
|
||||
export const noAzdata = localize('azdata.NoAzdata', "No azdata available");
|
||||
|
||||
@@ -3,29 +3,30 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from '../azdata';
|
||||
import * as sinon from 'sinon';
|
||||
import * as childProcess from '../common/childProcess';
|
||||
import * as path from 'path';
|
||||
import { SemVer } from 'semver';
|
||||
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 nock from 'nock';
|
||||
import * as loc from '../localizedConstants';
|
||||
|
||||
const oldAzdataMock = <azdata.AzdataTool>{path:'/path/to/azdata', cachedVersion: new SemVer('0.0.0')};
|
||||
|
||||
describe('azdata', function () {
|
||||
|
||||
afterEach(function (): void {
|
||||
sinon.restore();
|
||||
nock.cleanAll();
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
describe('findAzdata', function () {
|
||||
it('successful', async function (): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
// Mock searchForCmd to return a path to azdata.cmd
|
||||
sinon.stub(utils, 'searchForCmd').returns(Promise.resolve('C:\\path\\to\\azdata.cmd'));
|
||||
}
|
||||
// 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: 'v1.0.0', stderr: '' }));
|
||||
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> {
|
||||
@@ -40,26 +41,221 @@ describe('azdata', function () {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Install not implemented on linux yet
|
||||
describe('downloadAndInstallAzdata', function (): void {
|
||||
it('successful download & install', async function (): Promise<void> {
|
||||
sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
|
||||
if (process.platform === 'linux') {
|
||||
sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: '', stderr: '' }));
|
||||
describe('installAzdata', function (): void {
|
||||
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;
|
||||
}
|
||||
nock(azdata.azdataHostname)
|
||||
.get(`/${azdata.azdataUri}`)
|
||||
.replyWithFile(200, __filename);
|
||||
const downloadPromise = azdata.downloadAndInstallAzdata();
|
||||
await downloadPromise;
|
||||
});
|
||||
|
||||
it('errors on unsuccessful download', async function (): Promise<void> {
|
||||
nock('https://aka.ms')
|
||||
.get('/azdata-msi')
|
||||
.reply(404);
|
||||
const downloadPromise = azdata.downloadAndInstallAzdata();
|
||||
await should(downloadPromise).be.rejected();
|
||||
if (process.platform === 'win32') {
|
||||
it('unsuccessful download - win32', async function (): Promise<void> {
|
||||
sinon.stub(HttpClient, 'downloadFile').rejects();
|
||||
const downloadPromise = azdata.installAzdata();
|
||||
await should(downloadPromise).be.rejected();
|
||||
});
|
||||
}
|
||||
|
||||
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('upgradeAzdata', function (): void {
|
||||
beforeEach(function (): void {
|
||||
sinon.stub(vscode.window, 'showInformationMessage').returns(Promise.resolve(<any>loc.yes));
|
||||
});
|
||||
|
||||
it('successful upgrade', async function (): Promise<void> {
|
||||
const releaseJson = {
|
||||
win32: {
|
||||
'version': '9999.999.999',
|
||||
'link': 'https://download.com/azdata-20.0.1.msi'
|
||||
},
|
||||
darwin: {
|
||||
'version': '9999.999.999'
|
||||
},
|
||||
linux: {
|
||||
'version': '9999.999.999'
|
||||
}
|
||||
};
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32SuccessfulUpgrade(releaseJson);
|
||||
break;
|
||||
|
||||
case 'darwin':
|
||||
await testDarwinSuccessfulUpgrade();
|
||||
break;
|
||||
case 'linux':
|
||||
await testLinuxSuccessfulUpgrade(releaseJson);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('unsuccessful upgrade', async function (): Promise<void> {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
await testWin32UnsuccessfulUpgrade();
|
||||
break;
|
||||
case 'darwin':
|
||||
await testDarwinUnsuccessfulUpgrade();
|
||||
break;
|
||||
|
||||
case 'linux':
|
||||
await testLinuxUnsuccessfulUpgrade();
|
||||
}
|
||||
});
|
||||
|
||||
describe('discoverLatestAvailableAzdataVersion', function (): void {
|
||||
this.timeout(20000);
|
||||
it(`finds latest available version of azdata successfully`, async function (): Promise<void> {
|
||||
// if the latest version is not discovered then the following call throws failing the test
|
||||
await azdata.discoverLatestAvailableAzdataVersion();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function testLinuxUnsuccessfulUpgrade() {
|
||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').rejects();
|
||||
const upgradePromise = azdata.checkAndUpgradeAzdata(oldAzdataMock);
|
||||
await should(upgradePromise).be.rejected();
|
||||
should(executeSudoCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testDarwinUnsuccessfulUpgrade() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
|
||||
const upgradePromise = azdata.checkAndUpgradeAzdata(oldAzdataMock);
|
||||
await should(upgradePromise).be.rejected();
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testWin32UnsuccessfulUpgrade() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
sinon.stub(childProcess, 'executeCommand').rejects();
|
||||
const upgradePromise = azdata.checkAndUpgradeAzdata(oldAzdataMock);
|
||||
await should(upgradePromise).be.rejected();
|
||||
}
|
||||
|
||||
async function testLinuxSuccessfulUpgrade(releaseJson: { win32: { version: string; }; darwin: { version: string; }; linux: { version: string; }; }) {
|
||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: 'success', stderr: '' }));
|
||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: 'success', stderr: '' }));
|
||||
await azdata.checkAndUpgradeAzdata(oldAzdataMock);
|
||||
should(executeSudoCommandStub.callCount).be.equal(6);
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testDarwinSuccessfulUpgrade() {
|
||||
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[]) => {
|
||||
should(command).be.equal('brew');
|
||||
should(args).deepEqual(['info', 'azdata-cli', '--json']);
|
||||
return Promise.resolve({
|
||||
stderr: '',
|
||||
stdout: JSON.stringify(brewInfoOutput)
|
||||
});
|
||||
})
|
||||
.callsFake(async (_command: string, _args: string[]) => { // return success on all other command executions
|
||||
return Promise.resolve({ stdout: 'success', stderr: '' });
|
||||
});
|
||||
await azdata.checkAndUpgradeAzdata(oldAzdataMock);
|
||||
should(executeCommandStub.callCount).be.equal(6);
|
||||
}
|
||||
|
||||
async function testWin32SuccessfulUpgrade(releaseJson: { win32: { version: string; link: string; }; darwin: { version: string; }; linux: { version: string; }; }) {
|
||||
sinon.stub(HttpClient, 'getTextContent').returns(Promise.resolve(JSON.stringify(releaseJson)));
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').callsFake(async (command: string, args: string[]) => {
|
||||
should(command).be.equal('msiexec');
|
||||
should(args[0]).be.equal('/qn');
|
||||
should(args[1]).be.equal('/i');
|
||||
should(path.basename(args[2])).be.equal(azdata.azdataUri);
|
||||
return { stdout: 'success', stderr: '' };
|
||||
});
|
||||
await azdata.checkAndUpgradeAzdata(oldAzdataMock);
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testWin32SuccessfulInstall() {
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').callsFake(async (command: string, args: string[]) => {
|
||||
should(command).be.equal('msiexec');
|
||||
should(args[0]).be.equal('/qn');
|
||||
should(args[1]).be.equal('/i');
|
||||
should(path.basename(args[2])).be.equal(azdata.azdataUri);
|
||||
return { stdout: 'success', stderr: '' };
|
||||
});
|
||||
await azdata.installAzdata();
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testDarwinSuccessfulInstall() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').callsFake(async (command: string, _args: string[]) => {
|
||||
should(command).be.equal('brew');
|
||||
return { stdout: 'success', stderr: '' };
|
||||
});
|
||||
await azdata.installAzdata();
|
||||
should(executeCommandStub.calledThrice).be.true();
|
||||
}
|
||||
|
||||
async function testLinuxSuccessfulInstall() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').returns(Promise.resolve({ stdout: 'success', stderr: '' }));
|
||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').returns(Promise.resolve({ stdout: 'success', stderr: '' }));
|
||||
await azdata.installAzdata();
|
||||
should(executeSudoCommandStub.callCount).be.equal(6);
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
async function testLinuxUnsuccessfulInstall() {
|
||||
const executeSudoCommandStub = sinon.stub(childProcess, 'executeSudoCommand').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() {
|
||||
const executeCommandStub = sinon.stub(childProcess, 'executeCommand').rejects();
|
||||
sinon.stub(HttpClient, 'downloadFile').returns(Promise.resolve(__filename));
|
||||
const downloadPromise = azdata.installAzdata();
|
||||
await should(downloadPromise).be.rejected();
|
||||
should(executeCommandStub.calledOnce).be.true();
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as should from 'should';
|
||||
import { HttpClient } from '../../common/httpClient';
|
||||
import * as os from 'os';
|
||||
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 {
|
||||
@@ -17,15 +17,16 @@ describe('HttpClient', function (): void {
|
||||
afterEach(function (): void {
|
||||
nock.cleanAll();
|
||||
nock.enableNetConnect();
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('download', function(): void {
|
||||
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.download('https://127.0.0.1/README.md', downloadFolder);
|
||||
const downloadPath = await HttpClient.downloadFile('https://127.0.0.1/README.md', downloadFolder);
|
||||
// Verify file was downloaded correctly
|
||||
await fs.promises.stat(downloadPath);
|
||||
});
|
||||
@@ -35,8 +36,7 @@ describe('HttpClient', function (): void {
|
||||
nock('https://127.0.0.1')
|
||||
.get('/')
|
||||
.replyWithError('Unexpected Error');
|
||||
const downloadPromise = HttpClient.download('https://127.0.0.1', downloadFolder);
|
||||
|
||||
const downloadPromise = HttpClient.downloadFile('https://127.0.0.1', downloadFolder);
|
||||
await should(downloadPromise).be.rejected();
|
||||
});
|
||||
|
||||
@@ -45,8 +45,7 @@ describe('HttpClient', function (): void {
|
||||
nock('https://127.0.0.1')
|
||||
.get('/')
|
||||
.reply(404, '');
|
||||
const downloadPromise = HttpClient.download('https://127.0.0.1', downloadFolder);
|
||||
|
||||
const downloadPromise = HttpClient.downloadFile('https://127.0.0.1', downloadFolder);
|
||||
await should(downloadPromise).be.rejected();
|
||||
});
|
||||
|
||||
@@ -61,7 +60,7 @@ describe('HttpClient', function (): void {
|
||||
nock('https://127.0.0.1')
|
||||
.get('/')
|
||||
.reply(200, '');
|
||||
const downloadPromise = HttpClient.download('https://127.0.0.1', downloadFolder);
|
||||
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 {
|
||||
@@ -73,34 +72,29 @@ describe('HttpClient', function (): void {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFilename', function(): void {
|
||||
it('Gets filename correctly', async function (): Promise<void> {
|
||||
const filename = 'azdata-cli-20.0.0.msi';
|
||||
describe('getTextContent', function (): void {
|
||||
it.skip('Gets file contents correctly', async function (): Promise<void> {
|
||||
nock('https://127.0.0.1')
|
||||
.get(`/${filename}`)
|
||||
.reply(200);
|
||||
const receivedFilename = await HttpClient.getFilename(`https://127.0.0.1/${filename}`);
|
||||
|
||||
should(receivedFilename).equal(filename);
|
||||
.get('/arbitraryFile')
|
||||
.replyWithFile(200, __filename);
|
||||
const receivedContents = await HttpClient.getTextContent(`https://127.0.0.1/arbitraryFile`);
|
||||
should(receivedContents).equal(await fs.promises.readFile(__filename));
|
||||
});
|
||||
|
||||
it('errors on response error', async function (): Promise<void> {
|
||||
it('rejects on response error', async function (): Promise<void> {
|
||||
nock('https://127.0.0.1')
|
||||
.get('/')
|
||||
.replyWithError('Unexpected Error');
|
||||
const getFilenamePromise = HttpClient.getFilename('https://127.0.0.1');
|
||||
|
||||
await should(getFilenamePromise).be.rejected();
|
||||
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 getFilenamePromise = HttpClient.getFilename('https://127.0.0.1');
|
||||
|
||||
await should(getFilenamePromise).be.rejected();
|
||||
const getFileContentsPromise = HttpClient.getTextContent('https://127.0.0.1/', );
|
||||
await should(getFileContentsPromise).be.rejected();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
* 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 { searchForCmd as searchForExe } from '../../common/utils';
|
||||
|
||||
describe('utils', function () {
|
||||
describe('searchForExe', function(): void {
|
||||
it('finds exe successfully', async function(): Promise<void> {
|
||||
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> {
|
||||
it('throws for non-existent exe', async function (): Promise<void> {
|
||||
await should(searchForExe('someFakeExe')).be.rejected();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -247,6 +247,13 @@
|
||||
"@types/tough-cookie" "*"
|
||||
form-data "^2.5.0"
|
||||
|
||||
"@types/semver@^7.3.1":
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.1.tgz#7a9a5d595b6d873f338c867dcef64df289468cfa"
|
||||
integrity sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/sinon@^9.0.4":
|
||||
version "9.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1"
|
||||
@@ -1071,6 +1078,11 @@ semver@^6.0.0, semver@^6.3.0:
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@^7.3.2:
|
||||
version "7.3.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||
|
||||
should-equal@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
|
||||
|
||||
Reference in New Issue
Block a user