pact 1.0.15 → 1.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG.md +29 -0
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +23 -6
  4. data/README.md +64 -19
  5. data/Rakefile +2 -2
  6. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +5 -2
  7. data/lib/pact/consumer/consumer_contract_builder.rb +2 -2
  8. data/lib/pact/consumer/mock_service/app.rb +2 -2
  9. data/lib/pact/consumer/mock_service_interaction_expectation.rb +5 -1
  10. data/lib/pact/consumer_contract/active_support_support.rb +29 -0
  11. data/lib/pact/consumer_contract/consumer_contract.rb +30 -6
  12. data/lib/pact/consumer_contract/interaction.rb +7 -1
  13. data/lib/pact/consumer_contract/request.rb +2 -2
  14. data/lib/pact/consumer_contract/service_consumer.rb +5 -1
  15. data/lib/pact/consumer_contract/service_provider.rb +5 -1
  16. data/lib/pact/matchers/matchers.rb +1 -1
  17. data/lib/pact/provider/pact_spec_runner.rb +100 -72
  18. data/lib/pact/provider/rspec.rb +17 -33
  19. data/lib/pact/provider/verification_report.rb +5 -1
  20. data/lib/pact/shared/request.rb +7 -3
  21. data/lib/pact/something_like.rb +5 -1
  22. data/lib/pact/tasks/task_helper.rb +9 -0
  23. data/lib/pact/tasks/verification_task.rb +74 -73
  24. data/lib/pact/term.rb +15 -1
  25. data/lib/pact/version.rb +1 -1
  26. data/lib/tasks/pact.rake +4 -2
  27. data/pact.gemspec +2 -1
  28. data/spec/features/production_spec.rb +1 -0
  29. data/spec/integration/consumer_spec.rb +1 -0
  30. data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +43 -0
  31. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +1 -1
  32. data/spec/lib/pact/consumer_contract/interaction_spec.rb +3 -3
  33. data/spec/lib/pact/matchers/matchers_spec.rb +10 -1
  34. data/spec/lib/pact/verification_task_spec.rb +102 -79
  35. data/spec/spec_helper.rb +11 -0
  36. data/tasks/pact-test.rake +1 -1
  37. data/tasks/spec.rake +8 -0
  38. metadata +40 -23
  39. data/lib/pact/json_warning.rb +0 -32
  40. data/spec/lib/pact/json_warning_spec.rb +0 -39
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@ Do this to generate your change history
2
2
 
3
3
  git log --date=relative --pretty=format:' * %h - %s (%an, %ad)'
4
4
 
5
+ ### 1.0.18 (29 October 2013)
6
+
7
+ * f2892d4 - Fixed bug where an exception is thrown when a key is not found and is attempted to be matched to a regexp (Beth, 60 seconds ago)
8
+
9
+ ### 1.0.17 (29 October 2013)
10
+
11
+ * 74bdf09 - Added missing require for Regexp json deserialisation (Beth, 3 minutes ago)
12
+ * d69482e - Removed JsonWarning for ActiveSupport JSON. (Beth, 3 hours ago)
13
+ * 5f72720 - Fixing ALL THE REGEXPS that ActiveSupport JSON broke. The pact gem should now serialise and deserialise its own JSON properly even when ActiveSupport is loaded by the call
14
+ * c3e6430 - Added config.ru parsing to best practices. (Beth, 9 hours ago)
15
+ * ae3a70f - DRYing up pact file reading code. (Beth, 11 hours ago)
16
+ * dc83557 - Fixing VerificationTask spec (Beth, 11 hours ago)
17
+ * bae379c - Added consumer name, provider name and request method to output of rspec. (Beth, 12 hours ago)
18
+ * 89c2620 - Adding spec filtering using PACT_DESCRIPTION and PACT_PROVIDER_STATE to pact:verify and pact:verify:at tasks. (Beth, 28 hours ago)
19
+ * 7ab43a9 - Adding puts to show when pact:verify specs are being filtered. (Beth, 28 hours ago)
20
+
21
+ ### 1.0.16 (28 October 2013)
22
+
23
+ * ce0d102 - Fixing specs after adding pact_helper and changing producer_state to provider_state. There is no producer here any more! Naughty producer. (Beth, 71 seconds ago)
24
+ * 90f7203 - Fixing bug where RSpec world was not cleared between pact:verify tasks. (Beth, 16 minutes ago)
25
+ * b323336 - Fixed bug where pact_helper option was not being passed into the PactSpecRunner from the task configuration (Beth, 4 hours ago)
26
+ * b1e78f5 - Added environment variable support. (Sergei Matheson, 3 days ago)
27
+ * 2b9f39a - Allow match criteria to be passed through to pact:verify tasks on command line (Sergei Matheson, 3 days ago)
28
+ * 2241f29 - Un-deprecating the support_file functionality after having discovered a valid use for it (project that contains two rack apps that have a pact with each other). Renamed op
29
+ * c94fc13 - Updating example provider state (Beth, 4 days ago)
30
+ * 6900f39 - Updating README with better client class example (Beth, 5 days ago)
31
+ * e41f755 - Update README.md (bskurrie, 5 days ago)
32
+ * 2abcce4 - Adding to pact best practices. (Beth, 5 days ago)
33
+
5
34
  ### 1.0.15 (22 October 2013)
