pact-support 1.4.0 → 1.7.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -0
  3. data/lib/pact/configuration.rb +4 -0
  4. data/lib/pact/consumer_contract/consumer_contract.rb +19 -8
  5. data/lib/pact/consumer_contract/http_consumer_contract_parser.rb +37 -0
  6. data/lib/pact/consumer_contract/interaction.rb +52 -57
  7. data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
  8. data/lib/pact/consumer_contract/interaction_v2_parser.rb +28 -0
  9. data/lib/pact/consumer_contract/interaction_v3_parser.rb +61 -0
  10. data/lib/pact/consumer_contract/pact_file.rb +24 -24
  11. data/lib/pact/consumer_contract/query_hash.rb +2 -0
  12. data/lib/pact/consumer_contract/query_string.rb +2 -2
  13. data/lib/pact/consumer_contract/request.rb +1 -5
  14. data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
  15. data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
  16. data/lib/pact/matching_rules/merge.rb +30 -14
  17. data/lib/pact/matching_rules/v3/extract.rb +94 -0
  18. data/lib/pact/matching_rules/v3/merge.rb +128 -0
  19. data/lib/pact/matching_rules.rb +19 -6
  20. data/lib/pact/reification.rb +6 -3
  21. data/lib/pact/shared/multipart_form_differ.rb +14 -0
  22. data/lib/pact/specification_version.rb +18 -0
  23. data/lib/pact/support/version.rb +1 -1
  24. data/script/release.sh +1 -1
  25. data/spec/fixtures/multipart-form-diff.txt +9 -0
  26. data/spec/fixtures/not-a-pact.json +3 -0
  27. data/spec/fixtures/pact-http-v2.json +36 -0
  28. data/spec/fixtures/pact-http-v3.json +36 -0
  29. data/spec/integration/matching_rules_extract_and_merge_spec.rb +41 -4
  30. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +54 -31
  31. data/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb +25 -0
  32. data/spec/lib/pact/consumer_contract/interaction_parser_spec.rb +62 -0
  33. data/spec/lib/pact/consumer_contract/interaction_spec.rb +0 -23
  34. data/spec/lib/pact/consumer_contract/pact_file_spec.rb +9 -0
  35. data/spec/lib/pact/consumer_contract/query_hash_spec.rb +23 -0
  36. data/spec/lib/pact/matchers/multipart_form_diff_formatter_spec.rb +36 -0
  37. data/spec/lib/pact/matching_rules/merge_spec.rb +20 -7
  38. data/spec/lib/pact/matching_rules/v3/extract_spec.rb +238 -0
  39. data/spec/lib/pact/matching_rules/v3/merge_spec.rb +386 -0
  40. data/spec/lib/pact/matching_rules_spec.rb +82 -0
  41. data/spec/lib/pact/reification_spec.rb +18 -5
  42. data/spec/lib/pact/shared/multipart_form_differ_spec.rb +39 -0
  43. data/tasks/spec.rake +0 -1
  44. metadata +35 -3
