@@ -171,6 +171,38 @@ describe('UploadFileService', () => {
171171 expect ( emitProgress ) . toHaveBeenCalledTimes ( 2 ) ;
172172 uploadFileWithRetrySpy . mockRestore ( ) ;
173173 } ) ;
174+ it ( 'should skip files when parent folder is not found in folderMap' , async ( ) => {
175+ const bucket = 'test-bucket' ;
176+ const destinationFolderUuid = 'dest-uuid' ;
177+ const folderMap = new Map < string , string > ( ) ;
178+ const files = [
179+ createFileSystemNodeFixture ( {
180+ type : 'file' ,
181+ name : 'nested.txt' ,
182+ relativePath : 'subfolder/nested.txt' ,
183+ size : 100 ,
184+ } ) ,
185+ ] ;
186+ const { currentProgress, emitProgress } = createProgressFixtures ( ) ;
187+
188+ const uploadFileWithRetrySpy = vi . spyOn ( sut , 'uploadFileWithRetry' ) ;
189+
190+ const result = await sut . uploadFilesInChunks ( {
191+ network : mockNetworkFacade ,
192+ filesToUpload : files ,
193+ folderMap,
194+ bucket,
195+ destinationFolderUuid,
196+ currentProgress,
197+ emitProgress,
198+ } ) ;
199+
200+ expect ( result ) . toBe ( 0 ) ;
201+ expect ( logger . warn ) . toHaveBeenCalledWith ( 'Parent folder not found for subfolder/nested.txt, skipping...' ) ;
202+ expect ( uploadFileWithRetrySpy ) . not . toHaveBeenCalled ( ) ;
203+
204+ uploadFileWithRetrySpy . mockRestore ( ) ;
205+ } ) ;
174206 } ) ;
175207 describe ( 'uploadFileWithRetry' , ( ) => {
176208 const bucket = 'test-bucket' ;
@@ -255,5 +287,115 @@ describe('UploadFileService', () => {
255287
256288 vi . useRealTimers ( ) ;
257289 } ) ;
290+
291+ it ( 'should skip empty files and return null' , async ( ) => {
292+ const file = createFileSystemNodeFixture ( {
293+ type : 'file' ,
294+ name : 'empty.txt' ,
295+ relativePath : 'empty.txt' ,
296+ size : 0 ,
297+ } ) ;
298+
299+ vi . mocked ( stat ) . mockResolvedValue ( createMockStats ( 0 ) as Awaited < ReturnType < typeof stat > > ) ;
300+
301+ const result = await sut . uploadFileWithRetry ( {
302+ file,
303+ network : mockNetworkFacade ,
304+ bucket,
305+ parentFolderUuid : destinationFolderUuid ,
306+ } ) ;
307+
308+ expect ( result ) . toBeNull ( ) ;
309+ expect ( logger . warn ) . toHaveBeenCalledWith ( 'Skipping empty file: empty.txt' ) ;
310+ expect ( mockNetworkFacade . uploadFile ) . not . toHaveBeenCalled ( ) ;
311+ } ) ;
312+
313+ it ( 'should call tryUploadThumbnail when bufferStream is present' , async ( ) => {
314+ const mockBufferStream = { getBuffer : vi . fn ( ) } ;
315+ vi . mocked ( createFileStreamWithBuffer ) . mockReturnValue ( {
316+ fileStream : createMockReadStream ( ) as ReturnType < typeof createReadStream > ,
317+ bufferStream : mockBufferStream as unknown as ReturnType < typeof createFileStreamWithBuffer > [ 'bufferStream' ] ,
318+ } ) ;
319+
320+ const file = createFileSystemNodeFixture ( {
321+ type : 'file' ,
322+ name : 'image.png' ,
323+ relativePath : 'image.png' ,
324+ size : 1024 ,
325+ } ) ;
326+
327+ await sut . uploadFileWithRetry ( {
328+ file,
329+ network : mockNetworkFacade ,
330+ bucket,
331+ parentFolderUuid : destinationFolderUuid ,
332+ } ) ;
333+
334+ expect ( tryUploadThumbnail ) . toHaveBeenCalledWith ( {
335+ bufferStream : mockBufferStream ,
336+ fileType : 'png' ,
337+ userBucket : bucket ,
338+ fileUuid : 'mock-file-uuid' ,
339+ networkFacade : mockNetworkFacade ,
340+ } ) ;
341+ } ) ;
342+
343+ it ( 'should return null when file already exists' , async ( ) => {
344+ vi . mocked ( isAlreadyExistsError ) . mockReturnValue ( true ) ;
345+ vi . mocked ( mockNetworkFacade . uploadFile ) . mockImplementation ( ( _stream , _size , _bucket , callback ) => {
346+ callback ( new Error ( 'File already exists' ) , null ) ;
347+ return { stop : vi . fn ( ) } as unknown as ReturnType < typeof mockNetworkFacade . uploadFile > ;
348+ } ) ;
349+
350+ const file = createFileSystemNodeFixture ( {
351+ type : 'file' ,
352+ name : 'duplicate.txt' ,
353+ relativePath : 'duplicate.txt' ,
354+ size : 1024 ,
355+ } ) ;
356+
357+ const result = await sut . uploadFileWithRetry ( {
358+ file,
359+ network : mockNetworkFacade ,
360+ bucket,
361+ parentFolderUuid : destinationFolderUuid ,
362+ } ) ;
363+
364+ expect ( result ) . toBeNull ( ) ;
365+ expect ( logger . info ) . toHaveBeenCalledWith ( 'File duplicate.txt already exists, skipping...' ) ;
366+ } ) ;
367+
368+ it ( 'should return null after max retries exceeded' , async ( ) => {
369+ vi . useFakeTimers ( ) ;
370+ const file = createFileSystemNodeFixture ( {
371+ type : 'file' ,
372+ name : 'fail.txt' ,
373+ relativePath : 'fail.txt' ,
374+ size : 1024 ,
375+ } ) ;
376+ const error = new Error ( 'Network error' ) ;
377+
378+ vi . mocked ( mockNetworkFacade . uploadFile ) . mockImplementation ( ( _stream , _size , _bucket , callback ) => {
379+ callback ( error , null ) ;
380+ return { stop : vi . fn ( ) } as unknown as ReturnType < typeof mockNetworkFacade . uploadFile > ;
381+ } ) ;
382+
383+ const resultPromise = sut . uploadFileWithRetry ( {
384+ file,
385+ network : mockNetworkFacade ,
386+ bucket,
387+ parentFolderUuid : destinationFolderUuid ,
388+ } ) ;
389+
390+ await vi . runAllTimersAsync ( ) ;
391+
392+ const result = await resultPromise ;
393+
394+ expect ( result ) . toBeNull ( ) ;
395+ expect ( logger . error ) . toHaveBeenCalledWith ( 'Failed to upload file fail.txt after 3 attempts' ) ;
396+ expect ( mockNetworkFacade . uploadFile ) . toHaveBeenCalledTimes ( 3 ) ;
397+
398+ vi . useRealTimers ( ) ;
399+ } ) ;
258400 } ) ;
259401} ) ;
0 commit comments