@@ -441,5 +441,168 @@ describe('listFiles Handler (Integration)', () => {
441441 } ) ;
442442 } ) ;
443443
444- // Add more tests...
444+ it ( 'should handle stat errors gracefully during non-recursive list' , async ( ) => {
445+ if ( ! tempTestDir ) throw new Error ( 'Temp directory not created' ) ;
446+ const testDirPathRelative = path . relative ( process . cwd ( ) , tempTestDir ! ) ;
447+
448+ // Create a file and a potentially problematic entry (like a broken symlink simulation)
449+ await fsPromises . writeFile (
450+ path . join ( tempTestDir , 'goodFile.txt' ) ,
451+ 'content' ,
452+ ) ;
453+ // We'll mock readdir to return an entry, and stat to fail for that entry
454+ const mockReaddir = mockDependencies . readdir as Mock ;
455+ mockReaddir . mockResolvedValue ( [
456+ { name : 'goodFile.txt' , isDirectory : ( ) => false , isFile : ( ) => true } ,
457+ {
458+ name : 'badEntry' ,
459+ isDirectory : ( ) => false ,
460+ isFile : ( ) => false ,
461+ isSymbolicLink : ( ) => true ,
462+ } , // Simulate needing stat
463+ ] ) ;
464+
465+ const mockStat = mockDependencies . stat as Mock ;
466+ mockStat . mockImplementation ( async ( p : PathLike ) => {
467+ if ( p . toString ( ) . endsWith ( 'badEntry' ) ) {
468+ throw new Error ( 'Mocked stat failure for bad entry' ) ;
469+ }
470+ // Use actual stat for the good file
471+ const actualFsPromises =
472+ await vi . importActual < typeof fsPromises > ( 'fs/promises' ) ;
473+ return actualFsPromises . stat ( p ) ;
474+ } ) ;
475+
476+ const args = {
477+ path : testDirPathRelative ,
478+ recursive : false ,
479+ include_stats : false ,
480+ } ;
481+ const result = await handleListFilesFunc ( mockDependencies , args ) ;
482+ const resultData = JSON . parse ( result . content [ 0 ] . text ) ;
483+
484+ // Should still list the good file, and the bad entry (assuming not a dir)
485+ expect ( resultData ) . toHaveLength ( 2 ) ;
486+ expect ( resultData ) . toEqual (
487+ expect . arrayContaining ( [
488+ `${ testDirPathRelative } /goodFile.txt` . replace ( / \\ \\ / g, '/' ) ,
489+ `${ testDirPathRelative } /badEntry` . replace ( / \\ \\ / g, '/' ) , // Assumes not a dir if stat fails
490+ ] ) ,
491+ ) ;
492+ } ) ;
493+
494+ it ( 'should skip current directory entry (.) when returned by glob' , async ( ) => {
495+ if ( ! tempTestDir ) throw new Error ( 'Temp directory not created' ) ;
496+ const testDirPathRelative = path . relative ( process . cwd ( ) , tempTestDir ! ) ;
497+ await fsPromises . writeFile ( path . join ( tempTestDir , 'file1.txt' ) , 'content' ) ;
498+
499+ // Mock glob to return '.' along with the file
500+ mockGlob . mockResolvedValue ( [ '.' , 'file1.txt' ] ) ;
501+
502+ const args = {
503+ path : testDirPathRelative ,
504+ recursive : false ,
505+ include_stats : true ,
506+ } ; // Use glob path
507+ const result = await handleListFilesFunc ( mockDependencies , args ) ;
508+ const resultData = JSON . parse ( result . content [ 0 ] . text ) ;
509+
510+ expect ( resultData ) . toHaveLength ( 1 ) ; // '.' should be skipped
511+ expect ( resultData [ 0 ] . path ) . toBe (
512+ `${ testDirPathRelative } /file1.txt` . replace ( / \\ \\ / g, '/' ) ,
513+ ) ;
514+ } ) ;
515+
516+ it ( 'should handle stat errors within glob results when include_stats is true' , async ( ) => {
517+ if ( ! tempTestDir ) throw new Error ( 'Temp directory not created' ) ;
518+ const testDirPathRelative = path . relative ( process . cwd ( ) , tempTestDir ! ) ;
519+ await fsPromises . writeFile ( path . join ( tempTestDir , 'file1.txt' ) , 'content' ) ;
520+ await fsPromises . writeFile (
521+ path . join ( tempTestDir , 'file2-stat-error.txt' ) ,
522+ 'content2' ,
523+ ) ;
524+
525+ // Mock glob to return both files
526+ mockGlob . mockResolvedValue ( [ 'file1.txt' , 'file2-stat-error.txt' ] ) ;
527+
528+ // Mock stat to fail for the second file
529+ const mockStat = mockDependencies . stat as Mock ;
530+ mockStat . mockImplementation ( async ( p : PathLike ) => {
531+ if ( p . toString ( ) . endsWith ( 'file2-stat-error.txt' ) ) {
532+ throw new Error ( 'Mocked stat error for glob' ) ;
533+ }
534+ const actualFsPromises =
535+ await vi . importActual < typeof fsPromises > ( 'fs/promises' ) ;
536+ return actualFsPromises . stat ( p ) ;
537+ } ) ;
538+
539+ const args = {
540+ path : testDirPathRelative ,
541+ recursive : false ,
542+ include_stats : true ,
543+ } ; // Use glob path
544+ const result = await handleListFilesFunc ( mockDependencies , args ) ;
545+ const resultData = JSON . parse ( result . content [ 0 ] . text ) ;
546+
547+ expect ( resultData ) . toHaveLength ( 2 ) ;
548+ const file1Result = resultData . find ( ( r : any ) =>
549+ r . path . endsWith ( 'file1.txt' ) ,
550+ ) ;
551+ const file2Result = resultData . find ( ( r : any ) =>
552+ r . path . endsWith ( 'file2-stat-error.txt' ) ,
553+ ) ;
554+
555+ expect ( file1Result ?. stats ?. error ) . toBeUndefined ( ) ;
556+ expect ( file2Result ?. stats ?. error ) . toMatch (
557+ / C o u l d n o t g e t s t a t s : M o c k e d s t a t e r r o r f o r g l o b / ,
558+ ) ;
559+ } ) ;
560+
561+ it ( 'should throw McpError if glob itself throws an error' , async ( ) => {
562+ if ( ! tempTestDir ) throw new Error ( 'Temp directory not created' ) ;
563+ const testDirPathRelative = path . relative ( process . cwd ( ) , tempTestDir ! ) ;
564+ const globError = new Error ( 'Internal glob failure' ) ;
565+ mockGlob . mockRejectedValue ( globError ) ;
566+
567+ const args = {
568+ path : testDirPathRelative ,
569+ recursive : true ,
570+ include_stats : true ,
571+ } ; // Use glob path
572+
573+ await expect ( handleListFilesFunc ( mockDependencies , args ) ) . rejects . toThrow (
574+ McpError ,
575+ ) ;
576+ await expect (
577+ handleListFilesFunc ( mockDependencies , args ) ,
578+ ) . rejects . toMatchObject ( {
579+ code : ErrorCode . InternalError ,
580+ message : expect . stringContaining (
581+ 'Failed to list files using glob: Internal glob failure' ,
582+ ) ,
583+ } ) ;
584+ } ) ;
585+
586+ it ( 'should handle generic errors during initial stat (non-ENOENT)' , async ( ) => {
587+ if ( ! tempTestDir ) throw new Error ( 'Temp directory not created' ) ;
588+ const testDirPathRelative = path . relative ( process . cwd ( ) , tempTestDir ! ) ;
589+ const genericError = new Error ( 'Generic stat failure' ) ;
590+ ( mockDependencies . stat as Mock ) . mockRejectedValue ( genericError ) ;
591+
592+ const args = { path : testDirPathRelative } ;
593+
594+ await expect ( handleListFilesFunc ( mockDependencies , args ) ) . rejects . toThrow (
595+ McpError ,
596+ ) ;
597+ await expect (
598+ handleListFilesFunc ( mockDependencies , args ) ,
599+ ) . rejects . toMatchObject ( {
600+ code : ErrorCode . InternalError ,
601+ message : expect . stringContaining (
602+ 'Failed to process path: Generic stat failure' ,
603+ ) ,
604+ } ) ;
605+ } ) ;
606+
607+ // Add more tests..." // Keep this line for potential future additions
445608} ) ;
0 commit comments