6
35
 
7
36
  * 6800a58 - Updating README with latest TODOs (Beth, 2 hours ago)
data/Gemfile CHANGED
@@ -1,5 +1,4 @@
1
1
  source 'https://rubygems.org'
2
- source 'http://rea-rubygems'
3
2
 
4
3
  # Specify your gem's dependencies in pact.gemspec
5
4
  gemspec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pact (1.0.15)
4
+ pact (1.0.18)
5
5
  awesome_print (~> 1.1.0)
6
6
  find_a_port (~> 1.0.1)
7
7
  json
@@ -13,24 +13,37 @@ PATH
13
13
 
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
- remote: http://rea-rubygems/
17
16
  specs:
17
+ activesupport (4.0.0)
18
+ i18n (~> 0.6, >= 0.6.4)
19
+ minitest (~> 4.2)
20
+ multi_json (~> 1.3)
21
+ thread_safe (~> 0.1)
22
+ tzinfo (~> 0.3.37)
18
23
  addressable (2.3.5)
24
+ atomic (1.1.14)
19
25
  awesome_print (1.1.0)
20
26
  coderay (1.0.9)
27
+ columnize (0.3.6)
21
28
  crack (0.4.1)
22
29
  safe_yaml (~> 0.9.0)
23
30
  daemons (1.1.9)
31
+ debugger (1.6.2)
32
+ columnize (>= 0.3.1)
33
+ debugger-linecache (~> 1.2.0)
34
+ debugger-ruby_core_source (~> 1.2.3)
35
+ debugger-linecache (1.2.0)
36
+ debugger-ruby_core_source (1.2.3)
24
37
  diff-lcs (1.2.4)
25
38
  eventmachine (1.0.3)
26
39
  fakefs (0.4.2)
27
40
  find_a_port (1.0.1)
28
- geminabox-client (0.0.2)
29
- multipart-post
30
41
  hashie (2.0.5)
42
+ i18n (0.6.5)
31
43
  json (1.8.1)
32
44
  method_source (0.8.2)
33
- multipart-post (1.2.0)
45
+ minitest (4.7.5)
46
+ multi_json (1.8.2)
34
47
  pry (0.9.12.2)
35
48
  coderay (~> 1.0.5)
36
49
  method_source (~> 0.8)
@@ -57,6 +70,9 @@ GEM
57
70
  eventmachine (>= 1.0.0)
58
71
  rack (>= 1.5.0)
59
72
  thor (0.18.1)
73
+ thread_safe (0.1.3)
74
+ atomic
75
+ tzinfo (0.3.38)
60
76
  webmock (1.9.3)
61
77
  addressable (>= 2.2.7)
62
78
  crack (>= 0.3.2)
@@ -65,8 +81,9 @@ PLATFORMS
65
81
  ruby
66
82
 
67
83
  DEPENDENCIES
84
+ activesupport
85
+ debugger
68
86
  fakefs (~> 0.4)
69
- geminabox-client
70
87
  hashie (~> 2.0)
71
88
  pact!
72
89
  pry
data/README.md CHANGED
@@ -47,11 +47,25 @@ class MyServiceProviderClient
47
47
  include HTTParty
48
48
  base_uri 'http://my-service'
49
49
 
50
- def get_text_from_something
51
- JSON.parse(self.class.get("/something").body)['text']
50
+ def get_something
51
+ name = JSON.parse(self.class.get("/something").body)['name']
52
+ Something.new(name)
52
53
  end
53
54
  end
54
55
 
56
+ class Something
57
+ attr_reader :name
58
+
59
+ def intialize name
60
+ @name = name
61
+ end
62
+
63
+ def == other
64
+ other.is_a?(Something) && other.name == name
65
+ end
66
+ end
67
+
68
+
55
69
  # The following code creates a service on localhost:1234 which will respond to your application's queries
