pact-support 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d63ce6f1b637b9ec8c1aa7c097631c13134c8085
4
- data.tar.gz: e7f099a1364dd8e6ea4da06c5e6c15e6d1b83b38
3
+ metadata.gz: bbfe3583ddef289cebbe55fdfe7319853eb2c88b
4
+ data.tar.gz: cacbf113d031f4af3552ca441d12b1f47c1decf4
5
5
  SHA512:
6
- metadata.gz: 6fab573b4105cb73ab146e406cfddbb56508160324e639b35a287f823a1b8c1570347768ef0198dbcf7454ea9dbfef7f0a53f9e3b48c9e22f430c5cd4d45548e
7
- data.tar.gz: 424fdc09077746fda17301499934ef326c6f1af664baac0cc08e5f3d3c318d1047a3a7a95db7b90cf82d6139da774cb579fedb0a56929aba6fdc66dbaf15ca0d
6
+ metadata.gz: f100ad0607607f0ef0de629774b4f8fa4eb6a66ff0b556335a510111fa64c0f494cc86c7f8e52d455ceedb683263c57b00b669ee51029c8ce32e97ebabb7a67b
7
+ data.tar.gz: 50691e89980a9fb1af2b38ac5f9b1c1b50d31e0476816ea36cda7715cf9b797bb0230713f837356dd3f6414b0b948a9ac2a9be111cb8587f29b47cf7f17b6f77
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ <a name="v1.5.0"></a>
2
+ ### v1.5.0 (2018-03-23)
3
+
4
+ #### Features
5
+
6
+ * parse pacts without a specification version as v2 ([a69b5e6](/../../commit/a69b5e6))
7
+ * locate matching rules correctly for v3 pacts ([0f22db2](/../../commit/0f22db2))
8
+ * read matching rules from v3 format ([07013de](/../../commit/07013de))
9
+ * allow different consumer contract parsers to be registered ([531ab3a](/../../commit/531ab3a))
10
+ * update message classes to support pact-message ([2e48892](/../../commit/2e48892))
11
+ * add request and response to message ([93839cf](/../../commit/93839cf))
12
+
13
+ * **message contracts**
14
+ * dynamically mix in new and from_hash into Pact::Message ([c0c3ad5](/../../commit/c0c3ad5))
15
+ * read message pact into Ruby object ([6573bd4](/../../commit/6573bd4))
16
+
17
+
1
18
  <a name="v1.3.1"></a>
2
19
  ### v1.3.1 (2018-03-19)
3
20
 
@@ -10,9 +10,9 @@ require 'pact/consumer_contract/service_consumer'
10
10
  require 'pact/consumer_contract/service_provider'
11
11
  require 'pact/consumer_contract/interaction'
12
12
  require 'pact/consumer_contract/pact_file'
13
+ require 'pact/consumer_contract/http_consumer_contract_parser'
13
14
 
14
15
  module Pact
15
-
16
16
  class ConsumerContract
17
17
 
18
18
  include SymbolizeKeys
@@ -29,13 +29,19 @@ module Pact
29
29
  @provider = attributes[:provider]
30
30
  end
31
31
 
32
+ def self.add_parser consumer_contract_parser
33
+ parsers << consumer_contract_parser
34
+ end
35
+
36
+ def self.parsers
37
+ @parsers ||= [Pact::HttpConsumerContractParser.new]
38
+ end
39
+
32
40
  def self.from_hash(hash)
33
- hash = symbolize_keys(hash)
34
- new(
35
- :consumer => ServiceConsumer.from_hash(hash[:consumer]),
36
- :provider => ServiceProvider.from_hash(hash[:provider]),
37
- :interactions => hash[:interactions].collect { |h| Interaction.from_hash(h)}
38
- )
41
+ parsers.each do | parser |
42
+ return parser.call(hash) if parser.can_parse?(hash)
43
+ end
44
+ raise Pact::Error.new("No consumer contract parser found for hash: #{hash}")
39
45
  end
40
46
 
41
47
  def self.from_json string
@@ -54,9 +60,9 @@ module Pact
54
60
  def find_interaction criteria
55
61
  interactions = find_interactions criteria
56
62
  if interactions.size == 0
57
- raise "Could not find interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}."
63
+ raise Pact::Error.new("Could not find interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}.")
58
64
  elsif interactions.size > 1
59
- raise "Found more than 1 interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}."
65
+ raise Pact::Error.new("Found more than 1 interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}.")
60
66
  end
61
67
  interactions.first
62
68
  end
