@@ -491,6 +491,133 @@ describe('LocalBinary', function () {
491491 } ) ;
492492} ) ;
493493
494+ // Regression tests for https://github.com/browserstack/browserstack-local-nodejs/issues/164:
495+ // proxyUser/proxyPass were accepted by Local's config and forwarded to the
496+ // BrowserStackLocal *binary* itself (--proxy-user/--proxy-pass), but never reached
497+ // the node-side binary download, so an authenticating proxy rejected the download
498+ // even though the same config worked for everything the binary does afterwards.
499+ describe ( 'Proxy authentication for binary download' , function ( ) {
500+ var https = require ( 'https' ) ;
501+ var childProcess = require ( 'child_process' ) ;
502+ var Local = require ( '../lib/Local' ) ;
503+ var binary , sandBox , tempDownloadPath ;
504+
505+ beforeEach ( function ( ) {
506+ binary = new LocalBinary ( ) ;
507+ sandBox = sinon . sandbox . create ( ) ;
508+ tempDownloadPath = path . join ( process . cwd ( ) , 'download-proxy-auth' ) ;
509+ } ) ;
510+
511+ afterEach ( function ( ) {
512+ sandBox . restore ( ) ;
513+ rimraf . sync ( tempDownloadPath ) ;
514+ } ) ;
515+
516+ it ( 'sends proxy credentials on the CONNECT agent used by the async download' , function ( done ) {
517+ sandBox . stub ( binary , 'getDownloadPath' , function ( conf , retries , callback ) {
518+ callback ( null , 'https://example.invalid/fake-binary' ) ;
519+ } ) ;
520+ sandBox . stub ( https , 'get' , function ( options ) {
521+ check ( done , function ( ) {
522+ expect ( options . agent . proxy . host ) . to . equal ( '127.0.0.1' ) ;
523+ expect ( String ( options . agent . proxy . port ) ) . to . equal ( '8080' ) ;
524+ expect ( options . agent . proxy . auth ) . to . equal ( 'proxyuser:proxypass' ) ;
525+ } ) ;
526+ return { on : function ( ) { return this ; } } ;
527+ } ) ;
528+
529+ binary . download ( {
530+ proxyHost : '127.0.0.1' ,
531+ proxyPort : 8080 ,
532+ proxyUser : 'proxyuser' ,
533+ proxyPass : 'proxypass'
534+ } , tempDownloadPath , function ( ) { } ) ;
535+ } ) ;
536+
537+ it ( 'does not set agent auth when no proxy credentials are configured' , function ( done ) {
538+ sandBox . stub ( binary , 'getDownloadPath' , function ( conf , retries , callback ) {
539+ callback ( null , 'https://example.invalid/fake-binary' ) ;
540+ } ) ;
541+ sandBox . stub ( https , 'get' , function ( options ) {
542+ check ( done , function ( ) {
543+ expect ( options . agent . proxy . auth ) . to . equal ( undefined ) ;
544+ } ) ;
545+ return { on : function ( ) { return this ; } } ;
546+ } ) ;
547+
548+ binary . download ( { proxyHost : '127.0.0.1' , proxyPort : 8080 } , tempDownloadPath , function ( ) { } ) ;
549+ } ) ;
550+
551+ it ( 'passes proxy credentials to the spawned download.js child via env, not argv' , function ( ) {
552+ var spawnStub = sandBox . stub ( childProcess , 'spawnSync' , function ( ) {
553+ return { stdout : Buffer . from ( 'ok' ) , stderr : Buffer . from ( '' ) } ;
554+ } ) ;
555+ sandBox . stub ( fs , 'existsSync' , function ( ) { return true ; } ) ;
556+ sandBox . stub ( fs , 'chmodSync' , function ( ) { } ) ;
557+
558+ binary . downloadSync ( {
559+ proxyHost : '127.0.0.1' ,
560+ proxyPort : 8080 ,
561+ proxyUser : 'proxyuser' ,
562+ proxyPass : 'proxypass'
563+ } , tempDownloadPath , 0 ) ;
564+
565+ var call = spawnStub . getCall ( 0 ) ;
566+ expect ( call . args [ 1 ] ) . to . not . contain ( 'proxyuser' ) ;
567+ expect ( call . args [ 1 ] ) . to . not . contain ( 'proxypass' ) ;
568+ expect ( call . args [ 2 ] . env . BROWSERSTACK_LOCAL_PROXY_USER ) . to . equal ( 'proxyuser' ) ;
569+ expect ( call . args [ 2 ] . env . BROWSERSTACK_LOCAL_PROXY_PASS ) . to . equal ( 'proxypass' ) ;
570+ } ) ;
571+
572+ it ( 'passes proxy credentials to the spawned fetchDownloadSourceUrl.js child via env, not argv' , function ( ) {
573+ var spawnStub = sandBox . stub ( childProcess , 'spawnSync' , function ( ) {
574+ return { stdout : Buffer . from ( 'https://example.invalid' ) , stderr : Buffer . from ( '' ) } ;
575+ } ) ;
576+
577+ binary . getSourceUrlSync ( {
578+ proxyHost : '127.0.0.1' ,
579+ proxyPort : 8080 ,
580+ proxyUser : 'proxyuser' ,
581+ proxyPass : 'proxypass'
582+ } , 0 ) ;
583+
584+ var call = spawnStub . getCall ( 0 ) ;
585+ expect ( call . args [ 1 ] ) . to . not . contain ( 'proxyuser' ) ;
586+ expect ( call . args [ 1 ] ) . to . not . contain ( 'proxypass' ) ;
587+ expect ( call . args [ 2 ] . env . BROWSERSTACK_LOCAL_PROXY_USER ) . to . equal ( 'proxyuser' ) ;
588+ expect ( call . args [ 2 ] . env . BROWSERSTACK_LOCAL_PROXY_PASS ) . to . equal ( 'proxypass' ) ;
589+ } ) ;
590+
591+ it ( 'forwards proxyUser/proxyPass from Local config through to the download child process env' , function ( ) {
592+ var spawnStub = sandBox . stub ( childProcess , 'spawnSync' , function ( ) {
593+ return { stdout : Buffer . from ( 'https://example.invalid' ) , stderr : Buffer . from ( '' ) } ;
594+ } ) ;
595+ // Make the sync path succeed on the first attempt instead of retrying:
596+ // downloadSync's spawnSync is stubbed and never actually writes a binary,
597+ // so without this the real retry loop (async via fs.unlink) keeps firing
598+ // child processes after the test has already finished and its stubs are
599+ // restored.
600+ sandBox . stub ( fs , 'existsSync' , function ( ) { return true ; } ) ;
601+ sandBox . stub ( fs , 'chmodSync' , function ( ) { } ) ;
602+
603+ var bsLocal = new Local ( ) ;
604+ bsLocal . proxyHost = '127.0.0.1' ;
605+ bsLocal . proxyPort = 8080 ;
606+ bsLocal . proxyUser = 'proxyuser' ;
607+ bsLocal . proxyPass = 'proxypass' ;
608+
609+ // No callback -> the sync path, which ends in the same spawnSync used by
610+ // getSourceUrlSync/downloadSync, whichever this hits first with an empty
611+ // binary directory.
612+ bsLocal . getBinaryPath ( ) ;
613+
614+ expect ( spawnStub . called ) . to . equal ( true ) ;
615+ var call = spawnStub . getCall ( 0 ) ;
616+ expect ( call . args [ 2 ] . env . BROWSERSTACK_LOCAL_PROXY_USER ) . to . equal ( 'proxyuser' ) ;
617+ expect ( call . args [ 2 ] . env . BROWSERSTACK_LOCAL_PROXY_PASS ) . to . equal ( 'proxypass' ) ;
618+ } ) ;
619+ } ) ;
620+
494621// Regression tests: the binary-download fallback signalling used to live on
495622// process.env, so (a) a value planted in process.env steered the download to an
496623// arbitrary host with no validation, and (b) a failure on one Local instance bled
0 commit comments