From 0f22db22746dcc4947e9a190fb980d455d4157a4 Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Fri, 23 Mar 2018 20:41:40 +1100 Subject: [PATCH] feat: locate matching rules correctly for v3 pacts --- .../http_consumer_contract_parser.rb | 13 ++- lib/pact/consumer_contract/interaction.rb | 29 +++++- lib/pact/matching_rules.rb | 21 +++- spec/fixtures/pact-http-v3.json | 36 +++++++ spec/fixtures/pact-v3.json | 27 ------ .../http_consumer_contract_parser_spec.rb | 17 ++++ .../consumer_contract/interaction_spec.rb | 2 +- spec/lib/pact/matching_rules_spec.rb | 97 +++++++++++++++++++ 8 files changed, 204 insertions(+), 38 deletions(-) create mode 100644 spec/fixtures/pact-http-v3.json delete mode 100644 spec/fixtures/pact-v3.json create mode 100644 spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb create mode 100644 spec/lib/pact/matching_rules_spec.rb diff --git a/lib/pact/consumer_contract/http_consumer_contract_parser.rb b/lib/pact/consumer_contract/http_consumer_contract_parser.rb index 27f3557..a2443cf 100644 --- a/lib/pact/consumer_contract/http_consumer_contract_parser.rb +++ b/lib/pact/consumer_contract/http_consumer_contract_parser.rb @@ -4,8 +4,8 @@ class HttpConsumerContractParser def call(hash) hash = symbolize_keys(hash) - interactions = hash[:interactions].collect { |hash| Interaction.from_hash(hash)} - + options = { pact_specification_version: pact_specification_version(hash) } + interactions = hash[:interactions].collect { |hash| Interaction.from_hash(hash, options) } ConsumerContract.new( :consumer => ServiceConsumer.from_hash(hash[:consumer]), :provider => ServiceProvider.from_hash(hash[:provider]), @@ -13,6 +13,15 @@ def call(hash) ) end + def pact_specification_version hash + # TODO handle all 3 ways of defining this... + # metadata.pactSpecificationVersion + maybe_pact_specification_version_1 = hash[:metadata] && hash[:metadata]['pactSpecification'] && hash[:metadata]['pactSpecification']['version'] + maybe_pact_specification_version_2 = hash[:metadata] && hash[:metadata]['pactSpecificationVersion'] + pact_specification_version = maybe_pact_specification_version_1 || maybe_pact_specification_version_2 + Gem::Version.new(pact_specification_version) + end + def can_parse?(hash) hash.key?('interactions') || hash.key?(:interactions) end diff --git a/lib/pact/consumer_contract/interaction.rb b/lib/pact/consumer_contract/interaction.rb index dc476a3..3f6e7bb 100644 --- a/lib/pact/consumer_contract/interaction.rb +++ b/lib/pact/consumer_contract/interaction.rb @@ -19,10 +19,33 @@ def initialize attributes = {} @provider_state = attributes[:provider_state] || attributes[:providerState] end - def self.from_hash hash - request_hash = Pact::MatchingRules.merge(hash['request'], hash['request']['matchingRules']) + def self.from_hash hash, options = {} + pact_specification_version = options[:pact_specification_version] || Gem::Version.new("") # use some global default + case pact_specification_version.segments.first + when 1, 2 then parse_v2_interaction(hash, pact_specification_version: pact_specification_version) + else parse_v3_interaction(hash, pact_specification_version: pact_specification_version) + end + end + + def self.parse_v2_interaction hash, options + request_hash = Pact::MatchingRules.merge(hash['request'], hash['request']['matchingRules'], options) request = Pact::Request::Expected.from_hash(request_hash) - response_hash = Pact::MatchingRules.merge(hash['response'], hash['response']['matchingRules']) + response_hash = Pact::MatchingRules.merge(hash['response'], hash['response']['matchingRules'], options) + response = Pact::Response.from_hash(response_hash) + new(symbolize_keys(hash).merge(request: request, response: response)) + end + + def self.parse_v3_interaction hash, options + + request_hash = hash['request'].keys.each_with_object({}) do | key, new_hash | + new_hash[key] = Pact::MatchingRules.merge(hash['request'][key], hash['request'].fetch('matchingRules', {})[key], options) + end + request = Pact::Request::Expected.from_hash(request_hash) + + response_hash = hash['response'].keys.each_with_object({}) do | key, new_hash | + new_hash[key] = Pact::MatchingRules.merge(hash['response'][key], hash['response'].fetch('matchingRules', {})[key], options) + end + response = Pact::Response.from_hash(response_hash) new(symbolize_keys(hash).merge(request: request, response: response)) end diff --git a/lib/pact/matching_rules.rb b/lib/pact/matching_rules.rb index 6df3927..4c98169 100644 --- a/lib/pact/matching_rules.rb +++ b/lib/pact/matching_rules.rb @@ -1,17 +1,28 @@ require 'pact/matching_rules/extract' require 'pact/matching_rules/merge' +require 'pact/matching_rules/v3/merge' module Pact module MatchingRules # @api public Used by pact-mock_service - def self.extract object_graph + def self.extract object_graph, options = {} Extract.(object_graph) end - def self.merge object_graph, matching_rules - Merge.(object_graph, matching_rules) + def self.merge object_graph, matching_rules, options = {} + case options[:pact_specification_version].segments.first + when nil + Pact.configuration.error_stream.puts "No pact specification version found, using v2 code to parse contract" + Merge.(object_graph, matching_rules) + when 1, 2 + Merge.(object_graph, matching_rules) + when 3 + V3::Merge.(object_graph, matching_rules) + else + Pact.configuration.error_stream.puts "This code only knows how to parse v3 pacts, attempting to parse v#{options[:pact_specification_version]} pact using v3 code." + V3::Merge.(object_graph, matching_rules) + end end - end -end \ No newline at end of file +end diff --git a/spec/fixtures/pact-http-v3.json b/spec/fixtures/pact-http-v3.json new file mode 100644 index 0000000..2d73807 --- /dev/null +++ b/spec/fixtures/pact-http-v3.json @@ -0,0 +1,36 @@ +{ + "consumer": { + "name": "consumer" + }, + "interactions": [ + { + "description": "a test request", + "providerState": "the weather is sunny", + "request": { + "method": "get", + "path": "/foo" + }, + "response": { + "body": { + "foo": "bar" + }, + "matchingRules": { + "body": { + "$.foo": { + "matchers": [{ "match": "type" }] + } + } + }, + "status": 200 + } + } + ], + "provider": { + "name": "provider" + }, + "metadata": { + "pactSpecification": { + "version": "3.0" + } + } +} \ No newline at end of file diff --git a/spec/fixtures/pact-v3.json b/spec/fixtures/pact-v3.json deleted file mode 100644 index a43aef2..0000000 --- a/spec/fixtures/pact-v3.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "consumer": { - "name": "Consumer" - }, - "provider": { - "name": "Provider" - }, - "messages": [ - { - "contents": { - "foo": "bar" - }, - "description": "Published credit data", - "metaData": { - "contentType": "application/json" - }, - "providerState": "or maybe 'scenario'? not sure about this", - "matchingRules": { - "body": { - "$.foo": { - "matchers": [{"match" : "type"}] - } - } - } - } - ] -} diff --git a/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb b/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb new file mode 100644 index 0000000..cbca7b3 --- /dev/null +++ b/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb @@ -0,0 +1,17 @@ +require 'pact/consumer_contract/http_consumer_contract_parser' + +module Pact + describe HttpConsumerContractParser do + describe "#call integration test" do + subject { HttpConsumerContractParser.new.call(pact_hash) } + + context "with a v3 pact" do + let(:pact_hash) { load_json_fixture('pact-http-v3.json') } + + it "correctly parses the pact" do + expect(subject.interactions.first.response.body['foo']).to be_a(Pact::SomethingLike) + end + end + end + end +end diff --git a/spec/lib/pact/consumer_contract/interaction_spec.rb b/spec/lib/pact/consumer_contract/interaction_spec.rb index f6e9801..d384a0c 100644 --- a/spec/lib/pact/consumer_contract/interaction_spec.rb +++ b/spec/lib/pact/consumer_contract/interaction_spec.rb @@ -65,7 +65,7 @@ module Consumer context "when there are matching rules" do let(:hash) { load_json_fixture 'interaction-with-matching-rules.json' } - subject { Interaction.from_hash hash } + subject { Interaction.from_hash hash, pact_specification_version: Gem::Version.new("2") } it "merges the rules with the example for the request" do expect(subject.request.body['name']).to be_instance_of(Pact::Term) diff --git a/spec/lib/pact/matching_rules_spec.rb b/spec/lib/pact/matching_rules_spec.rb new file mode 100644 index 0000000..ee86090 --- /dev/null +++ b/spec/lib/pact/matching_rules_spec.rb @@ -0,0 +1,97 @@ +require 'pact/matching_rules' + +module Pact + module MatchingRules + describe ".merge" do + before do + allow(V3::Merge).to receive(:call) + allow(Merge).to receive(:call) + allow(Pact.configuration.error_stream).to receive(:puts) + end + + let(:object) { double('object') } + let(:rules) { double('rules') } + let(:options) { { pact_specification_version: Gem::Version.new(pact_specification_version) } } + + subject { MatchingRules.merge(object, rules, options)} + + context "when the pact_specification_version is nil" do + let(:pact_specification_version) { nil } + + it "prints a warning" do + expect(Pact.configuration.error_stream).to receive(:puts).with(/No pact specification version found/) + subject + end + + it "calls Merge" do + expect(Merge).to receive(:call) + subject + end + end + + context "when the pact_specification_version starts with '1.'" do + let(:pact_specification_version) { "1.0" } + + it "calls Merge" do + expect(Merge).to receive(:call) + subject + end + end + + context "when the pact_specification_version is with '1'" do + let(:pact_specification_version) { "1" } + + it "calls Merge" do + expect(Merge).to receive(:call) + subject + end + end + + context "when the pact_specification_version starts with '2.'" do + let(:pact_specification_version) { "2.0" } + + it "calls Merge" do + expect(Merge).to receive(:call) + subject + end + end + + context "when the pact_specification_version starts with '3.'" do + let(:pact_specification_version) { "3.0" } + + it "calls V3::Merge" do + expect(V3::Merge).to receive(:call) + subject + end + end + + context "when the pact_specification_version starts with '4.'" do + let(:pact_specification_version) { "4.0" } + + it "prints a warning" do + expect(Pact.configuration.error_stream).to receive(:puts).with(/only knows how to parse v3 pacts/) + subject + end + + it "calls V3::Merge" do + expect(V3::Merge).to receive(:call) + subject + end + end + + context "when the pact_specification_version is with '11'" do + let(:pact_specification_version) { "11" } + + it "prints a warning" do + expect(Pact.configuration.error_stream).to receive(:puts).with(/only knows how to parse v3 pacts/) + subject + end + + it "calls V3::Merge" do + expect(V3::Merge).to receive(:call) + subject + end + end + end + end +end