@@ -13,14 +13,41 @@ import {
13
13
parseIgnoreContent ,
14
14
searchFiles ,
15
15
} from '../../../src/core/file/fileSearch.js' ;
16
+ import { PermissionError } from '../../../src/core/file/permissionCheck.js' ;
17
+ import { RepomixError } from '../../../src/shared/errorHandle.js' ;
16
18
import { createMockConfig , isWindows } from '../../testing/testUtils.js' ;
17
19
20
+ import { checkDirectoryPermissions } from '../../../src/core/file/permissionCheck.js' ;
21
+
18
22
vi . mock ( 'fs/promises' ) ;
19
23
vi . mock ( 'globby' ) ;
24
+ vi . mock ( '../../../src/core/file/permissionCheck.js' , ( ) => ( {
25
+ checkDirectoryPermissions : vi . fn ( ) ,
26
+ PermissionError : class extends Error {
27
+ constructor (
28
+ message : string ,
29
+ public readonly path : string ,
30
+ public readonly code ?: string ,
31
+ ) {
32
+ super ( message ) ;
33
+ this . name = 'PermissionError' ;
34
+ }
35
+ } ,
36
+ } ) ) ;
20
37
21
38
describe ( 'fileSearch' , ( ) => {
22
39
beforeEach ( ( ) => {
23
40
vi . resetAllMocks ( ) ;
41
+ // Default mock for fs.stat to assume directory exists and is a directory
42
+ vi . mocked ( fs . stat ) . mockResolvedValue ( {
43
+ isDirectory : ( ) => true ,
44
+ isFile : ( ) => false ,
45
+ } as Stats ) ;
46
+ // Default mock for checkDirectoryPermissions
47
+ vi . mocked ( checkDirectoryPermissions ) . mockResolvedValue ( {
48
+ hasAllPermission : true ,
49
+ details : { read : true , write : true , execute : true } ,
50
+ } ) ;
24
51
} ) ;
25
52
26
53
describe ( 'getIgnoreFilePaths' , ( ) => {
@@ -204,6 +231,15 @@ node_modules
204
231
describe ( 'filterFiles' , ( ) => {
205
232
beforeEach ( ( ) => {
206
233
vi . resetAllMocks ( ) ;
234
+ // Re-establish default mocks after reset
235
+ vi . mocked ( fs . stat ) . mockResolvedValue ( {
236
+ isDirectory : ( ) => true ,
237
+ isFile : ( ) => false ,
238
+ } as Stats ) ;
239
+ vi . mocked ( checkDirectoryPermissions ) . mockResolvedValue ( {
240
+ hasAllPermission : true ,
241
+ details : { read : true , write : true , execute : true } ,
242
+ } ) ;
207
243
} ) ;
208
244
209
245
test ( 'should call globby with correct parameters' , async ( ) => {
@@ -311,12 +347,24 @@ node_modules
311
347
// Mock .git file content for worktree
312
348
const gitWorktreeContent = 'gitdir: /path/to/main/repo/.git/worktrees/feature-branch' ;
313
349
314
- // Mock fs.stat and fs.readFile for .git file
315
- vi . mocked ( fs . stat ) . mockResolvedValue ( {
316
- isFile : ( ) => true ,
317
- } as Stats ) ;
350
+ // Mock fs.stat - first call for rootDir, subsequent calls for .git file
351
+ vi . mocked ( fs . stat )
352
+ . mockResolvedValueOnce ( {
353
+ isDirectory : ( ) => true ,
354
+ isFile : ( ) => false ,
355
+ } as Stats )
356
+ . mockResolvedValue ( {
357
+ isFile : ( ) => true ,
358
+ isDirectory : ( ) => false ,
359
+ } as Stats ) ;
318
360
vi . mocked ( fs . readFile ) . mockResolvedValue ( gitWorktreeContent ) ;
319
361
362
+ // Override checkDirectoryPermissions mock for this test
363
+ vi . mocked ( checkDirectoryPermissions ) . mockResolvedValue ( {
364
+ hasAllPermission : true ,
365
+ details : { read : true , write : true , execute : true } ,
366
+ } ) ;
367
+
320
368
// Mock globby to return some test files
321
369
vi . mocked ( globby ) . mockResolvedValue ( [ 'file1.js' , 'file2.js' ] ) ;
322
370
@@ -345,9 +393,21 @@ node_modules
345
393
346
394
test ( 'should handle regular git repository correctly' , async ( ) => {
347
395
// Mock .git as a directory
348
- vi . mocked ( fs . stat ) . mockResolvedValue ( {
349
- isFile : ( ) => false ,
350
- } as Stats ) ;
396
+ vi . mocked ( fs . stat )
397
+ . mockResolvedValueOnce ( {
398
+ isDirectory : ( ) => true ,
399
+ isFile : ( ) => false ,
400
+ } as Stats )
401
+ . mockResolvedValue ( {
402
+ isFile : ( ) => false ,
403
+ isDirectory : ( ) => true ,
404
+ } as Stats ) ;
405
+
406
+ // Override checkDirectoryPermissions mock for this test
407
+ vi . mocked ( checkDirectoryPermissions ) . mockResolvedValue ( {
408
+ hasAllPermission : true ,
409
+ details : { read : true , write : true , execute : true } ,
410
+ } ) ;
351
411
352
412
// Mock globby to return some test files
353
413
vi . mocked ( globby ) . mockResolvedValue ( [ 'file1.js' , 'file2.js' ] ) ;
@@ -453,4 +513,54 @@ node_modules
453
513
expect ( normalizeGlobPattern ( '**/folder/**/*' ) ) . toBe ( '**/folder/**/*' ) ;
454
514
} ) ;
455
515
} ) ;
516
+
517
+ describe ( 'searchFiles path validation' , ( ) => {
518
+ test ( 'should throw error when target path does not exist' , async ( ) => {
519
+ const error = new Error ( 'ENOENT' ) as Error & { code : string } ;
520
+ error . code = 'ENOENT' ;
521
+ vi . mocked ( fs . stat ) . mockRejectedValue ( error ) ;
522
+
523
+ const mockConfig = createMockConfig ( ) ;
524
+
525
+ await expect ( searchFiles ( '/nonexistent/path' , mockConfig ) ) . rejects . toThrow ( RepomixError ) ;
526
+ await expect ( searchFiles ( '/nonexistent/path' , mockConfig ) ) . rejects . toThrow (
527
+ 'Target path does not exist: /nonexistent/path' ,
528
+ ) ;
529
+ } ) ;
530
+
531
+ test ( 'should throw PermissionError when access is denied' , async ( ) => {
532
+ const error = new Error ( 'EPERM' ) as Error & { code : string } ;
533
+ error . code = 'EPERM' ;
534
+ vi . mocked ( fs . stat ) . mockRejectedValue ( error ) ;
535
+
536
+ const mockConfig = createMockConfig ( ) ;
537
+
538
+ await expect ( searchFiles ( '/forbidden/path' , mockConfig ) ) . rejects . toThrow ( PermissionError ) ;
539
+ } ) ;
540
+
541
+ test ( 'should throw error when target path is a file, not a directory' , async ( ) => {
542
+ vi . mocked ( fs . stat ) . mockResolvedValue ( {
543
+ isDirectory : ( ) => false ,
544
+ isFile : ( ) => true ,
545
+ } as Stats ) ;
546
+
547
+ const mockConfig = createMockConfig ( ) ;
548
+
549
+ await expect ( searchFiles ( '/path/to/file.txt' , mockConfig ) ) . rejects . toThrow ( RepomixError ) ;
550
+ await expect ( searchFiles ( '/path/to/file.txt' , mockConfig ) ) . rejects . toThrow (
551
+ 'Target path is not a directory: /path/to/file.txt. Please specify a directory path, not a file path.' ,
552
+ ) ;
553
+ } ) ;
554
+
555
+ test ( 'should succeed when target path is a valid directory' , async ( ) => {
556
+ vi . mocked ( globby ) . mockResolvedValue ( [ 'test.js' ] ) ;
557
+
558
+ const mockConfig = createMockConfig ( ) ;
559
+
560
+ const result = await searchFiles ( '/valid/directory' , mockConfig ) ;
561
+
562
+ expect ( result . filePaths ) . toEqual ( [ 'test.js' ] ) ;
563
+ expect ( result . emptyDirPaths ) . toEqual ( [ ] ) ;
564
+ } ) ;
565
+ } ) ;
456
566
} ) ;
0 commit comments