pact-support 0.2.1 → 0.3.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 +12 -0
- data/lib/pact/consumer_contract/interaction.rb +6 -3
- data/lib/pact/matchers/index_not_found.rb +1 -1
- data/lib/pact/matchers/list_diff_formatter.rb +2 -2
- data/lib/pact/matchers/matchers.rb +42 -34
- data/lib/pact/matchers/{no_diff_indicator.rb → no_diff_at_index.rb} +3 -3
- data/lib/pact/matchers/unexpected_index.rb +1 -1
- data/lib/pact/matchers/unix_diff_formatter.rb +23 -5
- data/lib/pact/matching_rules.rb +17 -0
- data/lib/pact/matching_rules/extract.rb +64 -0
- data/lib/pact/matching_rules/merge.rb +80 -0
- data/lib/pact/support/version.rb +1 -1
- data/lib/pact/term.rb +1 -1
- data/spec/fixtures/interaction-with-matching-rules.json +27 -0
- data/spec/integration/matching_rules_extract_and_merge_spec.rb +98 -0
- data/spec/lib/pact/consumer_contract/interaction_spec.rb +14 -0
- data/spec/lib/pact/matchers/list_diff_formatter_spec.rb +2 -2
- data/spec/lib/pact/matchers/matchers_spec.rb +9 -8
- data/spec/lib/pact/matchers/no_diff_at_index_spec.rb +15 -0
- data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +15 -14
- data/spec/lib/pact/matching_rules/extract_spec.rb +105 -0
- data/spec/lib/pact/matching_rules/merge_spec.rb +148 -0
- data/spec/support/spec_support.rb +9 -0
- metadata +51 -39
- data/spec/lib/pact/matchers/no_diff_indicator_spec.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d30e4cc422b899773c2917361c59ad211792f922
|
4
|
+
data.tar.gz: e20e3a998c5ba7f018b615567718193e009bcd26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e8e7a5188f514b4c5b47de5e3a5005f863544b1625a8bf932a84ac6a700b8d5926ef87c7bfd35dbe56b7c3579ba7793d9704a64f8eae4592c73b2408696316c
|
7
|
+
data.tar.gz: 095ec74b09b3ffa4bce8212d495eb88d93ee69723997d58c2d836be356493fed9bf1b17f0a4e5150f81845cdc67fdf8639598ca5ea9c1e0f9e982bfd5627bfde
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,18 @@ Do this to generate your change history
|
|
2
2
|
|
3
3
|
git log --pretty=format:' * %h - %s (%an, %ad)'
|
4
4
|
|
5
|
+
### 0.3.0 (13 Februrary 2015)
|
6
|
+
|
7
|
+
* 4e29277 - Create a public API for extracting matching rules for pact-mock_service to use. (Beth Skurrie, Fri Feb 13 15:35:14 2015 +1100)
|
8
|
+
* 17ffb7e - Improve Pact::Term error message when value does not match regexp. (Beth Skurrie, Thu Feb 12 15:35:28 2015 +1100)
|
9
|
+
* ad0b37b - Added logic to convert Term and SomethingLike to v2 matching rules (Beth Skurrie, Thu Feb 12 14:55:34 2015 +1100)
|
10
|
+
* cc15c4d - Renamed <index not found> to <item not found>, and <index not to exist> to <item not to exist> (Beth Skurrie, Thu Feb 12 11:47:53 2015 +1100)
|
11
|
+
* 1b65c46 - Change "no difference here" to ... in unix diff output (Beth Skurrie, Thu Feb 12 11:43:58 2015 +1100)
|
12
|
+
* 3cb5b30 - Fix duplicate "no difference here!" in diff when actual array has more items than the expected (Beth Skurrie, Thu Feb 12 11:30:58 2015 +1100)
|
13
|
+
* a9da567 - Changed display of NoDiffAtIndex (Beth Skurrie, Tue Dec 23 15:16:49 2014 +1100)
|
14
|
+
* f9619e6 - Log warning message when unsupported rules are detected (Beth Skurrie, Tue Dec 23 14:42:22 2014 +1100)
|
15
|
+
* 9875bef - Added support for v2 regular expression matching in provider (Beth Skurrie, Tue Dec 23 14:15:00 2014 +1100)
|
16
|
+
|
5
17
|
### 0.2.1 (21 January 2015)
|
6
18
|
|
7
19
|
* 4e26c75 - Ignore HTTP method case when determining if routes match. https://github.com/bethesque/pact-support/issues/3 (Beth, Tue Jan 20 20:15:20 2015 +1100)
|
@@ -2,6 +2,7 @@ require 'pact/consumer_contract/request'
|
|
2
2
|
require 'pact/consumer_contract/response'
|
3
3
|
require 'pact/symbolize_keys'
|
4
4
|
require 'pact/shared/active_support_support'
|
5
|
+
require 'pact/matching_rules'
|
5
6
|
|
6
7
|
module Pact
|
7
8
|
class Interaction
|
@@ -18,9 +19,11 @@ module Pact
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def self.from_hash hash
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
request_hash = Pact::MatchingRules.merge(hash['request'], hash['request']['requestMatchingRules'])
|
23
|
+
request = Pact::Request::Expected.from_hash(request_hash)
|
24
|
+
response_hash = Pact::MatchingRules.merge(hash['response'], hash['response']['responseMatchingRules'])
|
25
|
+
response = Pact::Response.from_hash(response_hash)
|
26
|
+
new(symbolize_keys(hash).merge(request: request, response: response))
|
24
27
|
end
|
25
28
|
|
26
29
|
def to_hash
|
@@ -31,9 +31,9 @@ module Pact
|
|
31
31
|
when Difference then handle_difference obj, path, descriptions
|
32
32
|
when TypeDifference then handle_mismatched_type obj, path, descriptions
|
33
33
|
when RegexpDifference then handle_mismatched_regexp obj, path, descriptions
|
34
|
-
when
|
34
|
+
when NoDiffAtIndex then nil
|
35
35
|
else
|
36
|
-
raise "Invalid diff, expected Hash, Array,
|
36
|
+
raise "Invalid diff, expected Hash, Array, NoDiffAtIndex or Difference, found #{obj.class}"
|
37
37
|
end
|
38
38
|
descriptions
|
39
39
|
end
|
@@ -11,13 +11,14 @@ require 'pact/matchers/regexp_difference'
|
|
11
11
|
require 'pact/matchers/type_difference'
|
12
12
|
require 'pact/matchers/expected_type'
|
13
13
|
require 'pact/matchers/actual_type'
|
14
|
-
require 'pact/matchers/
|
14
|
+
require 'pact/matchers/no_diff_at_index'
|
15
15
|
|
16
16
|
module Pact
|
17
17
|
module Matchers
|
18
18
|
|
19
|
-
|
19
|
+
NO_DIFF_AT_INDEX = NoDiffAtIndex.new
|
20
20
|
DEFAULT_OPTIONS = {allow_unexpected_keys: true, type: false}.freeze
|
21
|
+
NO_DIFF = {}.freeze
|
21
22
|
|
22
23
|
def diff expected, actual, opts = {}
|
23
24
|
calculate_diff(Pact::Term.unpack_regexps(expected), actual, DEFAULT_OPTIONS.merge(opts))
|
@@ -44,7 +45,7 @@ module Pact
|
|
44
45
|
|
45
46
|
def regexp_diff regexp, actual, options
|
46
47
|
if actual.is_a?(String) && regexp.match(actual)
|
47
|
-
|
48
|
+
NO_DIFF
|
48
49
|
else
|
49
50
|
RegexpDifference.new regexp, actual
|
50
51
|
end
|
@@ -69,71 +70,78 @@ module Pact
|
|
69
70
|
diff_found = true
|
70
71
|
difference << item_diff
|
71
72
|
else
|
72
|
-
difference <<
|
73
|
+
difference << NO_DIFF_AT_INDEX
|
73
74
|
end
|
74
75
|
end
|
75
|
-
diff_found ? difference :
|
76
|
+
diff_found ? difference : NO_DIFF
|
77
|
+
end
|
78
|
+
|
79
|
+
def hash_diff expected, actual, options
|
80
|
+
if actual.is_a? Hash
|
81
|
+
actual_hash_diff expected, actual, options
|
82
|
+
else
|
83
|
+
Difference.new expected, actual
|
84
|
+
end
|
76
85
|
end
|
77
86
|
|
78
87
|
def actual_hash_diff expected, actual, options
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
calculate_diff
|
88
|
+
hash_diff = expected.each_with_object({}) do |(key, value), difference|
|
89
|
+
diff_at_key = calculate_diff(value, actual.fetch(key, Pact::KeyNotFound.new), options)
|
90
|
+
difference[key] = diff_at_key if diff_at_key.any?
|
84
91
|
end
|
85
|
-
|
92
|
+
hash_diff.merge(check_for_unexpected_keys(expected, actual, options))
|
86
93
|
end
|
87
94
|
|
88
95
|
def check_for_unexpected_keys expected, actual, options
|
89
96
|
if options[:allow_unexpected_keys]
|
90
|
-
|
97
|
+
NO_DIFF
|
91
98
|
else
|
92
|
-
(actual.keys - expected.keys).
|
93
|
-
|
94
|
-
calculate_diff
|
99
|
+
(actual.keys - expected.keys).each_with_object({}) do | key, running_diff |
|
100
|
+
running_diff[key] = Difference.new(UnexpectedKey.new, actual[key])
|
95
101
|
end
|
96
102
|
end
|
97
103
|
end
|
98
104
|
|
99
|
-
def
|
100
|
-
if
|
101
|
-
|
105
|
+
def object_diff expected, actual, options
|
106
|
+
if options[:type]
|
107
|
+
type_difference expected, actual
|
102
108
|
else
|
109
|
+
exact_value_diff expected, actual, options
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def exact_value_diff expected, actual, options
|
114
|
+
if expected != actual
|
103
115
|
Difference.new expected, actual
|
116
|
+
else
|
117
|
+
NO_DIFF
|
104
118
|
end
|
105
119
|
end
|
106
120
|
|
107
121
|
def type_difference expected, actual
|
108
122
|
if types_match? expected, actual
|
109
|
-
|
123
|
+
NO_DIFF
|
110
124
|
else
|
111
125
|
TypeDifference.new type_diff_expected_display(expected), type_diff_actual_display(actual)
|
112
126
|
end
|
113
127
|
end
|
114
128
|
|
115
|
-
def type_diff_actual_display actual
|
116
|
-
actual.is_a?(KeyNotFound) ? actual : ActualType.new(actual)
|
117
|
-
end
|
118
|
-
|
119
129
|
def type_diff_expected_display expected
|
120
130
|
ExpectedType.new(expected)
|
121
131
|
end
|
122
132
|
|
133
|
+
def type_diff_actual_display actual
|
134
|
+
actual.is_a?(KeyNotFound) ? actual : ActualType.new(actual)
|
135
|
+
end
|
136
|
+
|
123
137
|
def types_match? expected, actual
|
124
|
-
|
125
|
-
expected.class == actual.class ||
|
126
|
-
(expected.is_a?(TrueClass) && actual.is_a?(FalseClass)) ||
|
127
|
-
(expected.is_a?(FalseClass) && actual.is_a?(TrueClass))
|
138
|
+
expected.class == actual.class || (is_boolean(expected) && is_boolean(actual))
|
128
139
|
end
|
129
140
|
|
130
|
-
def
|
131
|
-
|
132
|
-
if expected != actual
|
133
|
-
Difference.new expected, actual
|
134
|
-
else
|
135
|
-
{}
|
136
|
-
end
|
141
|
+
def is_boolean object
|
142
|
+
object == true || object == false
|
137
143
|
end
|
144
|
+
|
145
|
+
|
138
146
|
end
|
139
147
|
end
|
@@ -1,17 +1,17 @@
|
|
1
1
|
module Pact
|
2
2
|
module Matchers
|
3
|
-
class
|
3
|
+
class NoDiffAtIndex
|
4
4
|
|
5
5
|
def to_json options = {}
|
6
6
|
to_s.inspect
|
7
7
|
end
|
8
8
|
|
9
9
|
def to_s
|
10
|
-
'no difference
|
10
|
+
'<no difference at this index>'
|
11
11
|
end
|
12
12
|
|
13
13
|
def == other
|
14
|
-
other.is_a?
|
14
|
+
other.is_a? NoDiffAtIndex
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -27,7 +27,8 @@ module Pact
|
|
27
27
|
expected = generate_string(diff, :expected)
|
28
28
|
actual = generate_string(diff, :actual)
|
29
29
|
suffix = @include_explanation ? "\n" + key : ''
|
30
|
-
@differ.diff_as_string(actual, expected).lstrip
|
30
|
+
string_diff = remove_comma_from_end_of_arrays(@differ.diff_as_string(actual, expected).lstrip)
|
31
|
+
string_diff + suffix
|
31
32
|
end
|
32
33
|
|
33
34
|
private
|
@@ -39,7 +40,7 @@ module Pact
|
|
39
40
|
when Difference then copy_diff(thing, target)
|
40
41
|
when TypeDifference then copy_diff(thing, target)
|
41
42
|
when RegexpDifference then copy_diff(thing, target)
|
42
|
-
when
|
43
|
+
when NoDiffAtIndex then copy_no_diff(thing, target)
|
43
44
|
else copy_object(thing, target)
|
44
45
|
end
|
45
46
|
end
|
@@ -48,7 +49,8 @@ module Pact
|
|
48
49
|
comparable = handle(diff, target)
|
49
50
|
begin
|
50
51
|
# Can't think of an elegant way to check if we can pretty generate other than to try it and maybe fail
|
51
|
-
fix_blank_lines_in_empty_hashes JSON.pretty_generate(comparable)
|
52
|
+
json = fix_blank_lines_in_empty_hashes JSON.pretty_generate(comparable)
|
53
|
+
add_comma_to_end_of_arrays json
|
52
54
|
rescue JSON::GeneratorError
|
53
55
|
comparable.to_s
|
54
56
|
end
|
@@ -69,7 +71,7 @@ module Pact
|
|
69
71
|
end
|
70
72
|
|
71
73
|
def copy_no_diff(thing, target)
|
72
|
-
|
74
|
+
NoDifferenceDecorator.new
|
73
75
|
end
|
74
76
|
|
75
77
|
def copy_diff difference, target
|
@@ -94,13 +96,29 @@ module Pact
|
|
94
96
|
" Values where the expected matches the actual are not shown.\n"
|
95
97
|
end
|
96
98
|
|
99
|
+
def add_comma_to_end_of_arrays string
|
100
|
+
string.gsub(/(\n\s*\])/, ',\1')
|
101
|
+
end
|
102
|
+
|
103
|
+
def remove_comma_from_end_of_arrays string
|
104
|
+
string.gsub(/,(\n\s*\])/, '\1')
|
105
|
+
end
|
106
|
+
|
107
|
+
class NoDifferenceDecorator
|
108
|
+
|
109
|
+
def to_json options = {}
|
110
|
+
"... "
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
97
115
|
class RegexpDecorator
|
98
116
|
|
99
117
|
def initialize regexp
|
100
118
|
@regexp = regexp
|
101
119
|
end
|
102
120
|
|
103
|
-
def to_json options={}
|
121
|
+
def to_json options = {}
|
104
122
|
@regexp.inspect
|
105
123
|
end
|
106
124
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'pact/matching_rules/extract'
|
2
|
+
require 'pact/matching_rules/merge'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module MatchingRules
|
6
|
+
|
7
|
+
# @api public Used by pact-mock_service
|
8
|
+
def self.extract object_graph
|
9
|
+
Extract.(object_graph)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.merge object_graph, matching_rules
|
13
|
+
Merge.(object_graph, matching_rules)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Pact
|
2
|
+
module MatchingRules
|
3
|
+
class Extract
|
4
|
+
|
5
|
+
def self.call matchable
|
6
|
+
new(matchable).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize matchable
|
10
|
+
@matchable = matchable
|
11
|
+
@rules = Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
recurse matchable, "$", nil
|
16
|
+
rules
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :matchable, :rules
|
22
|
+
|
23
|
+
def recurse object, path, match_type
|
24
|
+
|
25
|
+
case object
|
26
|
+
when Hash then recurse_hash(object, path, match_type)
|
27
|
+
when Array then recurse_array(object, path, match_type)
|
28
|
+
when Pact::SomethingLike then recurse_something_like(object, path, match_type)
|
29
|
+
when Pact::Term then record_regex_rule object, path
|
30
|
+
else
|
31
|
+
record_rule object, path, match_type
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def recurse_hash hash, path, match_type
|
37
|
+
hash.each do | (key, value) |
|
38
|
+
recurse value, "#{path}.#{key.to_s}", match_type
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def recurse_array new_array, path, match_type
|
43
|
+
new_array.each_with_index do | value, index |
|
44
|
+
recurse value, "#{path}[#{index}]", match_type
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def recurse_something_like something_like, path, match_type
|
49
|
+
recurse something_like.contents, path, "type"
|
50
|
+
end
|
51
|
+
|
52
|
+
def record_regex_rule term, path
|
53
|
+
rules[path] ||= {}
|
54
|
+
rules[path]['match'] = 'regex'
|
55
|
+
rules[path]['regex'] = term.matcher.inspect[1..-2]
|
56
|
+
end
|
57
|
+
|
58
|
+
def record_rule object, path, match_type
|
59
|
+
rules[path] ||= {}
|
60
|
+
rules[path]['match'] = 'type'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Pact
|
2
|
+
module MatchingRules
|
3
|
+
class Merge
|
4
|
+
|
5
|
+
def self.call expected, matching_rules, root_path = '$'
|
6
|
+
new(expected, matching_rules, root_path).call
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize expected, matching_rules, root_path
|
10
|
+
@expected = expected
|
11
|
+
@matching_rules = matching_rules
|
12
|
+
@root_path = root_path
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
return @expected if @matching_rules.nil? || @matching_rules.empty?
|
17
|
+
recurse @expected, @root_path
|
18
|
+
end
|
19
|
+
|
20
|
+
def recurse expected, path
|
21
|
+
case expected
|
22
|
+
when Hash then recurse_hash(expected, path)
|
23
|
+
when Array then recurse_array(expected, path)
|
24
|
+
else
|
25
|
+
expected
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def recurse_hash hash, path
|
30
|
+
hash.each_with_object({}) do | (k, v), new_hash |
|
31
|
+
new_path = path + ".#{k.to_s}"
|
32
|
+
new_hash[k] = recurse(wrap(v, new_path), new_path)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def recurse_array array, path
|
37
|
+
new_array = []
|
38
|
+
array.each_with_index do | item, index |
|
39
|
+
new_path = path + "[#{index}]"
|
40
|
+
new_array << recurse(wrap(item, new_path), new_path)
|
41
|
+
end
|
42
|
+
new_array
|
43
|
+
end
|
44
|
+
|
45
|
+
def wrap object, path
|
46
|
+
rules = @matching_rules[path]
|
47
|
+
return object unless rules
|
48
|
+
|
49
|
+
if rules['match'] == 'type'
|
50
|
+
handle_match_type(object, path, rules)
|
51
|
+
elsif rules['regex']
|
52
|
+
handle_regex(object, path, rules)
|
53
|
+
else
|
54
|
+
log_ignored_rules(path, rules, {})
|
55
|
+
object
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_match_type object, path, rules
|
60
|
+
log_ignored_rules(path, rules, {'match' => 'type'})
|
61
|
+
Pact::SomethingLike.new(object)
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_regex object, path, rules
|
65
|
+
log_ignored_rules(path, rules, {'match' => 'regex', 'regex' => rules['regex']})
|
66
|
+
Pact::Term.new(generate: object, matcher: Regexp.new(rules['regex']))
|
67
|
+
end
|
68
|
+
|
69
|
+
def log_ignored_rules path, rules, used_rules
|
70
|
+
dup_rules = rules.dup
|
71
|
+
used_rules.each_pair do | used_key, used_value |
|
72
|
+
dup_rules.delete(used_key) if dup_rules[used_key] == used_value
|
73
|
+
end
|
74
|
+
if dup_rules.any?
|
75
|
+
$stderr.puts "WARN: Ignoring unsupported matching rules #{dup_rules} for path #{path}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|