@@ -0,0 +1,37 @@
1
+ require 'pact/specification_version'
2
+
3
+ module Pact
4
+ class HttpConsumerContractParser
5
+ include SymbolizeKeys
6
+
7
+ def call(hash)
8
+ hash = symbolize_keys(hash)
9
+ v = pact_specification_version(hash)
10
+ options = { pact_specification_version: v }
11
+
12
+ if v.after? 3
13
+ Pact.configuration.error_stream.puts "WARN: This code only knows how to parse v3 pacts, attempting to parse v#{options[:pact_specification_version]} pact using v3 code."
14
+ end
15
+
16
+ interactions = hash[:interactions].collect { |hash| Interaction.from_hash(hash, options) }
17
+ ConsumerContract.new(
18
+ :consumer => ServiceConsumer.from_hash(hash[:consumer]),
19
+ :provider => ServiceProvider.from_hash(hash[:provider]),
20
+ :interactions => interactions
21
+ )
22
+ end
23
+
24
+ def pact_specification_version hash
25
+ # TODO handle all 3 ways of defining this...
26
+ # metadata.pactSpecificationVersion
27
+ maybe_pact_specification_version_1 = hash[:metadata] && hash[:metadata]['pactSpecification'] && hash[:metadata]['pactSpecification']['version']
28
+ maybe_pact_specification_version_2 = hash[:metadata] && hash[:metadata]['pactSpecificationVersion']
29
+ pact_specification_version = maybe_pact_specification_version_1 || maybe_pact_specification_version_2
30
+ pact_specification_version ? Pact::SpecificationVersion.new(pact_specification_version) : Pact::SpecificationVersion::NIL_VERSION
31
+ end
32
+
33
+ def can_parse?(hash)
34
+ hash.key?('interactions') || hash.key?(:interactions)
35
+ end
36
+ end
37
+ end
@@ -4,9 +4,10 @@ require 'pact/symbolize_keys'
4
4
  require 'pact/shared/active_support_support'
5
5
  require 'pact/matching_rules'
6
6
  require 'pact/errors'
7
+ require 'pact/specification_version'
7
8
 
8
9
  module Pact
9
- class Interaction
10
+ class Interaction
10
11
  include ActiveSupportSupport
11
12
  include SymbolizeKeys
12
13
 
@@ -19,10 +20,33 @@ module Pact
19
20
  @provider_state = attributes[:provider_state] || attributes[:providerState]
20
21
  end
21
22
 
22
- def self.from_hash hash
23
- request_hash = Pact::MatchingRules.merge(hash['request'], hash['request']['matchingRules'])
23
+ def self.from_hash hash, options = {}
24
+ pact_specification_version = options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION
25
+ case pact_specification_version.major
26
+ when nil, 0, 1, 2 then parse_v2_interaction(hash, pact_specification_version: pact_specification_version)
27
+ else parse_v3_interaction(hash, pact_specification_version: pact_specification_version)
28
+ end
29
+ end
30
+
31
+ def self.parse_v2_interaction hash, options
32
+ request_hash = Pact::MatchingRules.merge(hash['request'], hash['request']['matchingRules'], options)
33
+ request = Pact::Request::Expected.from_hash(request_hash)
34
+ response_hash = Pact::MatchingRules.merge(hash['response'], hash['response']['matchingRules'], options)
35
+ response = Pact::Response.from_hash(response_hash)
36
+ new(symbolize_keys(hash).merge(request: request, response: response))
37
+ end
38
+
39
+ def self.parse_v3_interaction hash, options
40
+
41
+ request_hash = hash['request'].keys.each_with_object({}) do | key, new_hash |
42
+ new_hash[key] = Pact::MatchingRules.merge(hash['request'][key], hash['request'].fetch('matchingRules', {})[key], options)
43
+ end
24
44
  request = Pact::Request::Expected.from_hash(request_hash)
25
- response_hash = Pact::MatchingRules.merge(hash['response'], hash['response']['matchingRules'])
45
+
46
+ response_hash = hash['response'].keys.each_with_object({}) do | key, new_hash |
47
+ new_hash[key] = Pact::MatchingRules.merge(hash['response'][key], hash['response'].fetch('matchingRules', {})[key], options)
48
+ end
49
+
26
50
  response = Pact::Response.from_hash(response_hash)
27
51
  new(symbolize_keys(hash).merge(request: request, response: response))
28
52
  end
@@ -36,6 +60,10 @@ module Pact
36
60
  }
37
61
  end
38
62
 
63
+ def http?
64
+ true
65
+ end
66
+
39
67
  def validate!
40
68
  raise Pact::InvalidInteractionError.new(self) unless description && request && response
41
69
  end
@@ -1,17 +1,22 @@
1
1
  require 'pact/matching_rules/extract'
2
2
  require 'pact/matching_rules/merge'
3
+ require 'pact/matching_rules/v3/merge'
3
4
 
4
5
  module Pact
5
6
  module MatchingRules
6
7
 
7
8
  # @api public Used by pact-mock_service
8
- def self.extract object_graph
9
+ def self.extract object_graph, options = {}
9
10
  Extract.(object_graph)
10
11
  end
11
12
 
12
- def self.merge object_graph, matching_rules
13
- Merge.(object_graph, matching_rules)
13
+ def self.merge object_graph, matching_rules, options = {}
14
+ case options[:pact_specification_version].major
15
+ when nil, 0, 1, 2
16
+ Merge.(object_graph, matching_rules)
17
+ else
18
+ V3::Merge.(object_graph, matching_rules)
19
+ end
14
20
  end
15
-
16
21
  end
