diff --git a/CHANGELOG.md b/CHANGELOG.md index e2e4bf3540..8ca50a1ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ NativeScript CLI Changelog ================ +4.2.3 (2018, August 27) +== + +### Fixed +* [Fixed #3840](https://github.com/NativeScript/nativescript-cli/issues/3840): Unable to reconnect to iOS Simulator when debugging +* [Fixed #3824](https://github.com/NativeScript/nativescript-cli/issues/3824): `tns create` command not using proxy set with `tns proxy set` + + +4.2.2 (2018, August 17) +== + +### Fixed +* [Fixed #3818](https://github.com/NativeScript/nativescript-cli/issues/3818): Unable to start application on Android device with a custom activity containing capital letters +* [Fixed #3820](https://github.com/NativeScript/nativescript-cli/issues/3820): A command help is shown on native build error +* [Fixed #3821](https://github.com/NativeScript/nativescript-cli/issues/3821): [Sporadic] Unable to start iOS debugger from VSCode extension + + 4.2.1 (2018, August 10) == diff --git a/lib/common b/lib/common index b02d994287..096936c04f 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit b02d99428798bbb85dac93f1649890d766bd3011 +Subproject commit 096936c04f4d350fbebf2c86811becfaaf392522 diff --git a/lib/definitions/ios-debugger-port-service.d.ts b/lib/definitions/ios-debugger-port-service.d.ts index 50f156afb0..1b6486a0f5 100644 --- a/lib/definitions/ios-debugger-port-service.d.ts +++ b/lib/definitions/ios-debugger-port-service.d.ts @@ -17,7 +17,7 @@ interface IIOSDebuggerPortService { * Gets iOS debugger port for specified deviceId and appId * @param {IIOSDebuggerPortInputData} data - Describes deviceId and appId */ - getPort(data: IIOSDebuggerPortInputData): Promise; + getPort(data: IIOSDebuggerPortInputData, debugOptions?: IDebugOptions): Promise; /** * Attaches on DEBUGGER_PORT_FOUND event and STARTING_IOS_APPLICATION events * In case when DEBUGGER_PORT_FOUND event is emitted, stores the port and clears the timeout if such. diff --git a/lib/definitions/pacote-service.d.ts b/lib/definitions/pacote-service.d.ts index 9f89b0e6df..8c24302a5c 100644 --- a/lib/definitions/pacote-service.d.ts +++ b/lib/definitions/pacote-service.d.ts @@ -18,7 +18,7 @@ declare global { extractPackage(packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise; } - interface IPacoteBaseOptions { + interface IPacoteBaseOptions extends IProxySettingsBase { /** * The path to npm cache */ diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 87eb3390ae..86215b9e05 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -90,7 +90,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact this.$logger.info("Frontend client connected."); let _socket; try { - _socket = await factory(); + _socket = await helpers.connectEventuallyUntilTimeout(factory, 10000); } catch (err) { err.deviceIdentifier = deviceIdentifier; this.$logger.trace(err); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index c1ea8a4796..6a71bdfb8b 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -701,11 +701,17 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject const childProcessOpts = opts.childProcessOpts || {}; childProcessOpts.cwd = childProcessOpts.cwd || projectRoot; childProcessOpts.stdio = childProcessOpts.stdio || "inherit"; + let commandResult; + try { + commandResult = await this.spawn(gradlew, + gradleArgs, + childProcessOpts, + spawnFromEventOptions); + } catch (err) { + this.$errors.failWithoutHelp(err.message); + } - return await this.spawn(gradlew, - gradleArgs, - childProcessOpts, - spawnFromEventOptions); + return commandResult; } } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index c5f48b7f1a..86ab0644e3 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -202,7 +202,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS // the VSCode Ext starts `tns debug ios --no-client` to start/attach to debug sessions // check if --no-client is passed - default to opening a tcp socket (versus Chrome DevTools (websocket)) if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { - this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(debugData, device)); + this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device, debugData, debugOptions)); await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); return null; } else { @@ -211,7 +211,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; - this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(debugData, device), deviceIdentifier); + this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device, debugData, debugOptions), deviceIdentifier); return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); } } @@ -230,15 +230,24 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } - private getSocketFactory(debugData: IDebugData, device?: Mobile.IiOSDevice): () => Promise { + private getSocketFactory(device: Mobile.IiOSDevice, debugData: IDebugData, debugOptions: IDebugOptions): () => Promise { + let pendingExecution: Promise = null; const factory = async () => { - const port = await this.$iOSDebuggerPortService.getPort({ projectDir: debugData.projectDir, deviceId: debugData.deviceIdentifier, appId: debugData.applicationIdentifier }); - if (!port) { - this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + if (!pendingExecution) { + const func = async () => { + const port = await this.$iOSDebuggerPortService.getPort({ projectDir: debugData.projectDir, deviceId: debugData.deviceIdentifier, appId: debugData.applicationIdentifier }, debugOptions); + if (!port) { + this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + } + const socket = device ? await device.connectToPort(port) : net.connect(port); + this._sockets.push(socket); + pendingExecution = null; + return socket; + }; + pendingExecution = func(); } - const socket = device ? await device.connectToPort(port) : net.connect(port); - this._sockets.push(socket); - return socket; + + return pendingExecution; }; factory.bind(this); diff --git a/lib/services/ios-debugger-port-service.ts b/lib/services/ios-debugger-port-service.ts index 8dac0bb27e..e52f4b509d 100644 --- a/lib/services/ios-debugger-port-service.ts +++ b/lib/services/ios-debugger-port-service.ts @@ -14,7 +14,7 @@ export class IOSDebuggerPortService implements IIOSDebuggerPortService { private $projectDataService: IProjectDataService, private $logger: ILogger) { } - public getPort(data: IIOSDebuggerPortInputData): Promise { + public getPort(data: IIOSDebuggerPortInputData, debugOptions?: IDebugOptions): Promise { return new Promise((resolve, reject) => { if (!this.canStartLookingForDebuggerPort(data)) { resolve(IOSDebuggerPortService.DEFAULT_PORT); @@ -22,7 +22,8 @@ export class IOSDebuggerPortService implements IIOSDebuggerPortService { } const key = `${data.deviceId}${data.appId}`; - let retryCount: number = 10; + const timeout = this.getTimeout(debugOptions); + let retryCount = Math.max(timeout * 1000 / 500, 10); const interval = setInterval(() => { let port = this.getPortByKey(key); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index fd05c066ed..d48ffa805c 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -57,7 +57,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ private $plistParser: IPlistParser, private $sysInfo: ISysInfo, private $xCConfigService: XCConfigService) { - super($fs, $projectDataService); + super($fs, $projectDataService); } private _platformsDirCache: string = null; @@ -442,11 +442,19 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ localArgs.push("-quiet"); this.$logger.info("Xcode build..."); } - return this.$childProcess.spawnFromEvent("xcodebuild", - localArgs, - "exit", - { stdio: stdio || "inherit", cwd }, - { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); + + let commandResult; + try { + commandResult = await this.$childProcess.spawnFromEvent("xcodebuild", + localArgs, + "exit", + { stdio: stdio || "inherit", cwd }, + { emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true }); + } catch (err) { + this.$errors.failWithoutHelp(err.message); + } + + return commandResult; } private async setupSigningFromTeam(projectRoot: string, projectData: IProjectData, teamId: string) { @@ -1112,7 +1120,7 @@ We will now place an empty obsolete compatability white screen LauncScreen.xib f private async prepareNativeSourceCode(pluginName: string, pluginPlatformsFolderPath: string, projectData: IProjectData): Promise { const project = this.createPbxProj(projectData); const group = this.getRootGroup(pluginName, pluginPlatformsFolderPath); - project.addPbxGroup(group.files, group.name, group.path, null, {isMain:true}); + project.addPbxGroup(group.files, group.name, group.path, null, { isMain: true }); project.addToHeaderSearchPaths(group.path); this.savePbxProj(project, projectData); } diff --git a/lib/services/livesync/android-livesync-tool.ts b/lib/services/livesync/android-livesync-tool.ts index 0f319faacb..83054d18c1 100644 --- a/lib/services/livesync/android-livesync-tool.ts +++ b/lib/services/livesync/android-livesync-tool.ts @@ -313,6 +313,10 @@ export class AndroidLivesyncTool implements IAndroidLivesyncTool { const connectionTimer = setTimeout(() => { if (!isConnected) { isConnected = true; + if (this.pendingConnectionData && this.pendingConnectionData.socketTimer) { + clearTimeout(this.pendingConnectionData.socketTimer); + } + reject(lastKnownError || new Error("Socket connection timeouted.")); this.pendingConnectionData = null; } diff --git a/lib/services/pacote-service.ts b/lib/services/pacote-service.ts index a69f12481f..94c12fbd0c 100644 --- a/lib/services/pacote-service.ts +++ b/lib/services/pacote-service.ts @@ -4,48 +4,78 @@ import * as path from "path"; export class PacoteService implements IPacoteService { constructor(private $fs: IFileSystem, - private $npm: INodePackageManager) { } + private $npm: INodePackageManager, + private $proxyService: IProxyService, + private $logger: ILogger) { } public async manifest(packageName: string, options?: IPacoteManifestOptions): Promise { - // In case `tns create myapp --template https://github.com/NativeScript/template-hello-world.git` command is executed, pacote module throws an error if cache option is not provided. - const cache = await this.$npm.getCachePath(); - const manifestOptions = { cache }; + this.$logger.trace(`Calling pacoteService.manifest for packageName: '${packageName}' and options: ${options}`); + const manifestOptions: IPacoteBaseOptions = await this.getPacoteBaseOptions(); if (options) { _.extend(manifestOptions, options); } - if (this.$fs.exists(packageName)) { - packageName = path.resolve(packageName); - } - + packageName = this.getRealPackageName(packageName); + this.$logger.trace(`Calling pacote.manifest for packageName: ${packageName} and options: ${JSON.stringify(manifestOptions, null, 2)}`); return pacote.manifest(packageName, manifestOptions); } public async extractPackage(packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise { // strip: Remove the specified number of leading path elements. Pathnames with fewer elements will be silently skipped. More info: https://github.com/npm/node-tar/blob/e89c4d37519b1c20133a9f49d5f6b85fa34c203b/README.md // C: Create an archive + this.$logger.trace(`Calling pacoteService.extractPackage for packageName: '${packageName}', destinationDir: '${destinationDirectory}' and options: ${options}`); const extractOptions = { strip: 1, C: destinationDirectory }; if (options) { _.extend(extractOptions, options); } - if (this.$fs.exists(packageName)) { - packageName = path.resolve(packageName); - } + packageName = this.getRealPackageName(packageName); + const pacoteOptions = await this.getPacoteBaseOptions(); - const cache = await this.$npm.getCachePath(); return new Promise((resolve, reject) => { - const source = pacote.tarball.stream(packageName, { cache }); + this.$logger.trace(`Calling pacoteService.extractPackage for packageName: '${packageName}', destinationDir: '${destinationDirectory}' and options: ${options}`); + + const source = pacote.tarball.stream(packageName, pacoteOptions); source.on("error", (err: Error) => { + this.$logger.trace(`Error in source while trying to extract stream from ${packageName}. Error is ${err}`); reject(err); }); + this.$logger.trace(`Creating extract tar stream with options: ${JSON.stringify(extractOptions, null, 2)}`); const destination = tar.x(extractOptions); source.pipe(destination); - destination.on("error", (err: Error) => reject(err)); - destination.on("finish", () => resolve()); + destination.on("error", (err: Error) => { + this.$logger.trace(`Error in destination while trying to extract stream from ${packageName}. Error is ${err}`); + reject(err); + }); + + destination.on("finish", () => { + this.$logger.trace(`Successfully extracted '${packageName}' to ${destinationDirectory}`); + resolve(); + }); }); } + + private async getPacoteBaseOptions(): Promise { + // In case `tns create myapp --template https://github.com/NativeScript/template-hello-world.git` command is executed, pacote module throws an error if cache option is not provided. + const cache = await this.$npm.getCachePath(); + const pacoteOptions = { cache }; + const proxySettings = await this.$proxyService.getCache(); + if (proxySettings) { + _.extend(pacoteOptions, proxySettings); + } + + return pacoteOptions; + } + + private getRealPackageName(packageName: string): string { + if (this.$fs.exists(packageName)) { + this.$logger.trace(`Will resolve the full path to package ${packageName}.`); + packageName = path.resolve(packageName); + } + + return packageName; + } } $injector.register("pacoteService", PacoteService); diff --git a/test/project-service.ts b/test/project-service.ts index 441e8ca461..2e887c6eb0 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -152,6 +152,9 @@ class ProjectIntegrationTest { executeAfterHooks: async (commandName: string, hookArguments?: IDictionary): Promise => undefined }); this.testInjector.register("pacoteService", PacoteService); + this.testInjector.register("proxyService", { + getCache: async (): Promise => null + }); } } diff --git a/test/services/ios-debugger-port-service.ts b/test/services/ios-debugger-port-service.ts index 765d255d11..66d4db19a4 100644 --- a/test/services/ios-debugger-port-service.ts +++ b/test/services/ios-debugger-port-service.ts @@ -157,7 +157,7 @@ describe("iOSDebuggerPortService", () => { } const promise = iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId, projectDir: mockProjectDirObj.projectDir }); - clock.tick(10000); + clock.tick(20000); const port = await promise; assert.deepEqual(port, testCase.emittedPort); }); @@ -171,7 +171,7 @@ describe("iOSDebuggerPortService", () => { } const promise = iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId, projectDir: mockProjectDirObj.projectDir }); - clock.tick(10000); + clock.tick(20000); const port = await promise; assert.deepEqual(port, testCase.emittedPort); }); diff --git a/test/services/pacote-service.ts b/test/services/pacote-service.ts new file mode 100644 index 0000000000..1d00ae4bc8 --- /dev/null +++ b/test/services/pacote-service.ts @@ -0,0 +1,282 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { PacoteService } from '../../lib/services/pacote-service'; +import { LoggerStub } from "../stubs"; +import { sandbox, SinonSandbox, SinonStub } from "sinon"; +import { EventEmitter } from "events"; +const pacote = require("pacote"); +const tar = require("tar"); +const path = require("path"); + +const npmCachePath = "npmCachePath"; +const packageName = "testPackage"; +const fullPath = `/Users/username/${packageName}`; +const destinationDir = "destinationDir"; +const defaultPacoteOpts: IPacoteBaseOptions = { cache: npmCachePath }; +const errorMessage = "error message"; +const proxySettings: IProxySettings = { + hostname: "hostname", + proxy: "proxy", + port: "8888", + rejectUnauthorized: true, + username: null, + password: null +}; + +interface ITestSetup { + isLocalPackage?: boolean; + useProxySettings?: boolean; + npmGetCachePathError?: Error; +} + +interface ITestCase extends ITestSetup { + manifestOptions?: IPacoteManifestOptions; + additionalExtractOpts?: IPacoteExtractOptions; + name: string; + expectedArgs: any[]; +} + +const createTestInjector = (opts?: ITestSetup): IInjector => { + opts = opts || {}; + + const testInjector = new Yok(); + testInjector.register("logger", LoggerStub); + testInjector.register("pacoteService", PacoteService); + testInjector.register("fs", { + exists: (p: string): boolean => opts.isLocalPackage + }); + + testInjector.register("proxyService", { + getCache: async (): Promise => opts.useProxySettings ? proxySettings : null + }); + + testInjector.register("npm", { + getCachePath: async (): Promise => { + if (opts.npmGetCachePathError) { + throw opts.npmGetCachePathError; + } + + return npmCachePath; + } + }); + + return testInjector; +}; + +class MockStream extends EventEmitter { + public pipe(destination: any, options?: { end?: boolean; }): any { + // Nothing to do here, just mock the method. + } +} + +describe("pacoteService", () => { + const manifestResult: any = {}; + const manifestOptions: IPacoteManifestOptions = { fullMetadata: true }; + let sandboxInstance: SinonSandbox = null; + let manifestStub: SinonStub = null; + let tarballStreamStub: SinonStub = null; + let tarXStub: SinonStub = null; + let tarballSourceStream: MockStream = null; + let tarExtractDestinationStream: MockStream = null; + + beforeEach(() => { + sandboxInstance = sandbox.create(); + manifestStub = sandboxInstance.stub(pacote, "manifest").returns(Promise.resolve(manifestResult)); + tarballSourceStream = new MockStream(); + tarballStreamStub = sandboxInstance.stub(pacote.tarball, "stream").returns(tarballSourceStream); + tarExtractDestinationStream = new MockStream(); + tarXStub = sandboxInstance.stub(tar, "x").returns(tarExtractDestinationStream); + }); + + afterEach(() => { + sandboxInstance.restore(); + }); + + const setupTest = (opts?: ITestSetup): IPacoteService => { + opts = opts || {}; + const testInjector = createTestInjector(opts); + if (opts.isLocalPackage) { + sandboxInstance.stub(path, "resolve").withArgs(packageName).returns(fullPath); + } + + return testInjector.resolve("pacoteService"); + }; + + describe("manifest", () => { + describe("calls pacote.manifest", () => { + + const testData: ITestCase[] = [ + { + name: "with 'cache' only when no opts are passed", + expectedArgs: [packageName, defaultPacoteOpts] + }, + { + name: "with 'cache' and passed options", + manifestOptions, + expectedArgs: [packageName, _.extend({}, defaultPacoteOpts, manifestOptions)] + }, + { + name: "with 'cache' and proxy settings", + useProxySettings: true, + expectedArgs: [packageName, _.extend({}, defaultPacoteOpts, proxySettings)] + }, + { + name: "with 'cache', passed options and proxy settings when proxy is configured", + manifestOptions, + useProxySettings: true, + expectedArgs: [packageName, _.extend({}, defaultPacoteOpts, manifestOptions, proxySettings)] + }, + { + name: "with full path to file when it is local one", + isLocalPackage: true, + expectedArgs: [fullPath, defaultPacoteOpts] + }, + { + name: "with full path to file, 'cache' and passed options when local path is passed", + manifestOptions, + isLocalPackage: true, + expectedArgs: [fullPath, _.extend({}, defaultPacoteOpts, manifestOptions)] + }, + { + name: "with full path to file, 'cache' and proxy settings when proxy is configured", + manifestOptions, + isLocalPackage: true, + useProxySettings: true, + expectedArgs: [fullPath, _.extend({}, defaultPacoteOpts, manifestOptions, proxySettings)] + }, + { + name: "with full path to file, 'cache', passed options and proxy settings when proxy is configured and local path is passed", + manifestOptions, + useProxySettings: true, + isLocalPackage: true, + expectedArgs: [fullPath, _.extend({}, defaultPacoteOpts, manifestOptions, proxySettings)] + }, + ]; + + testData.forEach(testCase => { + it(testCase.name, async () => { + const pacoteService = setupTest(testCase); + const result = await pacoteService.manifest(packageName, testCase.manifestOptions); + + assert.equal(result, manifestResult); + assert.deepEqual(manifestStub.firstCall.args, testCase.expectedArgs); + }); + }); + }); + + it("fails with npm error when unable to get npm cache", async () => { + const npmGetCachePathError = new Error("npm error"); + const pacoteService = setupTest({ npmGetCachePathError }); + await assert.isRejected(pacoteService.manifest(packageName, null), npmGetCachePathError.message); + }); + }); + + describe("extractPackage", () => { + it("fails with correct error when pacote.tarball.stream raises error event", async () => { + const pacoteService = setupTest(); + + const pacoteExtractPackagePromise = pacoteService.extractPackage(packageName, destinationDir); + setImmediate(() => { + tarballSourceStream.emit("error", new Error(errorMessage)); + }); + + await assert.isRejected(pacoteExtractPackagePromise, errorMessage); + }); + + it("fails with correct error when the destination stream raises error event", async () => { + const pacoteService = setupTest(); + + const pacoteExtractPackagePromise = pacoteService.extractPackage(packageName, destinationDir); + setImmediate(() => { + tarExtractDestinationStream.emit("error", new Error(errorMessage)); + }); + + await assert.isRejected(pacoteExtractPackagePromise, errorMessage); + }); + + it("resolves when the destination stream emits finish event", async () => { + const pacoteService = setupTest(); + + const pacoteExtractPackagePromise = pacoteService.extractPackage(packageName, destinationDir); + setImmediate(() => { + tarExtractDestinationStream.emit("finish"); + }); + + await assert.isFulfilled(pacoteExtractPackagePromise); + }); + + describe("passes correct options to tar.x", () => { + const defaultExtractOpts = { strip: 1, C: destinationDir }; + const additionalExtractOpts: IPacoteExtractOptions = { + filter: (p: string, stat: any) => true + }; + + const testData: ITestCase[] = [ + { + name: "when only default options should be passed", + expectedArgs: [defaultExtractOpts], + }, + { + name: "when additional options are passed", + expectedArgs: [_.extend({}, defaultExtractOpts, additionalExtractOpts)], + additionalExtractOpts + }, + ]; + + testData.forEach(testCase => { + it(testCase.name, async () => { + const pacoteService = setupTest(); + + const pacoteExtractPackagePromise = pacoteService.extractPackage(packageName, destinationDir, testCase.additionalExtractOpts); + setImmediate(() => { + tarExtractDestinationStream.emit("finish"); + }); + + await assert.isFulfilled(pacoteExtractPackagePromise); + + assert.deepEqual(tarXStub.firstCall.args, testCase.expectedArgs); + }); + }); + }); + + describe("passes correct options to pacote.tarball.stream", () => { + const testData: ITestCase[] = [ + { + name: "when proxy is not set", + expectedArgs: [packageName, defaultPacoteOpts] + }, + { + name: "when proxy is not set and a local path is passed", + isLocalPackage: true, + expectedArgs: [fullPath, defaultPacoteOpts] + }, + { + name: "when proxy is set", + useProxySettings: true, + expectedArgs: [packageName, _.extend({}, defaultPacoteOpts, proxySettings)] + }, + { + name: "when proxy is set and a local path is passed", + useProxySettings: true, + isLocalPackage: true, + expectedArgs: [fullPath, _.extend({}, defaultPacoteOpts, proxySettings)] + }, + + ]; + + testData.forEach(testCase => { + it(testCase.name, async () => { + const pacoteService = setupTest(testCase); + + const pacoteExtractPackagePromise = pacoteService.extractPackage(packageName, destinationDir); + setImmediate(() => { + tarExtractDestinationStream.emit("finish"); + }); + + await assert.isFulfilled(pacoteExtractPackagePromise); + assert.deepEqual(tarballStreamStub.firstCall.args, testCase.expectedArgs); + }); + }); + }); + }); +});