pact 1.0.30 → 1.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +2 -2
  3. data/Gemfile.lock +0 -7
  4. data/README.md +50 -53
  5. data/Rakefile +1 -25
  6. data/documentation/Testing with pact.png +0 -0
  7. data/documentation/faq.md +45 -0
  8. data/documentation/raq.md +39 -0
  9. data/documentation/terminology.md +25 -0
  10. data/example/animal-service/Gemfile +4 -4
  11. data/example/animal-service/Gemfile.lock +20 -15
  12. data/example/animal-service/Rakefile +1 -1
  13. data/example/animal-service/config.ru +3 -0
  14. data/example/animal-service/db/animal_db.sqlite3 +0 -0
  15. data/example/animal-service/db/animals_db.sqlite3 +0 -0
  16. data/example/animal-service/lib/animal_service/animal_repository.rb +16 -0
  17. data/example/animal-service/lib/animal_service/api.rb +28 -0
  18. data/example/animal-service/lib/animal_service/db.rb +5 -0
  19. data/example/animal-service/spec/service_consumers/pact_helper.rb +10 -16
  20. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +13 -3
  21. data/example/zoo-app/Gemfile +0 -2
  22. data/example/zoo-app/Gemfile.lock +7 -8
  23. data/example/zoo-app/lib/zoo_app/animal_service_client.rb +8 -4
  24. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +18 -64
  25. data/example/zoo-app/spec/service_providers/animal_service_client_spec.rb +76 -0
  26. data/example/zoo-app/spec/service_providers/pact_helper.rb +1 -1
  27. data/example/zoo-app/spec/spec_helper.rb +0 -2
  28. data/lib/pact/app.rb +4 -2
  29. data/lib/pact/consumer/configuration.rb +2 -2
  30. data/lib/pact/consumer/consumer_contract_builder.rb +2 -1
  31. data/lib/pact/consumer/interactions_filter.rb +7 -0
  32. data/lib/pact/consumer/mock_service/app.rb +7 -2
  33. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +6 -1
  34. data/lib/pact/consumer/mock_service/interaction_post.rb +1 -1
  35. data/lib/pact/consumer/mock_service/rack_request_helper.rb +1 -1
  36. data/lib/pact/consumer/rspec.rb +9 -15
  37. data/lib/pact/consumer/rspec/full_example_description.rb +28 -0
  38. data/lib/pact/consumer/spec_hooks.rb +31 -0
  39. data/lib/pact/consumer_contract/consumer_contract.rb +2 -31
  40. data/lib/pact/consumer_contract/file_name.rb +13 -0
  41. data/lib/pact/consumer_contract/pact_file.rb +24 -0
  42. data/lib/pact/provider/matchers.rb +3 -1
  43. data/lib/pact/provider/provider_state.rb +43 -20
  44. data/lib/pact/version.rb +1 -1
  45. data/pact.gemspec +0 -1
  46. data/spec/features/consumption_spec.rb +5 -0
  47. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +162 -147
  48. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  49. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +3 -3
  50. metadata +19 -25
  51. data/example/zoo-app/spec/service_providers/animal_service_spec.rb +0 -92
@@ -44,7 +44,7 @@ module Pact::Consumer
44
44
  def has_pact_with service_provider_name, &block
45
45
  ServiceProvider.build(service_provider_name, name, &block)
46
46
  end
47
- end
47
+ end
48
48
 
49
49
  def finalize
50
50
  validate
@@ -76,7 +76,7 @@ module Pact::Consumer
76
76
  @consumer_name = consumer_name
77
77
  end
78
78
 
79
- dsl do
79
+ dsl do
80
80
  def mock_service name, &block
81
81
  self.service = MockService.build(name, consumer_name, self.name, &block)
82
82
  end
@@ -11,7 +11,7 @@ module Pact
11
11
 
12
12
  include Pact::Logging
13
13
 
14
- attr_reader :consumer_contract
14
+ attr_reader :consumer_contract, :mock_service_base_url
15
15
 
16
16
  def initialize(attributes)
17
17
  @interaction_builder = nil
@@ -22,6 +22,7 @@ module Pact
22
22
  )
23
23
  @consumer_contract.interactions = interactions_for_new_consumer_contract(attributes[:pactfile_write_mode])
24
24
  @interactions_filter = filter(@consumer_contract.interactions, attributes[:pactfile_write_mode])
25
+ @mock_service_base_url = "http://localhost:#{attributes[:port]}"
25
26
  end