17
- end
22
+ end
@@ -0,0 +1,112 @@
1
+ require 'pact/array_like'
2
+ require 'pact/matching_rules/jsonpath'
3
+
4
+ module Pact
5
+ module MatchingRules
6
+ module V3
7
+ class Merge
8
+
9
+ def self.call expected, matching_rules, root_path = '$'
10
+ new(expected, matching_rules, root_path).call
11
+ end
12
+
13
+ def initialize expected, matching_rules, root_path
14
+ @expected = expected
15
+ @matching_rules = standardise_paths(matching_rules)
16
+ @root_path = JsonPath.new(root_path).to_s
17
+ end
18
+
19
+ def call
20
+ return @expected if @matching_rules.nil? || @matching_rules.empty?
21
+ recurse @expected, @root_path
22
+ end
23
+
24
+ private
25
+
26
+ def standardise_paths matching_rules
27
+ return matching_rules if matching_rules.nil? || matching_rules.empty?
28
+ matching_rules.each_with_object({}) do | (path, rule), new_matching_rules |
29
+ new_matching_rules[JsonPath.new(path).to_s] = rule
30
+ end
31
+ end
32
+
33
+ def recurse expected, path
34
+ case expected
35
+ when Hash then recurse_hash(expected, path)
36
+ when Array then recurse_array(expected, path)
37
+ else
38
+ expected
39
+ end
40
+ end
41
+
42
+ def recurse_hash hash, path
43
+ hash.each_with_object({}) do | (k, v), new_hash |
44
+ new_path = path + "['#{k}']"
45
+ new_hash[k] = recurse(wrap(v, new_path), new_path)
46
+ end
47
+ end
48
+
49
+ def recurse_array array, path
50
+ array_like_children_path = "#{path}[*]*"
51
+ parent_match_rule = @matching_rules[path] && @matching_rules[path]['matchers'] && @matching_rules[path]['matchers'].first && @matching_rules[path]['matchers'].first['match']
52
+ children_match_rule = @matching_rules[array_like_children_path] && @matching_rules[array_like_children_path]['matchers'] && @matching_rules[array_like_children_path]['matchers'].first && @matching_rules[array_like_children_path]['matchers'].first['match']
53
+ min = @matching_rules[path] && @matching_rules[path]['matchers'] && @matching_rules[path]['matchers'].first && @matching_rules[path]['matchers'].first['min']
54
+
55
+ if min && (children_match_rule == 'type' || (children_match_rule.nil? && parent_match_rule == 'type'))
56
+ warn_when_not_one_example_item(array, path)
57
+ # log_ignored_rules(path, @matching_rules[path], {'min' => min})
58
+ Pact::ArrayLike.new(recurse(array.first, "#{path}[*]"), min: min)
59
+ else
60
+ new_array = []
61
+ array.each_with_index do | item, index |
62
+ new_path = path + "[#{index}]"
63
+ new_array << recurse(wrap(item, new_path), new_path)
64
+ end
65
+ new_array
66
+ end
67
+ end
68
+
69
+ def warn_when_not_one_example_item array, path
70
+ unless array.size == 1
71
+ Pact.configuration.error_stream.puts "WARN: Only the first item will be used to match the items in the array at #{path}"
72
+ end
73
+ end
74
+
75
+ def wrap object, path
76
+ rules = @matching_rules[path] && @matching_rules[path]['matchers'] && @matching_rules[path]['matchers'].first
77
+ array_rules = @matching_rules["#{path}[*]*"] && @matching_rules["#{path}[*]*"]['matchers'] && @matching_rules["#{path}[*]*"]['matchers'].first
78
+ return object unless rules || array_rules
79
+
80
+ if rules['match'] == 'type' && !rules.has_key?('min')
81
+ handle_match_type(object, path, rules)
82
+ elsif rules['regex']
83
+ handle_regex(object, path, rules)
84
+ else
85
+ log_ignored_rules(path, rules, {})
86
+ object
87
+ end
88
+ end
89
+
90
+ def handle_match_type object, path, rules
91
+ log_ignored_rules(path, rules, {'match' => 'type'})
92
+ Pact::SomethingLike.new(object)
93
+ end
94
+
95
+ def handle_regex object, path, rules
96
+ log_ignored_rules(path, rules, {'match' => 'regex', 'regex' => rules['regex']})
97
+ Pact::Term.new(generate: object, matcher: Regexp.new(rules['regex']))
98
+ end
99
+
100
+ def log_ignored_rules path, rules, used_rules
101
+ dup_rules = rules.dup
102
+ used_rules.each_pair do | used_key, used_value |
103
+ dup_rules.delete(used_key) if dup_rules[used_key] == used_value
104
+ end
105
+ if dup_rules.any?
106
+ $stderr.puts "WARN: Ignoring unsupported matching rules #{dup_rules} for path #{path}"
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,18 @@
1
+ module Pact
2
+ class SpecificationVersion < Gem::Version
3
+
4
+ NIL_VERSION = Pact::SpecificationVersion.new('')
5
+
6
+ def major
7
+ segments.first
8
+ end
9
+
10
+ def === other
11
+ major && major == other
12
+ end
13
+
14
+ def after? other
15
+ major && other < major
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  module Pact
2
2
  module Support
3
- VERSION = "1.4.0"
3
+ VERSION = "1.5.0"
4
4
  end