@@ -0,0 +1,386 @@
1
+ require 'pact/matching_rules/v3/merge'
2
+
3
+ module Pact
4
+ module MatchingRules
5
+ module V3
6
+ describe Merge do
7
+ subject { Merge.(expected, matching_rules) }
8
+
9
+ before do
10
+ allow($stderr).to receive(:puts) do | message |
11
+ raise "Was not expecting stderr to receive #{message.inspect} in this spec. This may be because of a missed rule deletion in Merge."
12
+ end
13
+ end
14
+
15
+ describe "no recognised rules" do
16
+ before do
17
+ allow($stderr).to receive(:puts)
18
+ end
19
+
20
+ let(:expected) do
21
+ {
22
+ "_links" => {
23
+ "self" => {
24
+ "href" => "http://localhost:1234/thing"
25
+ }
26
+ }
27
+ }
28
+ end
29
+
30
+ let(:matching_rules) do
31
+ {
32
+ "$._links.self.href" => {
33
+ "matchers" => [{ "type" => "unknown" }]
34
+ }
35
+ }
36
+ end
37
+
38
+ it "returns the object at that path unaltered" do
39
+ expect(subject["_links"]["self"]["href"]).to eq "http://localhost:1234/thing"
40
+ end
41
+
42
+ it "it logs the rules it has ignored" do
43
+ expect($stderr).to receive(:puts) do | message |
44
+ expect(message).to include("WARN")
45
+ expect(message).to include("type")
46
+ expect(message).to include("unknown")
47
+ expect(message).to include("$['_links']")
48
+ end
49
+ subject
50
+ end
51
+
52
+ end
53
+
54
+ describe "with nil rules" do
55
+ let(:expected) do
56
+ {
57
+ "_links" => {
58
+ "self" => {
59
+ "href" => "http://localhost:1234/thing"
60
+ }
61
+ }
62
+ }
63
+ end
64
+
65
+ let(:matching_rules) { nil }
66
+
67
+ it "returns the example unaltered" do
68
+ expect(subject["_links"]["self"]["href"]).to eq "http://localhost:1234/thing"
69
+ end
70
+
71
+ end
72
+
73
+ describe "type based matching" do
74
+ before do
75
+ allow($stderr).to receive(:puts)
76
+ end
77
+
78
+ let(:expected) do
79
+ {
80
+ "name" => "Mary"
81
+ }
82
+ end
83
+
84
+ let(:matching_rules) do
85
+ {
86
+ "$.name" => {
87
+ "matchers" => [{ "match" => "type", "ignored" => "matchingrule" }]
88
+ }
89
+ }
90
+ end
91
+
92
+ it "creates a SomethingLike at the appropriate path" do
93
+ expect(subject['name']).to be_instance_of(Pact::SomethingLike)
94
+ end
95
+
96
+ it "it logs the rules it has ignored" do
97
+ expect($stderr).to receive(:puts).with(/ignored.*matchingrule/)
98
+ subject
99
+ end
100
+
101
+ it "does not alter the passed in rules hash" do
102
+ original_matching_rules = JSON.parse(matching_rules.to_json)
103
+ subject
104
+ expect(matching_rules).to eq original_matching_rules
105
+ end
106
+ end
107
+
108
+ describe "regular expressions" do
109
+ describe "in a hash" do
110
+ before do
111
+ allow($stderr).to receive(:puts)
112
+ end
113
+
114
+ let(:expected) do
115
+ {
116
+ "_links" => {
117
+ "self" => {
118
+ "href" => "http://localhost:1234/thing"
119
+ }
120
+ }
121
+ }
122
+ end
123
+
124
+ let(:matching_rules) do
125
+ {
126
+ "$._links.self.href" => {
127
+ "matchers" => [{ "regex" => "http:\\/\\/.*\\/thing", "match" => "regex", "ignored" => "somerule" }]
128
+ }
129
+ }
130
+ end
131
+
132
+ it "creates a Pact::Term at the appropriate path" do
133
+ expect(subject["_links"]["self"]["href"]).to be_instance_of(Pact::Term)
134
+ expect(subject["_links"]["self"]["href"].generate).to eq "http://localhost:1234/thing"
135
+ expect(subject["_links"]["self"]["href"].matcher.inspect).to eq "/http:\\/\\/.*\\/thing/"
136
+ end
137
+
138
+ it "it logs the rules it has ignored" do
139
+ expect($stderr).to receive(:puts) do | message |
140
+ expect(message).to match /ignored.*"somerule"/
141
+ expect(message).to_not match /regex/
142
+ expect(message).to_not match /"match"/
143
+ end
144
+ subject
145
+ end
146
+ end
147
+
148
+ describe "with an array" do
149
+
150
+ let(:expected) do
151
+ {
152
+ "_links" => {
153
+ "self" => [{
154
+ "href" => "http://localhost:1234/thing"
155
+ }]
156
+ }
157
+ }
158
+ end
159
+
160
+ let(:matching_rules) do
161
+ {
162
+ "$._links.self[0].href" => {
163
+ "matchers" => [{ "regex" => "http:\\/\\/.*\\/thing" }]
164
+ }
165
+ }
166
+ end
167
+
168
+ it "creates a Pact::Term at the appropriate path" do
169
+ expect(subject["_links"]["self"][0]["href"]).to be_instance_of(Pact::Term)
170
+ expect(subject["_links"]["self"][0]["href"].generate).to eq "http://localhost:1234/thing"
171
+ expect(subject["_links"]["self"][0]["href"].matcher.inspect).to eq "/http:\\/\\/.*\\/thing/"
172
+ end
173
+ end
174
+
175
+ describe "with an array where all elements should match by type and the rule is specified on the parent element and there is no min specified" do
176
+ let(:expected) do
177
+ {
178
+ 'alligators' => [{'name' => 'Mary'}]
179
+ }
180
+ end
181
+
182
+ let(:matching_rules) do
183
+ {
184
+ "$.alligators" => {
185
+ "matchers" => [{ 'match' => 'type' }]
186
+ }
187
+ }
188
+ end
189
+
190
+ it "creates a Pact::SomethingLike at the appropriate path" do
191
+ expect(subject["alligators"]).to be_instance_of(Pact::SomethingLike)
192
+ expect(subject["alligators"].contents).to eq ['name' => 'Mary']
193
+ end
194
+ end
195
+
196
+ describe "with an array where all elements should match by type and the rule is specified on the child elements" do
197
+ let(:expected) do
198
+ {
199
+ 'alligators' => [{'name' => 'Mary'}]
200
+ }
201
+ end
202
+
203
+ let(:matching_rules) do
204
+ {
205
+ "$.alligators" => {
206
+ "matchers" => [{ 'min' => 2, 'match' => 'type' }]
207
+ },
208
+ "$.alligators[*].*" => {
209
+ "matchers" => [{ 'match' => 'type'}]
210
+ }
211
+ }
212
+ end
213
+ it "creates a Pact::ArrayLike at the appropriate path" do
214
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
215
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
216
+ expect(subject["alligators"].min).to eq 2
217
+ end
218
+ end
219
+
220
+ describe "with an array where all elements should match by type and the rule is specified on both the parent element and the child elements" do
221
+ let(:expected) do
222
+ {
223
+ 'alligators' => [{'name' => 'Mary'}]
224
+ }
225
+ end
226
+
227
+ let(:matching_rules) do
228
+ {
229
+ "$.alligators" => {
230
+ "matchers" => [{ 'min' => 2, 'match' => 'type' }]
231
+ },
232
+ "$.alligators[*].*" => {
233
+ "matchers" => [{ 'match' => 'type' }]
234
+ }
235
+ }
236
+ end
237
+
238
+ it "creates a Pact::ArrayLike at the appropriate path" do
239
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
240
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
241
+ expect(subject["alligators"].min).to eq 2
242
+ end
243
+ end
244
+
245
+ describe "with an array where all elements should match by type and there is only a match:type on the parent element" do
246
+ let(:expected) do
247
+ {
248
+ 'alligators' => [{'name' => 'Mary'}]
249
+ }
250
+ end
251
+
252
+ let(:matching_rules) do
253
+ {
254
+ "$.alligators" => { 'matchers' => [{'min' => 2, 'match' => 'type'}] },
255
+ }
256
+ end
257
+
258
+ it "creates a Pact::ArrayLike at the appropriate path" do
259
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
260
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
261
+ expect(subject["alligators"].min).to eq 2
262
+ end
263
+ end
264
+
265
+ describe "with an array where all elements should match by type nested inside another array where all elements should match by type" do
266
+ let(:expected) do
267
+ {
268
+
269
+ 'alligators' => [
270
+ {
271
+ 'name' => 'Mary',
272
+ 'children' => [
273
+ 'age' => 9
274
+ ]
275
+ }
276
+ ]
277
+
278
+ }
279
+ end
280
+
281
+ let(:matching_rules) do
282
+ {
283
+ "$.alligators" => { "matchers" => [{ 'min' => 2, 'match' => 'type' }] },
284
+ "$.alligators[*].children" => { "matchers" => [{ 'min' => 1, 'match' => 'type' }]},
285
+ }
286
+ end
287
+
288
+ it "creates a Pact::ArrayLike at the appropriate path" do
289
+ expect(subject["alligators"].contents['children']).to be_instance_of(Pact::ArrayLike)
290
+ expect(subject["alligators"].contents['children'].contents).to eq 'age' => 9
291
+ expect(subject["alligators"].contents['children'].min).to eq 1
292
+ end
293
+ end
294
+
295
+ describe "with an example array with more than one item" do
296
+ before do
297
+ allow($stderr).to receive(:puts)
298
+ end
299
+
300
+ let(:expected) do
301
+ {
302
+
303
+ 'alligators' => [
304
+ {'name' => 'Mary'},
305
+ {'name' => 'Joe'}
306
+ ]
307
+
308
+ }
309
+ end
310
+
311
+ let(:matching_rules) do
312
+ {
313
+ "$.alligators" => { "matchers" => [{'min' => 2, 'match' => 'type'}] }
314
+ }
315
+ end
316
+
317
+ it "doesn't warn about the min size being ignored" do
318
+ expect(Pact.configuration.error_stream).to receive(:puts).once
319
+ subject
320
+ end
321
+
322
+ it "warns that the other items will be ignored" do
323
+ allow(Pact.configuration.error_stream).to receive(:puts)
324
+ expect(Pact.configuration.error_stream).to receive(:puts).with(/WARN: Only the first item/)
325
+ subject
326
+ end
327
+ end
328
+ end
329
+
330
+ describe "using bracket notation for a Hash" do
331
+ let(:expected) do
332
+ {
333
+ "name" => "Mary"
334
+ }
335
+ end
336
+
337
+ let(:matching_rules) do
338
+ {
339
+ "$['name']" => { "matchers" => [{"match" => "type"}] }
340
+ }
341
+ end
342
+
343
+ it "applies the rule" do
344
+ expect(subject['name']).to be_instance_of(Pact::SomethingLike)
345
+ end
346
+ end
347
+
348
+ describe "with a dot in the path" do
349
+ let(:expected) do
350
+ {
351
+ "first.name" => "Mary"
352
+ }
353
+ end
354
+
355
+ let(:matching_rules) do
356
+ {
357
+ "$['first.name']" => { "matchers" => [{ "match" => "type" }] }
358
+ }
359
+ end
360
+
361
+ it "applies the rule" do
362
+ expect(subject['first.name']).to be_instance_of(Pact::SomethingLike)
363
+ end
364
+ end
365
+
366
+ describe "with an @ in the path" do
367
+ let(:expected) do
368
+ {
369
+ "@name" => "Mary"
370
+ }
371
+ end
372
+
373
+ let(:matching_rules) do
374
+ {
375
+ "$['@name']" => { "matchers" => [ { "match" => "type" }] }
376
+ }
377
+ end
378
+
379
+ it "applies the rule" do
380
+ expect(subject['@name']).to be_instance_of(Pact::SomethingLike)
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
386
+ end
@@ -0,0 +1,82 @@
1
+ require 'pact/matching_rules'
2
+
3
+ module Pact
4
+ module MatchingRules
5
+ describe ".merge" do
6
+ before do
7
+ allow(V3::Merge).to receive(:call)
8
+ allow(Merge).to receive(:call)
9
+ allow(Pact.configuration.error_stream).to receive(:puts)
10
+ end
11
+
12
+ let(:object) { double('object') }
13
+ let(:rules) { double('rules') }
14
+ let(:options) { { pact_specification_version: Pact::SpecificationVersion.new(pact_specification_version) } }
15
+
16
+ subject { MatchingRules.merge(object, rules, options)}
17
+
18
+ context "when the pact_specification_version is nil" do
19
+ let(:options) { { pact_specification_version: nil } }
20
+
21
+ it "calls Merge" do
22
+ expect(Merge).to receive(:call)
23
+ subject
24
+ end
25
+ end
26
+
27
+ context "when the pact_specification_version starts with '1.'" do
28
+ let(:pact_specification_version) { "1.0" }
29
+
30
+ it "calls Merge" do
31
+ expect(Merge).to receive(:call)
32
+ subject
33
+ end
34
+ end
35
+
36
+ context "when the pact_specification_version is with '1'" do
37
+ let(:pact_specification_version) { "1" }
38
+
39
+ it "calls Merge" do
40
+ expect(Merge).to receive(:call)
41
+ subject
42
+ end
43
+ end
44
+
45
+ context "when the pact_specification_version starts with '2.'" do
46
+ let(:pact_specification_version) { "2.0" }
47
+
48
+ it "calls Merge" do
49
+ expect(Merge).to receive(:call)
50
+ subject
51
+ end
52
+ end
53
+
54
+ context "when the pact_specification_version starts with '3.'" do
55
+ let(:pact_specification_version) { "3.0" }
56
+
57
+ it "calls V3::Merge" do
58
+ expect(V3::Merge).to receive(:call)
59
+ subject
60
+ end
61
+ end
62
+
63
+ context "when the pact_specification_version starts with '4.'" do
64
+ let(:pact_specification_version) { "4.0" }
65
+
66
+ it "calls V3::Merge" do
67
+ expect(V3::Merge).to receive(:call)
68
+ subject
69
+ end
70
+ end
71
+
72
+ context "when the pact_specification_version is with '11'" do
73
+ let(:pact_specification_version) { "11" }
74
+
75
+ it "calls V3::Merge" do
76
+ expect(V3::Merge).to receive(:call)
77
+ subject
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -122,10 +122,9 @@ module Pact
122
122
  end
