@@ -61,9 +61,11 @@ def source_file(name, contents: "// #{name}")
6161 end
6262
6363 it "copies bundle + assets into <cache>/<hash>/ in :copy mode" do
64- described_class . call ( cache_dir : cache_dir , current_hashes : [ ] , mode : :copy )
65-
6664 bundle_dir = File . join ( cache_dir , "abc123" )
65+ promoted_bundle_dir = File . join ( File . realpath ( cache_dir ) , "abc123" )
66+ expect { described_class . call ( cache_dir : cache_dir , current_hashes : [ ] , mode : :copy ) }
67+ . to output ( /Staged previous bundle hash into #{ Regexp . escape ( promoted_bundle_dir ) } / ) . to_stdout
68+
6769 expect ( File . exist? ( File . join ( bundle_dir , "abc123.js" ) ) ) . to be ( true )
6870 expect ( File . exist? ( File . join ( bundle_dir , "loadable-stats.json" ) ) ) . to be ( true )
6971 expect ( File . symlink? ( File . join ( bundle_dir , "abc123.js" ) ) ) . to be ( false )
@@ -378,6 +380,36 @@ def source_file(name, contents: "// #{name}")
378380 end
379381 end
380382
383+ context "when restore finds the bundle directory recreated by another process" do
384+ let ( :src_bundle ) { source_file ( "bundle-new.js" , contents : "// new bundle" ) }
385+ let ( :src_asset ) { source_file ( "loadable-stats.json" , contents : "{}" ) }
386+ let ( :bundle_dir ) { File . join ( cache_dir , "abc123" ) }
387+ let ( :existing_bundle ) { File . join ( bundle_dir , "abc123.js" ) }
388+
389+ before do
390+ FileUtils . mkdir_p ( bundle_dir )
391+ File . write ( existing_bundle , "// existing bundle" )
392+ allow ( adapter ) . to receive_messages ( previous_bundle_hashes : [ "abc123" ] )
393+ allow ( adapter ) . to receive ( :fetch ) . with ( "abc123" ) . and_return ( bundle : src_bundle , assets : [ src_asset ] )
394+ allow ( FileUtils ) . to receive ( :mv ) . and_wrap_original do |original , source , destination , *args |
395+ raise Errno ::EIO , "promotion failed" if source . to_s . include? ( "abc123.staging-" )
396+
397+ original . call ( source , destination , *args )
398+ end
399+ allow ( FileUtils ) . to receive ( :rm_rf ) . and_wrap_original do |original , path , *args |
400+ original . call ( path , *args )
401+ FileUtils . mkdir_p ( bundle_dir ) if path . to_s . end_with? ( "/abc123" )
402+ end
403+ end
404+
405+ it "warns instead of silently skipping backup restore" do
406+ expect { described_class . call ( cache_dir : cache_dir , current_hashes : [ ] , mode : :copy ) }
407+ . to output ( /Could not restore previous rolling-deploy bundle directory/ ) . to_stderr
408+
409+ expect ( Dir . children ( cache_dir ) . grep ( /abc123\. previous/ ) ) . not_to be_empty
410+ end
411+ end
412+
381413 context "when refreshing an existing seeded hash cannot remove the old backup after promotion" do
382414 let ( :src_bundle ) { source_file ( "bundle-new.js" , contents : "// new bundle" ) }
383415 let ( :src_asset ) { source_file ( "loadable-stats.json" , contents : "{}" ) }
@@ -405,25 +437,29 @@ def source_file(name, contents: "// #{name}")
405437 end
406438
407439 context "when stale temporary bundle directories are present" do
408- let ( :stale_staging_dir ) { File . join ( cache_dir , "abc123.staging-1234-deadbeef" ) }
409- let ( :fresh_previous_dir ) { File . join ( cache_dir , "abc123.previous-1234-feedface" ) }
440+ let ( :stale_staging_dir ) { File . join ( cache_dir , "abc123.staging-1234-deadbeef12" ) }
441+ let ( :fresh_previous_dir ) { File . join ( cache_dir , "abc123.previous-1234-feedface12" ) }
442+ let ( :hash_like_dir ) { File . join ( cache_dir , "release.staging-123-deadbeef" ) }
410443
411444 before do
412445 stub_const ( "ReactOnRailsPro::RollingDeployCacheStager::STALE_TEMP_DIR_TTL_SECONDS" , 60 )
413446 allow ( adapter ) . to receive_messages ( previous_bundle_hashes : [ ] )
414447 FileUtils . mkdir_p ( stale_staging_dir )
415448 FileUtils . mkdir_p ( fresh_previous_dir )
449+ FileUtils . mkdir_p ( hash_like_dir )
416450 old_time = Time . now - 120
417451 File . utime ( old_time , old_time , stale_staging_dir )
452+ File . utime ( old_time , old_time , hash_like_dir )
418453 end
419454
420- it "removes stale temp directories and keeps fresh ones " do
455+ it "removes stale temp directories while preserving hash-like names outside the temp pattern " do
421456 expect { described_class . call ( cache_dir : cache_dir , current_hashes : [ ] , mode : :copy ) }
422457 . to output ( /Removed stale rolling-deploy temp directory/ ) . to_stderr
423458 . and output ( /No previous bundle hashes/ ) . to_stdout
424459
425460 expect ( File . exist? ( stale_staging_dir ) ) . to be ( false )
426461 expect ( File . exist? ( fresh_previous_dir ) ) . to be ( true )
462+ expect ( File . exist? ( hash_like_dir ) ) . to be ( true )
427463 end
428464
429465 it "does not match real bundle hash dirs that look superficially similar" do
0 commit comments