5
5
  end
@@ -0,0 +1,36 @@
1
+ {
2
+ "consumer": {
3
+ "name": "some-test-consumer"
4
+ },
5
+ "provider": {
6
+ "name": "an unknown provider"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a test request",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/weather",
14
+ "query": ""
15
+ },
16
+ "response": {
17
+ "matchingRules": {
18
+ "$.headers.Content-Type" : {
19
+ "match": "regex", "regex": "json"
20
+ },
21
+ "$.body.message" : {
22
+ "match": "regex", "regex": "sun"
23
+ }
24
+ },
25
+ "status": 200,
26
+ "headers" : {
27
+ "Content-Type": "foo/json"
28
+ },
29
+ "body": {
30
+ "message" : "sunful"
31
+ }
32
+ },
33
+ "provider_state": "the weather is sunny"
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "consumer": {
3
+ "name": "consumer"
4
+ },
5
+ "interactions": [
6
+ {
7
+ "description": "a test request",
8
+ "providerState": "the weather is sunny",
9
+ "request": {
10
+ "method": "get",
11
+ "path": "/foo"
12
+ },
13
+ "response": {
14
+ "body": {
15
+ "foo": "bar"
16
+ },
17
+ "matchingRules": {
18
+ "body": {
19
+ "$.foo": {
20
+ "matchers": [{ "match": "type" }]
21
+ }
22
+ }
23
+ },
24
+ "status": 200
25
+ }
26
+ }
27
+ ],
28
+ "provider": {
29
+ "name": "provider"
30
+ },
31
+ "metadata": {
32
+ "pactSpecification": {
33
+ "version": "3.0"
34
+ }
35
+ }
36
+ }
@@ -3,50 +3,53 @@ require 'pact/consumer_contract'
3
3
 
4
4
  module Pact
5
5
  describe ConsumerContract do
6
-
7
6
  describe ".from_json" do
7
+
8
8
  let(:loaded_pact) { ConsumerContract.from_json(string) }
9
- context "when the top level object is a ConsumerContract" do
10
- let(:string) { '{"interactions":[{"request": {"path":"/path", "method" : "get"}, "response": {"status" : 200}}], "consumer": {"name" : "Bob"} , "provider": {"name" : "Mary"} }' }
11
9
 
12
- it "should create a Pact" do
13
- expect(loaded_pact).to be_instance_of ConsumerContract
14
- end
10
+ context "with an HTTP contract" do
11
+ context "when the top level object is a ConsumerContract" do
12
+ let(:string) { '{"interactions":[{"request": {"path":"/path", "method" : "get"}, "response": {"status" : 200}}], "consumer": {"name" : "Bob"} , "provider": {"name" : "Mary"} }' }
15
13
 
16
- it "should have interactions" do
17
- expect(loaded_pact.interactions).to be_instance_of Array
18
- end
14
+ it "should create a Pact" do
15
+ expect(loaded_pact).to be_instance_of ConsumerContract
16
+ end
19
17
 
20
- it "should have a consumer" do
21
- expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
22
- end
18
+ it "should have interactions" do
19
+ expect(loaded_pact.interactions).to be_instance_of Array
20
+ end
23
21
 
24
- it "should have a provider" do
25
- expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
26
- end
27
- end
22
+ it "should have a consumer" do
23
+ expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
24
+ end
28
25
 
29
- context "with old 'producer' key" do
30
- let(:string) { File.read('./spec/support/a_consumer-a_producer.json')}
31
- it "should create a Pact" do
32
- expect(loaded_pact).to be_instance_of ConsumerContract
26
+ it "should have a provider" do
27
+ expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
28
+ end
33
29
  end
34
30
 
35
- it "should have interactions" do
36
- expect(loaded_pact.interactions).to be_instance_of Array
37
- end
31
+ context "with old 'producer' key" do
32
+ let(:string) { File.read('./spec/support/a_consumer-a_producer.json')}
33
+ it "should create a Pact" do
34
+ expect(loaded_pact).to be_instance_of ConsumerContract
35
+ end
38
36
 
39
- it "should have a consumer" do
40
- expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
41
- end
37
+ it "should have interactions" do
38
+ expect(loaded_pact.interactions).to be_instance_of Array
39
+ end
42
40
 
43
- it "should have a provider" do
44
- expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
45
- expect(loaded_pact.provider.name).to eq "an old producer"
46
- end
41
+ it "should have a consumer" do
42
+ expect(loaded_pact.consumer).to be_instance_of Pact::ServiceConsumer
43
+ end
47
44
 
48
- it "should have a provider_state" do
49
- expect(loaded_pact.interactions.first.provider_state).to eq 'state one'
45
+ it "should have a provider" do
46
+ expect(loaded_pact.provider).to be_instance_of Pact::ServiceProvider
47
+ expect(loaded_pact.provider.name).to eq "an old producer"
48
+ end
49
+
50
+ it "should have a provider_state" do
51
+ expect(loaded_pact.interactions.first.provider_state).to eq 'state one'
52
+ end
50
53
  end
51
54
  end
52
55
  end