26
27
 
27
28
  def given(provider_state)
@@ -1,3 +1,10 @@
1
+ #
2
+ # When running in pactfile_write_mode :overwrite, all interactions are cleared from the
3
+ # pact file, and all new interactions should be distinct (unique description and provider state).
4
+ # When running in pactfile_write_mode :update, an interaction with the same description
5
+ # and provider state as an existing one will just overwrite that one interaction.
6
+ #
7
+
1
8
  module Pact
2
9
  module Consumer
3
10
 
@@ -39,7 +39,7 @@ module Pact
39
39
  end
40
40
 
41
41
  def configure_logger options
42
- options = {log_file: STDOUT}.merge options
42
+ options = {log_file: $stdout}.merge options
43
43
  log_stream = options[:log_file]
44
44
  @logger = Logger.new log_stream
45
45
  @logger.level = Pact.configuration.logger.level
@@ -60,10 +60,15 @@ module Pact
60
60
  begin
61
61
  relevant_handler = @handlers.detect { |handler| handler.match? env }
62
62
  response = relevant_handler.respond env
63
- rescue Exception => e
63
+ rescue StandardError => e
64
64
  @logger.error 'Error ocurred in mock service:'
65
65
  @logger.ap e, :error
66
66
  @logger.ap e.backtrace
67
+ response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
68
+ rescue Exception => e
69
+ @logger.error 'Exception ocurred in mock service:'
70
+ @logger.ap e, :error
71
+ @logger.ap e.backtrace
67
72
  raise e
68
73
  end
69
74
  response
@@ -2,6 +2,11 @@ require 'pact/matchers/diff_decorator'
2
2
 
3
3
  module Pact
4
4
  module Consumer
5
+
6
+ # Presents the differences between an actual request, and a list of
7
+ # expected interactions where the methods and paths match the actual request.
8
+ # This is used to display a helpful message to the user when a request
9
+ # comes in that doesn't match any of the expected interactions.
5
10
  class InteractionMismatch
6
11
 
7
12
  attr_accessor :candidate_interactions, :actual_request
@@ -24,7 +29,7 @@ module Pact
24
29
 
25
30
  def short_summary
26
31
  mismatched_attributes = candiate_diffs.collect(&:mismatched_attributes).flatten.uniq.join(", ").reverse.sub(",", "dna ").reverse #OMG what a hack!
27
- actual_request.method_and_path + " (#{mismatched_attributes} did not match)"
32
+ actual_request.method_and_path + " (request #{mismatched_attributes} did not match)"
28
33
  end
29
34
 
30
35
  private
@@ -24,7 +24,7 @@ module Pact
24
24
  interaction_list.add interaction
25
25
  logger.info "Registered expected interaction #{interaction.request.method_and_path} for #{name}"
26
26
  logger.ap interaction.as_json
27
- [200, {}, ['Added interactions']]
27
+ [200, {}, ['Added interaction']]
28
28
  end
29
29
  end
30
30
  end
@@ -4,7 +4,7 @@ module Pact
4
4
  module RackRequestHelper
5
5
  REQUEST_KEYS = {
6
6
  'REQUEST_METHOD' => :method,
7
- 'REQUEST_PATH' => :path,
7
+ 'PATH_INFO' => :path,
8
8
  'QUERY_STRING' => :query,
9
9
  'rack.input' => :body
10
10
  }
@@ -1,4 +1,6 @@
1
1
  require 'pact/consumer'
2
+ require 'pact/consumer/spec_hooks'
3
+ require 'pact/consumer/rspec/full_example_description'
2
4
 
3
5
  module Pact
4
6
  module Consumer
@@ -8,33 +10,25 @@ module Pact
8
10
  end
9
11
  end
10
12
 
13
+ hooks = Pact::Consumer::SpecHooks.new
14
+
15
+
11
16
  RSpec.configure do |config|
12
17
  config.include Pact::Consumer::RSpec, :pact => true
13
18
 
14
19
  config.before :all, :pact => true do
15
- Pact::Consumer::AppManager.instance.spawn_all
16
- FileUtils.mkdir_p Pact.configuration.pact_dir
20
+ hooks.before_all
17
21
  end
18
22
 
19
23
  config.before :each, :pact => true do | example |