123
123
 
124
124
  context "when Hash Query with UTF-8 string" do
125
-
126
125
  subject { Reification.from_term(query)}
127
126
 
128
- let(:query) { QueryHash.new( {param: 'ILove', extra: '寿司'})}
127
+ let(:query) { QueryHash.new(param: 'ILove', extra: '寿司') }
129
128
 
130
129
  it "returns the hash with escaping UTF-8 string" do
131
130
  expect(subject).to eq("param=ILove&extra=%E5%AF%BF%E5%8F%B8")
@@ -133,7 +132,6 @@ module Pact
133
132
  end
134
133
 
135
134
  context "when Hash Query with embeded terms" do
136
-
137
135
  subject { Reification.from_term(query)}
138
136
 
139
137
  let(:query) { QueryHash.new( {param: 'hello', extra: Pact::Term.new(generate: "wonderworld", matcher: /\w+world/)})}
@@ -141,10 +139,9 @@ module Pact
141
139
  it "returns the hash in the natural order, and fills in Terms appropriately" do
142
140
  expect(subject).to eq("param=hello&extra=wonderworld")
143
141
  end
144
-
145
142
  end
146
- context "when Hash Query with Arrays and multiple params with the same name" do
147
143
 
144
+ context "when Hash Query with Arrays and multiple params with the same name" do
148
145
  subject { Reification.from_term(query)}