@@ -0,0 +1,25 @@
1
+ require 'pact/consumer_contract/http_consumer_contract_parser'
2
+
3
+ module Pact
4
+ describe HttpConsumerContractParser do
5
+ describe "#call integration test" do
6
+ subject { HttpConsumerContractParser.new.call(pact_hash) }
7
+
8
+ context "with a v2 pact" do
9
+ let(:pact_hash) { load_json_fixture('pact-http-v2.json') }
10
+
11
+ it "correctly parses the pact" do
12
+ expect(subject.interactions.first.response.headers['Content-Type']).to be_a(Pact::Term)
13
+ end
14
+ end
15
+
16
+ context "with a v3 pact" do
17
+ let(:pact_hash) { load_json_fixture('pact-http-v3.json') }
18
+
19
+ it "correctly parses the pact" do
20
+ expect(subject.interactions.first.response.body['foo']).to be_a(Pact::SomethingLike)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -65,7 +65,7 @@ module Pact
65
65
  context "when there are matching rules" do
66
66
  let(:hash) { load_json_fixture 'interaction-with-matching-rules.json' }
67
67
 
68
- subject { Interaction.from_hash hash }
68
+ subject { Interaction.from_hash hash, pact_specification_version: Pact::SpecificationVersion.new("2") }
69
69
 
70
70
  it "merges the rules with the example for the request" do
71
71
  expect(subject.request.body['name']).to be_instance_of(Pact::Term)
