pact 0.1.35 → 0.1.37

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pact (0.1.35)
4
+ pact (0.1.37)
5
5
  awesome_print (~> 1.1.0)
6
6
  capybara (~> 2.1.0)
7
7
  find_a_port (~> 1.0.1)
data/README.md CHANGED
@@ -11,6 +11,14 @@ This allows you to test both sides of an integration point using fast unit tests
11
11
 
12
12
  This gem is inspired by the concept of "Consumer driven contracts". See http://martinfowler.com/articles/consumerDrivenContracts.html for more information.
13
13
 
14
+ ## Features
15
+ * A services is mocked using an actual process running on a specified port, so javascript clients can be tested as easily as backend clients.
16
+ * "Provider states" (similar to fixtures) allow the same request to be made with a different expected response.
17
+ * Consumers specify only the fields they are interested in, allowing a provider to return more fields without breaking the pact. This allows a provider to have a different pact with a different consumer, and know which fields each cares about in a given response.
18
+ * Expected interactions are verified to have actually occurred.
19
+ * A rake verification task allows a pact at any URI to be checked against a given service provider codebase.
20
+ * Different versions of a consumer/provider pair can be easily tested against each other, allowing confidence when deploying new versions of each.
21
+
14
22
  ## Installation
15
23
 
16
24
  Put it in your Gemfile. You know how.
@@ -92,7 +92,7 @@ module Pact::Consumer
92
92
  @name = name
93
93
  @port = nil
94
94
  @standalone = false
95
- @verify = false
95
+ @verify = true
96
96
  instance_eval(&block)
97
97
  end
98
98
 
@@ -38,7 +38,8 @@ module Pact
38
38
  end
39
39
 
40
40
  def as_json_for_mock_service
41
- {:response => Reification.from_term(response), :request => @request.as_json_with_options, :description => description }
41
+ {:response => Reification.from_term(response), :request => @request.as_json_with_options, :description => description }.
42
+ tap{ | hash | hash[:producer_state] = @producer_state if @producer_state }
42
43
  end
43
44
 
44
45
  def to_json_for_mock_service
@@ -43,6 +43,7 @@ module Pact
43
43
  @matched_interactions << interaction
44
44
  end
45
45
 
46
+ # Request::Actual
46
47
  def register_unexpected request
47
48
  @unexpected_requests << request
48
49
  end
@@ -58,7 +59,7 @@ module Pact
58
59
  def interaction_diffs
59
60
  {
60
61
  :missing_interactions => missing_interactions,
61
- :unexpected_requests => unexpected_requests
62
+ :unexpected_requests => unexpected_requests.collect(&:as_json)
62
63
  }.inject({}) do | hash, pair |
63
64
  hash[pair.first] = pair.last if pair.last.any?
64
65
  hash
@@ -238,6 +239,7 @@ module Pact
238
239
  end
239
240
 
240
241
  def handle_unrecognised_request request, candidates
242
+ InteractionList.instance.register_unexpected request
241
243
  @logger.error "No interaction found on #{@name} amongst expected requests \"#{candidates.map(&:description).join(', ')}\""
242
244
  @logger.error 'Interaction diffs for that route:'
243
245
  interaction_diff = candidates.map do |candidate|
@@ -72,9 +72,10 @@ module Pact
72
72
  end
73
73
  end
74
74
 
75
+ # Move this to interaction
75
76
  def match_criteria? interaction, criteria
76
77
  criteria.each do | key, value |
77
- unless match_criterion interaction[key.to_s], value
78
+ unless match_criterion interaction.send(key.to_s), value
78
79
  return false
79
80
  end
80
81
  end
@@ -100,12 +101,12 @@ module Pact
100
101
  File.open(pactfile_path, 'w') do |f|
101
102
  f.write JSON.pretty_generate(self)
102
103
  end
103
- end
104
+ end
104
105
 
105
106
  private
106
107
 
