pact 1.0.9 → 1.0.10

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 (86) hide show
  1. data/CHANGELOG.md +20 -0
  2. data/Gemfile.lock +3 -3
  3. data/example/animal-service/Gemfile +14 -0
  4. data/example/animal-service/Gemfile.lock +67 -0
  5. data/example/animal-service/Rakefile +3 -0
  6. data/example/animal-service/spec/service_consumers/pact_helper.rb +24 -0
  7. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +9 -0
  8. data/example/zoo-app/Gemfile.lock +2 -15
  9. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +21 -6
  10. data/example/zoo-app/spec/service_providers/animal_service_spec.rb +1 -1
  11. data/lib/pact/consumer.rb +10 -11
  12. data/lib/pact/consumer/app_manager.rb +1 -0
  13. data/lib/pact/consumer/configuration.rb +179 -0
  14. data/lib/pact/consumer/interaction_builder.rb +1 -2
  15. data/lib/pact/consumer/mock_service.rb +1 -370
  16. data/lib/pact/consumer/mock_service/app.rb +70 -0
  17. data/lib/pact/consumer/mock_service/interaction_delete.rb +28 -0
  18. data/lib/pact/consumer/mock_service/interaction_list.rb +57 -0
  19. data/lib/pact/consumer/mock_service/interaction_post.rb +25 -0
  20. data/lib/pact/consumer/mock_service/interaction_replay.rb +126 -0
  21. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +26 -0
  22. data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
  23. data/lib/pact/consumer/mock_service/startup_poll.rb +22 -0
  24. data/lib/pact/consumer/mock_service/verification_get.rb +35 -0
  25. data/lib/pact/consumer/mock_service_client.rb +3 -1
  26. data/lib/pact/consumer/mock_service_interaction_expectation.rb +33 -0
  27. data/lib/pact/consumer/request.rb +27 -0
  28. data/lib/pact/consumer/rspec.rb +1 -4
  29. data/lib/pact/consumer_contract/consumer_contract.rb +11 -8
  30. data/lib/pact/consumer_contract/interaction.rb +9 -22
  31. data/lib/pact/consumer_contract/request.rb +68 -0
  32. data/lib/pact/consumer_contract/service_consumer.rb +6 -2
  33. data/lib/pact/consumer_contract/service_provider.rb +6 -2
  34. data/lib/pact/matchers/index_not_found.rb +20 -0
  35. data/lib/pact/matchers/matchers.rb +14 -28
  36. data/lib/pact/matchers/unexpected_index.rb +17 -0
  37. data/lib/pact/matchers/unexpected_key.rb +17 -0
  38. data/lib/pact/provider.rb +1 -1
  39. data/lib/pact/provider/configuration.rb +129 -0
  40. data/lib/pact/provider/request.rb +59 -0
  41. data/lib/pact/provider/rspec.rb +4 -5
  42. data/lib/pact/provider/test_methods.rb +14 -52
  43. data/lib/pact/shared/dsl.rb +20 -0
  44. data/lib/pact/shared/key_not_found.rb +24 -0
  45. data/lib/pact/shared/null_expectation.rb +31 -0
  46. data/lib/pact/shared/request.rb +68 -0
  47. data/lib/pact/something_like.rb +7 -1
  48. data/lib/pact/symbolize_keys.rb +12 -0
  49. data/lib/pact/tasks.rb +1 -1
  50. data/lib/pact/tasks/task_helper.rb +23 -0
  51. data/lib/pact/{verification_task.rb → tasks/verification_task.rb} +4 -4
  52. data/lib/pact/version.rb +1 -1
  53. data/lib/tasks/pact.rake +5 -4
  54. data/pact.gemspec +1 -1
  55. data/spec/features/consumption_spec.rb +1 -73
  56. data/spec/features/production_spec.rb +3 -0
  57. data/spec/integration/consumer_spec.rb +170 -0
  58. data/spec/integration/pact/consumer_configuration_spec.rb +1 -1
  59. data/spec/lib/pact/consumer/{dsl_spec.rb → configuration_spec.rb} +10 -9
  60. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +2 -2
  61. data/spec/lib/pact/consumer/interaction_builder_spec.rb +1 -1
  62. data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +66 -0
  63. data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +82 -0
  64. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
  65. data/spec/lib/pact/consumer/request_spec.rb +24 -0
  66. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +1 -1
  67. data/spec/lib/pact/consumer_contract/interaction_spec.rb +0 -34
  68. data/spec/lib/pact/{request_spec.rb → consumer_contract/request_spec.rb} +45 -121
  69. data/spec/lib/pact/matchers/matchers_spec.rb +29 -13
  70. data/spec/lib/pact/provider/configuration_spec.rb +163 -0
  71. data/spec/lib/pact/provider/request_spec.rb +78 -0
  72. data/spec/lib/pact/provider/test_methods_spec.rb +0 -30
  73. data/spec/lib/pact/verification_task_spec.rb +1 -1
  74. data/spec/spec_helper.rb +3 -0
  75. data/spec/support/factories.rb +5 -1
  76. data/spec/support/pact_helper.rb +6 -0
  77. data/spec/support/shared_examples_for_request.rb +83 -0
  78. data/spec/support/test_app_fail.json +3 -0
  79. data/spec/support/test_app_pass.json +18 -1
  80. metadata +68 -31
  81. data/lib/pact/consumer/dsl.rb +0 -157
  82. data/lib/pact/pact_task_helper.rb +0 -21
  83. data/lib/pact/provider/dsl.rb +0 -115
  84. data/lib/pact/request.rb +0 -167
  85. data/spec/lib/pact/consumer/mock_service_spec.rb +0 -143
  86. data/spec/lib/pact/provider/dsl_spec.rb +0 -179
