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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e4c79027ecd3c1da41ed2b2305c7d02ab8422bb
4
- data.tar.gz: b6900749dbe4f721b7c958d2522caa6fdabf0fe4
3
+ metadata.gz: 200c03efe0a34bab41ab409685601a5d069e5319
4
+ data.tar.gz: ffa20a890680ccf3dc0bc3c1aa20a78de93e64f6
5
5
  SHA512:
6
- metadata.gz: 8945a97c67fc3f7d6531a963f9325690781e95914f37b41e8ab091fc45bdeaedec1907ec6fc84d42c12fe660f7a343a0cec7396bf519b378cfed4a4abd0b4795
7
- data.tar.gz: a3c82d60ef644fd1057f2188d6d86571f026d1e59b8fc7333e8a14b91b8b397affb4558bfdc6dfb21546604c45d36197cc22f6eb173f0c509108ab8bf268d57c
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
- Define a pact between service consumers and providers, enabling "consumer driven contract" testing.
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
- 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.
5
+ The mock service provides the following endpoints:
6
6
 
7
- This allows testing of both sides of an integration point using fast unit tests.
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
- 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
-
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
- ### Service Consumer project
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
- ```ruby
163
- class MyServiceProviderClient
164
- include HTTParty
165
- base_uri 'http://my-service'
18
+ $ gem install pact-mock_service
19
+ $ pact-mock-service --port 1234
166
20
 
167
- def get_something
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
- #### 7. Run the specs again.
23
+ ### With Ruby on Windows
175
24
 
176
- Green! You now have a pact file that can be used to verify your expectations of the provider project.
25
+ Check out the wiki page [here][install-windows].
177
26
 
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.
27
+ #### With SSL
182
28
 
183
- ### Service Provider project
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
- #### 1. Create the skeleton API classes
31
+ $ pact-mock-service --port 1234 --ssl
186
32
 
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?
33
+ ### Mac OSX and Linux, without Ruby
188
34
 
189
- #### 2. Tell your provider that it needs to honour the pact file you made earlier
35
+ See the [releases][releases] page for the latest standalone executables.
190
36
 
191
- Require "pact/tasks" in your Rakefile.
37
+ ### Windows, without Ruby
192
38
 
193
- ```ruby
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
- 1. Fork it
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
- def call env
69
- response = []
70
- begin
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
- response
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
@@ -10,4 +10,4 @@ module Pact
10
10
 
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -1,10 +1,10 @@
1
1
  require 'pact/consumer/mock_service/rack_request_helper'
2
- require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+ require 'pact/consumer/mock_service/web_request_administration'
3
3
 
4
4
  module Pact
5
5
  module Consumer
6
6
 
7
- class InteractionDelete < MockServiceAdministrationEndpoint
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/mock_service_administration_endpoint'
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 < MockServiceAdministrationEndpoint
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/mock_service_administration_endpoint'
1
+ require 'pact/consumer/mock_service/web_request_administration'
2
2
 
3
3
  module Pact
4
4
  module Consumer
5
- class LogGet < MockServiceAdministrationEndpoint
5
+ class LogGet < WebRequestAdministration
6
6
 
7
7
  include RackRequestHelper
8
8
 
@@ -1,10 +1,10 @@
1
- require 'pact/consumer/mock_service/mock_service_administration_endpoint'
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 < MockServiceAdministrationEndpoint
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
- headers_from(env)['X-Pact-Mock-Service'] &&
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 < MockServiceAdministrationEndpoint
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,3 +1,4 @@
1
+ require 'cgi/core'
1
2
  module Pact
2
3
  module Consumer
3
4
 
@@ -1,8 +1,8 @@
1
- require 'pact/consumer/mock_service/mock_service_administration_endpoint'
1
+ require 'pact/consumer/mock_service/web_request_administration'
2
2
 
3
3
  module Pact
4
4
  module Consumer
5
- class VerificationGet < MockServiceAdministrationEndpoint
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.debug "Updating pact file for #{provider_name} at #{pactfile_path}"
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 pactfile_write_mode == :update
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
- end
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
- service_options = {pact_dir: options[:pact_dir]}
31
- if options[:log]
32
- FileUtils.mkdir_p File.dirname(options[:log])
33
- log = File.open(options[:log], 'w')
34
- log.sync = true
35
- service_options[:log_file] = log
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
- mock_service = Pact::Consumer::MockService.new(service_options)
39
- trap(:INT) { Rack::Handler::WEBrick.shutdown }
40
- trap(:TERM) { Rack::Handler::WEBrick.shutdown }
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
- webbrick_opts = {
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
- webbrick_opts.merge!({
93
+ def ssl_opts
94
+ {
48
95
  :SSLEnable => true,
49
- :SSLCertName => [ %w[CN localhost] ] }) if options[:ssl]
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
@@ -1,5 +1,5 @@
1
1
  module Pact
2
2
  module MockService
3
- VERSION = "0.2.3-rc1"
3
+ VERSION = "0.2.3.pre.rc2"
4
4
  end
5
5
  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.rc1
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-03 00:00:00.000000000 Z
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