pact-support 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
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