From 9ee556d39bf132de1c286a7c887bdefc8ccc61e8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 10 Feb 2026 18:11:16 +0900 Subject: [PATCH 1/8] Merge rubygems-attestation-patch.rb from rubygems/release-gem --- lib/rubygems/commands/push_command.rb | 45 +++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index d2ce86703ba3..2168c5dc2f7d 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -92,6 +92,19 @@ def send_gem(name) private def send_push_request(name, args) + if RUBY_ENGINE == "jruby" || options[:attestations].any? || !attestation_supported_host? + return send_push_request_without_attestation(name, args) + end + + begin + send_push_request_with_attestation(name, args) + rescue StandardError => e + alert_warning "Failed to push with attestation, retrying without attestation.\n#{e.full_message}" + send_push_request_without_attestation(name, args) + end + end + + def send_push_request_without_attestation(name, args) scope = get_push_scope rubygems_api_request(*args, scope: scope) do |request| body = Gem.read_binary name @@ -109,6 +122,34 @@ def send_push_request(name, args) end end + def send_push_request_with_attestation(name, args) + attestation = attest!(name) + + rubygems_api_request(*args, scope: get_push_scope) do |request| + request.set_form([ + ["gem", Gem.read_binary(name), { filename: name, content_type: "application/octet-stream" }], + ["attestations", "[#{Gem.read_binary(attestation)}]", { content_type: "application/json" }], + ], "multipart/form-data") + request.add_field "Authorization", api_key + end + end + + def attest!(name) + require "open3" + + bundle = "#{name}.sigstore.json" + env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h + out, st = Open3.capture2e( + env, + Gem.ruby, "-S", "gem", "exec", + "sigstore-cli:0.2.2", "sign", name, "--bundle", bundle, + unsetenv_others: true + ) + raise Gem::Exception, "Failed to sign gem:\n\n#{out}" unless st.success? + + bundle + end + def get_hosts_for(name) gem_metadata = Gem::Package.new(name).spec.metadata @@ -122,6 +163,10 @@ def get_push_scope :push_rubygem end + def attestation_supported_host? + (@host || Gem.host) == "https://rubygems.org" + end + def get_attestations_part bundles = "[" + options[:attestations].map do |attestation| Gem.read_binary(attestation) From e658556bd2db382a83fbee6fcdf12fbda4b2d851 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 10 Feb 2026 18:26:18 +0900 Subject: [PATCH 2/8] Refactor push request to support attestations --- lib/rubygems/commands/push_command.rb | 37 ++++++++++----------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 2168c5dc2f7d..b8af8cfd9188 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -92,7 +92,7 @@ def send_gem(name) private def send_push_request(name, args) - if RUBY_ENGINE == "jruby" || options[:attestations].any? || !attestation_supported_host? + if RUBY_ENGINE == "jruby" || !attestation_supported_host? return send_push_request_without_attestation(name, args) end @@ -108,27 +108,27 @@ def send_push_request_without_attestation(name, args) scope = get_push_scope rubygems_api_request(*args, scope: scope) do |request| body = Gem.read_binary name - if options[:attestations].any? - request.set_form([ - ["gem", body, { filename: name, content_type: "application/octet-stream" }], - get_attestations_part, - ], "multipart/form-data") - else - request.body = body - request.add_field "Content-Type", "application/octet-stream" - request.add_field "Content-Length", request.body.size - end + request.body = body + request.add_field "Content-Type", "application/octet-stream" + request.add_field "Content-Length", request.body.size request.add_field "Authorization", api_key end end def send_push_request_with_attestation(name, args) - attestation = attest!(name) + attestations = if options[:attestations].any? + options[:attestations].map do |attestation| + Gem.read_binary(attestation) + end + else + [Gem.read_binary(attest!(name))] + end + bundles = "[" + attestations.join(",") + "]" rubygems_api_request(*args, scope: get_push_scope) do |request| request.set_form([ ["gem", Gem.read_binary(name), { filename: name, content_type: "application/octet-stream" }], - ["attestations", "[#{Gem.read_binary(attestation)}]", { content_type: "application/json" }], + ["attestations", bundles, { content_type: "application/json" }], ], "multipart/form-data") request.add_field "Authorization", api_key end @@ -166,15 +166,4 @@ def get_push_scope def attestation_supported_host? (@host || Gem.host) == "https://rubygems.org" end - - def get_attestations_part - bundles = "[" + options[:attestations].map do |attestation| - Gem.read_binary(attestation) - end.join(",") + "]" - [ - "attestations", - bundles, - { content_type: "application/json" }, - ] - end end From 3a9f4bf84e818625039891371a73e5fa182d3a56 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 10 Feb 2026 18:47:15 +0900 Subject: [PATCH 3/8] Added test for auto-attestation --- .../test_gem_commands_push_command.rb | 87 ++++++++++++++----- 1 file changed, 64 insertions(+), 23 deletions(-) diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 1477a7494714..1f357159d3d1 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -115,32 +115,44 @@ def test_execute_attestation assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class content_length = @fetcher.last_request["Content-Length"].to_i assert_equal content_length, @fetcher.last_request.body.length - assert_equal "multipart", @fetcher.last_request.main_type, @fetcher.last_request.content_type - assert_equal "form-data", @fetcher.last_request.sub_type - assert_include @fetcher.last_request.type_params, "boundary" - boundary = @fetcher.last_request.type_params["boundary"] + assert_attestation_multipart Gem.read_binary("#{@path}.sigstore.json") + end - parts = @fetcher.last_request.body.split(/(?:\r\n|\A)--#{Regexp.quote(boundary)}(?:\r\n|--)/m) - refute_empty parts - assert_empty parts[0] - parts.shift # remove the first empty part + def test_execute_attestation_auto + @response = "Successfully registered gem: freewill (1.0.0)" + @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") - p1 = parts.shift - p2 = parts.shift - assert_equal "\r\n", parts.shift - assert_empty parts + attestation_path = "#{@path}.sigstore.json" + File.write(attestation_path, "auto-attestation") + @cmd.options[:args] = [@path] - assert_equal [ - "Content-Disposition: form-data; name=\"gem\"; filename=\"#{@path}\"", - "Content-Type: application/octet-stream", - nil, - Gem.read_binary(@path), - ].join("\r\n").b, p1 - assert_equal [ - "Content-Disposition: form-data; name=\"attestations\"", - nil, - "[#{Gem.read_binary("#{@path}.sigstore.json")}]", - ].join("\r\n").b, p2 + @cmd.stub(:attest!, attestation_path) do + @cmd.execute + end + + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + content_length = @fetcher.last_request["Content-Length"].to_i + assert_equal content_length, @fetcher.last_request.body.length + assert_attestation_multipart Gem.read_binary(attestation_path) + end + + def test_execute_attestation_fallback + @response = "Successfully registered gem: freewill (1.0.0)" + @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") + + @cmd.options[:args] = [@path] + + @cmd.stub(:attest!, proc { raise Gem::Exception, "boom" }) do + use_ui @ui do + @cmd.execute + end + end + + assert_match "Failed to push with attestation, retrying without attestation.", @ui.error + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + assert_equal Gem.read_binary(@path), @fetcher.last_request.body + assert_equal "application/octet-stream", + @fetcher.last_request["Content-Type"] end def test_execute_allowed_push_host @@ -642,6 +654,35 @@ def test_sending_gem_with_no_local_creds private + def assert_attestation_multipart(attestation_payload) + assert_equal "multipart", @fetcher.last_request.main_type, @fetcher.last_request.content_type + assert_equal "form-data", @fetcher.last_request.sub_type + assert_include @fetcher.last_request.type_params, "boundary" + boundary = @fetcher.last_request.type_params["boundary"] + + parts = @fetcher.last_request.body.split(/(?:\r\n|\A)--#{Regexp.quote(boundary)}(?:\r\n|--)/m) + refute_empty parts + assert_empty parts[0] + parts.shift # remove the first empty part + + p1 = parts.shift + p2 = parts.shift + assert_equal "\r\n", parts.shift + assert_empty parts + + assert_equal [ + "Content-Disposition: form-data; name=\"gem\"; filename=\"#{@path}\"", + "Content-Type: application/octet-stream", + nil, + Gem.read_binary(@path), + ].join("\r\n").b, p1 + assert_equal [ + "Content-Disposition: form-data; name=\"attestations\"", + nil, + "[#{attestation_payload}]", + ].join("\r\n").b, p2 + end + def singleton_gem_class class << Gem; self; end end From 0fd9443f3ecdefed957caca84392089eecfec6f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:18:20 +0000 Subject: [PATCH 4/8] Add test coverage for skipping auto-attestation Co-authored-by: hsbt <12301+hsbt@users.noreply.github.com> --- .../test_gem_commands_push_command.rb | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 1f357159d3d1..cbf0bf3f0c9b 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -155,6 +155,55 @@ def test_execute_attestation_fallback @fetcher.last_request["Content-Type"] end + def test_execute_attestation_skipped_on_non_rubygems_host + @spec, @path = util_gem "freebird", "1.0.1" do |spec| + spec.metadata["allowed_push_host"] = "https://privategemserver.example" + end + + @response = "Successfully registered gem: freebird (1.0.1)" + @fetcher.data["#{@spec.metadata["allowed_push_host"]}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") + + @cmd.options[:args] = [@path] + + attest_called = false + @cmd.stub(:attest!, proc { attest_called = true; raise "attest! should not be called" }) do + @cmd.execute + end + + refute attest_called, "attest! should not be called for non-rubygems.org hosts" + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + assert_equal Gem.read_binary(@path), @fetcher.last_request.body + assert_equal "application/octet-stream", + @fetcher.last_request["Content-Type"] + end + + def test_execute_attestation_skipped_on_jruby + @response = "Successfully registered gem: freewill (1.0.0)" + @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") + + @cmd.options[:args] = [@path] + + attest_called = false + engine = RUBY_ENGINE + Object.send :remove_const, :RUBY_ENGINE + Object.const_set :RUBY_ENGINE, "jruby" + + begin + @cmd.stub(:attest!, proc { attest_called = true; raise "attest! should not be called" }) do + @cmd.execute + end + + refute attest_called, "attest! should not be called on JRuby" + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + assert_equal Gem.read_binary(@path), @fetcher.last_request.body + assert_equal "application/octet-stream", + @fetcher.last_request["Content-Type"] + ensure + Object.send :remove_const, :RUBY_ENGINE + Object.const_set :RUBY_ENGINE, engine + end + end + def test_execute_allowed_push_host @spec, @path = util_gem "freebird", "1.0.1" do |spec| spec.metadata["allowed_push_host"] = "https://privategemserver.example" From 0460b2ad54589e9e943cebaa39ade0e8ca583c27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:19:16 +0000 Subject: [PATCH 5/8] Remove raise from stub to rely on flag for test assertions Co-authored-by: hsbt <12301+hsbt@users.noreply.github.com> --- test/rubygems/test_gem_commands_push_command.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index cbf0bf3f0c9b..704c1115c721 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -166,7 +166,7 @@ def test_execute_attestation_skipped_on_non_rubygems_host @cmd.options[:args] = [@path] attest_called = false - @cmd.stub(:attest!, proc { attest_called = true; raise "attest! should not be called" }) do + @cmd.stub(:attest!, proc { attest_called = true }) do @cmd.execute end @@ -189,7 +189,7 @@ def test_execute_attestation_skipped_on_jruby Object.const_set :RUBY_ENGINE, "jruby" begin - @cmd.stub(:attest!, proc { attest_called = true; raise "attest! should not be called" }) do + @cmd.stub(:attest!, proc { attest_called = true }) do @cmd.execute end From 365ccdc26ab88ac282c0fc4fbfc38c6c04cc00f4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:26:20 +0000 Subject: [PATCH 6/8] Use Tempfile for auto-attestation bundles and clean up after use Co-authored-by: hsbt <12301+hsbt@users.noreply.github.com> --- lib/rubygems/commands/push_command.rb | 13 +++++++++++-- test/rubygems/test_gem_commands_push_command.rb | 5 +++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index b8af8cfd9188..95f23e06330a 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -121,7 +121,12 @@ def send_push_request_with_attestation(name, args) Gem.read_binary(attestation) end else - [Gem.read_binary(attest!(name))] + bundle_path = attest!(name) + begin + [Gem.read_binary(bundle_path)] + ensure + File.unlink(bundle_path) if bundle_path && File.exist?(bundle_path) + end end bundles = "[" + attestations.join(",") + "]" @@ -136,8 +141,12 @@ def send_push_request_with_attestation(name, args) def attest!(name) require "open3" + require "tempfile" + + tempfile = Tempfile.new([File.basename(name, ".*"), ".sigstore.json"]) + bundle = tempfile.path + tempfile.close(false) - bundle = "#{name}.sigstore.json" env = defined?(Bundler.unbundled_env) ? Bundler.unbundled_env : ENV.to_h out, st = Open3.capture2e( env, diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 704c1115c721..188fd742aaf0 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -123,7 +123,8 @@ def test_execute_attestation_auto @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") attestation_path = "#{@path}.sigstore.json" - File.write(attestation_path, "auto-attestation") + attestation_content = "auto-attestation" + File.write(attestation_path, attestation_content) @cmd.options[:args] = [@path] @cmd.stub(:attest!, attestation_path) do @@ -133,7 +134,7 @@ def test_execute_attestation_auto assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class content_length = @fetcher.last_request["Content-Length"].to_i assert_equal content_length, @fetcher.last_request.body.length - assert_attestation_multipart Gem.read_binary(attestation_path) + assert_attestation_multipart attestation_content end def test_execute_attestation_fallback From 9cd860fcefd8ef50e88cad951e94d20afc7050fb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 10 Feb 2026 19:35:25 +0900 Subject: [PATCH 7/8] Omit tests of auto-attestation with JRuby --- test/rubygems/test_gem_commands_push_command.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 188fd742aaf0..8a2a397e3fc0 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -103,6 +103,8 @@ def test_execute_host end def test_execute_attestation + omit if RUBY_ENGINE == "jruby" + @response = "Successfully registered gem: freewill (1.0.0)" @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") @@ -119,6 +121,8 @@ def test_execute_attestation end def test_execute_attestation_auto + omit if RUBY_ENGINE == "jruby" + @response = "Successfully registered gem: freewill (1.0.0)" @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") @@ -138,6 +142,8 @@ def test_execute_attestation_auto end def test_execute_attestation_fallback + omit if RUBY_ENGINE == "jruby" + @response = "Successfully registered gem: freewill (1.0.0)" @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") From fcd4d813a7fac9345a8e34f9e7414c69f8910125 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 24 Feb 2026 18:57:11 +0900 Subject: [PATCH 8/8] Reverse to use attestation condition --- lib/rubygems/commands/push_command.rb | 13 ++- .../test_gem_commands_push_command.rb | 85 +++++++++++-------- 2 files changed, 56 insertions(+), 42 deletions(-) diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 95f23e06330a..62dbd14e0bb2 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -92,14 +92,10 @@ def send_gem(name) private def send_push_request(name, args) - if RUBY_ENGINE == "jruby" || !attestation_supported_host? - return send_push_request_without_attestation(name, args) - end - - begin + # Attestation is only supported on rubygems.org with GitHub Actions (not JRuby) + if RUBY_ENGINE != "jruby" && attestation_supported_host? && ENV["GITHUB_ACTIONS"] send_push_request_with_attestation(name, args) - rescue StandardError => e - alert_warning "Failed to push with attestation, retrying without attestation.\n#{e.full_message}" + else send_push_request_without_attestation(name, args) end end @@ -137,6 +133,9 @@ def send_push_request_with_attestation(name, args) ], "multipart/form-data") request.add_field "Authorization", api_key end + rescue StandardError => e + alert_warning "Failed to push with attestation, retrying without attestation.\n#{e.full_message}" + send_push_request_without_attestation(name, args) end def attest!(name) diff --git a/test/rubygems/test_gem_commands_push_command.rb b/test/rubygems/test_gem_commands_push_command.rb index 8a2a397e3fc0..ba70eef54b19 100644 --- a/test/rubygems/test_gem_commands_push_command.rb +++ b/test/rubygems/test_gem_commands_push_command.rb @@ -105,61 +105,76 @@ def test_execute_host def test_execute_attestation omit if RUBY_ENGINE == "jruby" - @response = "Successfully registered gem: freewill (1.0.0)" - @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") + ENV["GITHUB_ACTIONS"] = "true" + begin + @response = "Successfully registered gem: freewill (1.0.0)" + @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") - File.write("#{@path}.sigstore.json", "attestation") - @cmd.options[:args] = [@path] - @cmd.options[:attestations] = ["#{@path}.sigstore.json"] + File.write("#{@path}.sigstore.json", "attestation") + @cmd.options[:args] = [@path] + @cmd.options[:attestations] = ["#{@path}.sigstore.json"] - @cmd.execute + @cmd.execute - assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class - content_length = @fetcher.last_request["Content-Length"].to_i - assert_equal content_length, @fetcher.last_request.body.length - assert_attestation_multipart Gem.read_binary("#{@path}.sigstore.json") + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + content_length = @fetcher.last_request["Content-Length"].to_i + assert_equal content_length, @fetcher.last_request.body.length + assert_attestation_multipart Gem.read_binary("#{@path}.sigstore.json") + ensure + ENV.delete("GITHUB_ACTIONS") + end end def test_execute_attestation_auto omit if RUBY_ENGINE == "jruby" - @response = "Successfully registered gem: freewill (1.0.0)" - @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") + ENV["GITHUB_ACTIONS"] = "true" + begin + @response = "Successfully registered gem: freewill (1.0.0)" + @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") - attestation_path = "#{@path}.sigstore.json" - attestation_content = "auto-attestation" - File.write(attestation_path, attestation_content) - @cmd.options[:args] = [@path] + attestation_path = "#{@path}.sigstore.json" + attestation_content = "auto-attestation" + File.write(attestation_path, attestation_content) + @cmd.options[:args] = [@path] - @cmd.stub(:attest!, attestation_path) do - @cmd.execute - end + @cmd.stub(:attest!, attestation_path) do + @cmd.execute + end - assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class - content_length = @fetcher.last_request["Content-Length"].to_i - assert_equal content_length, @fetcher.last_request.body.length - assert_attestation_multipart attestation_content + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + content_length = @fetcher.last_request["Content-Length"].to_i + assert_equal content_length, @fetcher.last_request.body.length + assert_attestation_multipart attestation_content + ensure + ENV.delete("GITHUB_ACTIONS") + end end def test_execute_attestation_fallback omit if RUBY_ENGINE == "jruby" - @response = "Successfully registered gem: freewill (1.0.0)" - @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") + ENV["GITHUB_ACTIONS"] = "true" + begin + @response = "Successfully registered gem: freewill (1.0.0)" + @fetcher.data["#{Gem.host}/api/v1/gems"] = HTTPResponseFactory.create(body: @response, code: 200, msg: "OK") - @cmd.options[:args] = [@path] + @cmd.options[:args] = [@path] - @cmd.stub(:attest!, proc { raise Gem::Exception, "boom" }) do - use_ui @ui do - @cmd.execute + @cmd.stub(:attest!, proc { raise Gem::Exception, "boom" }) do + use_ui @ui do + @cmd.execute + end end - end - assert_match "Failed to push with attestation, retrying without attestation.", @ui.error - assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class - assert_equal Gem.read_binary(@path), @fetcher.last_request.body - assert_equal "application/octet-stream", - @fetcher.last_request["Content-Type"] + assert_match "Failed to push with attestation, retrying without attestation.", @ui.error + assert_equal Gem::Net::HTTP::Post, @fetcher.last_request.class + assert_equal Gem.read_binary(@path), @fetcher.last_request.body + assert_equal "application/octet-stream", + @fetcher.last_request["Content-Type"] + ensure + ENV.delete("GITHUB_ACTIONS") + end end def test_execute_attestation_skipped_on_non_rubygems_host