@@ -1,7 +1,4 @@
1
- require_relative '../configuration'
2
- require_relative '../consumer'
3
- require_relative 'dsl'
4
- require 'pact/consumer/consumer_contract_builder'
1
+ require 'pact/consumer'
5
2
 
6
3
  module Pact
7
4
  module Consumer
@@ -1,14 +1,15 @@
1
- require 'pact/consumer_contract'
2
1
  require 'pact/logging'
3
2
  require 'pact/json_warning'
4
- require 'date'
3
+ require 'pact/something_like'
4
+ require 'pact/symbolize_keys'
5
+ require 'pact/term'
5
6
  require 'pact/version'
7
+ require 'date'
6
8
  require 'open-uri'
7
- require 'pact/term'
8
- require 'pact/something_like'
9
9
  require_relative 'service_consumer'
10
10
  require_relative 'service_provider'
11
11
  require_relative 'interaction'
12
+ require_relative 'request'
12
13
 
13
14
 
14
15
 
@@ -27,6 +28,7 @@ module Pact
27
28
 
28
29
  class ConsumerContract
29
30
 
31
+ include SymbolizeKeys
30
32
  include Logging
31
33
  include JsonWarning
32
34
  include FileName
@@ -58,11 +60,12 @@ module Pact
58
60
  as_json.to_json(options)
59
61
  end
60
62
 
61
- def self.from_hash(obj)
63
+ def self.from_hash(hash)
64
+ hash = symbolize_keys(hash)
62
65
  new({
63
- :interactions => obj['interactions'].collect { |hash| Interaction.from_hash(hash)},
64
- :consumer => ServiceConsumer.from_hash(obj['consumer']),
65
- :provider => ServiceProvider.from_hash(obj['provider'] || {})
66
+ :interactions => hash[:interactions].collect { |hash| Interaction.from_hash(hash)},
67
+ :consumer => ServiceConsumer.from_hash(hash[:consumer]),
68
+ :provider => ServiceProvider.from_hash(hash[:provider])
66
69
  })
67
70
  end
68
71
 
@@ -1,7 +1,9 @@
1
- require 'pact/request'
1
+ require 'pact/consumer_contract/request'
2
+ require 'pact/symbolize_keys'
2
3
 
3
4
  module Pact
4
5
  class Interaction
6
+ include SymbolizeKeys
5
7
 
6
8
  attr_accessor :description, :request, :response, :provider_state
7
9
 
@@ -13,35 +15,20 @@ module Pact
13
15
  end
14
16
 
15
17
  def self.from_hash hash
16
- new(:description => hash['description'],
17
- :provider_state => hash['provider_state'],
18
- :request => Pact::Request::Expected.from_hash(hash['request']),
19
- :response => hash['response']
20
- )
18
+ request = Pact::Request::Expected.from_hash(hash['request'])
19
+ new(symbolize_keys(hash).merge({request: request}))
21
20
  end
22
21
 
23
22
  def as_json
