pact 0.1.28 → 0.1.35

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 (40) hide show
  1. data/Gemfile.lock +5 -5
  2. data/README.md +52 -53
  3. data/example/zoo-app/Gemfile +15 -0
  4. data/example/zoo-app/Gemfile.lock +77 -0
  5. data/example/zoo-app/lib/zoo_app/animal_service_client.rb +36 -0
  6. data/example/zoo-app/lib/zoo_app/models/alligator.rb +15 -0
  7. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +100 -0
  8. data/example/zoo-app/spec/service_providers/animal_service_spec.rb +92 -0
  9. data/example/zoo-app/spec/service_providers/pact_helper.rb +11 -0
  10. data/example/zoo-app/spec/spec_helper.rb +8 -0
  11. data/lib/pact/consumer/configuration_dsl.rb +2 -0
  12. data/lib/pact/consumer/consumer_contract_builder.rb +13 -12
  13. data/lib/pact/consumer/dsl.rb +51 -2
  14. data/lib/pact/consumer/interaction.rb +15 -12
  15. data/lib/pact/consumer/mock_service.rb +31 -9
  16. data/lib/pact/consumer/mock_service_client.rb +47 -0
  17. data/lib/pact/consumer/rspec.rb +2 -1
  18. data/lib/pact/consumer_contract.rb +2 -1
  19. data/lib/pact/matchers/matchers.rb +59 -18
  20. data/lib/pact/producer/dsl.rb +61 -0
  21. data/lib/pact/producer/producer_state.rb +7 -0
  22. data/lib/pact/producer/rspec.rb +2 -0
  23. data/lib/pact/provider/rspec.rb +1 -0
  24. data/lib/pact/request.rb +42 -6
  25. data/lib/pact/verification_task.rb +2 -5
  26. data/lib/pact/version.rb +1 -1
  27. data/spec/features/consumption_spec.rb +56 -18
  28. data/spec/features/production_spec.rb +7 -16
  29. data/spec/integration/pact/consumer_configuration_spec.rb +144 -0
  30. data/spec/integration/pact/provider_configuration_spec.rb +24 -0
  31. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +13 -13
  32. data/spec/lib/pact/consumer/dsl_spec.rb +1 -0
  33. data/spec/lib/pact/consumer/interaction_spec.rb +34 -0
  34. data/spec/lib/pact/consumer_contract_spec.rb +13 -2
  35. data/spec/lib/pact/matchers/matchers_spec.rb +32 -12
  36. data/spec/lib/pact/producer/rspec_spec.rb +1 -1
  37. data/spec/lib/pact/request_spec.rb +43 -1
  38. data/spec/support/pact_rake_support.rb +5 -8
  39. metadata +17 -10
  40. data/spec/integration/pact/configuration_spec.rb +0 -65
@@ -1,30 +1,27 @@
1
1
  require 'uri'
2
2
  require 'json/add/regexp'
3
- require 'pact/json_warning'
4
3
  require 'pact/logging'
4
+ require 'pact/consumer/mock_service_client'
5
5
 
6
6
  module Pact
7
7
  module Consumer
8
+
8
9
  class ConsumerContractBuilder
9
10
 
10
- include Pact::JsonWarning
11
11
  include Pact::Logging
12
12
 
13
- attr_reader :uri
14
13
  attr_reader :consumer_contract
15
- attr_reader :pactfile_write_mode
14
+ attr_reader :mock_service_client
16
15
 
17
16
  def initialize(attributes)
18
17
  @interactions = {}
19
18
  @producer_state = nil
20
- @pactfile_write_mode = attributes[:pactfile_write_mode]
21
19
  @consumer_contract = Pact::ConsumerContract.new(
22
20
  :consumer => ServiceConsumer.new(name: attributes[:consumer_name]),
23
21
  :producer => ServiceProducer.new(name: attributes[:producer_name])
24
22
  )
25
- @uri = URI("http://localhost:#{attributes[:port]}")
26
- @http = Net::HTTP.new(uri.host, uri.port)
27
- if pactfile_write_mode == :update && File.exist?(consumer_contract.pactfile_path)
23
+ @mock_service_client = MockServiceClient.new(attributes[:producer_name], attributes[:port])
24
+ if attributes[:pactfile_write_mode] == :update && File.exist?(consumer_contract.pactfile_path)
28
25
  load_existing_interactions
29
26
  end
30
27
  end
@@ -57,15 +54,19 @@ module Pact
57
54
  end
