@@ -12,21 +12,28 @@ afterEach(async () => {
1212 await rm ( root , { recursive : true , force : true } )
1313} )
1414
15- it ( 'rejects missing paths and directory cycles with explicit errors before uploading' , async ( ) => {
16- expect (
17- await executeLocalFileRequest (
18- { operation : 'read' } ,
19- { toolName : 'read_local_file' , args : { path : join ( root , 'missing' ) } }
20- )
21- ) . toMatchObject ( { ok : false } )
22- await symlink ( root , join ( root , 'cycle' ) )
23- expect (
24- await executeLocalFileRequest (
25- { operation : 'manifest' } ,
26- { toolName : 'import_local_files' , args : { path : root , targetWorkspaceId : 'target' } }
27- )
28- ) . toMatchObject ( { ok : false , error : expect . stringContaining ( 'cycle' ) } )
29- } )
15+ // Creating a symlink on Windows needs Developer Mode or elevation, which the
16+ // test runner cannot assume.
17+ const NO_SYMLINKS = process . platform === 'win32'
18+
19+ it . skipIf ( NO_SYMLINKS ) (
20+ 'rejects missing paths and directory cycles with explicit errors before uploading' ,
21+ async ( ) => {
22+ expect (
23+ await executeLocalFileRequest (
24+ { operation : 'read' } ,
25+ { toolName : 'read_local_file' , args : { path : join ( root , 'missing' ) } }
26+ )
27+ ) . toMatchObject ( { ok : false } )
28+ await symlink ( root , join ( root , 'cycle' ) )
29+ expect (
30+ await executeLocalFileRequest (
31+ { operation : 'manifest' } ,
32+ { toolName : 'import_local_files' , args : { path : root , targetWorkspaceId : 'target' } }
33+ )
34+ ) . toMatchObject ( { ok : false , error : expect . stringContaining ( 'cycle' ) } )
35+ }
36+ )
3037
3138it ( 'refuses oversized import files before any workspace mutation or bulk allocation' , async ( ) => {
3239 const path = join ( root , 'large.bin' )
@@ -40,7 +47,7 @@ it('refuses oversized import files before any workspace mutation or bulk allocat
4047 ) . toMatchObject ( { ok : false , error : expect . stringContaining ( '64 MB' ) } )
4148} )
4249
43- it . each ( [ 'file' , 'directory' ] as const ) (
50+ it . skipIf ( NO_SYMLINKS ) . each ( [ 'file' , 'directory' ] as const ) (
4451 'rejects %s symlinks outside the import source during manifest and chunk reads' ,
4552 async ( kind ) => {
4653 const source = join ( root , 'selected' )
@@ -79,38 +86,41 @@ it.each(['file', 'directory'] as const)(
7986 }
8087)
8188
82- it ( 'supports internal symlinks and an explicitly selected symlink root, but rejects a retargeted child' , async ( ) => {
83- const source = join ( root , 'selected' )
84- await mkdir ( source )
85- await writeFile ( join ( source , 'notes.txt' ) , 'inside' )
86- await symlink ( join ( source , 'notes.txt' ) , join ( source , 'alias.txt' ) )
87- const selectedAlias = join ( root , 'selected-alias' )
88- await symlink ( source , selectedAlias )
89- const authorization = {
90- toolName : 'import_local_files' ,
91- args : { path : selectedAlias , targetWorkspaceId : 'target' } ,
92- }
93- const manifest = await executeLocalFileRequest ( { operation : 'manifest' } , authorization )
94- if ( ! manifest . ok || manifest . data . kind !== 'manifest' ) throw new Error ( 'Expected manifest' )
95- const entry = manifest . data . entries . find ( ( item ) => item . relativePath === 'alias.txt' ) !
96- const request = {
97- operation : 'chunk' ,
98- relativePath : 'alias.txt' ,
99- revision : entry . revision ,
100- offset : 0 ,
89+ it . skipIf ( NO_SYMLINKS ) (
90+ 'supports internal symlinks and an explicitly selected symlink root, but rejects a retargeted child' ,
91+ async ( ) => {
92+ const source = join ( root , 'selected' )
93+ await mkdir ( source )
94+ await writeFile ( join ( source , 'notes.txt' ) , 'inside' )
95+ await symlink ( join ( source , 'notes.txt' ) , join ( source , 'alias.txt' ) )
96+ const selectedAlias = join ( root , 'selected-alias' )
97+ await symlink ( source , selectedAlias )
98+ const authorization = {
99+ toolName : 'import_local_files' ,
100+ args : { path : selectedAlias , targetWorkspaceId : 'target' } ,
101+ }
102+ const manifest = await executeLocalFileRequest ( { operation : 'manifest' } , authorization )
103+ if ( ! manifest . ok || manifest . data . kind !== 'manifest' ) throw new Error ( 'Expected manifest' )
104+ const entry = manifest . data . entries . find ( ( item ) => item . relativePath === 'alias.txt' ) !
105+ const request = {
106+ operation : 'chunk' ,
107+ relativePath : 'alias.txt' ,
108+ revision : entry . revision ,
109+ offset : 0 ,
110+ }
111+ expect ( await executeLocalFileRequest ( request , authorization ) ) . toEqual ( {
112+ ok : true ,
113+ data : { kind : 'chunk' , bytes : new Uint8Array ( Buffer . from ( 'inside' ) ) , eof : true } ,
114+ } )
115+ await writeFile ( join ( root , 'outside.txt' ) , 'outside' )
116+ await rm ( join ( source , 'alias.txt' ) )
117+ await symlink ( join ( root , 'outside.txt' ) , join ( source , 'alias.txt' ) )
118+ expect ( await executeLocalFileRequest ( request , authorization ) ) . toMatchObject ( {
119+ ok : false ,
120+ error : expect . stringContaining ( 'outside this import source' ) ,
121+ } )
101122 }
102- expect ( await executeLocalFileRequest ( request , authorization ) ) . toEqual ( {
103- ok : true ,
104- data : { kind : 'chunk' , bytes : new Uint8Array ( Buffer . from ( 'inside' ) ) , eof : true } ,
105- } )
106- await writeFile ( join ( root , 'outside.txt' ) , 'outside' )
107- await rm ( join ( source , 'alias.txt' ) )
108- await symlink ( join ( root , 'outside.txt' ) , join ( source , 'alias.txt' ) )
109- expect ( await executeLocalFileRequest ( request , authorization ) ) . toMatchObject ( {
110- ok : false ,
111- error : expect . stringContaining ( 'outside this import source' ) ,
112- } )
113- } )
123+ )
114124
115125it ( 'preserves a UTF-8 BOM and rejects split offsets and limits that cannot fit a character' , async ( ) => {
116126 const path = join ( root , 'unicode.txt' )
0 commit comments