33 *
44 * @vitest -environment node
55 */
6+
7+ import { Readable } from 'node:stream'
68import {
79 authMockFns ,
810 createMockRequest ,
@@ -24,15 +26,15 @@ const {
2426 mockGetStorageProvider,
2527 mockIsUsingCloudStorage,
2628 mockIsSupportedFileType,
27- mockParseFile,
2829 mockParseBuffer,
2930 mockPdfParseBuffer,
31+ mockCreateReadStream,
3032 mockFsAccess,
3133 mockFsStat,
32- mockFsReadFile,
3334 mockFsWriteFile,
3435 mockJoin,
3536 actualPath,
37+ mockUploadExecutionFile,
3638 mockUploadWorkspaceFile,
3739 mockReadWorkspaceFileNameByKey,
3840} = vi . hoisted ( ( ) => {
@@ -44,10 +46,6 @@ const {
4446 mockGetStorageProvider : vi . fn ( ) . mockReturnValue ( 's3' ) ,
4547 mockIsUsingCloudStorage : vi . fn ( ) . mockReturnValue ( true ) ,
4648 mockIsSupportedFileType : vi . fn ( ) . mockReturnValue ( true ) ,
47- mockParseFile : vi . fn ( ) . mockResolvedValue ( {
48- content : 'parsed content' ,
49- metadata : { pageCount : 1 } ,
50- } ) ,
5149 mockParseBuffer : vi . fn ( ) . mockResolvedValue ( {
5250 content : 'parsed buffer content' ,
5351 metadata : { pageCount : 1 } ,
@@ -56,9 +54,9 @@ const {
5654 content : 'parsed PDF content' ,
5755 metadata : { pageCount : 1 } ,
5856 } ) ,
57+ mockCreateReadStream : vi . fn ( ) ,
5958 mockFsAccess : vi . fn ( ) . mockResolvedValue ( undefined ) ,
6059 mockFsStat : vi . fn ( ) . mockImplementation ( ( ) => ( { isFile : ( ) => true , size : 17 } ) ) ,
61- mockFsReadFile : vi . fn ( ) . mockResolvedValue ( Buffer . from ( 'test file content' ) ) ,
6260 mockFsWriteFile : vi . fn ( ) . mockResolvedValue ( undefined ) ,
6361 mockJoin : vi . fn ( ( ...args : string [ ] ) : string => {
6462 if ( args [ 0 ] === '/test/uploads' ) {
@@ -67,6 +65,7 @@ const {
6765 return actualPath . join ( ...args )
6866 } ) ,
6967 actualPath,
68+ mockUploadExecutionFile : vi . fn ( ) ,
7069 mockUploadWorkspaceFile : vi
7170 . fn ( )
7271 . mockImplementation (
@@ -98,10 +97,13 @@ vi.mock('@/lib/uploads', () => ({
9897
9998vi . mock ( '@/lib/file-parsers' , ( ) => ( {
10099 isSupportedFileType : mockIsSupportedFileType ,
101- parseFile : mockParseFile ,
102100 parseBuffer : mockParseBuffer ,
103101} ) )
104102
103+ vi . mock ( 'node:fs' , ( ) => ( {
104+ createReadStream : mockCreateReadStream ,
105+ } ) )
106+
105107vi . mock ( '@/lib/file-parsers/pdf-parser' , ( ) => ( {
106108 PdfParser : class {
107109 parseBuffer ( ...args : Parameters < typeof mockPdfParseBuffer > ) {
@@ -131,7 +133,7 @@ vi.mock('@/lib/core/utils/logging', () => ({
131133} ) )
132134
133135vi . mock ( '@/lib/uploads/contexts/execution' , ( ) => ( {
134- uploadExecutionFile : vi . fn ( ) ,
136+ uploadExecutionFile : mockUploadExecutionFile ,
135137} ) )
136138
137139vi . mock ( '@/lib/uploads/contexts/workspace/workspace-file-manager' , ( ) => ( {
@@ -152,12 +154,10 @@ vi.mock('fs/promises', () => ({
152154 default : {
153155 access : mockFsAccess ,
154156 stat : mockFsStat ,
155- readFile : mockFsReadFile ,
156157 writeFile : mockFsWriteFile ,
157158 } ,
158159 access : mockFsAccess ,
159160 stat : mockFsStat ,
160- readFile : mockFsReadFile ,
161161 writeFile : mockFsWriteFile ,
162162} ) )
163163
@@ -241,14 +241,19 @@ describe('file parser operation', () => {
241241 storageServiceMockFns . mockHasCloudStorage . mockReturnValue ( true )
242242 storageServiceMockFns . mockDownloadFile . mockResolvedValue ( Buffer . from ( 'test file content' ) )
243243 mockFsStat . mockResolvedValue ( { isFile : ( ) => true , size : 17 } )
244- mockFsReadFile . mockResolvedValue ( Buffer . from ( 'test file content' ) )
244+ mockCreateReadStream . mockImplementation ( ( ) => Readable . from ( [ Buffer . from ( 'test file content' ) ] ) )
245245 mockIsSupportedFileType . mockReturnValue ( true )
246+ mockUploadExecutionFile . mockResolvedValue ( {
247+ id : 'file_test' ,
248+ name : 'report.pdf' ,
249+ url : '/api/files/serve/execution/report.pdf' ,
250+ size : 17 ,
251+ type : 'application/pdf' ,
252+ key : 'execution/report.pdf' ,
253+ context : 'execution' ,
254+ } )
246255 mockUploadWorkspaceFile . mockClear ( )
247256 mockReadWorkspaceFileNameByKey . mockResolvedValue ( { name : null } )
248- mockParseFile . mockResolvedValue ( {
249- content : 'parsed content' ,
250- metadata : { pageCount : 1 } ,
251- } )
252257 mockParseBuffer . mockResolvedValue ( {
253258 content : 'parsed buffer content' ,
254259 metadata : { pageCount : 1 } ,
@@ -422,21 +427,39 @@ describe('file parser operation', () => {
422427 } )
423428 } )
424429
425- it ( 'forwards request cancellation to local file parsing and reads ' , async ( ) => {
430+ it ( 'parses and uploads one bounded local- file snapshot ' , async ( ) => {
426431 setupFileApiMocks ( {
427432 cloudEnabled : false ,
428433 storageProvider : 'local' ,
429434 authenticated : true ,
430435 } )
436+ mockFsStat . mockResolvedValue ( { isFile : ( ) => true , size : 3 } )
431437 const req = createMockRequest ( 'POST' , {
432438 filePath : 'workspace/report.pdf' ,
433439 } )
434440
435441 const response = await POST ( req )
436442
443+ const data = await response . json ( )
444+ const parsedBuffer = mockParseBuffer . mock . calls [ 0 ] [ 0 ]
445+
437446 expect ( response . status ) . toBe ( 200 )
438- expect ( mockParseFile ) . toHaveBeenCalledWith ( expect . any ( String ) , { signal : req . signal } )
439- expect ( mockFsReadFile ) . toHaveBeenCalledWith ( expect . any ( String ) , { signal : req . signal } )
447+ expect ( data . output ) . toMatchObject ( {
448+ content : 'parsed buffer content' ,
449+ fileType : 'application/pdf' ,
450+ size : 17 ,
451+ } )
452+ expect ( mockCreateReadStream ) . toHaveBeenCalledWith ( '/test/uploads/workspace/report.pdf' )
453+ expect ( mockCreateReadStream ) . toHaveBeenCalledOnce ( )
454+ expect ( mockParseBuffer ) . toHaveBeenCalledWith ( parsedBuffer , 'pdf' , { signal : req . signal } )
455+ expect ( mockParseBuffer ) . toHaveBeenCalledOnce ( )
456+ expect ( mockUploadExecutionFile ) . toHaveBeenCalledWith (
457+ expect . any ( Object ) ,
458+ parsedBuffer ,
459+ 'report.pdf' ,
460+ 'application/pdf' ,
461+ 'test-user-id'
462+ )
440463 } )
441464
442465 it ( 'should reject parser complexity limits instead of returning raw text' , async ( ) => {
@@ -740,7 +763,9 @@ describe('file parser operation', () => {
740763 expect ( response . status ) . toBe ( 200 )
741764 expect ( data . success ) . toBe ( false )
742765 expect ( data . error ) . toContain ( 'too large' )
743- expect ( mockFsReadFile ) . not . toHaveBeenCalled ( )
766+ expect ( mockCreateReadStream ) . not . toHaveBeenCalled ( )
767+ expect ( mockParseBuffer ) . not . toHaveBeenCalled ( )
768+ expect ( mockUploadExecutionFile ) . not . toHaveBeenCalled ( )
744769 } )
745770
746771 it ( 'should process execution file URLs with context query param' , async ( ) => {
0 commit comments