24
- {
25
- :description => @description,
26
- :request => @request.as_json,
27
- :response => @response,
28
- }.tap{ | hash | hash[:provider_state] = @provider_state if @provider_state }
23
+ hash = { :description => @description }
24
+ hash[:provider_state] = @provider_state if @provider_state #Easier to read when provider state at top
25
+ hash.merge(:request => @request.as_json, :response => @response)
29
26
  end
30
27
 
31
28
  def to_json(options = {})
32
29
  as_json.to_json(options)
33
30
  end
34
31
 
35
- def as_json_for_mock_service
36
- {:response => Reification.from_term(response), :request => @request.as_json_with_options, :description => description }.
37
- tap{ | hash | hash[:provider_state] = @provider_state if @provider_state }
38
- end
39
-
40
- def to_json_for_mock_service
41
- as_json_for_mock_service.to_json
42
- end
43
-
44
- # Move this to interaction
45
32
  def matches_criteria? criteria
46
33
  criteria.each do | key, value |
47
34
  unless match_criterion self.send(key.to_s), value
@@ -64,7 +51,7 @@ module Pact
64
51
  end
65
52
 
66
53
  def to_s
67
- to_json
54
+ JSON.pretty_generate(self)
68
55
  end
69
56
  end
70
57
  end
@@ -0,0 +1,68 @@
1
+ require 'pact/shared/request'
2
+ require 'pact/shared/null_expectation'
3
+
4
+ module Pact
5
+
6
+ module Request
7
+
8
+ class Expected < Pact::Request::Base
9
+
10
+ DEFAULT_OPTIONS = {:allow_unexpected_keys => false}.freeze
11
+ attr_accessor :options #Temporary hack
12
+
13
+ def self.from_hash(hash)
14
+ sym_hash = symbolize_keys hash
15
+ method = sym_hash.fetch(:method)
16
+ path = sym_hash.fetch(:path)
17
+ query = sym_hash.fetch(:query, key_not_found)
18
+ headers = sym_hash.fetch(:headers, key_not_found)
19
+ body = sym_hash.fetch(:body, key_not_found)
20
+ options = sym_hash.fetch(:options, {})
21
+ new(method, path, headers, body, query, options)
22
+ end
23
+
24
+ def initialize(method, path, headers, body, query, options = {})
25
+ super(method, path, headers, body, query)
26
+ @options = options
27
+ end
28
+
29
+ def matches?(actual_request)
30
+ difference(actual_request).empty?
31
+ end
32
+
33
+ def matches_route? actual_request
34
+ diff({:method => method, :path => path}, {:method => actual_request.method, :path => actual_request.path}).empty?
35
+ end
36
+
37
+ def difference(actual_request)
38
+ request_diff = diff(as_json_without_body, actual_request.as_json_without_body)
39
+ unless body.is_a? NullExpectation
40
+ request_diff.merge(body_difference(actual_request.body))
41
+ else
42
+ request_diff
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def self.key_not_found
49
+ Pact::NullExpectation.new
50
+ end
51
+
52
+ private
53
+
54
+ # Options is a dirty hack to allow Condor to send extra keys in the request,
55
+ # as it's too much work to set up an exactly matching expectation.
56
+ # Need to implement a proper matching strategy and remove this.
57
+ # Do not rely on it!
58
+ def runtime_options
59
+ DEFAULT_OPTIONS.merge(symbolize_keys(options))
60
+ end
61
+
62
+ def body_difference(actual_body)
63
+ diff({:body => body}, {body: actual_body}, allow_unexpected_keys: runtime_options[:allow_unexpected_keys_in_body])
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -1,5 +1,9 @@
1
+ require 'pact/symbolize_keys'
2
+
1
3
  module Pact
2
4
  class ServiceConsumer
5
+ include SymbolizeKeys
6
+
3
7
  attr_accessor :name
4
8
  def initialize options
5
9
  @name = options[:name]
@@ -13,8 +17,8 @@ module Pact
13
17
  {name: name}
14
18
  end
15
19
 
16
- def self.from_hash obj
17
- ServiceConsumer.new(:name => obj['name'])
20
+ def self.from_hash hash
21
+ new(symbolize_keys(hash))
18
22
  end
19
23
  end
20
24
  end
@@ -1,5 +1,9 @@
1
+ require 'pact/symbolize_keys'
2
+
1
3
  module Pact
