11import { describe , expect , test , vi , beforeEach } from 'vitest'
2+ import { join } from 'path'
23import type { Response } from 'node-fetch'
34import type { ChildProcess } from 'child_process'
45
@@ -14,6 +15,7 @@ vi.mock('child_process', () => ({
1415vi . mock ( 'fs/promises' , ( ) => ( {
1516 readFile : vi . fn ( ) ,
1617 unlink : vi . fn ( ) ,
18+ mkdir : vi . fn ( ) ,
1719} ) )
1820
1921vi . mock ( '../../../../src/utils/command-helpers.js' , ( ) => ( {
@@ -199,7 +201,7 @@ describe('uploadSourceZip', () => {
199201
200202 expect ( mockChildProcess . execFile ) . toHaveBeenCalledWith (
201203 'zip' ,
202- expect . arrayContaining ( [ '-x' , 'node_modules' , '-x' , '.git' , '-x' , '.netlify' , '-x' , '.env' ] ) ,
204+ expect . arrayContaining ( [ '-x' , 'node_modules* ' , '-x' , '.git* ' , '-x' , '.netlify* ' , '-x' , '.env' ] ) ,
203205 expect . objectContaining ( {
204206 cwd : '/test/source' ,
205207 maxBuffer : 104857600 ,
@@ -368,4 +370,64 @@ describe('uploadSourceZip', () => {
368370
369371 expect ( result ) . toHaveProperty ( 'sourceZipFileName' )
370372 } )
373+
374+ test ( 'creates subdirectories when filename includes path' , async ( ) => {
375+ // Ensure OS platform mock returns non-Windows
376+ const mockOs = await import ( 'os' )
377+ vi . mocked ( mockOs . platform ) . mockReturnValue ( 'darwin' )
378+
379+ const { uploadSourceZip } = await import ( '../../../../src/utils/deploy/upload-source-zip.js' )
380+
381+ const mockFetch = await import ( 'node-fetch' )
382+ const mockChildProcess = await import ( 'child_process' )
383+ const mockFs = await import ( 'fs/promises' )
384+ const mockCommandHelpers = await import ( '../../../../src/utils/command-helpers.js' )
385+ const mockTempFile = await import ( '../../../../src/utils/temporary-file.js' )
386+
387+ vi . mocked ( mockFetch . default ) . mockResolvedValue ( {
388+ ok : true ,
389+ status : 200 ,
390+ statusText : 'OK' ,
391+ } as unknown as Response )
392+
393+ vi . mocked ( mockChildProcess . execFile ) . mockImplementation ( ( _command , _args , _options , callback ) => {
394+ if ( callback ) {
395+ callback ( null , '' , '' )
396+ }
397+ return { } as ChildProcess
398+ } )
399+
400+ vi . mocked ( mockFs . readFile ) . mockResolvedValue ( Buffer . from ( 'mock zip content' ) )
401+ vi . mocked ( mockFs . mkdir ) . mockResolvedValue ( undefined )
402+ vi . mocked ( mockCommandHelpers . log ) . mockImplementation ( ( ) => { } )
403+ vi . mocked ( mockTempFile . temporaryDirectory ) . mockReturnValue ( '/tmp/test-temp-dir' )
404+
405+ const mockStatusCb = vi . fn ( )
406+
407+ // Test with a filename that includes a subdirectory path (like the API provides)
408+ await uploadSourceZip ( {
409+ sourceDir : '/test/source' ,
410+ uploadUrl : 'https://s3.example.com/upload-url' ,
411+ filename : 'workspace-snapshots/source-abc123-def456.zip' ,
412+ statusCb : mockStatusCb ,
413+ } )
414+
415+ // Should create the subdirectory before attempting zip creation
416+ expect ( mockFs . mkdir ) . toHaveBeenCalledWith ( join ( '/tmp/test-temp-dir' , 'workspace-snapshots' ) , { recursive : true } )
417+
418+ // Should still call zip command with the full path
419+ expect ( mockChildProcess . execFile ) . toHaveBeenCalledWith (
420+ 'zip' ,
421+ expect . arrayContaining ( [
422+ '-r' ,
423+ join ( '/tmp/test-temp-dir' , 'workspace-snapshots' , 'source-abc123-def456.zip' ) ,
424+ '.' ,
425+ ] ) ,
426+ expect . objectContaining ( {
427+ cwd : '/test/source' ,
428+ maxBuffer : 104857600 ,
429+ } ) ,
430+ expect . any ( Function ) ,
431+ )
432+ } )
371433} )
0 commit comments