107
108
  def filenamify name
108
109
  name.downcase.gsub(/\s/, '_')
109
- end
110
+ end
110
111
  end
111
112
  end
@@ -4,6 +4,7 @@ require 'json/add/regexp'
4
4
  module Pact
5
5
  module JsonWarning
6
6
  def check_for_active_support_json
7
+ @already_warned ||= false
7
8
  # Active support clobbers the as_json methods defined in the json/add directory of the json gem.
8
9
  # These methods are required to serialize and deserialize the Regexp and Symbol classes properly.
9
10
  # You can potentially fix this by making sure the json gem is required AFTER the active_support/json gem
@@ -15,8 +16,11 @@ module Pact
15
16
  # without breaking the calling code, which may depend on activesupport/json... then please fix this.
16
17
  # Note: we can probably do this in Ruby 2.0 with refinements, but for now, we're all stuck on 1.9 :(
17
18
 
18
- unless Regexp.new('').as_json.is_a?(Hash)
19
- Logger.new($stderr).warn("It appears you are using ActiveSupport json in your project. You are now in rubygems hell. Please see Pact::JsonWarning for more info.")
19
+ unless @already_warned
20
+ unless Regexp.new('').as_json.is_a?(Hash)
21
+ Logger.new($stderr).warn("It appears you are using ActiveSupport json in your project. You are now in rubygems hell. Please see Pact::JsonWarning for more info.")
22
+ @already_warned = true
23
+ end
20
24
  end
21
25
  end
22
26
  end
@@ -105,15 +105,28 @@ module Pact
105
105
  end
106
106
 
107
107
  def class_diff expected, actual
108
- if expected.class != actual.class
109
- actual_display = actual.nil? ? nil : {:class => actual.class, :value => actual }
110
- expected_display = expected.nil? ? nil : {:class => expected.class, eg: expected}
111
- {:expected => expected_display, :actual => actual_display}
112
- else
108
+ if classes_match? expected, actual
113
109
  {}
110
+ else
111
+ {:expected => structure_diff_expected_display(expected), :actual => structure_diff_actual_display(actual)}
114
112
  end
115
113
  end
116
114
 
115
+ def structure_diff_actual_display actual
116
+ (actual.nil? || actual.is_a?(KeyNotFound) ) ? actual : {:class => actual.class, :value => actual }
117
+ end
118
+
119
+ def structure_diff_expected_display expected
120
+ (expected.nil?) ? expected : {:class => expected.class, eg: expected}
121
+ end
122
+
123
+ def classes_match? expected, actual
124
+ #There must be a more elegant way to do this
125
+ expected.class == actual.class ||
126
+ (expected.is_a?(TrueClass) && actual.is_a?(FalseClass)) ||
127
+ (expected.is_a?(FalseClass) && actual.is_a?(TrueClass))
128
+ end
129
+
117
130
  def object_diff expected, actual, options
118
131
  return class_diff(expected, actual) if options[:structure]
119
132
  if expected != actual
@@ -85,6 +85,23 @@ module Pact
85
85
  as_json.reject{ |key, value| !keep_keys.include? key }
86
86
  end
87
87
 
88
+ def short_description
89
+ "#{method} #{full_path}"
90
+ end
91
+
92
+ def full_path
93
+ fp = ''
94
+ if path.empty?
95
+ fp << "/"
96
+ else
97
+ fp << path
98
+ end
99
+ if query && !query.empty?
100
+ fp << ("?" + query)
101
+ end
102
+ fp
103
+ end
104
+
88
105
  end
89
106
 
90
107
  class Expected < Base
@@ -130,6 +147,10 @@ module Pact
130
147
  as_json.merge( options.empty? ? {} : { options: options} )
131
148
  end
132
149
 
150
+ def generated_body
151
+ Pact::Reification.from_term(body)
152
+ end
153
+
133
154
  # Don't want to put the default options in the pact json just yet, so calculating these at run time, rather than assigning