2
4
  class ServiceProvider
5
+ include SymbolizeKeys
6
+
3
7
  attr_accessor :name
4
8
  def initialize options
5
9
  @name = options[:name] || '[provider name unknown - please update the pact gem in the consumer project to the latest version and regenerate the pacts]'
@@ -13,8 +17,8 @@ module Pact
13
17
  {name: name}
14
18
  end
15
19
 
16
- def self.from_hash obj
17
- ServiceProvider.new(:name => obj['name'])
20
+ def self.from_hash hash
21
+ new(symbolize_keys(hash))
18
22
  end
19
23
  end
20
24
  end
@@ -0,0 +1,20 @@
1
+ module Pact
2
+ class IndexNotFound
3
+ def == other
4
+ other.is_a? IndexNotFound
5
+ end
6
+
7
+ def to_s
8
+ "<index not found>"
9
+ end
10
+
11
+ def to_json options = {}
12
+ to_s
13
+ end
14
+
15
+ def empty?
16
+ true
17
+ end
18
+ end
19
+
20
+ end
@@ -1,32 +1,19 @@
1
1
  require 'awesome_print'
2
2
  require 'pact/term'
3
3
  require 'pact/something_like'
4
+ require 'pact/shared/null_expectation'
5
+ require 'pact/shared/key_not_found'
6
+ require 'pact/matchers/unexpected_key'
7
+ require 'pact/matchers/unexpected_index'
8
+ require 'pact/matchers/index_not_found'
4
9
 
5
10
  module Pact
6
11
  module Matchers
7
12
 
8
13
  NO_DIFF_INDICATOR = 'no difference here!'
9
- UNEXPECTED_KEY = '<key not to be present>'
14
+ #UnexpectedKey.new = '<key not to be present>'
10
15
  DEFAULT_OPTIONS = {allow_unexpected_keys: true, structure: false}.freeze
11
16
 
12
- class KeyNotFound
13
- def == other
14
- other.is_a? KeyNotFound
15
- end
16
-
17
- def eql? other
18
- self == other
19
- end
20
-
21
- def to_s
22
- "<key not found>"
23
- end
24
-
25
- def to_json options = {}
26
- to_s
27
- end
28
- end
29
-
30
17
  def diff expected, actual, opts = {}
31
18
  options = DEFAULT_OPTIONS.merge(opts)
32
19
  case expected
@@ -53,11 +40,7 @@ module Pact
53
40
 
54
41
  def array_diff expected, actual, options
55
42
  if actual.is_a? Array
56
- if expected.length == actual.length
57
- actual_array_diff expected, actual, options
58
- else
59
- {expected: expected, actual: actual}
60
- end
43
+ actual_array_diff expected, actual, options
61
44
  else
62
45
  {expected: expected, actual: actual}
63
46
  end
@@ -66,8 +49,11 @@ module Pact
66
49
  def actual_array_diff expected, actual, options
67
50
  difference = []
68
51
  diff_found = false
69
- expected.each_with_index do | item, index|
70
- if (item_diff = diff(item, actual.fetch(index, KeyNotFound.new), options)).any?
52
+ length = [expected.length, actual.length].max
53
+ length.times do | index|
54
+ expected_item = expected.fetch(index, Pact::UnexpectedIndex.new)
55
+ actual_item = actual.fetch(index, Pact::IndexNotFound.new)
56
+ if (item_diff = diff(expected_item, actual_item, options)).any?
71
57
  diff_found = true
72
58
  difference << item_diff
73
59
  else
@@ -79,7 +65,7 @@ module Pact
79
65
 
80
66
  def actual_hash_diff expected, actual, options
81
67
  difference = expected.keys.inject({}) do |diff, key|
82
- if (diff_at_key = diff(expected[key], actual.fetch(key, KeyNotFound.new), options)).any?
68
+ if (diff_at_key = diff(expected[key], actual.fetch(key, Pact::KeyNotFound.new), options)).any?
83
69
  diff[key] = diff_at_key
84
70
  end
85
71
  diff
@@ -92,7 +78,7 @@ module Pact
92
78
  {}
93
79
  else
94
80
  (actual.keys - expected.keys).inject({}) do | diff, key |
95
- diff[key] = {:expected => UNEXPECTED_KEY, :actual => actual[key]}
81
+ diff[key] = {:expected => UnexpectedKey.new, :actual => actual[key]}
96
82
  diff
