From aaa5bf2184f16795e6910f31a5ae29270259d67d Mon Sep 17 00:00:00 2001 From: HendrikW Date: Fri, 27 Apr 2012 00:43:32 +0300 Subject: [PATCH 01/20] called start_with_request without a block, because as Net:HTTP's start method description says, it will open AND close a connection if start is called with a block. In this case Net:HTTP then later tries to close that same connection again if the server's response has "Connection: close" in its header. In that case Net:HTTP will throw an IOError. --- lib/webmock/http_lib_adapters/net_http.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/webmock/http_lib_adapters/net_http.rb b/lib/webmock/http_lib_adapters/net_http.rb index 96d108c83..7d59be924 100644 --- a/lib/webmock/http_lib_adapters/net_http.rb +++ b/lib/webmock/http_lib_adapters/net_http.rb @@ -87,10 +87,9 @@ def request_with_webmock(request, body = nil, &block) end response = if (started? && !WebMock::Config.instance.net_http_connect_on_start) || !started? @started = false #otherwise start_with_connect wouldn't execute and connect - start_with_connect { - response = request_without_webmock(request, nil) - after_request.call(response) - } + start_with_connect + response = request_without_webmock(request, nil) + after_request.call(response) else response = request_without_webmock(request, nil) after_request.call(response) From 3fb913dbd96e29e6227d915d328a052bd2d48502 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Mon, 14 May 2012 22:53:02 -0700 Subject: [PATCH 02/20] Add failing spec demonstrating em-http-request bug. When a response modifying middleware is used with em-http-request, and a real request is made, the middleware can modify the response before the after_request is invoked. This prevents VCR from being able to record the response accurately. The after_request hook should be invoked before the em-http-request middleware. Related to myronmarston/vcr#169. --- .../em_http_request/em_http_request_spec.rb | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/spec/acceptance/em_http_request/em_http_request_spec.rb b/spec/acceptance/em_http_request/em_http_request_spec.rb index e5292c87c..3496f3fa6 100644 --- a/spec/acceptance/em_http_request/em_http_request_spec.rb +++ b/spec/acceptance/em_http_request/em_http_request_spec.rb @@ -38,19 +38,21 @@ def request(client, head, body) end end - it "should work with response middleware" do - stub_request(:get, "www.example.com").to_return(:body => 'foo') - - middleware = Class.new do + let(:response_middleware) do + Class.new do def response(resp) resp.response = 'bar' end end + end + + it "should work with response middleware" do + stub_request(:get, "www.example.com").to_return(:body => 'foo') EM.run do conn = EventMachine::HttpRequest.new('http://www.example.com/') - conn.use middleware + conn.use response_middleware http = conn.get @@ -60,6 +62,36 @@ def response(resp) end end end + + let(:webmock_server_url) { "http://#{WebMockServer.instance.host_with_port}/" } + + shared_examples_for "em-http-request middleware/after_request hook integration" do + it 'yields the original raw body to the after_request hook even if a response middleware modifies the body' do + yielded_response_body = nil + ::WebMock.after_request do |request, response| + yielded_response_body = response.body + end + + EM::HttpRequest.use response_middleware + + EM.run do + http = EventMachine::HttpRequest.new(webmock_server_url).get + http.callback { EM.stop } + end + + yielded_response_body.should eq("hello world") + end + end + + context 'making a real request' do + before { WebMock.allow_net_connect! } + include_examples "em-http-request middleware/after_request hook integration" + end + + context 'when the request is stubbed' do + before { stub_request(:get, webmock_server_url).to_return(:body => 'hello world') } + include_examples "em-http-request middleware/after_request hook integration" + end end # not pretty, but it works From b3dfb035f16993244e9e5583bc81130fb72a5a9f Mon Sep 17 00:00:00 2001 From: Hans Hasselberg Date: Wed, 6 Jun 2012 17:08:11 +0200 Subject: [PATCH 03/20] Fix typhoeus 0.4.0 issues. --- spec/acceptance/shared/request_expectations.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/acceptance/shared/request_expectations.rb b/spec/acceptance/shared/request_expectations.rb index 73cdbbcbe..d9e92a2f2 100644 --- a/spec/acceptance/shared/request_expectations.rb +++ b/spec/acceptance/shared/request_expectations.rb @@ -190,10 +190,10 @@ it "should fail if request was executed with different body" do lambda { - http_request(:get, "http://www.example.com/", :body => "abc") - a_request(:get, "www.example.com"). + http_request(:post, "http://www.example.com/", :body => "abc") + a_request(:post, "www.example.com"). with(:body => "def").should have_been_made - }.should fail_with(%r(The request GET http://www.example.com/ with body "def" was expected to execute 1 time but it executed 0 times)) + }.should fail_with(%r(The request POST http://www.example.com/ with body "def" was expected to execute 1 time but it executed 0 times)) end describe "when expected request body is declared as a regexp" do @@ -206,10 +206,10 @@ it "should fail if request was executed with body not matching regexp" do lambda { - http_request(:get, "http://www.example.com/", :body => "abc") - a_request(:get, "www.example.com"). + http_request(:post, "http://www.example.com/", :body => "abc") + a_request(:post, "www.example.com"). with(:body => /^xabc/).should have_been_made - }.should fail_with(%r(The request GET http://www.example.com/ with body /\^xabc/ was expected to execute 1 time but it executed 0 times)) + }.should fail_with(%r(The request POST http://www.example.com/ with body /\^xabc/ was expected to execute 1 time but it executed 0 times)) end end @@ -389,11 +389,11 @@ it "should satisfy expectation if request was executed with body and headers but they were not specified in expectantion" do lambda { - http_request(:get, "http://www.example.com/", + http_request(:post, "http://www.example.com/", :body => "abc", :headers => SAMPLE_HEADERS ) - a_request(:get, "www.example.com").should have_been_made + a_request(:post, "www.example.com").should have_been_made }.should_not raise_error end From f7b323032e9df241900b7eaf967c8dd8f40d264d Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Mon, 11 Jun 2012 23:13:54 -0700 Subject: [PATCH 04/20] Fix Net::HTTP adapter so that it returns `nil` for an empty body response. This mirrors the real behavior of Net::HTTP and is the source of myronmarston/vcr#173. A couple things to note: - Rather than hitting an external URL (httpstat.us/204), this should probably hit the local webmock server; however, I can't figure out how to make the webmock server return a different response for different requests since it's writing directly to the socket w/o any request context available. Maybe it should be refactored to use rack or sinatra? - I have no idea why, but Curb is returning a 400 Bad Request response for the request. Weird. Not sure why or how to fix it. --- lib/webmock/http_lib_adapters/net_http.rb | 5 ++++- .../shared/complex_cross_concern_behaviors.rb | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/webmock/http_lib_adapters/net_http.rb b/lib/webmock/http_lib_adapters/net_http.rb index 96d108c83..85a987af3 100644 --- a/lib/webmock/http_lib_adapters/net_http.rb +++ b/lib/webmock/http_lib_adapters/net_http.rb @@ -128,7 +128,10 @@ def start_with_conditional_connect(&block) def build_net_http_response(webmock_response, &block) response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1]) - response.instance_variable_set(:@body, webmock_response.body) + body = webmock_response.body + body = nil if body.to_s == '' + + response.instance_variable_set(:@body, body) webmock_response.headers.to_a.each do |name, values| values = [values] unless values.is_a?(Array) values.each do |value| diff --git a/spec/acceptance/shared/complex_cross_concern_behaviors.rb b/spec/acceptance/shared/complex_cross_concern_behaviors.rb index 429fc595e..d16a66980 100644 --- a/spec/acceptance/shared/complex_cross_concern_behaviors.rb +++ b/spec/acceptance/shared/complex_cross_concern_behaviors.rb @@ -17,5 +17,18 @@ played_back_response.headers.keys.should include('Set-Cookie') played_back_response.should == real_response end + + let(:no_content_url) { 'http://httpstat.us/204' } + [nil, ''].each do |stub_val| + it "returns the same value (nil or "") for a request stubbed as #{stub_val.inspect} that a real empty response has" do + WebMock.allow_net_connect! + + real_response = http_request(:get, no_content_url) + stub_request(:get, no_content_url).to_return(:status => 204, :body => stub_val) + stubbed_response = http_request(:get, no_content_url) + + stubbed_response.body.should eq(real_response.body) + end + end end From ea67054d2f13714dec511d4344b81e9d495b350b Mon Sep 17 00:00:00 2001 From: Ryan Schlesinger Date: Thu, 5 Jul 2012 15:51:41 -0700 Subject: [PATCH 05/20] Fixing travis-ci image to report master's status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6dc24eb9..cc11fd5ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -WebMock [![Build Status](https://secure.travis-ci.org/bblimke/webmock.png)](http://travis-ci.org/bblimke/webmock) [![Dependency Status](https://gemnasium.com/bblimke/webmock.png)](http://gemnasium.com/bblimke/webmock) +WebMock [![Build Status](https://secure.travis-ci.org/bblimke/webmock.png?branch=master)](http://travis-ci.org/bblimke/webmock) [![Dependency Status](https://gemnasium.com/bblimke/webmock.png)](http://gemnasium.com/bblimke/webmock) ======= Library for stubbing and setting expectations on HTTP requests in Ruby. From 5676de8d821a358ae8147d8d0f7c7a49e3c3c333 Mon Sep 17 00:00:00 2001 From: chatgris Date: Wed, 11 Jul 2012 14:31:02 +0200 Subject: [PATCH 06/20] Fix EventMachine::HttpRequest on proxy method. Caused by https://github.com/igrigorik/em-http-request/commit/7d5752d1bcf90b749c3be6eb384e7bdf81441be0 Signed-off-by: chatgris --- .../http_lib_adapters/em_http_request/em_http_request_1_x.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb index c3b152728..33e40f453 100644 --- a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +++ b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb @@ -154,7 +154,7 @@ def build_request_signature method = @req.method uri = @req.uri.clone - auth = @req.proxy[:authorization] if @req.proxy + auth = @req.headers[:'proxy-authorization'] query = @req.query if auth From 824c33687acf2f3280beddedce13a6bd808e70f3 Mon Sep 17 00:00:00 2001 From: Kevin Glowacz Date: Thu, 12 Jul 2012 13:42:39 -0500 Subject: [PATCH 07/20] request_pattern should handle content_types that specify a charset --- lib/webmock/request_pattern.rb | 1 + spec/unit/request_pattern_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/lib/webmock/request_pattern.rb b/lib/webmock/request_pattern.rb index 0ea2fa2c1..0415acc6b 100644 --- a/lib/webmock/request_pattern.rb +++ b/lib/webmock/request_pattern.rb @@ -27,6 +27,7 @@ def with(options = {}, &block) def matches?(request_signature) content_type = request_signature.headers['Content-Type'] if request_signature.headers + content_type = content_type.split(';').first if content_type @method_pattern.matches?(request_signature.method) && @uri_pattern.matches?(request_signature.uri) && (@body_pattern.nil? || @body_pattern.matches?(request_signature.body, content_type || "")) && diff --git a/spec/unit/request_pattern_spec.rb b/spec/unit/request_pattern_spec.rb index db7d2c723..9ba0ba07f 100644 --- a/spec/unit/request_pattern_spec.rb +++ b/spec/unit/request_pattern_spec.rb @@ -358,6 +358,13 @@ def match(request_signature) should_not match(WebMock::RequestSignature.new(:post, "www.example.com", :headers => {:content_type => 'application/xml'}, :body => "foo bar")) end + + it "matches when the content type include a charset" do + WebMock::RequestPattern.new(:post, 'www.example.com', :body => body_hash). + should match(WebMock::RequestSignature.new(:post, "www.example.com", :headers => {:content_type => 'application/xml;charset=UTF-8'}, + :body => "\n \n e\n f\n \n\n")) + + end end end From 71e7baf8c38c08735bea8b9bfe25fa003eeda12c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 23 Jul 2012 11:55:01 +0100 Subject: [PATCH 08/20] Fix em-http-request callback triggering Previously, if there were other EM::Deferred callbacks registered, they might execute before WebMock's callback and raise an exception. This would prevent the WebMock callback ever running, even though there was a successful request. Overriding set_deferred_status prevents this by triggering WebMock's callbacks before running any of the EM::Deferred callbacks. Sorry, I have no idea how to test this. --- .../em_http_request/em_http_request_1_x.rb | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb index c3b152728..a4d9122b7 100644 --- a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +++ b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb @@ -105,16 +105,6 @@ def send_request_with_webmock(head, body) self elsif WebMock.net_connect_allowed?(request_signature.uri) send_request_without_webmock(head, body) - callback { - if WebMock::CallbackRegistry.any_callbacks? - webmock_response = build_webmock_response - WebMock::CallbackRegistry.invoke_callbacks( - {:lib => :em_http_request, :real_request => true}, - request_signature, - webmock_response) - end - } - self else raise WebMock::NetConnectNotAllowedError.new(request_signature) end @@ -123,6 +113,18 @@ def send_request_with_webmock(head, body) alias_method :send_request_without_webmock, :send_request alias_method :send_request, :send_request_with_webmock + def set_deferred_status(status, *args) + if status == :succeeded && !stubbed_webmock_response && WebMock::CallbackRegistry.any_callbacks? + webmock_response = build_webmock_response + WebMock::CallbackRegistry.invoke_callbacks( + {:lib => :em_http_request, :real_request => true}, + request_signature, + webmock_response) + end + + super + end + def request_signature @request_signature ||= build_request_signature end From 574cd18a44fdf8af430e9c50d9b708ce91983155 Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 00:06:46 +0200 Subject: [PATCH 09/20] Revert "Merge pull request #178 from HendrikW/master" This reverts commit 36aff464f7f4a95dd82a17c6954bc1160a6e83a1, reversing changes made to 1e61a8cea970a45b35431fdaaa94ed016f945457. --- lib/webmock/http_lib_adapters/net_http.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/webmock/http_lib_adapters/net_http.rb b/lib/webmock/http_lib_adapters/net_http.rb index 7d59be924..96d108c83 100644 --- a/lib/webmock/http_lib_adapters/net_http.rb +++ b/lib/webmock/http_lib_adapters/net_http.rb @@ -87,9 +87,10 @@ def request_with_webmock(request, body = nil, &block) end response = if (started? && !WebMock::Config.instance.net_http_connect_on_start) || !started? @started = false #otherwise start_with_connect wouldn't execute and connect - start_with_connect - response = request_without_webmock(request, nil) - after_request.call(response) + start_with_connect { + response = request_without_webmock(request, nil) + after_request.call(response) + } else response = request_without_webmock(request, nil) after_request.call(response) From 152d7d2b3b488cd93c5638407d9ba5e60c58cfa8 Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 00:16:37 +0200 Subject: [PATCH 10/20] Em-http-request now throws RuntimeError on connection refused. --- spec/acceptance/em_http_request/em_http_request_spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/acceptance/em_http_request/em_http_request_spec_helper.rb b/spec/acceptance/em_http_request/em_http_request_spec_helper.rb index fb202416c..5870bd4d6 100644 --- a/spec/acceptance/em_http_request/em_http_request_spec_helper.rb +++ b/spec/acceptance/em_http_request/em_http_request_spec_helper.rb @@ -49,7 +49,7 @@ def client_timeout_exception_class end def connection_refused_exception_class - "" + RuntimeError end def http_library From ff12dc28c663104d4e7d5264f00cb766754e6304 Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 00:31:56 +0200 Subject: [PATCH 11/20] Webmock is not compatible with Addressable >= 2.3.0 yet --- webmock.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webmock.gemspec b/webmock.gemspec index 7fb70e5d6..b530ae9a0 100644 --- a/webmock.gemspec +++ b/webmock.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.rubyforge_project = 'webmock' - s.add_dependency 'addressable', '>= 2.2.7' + s.add_dependency 'addressable', '~> 2.2.8' s.add_dependency 'crack', '>=0.1.7' s.add_development_dependency 'rspec', '~> 2.10' From fb41a00525afaf745531b0d22bc12db13d96315d Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 01:05:15 +0200 Subject: [PATCH 12/20] Acceptance spec incompatible with Curb doesn't run in Curb specs. --- .../shared/complex_cross_concern_behaviors.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/acceptance/shared/complex_cross_concern_behaviors.rb b/spec/acceptance/shared/complex_cross_concern_behaviors.rb index d16a66980..e0f828c23 100644 --- a/spec/acceptance/shared/complex_cross_concern_behaviors.rb +++ b/spec/acceptance/shared/complex_cross_concern_behaviors.rb @@ -21,13 +21,15 @@ let(:no_content_url) { 'http://httpstat.us/204' } [nil, ''].each do |stub_val| it "returns the same value (nil or "") for a request stubbed as #{stub_val.inspect} that a real empty response has" do - WebMock.allow_net_connect! + unless http_library == :curb + WebMock.allow_net_connect! - real_response = http_request(:get, no_content_url) - stub_request(:get, no_content_url).to_return(:status => 204, :body => stub_val) - stubbed_response = http_request(:get, no_content_url) + real_response = http_request(:get, no_content_url) + stub_request(:get, no_content_url).to_return(:status => 204, :body => stub_val) + stubbed_response = http_request(:get, no_content_url) - stubbed_response.body.should eq(real_response.body) + stubbed_response.body.should eq(real_response.body) + end end end end From 5fa0c9966867e0025a0d3cef4ca5601472a13c38 Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 01:12:43 +0200 Subject: [PATCH 13/20] Bump version to 1.8.8 --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ README.md | 5 +++++ lib/webmock/version.rb | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41fbed02f..0949a1a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## 1.8.8 + +* Fixed Net::HTTP adapter so that it returns `nil` for an empty body response. + + Thanks to [Myron Marston](https://github.com/myronmarston) + +* Gemspec defines compatibility with Addressable ~> 2.2.8, not >= 2.3.0 + +* Specs compatibility with Typhoeus 0.4.0 + + Thanks to [Hans Hasselberg](https://github.com/i0rek) + +* Handling content types that specify a charset + + Thanks to [Kevin Glowacz](https://github.com/kjg) + +* Fixed em-http-request adapter to correctly fetch authorization header from a request + + Thanks to [Julien Boyer](https://github.com/chatgris) + +* Fixing travis-ci image to report master's status + + Thanks to [Ryan Schlesinger](https://github.com/ryansch) + +* Fixed problem with em-http-request callback triggering if there were other EM::Deferred callbacks registered + + Thanks to [Jon Leighton](https://github.com/jonleighton) + +* Fixed problem with em-http-request appending the query to the URI a second time, and +the parameters are repeated. + + Thanks to [Jon Leighton](https://github.com/jonleighton) + ## 1.8.7 * Compatibility with RSpec >= 2.10 diff --git a/README.md b/README.md index cc11fd5ad..4e9f69c0c 100644 --- a/README.md +++ b/README.md @@ -707,6 +707,11 @@ People who submitted patches and new features or suggested improvements. Many th * Eric Oestrich * erwanlr * Ben Bleything +* Jon Leighton +* Ryan Schlesinger +* Julien Boyer +* Kevin Glowacz +* Hans Hasselberg For a full list of contributors you can visit the [contributors](https://github.com/bblimke/webmock/contributors) page. diff --git a/lib/webmock/version.rb b/lib/webmock/version.rb index 85c0ca234..a94e28b24 100644 --- a/lib/webmock/version.rb +++ b/lib/webmock/version.rb @@ -1,3 +1,3 @@ module WebMock - VERSION = '1.8.7' unless defined?(::WebMock::VERSION) + VERSION = '1.8.8' unless defined?(::WebMock::VERSION) end From ff99707432c182626f3afa68053e782c6d3e4a5c Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 01:27:09 +0200 Subject: [PATCH 14/20] Allow failures for Rubinius builds on Travis CI. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 61cb492f6..4ffa26fb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,9 @@ rvm: - jruby-18mode - rbx-18mode - rbx-19mode +matrix: + allow_failures: + - rvm: rbx-18mode + - rvm: rbx-19mode script: "bundle exec rake && rake em_http_request_0_x_spec" From d709d7c64b645f853e0949b6f141d68aecc31607 Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 01:35:06 +0200 Subject: [PATCH 15/20] Updated jruby-openssl dependency to the latest version. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 7be7924eb..d1720446e 100644 --- a/Gemfile +++ b/Gemfile @@ -17,5 +17,5 @@ group :test do end platforms :jruby do - gem 'jruby-openssl', '~> 0.7.4.0' + gem 'jruby-openssl', '~> 0.7.7' end From c31a3c05f7530c5fad6e06798b112a7f6d8ada3c Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Tue, 24 Jul 2012 01:42:21 +0200 Subject: [PATCH 16/20] Ignore errors on JRuby 1.9 on Travis using latest jruby-openssl gem. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4ffa26fb7..b5abba006 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,6 @@ matrix: allow_failures: - rvm: rbx-18mode - rvm: rbx-19mode + - rvm: jruby-19mode script: "bundle exec rake && rake em_http_request_0_x_spec" From 653d92fea6dc20f117829563932eea414fa264fc Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Mon, 6 Aug 2012 23:27:37 -0700 Subject: [PATCH 17/20] Clear cached webmock response in HTTPClient instance. When the a request was not stubbed, the `nil` value of the webmock response was being cached in the `webmock_responses` hash. Then, when a second request was made with the same HTTPClient instance and an identical signature, it was not checking for a stub again, even though there may have been one for the second request (e.g. when using a global stub hook or if another stub is registered between the 1st and 2nd requests). Fixes myronmarston/vcr#190. --- .../http_lib_adapters/httpclient_adapter.rb | 3 +++ spec/acceptance/httpclient/httpclient_spec.rb | 27 +++++++++++++++++++ .../httpclient/httpclient_spec_helper.rb | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/webmock/http_lib_adapters/httpclient_adapter.rb b/lib/webmock/http_lib_adapters/httpclient_adapter.rb index fcaded096..123702ba9 100644 --- a/lib/webmock/http_lib_adapters/httpclient_adapter.rb +++ b/lib/webmock/http_lib_adapters/httpclient_adapter.rb @@ -53,6 +53,9 @@ def do_get_with_webmock(req, proxy, conn, stream = false, &block) {:lib => :httpclient}, request_signature, webmock_response) res elsif WebMock.net_connect_allowed?(request_signature.uri) + # in case there is a nil entry in the hash... + webmock_responses.delete(request_signature) + res = if stream do_get_stream_without_webmock(req, proxy, conn, &block) else diff --git a/spec/acceptance/httpclient/httpclient_spec.rb b/spec/acceptance/httpclient/httpclient_spec.rb index 1dafd9e45..35af24739 100644 --- a/spec/acceptance/httpclient/httpclient_spec.rb +++ b/spec/acceptance/httpclient/httpclient_spec.rb @@ -73,4 +73,31 @@ def filter_response(request, response) end end + context 'when a client instance is re-used for another identical request' do + let(:client) { HTTPClient.new } + let(:webmock_server_url) {"http://#{WebMockServer.instance.host_with_port}/"} + + before { WebMock.allow_net_connect! } + + it 'invokes the global_stub_request hook for each request' do + request_signatures = [] + WebMock.globally_stub_request do |request_sig| + request_signatures << request_sig + nil # to let the request be made for real + end + + # To make two requests that have the same request signature, the headers must match. + # Since the webmock server has a Set-Cookie header, the 2nd request will automatically + # include a Cookie header (due to how httpclient works), so we have to set the header + # manually on the first request but not on the 2nd request. + http_request(:get, webmock_server_url, :client => client, + :headers => { "Cookie" => "bar=; foo=" }) + http_request(:get, webmock_server_url, :client => client) + + request_signatures.should have(2).signatures + # Verify the request signatures were identical as needed by this example + request_signatures.first.should eq(request_signatures.last) + end + end + end diff --git a/spec/acceptance/httpclient/httpclient_spec_helper.rb b/spec/acceptance/httpclient/httpclient_spec_helper.rb index abd7dc159..86cf34afa 100644 --- a/spec/acceptance/httpclient/httpclient_spec_helper.rb +++ b/spec/acceptance/httpclient/httpclient_spec_helper.rb @@ -5,7 +5,7 @@ class << self def http_request(method, uri, options = {}, &block) uri = Addressable::URI.heuristic_parse(uri) - c = HTTPClient.new + c = options.fetch(:client) { HTTPClient.new } c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE c.set_basic_auth(nil, uri.user, uri.password) if uri.user params = [method, "#{uri.omit(:userinfo, :query).normalize.to_s}", From caeb0daeddf0ab6139f4269774d108ad084b0cd9 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Sun, 12 Aug 2012 15:35:02 -0700 Subject: [PATCH 18/20] Prefer super to alias method chain. WebMock was using alias method chain in lots of situations where it didn't need to, since most of the adapters subclass the HTTP client. --- lib/webmock/http_lib_adapters/curb_adapter.rb | 94 ++++++------------- .../em_http_request/em_http_request_0_x.rb | 8 +- .../em_http_request/em_http_request_1_x.rb | 13 +-- .../http_lib_adapters/httpclient_adapter.rb | 25 ++--- lib/webmock/http_lib_adapters/net_http.rb | 24 ++--- .../http_lib_adapters/patron_adapter.rb | 7 +- 6 files changed, 56 insertions(+), 115 deletions(-) diff --git a/lib/webmock/http_lib_adapters/curb_adapter.rb b/lib/webmock/http_lib_adapters/curb_adapter.rb index 407fd3b4c..9c0496e6c 100644 --- a/lib/webmock/http_lib_adapters/curb_adapter.rb +++ b/lib/webmock/http_lib_adapters/curb_adapter.rb @@ -170,117 +170,83 @@ def build_webmock_response ### Mocks of Curl::Easy methods below here. ### - def http_with_webmock(method) + def http(method) @webmock_method = method - http_without_webmock(method) + super end - alias_method :http_without_webmock, :http - alias_method :http, :http_with_webmock %w[ get head delete ].each do |verb| - define_method "http_#{verb}_with_webmock" do + define_method "http_#{verb}" do @webmock_method = verb - send( "http_#{verb}_without_webmock" ) + super() end - - alias_method "http_#{verb}_without_webmock", "http_#{verb}" - alias_method "http_#{verb}", "http_#{verb}_with_webmock" end - def http_put_with_webmock data = nil + def http_put data = nil @webmock_method = :put @put_data = data if data - http_put_without_webmock(data) + super end - alias_method :http_put_without_webmock, :http_put - alias_method :http_put, :http_put_with_webmock - def http_post_with_webmock *data + def http_post *data @webmock_method = :post @post_body = data.join('&') if data && !data.empty? - http_post_without_webmock(*data) + super end - alias_method :http_post_without_webmock, :http_post - alias_method :http_post, :http_post_with_webmock - - def perform_with_webmock + def perform @webmock_method ||= :get - curb_or_webmock do - perform_without_webmock - end + curb_or_webmock { super } end - alias :perform_without_webmock :perform - alias :perform :perform_with_webmock - def put_data_with_webmock= data + def put_data= data @webmock_method = :put @put_data = data - self.put_data_without_webmock = data + super end - alias_method :put_data_without_webmock=, :put_data= - alias_method :put_data=, :put_data_with_webmock= - def post_body_with_webmock= data + def post_body= data @webmock_method = :post - self.post_body_without_webmock = data + super end - alias_method :post_body_without_webmock=, :post_body= - alias_method :post_body=, :post_body_with_webmock= - def delete_with_webmock= value + def delete= value @webmock_method = :delete if value - self.delete_without_webmock = value + super end - alias_method :delete_without_webmock=, :delete= - alias_method :delete=, :delete_with_webmock= - def head_with_webmock= value + def head= value @webmock_method = :head if value - self.head_without_webmock = value + super end - alias_method :head_without_webmock=, :head= - alias_method :head=, :head_with_webmock= - def body_str_with_webmock - @body_str || body_str_without_webmock + def body_str + @body_str || super end - alias :body_str_without_webmock :body_str - alias :body_str :body_str_with_webmock - def response_code_with_webmock - @response_code || response_code_without_webmock + def response_code + @response_code || super end - alias :response_code_without_webmock :response_code - alias :response_code :response_code_with_webmock - def header_str_with_webmock - @header_str || header_str_without_webmock + def header_str + @header_str || super end - alias :header_str_without_webmock :header_str - alias :header_str :header_str_with_webmock - def last_effective_url_with_webmock - @last_effective_url || last_effective_url_without_webmock + def last_effective_url + @last_effective_url || super end - alias :last_effective_url_without_webmock :last_effective_url - alias :last_effective_url :last_effective_url_with_webmock - def content_type_with_webmock - @content_type || content_type_without_webmock + def content_type + @content_type || super end - alias :content_type_without_webmock :content_type - alias :content_type :content_type_with_webmock %w[ success failure header body complete progress ].each do |callback| class_eval <<-METHOD, __FILE__, __LINE__ - def on_#{callback}_with_webmock &block + def on_#{callback} &block @on_#{callback} = block - on_#{callback}_without_webmock &block + super end METHOD - alias_method "on_#{callback}_without_webmock", "on_#{callback}" - alias_method "on_#{callback}", "on_#{callback}_with_webmock" end end end diff --git a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb index 82cdb9caf..7e12a60ec 100644 --- a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb +++ b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_0_x.rb @@ -47,7 +47,7 @@ def close_connection end end - def send_request_with_webmock(&block) + def send_request(&block) request_signature = build_request_signature WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) @@ -61,7 +61,7 @@ def send_request_with_webmock(&block) webmock_response.should_timeout ? "WebMock timeout error" : nil) client elsif WebMock.net_connect_allowed?(request_signature.uri) - http = send_request_without_webmock(&block) + http = super http.callback { if WebMock::CallbackRegistry.any_callbacks? webmock_response = build_webmock_response(http) @@ -76,10 +76,6 @@ def send_request_with_webmock(&block) end end - alias_method :send_request_without_webmock, :send_request - alias_method :send_request, :send_request_with_webmock - - private def build_webmock_response(http) diff --git a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb index fc2b63604..c384bbb2e 100644 --- a/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb +++ b/lib/webmock/http_lib_adapters/em_http_request/em_http_request_1_x.rb @@ -48,7 +48,7 @@ def #{type}(options = {}, &blk) end class WebMockHttpConnection < HttpConnection - def webmock_activate_connection(client) + def activate_connection(client) request_signature = client.request_signature if client.stubbed_webmock_response @@ -65,13 +65,11 @@ def webmock_activate_connection(client) finalize_request(client) @conn.set_deferred_status :succeeded elsif WebMock.net_connect_allowed?(request_signature.uri) - real_activate_connection(client) + super else raise WebMock::NetConnectNotAllowedError.new(request_signature) end end - alias_method :real_activate_connection, :activate_connection - alias_method :activate_connection, :webmock_activate_connection end class WebMockHttpClient < EventMachine::HttpClient @@ -92,7 +90,7 @@ def setup(response, uri, error = nil) end end - def send_request_with_webmock(head, body) + def send_request(head, body) WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) if stubbed_webmock_response @@ -104,15 +102,12 @@ def send_request_with_webmock(head, body) } self elsif WebMock.net_connect_allowed?(request_signature.uri) - send_request_without_webmock(head, body) + super else raise WebMock::NetConnectNotAllowedError.new(request_signature) end end - alias_method :send_request_without_webmock, :send_request - alias_method :send_request, :send_request_with_webmock - def set_deferred_status(status, *args) if status == :succeeded && !stubbed_webmock_response && WebMock::CallbackRegistry.any_callbacks? webmock_response = build_webmock_response diff --git a/lib/webmock/http_lib_adapters/httpclient_adapter.rb b/lib/webmock/http_lib_adapters/httpclient_adapter.rb index fcaded096..4e0721f3d 100644 --- a/lib/webmock/http_lib_adapters/httpclient_adapter.rb +++ b/lib/webmock/http_lib_adapters/httpclient_adapter.rb @@ -28,16 +28,18 @@ def self.disable! class WebMockHTTPClient < HTTPClient + alias_method :do_get_block_without_webmock, :do_get_block + alias_method :do_get_stream_without_webmock, :do_get_stream - def do_get_block_with_webmock(req, proxy, conn, &block) - do_get_with_webmock(req, proxy, conn, false, &block) + def do_get_block(req, proxy, conn, &block) + do_get(req, proxy, conn, false, &block) end - def do_get_stream_with_webmock(req, proxy, conn, &block) - do_get_with_webmock(req, proxy, conn, true, &block) + def do_get_stream(req, proxy, conn, &block) + do_get(req, proxy, conn, true, &block) end - def do_get_with_webmock(req, proxy, conn, stream = false, &block) + def do_get(req, proxy, conn, stream = false, &block) request_signature = build_request_signature(req, :reuse_existing) WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) @@ -72,27 +74,18 @@ def do_get_with_webmock(req, proxy, conn, stream = false, &block) end end - def do_request_async_with_webmock(method, uri, query, body, extheader) + def do_request_async(method, uri, query, body, extheader) req = create_request(method, uri, query, body, extheader) request_signature = build_request_signature(req) webmock_request_signatures << request_signature if webmock_responses[request_signature] || WebMock.net_connect_allowed?(request_signature.uri) - do_request_async_without_webmock(method, uri, query, body, extheader) + super else raise WebMock::NetConnectNotAllowedError.new(request_signature) end end - alias_method :do_get_block_without_webmock, :do_get_block - alias_method :do_get_block, :do_get_block_with_webmock - - alias_method :do_get_stream_without_webmock, :do_get_stream - alias_method :do_get_stream, :do_get_stream_with_webmock - - alias_method :do_request_async_without_webmock, :do_request_async - alias_method :do_request_async, :do_request_async_with_webmock - def build_httpclient_response(webmock_response, stream = false, &block) body = stream ? StringIO.new(webmock_response.body) : webmock_response.body response = HTTP::Message.new_response(body) diff --git a/lib/webmock/http_lib_adapters/net_http.rb b/lib/webmock/http_lib_adapters/net_http.rb index 85a987af3..a02390270 100644 --- a/lib/webmock/http_lib_adapters/net_http.rb +++ b/lib/webmock/http_lib_adapters/net_http.rb @@ -32,11 +32,9 @@ def self.disable! @webMockNetHTTP = Class.new(Net::HTTP) do class << self - def socket_type_with_webmock + def socket_type StubSocket end - alias_method :socket_type_without_webmock, :socket_type - alias_method :socket_type, :socket_type_with_webmock if Module.method(:const_defined?).arity == 1 def const_defined?(name) @@ -63,7 +61,7 @@ def constants(inherit=true) end end - def request_with_webmock(request, body = nil, &block) + def request(request, body = nil, &block) request_signature = WebMock::NetHTTPUtility.request_signature_from_request(self, request, body) WebMock::RequestRegistry.instance.requested_signatures.put(request_signature) @@ -88,19 +86,17 @@ def request_with_webmock(request, body = nil, &block) response = if (started? && !WebMock::Config.instance.net_http_connect_on_start) || !started? @started = false #otherwise start_with_connect wouldn't execute and connect start_with_connect { - response = request_without_webmock(request, nil) + response = super(request, nil, &nil) after_request.call(response) } else - response = request_without_webmock(request, nil) + response = super(request, nil, &nil) after_request.call(response) end else raise WebMock::NetConnectNotAllowedError.new(request_signature) end end - alias_method :request_without_webmock, :request - alias_method :request, :request_with_webmock def start_without_connect raise IOError, 'HTTP session already opened' if @started @@ -116,15 +112,15 @@ def start_without_connect self end - def start_with_conditional_connect(&block) + alias_method :start_with_connect, :start + + def start(&block) if WebMock::Config.instance.net_http_connect_on_start - start_with_connect(&block) + super(&block) else start_without_connect(&block) end end - alias_method :start_with_connect, :start - alias_method :start, :start_with_conditional_connect def build_net_http_response(webmock_response, &block) response = Net::HTTPResponse.send(:response_class, webmock_response.status[0].to_s).new("1.0", webmock_response.status[0].to_s, webmock_response.status[1]) @@ -204,7 +200,7 @@ def readuntil(*args) module Net #:nodoc: all class WebMockNetBufferedIO < BufferedIO - def initialize_with_webmock(io, debug_output = nil) + def initialize(io, debug_output = nil) @read_timeout = 60 @rbuf = '' @debug_output = debug_output @@ -217,8 +213,6 @@ def initialize_with_webmock(io, debug_output = nil) end raise "Unable to create local socket" unless @io end - alias_method :initialize_without_webmock, :initialize - alias_method :initialize, :initialize_with_webmock end end diff --git a/lib/webmock/http_lib_adapters/patron_adapter.rb b/lib/webmock/http_lib_adapters/patron_adapter.rb index 5e45e6a02..e4f5634e7 100644 --- a/lib/webmock/http_lib_adapters/patron_adapter.rb +++ b/lib/webmock/http_lib_adapters/patron_adapter.rb @@ -13,7 +13,7 @@ class PatronAdapter < ::WebMock::HttpLibAdapter OriginalPatronSession = ::Patron::Session unless const_defined?(:OriginalPatronSession) class WebMockPatronSession < ::Patron::Session - def handle_request_with_webmock(req) + def handle_request(req) request_signature = WebMock::HttpLibAdapters::PatronAdapter.build_request_signature(req) @@ -28,7 +28,7 @@ def handle_request_with_webmock(req) {:lib => :patron}, request_signature, webmock_response) res elsif WebMock.net_connect_allowed?(request_signature.uri) - res = handle_request_without_webmock(req) + res = super if WebMock::CallbackRegistry.any_callbacks? webmock_response = WebMock::HttpLibAdapters::PatronAdapter. build_webmock_response(res) @@ -41,9 +41,6 @@ def handle_request_with_webmock(req) raise WebMock::NetConnectNotAllowedError.new(request_signature) end end - - alias_method :handle_request_without_webmock, :handle_request - alias_method :handle_request, :handle_request_with_webmock end def self.enable! From 64eefebdbfe7390857e2225e622e1502bd4c36f5 Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Wed, 15 Aug 2012 11:55:27 +0200 Subject: [PATCH 19/20] Added support for Addressable >= 2.3.0. Addressable 2.3.0 removed support for multiple query value notations and broke backwards compatibility. https://github.com/sporkmonger/addressable/commit/f51e290b5f68a98293327a7da84eb9e2d5f21c62 https://github.com/sporkmonger/addressable/issues/77 --- lib/webmock.rb | 1 + .../http_lib_adapters/excon_adapter.rb | 2 +- .../http_lib_adapters/httpclient_adapter.rb | 2 +- lib/webmock/request_pattern.rb | 12 +- lib/webmock/request_stub.rb | 2 +- lib/webmock/util/query_mapper.rb | 188 ++++++++++++++++++ lib/webmock/util/uri.rb | 19 +- .../httpclient/httpclient_spec_helper.rb | 2 +- spec/unit/util/uri_spec.rb | 6 +- webmock.gemspec | 2 +- 10 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 lib/webmock/util/query_mapper.rb diff --git a/lib/webmock.rb b/lib/webmock.rb index f33859720..905145d40 100644 --- a/lib/webmock.rb +++ b/lib/webmock.rb @@ -8,6 +8,7 @@ require 'webmock/errors' +require 'webmock/util/query_mapper' require 'webmock/util/uri' require 'webmock/util/headers' require 'webmock/util/hash_counter' diff --git a/lib/webmock/http_lib_adapters/excon_adapter.rb b/lib/webmock/http_lib_adapters/excon_adapter.rb index 9a3e14db9..ef945997f 100644 --- a/lib/webmock/http_lib_adapters/excon_adapter.rb +++ b/lib/webmock/http_lib_adapters/excon_adapter.rb @@ -42,7 +42,7 @@ def self.build_request(params) params = params.dup method = (params.delete(:method) || :get).to_s.downcase.to_sym params[:query] = to_query(params[:query]) if params[:query].is_a?(Hash) - uri = Addressable::URI.new(params).to_s + uri = Addressable::URI.new(params).to_s WebMock::RequestSignature.new method, uri, :body => params[:body], :headers => params[:headers] end diff --git a/lib/webmock/http_lib_adapters/httpclient_adapter.rb b/lib/webmock/http_lib_adapters/httpclient_adapter.rb index b7e449d2a..47f680135 100644 --- a/lib/webmock/http_lib_adapters/httpclient_adapter.rb +++ b/lib/webmock/http_lib_adapters/httpclient_adapter.rb @@ -132,7 +132,7 @@ def build_webmock_response(httpclient_response) def build_request_signature(req, reuse_existing = false) uri = WebMock::Util::URI.heuristic_parse(req.header.request_uri.to_s) - uri.query_values = req.header.request_query if req.header.request_query + uri.query = WebMock::Util::QueryMapper.values_to_query(req.header.request_query) if req.header.request_query uri.port = req.header.request_uri.port uri = uri.omit(:userinfo) diff --git a/lib/webmock/request_pattern.rb b/lib/webmock/request_pattern.rb index 0415acc6b..892007d5f 100644 --- a/lib/webmock/request_pattern.rb +++ b/lib/webmock/request_pattern.rb @@ -95,7 +95,7 @@ def add_query_params(query_params) elsif rSpecHashIncludingMatcher?(query_params) WebMock::Matchers::HashIncludingMatcher.from_rspec_matcher(query_params) else - Addressable::URI.parse('?' + query_params).query_values + WebMock::Util::QueryMapper.query_to_values(query_params) end end @@ -109,7 +109,7 @@ def to_s class URIRegexpPattern < URIPattern def matches?(uri) WebMock::Util::URI.variations_of_uri_as_strings(uri).any? { |u| u.match(@pattern) } && - (@query_params.nil? || @query_params == uri.query_values) + (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)) end def to_s @@ -123,7 +123,8 @@ class URIStringPattern < URIPattern def matches?(uri) if @pattern.is_a?(Addressable::URI) if @query_params - uri.omit(:query) === @pattern && (@query_params.nil? || @query_params == uri.query_values) + uri.omit(:query) === @pattern && + (@query_params.nil? || @query_params == WebMock::Util::QueryMapper.query_to_values(uri.query)) else uri === @pattern end @@ -135,7 +136,8 @@ def matches?(uri) def add_query_params(query_params) super if @query_params.is_a?(Hash) || @query_params.is_a?(String) - @pattern.query_values = (@pattern.query_values || {}).merge(@query_params) + query_hash = (WebMock::Util::QueryMapper.query_to_values(@pattern.query) || {}).merge(@query_params) + @pattern.query = WebMock::Util::QueryMapper.values_to_query(query_hash) @query_params = nil end end @@ -199,7 +201,7 @@ def body_as_hash(body, content_type) when :xml then Crack::XML.parse(body) else - Addressable::URI.parse('?' + body).query_values + WebMock::Util::QueryMapper.query_to_values(body) end end diff --git a/lib/webmock/request_stub.rb b/lib/webmock/request_stub.rb index 3e6156fc5..8b36589fc 100644 --- a/lib/webmock/request_stub.rb +++ b/lib/webmock/request_stub.rb @@ -81,7 +81,7 @@ def self.from_request_signature(signature) if signature.body.to_s != '' body = if signature.url_encoded? - Addressable::URI.parse('?' + signature.body).query_values + WebMock::Util::QueryMapper.query_to_values(signature.body) else signature.body end diff --git a/lib/webmock/util/query_mapper.rb b/lib/webmock/util/query_mapper.rb new file mode 100644 index 000000000..c3f64a794 --- /dev/null +++ b/lib/webmock/util/query_mapper.rb @@ -0,0 +1,188 @@ +module WebMock::Util + class QueryMapper + #This class is based on Addressable::URI pre 2.3.0 + + ## + # Converts the query component to a Hash value. + # + # @option [Symbol] notation + # May be one of :flat, :dot, or + # :subscript. The :dot notation is not + # supported for assignment. Default value is :subscript. + # + # @return [Hash, Array] The query string parsed as a Hash or Array object. + # + # @example + # WebMock::Util::QueryMapper.query_to_values("?one=1&two=2&three=3") + # #=> {"one" => "1", "two" => "2", "three" => "3"} + # WebMock::Util::QueryMapper("?one[two][three]=four").query_values + # #=> {"one" => {"two" => {"three" => "four"}}} + # WebMock::Util::QueryMapper.query_to_values("?one.two.three=four", + # :notation => :dot + # ) + # #=> {"one" => {"two" => {"three" => "four"}}} + # WebMock::Util::QueryMapper.query_to_values("?one[two][three]=four", + # :notation => :flat + # ) + # #=> {"one[two][three]" => "four"} + # WebMock::Util::QueryMapper.query_to_values("?one.two.three=four", + # :notation => :flat + # ) + # #=> {"one.two.three" => "four"} + # WebMock::Util::QueryMapper( + # "?one[two][three][]=four&one[two][three][]=five" + # ) + # #=> {"one" => {"two" => {"three" => ["four", "five"]}}} + # WebMock::Util::QueryMapper.query_to_values( + # "?one=two&one=three").query_values(:notation => :flat_array) + # #=> [['one', 'two'], ['one', 'three']] + def self.query_to_values(query, options={}) + defaults = {:notation => :subscript} + options = defaults.merge(options) + if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation]) + raise ArgumentError, + "Invalid notation. Must be one of: " + + "[:flat, :dot, :subscript, :flat_array]." + end + dehash = lambda do |hash| + hash.each do |(key, value)| + if value.kind_of?(Hash) + hash[key] = dehash.call(value) + end + end + if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ } + hash.sort.inject([]) do |accu, (_, value)| + accu << value; accu + end + else + hash + end + end + return nil if query == nil + empty_accumulator = :flat_array == options[:notation] ? [] : {} + return ((query.split("&").map do |pair| + pair.split("=", 2) if pair && !pair.empty? + end).compact.inject(empty_accumulator.dup) do |accumulator, (key, value)| + value = true if value.nil? + key = Addressable::URI.unencode_component(key) + if value != true + value = Addressable::URI.unencode_component(value.gsub(/\+/, " ")) + end + if options[:notation] == :flat + if accumulator[key] + raise ArgumentError, "Key was repeated: #{key.inspect}" + end + accumulator[key] = value + elsif options[:notation] == :flat_array + accumulator << [key, value] + else + if options[:notation] == :dot + array_value = false + subkeys = key.split(".") + elsif options[:notation] == :subscript + array_value = !!(key =~ /\[\]$/) + subkeys = key.split(/[\[\]]+/) + end + current_hash = accumulator + for i in 0...(subkeys.size - 1) + subkey = subkeys[i] + current_hash[subkey] = {} unless current_hash[subkey] + current_hash = current_hash[subkey] + end + if array_value + current_hash[subkeys.last] = [] unless current_hash[subkeys.last] + current_hash[subkeys.last] << value + else + current_hash[subkeys.last] = value + end + end + accumulator + end).inject(empty_accumulator.dup) do |accumulator, (key, value)| + if options[:notation] == :flat_array + accumulator << [key, value] + else + accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value + end + accumulator + end + end + + ## + # Sets the query component for this URI from a Hash object. + # This method produces a query string using the :subscript notation. + # An empty Hash will result in a nil query. + # + # @param [Hash, #to_hash, Array] new_query_values The new query values. + def self.values_to_query(new_query_values) + if new_query_values == nil + return nil + end + + if !new_query_values.is_a?(Array) + if !new_query_values.respond_to?(:to_hash) + raise TypeError, + "Can't convert #{new_query_values.class} into Hash." + end + new_query_values = new_query_values.to_hash + new_query_values = new_query_values.map do |key, value| + key = key.to_s if key.kind_of?(Symbol) + [key, value] + end + # Useful default for OAuth and caching. + # Only to be used for non-Array inputs. Arrays should preserve order. + new_query_values.sort! + end + + ## + # Joins and converts parent and value into a properly encoded and + # ordered URL query. + # + # @private + # @param [String] parent an URI encoded component. + # @param [Array, Hash, Symbol, #to_str] value + # + # @return [String] a properly escaped and ordered URL query. + to_query = lambda do |parent, value| + if value.is_a?(Hash) + value = value.map do |key, val| + [ + Addressable::URI.encode_component(key, Addressable::URI::CharacterClasses::UNRESERVED), + val + ] + end + value.sort! + buffer = "" + value.each do |key, val| + new_parent = "#{parent}[#{key}]" + buffer << "#{to_query.call(new_parent, val)}&" + end + return buffer.chop + elsif value.is_a?(Array) + buffer = "" + value.each_with_index do |val, i| + new_parent = "#{parent}[#{i}]" + buffer << "#{to_query.call(new_parent, val)}&" + end + return buffer.chop + elsif value == true + return parent + else + encoded_value = Addressable::URI.encode_component( + value, Addressable::URI::CharacterClasses::UNRESERVED + ) + return "#{parent}=#{encoded_value}" + end + end + + # new_query_values have form [['key1', 'value1'], ['key2', 'value2']] + buffer = "" + new_query_values.each do |parent, value| + encoded_parent = Addressable::URI.encode_component( + parent, Addressable::URI::CharacterClasses::UNRESERVED + ) + buffer << "#{to_query.call(encoded_parent, value)}&" + end + return buffer.chop + end + end +end diff --git a/lib/webmock/util/uri.rb b/lib/webmock/util/uri.rb index 91241942f..6ed63d920 100644 --- a/lib/webmock/util/uri.rb +++ b/lib/webmock/util/uri.rb @@ -1,23 +1,22 @@ -module Addressable - class URI - module CharacterClasses - USERINFO = UNRESERVED + SUB_DELIMS + "\\:" - end - end -end - module WebMock module Util class URI + module CharacterClasses + USERINFO = Addressable::URI::CharacterClasses::UNRESERVED + Addressable::URI::CharacterClasses::SUB_DELIMS + "\\:" + end + ADDRESSABLE_URIS = Hash.new do |hash, key| hash[key] = Addressable::URI.heuristic_parse(key) end NORMALIZED_URIS = Hash.new do |hash, uri| normalized_uri = WebMock::Util::URI.heuristic_parse(uri) - normalized_uri.query_values = sort_query_values(normalized_uri.query_values) if normalized_uri.query_values + if normalized_uri.query_values + sorted_query_values = sort_query_values(WebMock::Util::QueryMapper.query_to_values(normalized_uri.query) || {}) + normalized_uri.query = WebMock::Util::QueryMapper.values_to_query(sorted_query_values) + end normalized_uri = normalized_uri.normalize #normalize! is slower normalized_uri.port = normalized_uri.inferred_port unless normalized_uri.port hash[uri] = normalized_uri @@ -63,7 +62,7 @@ def self.strip_default_port_from_uri_string(uri_string) end def self.encode_unsafe_chars_in_userinfo(userinfo) - Addressable::URI.encode_component(userinfo, Addressable::URI::CharacterClasses::USERINFO) + Addressable::URI.encode_component(userinfo, WebMock::Util::URI::CharacterClasses::USERINFO) end def self.is_uri_localhost?(uri) diff --git a/spec/acceptance/httpclient/httpclient_spec_helper.rb b/spec/acceptance/httpclient/httpclient_spec_helper.rb index 86cf34afa..1095d0489 100644 --- a/spec/acceptance/httpclient/httpclient_spec_helper.rb +++ b/spec/acceptance/httpclient/httpclient_spec_helper.rb @@ -9,7 +9,7 @@ def http_request(method, uri, options = {}, &block) c.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE c.set_basic_auth(nil, uri.user, uri.password) if uri.user params = [method, "#{uri.omit(:userinfo, :query).normalize.to_s}", - uri.query_values, options[:body], options[:headers] || {}] + WebMock::Util::QueryMapper.query_to_values(uri.query), options[:body], options[:headers] || {}] if HTTPClientSpecHelper.async_mode connection = c.request_async(*params) connection.join diff --git a/spec/unit/util/uri_spec.rb b/spec/unit/util/uri_spec.rb index 12b334016..eb18f19a3 100644 --- a/spec/unit/util/uri_spec.rb +++ b/spec/unit/util/uri_spec.rb @@ -170,19 +170,19 @@ it "should successfully handle array parameters" do uri_string = 'http://www.example.com:80/path?a[]=b&a[]=c' uri = WebMock::Util::URI.normalize_uri(uri_string) - uri.query_values.should == {"a"=>["b", "c"]} + WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"a"=>["b", "c"]} end it "should successfully handle hash parameters" do uri_string = 'http://www.example.com:80/path?a[d]=b&a[e]=c&a[b][c]=1' uri = WebMock::Util::URI.normalize_uri(uri_string) - uri.query_values.should == {"a"=>{"d"=>"b", "e"=>"c", "b"=>{"c"=>"1"}}} + WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"a"=>{"d"=>"b", "e"=>"c", "b"=>{"c"=>"1"}}} end it "should successfully handle nested hash parameters" do uri_string = 'http://www.example.com:80/path?one[two][three][]=four&one[two][three][]=five' uri = WebMock::Util::URI.normalize_uri(uri_string) - uri.query_values.should == {"one"=>{"two"=>{"three" => ["four", "five"]}}} + WebMock::Util::QueryMapper.query_to_values(uri.query).should == {"one"=>{"two"=>{"three" => ["four", "five"]}}} end end diff --git a/webmock.gemspec b/webmock.gemspec index b530ae9a0..7fb70e5d6 100644 --- a/webmock.gemspec +++ b/webmock.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |s| s.rubyforge_project = 'webmock' - s.add_dependency 'addressable', '~> 2.2.8' + s.add_dependency 'addressable', '>= 2.2.7' s.add_dependency 'crack', '>=0.1.7' s.add_development_dependency 'rspec', '~> 2.10' From 43098e1d916f87a1f5bfb83f83fddae4958f3c5a Mon Sep 17 00:00:00 2001 From: Bartosz Blimke Date: Wed, 15 Aug 2012 11:59:37 +0200 Subject: [PATCH 20/20] Version 1.8.9 --- CHANGELOG.md | 12 ++++++++++++ README.md | 1 + lib/webmock/version.rb | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0949a1a4a..4c1eebbd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 1.8.9 + +* Fixed problem with caching nil responses when the same HTTPClient instance is used. + + Thanks to [Myron Marston](https://github.com/myronmarston) + +* Added support for Addressable >= 2.3.0. Addressable 2.3.0 removed support for multiple query value notations and broke backwards compatibility. + + https://github.com/sporkmonger/addressable/commit/f51e290b5f68a98293327a7da84eb9e2d5f21c62 + https://github.com/sporkmonger/addressable/issues/77 + + ## 1.8.8 * Fixed Net::HTTP adapter so that it returns `nil` for an empty body response. diff --git a/README.md b/README.md index 4e9f69c0c..e0e313250 100644 --- a/README.md +++ b/README.md @@ -712,6 +712,7 @@ People who submitted patches and new features or suggested improvements. Many th * Julien Boyer * Kevin Glowacz * Hans Hasselberg +* Andrew France For a full list of contributors you can visit the [contributors](https://github.com/bblimke/webmock/contributors) page. diff --git a/lib/webmock/version.rb b/lib/webmock/version.rb index a94e28b24..f9f0a5dd0 100644 --- a/lib/webmock/version.rb +++ b/lib/webmock/version.rb @@ -1,3 +1,3 @@ module WebMock - VERSION = '1.8.8' unless defined?(::WebMock::VERSION) + VERSION = '1.8.9' unless defined?(::WebMock::VERSION) end