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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -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 +57 -56
  7. data/lib/pact/consumer_contract/interaction_parser.rb +23 -0
  8. data/lib/pact/consumer_contract/interaction_v2_parser.rb +34 -0
  9. data/lib/pact/consumer_contract/interaction_v3_parser.rb +73 -0
  10. data/lib/pact/consumer_contract/pact_file.rb +24 -24
  11. data/lib/pact/consumer_contract/provider_state.rb +34 -0
  12. data/lib/pact/consumer_contract/query.rb +0 -2
  13. data/lib/pact/consumer_contract/query_hash.rb +6 -0
  14. data/lib/pact/consumer_contract/query_string.rb +2 -2
  15. data/lib/pact/consumer_contract/request.rb +1 -5
  16. data/lib/pact/consumer_contract/string_with_matching_rules.rb +17 -0
  17. data/lib/pact/matchers/multipart_form_diff_formatter.rb +41 -0
  18. data/lib/pact/matching_rules/merge.rb +43 -28
  19. data/lib/pact/matching_rules/v3/extract.rb +94 -0
  20. data/lib/pact/matching_rules/v3/merge.rb +135 -0
  21. data/lib/pact/matching_rules.rb +19 -6
  22. data/lib/pact/reification.rb +6 -3
  23. data/lib/pact/shared/multipart_form_differ.rb +14 -0
  24. data/lib/pact/specification_version.rb +18 -0
  25. data/lib/pact/support/version.rb +1 -1
  26. data/script/release.sh +1 -1
  27. data/spec/fixtures/multipart-form-diff.txt +9 -0
  28. data/spec/fixtures/not-a-pact.json +3 -0
  29. data/spec/fixtures/pact-http-v2.json +36 -0
  30. data/spec/fixtures/pact-http-v3.json +36 -0
  31. data/spec/integration/matching_rules_extract_and_merge_spec.rb +41 -4
  32. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +54 -31
  33. data/spec/lib/pact/consumer_contract/http_consumer_contract_parser_spec.rb +25 -0
  34. data/spec/lib/pact/consumer_contract/interaction_parser_spec.rb +62 -0
  35. data/spec/lib/pact/consumer_contract/interaction_spec.rb +2 -26
  36. data/spec/lib/pact/consumer_contract/interaction_v2_parser_spec.rb +54 -0
  37. data/spec/lib/pact/consumer_contract/interaction_v3_parser_spec.rb +48 -0
  38. data/spec/lib/pact/consumer_contract/pact_file_spec.rb +9 -0
  39. data/spec/lib/pact/consumer_contract/query_hash_spec.rb +23 -0
  40. data/spec/lib/pact/matchers/multipart_form_diff_formatter_spec.rb +36 -0
  41. data/spec/lib/pact/matching_rules/merge_spec.rb +198 -110
  42. data/spec/lib/pact/matching_rules/v3/extract_spec.rb +238 -0
  43. data/spec/lib/pact/matching_rules/v3/merge_spec.rb +462 -0
  44. data/spec/lib/pact/matching_rules_spec.rb +82 -0
  45. data/spec/lib/pact/reification_spec.rb +18 -5
  46. data/spec/lib/pact/shared/multipart_form_differ_spec.rb +39 -0
  47. data/spec/support/factories.rb +5 -0
  48. data/tasks/spec.rake +0 -1
  49. metadata +40 -3
@@ -7,10 +7,16 @@ module Pact
7
7
  subject { Merge.(expected, matching_rules, "$.body") }
8
8
 
9
9
  before do
10
- allow($stderr).to receive(:puts)
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 call to log_used_rule in Merge."
12
+ end
11
13
  end
12
14
 
13
15
  describe "no recognised rules" do
16
+ before do
17
+ allow($stderr).to receive(:puts)
18
+ end
19
+
14
20
  let(:expected) do