134
155
  # the result to @options
135
156
  def runtime_options
@@ -1,3 +1,3 @@
1
1
  module Pact
2
- VERSION = "0.1.35"
2
+ VERSION = "0.1.37"
3
3
  end
@@ -46,7 +46,7 @@ module Pact
46
46
  let(:request) { double(Pact::Request::Expected, :as_json_with_options => {:opts => 'blah'})}
47
47
  let(:response) { double('response') }
48
48
  let(:generated_response ) { double('generated_response', :to_json => 'generated_response') }
49
- subject { Interaction.new(:description => 'description', :request => request, :response => response, :producer_state => 'ignored')}
49
+ subject { Interaction.new(:description => 'description', :request => request, :response => response, :producer_state => 'some state')}
50
50
  let(:expected_hash) { {:response => generated_response, :request => as_json_with_options, :description => '' } }
51
51
 
52
52
  before do
@@ -62,8 +62,8 @@ module Pact
62
62
  expect(subject.as_json_for_mock_service[:request]).to eq as_json_with_options
63
63
  end
64
64
 
65
- it "does not send the producer state" do
66
- expect(subject.as_json_for_mock_service.key?(:producer_state)).to be_false
65
+ it "includes the producer state" do
66
+ expect(subject.as_json_for_mock_service[:producer_state]).to eq 'some state'
67
67
  end
68
68
 
69
69
  it "includes the description" do
@@ -71,7 +71,7 @@ module Pact
71
71
  end
72
72
 
73
73
  it "doesn't have any other keys" do
74
- expect(subject.as_json_for_mock_service.keys).to eq [:response, :request, :description]
74
+ expect(subject.as_json_for_mock_service.keys).to eq [:response, :request, :description, :producer_state]
75
75
  end
76
76
  end
77
77
 
@@ -10,7 +10,7 @@ module Pact::Consumer
10
10
 
11
11
  shared_context "unexpected requests and missed interactions" do
12
12
  let(:expected_call) { {request: 'blah'} }