20
- example_description = "#{example.example.example_group.description} #{example.example.description}"
21
- Pact.configuration.logger.info "Clearing all expectations"
22
- Pact::Consumer::AppManager.instance.ports_of_mock_services.each do | port |
23
- Pact::Consumer::MockServiceClient.clear_interactions port, example_description
24
- end
24
+ hooks.before_each Pact::Consumer::RSpec::FullExampleDescription.new(example).to_s
25
25
  end
26
26
 
27
27
  config.after :each, :pact => true do | example |
28
- example_description = "#{example.example.example_group.description} #{example.example.description}"
29
- Pact.configuration.logger.info "Verifying interactions for #{example_description}"
30
- Pact.configuration.provider_verifications.each do | provider_verification |
31
- provider_verification.call example_description
32
- end
28
+ hooks.after_each Pact::Consumer::RSpec::FullExampleDescription.new(example).to_s
33
29
  end
34
30
 
35
31
  config.after :suite do
36
- Pact.configuration.logger.info "After suite"
37
- Pact::Consumer::AppManager.instance.kill_all
38
- Pact::Consumer::AppManager.instance.clear_all
32
+ hooks.after_suite
39
33
  end
40
34
  end
@@ -0,0 +1,28 @@
1
+ #
2
+ # Creates the full description for an example group
3
+ #
4
+ module Pact
5
+ module Consumer
6
+ module RSpec
7
+ class FullExampleDescription
8
+
9
+ def initialize example
10
+ @example = example
11
+ end
12
+
13
+ def parent_group_descriptions
14
+ @example.example.example_group.parent_groups.collect(&:description).reverse
15
+ end
16
+
17
+ def example_description
18
+ @example.example.description
19
+ end
20
+
21
+ def to_s
22
+ (parent_group_descriptions + [example_description]).join(" ")
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Pact
2
+ module Consumer
3
+ class SpecHooks
4
+
5
+ def before_all
6
+ Pact::Consumer::AppManager.instance.spawn_all
7
+ FileUtils.mkdir_p Pact.configuration.pact_dir
8
+ end
9
+
10
+ def before_each example_description
11
+ Pact.configuration.logger.info "Clearing all expectations"
12
+ Pact::Consumer::AppManager.instance.ports_of_mock_services.each do | port |
13
+ Pact::Consumer::MockServiceClient.clear_interactions port, example_description
14
+ end
15
+ end
16
+
17
+ def after_each example_description
18
+ Pact.configuration.logger.info "Verifying interactions for #{example_description}"
19
+ Pact.configuration.provider_verifications.each do | provider_verification |
20
+ provider_verification.call example_description
21
+ end
22
+ end
23
+
24
+ def after_suite
25
+ Pact.configuration.logger.info "After suite"
26
+ Pact::Consumer::AppManager.instance.kill_all
27
+ Pact::Consumer::AppManager.instance.clear_all
28
+ end
29
+ end
30
+ end
31
+ end
@@ -11,42 +11,13 @@ require_relative 'service_provider'
11
11
  require_relative 'interaction'
12
12
  require_relative 'request'
13
13
  require_relative 'active_support_support'
14
+ require_relative 'pact_file'
15
+ require_relative 'file_name'
14
16
 
15
17
 
16
18
 
17
19
  module Pact
18
20
 
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
-
39
- #TODO move to external file for reuse
40
- module FileName
41
- def file_name consumer_name, provider_name
42
- "#{filenamify(consumer_name)}-#{filenamify(provider_name)}.json"
43
- end
44
-
45
- def filenamify name
46
- name.downcase.gsub(/\s/, '_')
47
- end
48
- end
49
-
50
21
  class ConsumerContract
51
22
 
52
23
  include SymbolizeKeys
@@ -0,0 +1,13 @@
1
+ module Pact
2
+
3
+ #TODO move to external file for reuse
4
+ module FileName
5
+ def file_name consumer_name, provider_name
6
+ "#{filenamify(consumer_name)}-#{filenamify(provider_name)}.json"
7
+ end
8
+
9
+ def filenamify name
10
+ name.downcase.gsub(/\s/, '_')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+ module Pact
2
+
3
+ module PactFile
4
+
5
+ extend self
6
+
7
+ def read uri, options = {}
8
+ pact = open(uri) { | file | file.read }
9
+ if options[:save_pactfile_to_tmp]
10
+ save_pactfile_to_tmp pact, ::File.basename(uri)
11
+ end
12
+ pact
13
+ rescue StandardError => e
14
+ $stderr.puts "Error reading file from #{uri}"
15
+ $stderr.puts "#{e.to_s} #{e.backtrace.join("\n")}"
16
+ raise e
17
+ end
18
+
19
+ def save_pactfile_to_tmp pact, name
20
+ ::FileUtils.mkdir_p Pact.configuration.tmp_dir
21
+ ::File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
22
+ end
23
+ end
24
+ end
@@ -1,4 +1,5 @@
1
1
  require 'pact/term'
