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.
- data/.travis.yml +1 -1
- data/CHANGELOG.md +31 -0
- data/Gemfile.lock +6 -6
- data/README.md +48 -38
- data/documentation/README.md +9 -7
- data/lib/pact/configuration.rb +83 -12
- data/lib/pact/consumer/consumer_contract_builder.rb +1 -49
- data/lib/pact/consumer/mock_service/interaction_mismatch.rb +5 -1
- data/lib/pact/consumer/spec_hooks.rb +7 -4
- data/lib/pact/consumer/world.rb +12 -0
- data/lib/pact/consumer_contract/headers.rb +51 -0
- data/lib/pact/consumer_contract/interaction.rb +1 -1
- data/lib/pact/consumer_contract/pact_file.rb +2 -2
- data/lib/pact/consumer_contract/request.rb +6 -1
- data/lib/pact/matchers/differ.rb +10 -7
- data/lib/pact/matchers/unix_diff_formatter.rb +8 -1
- data/lib/pact/provider/configuration/service_provider_dsl.rb +6 -1
- data/lib/pact/provider/matchers/messages.rb +2 -2
- data/lib/pact/provider/pact_spec_runner.rb +9 -1
- data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
- data/lib/pact/provider/rspec/matchers.rb +9 -6
- data/lib/pact/provider/rspec.rb +7 -1
- data/lib/pact/reification.rb +5 -5
- data/lib/pact/shared/json_differ.rb +15 -0
- data/lib/pact/shared/request.rb +11 -2
- data/lib/pact/shared/text_differ.rb +14 -0
- data/lib/pact/version.rb +1 -1
- data/pact.gemspec +1 -1
- data/spec/lib/pact/configuration_spec.rb +127 -5
- data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +8 -5
- data/spec/lib/pact/consumer_contract/headers_spec.rb +107 -0
- data/spec/lib/pact/consumer_contract/interaction_spec.rb +14 -0
- data/spec/lib/pact/consumer_contract/request_spec.rb +9 -0
- data/spec/lib/pact/matchers/matchers_spec.rb +35 -0
- data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +9 -8
- data/spec/lib/pact/provider/configuration/service_provider_dsl_spec.rb +16 -0
- data/spec/lib/pact/provider/matchers/messages_spec.rb +5 -4
- data/spec/lib/pact/provider/rspec_spec.rb +15 -11
- data/spec/lib/pact/reification_spec.rb +12 -0
- data/spec/lib/pact/shared/json_differ_spec.rb +36 -0
- data/spec/lib/pact/shared/request_spec.rb +25 -1
- data/spec/lib/pact/shared/text_differ_spec.rb +54 -0
- data/spec/support/options.json +21 -0
- data/spec/support/options_app.rb +15 -0
- data/spec/support/pact_helper.rb +2 -0
- data/spec/support/test_app_with_right_content_type_differ.json +23 -0
- data/tasks/pact-test.rake +10 -0
- metadata +26 -16
- data/documentation/Testing with pact.png +0 -0
- data/documentation/best-practices.md +0 -33
- data/documentation/development-workflow.md +0 -22
- data/documentation/faq.md +0 -81
- data/documentation/provider-states.md +0 -167
- data/documentation/raq.md +0 -39
- data/documentation/terminology.md +0 -25
- data/documentation/troubleshooting.md +0 -4
- data/documentation/verifying-pacts.md +0 -106
data/.travis.yml
CHANGED
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.
|
|
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.
|
|
53
|
+
rspec-core (3.0.4)
|
|
54
54
|
rspec-support (~> 3.0.0)
|
|
55
|
-
rspec-expectations (3.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.
|
|
58
|
+
rspec-mocks (3.0.4)
|
|
59
59
|
rspec-support (~> 3.0.0)
|
|
60
|
-
rspec-support (3.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.
|
|
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
|
|
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: [](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
|
-
*
|
|
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
|
-
#
|
|
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
|
|
164
|
-
|
|
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](
|
|
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
|
-
|
|
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](/
|
|
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
|
-
* [
|
|
259
|
-
* [
|
|
260
|
-
* [
|
|
261
|
-
* [
|
|
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
|
+
|
data/documentation/README.md
CHANGED
|
@@ -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](
|
|
4
|
+
* [Terminology](https://github.com/realestate-com-au/pact/wiki/Terminology)
|
|
5
5
|
* [Configuration](configuration.md)
|
|
6
|
-
* [Provider States](
|
|
7
|
-
* [
|
|
8
|
-
* [
|
|
9
|
-
* [
|
|
10
|
-
* [
|
|
11
|
-
* [
|
|
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)
|
data/lib/pact/configuration.rb
CHANGED
|
@@ -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
|
-
|
|
65
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
data/lib/pact/consumer/world.rb
CHANGED
|
@@ -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
|
-
|
|
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
|