@@ -705,4 +705,50 @@ def stream_with_just_write.write(bytes)
705705 zip . close
706706 } . not_to raise_error
707707 end
708+
709+ it "handles rollback when second entry fails before header is written, preserving first entry" do
710+ # This test reproduces the bug where rollback! uses stale values from a previous
711+ # successful entry when a new entry fails before writing a header.
712+ # When write_file fails before the Heuristic calls write_deflated_file/write_stored_file,
713+ # rollback! should not affect the previous successful entry.
714+ zip_file = ManagedTempfile . new ( "zipp" )
715+ zip = described_class . new ( zip_file )
716+
717+ # First entry succeeds
718+ zip . write_file ( "success.txt" ) do |sink |
719+ sink << "successful content"
720+ end
721+
722+ # Second entry fails before header is written (exception in Heuristic before decide)
723+ expect {
724+ zip . write_file ( "fail.txt" ) do |sink |
725+ # Raise an error before any data is written, so the Heuristic hasn't decided
726+ # which compression method to use yet, and no header has been written
727+ raise "Invalid URL or other error"
728+ end
729+ } . to raise_error ( "Invalid URL or other error" )
730+
731+ # rollback! should not affect the first entry
732+ expect {
733+ zip . rollback!
734+ } . not_to raise_error
735+
736+ # Should be able to close successfully
737+ expect {
738+ zip . close
739+ } . not_to raise_error
740+
741+ zip_file . flush
742+
743+ # Verify the final ZIP file contains only the first entry with correct content
744+ per_filename = { }
745+ Zip ::File . open ( zip_file . path ) do |zipfile |
746+ zipfile . each do |entry |
747+ per_filename [ entry . name ] = entry . get_input_stream . read
748+ end
749+ end
750+
751+ expect ( per_filename . size ) . to eq ( 1 )
752+ expect ( per_filename [ "success.txt" ] ) . to eq ( "successful content" )
753+ end
708754end
0 commit comments