13
- let(:unexpected_call) { {request: 'meh'} }
13
+ let(:unexpected_call) { Pact::Request::Actual.from_hash(path: '/path', method: 'get') }
14
14
  subject {
15
15
  interactionList = InteractionList.instance
16
16
  interactionList.add expected_call
@@ -21,7 +21,7 @@ module Pact::Consumer
21
21
 
22
22
  shared_context "no unexpected requests or missed interactions exist" do
23
23
  let(:expected_call) { {request: 'blah'} }
24
- let(:unexpected_call) { {request: 'meh'} }
24
+ let(:unexpected_call) { Pact::Request::Actual.from_hash(path: '/path', method: 'get') }
25
25
  subject {
26
26
  interactionList = InteractionList.instance
27
27
  interactionList.add expected_call
@@ -34,7 +34,7 @@ module Pact::Consumer
34
34
  context "when unexpected requests and missed interactions exist" do
35
35
  include_context "unexpected requests and missed interactions"
36
36
  let(:expected) {
37
- {:missing_interactions=>[{:request=>"blah"}], :unexpected_requests=>[{:request=>"meh"}]}
37
+ {:missing_interactions=>[{:request=>"blah"}], :unexpected_requests=>[{:method=>"get", :path=>"/path"}]}
38
38
  }
39
39
  it "returns the unexpected requests and missed interactions" do
40
40
  expect(subject.interaction_diffs).to eq expected
@@ -68,8 +68,8 @@ module Pact
68
68
  describe "find_interactions" do
69
69
  let(:consumer) { double('ServiceConsumer', :name => 'Consumer')}
70
70
  let(:producer) { double('ServiceProducer', :name => 'Producer')}
71
- let(:interaction1) { {'description' => 'a request for food'} }
72
- let(:interaction2) { {'description' => 'a request for drink'} }
71
+ let(:interaction1) { Pact::Consumer::Interaction.new(:description => 'a request for food') }
72
+ let(:interaction2) { Pact::Consumer::Interaction.new(:description => 'a request for drink') }
73
73
  subject { ConsumerContract.new(:interactions => [interaction1, interaction2], :consumer => consumer, :producer => producer) }
74
74
  context "by description" do
75
75
  context "when no interactions are found" do
@@ -87,8 +87,9 @@ module Pact
87
87
  describe "find_interaction" do
88
88
  let(:consumer) { double('ServiceConsumer', :name => 'Consumer')}
89
89
  let(:producer) { double('ServiceProducer', :name => 'Producer')}
90
- let(:interaction1) { {'description' => 'a request for food'} }
91
- let(:interaction2) { {'description' => 'a request for drink'} }
90
+ # Should be stubbing these
91
+ let(:interaction1) { Pact::Consumer::Interaction.new(:description => 'a request for food') }
92
+ let(:interaction2) { Pact::Consumer::Interaction.new(:description => 'a request for drink') }
92
93
  subject { ConsumerContract.new(:interactions => [interaction1, interaction2], :consumer => consumer, :producer => producer) }
93
94
  context "by description" do
94
95
  context "when a match is found" do
@@ -27,13 +27,23 @@ describe Pact::Matchers do
27
27
 
28
28
  describe 'structure_diff' do
29
29
  let(:expected) {
30
- {a: 'a string', b: 1, c: nil, d: [{e: 'thing'}], f: {g: 10}}
30
+ {a: 'a string', b: 1, c: nil, d: [{e: 'thing'}], f: {g: 10}, h: false}
31
31
  }
32
32
 
33
33
  context "when the classes match" do
34
- let(:actual) { {a: 'another string', b: 2, c: nil, d: [{e: 'something'}], f: {g: 100}} }
34
+ let(:actual) { {a: 'another string', b: 2, c: nil, d: [{e: 'something'}], f: {g: 100}, h: true} }
35
+ let(:difference) { {} }
35
36
  it "returns an empty hash" do
36
- expect(structure_diff(expected, actual)).to be_empty
37
+ expect(structure_diff(expected, actual)).to eq difference
38
+ end
39
+ end
40
+
41
+ context "when a key is not found" do
42
+ let(:actual) { {a: 'blah'} }
43
+ let(:expected) { {b: 'blah'} }
44
+ let(:difference) { {:b=>{:expected=>{:class=>String, :eg=>"blah"}, :actual=>Pact::Matchers::KeyNotFound.new}} }
45
+ it "returns the difference" do
46
+ expect(structure_diff(expected, actual)).to eq difference
37
47
  end
38
48
  end
39
49
 
@@ -2,6 +2,28 @@ require 'spec_helper'
2
2
  require 'pact/request'
3
3
 
4
4
  shared_examples "a request" do
5
+
6
+ describe 'full_path' do
7
+ context "with empty path" do
8
+ subject { described_class.from_hash({:path => '', :method => 'get'}) }
9
+ it "returns the full path"do
10
+ expect(subject.full_path).to eq "/"
11
+ end
12
+ end
13
+ context "with a path" do
14
+ subject { described_class.from_hash({:path => '/path', :method => 'get'}) }
15
+ it "returns the full path"do
16
+ expect(subject.full_path).to eq "/path"
17
+ end
18
+ end
19
+ context "with a path and query" do
20
+ subject { described_class.from_hash({:path => '/path', :method => 'get', :query => "something"}) }
21
+ it "returns the full path"do
22
+ expect(subject.full_path).to eq "/path?something"
23
+ end
24
+ end
25
+ end
26
+
5
27
  describe "building from a hash" do
6
28
 
7
29
  let(:raw_request) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.35
4
+ version: 0.1.37
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2013-08-27 00:00:00.000000000 Z
16
+ date: 2013-09-11 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: randexp