pact 1.2.1.rc2 → 1.3.3

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 (57) hide show
  1. data/.travis.yml +1 -1
  2. data/CHANGELOG.md +31 -0
  3. data/Gemfile.lock +6 -6
  4. data/README.md +48 -38
  5. data/documentation/README.md +9 -7
  6. data/lib/pact/configuration.rb +83 -12
  7. data/lib/pact/consumer/consumer_contract_builder.rb +1 -49
  8. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +5 -1
  9. data/lib/pact/consumer/spec_hooks.rb +7 -4
  10. data/lib/pact/consumer/world.rb +12 -0
  11. data/lib/pact/consumer_contract/headers.rb +51 -0
  12. data/lib/pact/consumer_contract/interaction.rb +1 -1
  13. data/lib/pact/consumer_contract/pact_file.rb +2 -2
  14. data/lib/pact/consumer_contract/request.rb +6 -1
  15. data/lib/pact/matchers/differ.rb +10 -7
  16. data/lib/pact/matchers/unix_diff_formatter.rb +8 -1
  17. data/lib/pact/provider/configuration/service_provider_dsl.rb +6 -1
  18. data/lib/pact/provider/matchers/messages.rb +2 -2
  19. data/lib/pact/provider/pact_spec_runner.rb +9 -1
  20. data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
  21. data/lib/pact/provider/rspec/matchers.rb +9 -6
  22. data/lib/pact/provider/rspec.rb +7 -1
  23. data/lib/pact/reification.rb +5 -5
  24. data/lib/pact/shared/json_differ.rb +15 -0
  25. data/lib/pact/shared/request.rb +11 -2
  26. data/lib/pact/shared/text_differ.rb +14 -0
  27. data/lib/pact/version.rb +1 -1
  28. data/pact.gemspec +1 -1
  29. data/spec/lib/pact/configuration_spec.rb +127 -5
  30. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +8 -5
  31. data/spec/lib/pact/consumer_contract/headers_spec.rb +107 -0
  32. data/spec/lib/pact/consumer_contract/interaction_spec.rb +14 -0
  33. data/spec/lib/pact/consumer_contract/request_spec.rb +9 -0
  34. data/spec/lib/pact/matchers/matchers_spec.rb +35 -0
  35. data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +9 -8
  36. data/spec/lib/pact/provider/configuration/service_provider_dsl_spec.rb +16 -0
  37. data/spec/lib/pact/provider/matchers/messages_spec.rb +5 -4
  38. data/spec/lib/pact/provider/rspec_spec.rb +15 -11
  39. data/spec/lib/pact/reification_spec.rb +12 -0
  40. data/spec/lib/pact/shared/json_differ_spec.rb +36 -0
  41. data/spec/lib/pact/shared/request_spec.rb +25 -1
  42. data/spec/lib/pact/shared/text_differ_spec.rb +54 -0
  43. data/spec/support/options.json +21 -0
  44. data/spec/support/options_app.rb +15 -0
  45. data/spec/support/pact_helper.rb +2 -0
  46. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  47. data/tasks/pact-test.rake +10 -0
  48. metadata +26 -16
  49. data/documentation/Testing with pact.png +0 -0
  50. data/documentation/best-practices.md +0 -33
  51. data/documentation/development-workflow.md +0 -22
  52. data/documentation/faq.md +0 -81
  53. data/documentation/provider-states.md +0 -167
  54. data/documentation/raq.md +0 -39
  55. data/documentation/terminology.md +0 -25
  56. data/documentation/troubleshooting.md +0 -4
  57. data/documentation/verifying-pacts.md +0 -106
data/.travis.yml CHANGED
@@ -5,4 +5,4 @@ rvm:
5
5
  - 2.1.1
6
6
  - jruby-19mode
7
7
  - jruby-1.7.11
8
- - jruby-head
8
+ - jruby-1.7.12
data/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@ Do this to generate your change history
2
2
 
3
3
  git log --pretty=format:' * %h - %s (%an, %ad)'
4
4
 
