hs-pact-support 1.17.1
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 +7 -0
- data/CHANGELOG.md +620 -0
- data/LICENSE.txt +22 -0
- data/README.md +5 -0
- data/lib/pact/array_like.rb +49 -0
- data/lib/pact/configuration.rb +193 -0
- data/lib/pact/consumer/request.rb +27 -0
- data/lib/pact/consumer_contract/consumer_contract.rb +97 -0
- data/lib/pact/consumer_contract/file_name.rb +22 -0
- data/lib/pact/consumer_contract/headers.rb +51 -0
- data/lib/pact/consumer_contract/http_consumer_contract_parser.rb +37 -0
- data/lib/pact/consumer_contract/interaction.rb +81 -0
- data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
- data/lib/pact/consumer_contract/interaction_v2_parser.rb +57 -0
- data/lib/pact/consumer_contract/interaction_v3_parser.rb +92 -0
- data/lib/pact/consumer_contract/pact_file.rb +157 -0
- data/lib/pact/consumer_contract/provider_state.rb +34 -0
- data/lib/pact/consumer_contract/query.rb +138 -0
- data/lib/pact/consumer_contract/query_hash.rb +89 -0
- data/lib/pact/consumer_contract/query_string.rb +51 -0
- data/lib/pact/consumer_contract/request.rb +83 -0
- data/lib/pact/consumer_contract/response.rb +58 -0
- data/lib/pact/consumer_contract/service_consumer.rb +28 -0
- data/lib/pact/consumer_contract/service_provider.rb +28 -0
- data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
- data/lib/pact/consumer_contract.rb +1 -0
- data/lib/pact/errors.rb +21 -0
- data/lib/pact/helpers.rb +60 -0
- data/lib/pact/http/authorization_header_redactor.rb +32 -0
- data/lib/pact/logging.rb +14 -0
- data/lib/pact/matchers/actual_type.rb +16 -0
- data/lib/pact/matchers/base_difference.rb +39 -0
- data/lib/pact/matchers/differ.rb +153 -0
- data/lib/pact/matchers/difference.rb +13 -0
- data/lib/pact/matchers/difference_indicator.rb +26 -0
- data/lib/pact/matchers/embedded_diff_formatter.rb +60 -0
- data/lib/pact/matchers/expected_type.rb +35 -0
- data/lib/pact/matchers/extract_diff_messages.rb +76 -0
- data/lib/pact/matchers/index_not_found.rb +15 -0
- data/lib/pact/matchers/list_diff_formatter.rb +103 -0
- data/lib/pact/matchers/matchers.rb +285 -0
- data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
- data/lib/pact/matchers/no_diff_at_index.rb +18 -0
- data/lib/pact/matchers/regexp_difference.rb +13 -0
- data/lib/pact/matchers/type_difference.rb +16 -0
- data/lib/pact/matchers/unexpected_index.rb +11 -0
- data/lib/pact/matchers/unexpected_key.rb +11 -0
- data/lib/pact/matchers/unix_diff_formatter.rb +157 -0
- data/lib/pact/matchers.rb +1 -0
- data/lib/pact/matching_rules/extract.rb +91 -0
- data/lib/pact/matching_rules/jsonpath.rb +58 -0
- data/lib/pact/matching_rules/merge.rb +125 -0
- data/lib/pact/matching_rules/v3/extract.rb +94 -0
- data/lib/pact/matching_rules/v3/merge.rb +141 -0
- data/lib/pact/matching_rules.rb +30 -0
- data/lib/pact/reification.rb +56 -0
- data/lib/pact/rspec.rb +51 -0
- data/lib/pact/shared/active_support_support.rb +65 -0
- data/lib/pact/shared/dsl.rb +76 -0
- data/lib/pact/shared/form_differ.rb +32 -0
- data/lib/pact/shared/jruby_support.rb +18 -0
- data/lib/pact/shared/json_differ.rb +10 -0
- data/lib/pact/shared/key_not_found.rb +15 -0
- data/lib/pact/shared/multipart_form_differ.rb +16 -0
- data/lib/pact/shared/null_expectation.rb +31 -0
- data/lib/pact/shared/request.rb +106 -0
- data/lib/pact/shared/text_differ.rb +11 -0
- data/lib/pact/something_like.rb +49 -0
- data/lib/pact/specification_version.rb +18 -0
- data/lib/pact/support/version.rb +5 -0
- data/lib/pact/support.rb +12 -0
- data/lib/pact/symbolize_keys.rb +13 -0
- data/lib/pact/term.rb +85 -0
- data/lib/tasks/pact.rake +29 -0
- metadata +327 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'awesome_print' # The .ai method comes from awesome_print
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
module Matchers
|
5
|
+
class ListDiffFormatter
|
6
|
+
|
7
|
+
attr_reader :diff
|
8
|
+
|
9
|
+
def initialize diff, options = {}
|
10
|
+
@diff = diff
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.call diff, options = {}
|
14
|
+
new(diff, options).call
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
diff
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
diff_descriptions(diff).join("\n")
|
27
|
+
end
|
28
|
+
|
29
|
+
def diff_descriptions obj, path = [], descriptions = []
|
30
|
+
case obj
|
31
|
+
when Hash then handle_hash obj, path, descriptions
|
32
|
+
when Array then handle_array obj, path, descriptions
|
33
|
+
when Difference then handle_difference obj, path, descriptions
|
34
|
+
when TypeDifference then handle_mismatched_type obj, path, descriptions
|
35
|
+
when RegexpDifference then handle_mismatched_regexp obj, path, descriptions
|
36
|
+
when NoDiffAtIndex then nil
|
37
|
+
else
|
38
|
+
raise "Invalid diff, expected Hash, Array, NoDiffAtIndex or Difference, found #{obj.class}"
|
39
|
+
end
|
40
|
+
descriptions
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_hash hash, path, descriptions
|
44
|
+
hash.each_pair do | key, value |
|
45
|
+
diff_descriptions value, path + [key.inspect], descriptions
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_array array, path, descriptions
|
50
|
+
array.each_with_index do | obj, index |
|
51
|
+
diff_descriptions obj, path + [index], descriptions
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_difference difference, path, descriptions
|
56
|
+
case difference.expected
|
57
|
+
when Pact::UnexpectedKey then handle_unexpected_key(difference, path, descriptions)
|
58
|
+
when Pact::UnexpectedIndex then handle_unexpected_index(difference, path, descriptions)
|
59
|
+
else
|
60
|
+
case difference.actual
|
61
|
+
when Pact::KeyNotFound then handle_key_not_found(difference, path, descriptions)
|
62
|
+
when Pact::IndexNotFound then handle_index_not_found(difference, path, descriptions)
|
63
|
+
else
|
64
|
+
handle_mismatched_value(difference, path, descriptions)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_unexpected_index difference, path, descriptions
|
70
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tArray contained unexpected item:\n\t\t#{difference.actual.ai}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def handle_mismatched_value difference, path, descriptions
|
74
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tExpected:\n\t\t#{difference.expected.ai}\n\tActual:\n\t\t#{difference.actual.ai}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_mismatched_regexp difference, path, descriptions
|
78
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tExpected to match:\n\t\t#{difference.expected.inspect}\n\tActual:\n\t\t#{difference.actual.ai}"
|
79
|
+
end
|
80
|
+
|
81
|
+
def handle_mismatched_type difference, path, descriptions
|
82
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tExpected type:\n\t\t#{difference.expected}\n\tActual type:\n\t\t#{difference.actual}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_index_not_found difference, path, descriptions
|
86
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tMissing index with value:\n\t\t#{difference.expected.ai}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def handle_key_not_found difference, path, descriptions
|
90
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tMissing key with value:\n\t\t#{difference.expected.ai}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_unexpected_key difference, path, descriptions
|
94
|
+
descriptions << "\tAt:\n\t\t#{path_to_s(path)}\n\tHash contained unexpected key with value:\n\t\t#{difference.actual.ai}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def path_to_s path
|
98
|
+
"[" + path.join("][") + "]"
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
require 'pact/configuration'
|
2
|
+
require 'pact/term'
|
3
|
+
require 'pact/something_like'
|
4
|
+
require 'pact/array_like'
|
5
|
+
require 'pact/shared/null_expectation'
|
6
|
+
require 'pact/shared/key_not_found'
|
7
|
+
require 'pact/matchers/unexpected_key'
|
8
|
+
require 'pact/matchers/unexpected_index'
|
9
|
+
require 'pact/matchers/index_not_found'
|
10
|
+
require 'pact/matchers/difference'
|
11
|
+
require 'pact/matchers/regexp_difference'
|
12
|
+
require 'pact/matchers/type_difference'
|
13
|
+
require 'pact/matchers/expected_type'
|
14
|
+
require 'pact/matchers/actual_type'
|
15
|
+
require 'pact/matchers/no_diff_at_index'
|
16
|
+
require 'pact/reification'
|
17
|
+
|
18
|
+
module Pact
|
19
|
+
# Should be called Differs
|
20
|
+
# Note to self: Some people are using this module directly, so if you refactor it
|
21
|
+
# maintain backwards compatibility
|
22
|
+
|
23
|
+
module Matchers
|
24
|
+
NO_DIFF_AT_INDEX = NoDiffAtIndex.new
|
25
|
+
NO_DIFF = {}.freeze
|
26
|
+
NUMERIC_TYPES = %w[Integer Float Fixnum Bignum BigDecimal].freeze
|
27
|
+
DEFAULT_OPTIONS = {
|
28
|
+
allow_unexpected_keys: true,
|
29
|
+
type: false
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
extend self
|
33
|
+
|
34
|
+
def diff expected, actual, opts = {}
|
35
|
+
calculate_diff(expected, actual, DEFAULT_OPTIONS.merge(configurable_options).merge(opts))
|
36
|
+
end
|
37
|
+
|
38
|
+
def type_diff expected, actual, opts = {}
|
39
|
+
calculate_diff expected, actual, DEFAULT_OPTIONS.merge(configurable_options).merge(opts).merge(type: true)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def configurable_options
|
45
|
+
{ treat_all_number_classes_as_equivalent: Pact.configuration.treat_all_number_classes_as_equivalent }
|
46
|
+
end
|
47
|
+
|
48
|
+
def calculate_diff expected, actual, opts = {}
|
49
|
+
options = DEFAULT_OPTIONS.merge(opts)
|
50
|
+
case expected
|
51
|
+
when Hash then hash_diff(expected, actual, options)
|
52
|
+
when Array then array_diff(expected, actual, options)
|
53
|
+
when Regexp then regexp_diff(expected, actual, options)
|
54
|
+
when Pact::SomethingLike then calculate_diff(expected.contents, actual, options.merge(:type => true))
|
55
|
+
when Pact::ArrayLike then array_like_diff(expected, actual, options)
|
56
|
+
when Pact::Term then term_diff(expected, actual, options)
|
57
|
+
else object_diff(expected, actual, options)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :structure_diff, :type_diff # Backwards compatibility
|
62
|
+
|
63
|
+
def term_diff term, actual, options
|
64
|
+
if actual.is_a?(String)
|
65
|
+
actual_term_diff term, actual, options
|
66
|
+
else
|
67
|
+
RegexpDifference.new term.matcher, actual, "Expected a String matching #{term.matcher.inspect} (like #{term.generate.inspect}) but got #{class_name_with_value_in_brackets(actual)} at <path>"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def actual_term_diff term, actual, options
|
72
|
+
if term.matcher.match(actual)
|
73
|
+
NO_DIFF
|
74
|
+
else
|
75
|
+
RegexpDifference.new term.matcher, actual, "Expected a String matching #{term.matcher.inspect} (like #{term.generate.inspect}) but got #{actual.inspect} at <path>"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def regexp_diff regexp, actual, options
|
80
|
+
if actual.is_a?(String)
|
81
|
+
actual_regexp_diff regexp, actual, options
|
82
|
+
else
|
83
|
+
RegexpDifference.new regexp, actual, "Expected a String matching #{regexp.inspect} but got #{class_name_with_value_in_brackets(actual)} at <path>"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def actual_regexp_diff regexp, actual, options
|
88
|
+
if regexp.match(actual)
|
89
|
+
NO_DIFF
|
90
|
+
else
|
91
|
+
RegexpDifference.new regexp, actual, "Expected a String matching #{regexp.inspect} but got #{short_description(actual)} at <path>"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def array_diff expected, actual, options
|
96
|
+
if actual.is_a? Array
|
97
|
+
actual_array_diff expected, actual, options
|
98
|
+
else
|
99
|
+
Difference.new Pact::Reification.from_term(expected), actual, type_difference_message(Pact::Reification.from_term(expected), actual)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def actual_array_diff expected, actual, options
|
104
|
+
difference = []
|
105
|
+
diff_found = false
|
106
|
+
length = [expected.length, actual.length].max
|
107
|
+
length.times do | index|
|
108
|
+
expected_item = expected.fetch(index, Pact::UnexpectedIndex.new)
|
109
|
+
actual_item = actual.fetch(index, Pact::IndexNotFound.new)
|
110
|
+
if (item_diff = calculate_diff(expected_item, actual_item, options)).any?
|
111
|
+
diff_found = true
|
112
|
+
difference << item_diff
|
113
|
+
else
|
114
|
+
difference << NO_DIFF_AT_INDEX
|
115
|
+
end
|
116
|
+
end
|
117
|
+
diff_found ? difference : NO_DIFF
|
118
|
+
end
|
119
|
+
|
120
|
+
def array_like_diff array_like, actual, options
|
121
|
+
if actual.is_a? Array
|
122
|
+
expected_size = [array_like.min, actual.size].max
|
123
|
+
# I know changing this is going to break something, but I don't know what it is, as there's no
|
124
|
+
# test that fails when I make this change. I know the unpack regexps was there for a reason however.
|
125
|
+
# Guess we'll have to change it and see!
|
126
|
+
# expected_array = expected_size.times.collect{ Pact::Term.unpack_regexps(array_like.contents) }
|
127
|
+
expected_array = expected_size.times.collect{ array_like.contents }
|
128
|
+
actual_array_diff expected_array, actual, options.merge(:type => true)
|
129
|
+
else
|
130
|
+
Difference.new array_like.generate, actual, type_difference_message(array_like.generate, actual)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def hash_diff expected, actual, options
|
135
|
+
if actual.is_a? Hash
|
136
|
+
actual_hash_diff expected, actual, options
|
137
|
+
else
|
138
|
+
Difference.new Pact::Reification.from_term(expected), actual, type_difference_message(Pact::Reification.from_term(expected), actual)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def actual_hash_diff expected, actual, options
|
143
|
+
hash_diff = expected.each_with_object({}) do |(key, expected_value), difference|
|
144
|
+
diff_at_key = calculate_diff_at_key(key, expected_value, actual, difference, options)
|
145
|
+
difference[key] = diff_at_key if diff_at_key.any?
|
146
|
+
end
|
147
|
+
hash_diff.merge(check_for_unexpected_keys(expected, actual, options))
|
148
|
+
end
|
149
|
+
|
150
|
+
def calculate_diff_at_key key, expected_value, actual, difference, options
|
151
|
+
actual_value = actual.fetch(key, Pact::KeyNotFound.new)
|
152
|
+
diff_at_key = calculate_diff(expected_value, actual_value, options)
|
153
|
+
if actual_value.is_a?(Pact::KeyNotFound)
|
154
|
+
diff_at_key.message = key_not_found_message(key, actual)
|
155
|
+
end
|
156
|
+
diff_at_key
|
157
|
+
end
|
158
|
+
|
159
|
+
def check_for_unexpected_keys expected, actual, options
|
160
|
+
if options[:allow_unexpected_keys]
|
161
|
+
NO_DIFF
|
162
|
+
else
|
163
|
+
(actual.keys - expected.keys).each_with_object({}) do | key, running_diff |
|
164
|
+
running_diff[key] = Difference.new(UnexpectedKey.new, actual[key], "Did not expect the key \"#{key}\" to exist at <parent_path>")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def object_diff expected, actual, options
|
170
|
+
if options[:type]
|
171
|
+
type_difference expected, actual, options
|
172
|
+
else
|
173
|
+
exact_value_diff expected, actual, options
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def exact_value_diff expected, actual, options
|
178
|
+
if expected == actual
|
179
|
+
NO_DIFF
|
180
|
+
else
|
181
|
+
Difference.new expected, actual, value_difference_message(expected, actual, options)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def type_difference expected, actual, options
|
186
|
+
if types_match? expected, actual, options
|
187
|
+
NO_DIFF
|
188
|
+
else
|
189
|
+
TypeDifference.new type_diff_expected_display(expected), type_diff_actual_display(actual), type_difference_message(expected, actual)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def type_diff_expected_display expected
|
194
|
+
ExpectedType.new(expected)
|
195
|
+
end
|
196
|
+
|
197
|
+
def type_diff_actual_display actual
|
198
|
+
actual.is_a?(KeyNotFound) ? actual : ActualType.new(actual)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Make options optional to support existing monkey patches
|
202
|
+
def types_match? expected, actual, options = {}
|
203
|
+
expected.class == actual.class ||
|
204
|
+
(is_boolean(expected) && is_boolean(actual)) ||
|
205
|
+
(options.fetch(:treat_all_number_classes_as_equivalent, false) && is_number?(expected) && is_number?(actual))
|
206
|
+
end
|
207
|
+
|
208
|
+
def is_number? object
|
209
|
+
# deal with Fixnum and Integer without warnings by using string class names
|
210
|
+
NUMERIC_TYPES.include?(object.class.to_s)
|
211
|
+
end
|
212
|
+
|
213
|
+
def is_boolean object
|
214
|
+
object == true || object == false
|
215
|
+
end
|
216
|
+
|
217
|
+
def has_children? object
|
218
|
+
object.is_a?(Hash) || object.is_a?(Array)
|
219
|
+
end
|
220
|
+
|
221
|
+
def value_difference_message expected, actual, options = {}
|
222
|
+
case expected
|
223
|
+
when Pact::UnexpectedIndex
|
224
|
+
"Actual array is too long and should not contain #{short_description(actual)} at <path>"
|
225
|
+
else
|
226
|
+
case actual
|
227
|
+
when Pact::IndexNotFound
|
228
|
+
"Actual array is too short and should have contained #{short_description(expected)} at <path>"
|
229
|
+
else
|
230
|
+
"Expected #{short_description(expected)} but got #{short_description(actual)} at <path>"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def type_difference_message expected, actual
|
236
|
+
case expected
|
237
|
+
when Pact::UnexpectedIndex
|
238
|
+
"Actual array is too long and should not contain #{short_description(actual)} at <path>"
|
239
|
+
else
|
240
|
+
case actual
|
241
|
+
when Pact::IndexNotFound
|
242
|
+
"Actual array is too short and should have contained #{short_description(expected)} at <path>"
|
243
|
+
else
|
244
|
+
expected_desc = class_name_with_value_in_brackets(expected)
|
245
|
+
expected_desc.gsub!("(", "(like ")
|
246
|
+
actual_desc = class_name_with_value_in_brackets(actual)
|
247
|
+
"Expected #{expected_desc} but got #{actual_desc} at <path>"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def class_name_with_value_in_brackets object
|
253
|
+
object_desc = has_children?(object) && object.inspect.length < 100 ? "" : " (#{object.inspect})"
|
254
|
+
object_desc = if object.nil?
|
255
|
+
"nil"
|
256
|
+
else
|
257
|
+
"#{class_description(object)}#{object_desc}"
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def key_not_found_message key, actual
|
262
|
+
hint = actual.any? ? "(keys present are: #{actual.keys.join(", ")})" : "in empty Hash"
|
263
|
+
"Could not find key \"#{key}\" #{hint} at <parent_path>"
|
264
|
+
end
|
265
|
+
|
266
|
+
def short_description object
|
267
|
+
return "nil" if object.nil?
|
268
|
+
case object
|
269
|
+
when Hash then "a Hash"
|
270
|
+
when Array then "an Array"
|
271
|
+
else object.inspect
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def class_description object
|
276
|
+
return "nil" if object.nil?
|
277
|
+
clazz = object.class
|
278
|
+
case clazz.name[0]
|
279
|
+
when /[AEIOU]/ then "an #{clazz}"
|
280
|
+
else
|
281
|
+
"a #{clazz}"
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
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
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pact/matchers/base_difference'
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
module Matchers
|
5
|
+
class TypeDifference < BaseDifference
|
6
|
+
|
7
|
+
def as_json options = {}
|
8
|
+
if KeyNotFound === actual
|
9
|
+
{:EXPECTED_TYPE => expected.as_json, :ACTUAL => actual.as_json }
|
10
|
+
else
|
11
|
+
{:EXPECTED_TYPE => expected.as_json, :ACTUAL_TYPE => actual.as_json }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'pact/shared/jruby_support'
|
2
|
+
require 'pact/matchers/differ'
|
3
|
+
require 'pact/matchers/extract_diff_messages'
|
4
|
+
|
5
|
+
module Pact
|
6
|
+
module Matchers
|
7
|
+
|
8
|
+
class UnixDiffFormatter
|
9
|
+
|
10
|
+
include JRubySupport
|
11
|
+
|
12
|
+
MESSAGES_TITLE = "\n\nDescription of differences\n--------------------------------------"
|
13
|
+
|
14
|
+
def initialize diff, options = {}
|
15
|
+
@diff = diff
|
16
|
+
@colour = options.fetch(:colour, false)
|
17
|
+
@actual = options.fetch(:actual, {})
|
18
|
+
@include_explanation = options.fetch(:include_explanation, true)
|
19
|
+
@differ = Pact::Matchers::Differ.new(@colour)
|
20
|
+
@messages = Pact::Matchers::ExtractDiffMessages.call(diff).collect{ | message| "* #{message}" }.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.call diff, options = {}
|
24
|
+
# require stops circular dependency from pact/configuration <-> pact/matchers/unix_diff_formatter
|
25
|
+
require 'pact/configuration'
|
26
|
+
default_options = {colour: Pact.configuration.color_enabled}
|
27
|
+
new(diff, default_options.merge(options)).call
|
28
|
+
end
|
29
|
+
|
30
|
+
def call
|
31
|
+
to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
|
36
|
+
expected = generate_string(diff, :expected)
|
37
|
+
actual = generate_string(diff, :actual)
|
38
|
+
suffix = @include_explanation ? key + "\n" : ''
|
39
|
+
messages = @include_explanation ? "#{MESSAGES_TITLE}\n#{@messages}\n" : ''
|
40
|
+
string_diff = @differ.diff_as_string(actual, expected).lstrip
|
41
|
+
string_diff = remove_first_line(string_diff)
|
42
|
+
string_diff = remove_comma_from_end_of_arrays(string_diff)
|
43
|
+
suffix + string_diff + messages
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def handle thing, target
|
49
|
+
case thing
|
50
|
+
when Hash then copy_hash(thing, target)
|
51
|
+
when Array then copy_array(thing, target)
|
52
|
+
when Difference then copy_diff(thing, target)
|
53
|
+
when TypeDifference then copy_diff(thing, target)
|
54
|
+
when RegexpDifference then copy_diff(thing, target)
|
55
|
+
when NoDiffAtIndex then copy_no_diff(thing, target)
|
56
|
+
else copy_object(thing, target)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate_string diff, target
|
61
|
+
comparable = handle(diff, target)
|
62
|
+
begin
|
63
|
+
# Can't think of an elegant way to check if we can pretty generate other than to try it and maybe fail
|
64
|
+
json = fix_blank_lines_in_empty_hashes JSON.pretty_generate(comparable)
|
65
|
+
add_comma_to_end_of_arrays json
|
66
|
+
rescue JSON::GeneratorError
|
67
|
+
comparable.to_s
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def copy_hash hash, target
|
72
|
+
hash.keys.each_with_object({}) do | key, new_hash |
|
73
|
+
value = handle hash[key], target
|
74
|
+
new_hash[key] = value unless (KeyNotFound === value || UnexpectedKey === value)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def copy_array array, target
|
79
|
+
array.each_index.each_with_object([]) do | index, new_array |
|
80
|
+
value = handle array[index], target
|
81
|
+
new_array[index] = value unless (UnexpectedIndex === value || IndexNotFound === value)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def copy_no_diff(thing, target)
|
86
|
+
NoDifferenceDecorator.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def copy_diff difference, target
|
90
|
+
if target == :actual
|
91
|
+
handle difference.actual, target
|
92
|
+
else
|
93
|
+
handle difference.expected, target
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def copy_object object, target
|
98
|
+
if Regexp === object
|
99
|
+
RegexpDecorator.new(object)
|
100
|
+
else
|
101
|
+
object
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def key
|
106
|
+
"Diff\n--------------------------------------\n" +
|
107
|
+
"Key: " + @differ.red("-") + @differ.red(" is expected \n") +
|
108
|
+
@differ.green(" +") + @differ.green(" is actual \n") +
|
109
|
+
"Matching keys and values are not shown\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
def remove_first_line string_diff
|
113
|
+
lines = string_diff.split("\n")
|
114
|
+
if lines[0] =~ /@@/
|
115
|
+
lines[1..-1].join("\n")
|
116
|
+
else
|
117
|
+
string_diff
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def add_comma_to_end_of_arrays string
|
123
|
+
string.gsub(/(\n\s*\])/, ',\1')
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_comma_from_end_of_arrays string
|
127
|
+
string.gsub(/,(\n\s*\])/, '\1')
|
128
|
+
end
|
129
|
+
|
130
|
+
class NoDifferenceDecorator
|
131
|
+
|
132
|
+
def to_json options = {}
|
133
|
+
"... "
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
class RegexpDecorator
|
139
|
+
|
140
|
+
def initialize regexp
|
141
|
+
@regexp = regexp
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_json options = {}
|
145
|
+
@regexp.inspect
|
146
|
+
end
|
147
|
+
|
148
|
+
def as_json
|
149
|
+
@regexp.inspect
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
attr_reader :diff
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'pact/matchers/matchers'
|