@@ -0,0 +1,364 @@
1
+ require 'pact/matching_rules/v3/merge'
2
+
3
+ module Pact
4
+ module MatchingRules
5
+ module V3
6
+ describe Merge do
7
+ subject { Merge.(expected, matching_rules) }
8
+
9
+ before do
10
+ allow($stderr).to receive(:puts)
11
+ end
12
+
13
+ describe "no recognised rules" do
14
+ let(:expected) do
15
+ {
16
+ "_links" => {
17
+ "self" => {
18
+ "href" => "http://localhost:1234/thing"
19
+ }
20
+ }
21
+ }
22
+ end
23
+
24
+ let(:matching_rules) do
25
+ {
26
+ "$._links.self.href" => {
27
+ "matchers" => [{ "type" => "unknown" }]
28
+ }
29
+ }
30
+ end
31
+
32
+ it "returns the object at that path unaltered" do
33
+ expect(subject["_links"]["self"]["href"]).to eq "http://localhost:1234/thing"
34
+ end
35
+
36
+ it "it logs the rules it has ignored" do
37
+ expect($stderr).to receive(:puts) do | message |
38
+ expect(message).to include("WARN")
39
+ expect(message).to include("type")
40
+ expect(message).to include("unknown")
41
+ expect(message).to include("$['_links']")
42
+ end
43
+ subject
44
+ end
45
+
46
+ end
47
+
48
+ describe "with nil rules" do
49
+ let(:expected) do
50
+ {
51
+ "_links" => {
52
+ "self" => {
53
+ "href" => "http://localhost:1234/thing"
54
+ }
55
+ }
56
+ }
57
+ end
58
+
59
+ let(:matching_rules) { nil }
60
+
61
+ it "returns the example unaltered" do
62
+ expect(subject["_links"]["self"]["href"]).to eq "http://localhost:1234/thing"
63
+ end
64
+
65
+ end
66
+
67
+ describe "type based matching" do
68
+ let(:expected) do
69
+ {
70
+ "name" => "Mary"
71
+ }
72
+ end
73
+
74
+ let(:matching_rules) do
75
+ {
76
+ "$.name" => {
77
+ "matchers" => [{ "match" => "type", "ignored" => "matchingrule" }]
78
+ }
79
+ }
80
+ end
81
+
82
+ it "creates a SomethingLike at the appropriate path" do
83
+ expect(subject['name']).to be_instance_of(Pact::SomethingLike)
84
+ end
85
+
86
+ it "it logs the rules it has ignored" do
87
+ expect($stderr).to receive(:puts).with(/ignored.*matchingrule/)
88
+ subject
89
+ end
90
+
91
+ end
92
+
93
+ describe "regular expressions" do
94
+
95
+ describe "in a hash" do
96
+ let(:expected) do
97
+ {
98
+ "_links" => {
99
+ "self" => {
100
+ "href" => "http://localhost:1234/thing"
101
+ }
102
+ }
103
+ }
104
+ end
105
+
106
+ let(:matching_rules) do
107
+ {
108
+ "$._links.self.href" => {
109
+ "matchers" => [{ "regex" => "http:\\/\\/.*\\/thing", "match" => "regex", "ignored" => "somerule" }]
110
+ }
111
+ }
112
+ end
113
+
114
+ it "creates a Pact::Term at the appropriate path" do
115
+ expect(subject["_links"]["self"]["href"]).to be_instance_of(Pact::Term)
116
+ expect(subject["_links"]["self"]["href"].generate).to eq "http://localhost:1234/thing"
117
+ expect(subject["_links"]["self"]["href"].matcher.inspect).to eq "/http:\\/\\/.*\\/thing/"
118
+ end
119
+
120
+ it "it logs the rules it has ignored" do
121
+ expect($stderr).to receive(:puts) do | message |
122
+ expect(message).to match /ignored.*"somerule"/
123
+ expect(message).to_not match /regex/
124
+ expect(message).to_not match /"match"/
125
+ end
126
+ subject
127
+ end
128
+ end
129
+
130
+ describe "with an array" do
131
+
132
+ let(:expected) do
133
+ {
134
+ "_links" => {
135
+ "self" => [{
136
+ "href" => "http://localhost:1234/thing"
137
+ }]
138
+ }
139
+ }
140
+ end
141
+
142
+ let(:matching_rules) do
143
+ {
144
+ "$._links.self[0].href" => {
145
+ "matchers" => [{ "regex" => "http:\\/\\/.*\\/thing" }]
146
+ }
147
+ }
148
+ end
149
+
150
+ it "creates a Pact::Term at the appropriate path" do
151
+ expect(subject["_links"]["self"][0]["href"]).to be_instance_of(Pact::Term)
152
+ expect(subject["_links"]["self"][0]["href"].generate).to eq "http://localhost:1234/thing"
153
+ expect(subject["_links"]["self"][0]["href"].matcher.inspect).to eq "/http:\\/\\/.*\\/thing/"
154
+ end
155
+ end
156
+
157
+ describe "with an array where all elements should match by type and the rule is specified on the parent element and there is no min specified" do
158
+ let(:expected) do
159
+ {
160
+ 'alligators' => [{'name' => 'Mary'}]
161
+ }
162
+ end
163
+
164
+ let(:matching_rules) do
165
+ {
166
+ "$.alligators" => {
167
+ "matchers" => [{ 'match' => 'type' }]
168
+ }
169
+ }
170
+ end
171
+
172
+ it "creates a Pact::SomethingLike at the appropriate path" do
173
+ expect(subject["alligators"]).to be_instance_of(Pact::SomethingLike)
174
+ expect(subject["alligators"].contents).to eq ['name' => 'Mary']
175
+ end
176
+ end
177
+
178
+ describe "with an array where all elements should match by type and the rule is specified on the child elements" do
179
+ let(:expected) do
180
+ {
181
+ 'alligators' => [{'name' => 'Mary'}]
182
+ }
183
+ end
184
+
185
+ let(:matching_rules) do
186
+ {
187
+ "$.alligators" => {
188
+ "matchers" => [{ 'min' => 2, 'match' => 'type' }]
189
+ },
190
+ "$.alligators[*].*" => {
191
+ "matchers" => [{ 'match' => 'type'}]
192
+ }
193
+ }
194
+ end
195
+ it "creates a Pact::ArrayLike at the appropriate path" do
196
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
197
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
198
+ expect(subject["alligators"].min).to eq 2
199
+ end
200
+ end
201
+
202
+ describe "with an array where all elements should match by type and the rule is specified on both the parent element and the child elements" do
203
+ let(:expected) do
204
+ {
205
+ 'alligators' => [{'name' => 'Mary'}]
206
+ }
207
+ end
208
+
209
+ let(:matching_rules) do
210
+ {
211
+ "$.alligators" => {
212
+ "matchers" => [{ 'min' => 2, 'match' => 'type' }]
213
+ },
214
+ "$.alligators[*].*" => {
215
+ "matchers" => [{ 'match' => 'type' }]
216
+ }
217
+ }
218
+ end
219
+
220
+ it "creates a Pact::ArrayLike at the appropriate path" do
221
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
222
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
223
+ expect(subject["alligators"].min).to eq 2
224
+ end
225
+ end
226
+
227
+ describe "with an array where all elements should match by type and there is only a match:type on the parent element" do
228
+ let(:expected) do
229
+ {
230
+ 'alligators' => [{'name' => 'Mary'}]
231
+ }
232
+ end
233
+
234
+ let(:matching_rules) do
235
+ {
236
+ "$.alligators" => { 'matchers' => [{'min' => 2, 'match' => 'type'}] },
237
+ }
238
+ end
239
+
240
+ it "creates a Pact::ArrayLike at the appropriate path" do
241
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
242
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
243
+ expect(subject["alligators"].min).to eq 2
244
+ end
245
+ end
246
+
247
+ describe "with an array where all elements should match by type nested inside another array where all elements should match by type" do
248
+ let(:expected) do
249
+ {
250
+
251
+ 'alligators' => [
252
+ {
253
+ 'name' => 'Mary',
254
+ 'children' => [
255
+ 'age' => 9
256
+ ]
257
+ }
258
+ ]
259
+
260
+ }
261
+ end
262
+
263
+ let(:matching_rules) do
264
+ {
265
+ "$.alligators" => { "matchers" => [{ 'min' => 2, 'match' => 'type' }] },
266
+ "$.alligators[*].children" => { "matchers" => [{ 'min' => 1, 'match' => 'type' }]},
267
+ }
268
+ end
269
+
270
+ it "creates a Pact::ArrayLike at the appropriate path" do
271
+ expect(subject["alligators"].contents['children']).to be_instance_of(Pact::ArrayLike)
272
+ expect(subject["alligators"].contents['children'].contents).to eq 'age' => 9
273
+ expect(subject["alligators"].contents['children'].min).to eq 1
274
+ end
275
+ end
276
+
277
+ describe "with an example array with more than one item" do
278
+ let(:expected) do
279
+ {
280
+
281
+ 'alligators' => [
282
+ {'name' => 'Mary'},
283
+ {'name' => 'Joe'}
284
+ ]
285
+
286
+ }
287
+ end
288
+
289
+ let(:matching_rules) do
290
+ {
291
+ "$.alligators" => { "matchers" => [{'min' => 2, 'match' => 'type'}] }
292
+ }
293
+ end
294
+
295
+ xit "doesn't warn about the min size being ignored" do
296
+ expect(Pact.configuration.error_stream).to receive(:puts).once
297
+ subject
298
+ end
299
+
300
+ it "warns that the other items will be ignored" do
301
+ allow(Pact.configuration.error_stream).to receive(:puts)
302
+ expect(Pact.configuration.error_stream).to receive(:puts).with(/WARN: Only the first item/)
303
+ subject
304
+ end
305
+ end
306
+ end
307
+
308
+ describe "using bracket notation for a Hash" do
309
+ let(:expected) do
310
+ {
311
+ "name" => "Mary"
312
+ }
313
+ end
314
+
315
+ let(:matching_rules) do
316
+ {
317
+ "$['name']" => { "matchers" => [{"match" => "type"}] }
318
+ }
319
+ end
320
+
321
+ it "applies the rule" do
322
+ expect(subject['name']).to be_instance_of(Pact::SomethingLike)
323
+ end
324
+ end
325
+
326
+ describe "with a dot in the path" do
327
+ let(:expected) do
328
+ {
329
+ "first.name" => "Mary"
330
+ }
331
+ end
332
+
333
+ let(:matching_rules) do
334
+ {
335
+ "$['first.name']" => { "matchers" => [{ "match" => "type" }] }
336
+ }
337
+ end
338
+
339
+ it "applies the rule" do
340
+ expect(subject['first.name']).to be_instance_of(Pact::SomethingLike)
341
+ end
342
+ end
343
+
344
+ describe "with an @ in the path" do
345
+ let(:expected) do
346
+ {
347
+ "@name" => "Mary"
348
+ }
349
+ end
350
+
351
+ let(:matching_rules) do
352
+ {
353
+ "$['@name']" => { "matchers" => [ { "match" => "type" }] }
354
+ }
355
+ end
356
+
357
+ it "applies the rule" do
358
+ expect(subject['@name']).to be_instance_of(Pact::SomethingLike)
359
+ end
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end
@@ -0,0 +1,82 @@
1
+ require 'pact/matching_rules'
2
+
3
+ module Pact
4
+ module MatchingRules
5
+ describe ".merge" do
6
+ before do
7
+ allow(V3::Merge).to receive(:call)
8
+ allow(Merge).to receive(:call)
9
+ allow(Pact.configuration.error_stream).to receive(:puts)
10
+ end
11
+
12
+ let(:object) { double('object') }
13
+ let(:rules) { double('rules') }
14
+ let(:options) { { pact_specification_version: Pact::SpecificationVersion.new(pact_specification_version) } }
15
+
16
+ subject { MatchingRules.merge(object, rules, options)}
17
+
18
+ context "when the pact_specification_version is nil" do
19
+ let(:pact_specification_version) { nil }
20
+
21
+ it "calls Merge" do
22
+ expect(Merge).to receive(:call)
23
+ subject
24
+ end
25
+ end
26
+
27
+ context "when the pact_specification_version starts with '1.'" do
28
+ let(:pact_specification_version) { "1.0" }
29
+
30
+ it "calls Merge" do
31
+ expect(Merge).to receive(:call)
32
+ subject
33
+ end
34
+ end
35
+
36
+ context "when the pact_specification_version is with '1'" do
37
+ let(:pact_specification_version) { "1" }
38
+
39
+ it "calls Merge" do
40
+ expect(Merge).to receive(:call)
41
+ subject
42
+ end
43
+ end
44
+
45
+ context "when the pact_specification_version starts with '2.'" do
46
+ let(:pact_specification_version) { "2.0" }
47
+
48
+ it "calls Merge" do
49
+ expect(Merge).to receive(:call)
50
+ subject
51
+ end
52
+ end
53
+
54
+ context "when the pact_specification_version starts with '3.'" do
55
+ let(:pact_specification_version) { "3.0" }
56
+
57
+ it "calls V3::Merge" do
58
+ expect(V3::Merge).to receive(:call)
59
+ subject
60
+ end
61
+ end
62
+
63
+ context "when the pact_specification_version starts with '4.'" do
64
+ let(:pact_specification_version) { "4.0" }
65
+
66
+ it "calls V3::Merge" do
67
+ expect(V3::Merge).to receive(:call)
68
+ subject
69
+ end
70
+ end
71
+
72
+ context "when the pact_specification_version is with '11'" do
73
+ let(:pact_specification_version) { "11" }
74
+
75
+ it "calls V3::Merge" do
76
+ expect(V3::Merge).to receive(:call)
77
+ subject
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
data/tasks/spec.rake CHANGED
@@ -12,4 +12,3 @@ task :spec_with_active_support => [:set_active_support_on] do
12
12
  end