5
+ ### 1.3.3 (23 September 2014)
6
+
7
+ * 9106aac - Fixed reification when using FactoryGirl. (bethesque, Tue Sep 23 08:23:51 2014 +1000)
8
+ * 2182803 - Added "query" to example. (bethesque, Fri Sep 19 14:29:36 2014 +1000)
9
+ * 0002014 - Added key to explain - and + in unix diff output. (bethesque, Mon Sep 8 15:21:37 2014 +1000)
10
+ * 34f1c9b - Adding forward compatibility for reading 'providerState' from pact. (bethesque, Mon Aug 25 16:09:57 2014 +1000)
11
+
12
+ ### 1.3.2 (20 August 2014)
13
+
14
+ * 65e1e23 - Renamed memoised :options to :diff_options because it clashes with the HTTP options method (bethesque, Wed Aug 20 09:31:0
15
+ * ca883a8 - Made options optional for ConsumerContactBuilder.wait_for_interactions (bethesque, Mon Aug 18 15:53:47 2014 +1000)
16
+
17
+ ### 1.3.1 (11 August 2014)
18
+
19
+ * 3432259 - Fixed 'pact:verify broken with rspec-core 3.0.3' https://github.com/realestate-com-au/pact/issues/44 (bethesque, Mon Aug 11 10:14:42 2014 +1000)
20
+ * e2e8eff - Deleted documentation that has been moved to the wiki (bethesque, Thu Jul 24 15:20:07 2014 +1000)
21
+ * bcc3143 - Fixing bug 'Method case should not matter when matching requests' https://github.com/realestate-com-au/pact/issues/41 (bethesque, Tue Jul 22 16:51:48 2014 +1000)
22
+ * d4bfab9 - Adding ability to configure DiffFormatter based on content-type (bethesque, Mon Jun 23 21:22:47 2014 +1000)
23
+ * eb330ea - Ensured content-type header works in a case insensitive way when looking up the right differ (bethesque, Mon Jun 23 17:23:04 2014 +1000)
24
+ * 2733e8e - Made header matching case insensitive for requests. Fixing issue https://github.com/realestate-com-au/pact/issues/20 (bethesque, Mon May 26 19:15:48 2014 +1000)
25
+ * 2b8355d - Added nicer error message for scenario when a service provider app has not been configured, and there is no config.ru (bethesque, Mon Jun 23 09:42:18 2014 +1000)
26
+ * 1e774bb - Defaulting to TextDiffer if response has no content-type (bethesque, Sat Jun 21 10:34:44 2014 +1000)
27
+ * 863b093 - Added support for documents without content types (bethesque, Sat Jun 21 10:32:08 2014 +1000)
28
+ * b21900b - Enabling differs to be configured based on Content-Type (bethesque, Sat Jun 21 10:21:37 2014 +1000)
29
+ * 527b5d5 - Modified after hook to only write pacts when one or more 'pact => true' examples have run (bethesque, Wed Jun 18 15:15:55 2014 +1000)
30
+
31
+ ### 1.3.0 (18 June 2014)
32
+
33
+ * ea79190 - Modifying (cough*monkeypatching*cough) RSpec::Core::BacktraceFormatter as RSpec3 has some hardcoded exclusion patterns that result in *all* the backtrace lines being shown when pact:verify fails. (bethesque, Wed Jun 18 13:02:38 2014 +1000)
34
+ * 0fd2bb2 - Support objects that return valid URI strings through to_s (Gabriel Rotbart, Fri Jun 13 14:34:03 2014 +1000)
35
+
5
36
  ### 1.2.1.rc2 (13 June 2014)
6
37
 
7
38
  * d805f35 - Ensuring the pact RSpec formatter works for both rspec 2 and rspec 3 (Beth, Fri Jun 13 16:27:01 2014 +1000)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pact (1.2.1.rc2)
4
+ pact (1.3.3)
5
5
  awesome_print (~> 1.1)
6
6
  find_a_port (~> 1.0.1)
7
7
  json
@@ -50,21 +50,21 @@ GEM
50
50
  rspec-core (~> 3.0.0)
51
51
  rspec-expectations (~> 3.0.0)
52
52
  rspec-mocks (~> 3.0.0)
53
- rspec-core (3.0.0)
53
+ rspec-core (3.0.4)
54
54
  rspec-support (~> 3.0.0)
55
- rspec-expectations (3.0.0)
55
+ rspec-expectations (3.0.4)
56
56
  diff-lcs (>= 1.2.0, < 2.0)
57
57
  rspec-support (~> 3.0.0)
58
- rspec-mocks (3.0.1)
58
+ rspec-mocks (3.0.4)
59
59
  rspec-support (~> 3.0.0)
60
- rspec-support (3.0.0)
60
+ rspec-support (3.0.4)
61
61
  safe_yaml (1.0.3)
62
62
  slop (3.5.0)
63
63
  term-ansicolor (1.3.0)
64
64
  tins (~> 1.0)
65
65
  thor (0.19.1)
66
66
  thread_safe (0.3.4)
67
- tins (1.3.0)
67
+ tins (1.3.2)
68
68
  tzinfo (1.2.1)
69
69
  thread_safe (~> 0.1)
70
70
  webmock (1.18.0)
data/README.md CHANGED
@@ -2,32 +2,39 @@
2
2
 
3
3
  Define a pact between service consumers and providers, enabling "consumer driven contract" testing.
4
4
 
5
- Pact provides an RSpec DSL for service consumers to define the HTTP requests they will make to a service provider and the HTTP responses they expect back. These expectations are used in the consumers specs to provide a mock service provider. The interactions are recorded, and played back in the service provider specs to ensure the service provider actually does provide the response the consumer expects.
5
+ Pact provides an fluent API for service consumers to define the HTTP requests they will make to a service provider and the HTTP responses they expect back. These expectations are used in the consumer specs to provide a mock service provider. The interactions are recorded, and played back in the service provider specs to ensure the service provider actually does provide the response the consumer expects.
6
6
 
7
7
  This allows testing of both sides of an integration point using fast unit tests.
8
8
 
9
- This gem is inspired by the concept of "Consumer driven contracts". See http://martinfowler.com/articles/consumerDrivenContracts.html for more information.
9
+ This gem is inspired by the concept of "Consumer driven contracts". See [this article](http://martinfowler.com/articles/consumerDrivenContracts.html) by Martin Fowler for more information.
10
10
 
11
11
  Travis CI Status: [![travis-ci.org Build Status](https://travis-ci.org/realestate-com-au/pact.png)](https://travis-ci.org/realestate-com-au/pact)
12
12
 
13
13
  ## What is it good for?
14
14
 
15
- Pact is most valuable for designing and testing integrations where you (or your team/organisation/partner organisation) control the development of both the consumer and the provider. It is fantastic tool for intra-organsation microservices.
15
+ Pact is most valuable for designing and testing integrations where you (or your team/organisation/partner organisation) control the development of both the consumer and the provider. It is fantastic tool for testing intra-organsation microservices.
16
+
17
+ ## What is it not good for?
18
+
19
+ * Performance and load testing.
20
+ * Functional testing of the provider - that is what the provider's own tests should do. Pact is about checking the contents and format of requests and responses.
21
+ * Situations where you cannot load data into the provider without using the API that you're actually testing.
16
22
 
17
23
  ## Features
18
24
 
19
25
  * A service is mocked using an actual process running on a specified port, so javascript clients can be tested as easily as backend clients.
20
26
  * "Provider states" (similar to fixtures) allow the same request to be made with a different expected response.
21
27
  * Consumers specify only the fields they are interested in, allowing a provider to return more fields without breaking the pact. This allows a provider to have a different pact with a different consumer, and know which fields each cares about in a given response.
28
+ * RSpec and Minitest support for the service consumer codebase.
22
29
  * Rake tasks allow pacts to be verified against a service provider codebase.
23
30
  * Different versions of a consumer/provider pairs can be easily tested against each other, allowing confidence when deploying new versions of each (see the pact_broker and pact_broker-client gems).
24
31
  * Autogenerated API documentation - need we say more?
25
- * Coming soon - autogenerated network diagrams with the [Pact Broker](https://github.com/bethesque/pact_broker)
32
+ * Autogenerated network diagrams with the [Pact Broker](https://github.com/bethesque/pact_broker)
26
33
 
27
34
  ## How does it work?
28
35
 
29
36
  1. In the specs for the provider facing code in the consumer project, expectations are set up on a mock service provider.
30
- 1. When the specs are run, the requests, and their expected responses, are written to a "pact" file.
37
+ 1. When the specs are run, the mock service returns the expected responses. The requests, and their expected responses, are then written to a "pact" file.
31
38
  1. The requests in the pact file are later replayed against the provider, and the actual responses are checked to make sure they match the expected responses.
32
39
 
33
40
  ## Why is developing and testing with Pact better than using traditional system integration tests?
@@ -37,6 +44,7 @@ Pact is most valuable for designing and testing integrations where you (or your
37
44
  * Causes of failure are easier to identify as only one component is being tested at a time.
38
45
  * Design of service provider is improved by considering first how the data is actually going to be used, rather than how it is most easily retrieved and serialised.
39
46
  * No separate integration environment required for automated integration tests - pact tests run in standalone CI builds.
47
+ * Integration flows that would traditionally require running multiple services at the same time can be broken down and each integration point tested separately.
40
48
 
41
49
  ## Contact
42
50
 
@@ -74,6 +82,8 @@ end
74
82
  Imagine a service provider client class that looks something like this.
75
83
 
76
84
  ```ruby
85
+ require 'httparty'
86
+
77
87
  class MyServiceProviderClient
78
88
  include HTTParty
79
89
  base_uri 'http://my-service'
@@ -92,6 +102,7 @@ The following code will create a mock service on localhost:1234 which will respo
92
102
  # In /spec/service_providers/pact_helper.rb
93
103
 
94
104
  require 'pact/consumer/rspec'
105
+ # or require 'pact/consumer/minitest' if you are using Minitest
95
106
 
96
107
  Pact.service_consumer "My Service Consumer" do
97
108
  has_pact_with "My Service Provider" do
@@ -107,7 +118,9 @@ end
107
118
  ```ruby
108
119
  # In /spec/service_providers/my_service_provider_client_spec.rb
109
120
 
110
- # Use the :pact => true describe metadata to include all the pact generation functionality in your spec.
121
+ # When using RSpec, use the metadata `:pact => true` to include all the pact functionality in your spec.
122
+ # When using Minitest, include Pact::Consumer::Minitest in your spec.
123
+
111
124
  describe MyServiceProviderClient, :pact => true do
112
125
 
113
126
  before do
@@ -121,7 +134,7 @@ describe MyServiceProviderClient, :pact => true do
121
134
 
122
135
  before do
123
136
  my_service_provider.given("something exists").
124
- upon_receiving("a request for something").with(method: :get, path: '/something').
137
+ upon_receiving("a request for something").with(method: :get, path: '/something', query: '').
125
138
  will_respond_with(
126
139
  status: 200,
127
140
  headers: {'Content-Type' => 'application/json'},
@@ -160,14 +173,18 @@ end
160
173
 
161
174
  #### 7. Run the specs again.
162
175
 
163
- Green! You now have a pact file that can be used to verify your expectations in the provider project.
164
- Now, rinse and repeat for ALL the likely status codes that may be returned (we recommend 404, 500 and 401/403 if there is authorisation.)
176
+ Green! You now have a pact file that can be used to verify your expectations of the provider project.
177
+
178
+ Now, rinse and repeat for other likely status codes that may be returned. For example, consider how you want your client to respond to a:
179
+ * 404 (return null, or raise an error?)
180
+ * 500 (specifying that the response body should contain an error message, and ensuring that your client logs that error message will make your life much easier when things go wrong)
181
+ * 401/403 if there is authorisation.
165
182
 
166
183
  ### Service Provider project
167
184
 
168
185
  #### 1. Create the skeleton API classes
169
186
 
170
- Create your API class using the framework of your choice - leave the methods unimplemented, we're doing Test First Develoment, remember?
187
+ Create your API class using the framework of your choice (the Pact authors have a preference for [Webmachine][webmachine] and [Roar][roar]) - leave the methods unimplemented, we're doing Test First Develoment, remember?
171
188
 
172
189
  #### 2. Tell your provider that it needs to honour the pact file you made earlier
173
190
 
@@ -180,7 +197,7 @@ require 'pact/tasks'
180
197
 
181
198
  Create a `pact_helper.rb` in your service provider project. The recommended place is `spec/service_consumers/pact_helper.rb`.
182
199
 
183
- See [Verifying Pacts](documentation/verifying-pacts.md) and the [Provider](documentation/configuration.md#provider) section of the Configuration documentation for more information.
200
+ See [Verifying Pacts](https://github.com/realestate-com-au/pact/wiki/Verifying-pacts) and the [Provider](documentation/configuration.md#provider) section of the Configuration documentation for more information.
184
201
 
185
202
  ```ruby
186
203
  # In specs/service_consumers/pact_helper.rb
@@ -220,29 +237,8 @@ Yay! Your provider now honours the pact it has with your consumer. You can now h
220
237
 
221
238
  ### Using provider states
222
239
 
223
- Provider states allow you to set up data on the provider before the interaction is run, so that it can make a response that matches what the consumer expects. It also allows the consumer to make the same request with different expected responses.
240
+ Each interaction in a pact is verified in isolation, with no context maintained from the previous interactions. So how do you test a request that requires data to already exist on the provider? Read about provider states [here](https://github.com/realestate-com-au/pact/wiki/Provider-states).
224
241
 
225
- Read more about provider states [here](/documentation/provider-states.md).
226
-
227
- ### Verifying pacts
228
-
229
- You can verify a pact at an arbitrary local or remote URL
230
-
231
- $ rake pact:verify:at[../path-to-your-consumer-project/specs/pacts/my_consumer-my_provider.json]
232
- $ rake pact:verify:at[http://build-box/MyConsumerBuild/latestSuccessful/artifacts/my_consumer-my_provider.json]
233
-
234
- To make a shortcut task for pact at an arbitrary URL, add the following to your Rakefile. The pact.uri may be a local file system path or a remote URL.
235
-
236
- ```ruby
237
- # In Rakefile or /tasks/pact.rake
238
-
239
- # This creates a rake task that can be executed by running
240
- # $ rake pact:verify:dev
241
-
242
- Pact::VerificationTask.new(:dev) do | pact |
243
- pact.uri '../path-to-your-consumer-project/specs/pacts/my_consumer-my_provider.json'
244
- end
245
- ```
246
242
 
247
243
  ## Configuration
248
244
 
@@ -250,15 +246,21 @@ See the [Configuration](/documentation/configuration.md) section of the document
250
246
 
251
247
  ## Pact best practices
252
248
 
253
- As in all things, there are good ways to implement Pacts, and there are not so good ways. Check out the [Best practices](/documentation/best-practices.md) section of the documentation to make sure you're not Pacting it Wrong.
249
+ As in all things, there are good ways to implement Pacts, and there are not so good ways. Check out the [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices) section of the documentation to make sure you're not Pacting it Wrong.
254
250
 
255
251
  ## Docs
256
252
 
257
253
  * [Example](example)
258
- * [Frequently Asked Questions](documentation/faq.md)
259
- * [Rarely Asked Questions](documentation/raq.md)
260
- * [Terminology](documentation/terminology.md)
261
- * [Configuration](/documentation/configuration.md)
254
+ * [Configuration](documentation/configuration.md)
255
+ * [Terminology](https://github.com/realestate-com-au/pact/wiki/Terminology)
256
+ * [Provider States](https://github.com/realestate-com-au/pact/wiki/Provider-states)
257
+ * [Verifying pacts](https://github.com/realestate-com-au/pact/wiki/Verifying-pacts)
258
+ * [Sharing pacts between consumer and provider](https://github.com/realestate-com-au/pact/wiki/Sharing-pacts-between-consumer-and-provider)
259
+ * [Frequently asked questions](https://github.com/realestate-com-au/pact/wiki/FAQ)
260
+ * [Rarely asked questions](https://github.com/realestate-com-au/pact/wiki/RAQ)
261
+ * [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices)
262
+ * [Troubleshooting](https://github.com/realestate-com-au/pact/wiki/Troubleshooting)
263
+ * [Testing with pact diagram](https://github.com/realestate-com-au/pact/wiki/Testing with pact.png)
262
264
 
263
265
  ## Related libraries
264
266
 
@@ -270,6 +272,8 @@ As in all things, there are good ways to implement Pacts, and there are not so g
270
272
 
271
273
  [Pact JVM](https://github.com/DiUS/pact-jvm) - A Pact implementation for the JVM (Java and Scala). It generates pact files that are compatible with the Ruby implementation.
272
274
 
275
+ [Pact .NET](https://github.com/SEEK-Jobs/pact-net) - A Pact implementation for .NET.
276
+
273
277
  [Shokkenki](https://github.com/brentsnook/shokkenki) - Another Consumer Driven Contract gem written by one of Pact's original authors, Brent Snook. Shokkenki allows matchers to be composed using jsonpath expressions and allows auto-generation of mock response values based on regular expressions.
274
278
 
275
279
  ## Links
@@ -301,3 +305,9 @@ Long term:
301
305
  3. Commit your changes (`git commit -am 'Add some feature'`)
302
306
  4. Push to the branch (`git push origin my-new-feature`)
303
307
  5. Create new Pull Request
308
+
309
+ If you would like to implement pact in another language, please check out the [Pact specification](https://github.com/bethesque/pact-specification) and have a chat to one of us on the [pact-dev Google group](https://groups.google.com/forum/#!forum/pact-dev). The vision is to have a compatible pact implementation in all the commonly used languages, your help would be greatly appreciated!
310
+
311
+ [webmachine]: https://github.com/seancribbs/webmachine-ruby
312
+ [roar]: https://github.com/apotonick/roar
313
+
@@ -1,11 +1,13 @@
1
1
  ### Pact Documentation
2
2
 
3
3
  * Step by step instructions for getting started with pacts can be found in the project [README.md](/README.md#usage)
4
- * [Terminology](terminology.md)
4
+ * [Terminology](https://github.com/realestate-com-au/pact/wiki/Terminology)
5
5
  * [Configuration](configuration.md)
6
- * [Provider States](provider-states.md)
7
- * [Frequently asked questions](faq.md)
8
- * [Rarely asked questions](raq.md)
9
- * [Best practices](best-practices.md)
10
- * [Troubleshooting](troubleshooting.md)
11
- * [Testing with pact diagram](Testing with pact.png)
6
+ * [Provider States](https://github.com/realestate-com-au/pact/wiki/Provider-states)
7
+ * [Verifying pacts](https://github.com/realestate-com-au/pact/wiki/Verifying-pacts)
8
+ * [Frequently asked questions](https://github.com/realestate-com-au/pact/wiki/FAQ)
9
+ * [Rarely asked questions](https://github.com/realestate-com-au/pact/wiki/RAQ)
10
+ * [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices)
11
+ * [Troubleshooting](https://github.com/realestate-com-au/pact/wiki/Troubleshooting)
12
+ * [Testing with pact diagram](https://github.com/realestate-com-au/pact/wiki/Testing with pact.png)
13
+ * [Development workflow](https://github.com/realestate-com-au/pact/wiki/Development-workflow)
@@ -4,6 +4,8 @@ require 'pact/doc/markdown/generator'
4
4
  require 'pact/matchers/unix_diff_formatter'
5
5
  require 'pact/matchers/embedded_diff_formatter'
6
6
  require 'pact/matchers/list_diff_formatter'
7
+ require 'pact/shared/json_differ'
8
+ require 'pact/shared/text_differ'
7
9
 
8
10
  module Pact
9
11
 
@@ -17,6 +19,27 @@ module Pact
17
19
  :list => Pact::Matchers::ListDiffFormatter
18
20
  }
19
21
 
22
+
23
+ class NilMatcher
24
+ def self.=~ other
25
+ other == nil ? 0 : nil
26
+ end
27
+ end
28
+
29
+ DIFF_FORMATTER_REGISTRATIONS = [
30
+ [/.*/, Pact::Matchers::UnixDiffFormatter],
31
+ [NilMatcher, Pact::Matchers::UnixDiffFormatter]
32
+ ]
33
+
34
+ DIFFERS = [
35
+ [/json/, Pact::JsonDiffer],
36
+ [NilMatcher, Pact::TextDiffer],
37
+ [/.*/, Pact::TextDiffer]
38
+ ]
39
+
40
+
41
+ DEFAULT_DIFFER = Pact::TextDiffer
42
+
20
43
  attr_accessor :pact_dir
21
44
  attr_accessor :log_dir
22
45
  attr_accessor :doc_dir
@@ -41,6 +64,11 @@ module Pact
41
64
  c
42
65
  end
43
66
 
67
+ def initialize
68
+ @differ_registrations = []
69
+ @diff_formatter_registrations = []
70
+ end
71
+
44
72
  def logger
45
73
  @logger ||= create_logger
46
74
  end
@@ -61,20 +89,29 @@ module Pact
61
89
  @doc_generators ||= []
62
90
  end
63
91
 
64
- def diff_formatter
65
- @diff_formatter ||= DIFF_FORMATTERS[:unix]
92
+ # Should this be deprecated in favour of register_diff_formatter???
93
+ def diff_formatter= diff_formatter
94
+ register_diff_formatter /.*/, diff_formatter
95
+ register_diff_formatter nil, diff_formatter
66
96
  end
67
97
 
68
- def diff_formatter= diff_formatter
69
- @diff_formatter = begin
70
- if DIFF_FORMATTERS[diff_formatter]
71
- DIFF_FORMATTERS[diff_formatter]
72
- elsif diff_formatter.respond_to?(:call)
73
- diff_formatter
74
- else
75
- raise "Pact.configuration.diff_formatter needs to respond to call, or be in the preconfigured list: #{DIFF_FORMATTERS.keys}"
76
- end
77
- end
98
+ def register_diff_formatter content_type, diff_formatter
99
+ key = content_type_regexp_for content_type
100
+ @diff_formatter_registrations << [key, diff_formatter_for(diff_formatter)]
101
+ end
102
+
103
+ def diff_formatter_for_content_type content_type
104
+ diff_formatter_registrations.find{ | registration | registration.first =~ content_type }.last
105
+ end
106
+
107
+ def register_body_differ content_type, differ
108
+ key = content_type_regexp_for content_type
109
+ validate_differ differ
110
+ @differ_registrations << [key, differ]
111
+ end
112
+
113
+ def body_differ_for_content_type content_type
114
+ differ_registrations.find{ | registration | registration.first =~ content_type }.last
78
115
  end
79
116
 
80
117
  def log_path
@@ -91,6 +128,40 @@ module Pact
91
128
 
92
129
  private
93
130
 
131
+ def diff_formatter_for input
132
+ if DIFF_FORMATTERS[input]
133
+ DIFF_FORMATTERS[input]
134
+ elsif input.respond_to?(:call)
135
+ input
136
+ else
137
+ raise "Pact diff_formatter needs to respond to call, or be in the preconfigured list: #{DIFF_FORMATTERS.keys}"
138
+ end
139
+ end
140
+
141
+ def validate_differ differ
142
+ if !differ.respond_to?(:call)
143
+ raise "Pact.configuration.register_body_differ expects a differ that is a lamda or a class/object that responds to call."
144
+ end
145
+ end
146
+
147
+ def content_type_regexp_for content_type
148
+ case content_type
149
+ when String then Regexp.new(/^#{Regexp.escape(content_type)}$/)
150
+ when Regexp then content_type
151
+ when nil then NilMatcher
152
+ else
153
+ raise "Invalid content type used to register a differ (#{content_type.inspect}). Please use a Regexp or a String."
154
+ end
155
+ end
156
+
157
+ def differ_registrations
158
+ @differ_registrations + DIFFERS
159
+ end
160
+
161
+ def diff_formatter_registrations
162
+ @diff_formatter_registrations + DIFF_FORMATTER_REGISTRATIONS
163
+ end
164
+
94
165
  def self.default_log_dir
95
166
  File.expand_path("./log")
96
167
  end
@@ -55,7 +55,7 @@ module Pact
55
55
  mock_service_client.write_pact @consumer_contract_details
56
56
  end
57
57
 
58
- def wait_for_interactions options
58
+ def wait_for_interactions options = {}
59
59
  wait_max_seconds = options.fetch(:wait_max_seconds, 3)
60
60
  poll_interval = options.fetch(:poll_interval, 0.1)
61
61
  mock_service_client.wait_for_interactions wait_max_seconds, poll_interval
@@ -71,54 +71,6 @@ module Pact
71
71
  attr_reader :mock_service_client
72
72
  attr_writer :interaction_builder
73
73
 
74
- # def interactions_for_new_consumer_contract pactfile_write_mode
75
- # pactfile_write_mode == :update ? existing_interactions : []
76
- # end
77
-
78
- # def filter interactions, pactfile_write_mode
79
- # if pactfile_write_mode == :update
80
- # UpdatableInteractionsFilter.new(interactions)
81
- # else
82
- # DistinctInteractionsFilter.new(interactions)
83
- # end
84
- # end
85
-
86
- # def warn_and_stderr msg
87
- # Pact.configuration.error_stream.puts msg
88
- # logger.warn msg
89
- # end
90
-
91
- # def info_and_puts msg
92
- # $stdout.puts msg
93
- # logger.info msg
94
- # end
95
-
96
- # def existing_interactions
97
- # interactions = []
98
- # if pactfile_exists?
99
- # begin
100
- # interactions = existing_consumer_contract.interactions
101
- # info_and_puts "*****************************************************************************"
102
- # info_and_puts "Updating existing file .#{consumer_contract.pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
103
- # info_and_puts "Only interactions defined in this test run will be updated."
104
- # info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
105
- # info_and_puts "*****************************************************************************"
106
- # rescue StandardError => e
107
- # warn_and_stderr "Could not load existing consumer contract from #{consumer_contract.pactfile_path} due to #{e}"
108
- # warn_and_stderr "Creating a new file."
109
- # end
110
- # end
111
- # interactions
112
- # end
113
-
114
- # def pactfile_exists?
115
- # File.exist?(consumer_contract.pactfile_path)
116
- # end
117
-
118
- # def existing_consumer_contract
119
- # Pact::ConsumerContract.from_uri(consumer_contract.pactfile_path)
120
- # end
121
-
122
74
  end
123
75
  end
124
76
  end
@@ -56,10 +56,14 @@ module Pact
56
56
  def to_s
57
57
  [
58
58
  "Diff with interaction: #{candidate_interaction.description_with_provider_state_quoted}",
59
- Pact.configuration.diff_formatter.call(diff, {colour: false})
59
+ diff_formatter.call(diff, {colour: false})
60
60
  ].join("\n")
61
61
  end
62
62
 
63
+ def diff_formatter
64
+ Pact.configuration.diff_formatter_for_content_type(candidate_interaction.request.content_type)
65
+ end
66
+
63
67
  def diff
64
68
  @diff ||= candidate_interaction.request.difference(actual_request)
65
69
  end
@@ -11,6 +11,7 @@ module Pact
11
11
  end
12
12
 
13
13
  def before_each example_description
14
+ Pact.consumer_world.register_pact_example_ran
14
15
  Pact.configuration.logger.info "Clearing all expectations"
15
16
  Pact::Consumer::AppManager.instance.ports_of_mock_services.each do | port |
16
17
  Pact::Consumer::MockServiceClient.clear_interactions port, example_description
@@ -25,10 +26,12 @@ module Pact
25
26
  end
26
27
 
27
28
  def after_suite
28
- Pact.consumer_world.consumer_contract_builders.each { | c | c.write_pact }
29
- Pact::Doc::Generate.call
30
- Pact::Consumer::AppManager.instance.kill_all
31
- Pact::Consumer::AppManager.instance.clear_all
29
+ if Pact.consumer_world.any_pact_examples_ran?
30
+ Pact.consumer_world.consumer_contract_builders.each { | c | c.write_pact }
31
+ Pact::Doc::Generate.call
32
+ Pact::Consumer::AppManager.instance.kill_all
33
+ Pact::Consumer::AppManager.instance.clear_all
34
+ end
32
35
  end
33
36
  end
34
37
  end
@@ -12,6 +12,10 @@ module Pact
12
12
  module Consumer
13
13
  class World
14
14
 
15
+ def initialize
16
+ @any_pact_examples_ran = false
17
+ end
18
+
15
19
  def consumer_contract_builders
16
20
  @consumer_contract_builders ||= []
17
21
  end
@@ -20,6 +24,14 @@ module Pact
20
24
  consumer_contract_builders << consumer_contract_builder
21
25
  end
22
26
 
27
+ def register_pact_example_ran
28
+ @any_pact_examples_ran = true
29
+ end
30
+
31
+ def any_pact_examples_ran?
32
+ @any_pact_examples_ran
33
+ end
34
+
23
35
  end
24
36
  end
25
37
  end
@@ -0,0 +1,51 @@
1
+ module Pact
2
+
3
+ class DuplicateHeaderError < StandardError; end
4
+ class InvalidHeaderNameTypeError < StandardError; end
5
+
6
+ class Headers < Hash
7
+
8
+ def initialize hash = {}
9
+ hash.each_pair do | key, value |
10
+ check_for_invalid key
11
+ self[find_matching_key(key)] = value
12
+ end
13
+ self.freeze
14
+ end
15
+
16
+ def [] key
17
+ super(find_matching_key(key))
18
+ end
19
+
20
+ def fetch *args, &block
21
+ args[0] = find_matching_key(args[0]) if args.first
22
+ super(*args, &block)
23
+ end
24
+
25
+ def key? key
26
+ super(find_matching_key(key))
27
+ end
28
+
29
+ alias_method :has_key?, :key?
30
+ alias_method :include?, :key?
31
+
32
+ private
33
+
34
+ def find_matching_key key
35
+ key = key.to_s
36
+ match = keys.find { |k| k.downcase == key.downcase }
37
+ match.nil? ? key : match
38
+ end
39
+
40
+ def check_for_invalid key
41
+ unless (String === key || Symbol === key)
42
+ raise InvalidHeaderNameTypeError.new "Header name (#{key}) must be a String or a Symbol."
43
+ end
44
+ if key? key
45
+ raise DuplicateHeaderError.new "Duplicate header found (#{find_matching_key(key)} and #{key}. Please use a comma separated single value when multiple headers with the same name are required."
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -13,7 +13,7 @@ module Pact
13
13
  @description = attributes[:description]
14
14
  @request = attributes[:request]
15
15
  @response = attributes[:response]
16
- @provider_state = attributes[:provider_state]
16
+ @provider_state = attributes[:provider_state] || attributes[:providerState]
17
17
  end
18
18
 
19
19
  def self.from_hash hash
@@ -5,9 +5,9 @@ module Pact
5
5
  extend self
6
6
 
7
7
  def read uri, options = {}
8
- pact = open(uri) { | file | file.read }
8
+ pact = open(uri.to_s) { | file | file.read }
9
9
  if options[:save_pactfile_to_tmp]
10
- save_pactfile_to_tmp pact, ::File.basename(uri)
10
+ save_pactfile_to_tmp pact, ::File.basename(uri.to_s)
11
11
  end
12
12
  pact
13
13
  rescue StandardError => e
@@ -60,8 +60,13 @@ module Pact
60
60
  end
61
61
 
62
62
  def body_difference(actual_body)
63
- diff({:body => body}, {body: actual_body}, allow_unexpected_keys: runtime_options[:allow_unexpected_keys_in_body])
63
+ body_differ.call({:body => body}, {body: actual_body}, allow_unexpected_keys: runtime_options[:allow_unexpected_keys_in_body])
64
64
  end
65
+
66
+ def body_differ
67
+ Pact.configuration.body_differ_for_content_type content_type
68
+ end
69
+
65
70
  end
66
71
 
67
72
  end