58
55
 
59
56
  def handle_interaction_fully_defined interaction
60
- @http.request_post('/interactions', interaction.to_json_with_generated_response)
57
+ mock_service_client.add_expected_interaction interaction
61
58
  @producer_state = nil
62
59
  consumer_contract.update_pactfile
63
60
  end
64
61
 
65
62
  def verify example_description
66
- http = Net::HTTP.new(uri.host, uri.port)
67
- response = http.request_get("/verify?example_description=#{URI.encode(example_description)}")
68
- raise response.body unless response.is_a? Net::HTTPSuccess
63
+ mock_service_client.verify example_description
64
+ end
65
+
66
+ def wait_for_interactions options
67
+ wait_max_seconds = options.fetch(:wait_max_seconds, 3)
68
+ poll_interval = options.fetch(:poll_interval, 0.1)
69
+ mock_service_client.wait_for_interactions wait_max_seconds, poll_interval
69
70
  end
70
71
 
71
72
  private
@@ -2,14 +2,63 @@ require_relative 'consumer_contract_builders'
2
2
 
3
3
  module Pact::Consumer
4
4
  module DSL
5
+
6
+ def service_consumer name, &block
7
+ ServiceConsumerDSL.new(name, &block).create_service_consumer
8
+ end
9
+
10
+ class ServiceConsumerDSL
11
+
12
+ def initialize name, &block
13
+ @name = name
14
+ consumer = Pact::Consumer::ServiceConsumer.new name: @name
15
+ @app = nil
16
+ @port = nil
17
+ instance_eval(&block)
18
+ end
19
+
20
+ def validate
21
+ raise "Please provide a consumer name" unless (@name && !@name.empty?)
22
+ raise "Please provide a port for the consumer app" if @app && !@port
23
+ end
24
+
25
+ def app app
26
+ @app = app
27
+ end
28
+
29
+ def port port
30
+ @port = port
31
+ end
32
+
33
+ def has_pact_with service_provider_name, &block
34
+ Producer.new(service_provider_name, @name, &block).create_consumer_contract_builder
35
+ end
36
+
37
+ def create_service_consumer
38
+ validate
39
+ register_consumer_app if @app
40
+ end
41
+
42
+ def register_consumer_app
43
+ Pact::Consumer::AppManager.instance.register @app, @port
44
+ end
45
+ end
46
+
47
+
48
+
49
+
50
+ #OLD ####
5
51
  def with_producer name, &block
6
52
  Producer.new(name, &block).create_consumer_contract_builder
7
53
  end
8
54
 
55
+ alias_method :with_service_provider, :with_producer
56
+
9
57
  class Producer
10
- def initialize name, &block
58
+ def initialize name, consumer_name = Pact.configuration.consumer.name, &block
11
59
  @name = name
12
60
  @service = nil
61
+ @consumer_name = consumer_name
13
62
  instance_eval(&block)
14
63
  end
15
64
 
@@ -30,7 +79,7 @@ module Pact::Consumer
30
79
 
31
80
  def consumer_contract_builder_from_attributes
32
81
  consumer_contract_builder_fields = {
33
- :consumer_name => Pact.configuration.consumer.name,
82
+ :consumer_name => @consumer_name,
34
83
  :producer_name => @name,
35
84
  :pactfile_write_mode => Pact.configuration.pactfile_write_mode
36
85
  }
@@ -10,18 +10,18 @@ module Pact
10
10
 
11
11
  attr_accessor :description, :request, :response, :producer_state
12
12
 
13
- def initialize options
14
- @description = options[:description]
15
- @request = options[:request]
16
- @response = options[:response]
17
- @producer_state = options[:producer_state]
13
+ def initialize attributes
14
+ @description = attributes[:description]
15
+ @request = attributes[:request]
16
+ @response = attributes[:response]
17
+ @producer_state = attributes[:producer_state]
18
18
  end
19
19
 