149
146
 
150
147
  let(:query) { QueryHash.new( {param: 'hello', double: [Pact::Term.new(generate: "wonder", matcher: /\w+/), 'world'], simple: 'bye'})}
@@ -152,8 +149,24 @@ module Pact
152
149
  it "returns the hash in the natural order, and fills in Terms appropriately" do
153
150
  expect(subject).to eq("param=hello&double=wonder&double=world&simple=bye")
154
151
  end
152
+ end
155
153
 
154
+ context "when Hash Query with an ArrayLike" do
155
+ subject { Reification.from_term(query)}
156
+
157
+ let(:query) { QueryHash.new(param: Pact.each_like("1", min: 2)) }
158
+
159
+ it "turns the hash into a string with the right number of params" do
160
+ expect(subject).to eq("param=1&param=1")
161
+ end
156
162
  end
157
163
 
164
+ context "with a StringWithMatchingRules" do
165
+ subject { Reification.from_term(StringWithMatchingRules.new("foo", Pact::SpecificationVersion.new("3"), {}))}
166
+
167
+ it "returns a String" do
168
+ expect(subject.class).to be String
169
+ end
170
+ end
158
171
  end
159
172
  end
@@ -0,0 +1,39 @@
1
+ require 'pact/shared/multipart_form_differ'
2
+
3
+ module Pact
4
+ describe MultipartFormDiffer do
5
+
6
+ describe ".call" do
7
+
8
+ let(:expected_body) do
9
+ "-------------RubyMultipartPost-1e4912957c7bb64de3c444568326663b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"text.txt\"\r\nContent-Length: 14\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\n\r\nThis is a file\r\n-------------RubyMultipartPost-1e4912957c7bb64de3c444568326663b--\r\n\r\n"
10
+ end
11
+
12
+ let(:actual_body) do
13
+ "-------------RubyMultipartPost-1e4912957c7bb64de3c4445683266XXX\r\nContent-Disposition: form-data; name=\"file\"; filename=\"text.txt\"\r\nContent-Length: 14\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\n\r\nThis is a file\r\n-------------RubyMultipartPost-1e4912957c7bb64de3c4445683266XXX--\r\n\r\n"
14
+ end
15
+
16
+ let(:options) do
17
+ {}
18
+ end
19
+
20
+ subject { MultipartFormDiffer.call(expected_body, actual_body, options) }
21
+
22
+ context "when the bodies are the same apart from the boundary" do
23
+ it "returns an empty diff" do
24
+ expect(subject).to eq({})
25
+ end
26
+ end
27
+
28
+ context "when the bodies are not the same" do
29
+ let(:actual_body) do
30
+ "-------------RubyMultipartPost-1e4912957c7bb64de3c4445683266XXX\r\nContent-Disposition: form-data; name=\"file\"; filename=\"bar.txt\"\r\nContent-Length: 14\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: binary\r\n\r\nThis is a file\r\n-------------RubyMultipartPost-1e4912957c7bb64de3c4445683266XXX--\r\n\r\n"
31
+ end
32
+
33
+ it "returns a text diff" do
34
+ expect(subject).to_not eq({})
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
data/tasks/spec.rake CHANGED
@@ -12,4 +12,3 @@ task :spec_with_active_support => [:set_active_support_on] do
12
12
  end
13
13
 
14
14
  task :default => [:spec, :spec_with_active_support]
15
-