pact-mock_service 0.2.3.pre.rc1 → 0.2.3.pre.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +28 -292
- data/lib/pact/consumer/mock_service/app.rb +42 -23
- data/lib/pact/consumer/mock_service/candidate_interactions.rb +1 -1
- data/lib/pact/consumer/mock_service/interaction_delete.rb +3 -3
- data/lib/pact/consumer/mock_service/interaction_post.rb +2 -2
- data/lib/pact/consumer/mock_service/interaction_replay.rb +6 -1
- data/lib/pact/consumer/mock_service/log_get.rb +2 -2
- data/lib/pact/consumer/mock_service/missing_interactions_get.rb +2 -2
- data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +17 -6
- data/lib/pact/consumer/mock_service/options.rb +50 -0
- data/lib/pact/consumer/mock_service/pact_post.rb +6 -3
- data/lib/pact/consumer/mock_service/rack_request_helper.rb +1 -0
- data/lib/pact/consumer/mock_service/verification_get.rb +2 -2
- data/lib/pact/consumer/mock_service/web_request_administration.rb +31 -0
- data/lib/pact/consumer_contract/consumer_contract_writer.rb +25 -9
- data/lib/pact/mock_service/cli.rb +61 -16
- data/lib/pact/mock_service/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 200c03efe0a34bab41ab409685601a5d069e5319
|
4
|
+
data.tar.gz: ffa20a890680ccf3dc0bc3c1aa20a78de93e64f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 143b3ff38ebc4689c56201b6f6280dd089a4a77883284571c7a07e89e1bdeb5b3b5b78a4eabb5226c7bfab4d5c797c436395c6e7b0c34050e9e32834b8993f05
|
7
|
+
data.tar.gz: 42a1f1bb0d3f374fa23b739b3bfb2273d46a51babced8496d1814e174f6da465896cce4f1fbb414f20fcad47fb346cf3b1d4fee136cdf4ebde2177586ea3d258
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@ Do this to generate your change history
|
|
2
2
|
|
3
3
|
git log --pretty=format:' * %h - %s (%an, %ad)'
|
4
4
|
|
5
|
+
### 0.2.3.rc2 (13 Jan 2015)
|
6
|
+
|
7
|
+
* daf0696 - Added --consumer and --provider options to CLI. Automatically write pact if both options are given at startup. (Beth, Mon Jan 5 20:48:47 2015 +1100)
|
8
|
+
* 351c44e - Write pact on shutdown (Beth, Mon Jan 5 17:17:24 2015 +1100)
|
9
|
+
* e206c9f - Adding cross domain headers (André Allavena, Tue Dec 23 18:01:46 2014 +1000)
|
10
|
+
|
5
11
|
### 0.2.3.rc1 (3 Jan 2015)
|
6
12
|
|
7
13
|
* afd9cf3 - Removed awesome print gem dependency. (Beth, Sat Jan 3 16:49:40 2015 +1100)
|
data/README.md
CHANGED
@@ -1,314 +1,50 @@
|
|
1
|
-
# Pact
|
1
|
+
# Pact Mock Service
|
2
2
|
|
3
|
-
|
3
|
+
This codebase provides the mock service used by implementations of [Pact][pact]. It is packaged as a gem, and as a standalone executable for Mac OSX and Linux (Windows coming soon.)
|
4
4
|
|
5
|
-
|
5
|
+
The mock service provides the following endpoints:
|
6
6
|
|
7
|
-
|
7
|
+
* DELETE /interactions - clear previously mocked interactions
|
8
|
+
* POST /interactions - set up an expected interaction
|
9
|
+
* GET /interactions/verification - determine whether the expected interactions have taken place
|
10
|
+
* POST /pact - write the pact file
|
8
11
|
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
## What is it good for?
|
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 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.
|
22
|
-
|
23
|
-
## Features
|
24
|
-
|
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.
|
26
|
-
* "Provider states" (similar to fixtures) allow the same request to be made with a different expected response.
|
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.
|
29
|
-
* Rake tasks allow pacts to be verified against a service provider codebase.
|
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).
|
31
|
-
* Autogenerated API documentation - need we say more?
|
32
|
-
* Autogenerated network diagrams with the [Pact Broker](https://github.com/bethesque/pact_broker)
|
33
|
-
|
34
|
-
## How does it work?
|
35
|
-
|
36
|
-
1. In the specs for the provider facing code in the consumer project, expectations are set up on a mock service provider.
|
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.
|
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.
|
39
|
-
|
40
|
-
## Why is developing and testing with Pact better than using traditional system integration tests?
|
41
|
-
|
42
|
-
* Faster execution.
|
43
|
-
* Reliable responses from mock service reduce likelihood of flakey tests.
|
44
|
-
* Causes of failure are easier to identify as only one component is being tested at a time.
|
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.
|
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.
|
48
|
-
|
49
|
-
## Contact
|
50
|
-
|
51
|
-
* Twitter: [@pact_up](https://twitter.com/pact_up)
|
52
|
-
* Google users group: https://groups.google.com/forum/#!forum/pact-support
|
53
|
-
|
54
|
-
## Installation
|
55
|
-
|
56
|
-
Put it in your Gemfile. You know how.
|
12
|
+
As the Pact mock service can be used as a standalone executable and administered via HTTP, it can be used for testing with any language. All that is required is a library in the native language to create the HTTP calls listed above. Currently there are binding for [Ruby][pact] and [Javascript][javascript]. If you are interested in creating bindings in a new langauge, and have a chat to one of us on the [pact-dev Google group][pact-dev].
|
57
13
|
|
58
14
|
## Usage
|
59
15
|
|
60
|
-
###
|
61
|
-
|
62
|
-
#### 1. Start with your model
|
63
|
-
|
64
|
-
Imagine a model class that looks something like this. The attributes for a Something live on a remote server, and will need to be retrieved by an HTTP call.
|
65
|
-
|
66
|
-
```ruby
|
67
|
-
class Something
|
68
|
-
attr_reader :name
|
69
|
-
|
70
|
-
def initialize name
|
71
|
-
@name = name
|
72
|
-
end
|
73
|
-
|
74
|
-
def == other
|
75
|
-
other.is_a?(Something) && other.name == name
|
76
|
-
end
|
77
|
-
end
|
78
|
-
```
|
79
|
-
|
80
|
-
#### 2. Create a skeleton client class
|
81
|
-
|
82
|
-
Imagine a service provider client class that looks something like this.
|
83
|
-
|
84
|
-
```ruby
|
85
|
-
require 'httparty'
|
86
|
-
|
87
|
-
class MyServiceProviderClient
|
88
|
-
include HTTParty
|
89
|
-
base_uri 'http://my-service'
|
90
|
-
|
91
|
-
def get_something
|
92
|
-
# Yet to be implemented because we're doing Test First Development...
|
93
|
-
end
|
94
|
-
end
|
95
|
-
```
|
96
|
-
#### 3. Configure the mock server
|
97
|
-
|
98
|
-
The following code will create a mock service on localhost:1234 which will respond to your application's queries over HTTP as if it were the real "My Service Provider" app. It also creates a mock service provider object which you will use to set up your expectations. The method name to access the mock service provider will be what ever name you give as the service argument - in this case "my_service_provider"
|
99
|
-
|
100
|
-
|
101
|
-
```ruby
|
102
|
-
# In /spec/service_providers/pact_helper.rb
|
103
|
-
|
104
|
-
require 'pact/consumer/rspec'
|
105
|
-
# or require 'pact/consumer/minitest' if you are using Minitest
|
106
|
-
|
107
|
-
Pact.service_consumer "My Service Consumer" do
|
108
|
-
has_pact_with "My Service Provider" do
|
109
|
-
mock_service :my_service_provider do
|
110
|
-
port 1234
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
```
|
115
|
-
|
116
|
-
#### 4. Write a failing spec for the client
|
117
|
-
|
118
|
-
```ruby
|
119
|
-
# In /spec/service_providers/my_service_provider_client_spec.rb
|
120
|
-
|
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
|
-
|
124
|
-
describe MyServiceProviderClient, :pact => true do
|
125
|
-
|
126
|
-
before do
|
127
|
-
# Configure your client to point to the stub service on localhost using the port you have specified
|
128
|
-
MyServiceProviderClient.base_uri 'localhost:1234'
|
129
|
-
end
|
130
|
-
|
131
|
-
subject { MyServiceProviderClient.new }
|
132
|
-
|
133
|
-
describe "get_something" do
|
134
|
-
|
135
|
-
before do
|
136
|
-
my_service_provider.given("something exists").
|
137
|
-
upon_receiving("a request for something").with(method: :get, path: '/something', query: '').
|
138
|
-
will_respond_with(
|
139
|
-
status: 200,
|
140
|
-
headers: {'Content-Type' => 'application/json'},
|
141
|
-
body: {name: 'A small something'} )
|
142
|
-
end
|
143
|
-
|
144
|
-
it "returns a Something" do
|
145
|
-
expect(subject.get_something).to eq(Something.new('A small something'))
|
146
|
-
end
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
```
|
152
|
-
|
153
|
-
#### 5. Run the specs
|
154
|
-
|
155
|
-
Running the consumer spec will generate a pact file in the configured pact dir (spec/pacts by default).
|
156
|
-
Logs will be output to the configured log dir that can be useful when diagnosing problems.
|
157
|
-
|
158
|
-
Of course, the above specs will fail because the client method is not implemented, so next, implement your client methods.
|
159
|
-
|
160
|
-
#### 6. Implement the client methods
|
16
|
+
### With Ruby on Mac OSX and Linux
|
161
17
|
|
162
|
-
|
163
|
-
|
164
|
-
include HTTParty
|
165
|
-
base_uri 'http://my-service'
|
18
|
+
$ gem install pact-mock_service
|
19
|
+
$ pact-mock-service --port 1234
|
166
20
|
|
167
|
-
|
168
|
-
name = JSON.parse(self.class.get("/something").body)['name']
|
169
|
-
Something.new(name)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
```
|
21
|
+
Run `pact-mock-service help` for command line options.
|
173
22
|
|
174
|
-
|
23
|
+
### With Ruby on Windows
|
175
24
|
|
176
|
-
|
25
|
+
Check out the wiki page [here][install-windows].
|
177
26
|
|
178
|
-
|
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.
|
27
|
+
#### With SSL
|
182
28
|
|
183
|
-
|
29
|
+
If you need to use the mock service with HTTPS, you can use the built-in SSL mode which relies on a self-signed certificate.
|
184
30
|
|
185
|
-
|
31
|
+
$ pact-mock-service --port 1234 --ssl
|
186
32
|
|
187
|
-
|
33
|
+
### Mac OSX and Linux, without Ruby
|
188
34
|
|
189
|
-
|
35
|
+
See the [releases][releases] page for the latest standalone executables.
|
190
36
|
|
191
|
-
|
37
|
+
### Windows, without Ruby
|
192
38
|
|
193
|
-
|
194
|
-
# In Rakefile
|
195
|
-
require 'pact/tasks'
|
196
|
-
```
|
197
|
-
|
198
|
-
Create a `pact_helper.rb` in your service provider project. The recommended place is `spec/service_consumers/pact_helper.rb`.
|
199
|
-
|
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.
|
201
|
-
|
202
|
-
```ruby
|
203
|
-
# In specs/service_consumers/pact_helper.rb
|
204
|
-
|
205
|
-
require 'pact/provider/rspec'
|
206
|
-
|
207
|
-
Pact.service_provider "My Service Provider" do
|
208
|
-
|
209
|
-
honours_pact_with 'My Service Consumer' do
|
210
|
-
|
211
|
-
# This example points to a local file, however, on a real project with a continuous
|
212
|
-
# integration box, you would use a [Pact Broker](https://github.com/bethesque/pact_broker) or publish your pacts as artifacts,
|
213
|
-
# and point the pact_uri to the pact published by the last successful build.
|
214
|
-
|
215
|
-
pact_uri '../path-to-your-consumer-project/specs/pacts/my_consumer-my_provider.json'
|
216
|
-
end
|
217
|
-
end
|
218
|
-
```
|
219
|
-
|
220
|
-
#### 3. Run your failing specs
|
221
|
-
|
222
|
-
$ rake pact:verify
|
223
|
-
|
224
|
-
Congratulations! You now have a failing spec to develop against.
|
225
|
-
|
226
|
-
At this stage, you'll want to be able to run your specs one at a time while you implement each feature. At the bottom of the failed pact:verify output you will see the commands to rerun each failed interaction individually. A command to run just one interaction will look like this:
|
227
|
-
|
228
|
-
$ rake pact:verify PACT_DESCRIPTION="a request for something" PACT_PROVIDER_STATE="something exists"
|
229
|
-
|
230
|
-
#### 4. Implement enough to make your first interaction spec pass
|
231
|
-
|
232
|
-
Rinse and repeat.
|
233
|
-
|
234
|
-
#### 5. Keep going til you're green
|
235
|
-
|
236
|
-
Yay! Your provider now honours the pact it has with your consumer. You can now have confidence that your consumer and provider will play nicely together.
|
237
|
-
|
238
|
-
### Using provider states
|
239
|
-
|
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).
|
241
|
-
|
242
|
-
|
243
|
-
## Configuration
|
244
|
-
|
245
|
-
See the [Configuration](/documentation/configuration.md) section of the documentation for options relating to thing like logging, diff formatting, and documentation generation.
|
246
|
-
|
247
|
-
## Pact best practices
|
248
|
-
|
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.
|
250
|
-
|
251
|
-
## Docs
|
252
|
-
|
253
|
-
* [Example](example)
|
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
|
-
* [Using regular expressions with Pact](https://github.com/realestate-com-au/pact/wiki/Using-regular-expressions-with-Pact)
|
260
|
-
* [Frequently asked questions](https://github.com/realestate-com-au/pact/wiki/FAQ)
|
261
|
-
* [Rarely asked questions](https://github.com/realestate-com-au/pact/wiki/RAQ)
|
262
|
-
* [Best practices](https://github.com/realestate-com-au/pact/wiki/Best-practices)
|
263
|
-
* [Troubleshooting](https://github.com/realestate-com-au/pact/wiki/Troubleshooting)
|
264
|
-
* [Testing with pact diagram](https://github.com/realestate-com-au/pact/wiki/Testing with pact.png)
|
265
|
-
|
266
|
-
## Related libraries
|
267
|
-
|
268
|
-
[Pact Provider Proxy](https://github.com/bethesque/pact-provider-proxy) - Verify a pact against a running server, allowing you to use pacts with a provider of any language.
|
269
|
-
|
270
|
-
[Pact Broker](https://github.com/bethesque/pact_broker) - A pact repository. Provides endpoints to access published pacts, meaning you don't need to use messy CI URLs in your codebase. Enables cross testing of prod/head versions of your consumer and provider, allowing you to determine whether the head version of one is compatible with the production version of the other. Helps you to answer that ever so important question, "can I deploy without breaking all the things?"
|
271
|
-
|
272
|
-
[Pact Broker Client](https://github.com/bethesque/pact_broker-client) - Contains rake tasks for publishing pacts to the pact_broker.
|
273
|
-
|
274
|
-
[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.
|
275
|
-
|
276
|
-
[Pact .NET](https://github.com/SEEK-Jobs/pact-net) - A Pact implementation for .NET.
|
277
|
-
|
278
|
-
[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.
|
279
|
-
|
280
|
-
## Links
|
281
|
-
|
282
|
-
[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)
|
283
|
-
|
284
|
-
[Pact specification](https://github.com/bethesque/pact-specification)
|
285
|
-
|
286
|
-
[Integrated tests are a scam](http://vimeo.com/80533536) - J.B. Rainsberger
|
287
|
-
|
288
|
-
[Consumer Driven Contracts](http://martinfowler.com/articles/consumerDrivenContracts.html) - Ian Robinson
|
289
|
-
|
290
|
-
[Integration Contract Tests](http://martinfowler.com/bliki/IntegrationContractTest.html) - Martin Fowler
|
291
|
-
|
292
|
-
## TODO
|
293
|
-
|
294
|
-
Short term:
|
295
|
-
- Support hash of query params
|
296
|
-
|
297
|
-
Long term:
|
298
|
-
- Provide more flexible matching (eg the keys should match, and the classes of the values should match, but the values of each key do not need to be equal). This is to make the pact verification less brittle.
|
299
|
-
- Add XML support
|
300
|
-
- Decouple Rspec from Pact and make rspec-pact gem for easy integration
|
39
|
+
I had a package somewhere lying around, but I lost it, and I don't have a Windows machine. If you are interested in using the mock server on Windows, please check out the instructions for building one [here][windows], and then let me know so I can upload it to the releases page. Thanks!
|
301
40
|
|
302
41
|
## Contributing
|
303
42
|
|
304
|
-
|
305
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
306
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
307
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
308
|
-
5. Create new Pull Request
|
309
|
-
|
310
|
-
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!
|
311
|
-
|
312
|
-
[webmachine]: https://github.com/seancribbs/webmachine-ruby
|
313
|
-
[roar]: https://github.com/apotonick/roar
|
43
|
+
See [CONTRIBUTING.md](/CONTRIBUTING.md)
|
314
44
|
|
45
|
+
[pact]: https://github.com/realestate-com-au/pact
|
46
|
+
[releases]: https://github.com/bethesque/pact-mock_service/releases
|
47
|
+
[javascript]: https://github.com/DiUS/pact-consumer-js-dsl
|
48
|
+
[pact-dev]: https://groups.google.com/forum/#!forum/pact-dev
|
49
|
+
[windows]: https://github.com/bethesque/pact-mock_service/wiki/Building-a-Windows-standalone-executable
|
50
|
+
[install-windows]: https://github.com/bethesque/pact-mock_service/wiki/Installing-the-pact-mock_service-gem-on-Windows
|
@@ -2,7 +2,6 @@ require 'rack'
|
|
2
2
|
require 'uri'
|
3
3
|
require 'json'
|
4
4
|
require 'logger'
|
5
|
-
require 'awesome_print'
|
6
5
|
require 'pact/consumer/request'
|
7
6
|
require 'pact/consumer/mock_service/expected_interactions'
|
8
7
|
require 'pact/consumer/mock_service/actual_interactions'
|
@@ -14,14 +13,9 @@ require 'pact/consumer/mock_service/missing_interactions_get'
|
|
14
13
|
require 'pact/consumer/mock_service/verification_get'
|
15
14
|
require 'pact/consumer/mock_service/log_get'
|
16
15
|
require 'pact/consumer/mock_service/pact_post'
|
16
|
+
require 'pact/consumer/mock_service/options'
|
17
17
|
require 'pact/support'
|
18
18
|
|
19
|
-
AwesomePrint.defaults = {
|
20
|
-
indent: -2,
|
21
|
-
plain: true,
|
22
|
-
index: false
|
23
|
-
}
|
24
|
-
|
25
19
|
module Pact
|
26
20
|
module Consumer
|
27
21
|
|
@@ -35,18 +29,50 @@ module Pact
|
|
35
29
|
expected_interactions = ExpectedInteractions.new
|
36
30
|
actual_interactions = ActualInteractions.new
|
37
31
|
verified_interactions = VerifiedInteractions.new
|
32
|
+
@consumer_contact_details = {
|
33
|
+
pact_dir: options[:pact_dir],
|
34
|
+
consumer: {name: options[:consumer]},
|
35
|
+
provider: {name: options[:provider]},
|
36
|
+
interactions: verified_interactions
|
37
|
+
}
|
38
38
|
|
39
39
|
@handlers = [
|
40
|
+
Options.new(@name, @logger, options[:cors_enabled]),
|
40
41
|
MissingInteractionsGet.new(@name, @logger, expected_interactions, actual_interactions),
|
41
42
|
VerificationGet.new(@name, @logger, expected_interactions, actual_interactions, log_description),
|
42
43
|
InteractionPost.new(@name, @logger, expected_interactions, verified_interactions),
|
43
44
|
InteractionDelete.new(@name, @logger, expected_interactions, actual_interactions),
|
44
45
|
LogGet.new(@name, @logger),
|
45
|
-
PactPost.new(@name, @logger, verified_interactions, pact_dir),
|
46
|
-
InteractionReplay.new(@name, @logger, expected_interactions, actual_interactions, verified_interactions)
|
46
|
+
PactPost.new(@name, @logger, verified_interactions, pact_dir, options[:consumer_contract_details]),
|
47
|
+
InteractionReplay.new(@name, @logger, expected_interactions, actual_interactions, verified_interactions, options[:cors_enabled])
|
47
48
|
]
|
48
49
|
end
|
49
50
|
|
51
|
+
def call env
|
52
|
+
response = []
|
53
|
+
begin
|
54
|
+
relevant_handler = @handlers.detect { |handler| handler.match? env }
|
55
|
+
res = relevant_handler.respond(env)
|
56
|
+
response = relevant_handler.enable_cors? ? add_cors_header(res) : res
|
57
|
+
rescue StandardError => e
|
58
|
+
@logger.error "Error ocurred in mock service: #{e.class} - #{e.message}"
|
59
|
+
@logger.error e.backtrace.join("\n")
|
60
|
+
response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
|
61
|
+
rescue Exception => e
|
62
|
+
@logger.error "Exception ocurred in mock service: #{e.class} - #{e.message}"
|
63
|
+
@logger.error e.backtrace.join("\n")
|
64
|
+
raise e
|
65
|
+
end
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
def write_pact_if_configured
|
70
|
+
consumer_contract_writer = ConsumerContractWriter.new(@consumer_contact_details, StdoutLogger.new)
|
71
|
+
consumer_contract_writer.write if consumer_contract_writer.can_write?
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
50
76
|
def configure_logger options
|
51
77
|
options = {log_file: $stdout}.merge options
|
52
78
|
log_stream = options[:log_file]
|
@@ -65,21 +91,14 @@ module Pact
|
|
65
91
|
"#{@name} #{super.to_s}"
|
66
92
|
end
|
67
93
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
relevant_handler = @handlers.detect { |handler| handler.match? env }
|
72
|
-
response = relevant_handler.respond(env)
|
73
|
-
rescue StandardError => e
|
74
|
-
@logger.error "Error ocurred in mock service: #{e.class} - #{e.message}"
|
75
|
-
@logger.error e.backtrace.join("\n")
|
76
|
-
response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
|
77
|
-
rescue Exception => e
|
78
|
-
@logger.error "Exception ocurred in mock service: #{e.class} - #{e.message}"
|
79
|
-
@logger.error e.backtrace.join("\n")
|
80
|
-
raise e
|
94
|
+
class StdoutLogger
|
95
|
+
def info message
|
96
|
+
$stdout.puts "\n#{message}"
|
81
97
|
end
|
82
|
-
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_cors_header response
|
101
|
+
[response[0], response[1].merge('Access-Control-Allow-Origin' => '*'), response[2]]
|
83
102
|
end
|
84
103
|
|
85
104
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'pact/consumer/mock_service/rack_request_helper'
|
2
|
-
require 'pact/consumer/mock_service/
|
2
|
+
require 'pact/consumer/mock_service/web_request_administration'
|
3
3
|
|
4
4
|
module Pact
|
5
5
|
module Consumer
|
6
6
|
|
7
|
-
class InteractionDelete <
|
7
|
+
class InteractionDelete < WebRequestAdministration
|
8
8
|
|
9
9
|
include RackRequestHelper
|
10
10
|
|
@@ -36,4 +36,4 @@ module Pact
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
39
|
-
end
|
39
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'pact/consumer/mock_service/
|
1
|
+
require 'pact/consumer/mock_service/web_request_administration'
|
2
2
|
require 'pact/mock_service/interaction_decorator'
|
3
3
|
require 'pact/shared/json_differ'
|
4
4
|
|
5
5
|
module Pact
|
6
6
|
module Consumer
|
7
|
-
class InteractionPost <
|
7
|
+
class InteractionPost < WebRequestAdministration
|
8
8
|
|
9
9
|
def initialize name, logger, expected_interactions, verified_interactions
|
10
10
|
super name, logger
|
@@ -23,12 +23,13 @@ module Pact
|
|
23
23
|
|
24
24
|
attr_accessor :name, :logger, :expected_interactions, :actual_interactions, :verified_interactions
|
25
25
|
|
26
|
-
def initialize name, logger, expected_interactions, actual_interactions, verified_interactions
|
26
|
+
def initialize name, logger, expected_interactions, actual_interactions, verified_interactions, cors_enabled=false
|
27
27
|
@name = name
|
28
28
|
@logger = logger
|
29
29
|
@expected_interactions = expected_interactions
|
30
30
|
@actual_interactions = actual_interactions
|
31
31
|
@verified_interactions = verified_interactions
|
32
|
+
@cors_enabled = cors_enabled
|
32
33
|
end
|
33
34
|
|
34
35
|
def match? env
|
@@ -39,6 +40,10 @@ module Pact
|
|
39
40
|
find_response request_as_hash_from(env)
|
40
41
|
end
|
41
42
|
|
43
|
+
def enable_cors?
|
44
|
+
@cors_enabled
|
45
|
+
end
|
46
|
+
|
42
47
|
private
|
43
48
|
|
44
49
|
def find_response request_hash
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'pact/consumer/mock_service/
|
1
|
+
require 'pact/consumer/mock_service/web_request_administration'
|
2
2
|
|
3
3
|
module Pact
|
4
4
|
module Consumer
|
5
|
-
class LogGet <
|
5
|
+
class LogGet < WebRequestAdministration
|
6
6
|
|
7
7
|
include RackRequestHelper
|
8
8
|
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require 'pact/consumer/mock_service/
|
1
|
+
require 'pact/consumer/mock_service/web_request_administration'
|
2
2
|
require 'pact/consumer/mock_service/verification'
|
3
3
|
|
4
4
|
module Pact
|
5
5
|
module Consumer
|
6
6
|
|
7
|
-
class MissingInteractionsGet <
|
7
|
+
class MissingInteractionsGet < WebRequestAdministration
|
8
8
|
include RackRequestHelper
|
9
9
|
|
10
10
|
def initialize name, logger, expected_interactions, actual_interactions
|
@@ -3,6 +3,8 @@ module Pact
|
|
3
3
|
module Consumer
|
4
4
|
class MockServiceAdministrationEndpoint
|
5
5
|
|
6
|
+
include RackRequestHelper
|
7
|
+
|
6
8
|
attr_accessor :logger, :name
|
7
9
|
|
8
10
|
def initialize name, logger
|
@@ -10,12 +12,8 @@ module Pact
|
|
10
12
|
@logger = logger
|
11
13
|
end
|
12
14
|
|
13
|
-
include RackRequestHelper
|
14
|
-
|
15
15
|
def match? env
|
16
|
-
|
17
|
-
env['PATH_INFO'] == request_path &&
|
18
|
-
env['REQUEST_METHOD'] == request_method
|
16
|
+
(request_header_match? env) && (request_path_match? env) && (request_method_match? env)
|
19
17
|
end
|
20
18
|
|
21
19
|
def request_path
|
@@ -26,6 +24,19 @@ module Pact
|
|
26
24
|
raise NotImplementedError
|
27
25
|
end
|
28
26
|
|
27
|
+
private
|
28
|
+
|
29
|
+
def request_header_match? env
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
def request_path_match? env
|
34
|
+
env['PATH_INFO'] == request_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def request_method_match? env
|
38
|
+
env['REQUEST_METHOD'] == request_method
|
39
|
+
end
|
29
40
|
end
|
30
41
|
end
|
31
|
-
end
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'pact/consumer/mock_service/rack_request_helper'
|
2
|
+
require 'pact/consumer/mock_service/mock_service_administration_endpoint'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module Consumer
|
6
|
+
|
7
|
+
class Options
|
8
|
+
|
9
|
+
include RackRequestHelper
|
10
|
+
|
11
|
+
attr_reader :name, :logger, :cors_enabled
|
12
|
+
|
13
|
+
def initialize name, logger, cors_enabled
|
14
|
+
@name = name
|
15
|
+
@logger = logger
|
16
|
+
@cors_enabled = cors_enabled
|
17
|
+
end
|
18
|
+
|
19
|
+
def match? env
|
20
|
+
is_options_request?(env) && (cors_enabled || is_administration_request?(env))
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond env
|
24
|
+
logger.info "Received OPTIONS request for #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']} #{env['PATH_INFO']}. Returning CORS headers."
|
25
|
+
[200,
|
26
|
+
{
|
27
|
+
'Access-Control-Allow-Origin' => '*',
|
28
|
+
'Access-Control-Allow-Headers' => headers_from(env)["Access-Control-Request-Headers"],
|
29
|
+
'Access-Control-Allow-Methods' => 'DELETE, POST, GET, HEAD, PUT, TRACE, CONNECT'
|
30
|
+
},
|
31
|
+
[]
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Access-Control-Domain does not work on OPTIONs requests.
|
36
|
+
def enable_cors?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_options_request? env
|
41
|
+
env['REQUEST_METHOD'] == 'OPTIONS'
|
42
|
+
end
|
43
|
+
|
44
|
+
def is_administration_request? env
|
45
|
+
env["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"].match(/x-pact-mock-service/i)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,16 +1,19 @@
|
|
1
|
-
require 'pact/consumer/mock_service/mock_service_administration_endpoint'
|
2
1
|
require 'pact/consumer_contract/consumer_contract_writer'
|
2
|
+
require 'pact/consumer/mock_service/web_request_administration'
|
3
3
|
|
4
4
|
module Pact
|
5
5
|
module Consumer
|
6
|
-
class PactPost <
|
6
|
+
class PactPost < WebRequestAdministration
|
7
7
|
|
8
8
|
attr_accessor :consumer_contract, :verified_interactions, :default_options
|
9
9
|
|
10
|
-
def initialize name, logger, verified_interactions, pact_dir
|
10
|
+
def initialize name, logger, verified_interactions, pact_dir, consumer_contract_details
|
11
11
|
super name, logger
|
12
12
|
@verified_interactions = verified_interactions
|
13
13
|
@default_options = {pact_dir: pact_dir}
|
14
|
+
if consumer_contract_details
|
15
|
+
@default_options.merge!(consumer_contract_details)
|
16
|
+
end
|
14
17
|
end
|
15
18
|
|
16
19
|
def request_path
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'pact/consumer/mock_service/
|
1
|
+
require 'pact/consumer/mock_service/web_request_administration'
|
2
2
|
|
3
3
|
module Pact
|
4
4
|
module Consumer
|
5
|
-
class VerificationGet <
|
5
|
+
class VerificationGet < WebRequestAdministration
|
6
6
|
|
7
7
|
include RackRequestHelper
|
8
8
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'pact/consumer/mock_service/rack_request_helper'
|
2
|
+
require 'pact/consumer/mock_service/mock_service_administration_endpoint'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
module Consumer
|
6
|
+
|
7
|
+
# Administration web requests (GET, DELETE, POST, PUT, etc.)
|
8
|
+
class WebRequestAdministration < MockServiceAdministrationEndpoint
|
9
|
+
|
10
|
+
def request_path
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def request_method
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def enable_cors?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def request_header_match? env
|
25
|
+
headers_from(env)['X-Pact-Mock-Service']
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -35,17 +35,17 @@ module Pact
|
|
35
35
|
pact_json
|
36
36
|
end
|
37
37
|
|
38
|
+
def can_write?
|
39
|
+
consumer_name && provider_name && consumer_contract_details[:pact_dir]
|
40
|
+
end
|
41
|
+
|
38
42
|
private
|
39
43
|
|
40
44
|
attr_reader :consumer_contract_details, :pactfile_write_mode, :interactions, :logger
|
41
45
|
|
42
|
-
def pactfile_path
|
43
|
-
raise 'You must specify a consumer and provider name' unless (consumer_name && provider_name)
|
44
|
-
file_path consumer_name, provider_name, pact_dir
|
45
|
-
end
|
46
|
-
|
47
46
|
def update_pactfile
|
48
|
-
logger.
|
47
|
+
logger.info log_message
|
48
|
+
|
49
49
|
FileUtils.mkdir_p File.dirname(pactfile_path)
|
50
50
|
File.open(pactfile_path, 'w') do |f|
|
51
51
|
f.write pact_json
|
@@ -61,7 +61,7 @@ module Pact
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def interactions_for_new_consumer_contract
|
64
|
-
if
|
64
|
+
if updating?
|
65
65
|
merged_interactions = existing_interactions.dup
|
66
66
|
filter = Consumer::UpdatableInteractionsFilter.new(merged_interactions)
|
67
67
|
interactions.each {|i| filter << i }
|
@@ -117,12 +117,28 @@ module Pact
|
|
117
117
|
consumer_contract_details[:provider][:name]
|
118
118
|
end
|
119
119
|
|
120
|
+
def pactfile_path
|
121
|
+
raise 'You must specify a consumer and provider name' unless (consumer_name && provider_name)
|
122
|
+
file_path consumer_name, provider_name, pact_dir
|
123
|
+
end
|
124
|
+
|
120
125
|
def pact_dir
|
121
126
|
unless consumer_contract_details[:pact_dir]
|
122
127
|
raise ConsumerContractWriterError.new("Please indicate the directory to write the pact to by specifying the pact_dir field")
|
123
128
|
end
|
124
129
|
consumer_contract_details[:pact_dir]
|
125
130
|
end
|
126
|
-
end
|
127
131
|
|
128
|
-
|
132
|
+
def updating?
|
133
|
+
pactfile_write_mode == :update
|
134
|
+
end
|
135
|
+
|
136
|
+
def log_message
|
137
|
+
if updating?
|
138
|
+
"Updating pact for #{provider_name} at #{pactfile_path}"
|
139
|
+
else
|
140
|
+
"Writing pact for #{provider_name} to #{pactfile_path}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -9,11 +9,14 @@ module Pact
|
|
9
9
|
module MockService
|
10
10
|
class CLI < Thor
|
11
11
|
|
12
|
-
desc 'execute', "Start a mock service"
|
12
|
+
desc 'execute', "Start a mock service. If the consumer, provider and pact-dir options are provided, the pact will be written automatically on shutdown."
|
13
13
|
method_option :port, aliases: "-p", desc: "Port on which to run the service"
|
14
14
|
method_option :ssl, desc: "Use a self-signed SSL cert to run the service over HTTPS"
|
15
15
|
method_option :log, aliases: "-l", desc: "File to which to log output"
|
16
|
+
method_option :cors_enabled, aliases: "-o", desc: "If true, mocks requests will have access control origin headers set to '*'"
|
16
17
|
method_option :pact_dir, aliases: "-d", desc: "Directory to which the pacts will be written"
|
18
|
+
method_option :consumer, desc: "Consumer name"
|
19
|
+
method_option :provider, desc: "Provider name"
|
17
20
|
|
18
21
|
def execute
|
19
22
|
RunStandaloneMockService.call(options)
|
@@ -26,31 +29,73 @@ module Pact
|
|
26
29
|
class RunStandaloneMockService
|
27
30
|
|
28
31
|
def self.call options
|
32
|
+
new(options).call
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize options
|
36
|
+
@options = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def call
|
29
40
|
require 'pact/consumer/mock_service/app'
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
|
42
|
+
trap(:INT) { call_shutdown_hooks }
|
43
|
+
trap(:TERM) { call_shutdown_hooks }
|
44
|
+
|
45
|
+
Rack::Handler::WEBrick.run(mock_service, webbrick_opts)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :options
|
51
|
+
|
52
|
+
def mock_service
|
53
|
+
@mock_service ||= Pact::Consumer::MockService.new(service_options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def call_shutdown_hooks
|
57
|
+
begin
|
58
|
+
mock_service.write_pact_if_configured
|
59
|
+
rescue StandardError => e
|
60
|
+
$stderr.puts "Error writing pact on shutdown. #{e.class} - #{e.message}"
|
61
|
+
$stderr.puts e.backtrace.join("\n")
|
36
62
|
end
|
63
|
+
Rack::Handler::WEBrick.shutdown
|
64
|
+
end
|
65
|
+
|
66
|
+
def service_options
|
67
|
+
service_options = {
|
68
|
+
pact_dir: options[:pact_dir],
|
69
|
+
consumer: options[:consumer],
|
70
|
+
provider: options[:provider],
|
71
|
+
cors_enabled: options[:cors_enabled]
|
72
|
+
}
|
73
|
+
service_options[:log_file] = open_log_file if options[:log]
|
74
|
+
service_options
|
75
|
+
end
|
37
76
|
|
38
|
-
|
39
|
-
|
40
|
-
|
77
|
+
def open_log_file
|
78
|
+
FileUtils.mkdir_p File.dirname(options[:log])
|
79
|
+
log = File.open(options[:log], 'w')
|
80
|
+
log.sync = true
|
81
|
+
log
|
82
|
+
end
|
41
83
|
|
42
|
-
|
84
|
+
def webbrick_opts
|
85
|
+
opts = {
|
43
86
|
:Port => options[:port] || FindAPort.available_port,
|
44
87
|
:AccessLog => []
|
45
88
|
}
|
89
|
+
opts.merge!(ssl_opts) if options[:ssl]
|
90
|
+
opts
|
91
|
+
end
|
46
92
|
|
47
|
-
|
93
|
+
def ssl_opts
|
94
|
+
{
|
48
95
|
:SSLEnable => true,
|
49
|
-
:SSLCertName => [ %w[CN localhost] ]
|
50
|
-
|
51
|
-
Rack::Handler::WEBrick.run(mock_service, webbrick_opts)
|
96
|
+
:SSLCertName => [ %w[CN localhost] ]
|
97
|
+
}
|
52
98
|
end
|
53
99
|
end
|
54
|
-
|
55
100
|
end
|
56
101
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pact-mock_service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.3.pre.
|
4
|
+
version: 0.2.3.pre.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Fraser
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2015-01-
|
15
|
+
date: 2015-01-13 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: rack
|
@@ -269,11 +269,13 @@ files:
|
|
269
269
|
- lib/pact/consumer/mock_service/log_get.rb
|
270
270
|
- lib/pact/consumer/mock_service/missing_interactions_get.rb
|
271
271
|
- lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb
|
272
|
+
- lib/pact/consumer/mock_service/options.rb
|
272
273
|
- lib/pact/consumer/mock_service/pact_post.rb
|
273
274
|
- lib/pact/consumer/mock_service/rack_request_helper.rb
|
274
275
|
- lib/pact/consumer/mock_service/verification.rb
|
275
276
|
- lib/pact/consumer/mock_service/verification_get.rb
|
276
277
|
- lib/pact/consumer/mock_service/verified_interactions.rb
|
278
|
+
- lib/pact/consumer/mock_service/web_request_administration.rb
|
277
279
|
- lib/pact/consumer/mock_service_client.rb
|
278
280
|
- lib/pact/consumer/server.rb
|
279
281
|
- lib/pact/consumer_contract/consumer_contract_decorator.rb
|