pact 1.0.9 → 1.0.10

Sign up to get free protection for your applications and to get access to all the features.
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