15
21
  {
16
22
  "_links" => {
@@ -63,6 +69,10 @@ module Pact
63
69
  end
64
70
 
65
71
  describe "type based matching" do
72
+ before do
73
+ allow($stderr).to receive(:puts).and_call_original
74
+ end
75
+
66
76
  let(:expected) do
67
77
  {
68
78
  "name" => "Mary"
@@ -80,7 +90,7 @@ module Pact
80
90
  end
81
91
 
82
92
  it "it logs the rules it has ignored" do
83
- expect($stderr).to receive(:puts).with(/ignored.*matchingrule/)
93
+ expect($stderr).to receive(:puts).once.with(/ignored.*matchingrule/)
84
94
  subject
85
95
  end
86
96
 
@@ -89,6 +99,10 @@ module Pact
89
99
  describe "regular expressions" do
90
100
 
91
101
  describe "in a hash" do
102
+ before do
103
+ allow($stderr).to receive(:puts)
104
+ end
105
+
92
106
  let(:expected) do
93
107
  {
94
108
  "_links" => {
@@ -146,150 +160,169 @@ module Pact
146
160
  end
147
161
  end
148
162
 
149
- 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
163
+ describe "with an ArrayLike containing a Term" do
150
164
  let(:expected) do
151
- {
152
- 'alligators' => [{'name' => 'Mary'}]
153
- }
165
+ ["foo"]
154
166
  end
155
167
 
156
168
  let(:matching_rules) do
157
169
  {
158
- "$.body.alligators" => { 'match' => 'type' }
170
+ "$.body" => {"min" => 1},
171
+ "$.body[*].*" => {"match" => "type"},
172
+ "$.body[*]" => {"match" => "regex", "regex"=>"f"}
159
173
  }
160
174
  end
161
175
 
162
- it "creates a Pact::SomethingLike at the appropriate path" do
163
- expect(subject["alligators"]).to be_instance_of(Pact::SomethingLike)
164
- expect(subject["alligators"].contents).to eq ['name' => 'Mary']
176
+ it "it creates an ArrayLike with a Pact::Term as the contents" do
177
+ expect(subject).to be_a(Pact::ArrayLike)
178
+ expect(subject.contents).to be_a(Pact::Term)
165
179
  end
166
180
  end
181
+ end
167
182
 
168
- describe "with an array where all elements should match by type and the rule is specified on the child elements" do
169
- let(:expected) do
170
- {
171
- 'alligators' => [{'name' => 'Mary'}]
172
- }
173
- end
183
+ 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
184
+ let(:expected) do
185
+ {
186
+ 'alligators' => [{'name' => 'Mary'}]
187
+ }
188
+ end
174
189
 
175
- let(:matching_rules) do
176
- {
177
- "$.body.alligators" => { 'min' => 2 },
178
- "$.body.alligators[*].*" => { 'match' => 'type'}
179
- }
180
- end
181
- it "creates a Pact::ArrayLike at the appropriate path" do
182
- expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
183
- expect(subject["alligators"].contents).to eq 'name' => 'Mary'
184
- expect(subject["alligators"].min).to eq 2
185
- end
190
+ let(:matching_rules) do
191
+ {
192
+ "$.body.alligators" => { 'match' => 'type' }
193
+ }
186
194
  end
187
195
 
188
- 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
189
- let(:expected) do
190
- {
191
- 'alligators' => [{'name' => 'Mary'}]
192
- }
193
- end
196
+ it "creates a Pact::SomethingLike at the appropriate path" do
197
+ expect(subject["alligators"]).to be_instance_of(Pact::SomethingLike)
198
+ expect(subject["alligators"].contents).to eq ['name' => 'Mary']
199
+ end
200
+ end
194
201
 
195
- let(:matching_rules) do
196
- {
197
- "$.body.alligators" => { 'min' => 2, 'match' => 'type' },
198
- "$.body.alligators[*].*" => { 'match' => 'type'}
199
- }
200
- end
202
+ describe "with an array where all elements should match by type and the rule is specified on the child elements" do
203
+ let(:expected) do
204
+ {
205
+ 'alligators' => [{'name' => 'Mary'}]
206
+ }
207
+ end
201
208
 
202
- it "creates a Pact::ArrayLike at the appropriate path" do
203
- expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
204
- expect(subject["alligators"].contents).to eq 'name' => 'Mary'
205
- expect(subject["alligators"].min).to eq 2
206
- end
209
+ let(:matching_rules) do
210
+ {
211
+ "$.body.alligators" => { 'min' => 2 },
212
+ "$.body.alligators[*].*" => { 'match' => 'type'}
213
+ }
207
214
  end
208
215
 
209
- describe "with an array where all elements should match by type and there is only a match:type on the parent element" do
210
- let(:expected) do
211
- {
212
- 'alligators' => [{'name' => 'Mary'}]
213
- }
214
- end
216
+ it "creates a Pact::ArrayLike at the appropriate path" do
217
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
218
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
219
+ expect(subject["alligators"].min).to eq 2
220
+ end
221
+ end
215
222
 
216
- let(:matching_rules) do
217
- {
218
- "$.body.alligators" => { 'min' => 2, 'match' => 'type' },
219
- }
220
- end
223
+ 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
224
+ let(:expected) do
225
+ {
226
+ 'alligators' => [{'name' => 'Mary'}]
227
+ }
228
+ end
221
229
 
222
- it "creates a Pact::ArrayLike at the appropriate path" do
223
- expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
224
- expect(subject["alligators"].contents).to eq 'name' => 'Mary'
225
- expect(subject["alligators"].min).to eq 2
226
- end
230
+ let(:matching_rules) do
231
+ {
232
+ "$.body.alligators" => { 'min' => 2, 'match' => 'type' },
233
+ "$.body.alligators[*].*" => { 'match' => 'type'}
234
+ }
227
235
  end
228
236
 
229
- describe "with an array where all elements should match by type nested inside another array where all elements should match by type" do
230
- let(:expected) do
231
- {
237
+ it "creates a Pact::ArrayLike at the appropriate path" do
238
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
239
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
240
+ expect(subject["alligators"].min).to eq 2
241
+ end
242
+ end
232
243
 
233
- 'alligators' => [
234
- {
235
- 'name' => 'Mary',
236
- 'children' => [
237
- 'age' => 9
238
- ]
239
- }
240
- ]
244
+ describe "with an array where all elements should match by type and there is only a match:type on the parent element" do
245
+ let(:expected) do
246
+ {
247
+ 'alligators' => [{'name' => 'Mary'}]
248
+ }
249
+ end
241
250
 
242
- }
243
- end
251
+ let(:matching_rules) do
252
+ {
253
+ "$.body.alligators" => { 'min' => 2, 'match' => 'type' },
254
+ }
255
+ end
244
256
 
245
- let(:matching_rules) do
246
- {
247
- "$.body.alligators" => { 'min' => 2 },
248
- "$.body.alligators[*].*" => { 'match' => 'type'},
249
- "$.body.alligators[*].children" => { 'min' => 1 },
250
- "$.body.alligators[*].children[*].*" => { 'match' => 'type'}
251
- }
252
- end
257
+ it "creates a Pact::ArrayLike at the appropriate path" do
258
+ expect(subject["alligators"]).to be_instance_of(Pact::ArrayLike)
259
+ expect(subject["alligators"].contents).to eq 'name' => 'Mary'
260
+ expect(subject["alligators"].min).to eq 2
261
+ end
262
+ end
253
263
 
254
- it "creates a Pact::ArrayLike at the appropriate path" do
255
- expect(subject["alligators"].contents['children']).to be_instance_of(Pact::ArrayLike)
256
- expect(subject["alligators"].contents['children'].contents).to eq 'age' => 9
257
- expect(subject["alligators"].contents['children'].min).to eq 1
258
- end
264
+ describe "with an array where all elements should match by type nested inside another array where all elements should match by type" do
265
+ let(:expected) do
266
+ {
267
+
268
+ 'alligators' => [
269
+ {
270
+ 'name' => 'Mary',
271
+ 'children' => [
272
+ 'age' => 9
273
+ ]
274
+ }
275
+ ]
276
+
277
+ }
259
278
  end
260
279
 
261
- describe "with an example array with more than one item" do
262
- let(:expected) do
263
- {
280
+ let(:matching_rules) do
281
+ {
282
+ "$.body.alligators" => { 'min' => 2 },
283
+ "$.body.alligators[*].*" => { 'match' => 'type'},
284
+ "$.body.alligators[*].children" => { 'min' => 1 },
285
+ "$.body.alligators[*].children[*].*" => { 'match' => 'type'}
286
+ }
287
+ end
264
288
 
265
- 'alligators' => [
266
- {'name' => 'Mary'},
267
- {'name' => 'Joe'}
268
- ]
289
+ it "creates a Pact::ArrayLike at the appropriate path" do
290
+ expect(subject["alligators"].contents['children']).to be_instance_of(Pact::ArrayLike)
291
+ expect(subject["alligators"].contents['children'].contents).to eq 'age' => 9
292
+ expect(subject["alligators"].contents['children'].min).to eq 1
293
+ end
294
+ end
269
295
 
270
- }
271
- end
296
+ describe "with an example array with more than one item" do
297
+ before do
298
+ allow(Pact.configuration.error_stream).to receive(:puts)
299
+ end
272
300
 
273
- let(:matching_rules) do
274
- {
275
- "$.body.alligators" => { 'min' => 2 },
276
- "$.body.alligators[*].*" => { 'match' => 'type'}
277
- }
278
- end
301
+ let(:expected) do
302
+ {
279
303
 
280
- xit "doesn't warn about the min size being ignored" do
281
- expect(Pact.configuration.error_stream).to receive(:puts).once
282
- subject
283
- end
304
+ 'alligators' => [
305
+ {'name' => 'Mary'},
306
+ {'name' => 'Joe'}
307
+ ]
284
308
 
285
- it "warns that the other items will be ignored" do
286
- allow(Pact.configuration.error_stream).to receive(:puts)
287
- expect(Pact.configuration.error_stream).to receive(:puts).with(/WARN: Only the first item/)
288
- subject
289
- end
309
+ }
310
+ end
311
+
312
+ let(:matching_rules) do
313
+ {
314
+ "$.body.alligators" => { 'min' => 2 },
315
+ "$.body.alligators[*].*" => { 'match' => 'type'}
316
+ }
317
+ end
318
+
319
+ it "warns that the other items will be ignored" do
320
+ expect(Pact.configuration.error_stream).to receive(:puts).with(/WARN: Only the first item/)
321
+ subject
290
322
  end
291
323
  end
292
324
 
325
+
293
326
  describe "using bracket notation for a Hash" do
294
327
  let(:expected) do
295
328
  {
@@ -343,6 +376,61 @@ module Pact
343
376
  expect(subject['@name']).to be_instance_of(Pact::SomethingLike)
344
377
  end
345
378
  end
379
+
380
+ describe "when a Pact.like is nested inside a Pact.each_like which is nested inside a Pact.like" do
381
+ let(:original_definition) do
382
+ Pact.like('foos' => Pact.each_like(Pact.like('name' => "foo1")))
383
+ end
384
+
385
+ let(:expected) do
386
+ Pact::Reification.from_term(original_definition)
387
+ end
388
+
389
+ let(:matching_rules) do
390
+ Extract.call(body: original_definition)
391
+ end
392
+
393
+ it "creates a Pact::SomethingLike containing a Pact::ArrayLike containing a Pact::SomethingLike" do
394
+ expect(subject.to_hash).to eq original_definition.to_hash
395
+ end
396
+ end
397
+
398
+ describe "when a Pact.array_like is the top level object" do
399
+ let(:original_definition) do
400
+ Pact.each_like('foos')
401
+ end
402
+
403
+ let(:expected) do
404
+ Pact::Reification.from_term(original_definition)
405
+ end
406
+
407
+ let(:matching_rules) do
408
+ Extract.call(body: original_definition)
409
+ end
410
+
411
+ it "creates a Pact::ArrayLike" do
412
+ expect(subject.to_hash).to eq original_definition.to_hash
413
+ end
414
+ end
415
+
416
+ describe "when a Pact.like containing an array is the top level object" do
417
+ let(:original_definition) do
418
+ Pact.like(['foos'])
419
+ end
420
+
421
+ let(:expected) do
422
+ Pact::Reification.from_term(original_definition)
423
+ end
424
+
425
+ let(:matching_rules) do
426
+ Extract.call(body: original_definition)
427
+ end
428
+
429
+ it "creates a Pact::SomethingLike" do
430
+ expect(subject).to be_a(Pact::SomethingLike)
431
+ expect(subject.to_hash).to eq original_definition.to_hash
432
+ end
433
+ end
346
434
  end
347
435
  end
348
436
  end
@@ -0,0 +1,238 @@
1
+ require 'pact/matching_rules/v3/extract'
2
+ require 'pact/support'
3
+
4
+ module Pact
5
+ module MatchingRules::V3
6
+ describe Extract do
7
+
8
+ describe ".call" do
9
+
10
+ subject { Extract.call(matchable) }
11
+
12
+ context "with a Pact::SomethingLike" do
13
+ let(:matchable) do
14
+ {
15
+ body: Pact::SomethingLike.new(foo: 'bar', alligator: { name: 'Mary' })
16
+ }
17
+ end
18
+
19
+ let(:rules) do
20
+ {
21
+ "$.body" => {
22
+ "matchers" => [ {"match" => "type"} ]
23
+ }
24
+ }
25
+ end
26
+
27
+ it "creates a rule that matches by type" do
28
+ expect(subject).to eq rules
29
+ end
30
+ end
31
+
32
+ context "with a Pact::Term" do
33
+ let(:matchable) do
34
+ {
35
+ body: {
36
+ alligator: {
37
+ name: Pact::Term.new(generate: 'Mary', matcher: /.*a/)
38
+ }
39
+ }
40
+ }
41
+ end
42
+
43
+ let(:rules) do
44
+ {
45
+ "$.body.alligator.name" => {
46
+ "matchers" => [ {"match" => "regex", "regex" => ".*a"} ]
47
+ }
48
+ }
49
+ end
50
+
51
+ it "creates a rule that matches by regex" do
52
+ expect(subject).to eq rules
53
+ end
54
+ end
55
+
56
+ context "with a Pact::SomethingLike containing a Term" do
57
+ let(:matchable) do
58
+ {
59
+ body: Pact::SomethingLike.new(
60
+ foo: 'bar',
61
+ alligator: { name: Pact::Term.new(generate: 'Mary', matcher: /.*a/) }
62
+ )
63
+ }
64
+ end
65
+
66
+ let(:rules) do
67
+ {
68
+ "$.body" => {
69
+ "matchers" => [ {"match" => "type"} ]
70
+ },
71
+ "$.body.alligator.name" => {
72
+ "matchers" => [ {"match" => "regex", "regex"=>".*a"} ]
73
+ },
74
+ }
75
+ end
76
+
77
+ it "the match:regex overrides the match:type" do
78
+ expect(subject).to eq rules
79
+ end
80
+ end
81
+
82
+ context "with a Pact::SomethingLike containing an array" do
83
+ let(:matchable) do
84
+ {
85
+ body: Pact::SomethingLike.new(
86
+ alligators: [
87
+ {name: 'Mary'},
88
+ {name: 'Betty'}
89
+ ]
90
+ )
91
+ }
92
+ end
93
+
94
+ let(:rules) do
95
+ {
96
+ "$.body" => {
97
+ "matchers" => [ {"match" => "type"} ]
98
+ }
99
+ }
100
+ end
101
+
102
+ it "lists a rule for each item" do
103
+ expect(subject).to eq rules
104
+ end
105
+ end
106
+
107
+ context "with an ArrayLike" do
108
+ let(:matchable) do
109
+ {
110
+ body: {
111
+ alligators: Pact::ArrayLike.new(
112
+ name: 'Fred'
113
+ )
114
+ }
115
+ }
116
+ end
117
+
118
+ let(:rules) do
119
+ {
120
+ "$.body.alligators" => {
121
+ "matchers" => [ {"min" => 1} ]
122
+ },
123
+ "$.body.alligators[*].*" => {
124
+ "matchers" => [ {"match" => "type"} ]
125
+ }
126
+ }
127
+ end
128
+
129
+ it "lists a rule for all items" do
130
+ expect(subject).to eq rules
131
+ end
132
+ end
133
+
134
+ context "with an ArrayLike with a Pact::Term inside" do
135
+ let(:matchable) do
136
+ {
137
+ body: {
138
+ alligators: Pact::ArrayLike.new(
139
+ name: 'Fred',
140
+ phoneNumber: Pact::Term.new(generate: '1234567', matcher: /\d+/)
141
+ )
142
+ }
143
+ }
144
+ end
145
+
146
+ let(:rules) do
147
+ {
148
+ "$.body.alligators" => {
149
+ "matchers" => [ {"min" => 1} ]
150
+ },
151
+ "$.body.alligators[*].*" => {
152
+ "matchers" => [ {"match" => "type"} ]
153
+ },
154
+ "$.body.alligators[*].phoneNumber" => {
155
+ "matchers" => [ {"match" => "regex", "regex" => "\\d+"} ]
156
+ }
157
+ }
158
+ end
159
+
160
+ it "lists a rule that specifies that the regular expression must match" do
161
+ expect(subject).to eq rules
162
+ end
163
+ end
164
+
165
+ context "with a Pact::QueryString containing a Pact::Term" do
166
+ let(:matchable) do
167
+ {
168
+ query: Pact::QueryString.new(Pact::Term.new(generate: 'foobar', matcher: /foo/))
169
+ }
170
+ end
171
+
172
+ let(:rules) do
173
+ {
174
+ "$.query" => {
175
+ "matchers" => [ {"match" => "regex", "regex" => "foo"} ]
176
+ }
177
+ }
178
+ end
179
+
180
+ it "lists a rule that specifies that the regular expression must match" do
181
+ expect(subject).to eq rules
182
+ end
183
+ end
184
+
185
+ context "with a Pact::QueryHash containing a Pact::Term" do
186
+ let(:matchable) do
187
+ {
188
+ query: Pact::QueryHash.new(bar: Pact::Term.new(generate: 'foobar', matcher: /foo/))
189
+ }
190
+ end
191
+
192
+ let(:rules) do
193
+ {
194
+ "$.query.bar[0]" => {
195
+ "matchers" => [ {"match" => "regex", "regex" => "foo"} ]
196
+ }
197
+ }
198
+ end
199
+
200
+ it "lists a rule that specifies that the regular expression must match" do
201
+ expect(subject).to eq rules
202
+ end
203
+ end
204
+
205
+ context "with no special matching" do
206
+ let(:matchable) do
207
+ {
208
+ body: { alligator: { name: 'Mary' } }
209
+ }
210
+ end
211
+
212
+ let(:rules) do
213
+ {}
214
+ end
215
+
216
+
217
+ it "does not create any rules" do
218
+ expect(subject).to eq rules
219
+ end
220
+ end
221
+
222
+ context "with a key containing a dot" do
223
+ let(:matchable) do
224
+ {
225
+ "key" => {
226
+ "key.with.dots" => Pact::SomethingLike.new("foo")
227
+ }
228
+ }
229
+ end
230
+
231
+ it "uses square brackets notation for the key with dots" do
232
+ expect(subject.keys).to include "$.key['key.with.dots']"
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end