diff --git a/lib/pact/consumer_contract/interaction_v2_parser.rb b/lib/pact/consumer_contract/interaction_v2_parser.rb index fbc8b97..35dc520 100644 --- a/lib/pact/consumer_contract/interaction_v2_parser.rb +++ b/lib/pact/consumer_contract/interaction_v2_parser.rb @@ -36,7 +36,7 @@ def self.parse_request request_hash, options # in the translation between string => structured object, as we don't know/store which # query string convention was used. if query_is_string - request_hash['query'] = Pact::QueryHash.new(request_hash['query'], original_query_string) + request_hash['query'] = Pact::QueryHash.new(request_hash['query'], original_query_string, Pact::Query.parsed_as_nested?(request_hash['query'])) end request = Pact::Request::Expected.from_hash(request_hash) end diff --git a/lib/pact/consumer_contract/query.rb b/lib/pact/consumer_contract/query.rb index ee7c6de..7b19af2 100644 --- a/lib/pact/consumer_contract/query.rb +++ b/lib/pact/consumer_contract/query.rb @@ -6,6 +6,8 @@ class Query DEFAULT_SEP = /[&;] */n COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n } + class NestedQuery < Hash; end + def self.create query if query.is_a? Hash Pact::QueryHash.new(query) @@ -18,12 +20,16 @@ def self.is_a_query_object?(object) object.is_a?(Pact::QueryHash) || object.is_a?(Pact::QueryString) end + def self.parsed_as_nested?(object) + object.is_a?(NestedQuery) + end + def self.parse_string query_string - parsed_query = parse_query(query_string) + parsed_query = parse_string_as_non_nested_query(query_string) # If Rails nested params... if parsed_query.keys.any?{ | key| key =~ /\[.*\]/ } - parse_nested_query(query_string) + parse_string_as_nested_query(query_string) else parsed_query.each_with_object({}) do | (key, value), new_hash | new_hash[key] = [*value] @@ -33,7 +39,7 @@ def self.parse_string query_string # Ripped from Rack to avoid adding an unnecessary dependency, thank you Rack # https://github.com/rack/rack/blob/649c72bab9e7b50d657b5b432d0c205c95c2be07/lib/rack/utils.rb - def self.parse_query(qs, d = nil, &unescaper) + def self.parse_string_as_non_nested_query(qs, d = nil, &unescaper) unescaper ||= method(:unescape) params = {} @@ -56,7 +62,7 @@ def self.parse_query(qs, d = nil, &unescaper) return params.to_h end - def self.parse_nested_query(qs, d = nil) + def self.parse_string_as_nested_query(qs, d = nil) params = {} unless qs.nil? || qs.empty? @@ -67,7 +73,7 @@ def self.parse_nested_query(qs, d = nil) end end - return params.to_h + return NestedQuery[params.to_h] end def self.normalize_params(params, name, v) diff --git a/lib/pact/consumer_contract/query_hash.rb b/lib/pact/consumer_contract/query_hash.rb index e8e63de..ee7812f 100644 --- a/lib/pact/consumer_contract/query_hash.rb +++ b/lib/pact/consumer_contract/query_hash.rb @@ -10,9 +10,18 @@ class QueryHash attr_reader :original_string - def initialize(query, original_string = nil) + def initialize(query, original_string = nil, nested = false) @hash = query.nil? ? query : convert_to_hash_of_arrays(query) @original_string = original_string + @nested = nested + end + + def nested? + @nested + end + + def any_key_contains_square_brackets? + query.keys.any?{ |key| key =~ /\[.*\]/ } end def as_json(opts = {}) @@ -35,7 +44,14 @@ def ==(other) # from the actual query string. def difference(other) require 'pact/matchers' # avoid recursive loop between this file, pact/reification and pact/matchers - Pact::Matchers.diff(query, symbolize_keys(convert_to_hash_of_arrays(Query.parse_string(other.query))), allow_unexpected_keys: false) + + if any_key_contains_square_brackets? + other_query_hash_non_nested = Query.parse_string_as_non_nested_query(other.query) + Pact::Matchers.diff(query, convert_to_hash_of_arrays(other_query_hash_non_nested), allow_unexpected_keys: false) + else + other_query_hash = Query.parse_string(other.query) + Pact::Matchers.diff(query, symbolize_keys(convert_to_hash_of_arrays(other_query_hash)), allow_unexpected_keys: false) + end end def query diff --git a/spec/lib/pact/consumer_contract/query_hash_spec.rb b/spec/lib/pact/consumer_contract/query_hash_spec.rb index 129bf3c..f5818ed 100644 --- a/spec/lib/pact/consumer_contract/query_hash_spec.rb +++ b/spec/lib/pact/consumer_contract/query_hash_spec.rb @@ -99,6 +99,24 @@ module Pact end end + context "when the key in an expected hash contains [] and the actual query string also contains []" do + let(:query) { { "catId[]" => Pact.each_like("1") } } + let(:other) { QueryString.new("catId[]=1&catId[]=2")} + + it "returns an empty diff" do + expect(subject.difference(other)).to be_empty + end + end + + context "when the key in an expected hash does not contain [] and the actual query string contains [], GAH!!! something is going to get broken/bug missed no matter which way I code this." do + let(:query) { { "catId" => Pact.each_like("1") } } + let(:other) { QueryString.new("catId[]=1&catId[]=2")} + + it "returns an empty diff and it probably shouldn't but if I change it now, all the Rails people are going to get mad at me" do + expect(subject.difference(other)).to be_empty + end + end + context "when there is an ArrayLike" do let(:query) { { param: Pact.each_like("1") } } let(:other) { QueryString.new('param=1¶m=2') } diff --git a/spec/lib/pact/consumer_contract/query_spec.rb b/spec/lib/pact/consumer_contract/query_spec.rb index d0b1df9..5314b44 100644 --- a/spec/lib/pact/consumer_contract/query_spec.rb +++ b/spec/lib/pact/consumer_contract/query_spec.rb @@ -28,6 +28,10 @@ module Pact expect(subject).to eq "foo" => "bar2", "baz" => ["thing1", "thing2"] end + it "returns a NestedQuery" do + expect(subject).to be_a(Query::NestedQuery) + end + it "handles arrays and hashes" do expect(Query.parse_string("a[]=1&a[]=2&b[c]=3")).to eq "a" => ["1","2"], "b" => { "c" => "3" } end diff --git a/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb b/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb index 6dbf74c..7ff3117 100644 --- a/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb +++ b/spec/lib/pact/matchers/matchers_messages_mismatched_value_spec.rb @@ -10,11 +10,11 @@ module Pact::Matchers extend RubyVersionHelpers describe "diff" do - STRING = "foo" - INT = 1 - FLOAT = 1.0 - HASH = {foo: "bar"} - ARRAY = ["foo"] + STRING ||= "foo" + INT ||= 1 + FLOAT ||= 1.0 + HASH ||= {foo: "bar"} + ARRAY ||= ["foo"] COMBINATIONS = [ [STRING, "bar", "Expected \"foo\" but got \"bar\" at "], diff --git a/spec/lib/pact/matchers/matchers_messages_regexp_spec.rb b/spec/lib/pact/matchers/matchers_messages_regexp_spec.rb index 43c9c51..4eadeda 100644 --- a/spec/lib/pact/matchers/matchers_messages_regexp_spec.rb +++ b/spec/lib/pact/matchers/matchers_messages_regexp_spec.rb @@ -10,11 +10,11 @@ module Pact::Matchers include RubyVersionHelpers describe "diff" do - STRING = "foo" - INT = 1 - FLOAT = 1.0 - HASH = {foo: "bar"} - ARRAY = ["foo"] + STRING ||= "foo" + INT ||= 1 + FLOAT ||= 1.0 + HASH ||= {foo: "bar"} + ARRAY ||= ["foo"] let(:term) { Pact.term(/foo/, "food") }