56
70
  # over HTTP as if it were the real "My Service Provider" app. It also creats a mock service provider object
57
71
  # which you will use to set up your expectations. The method name to access the mock service provider
@@ -76,7 +90,7 @@ describe MyServiceProviderClient, :pact => true do
76
90
  MyServiceProviderClient.base_uri 'localhost:1234'
77
91
  end
78
92
 
79
- describe "get_text_from_something" do
93
+ describe "get_something" do
80
94
  before do
81
95
  my_service_provider.
82
96
  .given("something exists")
@@ -85,15 +99,12 @@ describe MyServiceProviderClient, :pact => true do
85
99
  .will_respond_with(
86
100
  status: 200,
87
101
  headers: { 'Content-Type' => 'application/json' },
88
- body: {text: 'A thing!', something_else: 'Woot!'}
102
+ body: {name: 'A small something'}
89
103
  )
90
104
  end
91
105
 
92
- it "returns the text from something" do
93
- # Use your service's client to make the request, rather than hand crafting a HTTP request,
94
- # so that you can be sure that the request that will be recorded in the pact file
95
- # is one that is actually made by your app.
96
- expect(MyServiceProviderClient.get_text_from_something).to eql("A thing!")
106
+ it "returns a Something" do
107
+ expect(MyServiceProviderClient.get_something).to eq(Something.new('A small something'))
97
108
  end
98
109
 
99
110
  end
@@ -147,14 +158,14 @@ For example, some code that creates the pact in a consumer project might look li
147
158
  my_service.
148
159
  given("a thing exists").
149
160
  upon_receiving("a request for a thing").
150
- with({method: 'get', path: '/thing'}).
151
- will_respond_with({status: 200, :body => {thing: "yay!"} })
161
+ with(method: 'get', path: '/thing').
162
+ will_respond_with(status: 200, :body => {thing: "yay!"} )
152
163
 
153
164
  my_service.
154
165
  given("a thing does not exist").
155
166
  upon_receiving("a request for a thing").
