pact-support 1.4.0 → 1.8.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 +4 -4
- data/CHANGELOG.md +143 -0
- data/lib/pact/configuration.rb +4 -0
- data/lib/pact/consumer_contract/consumer_contract.rb +19 -8
- data/lib/pact/consumer_contract/http_consumer_contract_parser.rb +37 -0
- data/lib/pact/consumer_contract/interaction.rb +57 -56
- data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
- data/lib/pact/consumer_contract/interaction_v2_parser.rb +34 -0
- data/lib/pact/consumer_contract/interaction_v3_parser.rb +73 -0
- data/lib/pact/consumer_contract/pact_file.rb +24 -24
- data/lib/pact/consumer_contract/provider_state.rb +34 -0
- data/lib/pact/consumer_contract/query.rb +0 -2
- data/lib/pact/consumer_contract/query_hash.rb +6 -0
- data/lib/pact/consumer_contract/query_string.rb +2 -2
- data/lib/pact/consumer_contract/request.rb +1 -5
- data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
- data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
- data/lib/pact/matching_rules/merge.rb +43 -28
- data/lib/pact/matching_rules/v3/extract.rb +94 -0
- data/lib/pact/matching_rules/v3/merge.rb +135 -0
- data/lib/pact/matching_rules.rb +19 -6
- data/lib/pact/reification.rb +6 -3
- data/lib/pact/shared/multipart_form_differ.rb +14 -0
- data/lib/pact/specification_version.rb +18 -0
- data/lib/pact/support/version.rb +1 -1
- data/script/release.sh +1 -1
- data/spec/fixtures/multipart-form-diff.txt +9 -0
- data/spec/fixtures/not-a-pact.json +3 -0
- data/spec/fixtures/pact-http-v2.json +36 -0
- data/spec/fixtures/pact-http-v3.json +36 -0
- data/spec/integration/matching_rules_extract_and_merge_spec.rb +41 -4
- data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +54 -31
- data/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb +25 -0
- data/spec/lib/pact/consumer_contract/interaction_parser_spec.rb +62 -0
- data/spec/lib/pact/consumer_contract/interaction_spec.rb +2 -26
- data/spec/lib/pact/consumer_contract/interaction_v2_parser_spec.rb +54 -0
- data/spec/lib/pact/consumer_contract/interaction_v3_parser_spec.rb +48 -0
- data/spec/lib/pact/consumer_contract/pact_file_spec.rb +9 -0
- data/spec/lib/pact/consumer_contract/query_hash_spec.rb +23 -0
- data/spec/lib/pact/matchers/multipart_form_diff_formatter_spec.rb +36 -0
- data/spec/lib/pact/matching_rules/merge_spec.rb +198 -110
- data/spec/lib/pact/matching_rules/v3/extract_spec.rb +238 -0
- data/spec/lib/pact/matching_rules/v3/merge_spec.rb +462 -0
- data/spec/lib/pact/matching_rules_spec.rb +82 -0
- data/spec/lib/pact/reification_spec.rb +18 -5
- data/spec/lib/pact/shared/multipart_form_differ_spec.rb +39 -0
- data/spec/support/factories.rb +5 -0
- data/tasks/spec.rake +0 -1
- metadata +40 -3
|
@@ -47,6 +47,10 @@ module Pact
|
|
|
47
47
|
@hash && @hash.empty?
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def to_hash
|
|
51
|
+
@hash
|
|
52
|
+
end
|
|
53
|
+
|
|
50
54
|
private
|
|
51
55
|
|
|
52
56
|
def convert_to_hash_of_arrays(query)
|
|
@@ -56,6 +60,8 @@ module Pact
|
|
|
56
60
|
def insert(hash, k, v)
|
|
57
61
|
if Hash === v
|
|
58
62
|
v.each {|k2, v2| insert(hash, :"#{k}[#{k2}]", v2) }
|
|
63
|
+
elsif Pact::ArrayLike === v
|
|
64
|
+
hash[k.to_sym] = v
|
|
59
65
|
else
|
|
60
66
|
hash[k.to_sym] = [*v]
|
|
61
67
|
end
|
|
@@ -4,7 +4,6 @@ module Pact
|
|
|
4
4
|
class QueryString
|
|
5
5
|
|
|
6
6
|
include ActiveSupportSupport
|
|
7
|
-
include Pact::Matchers
|
|
8
7
|
|
|
9
8
|
def initialize query
|
|
10
9
|
@query = query.nil? ? query : query.dup
|
|
@@ -27,7 +26,8 @@ module Pact
|
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def difference(other)
|
|
30
|
-
|
|
29
|
+
require 'pact/matchers' # avoid recursive loop between this file and pact/matchers
|
|
30
|
+
Pact::Matchers.diff(query, other.query)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def query
|
|
@@ -2,9 +2,7 @@ require 'pact/shared/request'
|
|
|
2
2
|
require 'pact/shared/null_expectation'
|
|
3
3
|
|
|
4
4
|
module Pact
|
|
5
|
-
|
|
6
5
|
module Request
|
|
7
|
-
|
|
8
6
|
class Expected < Pact::Request::Base
|
|
9
7
|
|
|
10
8
|
DEFAULT_OPTIONS = {:allow_unexpected_keys => false}.freeze
|
|
@@ -80,8 +78,6 @@ module Pact
|
|
|
80
78
|
def body_differ
|
|
81
79
|
Pact.configuration.body_differ_for_content_type content_type
|
|
82
80
|
end
|
|
83
|
-
|
|
84
81
|
end
|
|
85
|
-
|
|
86
82
|
end
|
|
87
|
-
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Pact
|
|
2
|
+
class StringWithMatchingRules < String
|
|
3
|
+
attr_reader :matching_rules
|
|
4
|
+
attr_reader :pact_specification_version
|
|
5
|
+
|
|
6
|
+
def initialize string, pact_specification_version, matching_rules = {}
|
|
7
|
+
super(string)
|
|
8
|
+
@matching_rules = matching_rules
|
|
9
|
+
@pact_specification_version = pact_specification_version
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# How can we show the matching rules too?
|
|
13
|
+
def to_s
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'pact/matchers/unix_diff_formatter'
|
|
2
|
+
require 'pact/matchers/differ'
|
|
3
|
+
|
|
4
|
+
module Pact
|
|
5
|
+
module Matchers
|
|
6
|
+
class MultipartFormDiffFormatter
|
|
7
|
+
|
|
8
|
+
def initialize diff, options = {}
|
|
9
|
+
@options = options
|
|
10
|
+
@body_diff = diff[:body]
|
|
11
|
+
@non_body_diff = diff.reject{ |k, v| k == :body }
|
|
12
|
+
@colour = options.fetch(:colour, false)
|
|
13
|
+
@differ = Pact::Matchers::Differ.new(@colour)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.call diff, options = {}
|
|
17
|
+
new(diff, options).call
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
Pact::Matchers::UnixDiffFormatter::MESSAGES_TITLE + "\n" + non_body_diff_string + "\n" + body_diff_string
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def non_body_diff_string
|
|
25
|
+
if @non_body_diff.any?
|
|
26
|
+
Pact::Matchers::ExtractDiffMessages.call(@non_body_diff).collect{ | message| "* #{message}" }.join("\n")
|
|
27
|
+
else
|
|
28
|
+
""
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def body_diff_string
|
|
33
|
+
if @body_diff
|
|
34
|
+
@differ.diff_as_string(@body_diff.expected, @body_diff.actual)
|
|
35
|
+
else
|
|
36
|
+
""
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -13,11 +13,12 @@ module Pact
|
|
|
13
13
|
@expected = expected
|
|
14
14
|
@matching_rules = standardise_paths(matching_rules)
|
|
15
15
|
@root_path = JsonPath.new(root_path).to_s
|
|
16
|
+
@used_rules = []
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def call
|
|
19
20
|
return @expected if @matching_rules.nil? || @matching_rules.empty?
|
|
20
|
-
recurse
|
|
21
|
+
recurse(@expected, @root_path).tap { log_ignored_rules }
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
private
|
|
@@ -30,36 +31,42 @@ module Pact
|
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
def recurse expected, path
|
|
33
|
-
case expected
|
|
34
|
+
recursed = case expected
|
|
34
35
|
when Hash then recurse_hash(expected, path)
|
|
35
36
|
when Array then recurse_array(expected, path)
|
|
36
37
|
else
|
|
37
38
|
expected
|
|
38
39
|
end
|
|
40
|
+
|
|
41
|
+
wrap(recursed, path)
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
def recurse_hash hash, path
|
|
42
|
-
hash.each_with_object({}) do | (k, v), new_hash |
|
|
45
|
+
recursed = hash.each_with_object({}) do | (k, v), new_hash |
|
|
43
46
|
new_path = path + "['#{k}']"
|
|
44
|
-
new_hash[k] = recurse(
|
|
47
|
+
new_hash[k] = recurse(v, new_path)
|
|
45
48
|
end
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
def recurse_array array, path
|
|
52
|
+
parent_match_rule = find_rule(path, 'match')
|
|
53
|
+
log_used_rule(path, 'match', parent_match_rule) if parent_match_rule
|
|
54
|
+
|
|
49
55
|
array_like_children_path = "#{path}[*]*"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
children_match_rule = find_rule(array_like_children_path, 'match')
|
|
57
|
+
log_used_rule(array_like_children_path, 'match', children_match_rule) if children_match_rule
|
|
58
|
+
|
|
59
|
+
min = find_rule(path, 'min')
|
|
60
|
+
log_used_rule(path, 'min', min) if min
|
|
53
61
|
|
|
54
62
|
if min && (children_match_rule == 'type' || (children_match_rule.nil? && parent_match_rule == 'type'))
|
|
55
63
|
warn_when_not_one_example_item(array, path)
|
|
56
|
-
# log_ignored_rules(path, @matching_rules[path], {'min' => min})
|
|
57
64
|
Pact::ArrayLike.new(recurse(array.first, "#{path}[*]"), min: min)
|
|
58
65
|
else
|
|
59
66
|
new_array = []
|
|
60
67
|
array.each_with_index do | item, index |
|
|
61
68
|
new_path = path + "[#{index}]"
|
|
62
|
-
new_array << recurse(
|
|
69
|
+
new_array << recurse(item, new_path)
|
|
63
70
|
end
|
|
64
71
|
new_array
|
|
65
72
|
end
|
|
@@ -72,39 +79,47 @@ module Pact
|
|
|
72
79
|
end
|
|
73
80
|
|
|
74
81
|
def wrap object, path
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if rules['match'] == 'type' && !rules.has_key?('min')
|
|
80
|
-
handle_match_type(object, path, rules)
|
|
81
|
-
elsif rules['regex']
|
|
82
|
-
handle_regex(object, path, rules)
|
|
82
|
+
if find_rule(path, 'match') == 'type' && !find_rule(path, 'min')
|
|
83
|
+
handle_match_type(object, path)
|
|
84
|
+
elsif find_rule(path, 'regex')
|
|
85
|
+
handle_regex(object, path)
|
|
83
86
|
else
|
|
84
|
-
log_ignored_rules(path, rules, {})
|
|
85
87
|
object
|
|
86
88
|
end
|
|
87
89
|
end
|
|
88
90
|
|
|
89
|
-
def handle_match_type object, path
|
|
90
|
-
|
|
91
|
+
def handle_match_type object, path
|
|
92
|
+
log_used_rule(path, 'match', 'type')
|
|
91
93
|
Pact::SomethingLike.new(object)
|
|
92
94
|
end
|
|
93
95
|
|
|
94
|
-
def handle_regex object, path
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
def handle_regex object, path
|
|
97
|
+
regex = find_rule(path, 'regex')
|
|
98
|
+
log_used_rule(path, 'match', 'regex') # assumed to be present
|
|
99
|
+
log_used_rule(path, 'regex', regex)
|
|
100
|
+
Pact::Term.new(generate: object, matcher: Regexp.new(regex))
|
|
97
101
|
end
|
|
98
102
|
|
|
99
|
-
def log_ignored_rules
|
|
100
|
-
dup_rules =
|
|
101
|
-
used_rules.
|
|
102
|
-
dup_rules.delete(
|
|
103
|
+
def log_ignored_rules
|
|
104
|
+
dup_rules = @matching_rules.dup
|
|
105
|
+
@used_rules.each do | (path, key, value) |
|
|
106
|
+
dup_rules[path].delete(key) if dup_rules[path][key] == value
|
|
103
107
|
end
|
|
108
|
+
|
|
104
109
|
if dup_rules.any?
|
|
105
|
-
|
|
110
|
+
dup_rules.each do | path, rules |
|
|
111
|
+
$stderr.puts "WARN: Ignoring unsupported matching rules #{rules} for path #{path}" if rules.any?
|
|
112
|
+
end
|
|
106
113
|
end
|
|
107
114
|
end
|
|
115
|
+
|
|
116
|
+
def find_rule(path, key)
|
|
117
|
+
@matching_rules[path] && @matching_rules[path][key]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def log_used_rule path, key, value
|
|
121
|
+
@used_rules << [path, key, value]
|
|
122
|
+
end
|
|
108
123
|
end
|
|
109
124
|
end
|
|
110
125
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require 'pact/something_like'
|
|
2
|
+
require 'pact/array_like'
|
|
3
|
+
require 'pact/term'
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module MatchingRules::V3
|
|
7
|
+
class Extract
|
|
8
|
+
|
|
9
|
+
def self.call matchable
|
|
10
|
+
new(matchable).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize matchable
|
|
14
|
+
@matchable = matchable
|
|
15
|
+
@rules = Hash.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
recurse matchable, "$", nil
|
|
20
|
+
rules
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
attr_reader :matchable, :rules
|
|
26
|
+
|
|
27
|
+
def recurse object, path, match_type
|
|
28
|
+
case object
|
|
29
|
+
when Hash then recurse_hash(object, path, match_type)
|
|
30
|
+
when Array then recurse_array(object, path, match_type)
|
|
31
|
+
when Pact::SomethingLike then handle_something_like(object, path, match_type)
|
|
32
|
+
when Pact::ArrayLike then handle_array_like(object, path, match_type)
|
|
33
|
+
when Pact::Term then record_regex_rule object, path
|
|
34
|
+
when Pact::QueryString then recurse(object.query, path, match_type)
|
|
35
|
+
when Pact::QueryHash then recurse_hash(object.query, path, match_type)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def recurse_hash hash, path, match_type
|
|
40
|
+
hash.each do | (key, value) |
|
|
41
|
+
recurse value, "#{path}#{next_path_part(key)}", match_type
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def recurse_array new_array, path, match_type
|
|
46
|
+
new_array.each_with_index do | value, index |
|
|
47
|
+
recurse value, "#{path}[#{index}]", match_type
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def handle_something_like something_like, path, match_type
|
|
52
|
+
record_match_type_rule path, "type"
|
|
53
|
+
recurse something_like.contents, path, "type"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def handle_array_like array_like, path, match_type
|
|
57
|
+
record_rule "#{path}", 'min' => array_like.min
|
|
58
|
+
record_match_type_rule "#{path}[*].*", 'type'
|
|
59
|
+
recurse array_like.contents, "#{path}[*]", :array_like
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def record_rule path, rule
|
|
63
|
+
rules[path] ||= {}
|
|
64
|
+
rules[path]['matchers'] ||= []
|
|
65
|
+
rules[path]['matchers'] << rule
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def record_regex_rule term, path
|
|
69
|
+
rules[path] ||= {}
|
|
70
|
+
rules[path]['matchers'] ||= []
|
|
71
|
+
rule = { 'match' => 'regex', 'regex' => term.matcher.inspect[1..-2]}
|
|
72
|
+
rules[path]['matchers'] << rule
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def record_match_type_rule path, match_type
|
|
76
|
+
unless match_type == :array_like || match_type.nil?
|
|
77
|
+
rules[path] ||= {}
|
|
78
|
+
rules[path]['matchers'] ||= []
|
|
79
|
+
rules[path]['matchers'] << { 'match' => match_type }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Beth: there's a potential bug if the key contains a dot and a single quote.
|
|
84
|
+
# Not sure what to do then.
|
|
85
|
+
def next_path_part key
|
|
86
|
+
if key.to_s.include?('.')
|
|
87
|
+
"['#{key}']"
|
|
88
|
+
else
|
|
89
|
+
".#{key}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
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).tap { log_ignored_rules }
|
|
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, rules), new_matching_rules |
|
|
29
|
+
new_matching_rules[JsonPath.new(path).to_s] = Marshal.load(Marshal.dump(rules)) # simplest way to deep clone
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def recurse expected, path
|
|
34
|
+
recursed = 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
|
+
wrap(recursed, path)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def recurse_hash hash, path
|
|
44
|
+
hash.each_with_object({}) do | (k, v), new_hash |
|
|
45
|
+
new_path = path + "['#{k}']"
|
|
46
|
+
new_hash[k] = recurse(v, new_path)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def recurse_array array, path
|
|
51
|
+
# This assumes there is only one rule! TODO make this find the appropriate rule.
|
|
52
|
+
parent_match_rule = @matching_rules[path]['matchers'].first['match'] rescue nil
|
|
53
|
+
array_like_children_path = "#{path}[*]*"
|
|
54
|
+
children_match_rule = @matching_rules[array_like_children_path]['matchers'].first['match'] rescue nil
|
|
55
|
+
min = @matching_rules[path]['matchers'].first['min'] rescue nil
|
|
56
|
+
|
|
57
|
+
if min && children_match_rule == 'type'
|
|
58
|
+
@matching_rules[path]['matchers'].first.delete('min')
|
|
59
|
+
@matching_rules[array_like_children_path]['matchers'].first.delete('match')
|
|
60
|
+
warn_when_not_one_example_item(array, path)
|
|
61
|
+
Pact::ArrayLike.new(recurse(array.first, "#{path}[*]"), min: min)
|
|
62
|
+
elsif min && parent_match_rule == 'type'
|
|
63
|
+
@matching_rules[path]['matchers'].first.delete('min')
|
|
64
|
+
@matching_rules[path]['matchers'].first.delete('match')
|
|
65
|
+
warn_when_not_one_example_item(array, path)
|
|
66
|
+
Pact::ArrayLike.new(recurse(array.first, "#{path}[*]"), min: min)
|
|
67
|
+
else
|
|
68
|
+
new_array = []
|
|
69
|
+
array.each_with_index do | item, index |
|
|
70
|
+
new_path = path + "[#{index}]"
|
|
71
|
+
new_array << recurse(item, new_path)
|
|
72
|
+
end
|
|
73
|
+
new_array
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def warn_when_not_one_example_item array, path
|
|
78
|
+
unless array.size == 1
|
|
79
|
+
Pact.configuration.error_stream.puts "WARN: Only the first item will be used to match the items in the array at #{path}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def wrap object, path
|
|
84
|
+
rules = @matching_rules[path] && @matching_rules[path]['matchers'] && @matching_rules[path]['matchers'].first
|
|
85
|
+
array_rules = @matching_rules["#{path}[*]*"] && @matching_rules["#{path}[*]*"]['matchers'] && @matching_rules["#{path}[*]*"]['matchers'].first
|
|
86
|
+
return object unless rules || array_rules
|
|
87
|
+
|
|
88
|
+
if rules['match'] == 'type' && !rules.has_key?('min')
|
|
89
|
+
handle_match_type(object, path, rules)
|
|
90
|
+
elsif rules['regex']
|
|
91
|
+
handle_regex(object, path, rules)
|
|
92
|
+
else
|
|
93
|
+
object
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def handle_match_type object, path, rules
|
|
98
|
+
rules.delete('match')
|
|
99
|
+
Pact::SomethingLike.new(object)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def handle_regex object, path, rules
|
|
103
|
+
rules.delete('match')
|
|
104
|
+
regex = rules.delete('regex')
|
|
105
|
+
Pact::Term.new(generate: object, matcher: Regexp.new(regex))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def log_ignored_rules
|
|
109
|
+
@matching_rules.each do | jsonpath, rules_hash |
|
|
110
|
+
rules_array = rules_hash["matchers"]
|
|
111
|
+
((rules_array.length - 1)..0).each do | index |
|
|
112
|
+
rules_array.delete_at(index) if rules_array[index].empty?
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if @matching_rules.any?
|
|
117
|
+
@matching_rules.each do | path, rules_hash |
|
|
118
|
+
rules_hash.each do | key, value |
|
|
119
|
+
$stderr.puts "WARN: Ignoring unsupported #{key} #{value} for path #{path}" if value.any?
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def find_rule(path, key)
|
|
126
|
+
@matching_rules[path] && @matching_rules[path][key]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def log_used_rule path, key, value
|
|
130
|
+
@used_rules << [path, key, value]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
data/lib/pact/matching_rules.rb
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
require 'pact/matching_rules/extract'
|
|
2
|
+
require 'pact/matching_rules/v3/extract'
|
|
2
3
|
require 'pact/matching_rules/merge'
|
|
4
|
+
require 'pact/matching_rules/v3/merge'
|
|
3
5
|
|
|
4
6
|
module Pact
|
|
5
7
|
module MatchingRules
|
|
6
8
|
|
|
7
9
|
# @api public Used by pact-mock_service
|
|
8
|
-
def self.extract object_graph
|
|
9
|
-
|
|
10
|
+
def self.extract object_graph, options = {}
|
|
11
|
+
pact_specification_version = options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION
|
|
12
|
+
case pact_specification_version.major
|
|
13
|
+
when nil, 0, 1, 2
|
|
14
|
+
Extract.(object_graph)
|
|
15
|
+
else
|
|
16
|
+
V3::Extract.(object_graph)
|
|
17
|
+
end
|
|
10
18
|
end
|
|
11
19
|
|
|
12
|
-
def self.merge object_graph, matching_rules
|
|
13
|
-
|
|
20
|
+
def self.merge object_graph, matching_rules, options = {}
|
|
21
|
+
pact_specification_version = options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION
|
|
22
|
+
case pact_specification_version.major
|
|
23
|
+
when nil, 0, 1, 2
|
|
24
|
+
Merge.(object_graph, matching_rules)
|
|
25
|
+
else
|
|
26
|
+
V3::Merge.(object_graph, matching_rules)
|
|
27
|
+
end
|
|
14
28
|
end
|
|
15
|
-
|
|
16
29
|
end
|
|
17
|
-
end
|
|
30
|
+
end
|
data/lib/pact/reification.rb
CHANGED
|
@@ -5,6 +5,7 @@ require 'pact/array_like'
|
|
|
5
5
|
require 'pact/shared/request'
|
|
6
6
|
require 'pact/consumer_contract/query_hash'
|
|
7
7
|
require 'pact/consumer_contract/query_string'
|
|
8
|
+
require 'pact/consumer_contract/string_with_matching_rules'
|
|
8
9
|
|
|
9
10
|
module Pact
|
|
10
11
|
module Reification
|
|
@@ -26,15 +27,17 @@ module Pact
|
|
|
26
27
|
when Pact::QueryString
|
|
27
28
|
from_term(term.query)
|
|
28
29
|
when Pact::QueryHash
|
|
29
|
-
term.query.map { |k, v|
|
|
30
|
+
from_term(term.query).map { |k, v|
|
|
30
31
|
if v.nil?
|
|
31
32
|
k
|
|
32
33
|
elsif v.is_a?(Array) #For cases where there are multiple instance of the same parameter
|
|
33
|
-
v.map { |x| "#{k}=#{escape(
|
|
34
|
+
v.map { |x| "#{k}=#{escape(x)}"}.join('&')
|
|
34
35
|
else
|
|
35
|
-
"#{k}=#{escape(
|
|
36
|
+
"#{k}=#{escape(v)}"
|
|
36
37
|
end
|
|
37
38
|
}.join('&')
|
|
39
|
+
when Pact::StringWithMatchingRules
|
|
40
|
+
String.new(term)
|
|
38
41
|
else
|
|
39
42
|
term
|
|
40
43
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'pact/shared/text_differ'
|
|
3
|
+
|
|
4
|
+
module Pact
|
|
5
|
+
class MultipartFormDiffer
|
|
6
|
+
def self.call expected, actual, options = {}
|
|
7
|
+
require 'pact/matchers' # avoid recursive loop between this file and pact/matchers
|
|
8
|
+
expected_boundary = expected.split.first
|
|
9
|
+
actual_boundary = actual.split.first
|
|
10
|
+
actual_with_hardcoded_boundary = actual.gsub(actual_boundary, expected_boundary)
|
|
11
|
+
TextDiffer.call(expected, actual_with_hardcoded_boundary, options)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Pact
|
|
2
|
+
class SpecificationVersion < Gem::Version
|
|
3
|
+
|
|
4
|
+
def major
|
|
5
|
+
segments.first
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def === other
|
|
9
|
+
major && major == other
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def after? other
|
|
13
|
+
major && other < major
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
SpecificationVersion::NIL_VERSION = Pact::SpecificationVersion.new('0')
|
|
18
|
+
end
|
data/lib/pact/support/version.rb
CHANGED
data/script/release.sh
CHANGED
|
@@ -5,5 +5,5 @@ git checkout -- lib/pact/support/version.rb
|
|
|
5
5
|
bundle exec bump ${1:-minor} --no-commit
|
|
6
6
|
bundle exec rake generate_changelog
|
|
7
7
|
git add CHANGELOG.md lib/pact/support/version.rb
|
|
8
|
-
git commit -m "
|
|
8
|
+
git commit -m "chore(release): version $(ruby -r ./lib/pact/support/version.rb -e "puts Pact::Support::VERSION")"
|
|
9
9
|
bundle exec rake release
|
|
@@ -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
|
+
}
|