1515 */
1616
1717import * as assert from 'assert' ;
18- import * as rawFs from 'fs' ;
18+ import * as fs from 'fs' ;
1919import * as _ from 'lodash' ;
2020import * as path from 'path' ;
2121import * as proxyquire from 'proxyquire' ;
@@ -28,7 +28,7 @@ import * as stackdriver from '../src/types/stackdriver';
2828
2929DEFAULT_CONFIG . allowExpressions = true ;
3030DEFAULT_CONFIG . workingDirectory = path . join ( __dirname , '..' , '..' ) ;
31- import { Debuglet , CachedPromise } from '../src/agent/debuglet' ;
31+ import { Debuglet , CachedPromise , FindFilesResult } from '../src/agent/debuglet' ;
3232import { ScanResults } from '../src/agent/io/scanner' ;
3333import * as dns from 'dns' ;
3434import * as extend from 'extend' ;
@@ -814,6 +814,122 @@ describe('Debuglet', () => {
814814 debuglet . start ( ) ;
815815 } ) ;
816816
817+ it ( 'should by default error when workingDirectory is a root directory with a package.json' ,
818+ ( done ) => {
819+ const debug = new Debug ( { } , packageInfo ) ;
820+ /*
821+ * `path.sep` represents a root directory on both Windows and Unix.
822+ * On Windows, `path.sep` resolves to the current drive.
823+ *
824+ * That is, after opening a command prompt in Windows, relative to the
825+ * drive C: and starting the Node REPL, the value of `path.sep`
826+ * represents `C:\\'.
827+ *
828+ * If `D:` is entered at the prompt to switch to the D: drive before
829+ * starting the Node REPL, `path.sep` represents `D:\\`.
830+ */
831+ const root = path . sep ;
832+ const mockedDebuglet = proxyquire ( '../src/agent/debuglet' , {
833+ /*
834+ * Mock the 'fs' module to verify that if the root directory is used,
835+ * and the root directory is reported to contain a `package.json`
836+ * file, then the agent still produces an `initError` when the
837+ * working directory is the root directory.
838+ */
839+ fs : {
840+ stat :
841+ ( filepath : string | Buffer ,
842+ cb : ( err : Error | null , stats : { } ) => void ) => {
843+ if ( filepath === path . join ( root , 'package.json' ) ) {
844+ // The key point is that looking for `package.json` in the
845+ // root directory does not cause an error.
846+ return cb ( null , { } ) ;
847+ }
848+ fs . stat ( filepath , cb ) ;
849+ }
850+ }
851+ } ) ;
852+ const config = extend ( { } , defaultConfig , { workingDirectory : root } ) ;
853+ const debuglet = new mockedDebuglet . Debuglet ( debug , config ) ;
854+ let text = '' ;
855+ debuglet . logger . error = ( str : string ) => {
856+ text += str ;
857+ } ;
858+
859+ debuglet . on ( 'initError' , ( err : Error ) => {
860+ const errorMessage = 'The working directory is a root ' +
861+ 'directory. Disabling to avoid a scan of the entire filesystem ' +
862+ 'for JavaScript files. Use config \allowRootAsWorkingDirectory` ' +
863+ 'if you really want to do this.' ;
864+ assert . ok ( err ) ;
865+ assert . strictEqual ( err . message , errorMessage ) ;
866+ assert . ok ( text . includes ( errorMessage ) ) ;
867+ done ( ) ;
868+ } ) ;
869+
870+ debuglet . once ( 'started' , ( ) => {
871+ assert . fail (
872+ 'Should not start if workingDirectory is a root directory' ) ;
873+ } ) ;
874+
875+ debuglet . start ( ) ;
876+ } ) ;
877+
878+ it ( 'should be able to force the workingDirectory to be a root directory' ,
879+ ( done ) => {
880+ const root = path . sep ;
881+ // Act like the root directory contains a `package.json` file
882+ const mockedDebuglet = proxyquire ( '../src/agent/debuglet' , {
883+ fs : {
884+ stat :
885+ ( filepath : string | Buffer ,
886+ cb : ( err : Error | null , stats : { } ) => void ) => {
887+ if ( filepath === path . join ( root , 'package.json' ) ) {
888+ return cb ( null , { } ) ;
889+ }
890+ fs . stat ( filepath , cb ) ;
891+ }
892+ }
893+ } ) ;
894+
895+ // Don't actually scan the entire filesystem. Act like the filesystem
896+ // is empty.
897+ mockedDebuglet . Debuglet . findFiles =
898+ ( shouldHash : boolean , baseDir : string ) :
899+ Promise < FindFilesResult > => {
900+ assert . strictEqual ( baseDir , root ) ;
901+ return Promise . resolve ( {
902+ jsStats : { } ,
903+ mapFiles : [ ] ,
904+ errors : new Map < string , Error > ( )
905+ } ) ;
906+ } ;
907+
908+ // Act like the debuglet can get a project id
909+ mockedDebuglet . Debuglet . getProjectId = ( ) => 'some-project-id' ;
910+
911+ // No need to restore `findFiles` and `getProjectId` because we are
912+ // modifying a mocked version of `Debuglet` not `Debuglet` itself.
913+
914+ const config = extend (
915+ { } , defaultConfig ,
916+ { workingDirectory : root , allowRootAsWorkingDirectory : true } ) ;
917+ const debug = new Debug ( { } , packageInfo ) ;
918+ const debuglet = new mockedDebuglet . Debuglet ( debug , config ) ;
919+
920+ debuglet . on ( 'initError' , ( err : Error ) => {
921+ assert . ifError ( err ) ;
922+ done ( ) ;
923+ } ) ;
924+
925+ debuglet . once ( 'started' , ( ) => {
926+ debuglet . stop ( ) ;
927+ done ( ) ;
928+ } ) ;
929+
930+ debuglet . start ( ) ;
931+ } ) ;
932+
817933 it ( 'should register successfully otherwise' , ( done ) => {
818934 const debug = new Debug (
819935 { projectId : 'fake-project' , credentials : fakeCredentials } ,
0 commit comments