pact 1.1.1 → 1.2.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +42 -39
- data/README.md +2 -0
- data/documentation/configuration.md +2 -2
- data/documentation/faq.md +5 -7
- data/documentation/provider-states.md +2 -10
- data/example/animal-service/spec/service_consumers/pact_helper.rb +0 -4
- data/lib/pact/consumer/configuration/mock_service.rb +2 -0
- data/lib/pact/consumer/consumer_contract_builder.rb +58 -58
- data/lib/pact/consumer/mock_service/app.rb +4 -1
- data/lib/pact/consumer/mock_service/interaction_replay.rb +11 -3
- data/lib/pact/consumer/mock_service/missing_interactions_get.rb +2 -2
- data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
- data/lib/pact/consumer/mock_service/verification_get.rb +1 -2
- data/lib/pact/consumer/mock_service_client.rb +14 -5
- data/lib/pact/consumer/mock_service_interaction_expectation.rb +1 -1
- data/lib/pact/consumer/spec_hooks.rb +2 -0
- data/lib/pact/consumer/world.rb +25 -0
- data/lib/pact/consumer_contract/consumer_contract.rb +1 -1
- data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
- data/lib/pact/consumer_contract/file_name.rb +7 -1
- data/lib/pact/provider/pact_helper_locator.rb +1 -1
- data/lib/pact/provider/pact_spec_runner.rb +3 -9
- data/lib/pact/provider/rspec/{formatter.rb → formatter_rspec_2.rb} +2 -2
- data/lib/pact/provider/rspec/formatter_rspec_3.rb +96 -0
- data/lib/pact/provider/rspec/matchers.rb +79 -19
- data/lib/pact/provider/rspec.rb +3 -1
- data/lib/pact/provider/state/provider_state_configured_modules.rb +6 -0
- data/lib/pact/provider/state/provider_state_manager.rb +3 -3
- data/lib/pact/provider/world.rb +2 -8
- data/lib/pact/rspec.rb +32 -0
- data/lib/pact/version.rb +1 -1
- data/pact.gemspec +3 -3
- data/spec/features/consumption_spec.rb +6 -1
- data/spec/integration/consumer_spec.rb +16 -9
- data/spec/integration/pact/consumer_configuration_spec.rb +7 -22
- data/spec/lib/pact/app_spec.rb +5 -5
- data/spec/lib/pact/configuration_spec.rb +1 -1
- data/spec/lib/pact/consumer/app_manager_spec.rb +3 -3
- data/spec/lib/pact/consumer/configuration_spec.rb +11 -8
- data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +3 -101
- data/spec/lib/pact/consumer/interaction_builder_spec.rb +8 -8
- data/spec/lib/pact/consumer/mock_service/app_spec.rb +2 -2
- data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +2 -2
- data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
- data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +2 -2
- data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
- data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +4 -4
- data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +18 -18
- data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
- data/spec/lib/pact/provider/configuration/pact_verification_spec.rb +1 -1
- data/spec/lib/pact/provider/pact_helper_locator_spec.rb +2 -2
- data/spec/lib/pact/provider/rspec/{formatter_spec.rb → formatter_rspec_2_spec.rb} +14 -4
- data/spec/lib/pact/provider/rspec/formatter_rspec_3_spec.rb +72 -0
- data/spec/lib/pact/provider/rspec_spec.rb +3 -0
- data/spec/lib/pact/provider/state/provider_state_manager_spec.rb +1 -1
- data/spec/lib/pact/provider/state/provider_state_proxy_spec.rb +4 -4
- data/spec/lib/pact/provider/state/provider_state_spec.rb +7 -7
- data/spec/lib/pact/provider/world_spec.rb +8 -8
- data/spec/lib/pact/tasks/verification_task_spec.rb +2 -2
- data/spec/spec_helper.rb +2 -4
- data/spec/support/factories.rb +13 -13
- data/spec/support/spec_support.rb +10 -0
- data/spec/support/stubbing_using_allow.rb +0 -4
- data/tasks/pact-test.rake +12 -8
- metadata +27 -24
- data/bethtest.rb +0 -30
- data/lib/pact/provider/rspec/silent_json_formatter.rb +0 -18
- data/spec/support/stubbing.rb +0 -26
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@ Do this to generate your change history
|
|
2
2
|
|
3
3
|
git log --pretty=format:' * %h - %s (%an, %ad)'
|
4
4
|
|
5
|
+
### 1.2.1.rc1 (13 June 2014)
|
6
|
+
|
7
|
+
* b8d1586 - Making RSpec::Mocks::ExampleMethods available in set_up and tear_down, so the allow method is available without configuration.
|
8
|
+
* 5ec17aa - Updating code to work with RSpec 3 (bethesque, Wed Jun 11 22:09:30 2014 +1000)
|
9
|
+
* 1227b71 - RESTifying the endpoints on the mock server (bethesque, Tue Jun 10 10:06:33 2014 +1000)
|
10
|
+
* 57409b5 - Moved pact writing into mock server, so the mock server can be reused by consumer libraries in other languages. (bethesque, Thu Jun
|
11
|
+
|
5
12
|
### 1.1.1 (3 June 2014)
|
6
13
|
|
7
14
|
* 503a3f4 - The pact verify executable now adds lib to the load path before requiring the pact_helper. (bethesque, Tue Jun 3 09:26:46 2014 +1000)
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
pact (1.1.
|
4
|
+
pact (1.2.1.rc1)
|
5
5
|
awesome_print (~> 1.1)
|
6
6
|
find_a_port (~> 1.0.1)
|
7
7
|
json
|
8
8
|
rack-test (~> 0.6.2)
|
9
9
|
randexp (~> 0.1.7)
|
10
|
-
rspec (
|
10
|
+
rspec (>= 2.14)
|
11
11
|
term-ansicolor (~> 1.0)
|
12
12
|
thor
|
13
13
|
webrick
|
@@ -15,57 +15,60 @@ PATH
|
|
15
15
|
GEM
|
16
16
|
remote: https://rubygems.org/
|
17
17
|
specs:
|
18
|
-
activesupport (4.
|
19
|
-
i18n (~> 0.6, >= 0.6.
|
20
|
-
|
21
|
-
|
18
|
+
activesupport (4.1.1)
|
19
|
+
i18n (~> 0.6, >= 0.6.9)
|
20
|
+
json (~> 1.7, >= 1.7.7)
|
21
|
+
minitest (~> 5.1)
|
22
22
|
thread_safe (~> 0.1)
|
23
|
-
tzinfo (~>
|
24
|
-
addressable (2.3.
|
25
|
-
atomic (1.1.14)
|
23
|
+
tzinfo (~> 1.1)
|
24
|
+
addressable (2.3.6)
|
26
25
|
awesome_print (1.2.0)
|
27
|
-
coderay (1.0
|
28
|
-
crack (0.4.
|
29
|
-
safe_yaml (~> 0.
|
26
|
+
coderay (1.1.0)
|
27
|
+
crack (0.4.2)
|
28
|
+
safe_yaml (~> 1.0.0)
|
30
29
|
diff-lcs (1.2.5)
|
31
|
-
fakefs (0.
|
30
|
+
fakefs (0.5.2)
|
31
|
+
faraday (0.9.0)
|
32
|
+
multipart-post (>= 1.2, < 3)
|
32
33
|
find_a_port (1.0.1)
|
33
|
-
hashie (2.
|
34
|
-
i18n (0.6.
|
34
|
+
hashie (2.1.1)
|
35
|
+
i18n (0.6.9)
|
35
36
|
json (1.8.1)
|
36
37
|
method_source (0.8.2)
|
37
|
-
minitest (
|
38
|
-
|
39
|
-
pry (0.
|
40
|
-
coderay (~> 1.0
|
41
|
-
method_source (~> 0.8)
|
38
|
+
minitest (5.3.4)
|
39
|
+
multipart-post (2.0.0)
|
40
|
+
pry (0.10.0)
|
41
|
+
coderay (~> 1.1.0)
|
42
|
+
method_source (~> 0.8.1)
|
42
43
|
slop (~> 3.4)
|
43
44
|
rack (1.5.2)
|
44
45
|
rack-test (0.6.2)
|
45
46
|
rack (>= 1.0)
|
46
47
|
rake (10.0.4)
|
47
48
|
randexp (0.1.7)
|
48
|
-
rspec (
|
49
|
-
rspec-core (~>
|
50
|
-
rspec-expectations (~>
|
51
|
-
rspec-mocks (~>
|
52
|
-
rspec-core (
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
rspec (
|
57
|
-
rspec-mocks (
|
58
|
-
|
59
|
-
|
49
|
+
rspec (3.0.0)
|
50
|
+
rspec-core (~> 3.0.0)
|
51
|
+
rspec-expectations (~> 3.0.0)
|
52
|
+
rspec-mocks (~> 3.0.0)
|
53
|
+
rspec-core (3.0.0)
|
54
|
+
rspec-support (~> 3.0.0)
|
55
|
+
rspec-expectations (3.0.0)
|
56
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
57
|
+
rspec-support (~> 3.0.0)
|
58
|
+
rspec-mocks (3.0.1)
|
59
|
+
rspec-support (~> 3.0.0)
|
60
|
+
rspec-support (3.0.0)
|
61
|
+
safe_yaml (1.0.3)
|
62
|
+
slop (3.5.0)
|
60
63
|
term-ansicolor (1.3.0)
|
61
64
|
tins (~> 1.0)
|
62
65
|
thor (0.19.1)
|
63
|
-
thread_safe (0.
|
64
|
-
atomic
|
66
|
+
thread_safe (0.3.4)
|
65
67
|
tins (1.3.0)
|
66
|
-
tzinfo (
|
67
|
-
|
68
|
-
|
68
|
+
tzinfo (1.2.1)
|
69
|
+
thread_safe (~> 0.1)
|
70
|
+
webmock (1.18.0)
|
71
|
+
addressable (>= 2.3.6)
|
69
72
|
crack (>= 0.3.2)
|
70
73
|
webrick (1.3.1)
|
71
74
|
|
@@ -75,9 +78,9 @@ PLATFORMS
|
|
75
78
|
DEPENDENCIES
|
76
79
|
activesupport
|
77
80
|
fakefs (~> 0.4)
|
81
|
+
faraday
|
78
82
|
hashie (~> 2.0)
|
79
83
|
pact!
|
80
84
|
pry
|
81
85
|
rake (~> 10.0.3)
|
82
|
-
|
83
|
-
webmock (~> 1.9.3)
|
86
|
+
webmock (~> 1.18.0)
|
data/README.md
CHANGED
@@ -274,6 +274,8 @@ As in all things, there are good ways to implement Pacts, and there are not so g
|
|
274
274
|
|
275
275
|
## Links
|
276
276
|
|
277
|
+
[Simplifying microservices testing with pacts](http://dius.com.au/2014/05/19/simplifying-micro-service-testing-with-pacts/) - Ron Holshausen (one of the original pact authors)
|
278
|
+
|
277
279
|
[Pact specification](https://github.com/bethesque/pact-specification)
|
278
280
|
|
279
281
|
[Integrated tests are a scam](http://vimeo.com/80533536) - J.B. Rainsberger
|
@@ -158,9 +158,9 @@ Pact uses RSpec and Rack::Test to create dynamic specs based on the pact files.
|
|
158
158
|
|
159
159
|
```ruby
|
160
160
|
Pact.configure do | config |
|
161
|
-
config.include
|
161
|
+
config.include MyTestHelperMethods
|
162
162
|
end
|
163
163
|
```
|
164
164
|
|
165
|
-
To make modules available in the provider state set_up and tear_down blocks, include them in the configuration as shown below. One common use of this
|
165
|
+
To make modules available in the provider state set_up and tear_down blocks, include them in the configuration as shown below. One common use of this to include factory methods for setting up data so that the provider states file doesn't get too bloated.
|
166
166
|
|
data/documentation/faq.md
CHANGED
@@ -20,9 +20,9 @@ Unlike Webmock:
|
|
20
20
|
|
21
21
|
### How can I handle versioning?
|
22
22
|
|
23
|
-
Consumer driven contracts to some extent allows you to do away with versioning. As long as all your contract tests pass, you should be able to deploy changes without versioning the API. If you need to make a breaking change to a provider, you can do it in a multiple step process - add the new fields/endpoints to the provider and deploy. Update the consumers to use the new fields, then deploy. Remove the old fields/endpoints from the provider and deploy. At each step of the process, all the contract tests remain green.
|
23
|
+
Consumer driven contracts to some extent allows you to do away with versioning. As long as all your contract tests pass, you should be able to deploy changes without versioning the API. If you need to make a breaking change to a provider, you can do it in a multiple step process - add the new fields/endpoints to the provider and deploy. Update the consumers to use the new fields/endpoints, then deploy. Remove the old fields/endpoints from the provider and deploy. At each step of the process, all the contract tests remain green.
|
24
24
|
|
25
|
-
Using a [Pact Broker](
|
25
|
+
Using a [Pact Broker](https://github.com/bethesque/pact_broker), you can tag the production version of a pact when you make a release of a consumer. Then, any changes that you make to the provider can be checked agains the production version of the pact, as well as the latest version, to ensure backward compatiblity.
|
26
26
|
|
27
27
|
If you need to support multiple versions of the provider API concurrently, then you will probably be specifying which version your consumer uses by setting a header, or using a different URL component. As these are actually different requests, the interactions can be verified in the same pact without any problems.
|
28
28
|
|
@@ -70,14 +70,12 @@ end
|
|
70
70
|
|
71
71
|
### Should the database or any other part of the provider be stubbed?
|
72
72
|
|
73
|
-
This is a hotly debated issue.
|
74
|
-
|
75
73
|
The pact authors' experience with using pacts to test microservices has been that using the set_up hooks to populate the database, and running pact:verify with all the real provider code has worked very well, and gives us full confidence that the end to end scenario will work in the deployed code.
|
76
74
|
|
77
|
-
However, if you have a large and complex provider, you might decide to stub some of your application code. You will definitly need to stub calls to downstream systems. Make sure,
|
75
|
+
However, if you have a large and complex provider, you might decide to stub some of your application code. You will definitly need to stub calls to downstream systems or to set up error scenarios. Make sure, if you stub, that you don't stub the code that actually parses the request and pulls the expected data out, because otherwise the consumer could be sending absolute rubbish, and the pact:verify won't fail because that code won't get executed. If the validation happens when you insert a record into the datasource, either don't stub anything, or rethink your validation code.
|
78
76
|
|
79
|
-
### Why are the pacts generated and not
|
77
|
+
### Why are the pacts generated and not static?
|
80
78
|
|
81
79
|
* Maintainability: Pact is "contract by example", and the examples may involve large quantities of JSON. Maintaining the JSON files by hand would be both time consuming and error prone. By dynamically creating the pacts, you have the option to keep your expectations in fixture files, or to generate them from your domain (the recommended approach, as it ensures your domain objects and their JSON representations in the pacts can never get out of sync).
|
82
80
|
|
83
|
-
* Provider states: Dynamically setting expectations on the mock server allows the use of provider states, meaning you can make the same request
|
81
|
+
* Provider states: Dynamically setting expectations on the mock server allows the use of provider states, meaning you can make the same request in different tests, with different expected responses. This allows you to properly test all the code paths in your consumer (eg. with different response codes, or different states of the resource). If all the interactions were loaded at start up from a static file, the mock server wouldn't know which response to return. See this [gist](https://gist.github.com/bethesque/7fa8947c107f92ace9a4) as an example.
|
@@ -158,18 +158,10 @@ end
|
|
158
158
|
|
159
159
|
### Including modules for use in set_up and tear_down
|
160
160
|
|
161
|
-
|
161
|
+
Any modules included this way will be available in the set_up and tear_down blocks. One common use of this to include factory methods for setting up data so that the provider states file doesn't get too bloated.
|
162
162
|
|
163
163
|
```ruby
|
164
164
|
Pact.configure do | config |
|
165
|
-
config.include
|
166
|
-
end
|
167
|
-
```
|
168
|
-
|
169
|
-
Any modules included this way will be available in the set_up and tear_down blocks eg. test data helper methods.
|
170
|
-
|
171
|
-
```ruby
|
172
|
-
Pact.configure do | config |
|
173
|
-
config.include MyFactoryMethods
|
165
|
+
config.include MyTestHelperMethods
|
174
166
|
end
|
175
167
|
```
|
@@ -2,10 +2,6 @@ require 'pact/provider/rspec'
|
|
2
2
|
|
3
3
|
require "./spec/service_consumers/provider_states_for_zoo_app"
|
4
4
|
|
5
|
-
Pact.configure do | config |
|
6
|
-
config.include RSpec::Mocks::ExampleMethods
|
7
|
-
end
|
8
|
-
|
9
5
|
Pact.service_provider 'Animal Service' do
|
10
6
|
|
11
7
|
honours_pact_with "Zoo App" do
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'pact/consumer/app_manager'
|
2
2
|
require 'pact/consumer/consumer_contract_builder'
|
3
3
|
require 'pact/consumer/consumer_contract_builders'
|
4
|
+
require 'pact/consumer/world'
|
4
5
|
|
5
6
|
module Pact
|
6
7
|
module Consumer
|
@@ -78,6 +79,7 @@ module Pact
|
|
78
79
|
Pact::Consumer::ConsumerContractBuilders.send(:define_method, @name.to_sym) do
|
79
80
|
consumer_contract_builder
|
80
81
|
end
|
82
|
+
Pact.consumer_world.add_consumer_contract_builder consumer_contract_builder
|
81
83
|
end
|
82
84
|
|
83
85
|
def validate
|
@@ -2,7 +2,7 @@ require 'uri'
|
|
2
2
|
require 'json/add/regexp'
|
3
3
|
require 'pact/logging'
|
4
4
|
require 'pact/consumer/mock_service_client'
|
5
|
-
|
5
|
+
require 'pact/consumer/interaction_builder'
|
6
6
|
|
7
7
|
module Pact
|
8
8
|
module Consumer
|
@@ -15,13 +15,12 @@ module Pact
|
|
15
15
|
|
16
16
|
def initialize(attributes)
|
17
17
|
@interaction_builder = nil
|
18
|
-
@
|
19
|
-
|
20
|
-
:
|
21
|
-
:
|
22
|
-
|
23
|
-
@
|
24
|
-
@interactions_filter = filter(@consumer_contract.interactions, attributes[:pactfile_write_mode])
|
18
|
+
@consumer_contract_details = {
|
19
|
+
consumer: {name: attributes[:consumer_name]},
|
20
|
+
provider: {name: attributes[:provider_name]},
|
21
|
+
pactfile_write_mode: attributes[:pactfile_write_mode].to_s
|
22
|
+
}
|
23
|
+
@mock_service_client = MockServiceClient.new(attributes[:port])
|
25
24
|
@mock_service_base_url = "http://localhost:#{attributes[:port]}"
|
26
25
|
end
|
27
26
|
|
@@ -52,6 +51,10 @@ module Pact
|
|
52
51
|
mock_service_client.log msg
|
53
52
|
end
|
54
53
|
|
54
|
+
def write_pact
|
55
|
+
mock_service_client.write_pact @consumer_contract_details
|
56
|
+
end
|
57
|
+
|
55
58
|
def wait_for_interactions options
|
56
59
|
wait_max_seconds = options.fetch(:wait_max_seconds, 3)
|
57
60
|
poll_interval = options.fetch(:poll_interval, 0.1)
|
@@ -59,65 +62,62 @@ module Pact
|
|
59
62
|
end
|
60
63
|
|
61
64
|
def handle_interaction_fully_defined interaction
|
62
|
-
interactions_filter << interaction
|
63
65
|
mock_service_client.add_expected_interaction interaction #TODO: What will happen if duplicate added?
|
64
|
-
consumer_contract.update_pactfile
|
65
66
|
self.interaction_builder = nil
|
66
67
|
end
|
67
68
|
|
68
69
|
private
|
69
70
|
|
70
71
|
attr_reader :mock_service_client
|
71
|
-
attr_reader :interactions_filter
|
72
72
|
attr_writer :interaction_builder
|
73
73
|
|
74
|
-
def interactions_for_new_consumer_contract pactfile_write_mode
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
def filter interactions, pactfile_write_mode
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
def warn_and_stderr msg
|
87
|
-
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
def info_and_puts msg
|
92
|
-
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
def existing_interactions
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
def pactfile_exists?
|
115
|
-
|
116
|
-
end
|
117
|
-
|
118
|
-
def existing_consumer_contract
|
119
|
-
|
120
|
-
end
|
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
121
|
|
122
122
|
end
|
123
123
|
end
|
@@ -11,6 +11,7 @@ require 'pact/consumer/mock_service/interaction_replay'
|
|
11
11
|
require 'pact/consumer/mock_service/missing_interactions_get'
|
12
12
|
require 'pact/consumer/mock_service/verification_get'
|
13
13
|
require 'pact/consumer/mock_service/log_get'
|
14
|
+
require 'pact/consumer/mock_service/pact_post'
|
14
15
|
|
15
16
|
AwesomePrint.defaults = {
|
16
17
|
indent: -2,
|
@@ -28,13 +29,15 @@ module Pact
|
|
28
29
|
interaction_list = InteractionList.new
|
29
30
|
|
30
31
|
@name = options.fetch(:name, "MockService")
|
32
|
+
interactions = []
|
31
33
|
@handlers = [
|
32
34
|
MissingInteractionsGet.new(@name, @logger, interaction_list),
|
33
35
|
VerificationGet.new(@name, @logger, interaction_list, log_description),
|
34
36
|
InteractionPost.new(@name, @logger, interaction_list),
|
35
37
|
InteractionDelete.new(@name, @logger, interaction_list),
|
36
38
|
LogGet.new(@name, @logger),
|
37
|
-
|
39
|
+
PactPost.new(@name, @logger, interactions),
|
40
|
+
InteractionReplay.new(@name, @logger, interaction_list, interactions)
|
38
41
|
]
|
39
42
|
end
|
40
43
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'pact/matchers'
|
2
2
|
require 'pact/consumer/mock_service/rack_request_helper'
|
3
3
|
require 'pact/consumer/mock_service/interaction_mismatch'
|
4
|
+
require 'pact/consumer_contract'
|
5
|
+
require 'pact/consumer/interactions_filter'
|
4
6
|
|
5
7
|
module Pact
|
6
8
|
module Consumer
|
@@ -9,12 +11,13 @@ module Pact
|
|
9
11
|
include Pact::Matchers
|
10
12
|
include RackRequestHelper
|
11
13
|
|
12
|
-
attr_accessor :name, :logger, :interaction_list
|
14
|
+
attr_accessor :name, :logger, :interaction_list, :interactions
|
13
15
|
|
14
|
-
def initialize name, logger, interaction_list
|
16
|
+
def initialize name, logger, interaction_list, interactions
|
15
17
|
@name = name
|
16
18
|
@logger = logger
|
17
19
|
@interaction_list = interaction_list
|
20
|
+
@interactions = DistinctInteractionsFilter.new(interactions)
|
18
21
|
end
|
19
22
|
|
20
23
|
def match? env
|
@@ -27,6 +30,10 @@ module Pact
|
|
27
30
|
|
28
31
|
private
|
29
32
|
|
33
|
+
def add_verified_interaction interaction
|
34
|
+
interactions << interaction
|
35
|
+
end
|
36
|
+
|
30
37
|
def find_response request_hash
|
31
38
|
actual_request = Request::Actual.from_hash(request_hash)
|
32
39
|
logger.info "Received request #{actual_request.method_and_path}"
|
@@ -51,6 +58,7 @@ module Pact
|
|
51
58
|
|
52
59
|
def handle_matched_interaction interaction
|
53
60
|
interaction_list.register_matched interaction
|
61
|
+
add_verified_interaction interaction
|
54
62
|
response = response_from(interaction.response)
|
55
63
|
logger.info "Found matching response for #{interaction.request.method_and_path}"
|
56
64
|
logger.debug pretty_generate(interaction.response)
|
@@ -110,7 +118,7 @@ module Pact
|
|
110
118
|
end
|
111
119
|
|
112
120
|
def response_from response
|
113
|
-
[response['status'], (response['headers'] || {}).to_hash, [render_body(response['body'])]]
|
121
|
+
[response['status'], (response['headers'] || {}).to_hash, [render_body(Pact::Reification.from_term(response['body']))]]
|
114
122
|
end
|
115
123
|
|
116
124
|
def render_body body
|
@@ -12,7 +12,7 @@ module Pact
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def request_path
|
15
|
-
'/
|
15
|
+
'/interactions/missing'
|
16
16
|
end
|
17
17
|
|
18
18
|
def request_method
|
@@ -22,7 +22,7 @@ module Pact
|
|
22
22
|
def respond env
|
23
23
|
number_of_missing_interactions = @interaction_list.missing_interactions.size
|
24
24
|
logger.info "Number of missing interactions for mock \"#{name}\" = #{number_of_missing_interactions}"
|
25
|
-
[200, {}, [
|
25
|
+
[200, {}, [{size: number_of_missing_interactions}.to_json]]
|
26
26
|
end
|
27
27
|
|
28
28
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'pact/consumer/mock_service/mock_service_administration_endpoint'
|
2
|
+
require 'pact/consumer_contract/consumer_contract_writer'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module Consumer
|
6
|
+
class PactPost < MockServiceAdministrationEndpoint
|
7
|
+
|
8
|
+
attr_accessor :consumer_contract, :interactions
|
9
|
+
|
10
|
+
def initialize name, logger, interactions
|
11
|
+
super name, logger
|
12
|
+
@interactions = interactions
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_path
|
16
|
+
'/pact'
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_method
|
20
|
+
'POST'
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond env
|
24
|
+
consumer_contract_details = JSON.parse(env['rack.input'].string, symbolize_names: true)
|
25
|
+
logger.info "Writing pact with details #{consumer_contract_details}"
|
26
|
+
consumer_contract_writer = ConsumerContractWriter.new(consumer_contract_details.merge(interactions: interactions), logger)
|
27
|
+
json = consumer_contract_writer.write
|
28
|
+
|
29
|
+
[200, {'Content-Type' =>'application/json'}, [json]]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -14,7 +14,7 @@ module Pact
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def request_path
|
17
|
-
'/
|
17
|
+
'/interactions/verification'
|
18
18
|
end
|
19
19
|
|
20
20
|
def request_method
|
@@ -26,7 +26,6 @@ module Pact
|
|
26
26
|
logger.info "Verifying - interactions matched for example \"#{example_description(env)}\""
|
27
27
|
[200, {'Content-Type' => 'text/plain'}, ['Interactions matched']]
|
28
28
|
else
|
29
|
-
|
30
29
|
error_message = FailureMessage.new(interaction_list).to_s
|
31
30
|
logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". \n#{error_message}"
|
32
31
|
logger.warn error_message
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'net/http'
|
1
2
|
require 'pact/consumer/mock_service_interaction_expectation'
|
2
3
|
|
3
4
|
module Pact
|
@@ -6,12 +7,12 @@ module Pact
|
|
6
7
|
|
7
8
|
MOCK_SERVICE_ADMINISTRATON_HEADERS = {'X-Pact-Mock-Service' => 'true'}
|
8
9
|
|
9
|
-
def initialize
|
10
|
+
def initialize port
|
10
11
|
@http = Net::HTTP.new('localhost', port)
|
11
12
|
end
|
12
13
|
|
13
14
|
def verify example_description
|
14
|
-
response = http.request_get("/
|
15
|
+
response = http.request_get("/interactions/verification?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
15
16
|
raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
|
16
17
|
end
|
17
18
|
|
@@ -21,8 +22,8 @@ module Pact
|
|
21
22
|
|
22
23
|
def wait_for_interactions wait_max_seconds, poll_interval
|
23
24
|
wait_until_true(wait_max_seconds, poll_interval) do
|
24
|
-
response = http.request_get("/
|
25
|
-
response.body ==
|
25
|
+
response = http.request_get("/interactions/missing", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
26
|
+
JSON.parse(response.body)['size'] == 0
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
@@ -31,14 +32,22 @@ module Pact
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def add_expected_interaction interaction
|
34
|
-
http.request_post('/interactions', MockServiceInteractionExpectation.new(interaction).to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
35
|
+
response = http.request_post('/interactions', MockServiceInteractionExpectation.new(interaction).to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
|
36
|
+
raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
|
35
37
|
end
|
36
38
|
|
37
39
|
def self.clear_interactions port, example_description
|
38
40
|
Net::HTTP.new("localhost", port).delete("/interactions?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
|
39
41
|
end
|
40
42
|
|
43
|
+
def write_pact pacticipant_details
|
44
|
+
response = http.request_post("/pact", pacticipant_details.to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
|
45
|
+
raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
|
46
|
+
response.body
|
47
|
+
end
|
48
|
+
|
41
49
|
private
|
50
|
+
|
42
51
|
attr_reader :http
|
43
52
|
|
44
53
|
#todo: in need a better home (where can we move it?)
|
@@ -16,7 +16,7 @@ module Pact
|
|
16
16
|
hash[:provider_state] = interaction.provider_state if interaction.provider_state
|
17
17
|
options = interaction.request.options.empty? ? {} : { options: interaction.request.options}
|
18
18
|
hash[:request] = interaction.request.as_json.merge(options)
|
19
|
-
hash[:response] =
|
19
|
+
hash[:response] = interaction.response
|
20
20
|
hash
|
21
21
|
end
|
22
22
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'pact/doc/generate'
|
2
|
+
require 'pact/consumer/world'
|
2
3
|
|
3
4
|
module Pact
|
4
5
|
module Consumer
|
@@ -24,6 +25,7 @@ module Pact
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def after_suite
|
28
|
+
Pact.consumer_world.consumer_contract_builders.each { | c | c.write_pact }
|
27
29
|
Pact::Doc::Generate.call
|
28
30
|
Pact::Consumer::AppManager.instance.kill_all
|
29
31
|
Pact::Consumer::AppManager.instance.clear_all
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Pact
|
2
|
+
|
3
|
+
def self.consumer_world
|
4
|
+
@consumer_world ||= Pact::Consumer::World.new
|
5
|
+
end
|
6
|
+
|
7
|
+
# internal api, for testing only
|
8
|
+
def self.clear_consumer_world
|
9
|
+
@consumer_world = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
module Consumer
|
13
|
+
class World
|
14
|
+
|
15
|
+
def consumer_contract_builders
|
16
|
+
@consumer_contract_builders ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_consumer_contract_builder consumer_contract_builder
|
20
|
+
consumer_contract_builders << consumer_contract_builder
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|