@@ -115,32 +115,108 @@ def test_execute_attestation
115115 assert_equal Gem ::Net ::HTTP ::Post , @fetcher . last_request . class
116116 content_length = @fetcher . last_request [ "Content-Length" ] . to_i
117117 assert_equal content_length , @fetcher . last_request . body . length
118- assert_equal "multipart" , @fetcher . last_request . main_type , @fetcher . last_request . content_type
119- assert_equal "form-data" , @fetcher . last_request . sub_type
120- assert_include @fetcher . last_request . type_params , "boundary"
121- boundary = @fetcher . last_request . type_params [ "boundary" ]
118+ assert_attestation_multipart Gem . read_binary ( "#{ @path } .sigstore.json" )
119+ end
122120
123- parts = @fetcher . last_request . body . split ( /(?:\r \n |\A )--#{ Regexp . quote ( boundary ) } (?:\r \n |--)/m )
124- refute_empty parts
125- assert_empty parts [ 0 ]
126- parts . shift # remove the first empty part
121+ def test_execute_attestation_auto
122+ omit if RUBY_ENGINE == "jruby"
127123
128- p1 = parts . shift
129- p2 = parts . shift
130- assert_equal " \r \n " , parts . shift
131- assert_empty parts
124+ ENV [ "GITHUB_ACTIONS" ] = "true"
125+ begin
126+ @response = "Successfully registered gem: freewill (1.0.0)"
127+ @fetcher . data [ " #{ Gem . host } /api/v1/gems" ] = HTTPResponseFactory . create ( body : @response , code : 200 , msg : "OK" )
132128
133- assert_equal [
134- "Content-Disposition: form-data; name=\" gem\" ; filename=\" #{ @path } \" " ,
135- "Content-Type: application/octet-stream" ,
136- nil ,
137- Gem . read_binary ( @path ) ,
138- ] . join ( "\r \n " ) . b , p1
139- assert_equal [
140- "Content-Disposition: form-data; name=\" attestations\" " ,
141- nil ,
142- "[#{ Gem . read_binary ( "#{ @path } .sigstore.json" ) } ]" ,
143- ] . join ( "\r \n " ) . b , p2
129+ attestation_path = "#{ @path } .sigstore.json"
130+ attestation_content = "auto-attestation"
131+ File . write ( attestation_path , attestation_content )
132+ @cmd . options [ :args ] = [ @path ]
133+
134+ @cmd . stub ( :attest! , attestation_path ) do
135+ @cmd . execute
136+ end
137+
138+ assert_equal Gem ::Net ::HTTP ::Post , @fetcher . last_request . class
139+ content_length = @fetcher . last_request [ "Content-Length" ] . to_i
140+ assert_equal content_length , @fetcher . last_request . body . length
141+ assert_attestation_multipart attestation_content
142+ ensure
143+ ENV . delete ( "GITHUB_ACTIONS" )
144+ end
145+ end
146+
147+ def test_execute_attestation_fallback
148+ omit if RUBY_ENGINE == "jruby"
149+
150+ ENV [ "GITHUB_ACTIONS" ] = "true"
151+ begin
152+ @response = "Successfully registered gem: freewill (1.0.0)"
153+ @fetcher . data [ "#{ Gem . host } /api/v1/gems" ] = HTTPResponseFactory . create ( body : @response , code : 200 , msg : "OK" )
154+
155+ @cmd . options [ :args ] = [ @path ]
156+
157+ @cmd . stub ( :attest! , proc { raise Gem ::Exception , "boom" } ) do
158+ use_ui @ui do
159+ @cmd . execute
160+ end
161+ end
162+
163+ assert_match "Failed to push with attestation, retrying without attestation." , @ui . error
164+ assert_equal Gem ::Net ::HTTP ::Post , @fetcher . last_request . class
165+ assert_equal Gem . read_binary ( @path ) , @fetcher . last_request . body
166+ assert_equal "application/octet-stream" ,
167+ @fetcher . last_request [ "Content-Type" ]
168+ ensure
169+ ENV . delete ( "GITHUB_ACTIONS" )
170+ end
171+ end
172+
173+ def test_execute_attestation_skipped_on_non_rubygems_host
174+ @spec , @path = util_gem "freebird" , "1.0.1" do |spec |
175+ spec . metadata [ "allowed_push_host" ] = "https://privategemserver.example"
176+ end
177+
178+ @response = "Successfully registered gem: freebird (1.0.1)"
179+ @fetcher . data [ "#{ @spec . metadata [ "allowed_push_host" ] } /api/v1/gems" ] = HTTPResponseFactory . create ( body : @response , code : 200 , msg : "OK" )
180+
181+ @cmd . options [ :args ] = [ @path ]
182+
183+ attest_called = false
184+ @cmd . stub ( :attest! , proc { attest_called = true } ) do
185+ @cmd . execute
186+ end
187+
188+ refute attest_called , "attest! should not be called for non-rubygems.org hosts"
189+ assert_equal Gem ::Net ::HTTP ::Post , @fetcher . last_request . class
190+ assert_equal Gem . read_binary ( @path ) , @fetcher . last_request . body
191+ assert_equal "application/octet-stream" ,
192+ @fetcher . last_request [ "Content-Type" ]
193+ end
194+
195+ def test_execute_attestation_skipped_on_jruby
196+ @response = "Successfully registered gem: freewill (1.0.0)"
197+ @fetcher . data [ "#{ Gem . host } /api/v1/gems" ] = HTTPResponseFactory . create ( body : @response , code : 200 , msg : "OK" )
198+
199+ @cmd . options [ :args ] = [ @path ]
200+
201+ attest_called = false
202+ engine = RUBY_ENGINE
203+ Object . send :remove_const , :RUBY_ENGINE
204+ Object . const_set :RUBY_ENGINE , "jruby"
205+
206+ begin
207+ @cmd . stub ( :attest! , proc { attest_called = true } ) do
208+ @cmd . execute
209+ end
210+
211+ refute attest_called , "attest! should not be called on JRuby"
212+ assert_equal Gem ::Net ::HTTP ::Post , @fetcher . last_request . class
213+ assert_equal Gem . read_binary ( @path ) , @fetcher . last_request . body
214+ assert_equal "application/octet-stream" ,
215+ @fetcher . last_request [ "Content-Type" ]
216+ ensure
217+ Object . send :remove_const , :RUBY_ENGINE
218+ Object . const_set :RUBY_ENGINE , engine
219+ end
144220 end
145221
146222 def test_execute_allowed_push_host
@@ -643,6 +719,35 @@ def test_sending_gem_with_no_local_creds
643719
644720 private
645721
722+ def assert_attestation_multipart ( attestation_payload )
723+ assert_equal "multipart" , @fetcher . last_request . main_type , @fetcher . last_request . content_type
724+ assert_equal "form-data" , @fetcher . last_request . sub_type
725+ assert_include @fetcher . last_request . type_params , "boundary"
726+ boundary = @fetcher . last_request . type_params [ "boundary" ]
727+
728+ parts = @fetcher . last_request . body . split ( /(?:\r \n |\A )--#{ Regexp . quote ( boundary ) } (?:\r \n |--)/m )
729+ refute_empty parts
730+ assert_empty parts [ 0 ]
731+ parts . shift # remove the first empty part
732+
733+ p1 = parts . shift
734+ p2 = parts . shift
735+ assert_equal "\r \n " , parts . shift
736+ assert_empty parts
737+
738+ assert_equal [
739+ "Content-Disposition: form-data; name=\" gem\" ; filename=\" #{ @path } \" " ,
740+ "Content-Type: application/octet-stream" ,
741+ nil ,
742+ Gem . read_binary ( @path ) ,
743+ ] . join ( "\r \n " ) . b , p1
744+ assert_equal [
745+ "Content-Disposition: form-data; name=\" attestations\" " ,
746+ nil ,
747+ "[#{ attestation_payload } ]" ,
748+ ] . join ( "\r \n " ) . b , p2
749+ end
750+
646751 def singleton_gem_class
647752 class << Gem ; self ; end
648753 end
0 commit comments