20
- def self.from_hash options
21
- new(:description => options['description'],
22
- :producer_state => options['producer_state'],
23
- :request => Pact::Request::Expected.from_hash(options['request']),
24
- :response => options['response']
20
+ def self.from_hash hash
21
+ new(:description => hash['description'],
22
+ :producer_state => hash['producer_state'],
23
+ :request => Pact::Request::Expected.from_hash(hash['request']),
24
+ :response => hash['response']
25
25
  )
26
26
  end
27
27
 
@@ -37,9 +37,12 @@ module Pact
37
37
  as_json.to_json(options)
38
38
  end
39
39
 
40
+ def as_json_for_mock_service
41
+ {:response => Reification.from_term(response), :request => @request.as_json_with_options, :description => description }
42
+ end
40
43
 
41
- def to_json_with_generated_response
42
- as_json.tap { | hash | hash[:response] = Reification.from_term(response) }.to_json
44
+ def to_json_for_mock_service
45
+ as_json_for_mock_service.to_json
43
46
  end
44
47
  end
45
48
 
@@ -56,7 +56,7 @@ module Pact
56
56
  end
57
57
 
58
58
  def interaction_diffs
59
- {
59
+ {
60
60
  :missing_interactions => missing_interactions,
61
61
  :unexpected_requests => unexpected_requests
62
62
  }.inject({}) do | hash, pair |
@@ -222,9 +222,9 @@ module Pact
222
222
  expected_request.match actual_request
223
223
  end
224
224
  if matching_interactions.size > 1
225
- @logger.info "Multiple interactions found on #{@name}:"
225
+ @logger.error "Multiple interactions found on #{@name}:"
226
226
  @logger.ap matching_interactions
227
- raise 'Multiple interactions found!'
227
+ raise "Multiple interactions found for path #{actual_request.path}!"
228
228
  end
229
229
  if matching_interactions.empty?
230
230
  handle_unrecognised_request(actual_request, candidates)
@@ -238,12 +238,12 @@ module Pact
238
238
  end
239
239
 
240
240
  def handle_unrecognised_request request, candidates
241
- @logger.ap "No interaction found on #{@name} for request \"#{candidates.map(&:description).join(', ')}\""
242
- @logger.ap 'Interaction diffs for that route:'
241
+ @logger.error "No interaction found on #{@name} amongst expected requests \"#{candidates.map(&:description).join(', ')}\""
242
+ @logger.error 'Interaction diffs for that route:'
243
243
  interaction_diff = candidates.map do |candidate|
244
- diff(candidate.as_json, request.as_json)
244
+ candidate.difference(request)
245
245
  end.to_a
246
- @logger.ap(interaction_diff)
246
+ @logger.ap(interaction_diff, :error)
247
247
  response = {message: "No interaction found for #{request.path}", interaction_diff: interaction_diff}
248
248
  [500, {'Content-Type' => 'application/json'}, [response.to_json]]
249
249
  end
@@ -262,6 +262,27 @@ module Pact
262
262
  end
263
263
  end
264
264
 
265
+ class MissingInteractionsGet
266
+ include RackHelper
267
+
268
+ def initialize name, logger
269
+ @name = name
270
+ @logger = logger
271
+ end
272
+
273
+ def match? env
274
+ env['REQUEST_PATH'].start_with?('/number_of_missing_interactions') &&
275
+ env['REQUEST_METHOD'] == 'GET'
276
+ end
277
+
278
+ def respond env
279
+ number_of_missing_interactions = InteractionList.instance.missing_interactions.size
280
+ @logger.info "Number of missing interactions for mock \"#{@name}\" = #{number_of_missing_interactions}"
281
+ [200, {}, ["#{number_of_missing_interactions}"]]
282
+ end
283
+
284
+ end
285
+
265
286
  class VerificationGet
266
287
 
267
288
  include RackHelper
@@ -279,10 +300,10 @@ module Pact
279
300
 
280
301
  def respond env
281
302
  if InteractionList.instance.all_matched?
282
- @logger.info "Veryifying - interactions matched for example \"#{example_description(env)}\""
303
+ @logger.info "Verifying - interactions matched for example \"#{example_description(env)}\""
283
304
  [200, {}, ['Interactions matched']]
284
305
  else
285
- @logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". Missing interactions:"
306
+ @logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". Interaction diffs:"
286
307
  @logger.ap InteractionList.instance.interaction_diffs, :warn
287
308
  [500, {}, ["Actual interactions do not match expected interactions for mock #{@name}. See #{@log_description} for details."]]
288
309
  end
@@ -310,6 +331,7 @@ module Pact
310
331
  @handlers = [
311
332
  StartupPoll.new(@name, @logger),
312
333
  CapybaraIdentify.new(@name, @logger),
334
+ MissingInteractionsGet.new(@name, @logger),
313
335
  VerificationGet.new(@name, @logger, log_description),
314
336
  InteractionPost.new(@name, @logger),
315
337
  InteractionDelete.new(@name, @logger),
@@ -0,0 +1,47 @@
1
+ module Pact
2
+ module Consumer
3
+ class MockServiceClient
4
+ def initialize name, port
5
+ @http = Net::HTTP.new('localhost', port)
6
+ end
7
+
8
+ def verify example_description
9
+ response = http.request_get("/verify?example_description=#{URI.encode(example_description)}")
10
+ raise response.body unless response.is_a? Net::HTTPSuccess
11
+ end
12
+
13
+ def wait_for_interactions wait_max_seconds, poll_interval
14
+ wait_until_true(wait_max_seconds, poll_interval) do
15
+ response = http.request_get("/number_of_missing_interactions")
16
+ response.body == '0'
17
+ end
18
+ end
19
+
20
+ def clear_interactions example_description
21
+ http.delete("/interactions?example_description=#{URI.encode(example_description)}")
22
+ end
23
+
24
+ def add_expected_interaction interaction
25
+ http.request_post('/interactions', interaction.to_json_for_mock_service)
26
+ end
27
+
28
+ def self.clear_interactions port, example_description
29
+ Net::HTTP.new("localhost", port).delete("/interactions?example_description=#{URI.encode(example_description)}")
30
+ end
31
+
32
+ private
33
+ attr_reader :http
34
+
35
+ #todo: in need a better home (where can we move it?)
36
+ def wait_until_true timeout=3, interval=0.1
37
+ time_limit = Time.now + timeout
38
+ loop do
39
+ result = yield
40
+ return if result || Time.now >= time_limit
41
+ sleep interval
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -2,6 +2,7 @@ require_relative '../configuration'
2
2
  require_relative '../consumer'
3
3
  require_relative 'dsl'
4
4
  require_relative 'configuration_dsl'
5
+ require 'pact/consumer/consumer_contract_builder'
5
6
 
6
7
  module Pact
7
8
  module Consumer
@@ -23,7 +24,7 @@ RSpec.configure do |config|
23
24
  example_description = "#{example.example.example_group.description} #{example.example.description}"
24
25
  Pact.configuration.logger.info "Clearing all expectations"
25
26
  Pact::Consumer::AppManager.instance.ports_of_mock_services.each do | port |
26
- Net::HTTP.new("localhost", port).delete("/interactions?example_description=#{URI.encode(example_description)}")
27
+ Pact::Consumer::MockServiceClient.clear_interactions port, example_description
27
28
  end
28
29
  end
29
30
 
@@ -4,12 +4,13 @@ require 'pact/consumer/interaction'
4
4
  require 'pact/logging'
5
5
  require 'pact/json_warning'
6
6
  require 'date'
7
+ require 'pact/version'
7
8
 
8
9
  module Pact
9
10
  class ConsumerContract
10
11
 
11
12
  include Pact::Logging
12
- include Pact::JsonWarning
13
+ include Pact::JsonWarning
13
14
 
14
15
  attr_accessor :interactions
15
16
  attr_accessor :consumer
@@ -5,8 +5,29 @@ module Pact
5
5
  module Matchers
6
6
 
7
7
  NO_DIFF_INDICATOR = 'no difference here!'
8
+ UNEXPECTED_KEY = '<key not to be present>'
9
+ DEFAULT_OPTIONS = {allow_unexpected_keys: true, structure: false}.freeze
8
10
 
9
- def diff expected, actual, options = {}
11
+ class KeyNotFound
12
+ def == other
13
+ other.is_a? KeyNotFound
14
+ end
15
+
16
+ def eql? other
17
+ self == other
18
+ end
19
+
20
+ def to_s
21
+ "<key not found>"
22
+ end
23
+
24
+ def to_json options = {}
25
+ to_s
26
+ end
27
+ end
28
+
29
+ def diff expected, actual, opts = {}
30
+ options = DEFAULT_OPTIONS.merge(opts)
10
31
  case expected
11
32
  when Hash then hash_diff(expected, actual, options)
12
33
  when Array then array_diff(expected, actual, options)
@@ -31,17 +52,7 @@ module Pact
31
52
  def array_diff expected, actual, options
32
53
  if actual.is_a? Array
33
54
  if expected.length == actual.length
34
- difference = []
35
- diff_found = false
36
- expected.each_with_index do | item, index|
37
- if (item_diff = diff(item, actual[index], options)).any?
38
- diff_found = true
39
- difference << item_diff
40
- else
41
- difference << NO_DIFF_INDICATOR
42
- end
43
- end
44
- diff_found ? difference : {}
55
+ actual_array_diff expected, actual, options
45
56
  else
46
57
  {expected: expected, actual: actual}
47
58
  end
@@ -50,14 +61,44 @@ module Pact
50
61
  end
51
62
  end
52
63
 
53
- def hash_diff expected, actual, options
54
- if actual.is_a? Hash
55
- expected.keys.inject({}) do |diff, key|
56
- if (diff_at_key = diff(expected[key], actual[key], options)).any?
57
- diff[key] = diff_at_key
58
- end
64
+ def actual_array_diff expected, actual, options
65
+ difference = []
66
+ diff_found = false
67
+ expected.each_with_index do | item, index|
68
+ if (item_diff = diff(item, actual.fetch(index, KeyNotFound.new), options)).any?
69
+ diff_found = true
70
+ difference << item_diff
71
+ else
72
+ difference << NO_DIFF_INDICATOR
73
+ end
74
+ end
75
+ diff_found ? difference : {}
76
+ end
77
+
78
+ def actual_hash_diff expected, actual, options
79
+ difference = expected.keys.inject({}) do |diff, key|
80
+ if (diff_at_key = diff(expected[key], actual.fetch(key, KeyNotFound.new), options)).any?
81
+ diff[key] = diff_at_key
82
+ end
83
+ diff
84
+ end
85
+ difference.merge(check_for_unexpected_keys(expected, actual, options))
86
+ end
87
+
88
+ def check_for_unexpected_keys expected, actual, options
89
+ if options[:allow_unexpected_keys]
90
+ {}
91
+ else
92
+ (actual.keys - expected.keys).inject({}) do | diff, key |
93
+ diff[key] = {:expected => UNEXPECTED_KEY, :actual => actual[key]}
59
94
  diff
60
95
  end
96
+ end
97
+ end
98
+
99
+ def hash_diff expected, actual, options
100
+ if actual.is_a? Hash
101
+ actual_hash_diff expected, actual, options
61
102
  else
62
103
  {expected: expected, actual: actual}
63
104
  end
@@ -0,0 +1,61 @@
1
+ module Pact
2
+
3
+ module Producer
4
+ module DSL
5
+
6
+ module Configuration
7
+ def provider= provider
8
+ @producer = provider
9
+ end
10
+ end
11
+
12
+ Pact::Configuration.send(:include, Configuration)
13
+
14
+ def service_provider name, &block
15
+ service_provider = ServiceProviderDSL.new(name, &block).create_service_provider
16
+ Pact.configuration.provider = service_provider
17
+ service_provider
18
+ end
19
+
20
+ class ServiceProviderConfig
21
+ attr_accessor :name
22
+
23
+ def initialize name, &app_block
24
+ @name = name
25
+ @app_block = app_block
26
+ end
27
+
28
+ def app
29
+ @app_block.call
30
+ end
31
+ end
32
+
33
+ class ServiceProviderDSL
34
+
35
+ def initialize name, &block
36
+ @name = name
37
+ @app = nil
38
+ instance_eval(&block)
39
+ end
40
+
41
+ def validate
42
+ raise "Please provide a name for the Producer" unless @name
43
+ raise "Please configure an app for the Producer" unless @app_block
44
+ end
45
+
46
+ def name name
47
+ @name = name
48
+ end
49
+
50
+ def app &block
51
+ @app_block = block
52
+ end
53
+
54
+ def create_service_provider
55
+ validate
56
+ ServiceProviderConfig.new(@name, &@app_block)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -11,6 +11,9 @@ module Pact
11
11
  instance_eval(&block)
12
12
  ProducerState.current_namespaces.pop
13
13
  end
14
+
15
+ alias_method :provider_states_for, :with_consumer
16
+ alias_method :provider_state, :producer_state
14
17
  end
15
18
 
16
19
  class ProducerState
@@ -22,6 +25,10 @@ module Pact
22
25
  ProducerState.new(name, current_namespaces.join('.'), &block)
23
26
  end
24
27
 
28
+ def self.provider_state name, &block
29
+ producer_state name, &block
30
+ end
31
+
25
32
  def self.register name, producer_state
26
33
  producer_states[name] = producer_state
27
34
  end
@@ -4,6 +4,7 @@ require 'pact/json_warning'
4
4
  require_relative 'matchers'
5
5
  require_relative 'test_methods'
6
6
  require_relative 'configuration_dsl'
7
+ require_relative 'dsl'
7
8
 
8
9
  module Pact
9
10
  module Producer
@@ -111,6 +112,7 @@ module Pact
111
112
  end
112
113
 
113
114
  def save_pactfile_to_tmp pact, name
115
+ FileUtils.mkdir_p Pact.configuration.tmp_dir
114
116
  File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
115
117
  end
116
118
 
@@ -0,0 +1 @@
1
+ require 'pact/producer/rspec'
data/lib/pact/request.rb CHANGED
@@ -35,15 +35,15 @@ module Pact
35
35
  end
36
36
 
37
37
  class Base
38
- include Pact::Matchers
39
- extend Pact::Matchers
38
+ include Pact::Matchers
39
+ extend Pact::Matchers
40
40
 
41
- NULL_EXPECTATION = NullExpectation.new
41
+ NULL_EXPECTATION = NullExpectation.new
42
42
 
43
- attr_reader :method, :path, :headers, :body, :query
43
+ attr_reader :method, :path, :headers, :body, :query, :options
44
44
 
45
45
  def self.from_hash(hash)
46
- sym_hash = hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
46
+ sym_hash = symbolize_keys hash
47
47
  method = sym_hash.fetch(:method)
48
48
  path = sym_hash.fetch(:path)
49
49
  query = sym_hash.fetch(:query, NULL_EXPECTATION)
@@ -52,6 +52,10 @@ module Pact
52
52
  new(method, path, headers, body, query)
53
53
  end
54
54
 
55
+ def self.symbolize_keys hash
56
+ hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
57
+ end
58
+
55
59
  def initialize(method, path, headers, body, query)
56
60
  @method = method.to_s
57
61
  @path = path.chomp('/')
@@ -76,18 +80,31 @@ module Pact
76
80
  base_json
77
81
  end
78
82
 
83
+ def as_json_without_body
84
+ keep_keys = [:method, :path, :headers, :query]
85
+ as_json.reject{ |key, value| !keep_keys.include? key }
86
+ end
87
+
79
88
  end
80
89
 
81
90
  class Expected < Base
82
91
 
92
+ DEFAULT_OPTIONS = {:allow_unexpected_keys => false}.freeze
83
93
  attr_accessor :description
94
+ attr_accessor :options
84
95
 
85
96
  def self.from_hash(hash)
86
97
  request = super
87
98
  request.description = hash.fetch(:description, nil)
99
+ request.options = symbolize_keys(hash).fetch(:options, {})
88
100
  request
89
101
  end
90
102
 
103
+ def initialize(method, path, headers, body, query, options = {})
104
+ super(method, path, headers, body, query)
105
+ @options = options
106
+ end
107
+
91
108
  def match(actual_request)
92
109
  difference(actual_request).empty?
93
110
  end
@@ -97,7 +114,26 @@ module Pact
97
114
  end
98
115
 
99
116
  def difference(actual_request)
100
- diff(as_json, actual_request.as_json)
117
+ request_diff = diff(as_json_without_body, actual_request.as_json_without_body)
118
+ unless body.is_a? NullExpectation
119
+ request_diff.merge(body_difference(actual_request.body))
120
+ else
121
+ request_diff
122
+ end
123
+ end
124
+
125
+ def body_difference(actual_body)
126
+ diff({:body => body}, {body: actual_body}, allow_unexpected_keys: runtime_options[:allow_unexpected_keys_in_body])
127
+ end
128
+
129
+ def as_json_with_options
130
+ as_json.merge( options.empty? ? {} : { options: options} )
131
+ end
132
+
133
+ # Don't want to put the default options in the pact json just yet, so calculating these at run time, rather than assigning
134
+ # the result to @options
135
+ def runtime_options
136
+ DEFAULT_OPTIONS.merge(self.class.symbolize_keys(options))
101
137
  end
102
138
 
103
139
  end
@@ -15,11 +15,8 @@ require 'pact/producer/pact_spec_runner'
15
15
  The support_file should include code that makes your rack app available for the rack testing framework.
16
16
  Eg.
17
17
 
18
- Pact.configure do | config |
19
- config.producer do
20
- name "My Producer"
21
- app { TestApp.new }
22
- end
18
+ Pact.service_provider "My Provider" do
19
+ app { TestApp.new }
23
20
  end
24
21
 
25
22
  It should also load all your app's dependencies (eg by calling out to spec_helper)
data/lib/pact/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pact
2
- VERSION = "0.1.28"
2
+ VERSION = "0.1.35"
3
3
  end