13
13
 
14
14
  task :default => [:spec, :spec_with_active_support]
15
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact-support
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Fraser
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2018-03-21 00:00:00.000000000 Z
15
+ date: 2018-03-23 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: randexp
@@ -268,6 +268,7 @@ files:
268
268
  - lib/pact/consumer_contract/consumer_contract.rb
269
269
  - lib/pact/consumer_contract/file_name.rb
270
270
  - lib/pact/consumer_contract/headers.rb
271
+ - lib/pact/consumer_contract/http_consumer_contract_parser.rb
271
272
  - lib/pact/consumer_contract/interaction.rb
272
273
  - lib/pact/consumer_contract/pact_file.rb
273
274
  - lib/pact/consumer_contract/query.rb
@@ -302,6 +303,7 @@ files:
302
303
  - lib/pact/matching_rules/extract.rb
303
304
  - lib/pact/matching_rules/jsonpath.rb
304
305
  - lib/pact/matching_rules/merge.rb
306
+ - lib/pact/matching_rules/v3/merge.rb
305
307
  - lib/pact/reification.rb
306
308
  - lib/pact/rspec.rb
307
309
  - lib/pact/shared/active_support_support.rb
@@ -314,6 +316,7 @@ files:
314
316
  - lib/pact/shared/request.rb
