diff --git a/lib/webmock.rb b/lib/webmock.rb index 3fafa2f5e..7adeaa526 100644 --- a/lib/webmock.rb +++ b/lib/webmock.rb @@ -14,6 +14,7 @@ require 'webmock/util/headers' require 'webmock/util/hash_counter' require 'webmock/util/hash_keys_stringifier' +require 'webmock/util/values_stringifier' require 'webmock/util/json' require 'webmock/util/version_checker' require 'webmock/util/hash_validator' diff --git a/lib/webmock/request_pattern.rb b/lib/webmock/request_pattern.rb index 4e1ff5681..c7c4aa6d7 100644 --- a/lib/webmock/request_pattern.rb +++ b/lib/webmock/request_pattern.rb @@ -243,7 +243,7 @@ def matches?(body, content_type = "") if (@pattern).is_a?(Hash) return true if @pattern.empty? - matching_hashes?(body_as_hash(body, content_type), @pattern) + matching_body_hashes?(body_as_hash(body, content_type), @pattern, content_type) elsif (@pattern).is_a?(WebMock::Matchers::HashIncludingMatcher) @pattern == body_as_hash(body, content_type) else @@ -298,15 +298,16 @@ def assert_non_multipart_body(content_type) # # @return [Boolean] true if the paramaters match the comparison # hash, false if not. - def matching_hashes?(query_parameters, pattern) + def matching_body_hashes?(query_parameters, pattern, content_type) return false unless query_parameters.is_a?(Hash) return false unless query_parameters.keys.sort == pattern.keys.sort query_parameters.each do |key, actual| expected = pattern[key] if actual.is_a?(Hash) && expected.is_a?(Hash) - return false unless matching_hashes?(actual, expected) + return false unless matching_body_hashes?(actual, expected, content_type) else + expected = WebMock::Util::ValuesStringifier.stringify_values(expected) if url_encoded_body?(content_type) return false unless expected === actual end end @@ -321,6 +322,9 @@ def normalize_hash(hash) Hash[WebMock::Util::HashKeysStringifier.stringify_keys!(hash, deep: true).sort] end + def url_encoded_body?(content_type) + content_type =~ %r{^application/x-www-form-urlencoded} + end end class HeadersPattern diff --git a/lib/webmock/util/values_stringifier.rb b/lib/webmock/util/values_stringifier.rb new file mode 100644 index 000000000..6eea0e6b8 --- /dev/null +++ b/lib/webmock/util/values_stringifier.rb @@ -0,0 +1,20 @@ +class WebMock::Util::ValuesStringifier + def self.stringify_values(value) + case value + when nil + value + when Hash + Hash[ + value.map do |k, v| + [k, stringify_values(v)] + end + ] + when Array + value.map do |v| + stringify_values(v) + end + else + value.to_s + end + end +end diff --git a/spec/acceptance/shared/stubbing_requests.rb b/spec/acceptance/shared/stubbing_requests.rb index da2dfa6dd..28c3a2447 100644 --- a/spec/acceptance/shared/stubbing_requests.rb +++ b/spec/acceptance/shared/stubbing_requests.rb @@ -155,6 +155,24 @@ body: 'c[d][]=f&a=1&c[d][]=e') }.to raise_error(WebMock::NetConnectNotAllowedError, %r(Real HTTP connections are disabled. Unregistered request: POST http://www.example.com/ with body 'c\[d\]\[\]=f&a=1&c\[d\]\[\]=e')) end + + describe "for request with form url encoded body and content type" do + it "should match if stubbed request body hash has string values matching string values in request body" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => '1'}) + expect(http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, + body: "foo=1").status).to eq("200") + end + + it "should match if stubbed request body hash has NON string values matching string values in request body" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => 1}) + expect(http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, + body: "foo=1").status).to eq("200") + end + end end @@ -187,6 +205,30 @@ :post, "http://www.example.com/", headers: {'Content-Type' => 'application/json'}, body: "{\"foo\":\"a b c\"}").status).to eq("200") end + + it "should match if stubbed request body hash has NON string values matching NON string values in request body" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => 1}) + expect(http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/json'}, + body: "{\"foo\":1}").status).to eq("200") + end + + it "should not match if stubbed request body hash has string values matching NON string values in request body" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => '1'}) + expect{http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/json'}, + body: "{\"foo\":1}") }.to raise_error(WebMock::NetConnectNotAllowedError) + end + + it "should not match if stubbed request body hash has NON string values matching string values in request body" do + WebMock.reset! + stub_request(:post, "www.example.com").with(body: {"foo" => 1}) + expect{http_request( + :post, "http://www.example.com/", headers: {'Content-Type' => 'application/json'}, + body: "{\"foo\":\"1\"}") }.to raise_error(WebMock::NetConnectNotAllowedError) + end end describe "for request with xml body and content type is set to xml" do