@@ -504,6 +504,26 @@ describe('worker', () => {
504504 const bundle2Path = path . join ( serverBundleCachePathForTest ( ) , secondaryBundleHash , `${ secondaryBundleHash } .js` ) ;
505505 expect ( fs . existsSync ( bundle1Path ) ) . toBe ( true ) ;
506506 expect ( fs . existsSync ( bundle2Path ) ) . toBe ( true ) ;
507+
508+ // Verify the directory structure is correct
509+ const bundle1Dir = path . join ( bundlePathForTest ( ) , bundleHash ) ;
510+ const bundle2Dir = path . join ( bundlePathForTest ( ) , secondaryBundleHash ) ;
511+
512+ // Each bundle directory should contain: 1 bundle file + 2 assets = 3 files total
513+ const bundle1Files = fs . readdirSync ( bundle1Dir ) ;
514+ const bundle2Files = fs . readdirSync ( bundle2Dir ) ;
515+
516+ expect ( bundle1Files ) . toHaveLength ( 3 ) ; // bundle file + 2 assets
517+ expect ( bundle2Files ) . toHaveLength ( 3 ) ; // bundle file + 2 assets
518+
519+ // Verify the specific files exist in each directory
520+ expect ( bundle1Files ) . toContain ( `${ bundleHash } .js` ) ;
521+ expect ( bundle1Files ) . toContain ( 'loadable-stats.json' ) ;
522+ expect ( bundle1Files ) . toContain ( 'loadable-stats-other.json' ) ;
523+
524+ expect ( bundle2Files ) . toContain ( `${ secondaryBundleHash } .js` ) ;
525+ expect ( bundle2Files ) . toContain ( 'loadable-stats.json' ) ;
526+ expect ( bundle2Files ) . toContain ( 'loadable-stats-other.json' ) ;
507527 } ) ;
508528
509529 test ( 'post /upload-assets with only bundles (no assets)' , async ( ) => {
@@ -528,5 +548,128 @@ describe('worker', () => {
528548 // Verify bundle is placed in the correct directory
529549 const bundleFilePath = path . join ( serverBundleCachePathForTest ( ) , bundleHash , `${ bundleHash } .js` ) ;
530550 expect ( fs . existsSync ( bundleFilePath ) ) . toBe ( true ) ;
551+
552+ // Verify the directory structure is correct
553+ const bundleDir = path . join ( bundlePathForTest ( ) , bundleHash ) ;
554+ const files = fs . readdirSync ( bundleDir ) ;
555+
556+ // Should only contain the bundle file, no assets
557+ expect ( files ) . toHaveLength ( 1 ) ;
558+ expect ( files [ 0 ] ) . toBe ( `${ bundleHash } .js` ) ;
559+
560+ // Verify no asset files were accidentally copied
561+ expect ( files ) . not . toContain ( 'loadable-stats.json' ) ;
562+ expect ( files ) . not . toContain ( 'loadable-stats-other.json' ) ;
563+ } ) ;
564+
565+ test ( 'post /upload-assets with no assets and no bundles (empty request)' , async ( ) => {
566+ const bundleHash = 'empty-request-hash' ;
567+
568+ const app = worker ( {
569+ bundlePath : bundlePathForTest ( ) ,
570+ password : 'my_password' ,
571+ } ) ;
572+
573+ const form = formAutoContent ( {
574+ gemVersion,
575+ protocolVersion,
576+ password : 'my_password' ,
577+ targetBundles : [ bundleHash ] ,
578+ // No assets or bundles uploaded
579+ } ) ;
580+
581+ const res = await app . inject ( ) . post ( `/upload-assets` ) . payload ( form . payload ) . headers ( form . headers ) . end ( ) ;
582+ expect ( res . statusCode ) . toBe ( 200 ) ;
583+
584+ // Verify bundle directory is created
585+ const bundleDirectory = path . join ( bundlePathForTest ( ) , bundleHash ) ;
586+ expect ( fs . existsSync ( bundleDirectory ) ) . toBe ( true ) ;
587+
588+ // Verify no files were copied (since none were uploaded)
589+ const files = fs . readdirSync ( bundleDirectory ) ;
590+ expect ( files ) . toHaveLength ( 0 ) ;
591+ } ) ;
592+
593+ test ( 'post /upload-assets with duplicate bundle hash silently skips overwrite and returns 200' , async ( ) => {
594+ const bundleHash = 'duplicate-bundle-hash' ;
595+
596+ const app = worker ( {
597+ bundlePath : bundlePathForTest ( ) ,
598+ password : 'my_password' ,
599+ } ) ;
600+
601+ // First upload with bundle
602+ const form1 = formAutoContent ( {
603+ gemVersion,
604+ protocolVersion,
605+ password : 'my_password' ,
606+ targetBundles : [ bundleHash ] ,
607+ [ `bundle_${ bundleHash } ` ] : createReadStream ( getFixtureBundle ( ) ) ,
608+ } ) ;
609+
610+ const res1 = await app
611+ . inject ( )
612+ . post ( `/upload-assets` )
613+ . payload ( form1 . payload )
614+ . headers ( form1 . headers )
615+ . end ( ) ;
616+ expect ( res1 . statusCode ) . toBe ( 200 ) ;
617+ expect ( res1 . body ) . toBe ( '' ) ; // Empty body on success
618+
619+ // Verify first bundle was created correctly
620+ const bundleDir = path . join ( bundlePathForTest ( ) , bundleHash ) ;
621+ expect ( fs . existsSync ( bundleDir ) ) . toBe ( true ) ;
622+ const bundleFilePath = path . join ( bundleDir , `${ bundleHash } .js` ) ;
623+ expect ( fs . existsSync ( bundleFilePath ) ) . toBe ( true ) ;
624+
625+ // Get file stats to verify it's the first bundle
626+ const firstBundleStats = fs . statSync ( bundleFilePath ) ;
627+ const firstBundleSize = firstBundleStats . size ;
628+ const firstBundleModTime = firstBundleStats . mtime . getTime ( ) ;
629+
630+ // Second upload with the same bundle hash but different content
631+ // This logs: "File exists when trying to overwrite bundle... Assuming bundle written by other thread"
632+ // Then silently skips the overwrite operation and returns 200 success
633+ const form2 = formAutoContent ( {
634+ gemVersion,
635+ protocolVersion,
636+ password : 'my_password' ,
637+ targetBundles : [ bundleHash ] ,
638+ [ `bundle_${ bundleHash } ` ] : createReadStream ( getFixtureSecondaryBundle ( ) ) , // Different content
639+ } ) ;
640+
641+ const res2 = await app
642+ . inject ( )
643+ . post ( `/upload-assets` )
644+ . payload ( form2 . payload )
645+ . headers ( form2 . headers )
646+ . end ( ) ;
647+ expect ( res2 . statusCode ) . toBe ( 200 ) ; // Still returns 200 success (no error)
648+ expect ( res2 . body ) . toBe ( '' ) ; // Empty body, no error message returned to client
649+
650+ // Verify the bundle directory still exists
651+ expect ( fs . existsSync ( bundleDir ) ) . toBe ( true ) ;
652+
653+ // Verify the bundle file still exists
654+ expect ( fs . existsSync ( bundleFilePath ) ) . toBe ( true ) ;
655+
656+ // Verify the file was NOT overwritten (original bundle is preserved)
657+ const secondBundleStats = fs . statSync ( bundleFilePath ) ;
658+ const secondBundleSize = secondBundleStats . size ;
659+ const secondBundleModTime = secondBundleStats . mtime . getTime ( ) ;
660+
661+ // The file size should be the same as the first upload (no overwrite occurred)
662+ expect ( secondBundleSize ) . toBe ( firstBundleSize ) ;
663+
664+ // The modification time should be the same (file wasn't touched)
665+ expect ( secondBundleModTime ) . toBe ( firstBundleModTime ) ;
666+
667+ // Verify the directory only contains one file (the original bundle)
668+ const files = fs . readdirSync ( bundleDir ) ;
669+ expect ( files ) . toHaveLength ( 1 ) ;
670+ expect ( files [ 0 ] ) . toBe ( `${ bundleHash } .js` ) ;
671+
672+ // Verify the original content is preserved (62 bytes from bundle.js, not 84 from secondary-bundle.js)
673+ expect ( secondBundleSize ) . toBe ( 62 ) ; // Size of getFixtureBundle(), not getFixtureSecondaryBundle()
531674 } ) ;
532675} ) ;
0 commit comments