2
+ require 'pact/consumer_contract/active_support_support'
2
3
  require 'awesome_print'
3
4
  require 'pact/matchers'
4
5
  require 'awesome_print'
@@ -6,6 +7,7 @@ require 'rspec'
6
7
 
7
8
  RSpec::Matchers.define :match_term do |expected|
8
9
  include Pact::Matchers
10
+ include Pact::ActiveSupportSupport
9
11
 
10
12
  match do |actual|
11
13
  if (difference = diff(expected, actual)).any?
@@ -17,7 +19,7 @@ RSpec::Matchers.define :match_term do |expected|
17
19
  end
18
20
 
19
21
  failure_message_for_should do | actual |
20
- @message.ai
22
+ fix_json_formatting @message.to_json
21
23
  end
22
24
 
23
25
  end
@@ -1,3 +1,5 @@
1
+ require 'pact/shared/dsl'
2
+
1
3
  module Pact
2
4
  module Provider
3
5
 
@@ -15,7 +17,7 @@ module Pact
15
17
 
16
18
  class ProviderStates
17
19
  def self.provider_state name, &block
18
- ProviderState.new(name, current_namespaces.join('.'), &block)
20
+ ProviderState.build(name, current_namespaces.join('.'), &block)
19
21
  end
20
22
 
21
23
  def self.register name, provider_state
@@ -41,10 +43,7 @@ module Pact
41
43
  attr_accessor :name
42
44
  attr_accessor :namespace
43
45
 
44
-
45
- def register
46
- ProviderStates.register(namespaced(name), self)
47
- end
46
+ extend Pact::DSL
48
47
 
49
48
  def initialize name, namespace, &block
50
49
  @name = name
@@ -52,32 +51,56 @@ module Pact
52
51
  @set_up_defined = false
53
52
  @tear_down_defined = false
54
53
  @no_op_defined = false
55
- instance_eval(&block)
54
+ end
55
+
56
+ dsl do
57
+ def set_up &block
58
+ self.register_set_up &block
59
+ end
60
+
61
+ def tear_down &block
62
+ self.register_tear_down &block
63
+ end
64
+
65
+ def no_op
66
+ self.register_no_op
67
+ end
68
+ end
69
+
70
+ def register
71
+ ProviderStates.register(namespaced(name), self)
72
+ end
73
+
74
+ def finalize
56
75
  validate
57
76
  end
58
77
 
59
- def set_up &block
60
- if block_given?
61
- @set_up_block = block
62
- @set_up_defined = true
63
- elsif @set_up_block
78
+ def register_set_up &block
79
+ @set_up_block = block
80
+ @set_up_defined = true
81
+ end
82
+
83
+ def register_tear_down &block
84
+ @tear_down_block = block
85
+ @tear_down_defined = true
86
+ end
87
+
88
+ def register_no_op
89
+ @no_op_defined = true
90
+ end
91
+
92
+ def set_up
93
+ if @set_up_block
64
94
  instance_eval &@set_up_block
65
95
  end
66
96
  end
67
97
 
68
- def tear_down &block
69
- if block_given?
70
- @tear_down_block = block
71
- @tear_down_defined = true
72
- elsif @tear_down_block
98
+ def tear_down
99
+ if @tear_down_block
73
100
  instance_eval &@tear_down_block
74
101
  end
75
102
  end
76
103
 
77
- def no_op
78
- @no_op_defined = true
79
- end
80
-
81
104
  private
82
105
 
83
106
  attr_accessor :no_op_defined, :set_up_defined, :tear_down_defined
data/lib/pact/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pact
2
- VERSION = "1.0.30"
2
+ VERSION = "1.0.31"
3
3
  end
data/pact.gemspec CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |gem|
19
19
  gem.license = 'MIT'
20
20
 
21
21
  gem.add_runtime_dependency 'randexp', '~> 0.1.7'
22
- gem.add_runtime_dependency 'thin'
23
22
  gem.add_runtime_dependency 'rspec', '~> 2.12'
24
23
  gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
25
24
  gem.add_runtime_dependency 'rack-test', '~> 0.6.2'