pact 0.1.28 → 0.1.35

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