97
83
  end
98
84
  end
@@ -0,0 +1,17 @@
1
+ module Pact
2
+ class UnexpectedIndex
3
+
4
+ def == other
5
+ other.is_a? UnexpectedIndex
6
+ end
7
+
8
+ def to_s
9
+ '<index not to exist>'
10
+ end
11
+
12
+ def to_json opts = {}
13
+ to_s
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Pact
2
+ class UnexpectedKey
3
+
4
+ def == other
5
+ other.is_a? UnexpectedKey
6
+ end
7
+
8
+
9
+ def to_s
10
+ '<key not to exist>'
11
+ end
12
+
13
+ def to_json opts = {}
14
+ to_s
15
+ end
16
+ end
17
+ end
data/lib/pact/provider.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  require 'pact/configuration'
2
- require 'pact/provider/dsl'
2
+ require 'pact/provider/configuration'
3
3
  require 'pact/provider/pact_spec_runner'
@@ -0,0 +1,129 @@
1
+ require 'pact/provider/pact_verification'
2
+ require 'pact/shared/dsl'
3
+ module Pact
4
+
5
+ module Provider
6
+
7
+ module DSL
8
+ def service_provider name, &block
9
+ Configuration::ServiceProviderDSL.build(name, &block)
10
+ end
11
+ end
12
+
13
+ Pact.send(:extend, Pact::Provider::DSL)
14
+
15
+ module Configuration
16
+
17
+ module ConfigurationExtension
18
+ def provider= provider
19
+ @provider = provider
20
+ end
21
+
22
+ def provider
23
+ if defined? @provider
24
+ @provider
25
+ else
26
+ raise "Please configure your provider. See the Provider section in the README for examples."
27
+ end
28
+ end
29
+
30
+ def add_pact_verification verification
31
+ pact_verifications << verification
32
+ end
33
+
34
+ def pact_verifications
35
+ @pact_verifications ||= []
36
+ end
37
+ end
38
+
39
+ Pact::Configuration.send(:include, ConfigurationExtension)
40
+
41
+ class ServiceProviderConfig
42
+
43
+ def initialize &app_block
44
+ @app_block = app_block
45
+ end
46
+
47
+ def app
48
+ @app_block.call
49
+ end
50
+ end
51
+
52
+ class PactVerification
53
+ extend Pact::DSL
54
+
55
+ attr_accessor :consumer_name, :pact_uri, :ref
56
+
57
+ def initialize consumer_name, options = {}
58
+ @consumer_name = consumer_name
59
+ @ref = options.fetch(:ref, :head)
60
+ @pact_uri = nil
61
+ end
62
+
63
+ dsl do
64
+ def pact_uri pact_uri
65
+ self.pact_uri = pact_uri
66
+ end
67
+ end
68
+
69
+ def finalize
70
+ validate
71
+ create_pact_verification
72
+ end
73
+
74
+ private
75
+
76
+ def create_pact_verification
77
+ verification = Pact::Provider::PactVerification.new(consumer_name, pact_uri, ref)
78
+ Pact.configuration.add_pact_verification verification
79
+ end
80
+
81
+ def validate
82
+ raise "Please provide a pact_uri for the verification" unless pact_uri
83
+ end
84
+
85
+ end
86
+
87
+ class ServiceProviderDSL
88
+ extend Pact::DSL
89
+
90
+ attr_accessor :name, :app_block
91
+
92
+ def initialize name
93
+ @name = name
94
+ @app_block = nil
95
+ end
96
+
97
+ dsl do
98
+ def app &block
99
+ self.app_block = block
100
+ end
101
+
102
+ def honours_pact_with consumer_name, options = {}, &block
103
+ create_pact_verification consumer_name, options, &block
104
+ end
105
+ end
106
+
107
+ def create_pact_verification consumer_name, options, &block
108
+ PactVerification.build(consumer_name, options, &block)
109
+ end
110
+
111
+ def finalize
112
+ validate
113
+ create_service_provider
114
+ end
115
+
116
+ private
117
+
118
+ def validate
119
+ raise "Please provide a name for the Provider" unless name && !name.strip.empty?
120
+ raise "Please configure an app for the Provider" unless app_block
121
+ end
122
+
123
+ def create_service_provider
124
+ Pact.configuration.provider = ServiceProviderConfig.new(&@app_block)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end