156
- with({method: 'get', path: '/thing'}).
157
- will_respond_with({status: 404, :body => {error: "There is no thing :("} })
167
+ with(method: 'get', path: '/thing').
168
+ will_respond_with(status: 404, :body => {error: "There is no thing :("} )
158
169
  ```
159
170
 
160
171
  To define service provider states that create the right data for "a thing exists" and "a thing does not exist", write the following in the service provider project.
@@ -225,23 +236,57 @@ The pact.uri may be a local file system path or a remote URL.
225
236
 
226
237
  ## Pact best practices
227
238
 
228
- ### Ensure all calls to the provider go through your provider client class
239
+ ### In your consumer project
229
240
 
230
- Do not hand create any HTTP requests in your consumer app or specs. Testing through your provider client class gives you the assurance that your consumer app will be creating exactly the HTTP requests that you think it should.
241
+ #### Publish your pacts as artifacts on your CI machine
231
242
 
232
- ### Do not stub your database calls in the provider project
243
+ This makes the pact available via URL, which your provider build can then use when it runs pact:verify.
233
244
 
234
- This is the best time for you to test your database integration. If you stub your database calls, you are getting little more assurance that the real end-to-end will work than if you'd used a unit test. It's the appropriate time to incur the overhead of a database call.
245
+ #### Ensure all calls to the provider go through your provider client class
246
+
247
+ Do not hand create any HTTP requests in your consumer app or specs. Testing through your provider client class gives you the assurance that your consumer app will be creating exactly the HTTP requests that you think it should.
235
248
 
236
- ### Use factories to create your expected models in your consumer project
249
+ #### Use factories to create your expected models
237
250
 
238
251
  Sure, you've checked that your client deserialises the HTTP response into the object you expect, but then you need to make sure in your other tests where you stub your client that you're stubbing it with a valid object. The best way to do this is to use factories for all your tests.
239
252
 
240
- ### Publish your pacts as artifacts on your CI machine
253
+ ### In your provider project
254
+
255
+ #### Use the pact artifact published by your consumer's CI build to verify the provider
241
256
 
242
257
  Configure the pact_uri in the Pact.service_provider block with the pact artifact URL of your last successful build. This way you're only verifying green builds. No point verifying a broken one.
243
258
  (Watch this space - pact-broker coming soon, so we don't have to use messy build box artifact URLs)
244
259
 
260
+ #### Add pact:verify to your default rake task
261
+
262
+ It should run with all your other tests. If an integration is broken, you want to know about it *before* you check in.
263
+
264
+ #### Load your app from the config.ru file
265
+
266
+ Your config.ru file may mount your app at a specified path. e.g.
267
+
268
+ ```
269
+ run Rack::URLMap.new( '/some-path' => MyServiceProvider::API )
270
+ ```
271
+
272
+ To avoid duplicating this code, and potentially letting your tests get out of sync with the config.ru file, you can parse the config.ru file and use the returned app in your service provider definition. e.g.
273
+
274
+ ```
275
+ def app_in_config_ru
276
+ app, options = Rack::Builder.parse_file('config.ru')
277
+ app
278
+ end
279
+
280
+ Pact.service_provider "My Service Provider" do
281
+ app { app_in_config_ru }
282
+ end
283
+ ```
284
+
285
+ Be aware that the Rack::Builder.parse_file seems to require files even if they have already been required, so make sure your boot files are idempotent.
286
+
287
+ #### Do not stub your database calls in the provider project
288
+
289
+ This is the best time for you to test your database integration. If you stub your database calls, you are getting little more assurance that the real end-to-end will work than if you'd used a unit test. It's the appropriate time to incur the overhead of a database call.
245
290
 
246
291
  ## Advanced
247
292
 
data/Rakefile CHANGED
@@ -18,16 +18,16 @@ module Bundler
18
18
  end
19
19
  Bundler::GemHelper.install_tasks
20
20
  require 'rspec/core/rake_task'
21
- require 'geminabox-client'
22
21
 
23
22
  Dir.glob('lib/tasks/**/*.rake').each { |task| load task }
24
23
  Dir.glob('tasks/**/*.rake').each { |task| load task }
25
24
  RSpec::Core::RakeTask.new(:spec)
26
25
 
27
- task :default => [:spec, 'pact:tests']
26
+ task :default => [:spec, :spec_with_active_support, 'pact:tests']
28
27
 
29
28
  desc "Release to REA gems host"
30
29
  task :publish => :build do
31
30
  gem_file = "pkg/pact-#{Pact::VERSION}.gem"
31
+ require 'geminabox-client'
32
32
  Geminabox::Client.new('http://rea-rubygems').upload(gem_file)
33
33
  end
@@ -1,9 +1,12 @@
1
1
  Pact.provider_states_for "Zoo App" do
2
2
  provider_state "there are alligators" do
3
- #AlligatorRepo.save(Alligator.name("Mary"))
3
+ set_up do
4
+ #AlligatorRepo.save(Alligator.name("Mary"))
5
+ end
6
+
4
7
  end
5
8
 
6
9
  provider_state "there is not an alligator named Mary" do
7
-
10
+ no_op
8
11
  end
9
12
  end
@@ -100,7 +100,7 @@ module Pact
100
100
  info_and_puts "*****************************************************************************"
101
101
  info_and_puts "Updating existing file .#{consumer_contract.pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
102
102
  info_and_puts "Only interactions defined in this test run will be updated."
103
- 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 rake is next run."
103
+ 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."
104
104
  info_and_puts "*****************************************************************************"
105
105
  rescue StandardError => e
106
106
  warn_and_stderr "Could not load existing consumer contract from #{consumer_contract.pactfile_path} due to #{e}"
@@ -115,7 +115,7 @@ module Pact
115
115
  end
116
116
 
117
117
  def existing_consumer_contract
118
- Pact::ConsumerContract.from_json(File.read(consumer_contract.pactfile_path))
118
+ Pact::ConsumerContract.from_uri(consumer_contract.pactfile_path)
119
119
  end
120
120
 
121
121
  end
@@ -61,8 +61,8 @@ module Pact
61
61
  relevant_handler = @handlers.detect { |handler| handler.match? env }
62
62
  response = relevant_handler.respond env
63
63
  rescue Exception => e
64
- @logger.ap 'Error ocurred in mock service:'
65
- @logger.ap e
64
+ @logger.error 'Error ocurred in mock service:'
65
+ @logger.ap e, :error
66
66
  @logger.ap e.backtrace
67
67
  raise e
68
68
  end
@@ -11,7 +11,7 @@ module Pact
11
11
  @interaction = interaction
12
12
  end
13
13
 
14
- def as_json
14
+ def to_hash
15
15
  hash = {:description => interaction.description}
16
16
  hash[:provider_state] = interaction.provider_state if interaction.provider_state
17
17
  options = interaction.request.options.empty? ? {} : { options: interaction.request.options}
@@ -20,6 +20,10 @@ module Pact
20
20
  hash
21
21
  end
22
22
 
23
+ def as_json options = {}
24
+ to_hash
25
+ end
26
+
23
27
  def to_json opts = {}
24
28
  as_json.to_json(opts)
25
29
  end
@@ -0,0 +1,29 @@
1
+ module Pact
2
+ module ActiveSupportSupport
3
+
4
+ extend self
5
+
6
+ def fix_all_the_things thing
7
+ if thing.is_a?(Regexp)
8
+ fix_regexp(thing)
9
+ elsif thing.is_a?(Array)
10
+ thing.each{ | it | fix_all_the_things it }
11
+ elsif thing.is_a?(Hash)
12
+ thing.values.each{ | it | fix_all_the_things it }
13
+ elsif thing.class.name.start_with?("Pact")
14
+ thing.instance_variables.collect{ | iv_name | thing.instance_variable_get(iv_name)}.each do | iv |
15
+ fix_all_the_things iv
16
+ end
17
+ end
18
+ thing
19
+ end
20
+
21
+ def fix_regexp regexp
22
+ def regexp.as_json options = {}
23
+ {:json_class => 'Regexp', "o" => self.options, "s" => self.source }
24
+ end
25
+ regexp
26
+ end
27
+
28
+ end
29
+ end
@@ -1,20 +1,41 @@
1
1
  require 'pact/logging'
2
- require 'pact/json_warning'
3
2
  require 'pact/something_like'
4
3
  require 'pact/symbolize_keys'
5
4
  require 'pact/term'
6
5
  require 'pact/version'
7
6
  require 'date'
7
+ require 'json/add/regexp'
8
8
  require 'open-uri'
9
9
  require_relative 'service_consumer'
10
10
  require_relative 'service_provider'
11
11
  require_relative 'interaction'
12
12
  require_relative 'request'
13
+ require_relative 'active_support_support'
13
14
 
14
15
 
15
16
 
16
17
  module Pact
17
18
 
19
+ module PactFile
20
+ extend self
21
+ def read uri, options = {}
22
+ pact = open(uri) { | file | file.read }
23
+ if options[:save_pactfile_to_tmp]
24
+ save_pactfile_to_tmp pact, ::File.basename(uri)
25
+ end
26
+ pact
27
+ rescue StandardError => e
28
+ $stderr.puts "Error reading file from #{uri}"
29
+ $stderr.puts "#{e.to_s} #{e.backtrace.join("\n")}"
30
+ raise e
31
+ end
32
+
33
+ def save_pactfile_to_tmp pact, name
34
+ ::FileUtils.mkdir_p Pact.configuration.tmp_dir
35
+ ::File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
36
+ end
37
+ end
38
+
18
39
  #TODO move to external file for reuse
19
40
  module FileName
20
41
  def file_name consumer_name, provider_name
@@ -30,8 +51,8 @@ module Pact
30
51
 
31
52
  include SymbolizeKeys
32
53
  include Logging
33
- include JsonWarning
34
54
  include FileName
55
+ include ActiveSupportSupport
35
56
 
36
57
  attr_accessor :interactions
37
58
  attr_accessor :consumer
@@ -43,7 +64,7 @@ module Pact
43
64
  @provider = attributes[:provider]
44
65
  end
45
66
 
46
- def as_json(options = {})
67
+ def to_hash
47
68
  {
48
69
  provider: @provider.as_json,
49
70
  consumer: @consumer.as_json,
@@ -56,6 +77,10 @@ module Pact
56
77
  }
57
78
  end
58
79
 
80
+ def as_json(options = {})
81
+ fix_all_the_things to_hash
82
+ end
83
+
59
84
  def to_json(options = {})
60
85
  as_json.to_json(options)
61
86
  end
@@ -74,8 +99,8 @@ module Pact
74
99
  from_hash(deserialised_object)
75
100
  end
76
101
 
77
- def self.from_uri uri
78
- from_json(open(uri){ |f| f.read })
102
+ def self.from_uri uri, options = {}
103
+ from_json(Pact::PactFile.read(uri, options))
79
104
  end
80
105
 
81
106
  def self.maintain_backwards_compatiblity_with_producer_keys string
@@ -113,7 +138,6 @@ module Pact
113
138
 
114
139
  def update_pactfile
115
140
  logger.debug "Updating pact file for #{provider.name} at #{pactfile_path}"
116
- check_for_active_support_json
117
141
  File.open(pactfile_path, 'w') do |f|
118
142
  f.write JSON.pretty_generate(self)
119
143
  end