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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +620 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +5 -0
  5. data/lib/pact/array_like.rb +49 -0
  6. data/lib/pact/configuration.rb +193 -0
  7. data/lib/pact/consumer/request.rb +27 -0
  8. data/lib/pact/consumer_contract/consumer_contract.rb +97 -0
  9. data/lib/pact/consumer_contract/file_name.rb +22 -0
  10. data/lib/pact/consumer_contract/headers.rb +51 -0
  11. data/lib/pact/consumer_contract/http_consumer_contract_parser.rb +37 -0
  12. data/lib/pact/consumer_contract/interaction.rb +81 -0
  13. data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
  14. data/lib/pact/consumer_contract/interaction_v2_parser.rb +57 -0
  15. data/lib/pact/consumer_contract/interaction_v3_parser.rb +92 -0
  16. data/lib/pact/consumer_contract/pact_file.rb +157 -0
  17. data/lib/pact/consumer_contract/provider_state.rb +34 -0
  18. data/lib/pact/consumer_contract/query.rb +138 -0
  19. data/lib/pact/consumer_contract/query_hash.rb +89 -0
  20. data/lib/pact/consumer_contract/query_string.rb +51 -0
  21. data/lib/pact/consumer_contract/request.rb +83 -0
  22. data/lib/pact/consumer_contract/response.rb +58 -0
  23. data/lib/pact/consumer_contract/service_consumer.rb +28 -0
  24. data/lib/pact/consumer_contract/service_provider.rb +28 -0
  25. data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
  26. data/lib/pact/consumer_contract.rb +1 -0
  27. data/lib/pact/errors.rb +21 -0
  28. data/lib/pact/helpers.rb +60 -0
  29. data/lib/pact/http/authorization_header_redactor.rb +32 -0
  30. data/lib/pact/logging.rb +14 -0
  31. data/lib/pact/matchers/actual_type.rb +16 -0
  32. data/lib/pact/matchers/base_difference.rb +39 -0
  33. data/lib/pact/matchers/differ.rb +153 -0
  34. data/lib/pact/matchers/difference.rb +13 -0
  35. data/lib/pact/matchers/difference_indicator.rb +26 -0
  36. data/lib/pact/matchers/embedded_diff_formatter.rb +60 -0
  37. data/lib/pact/matchers/expected_type.rb +35 -0
  38. data/lib/pact/matchers/extract_diff_messages.rb +76 -0
  39. data/lib/pact/matchers/index_not_found.rb +15 -0
  40. data/lib/pact/matchers/list_diff_formatter.rb +103 -0
  41. data/lib/pact/matchers/matchers.rb +285 -0
  42. data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
  43. data/lib/pact/matchers/no_diff_at_index.rb +18 -0
  44. data/lib/pact/matchers/regexp_difference.rb +13 -0
  45. data/lib/pact/matchers/type_difference.rb +16 -0
  46. data/lib/pact/matchers/unexpected_index.rb +11 -0
  47. data/lib/pact/matchers/unexpected_key.rb +11 -0
  48. data/lib/pact/matchers/unix_diff_formatter.rb +157 -0
  49. data/lib/pact/matchers.rb +1 -0
  50. data/lib/pact/matching_rules/extract.rb +91 -0
  51. data/lib/pact/matching_rules/jsonpath.rb +58 -0
  52. data/lib/pact/matching_rules/merge.rb +125 -0
  53. data/lib/pact/matching_rules/v3/extract.rb +94 -0
  54. data/lib/pact/matching_rules/v3/merge.rb +141 -0
  55. data/lib/pact/matching_rules.rb +30 -0
  56. data/lib/pact/reification.rb +56 -0
  57. data/lib/pact/rspec.rb +51 -0
  58. data/lib/pact/shared/active_support_support.rb +65 -0
  59. data/lib/pact/shared/dsl.rb +76 -0
  60. data/lib/pact/shared/form_differ.rb +32 -0
  61. data/lib/pact/shared/jruby_support.rb +18 -0
  62. data/lib/pact/shared/json_differ.rb +10 -0
  63. data/lib/pact/shared/key_not_found.rb +15 -0
  64. data/lib/pact/shared/multipart_form_differ.rb +16 -0
  65. data/lib/pact/shared/null_expectation.rb +31 -0
  66. data/lib/pact/shared/request.rb +106 -0
  67. data/lib/pact/shared/text_differ.rb +11 -0
  68. data/lib/pact/something_like.rb +49 -0
  69. data/lib/pact/specification_version.rb +18 -0
  70. data/lib/pact/support/version.rb +5 -0
  71. data/lib/pact/support.rb +12 -0
  72. data/lib/pact/symbolize_keys.rb +13 -0
  73. data/lib/pact/term.rb +85 -0
  74. data/lib/tasks/pact.rake +29 -0
  75. 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,18 @@
1
+ module Pact
2
+ module Matchers
3
+ class NoDiffAtIndex
4
+
5
+ def to_json options = {}
6
+ to_s.inspect
7
+ end
8
+
9
+ def to_s
10
+ '<no difference at this index>'
11
+ end
12
+
13
+ def == other
14
+ other.is_a? NoDiffAtIndex
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require 'pact/matchers/base_difference'
2
+
3
+ module Pact
4
+ module Matchers
5
+ class RegexpDifference < BaseDifference
6
+
7
+ def as_json options = {}
8
+ {:EXPECTED_TO_MATCH => expected.inspect, :ACTUAL => actual}
9
+ end
10
+
11
+ end
12
+ end
13
+ 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,11 @@
1
+ require 'pact/matchers/difference_indicator'
2
+
3
+ module Pact
4
+ class UnexpectedIndex < Pact::DifferenceIndicator
5
+
6
+ def to_s
7
+ '<item not to exist>'
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'pact/matchers/difference_indicator'
2
+
3
+ module Pact
4
+ class UnexpectedKey < Pact::DifferenceIndicator
5
+
6
+ def to_s
7
+ '<key not to exist>'
8
+ end
9
+
10
+ end
11
+ 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'