315
317
  - lib/pact/shared/text_differ.rb
316
318
  - lib/pact/something_like.rb
319
+ - lib/pact/specification_version.rb
317
320
  - lib/pact/support.rb
318
321
  - lib/pact/support/version.rb
319
322
  - lib/pact/symbolize_keys.rb
@@ -323,6 +326,8 @@ files:
323
326
  - script/release.sh
324
327
  - script/update-pact-specification-v2
325
328
  - spec/fixtures/interaction-with-matching-rules.json
329
+ - spec/fixtures/pact-http-v2.json
330
+ - spec/fixtures/pact-http-v3.json
326
331
  - spec/integration/matching_rules_extract_and_merge_spec.rb
327
332
  - spec/lib/pact/array_like_spec.rb
328
333
  - spec/lib/pact/configuration_spec.rb
@@ -331,6 +336,7 @@ files:
331
336
  - spec/lib/pact/consumer_contract/consumer_contract_spec.rb
332
337
  - spec/lib/pact/consumer_contract/file_name_spec.rb
333
338
  - spec/lib/pact/consumer_contract/headers_spec.rb
339
+ - spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb
334
340
  - spec/lib/pact/consumer_contract/interaction_spec.rb
335
341
  - spec/lib/pact/consumer_contract/pact_file_spec.rb
336
342
  - spec/lib/pact/consumer_contract/query_hash_spec.rb
@@ -357,6 +363,8 @@ files:
357
363
  - spec/lib/pact/matchers/unix_diff_formatter_spec.rb
358
364
  - spec/lib/pact/matching_rules/extract_spec.rb
359
365
  - spec/lib/pact/matching_rules/merge_spec.rb
366
+ - spec/lib/pact/matching_rules/v3/merge_spec.rb
367
+ - spec/lib/pact/matching_rules_spec.rb
360
368
  - spec/lib/pact/reification_spec.rb
361
369
  - spec/lib/pact/shared/dsl_spec.rb
362
370
  - spec/lib/pact/shared/form_differ_spec.rb
@@ -420,6 +428,8 @@ specification_version: 4
420
428
  summary: Shared code for Pact gems
421
429
  test_files:
422
430
  - spec/fixtures/interaction-with-matching-rules.json
431
+ - spec/fixtures/pact-http-v2.json
432
+ - spec/fixtures/pact-http-v3.json
423
433
  - spec/integration/matching_rules_extract_and_merge_spec.rb
424
434
  - spec/lib/pact/array_like_spec.rb
425
435
  - spec/lib/pact/configuration_spec.rb
@@ -428,6 +438,7 @@ test_files:
428
438
  - spec/lib/pact/consumer_contract/consumer_contract_spec.rb
429
439
  - spec/lib/pact/consumer_contract/file_name_spec.rb
430
440
  - spec/lib/pact/consumer_contract/headers_spec.rb
441
+ - spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb
431
442
  - spec/lib/pact/consumer_contract/interaction_spec.rb
432
443
  - spec/lib/pact/consumer_contract/pact_file_spec.rb
433
444
  - spec/lib/pact/consumer_contract/query_hash_spec.rb
@@ -454,6 +465,8 @@ test_files:
454
465
  - spec/lib/pact/matchers/unix_diff_formatter_spec.rb
455
466
  - spec/lib/pact/matching_rules/extract_spec.rb
456
467
  - spec/lib/pact/matching_rules/merge_spec.rb
468
+ - spec/lib/pact/matching_rules/v3/merge_spec.rb
469
+ - spec/lib/pact/matching_rules_spec.rb
457
470
  - spec/lib/pact/reification_spec.rb
458
471
  - spec/lib/pact/shared/dsl_spec.rb
459
472
  - spec/lib/pact/shared/form_differ_spec.rb