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
data/pact.gemspec CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |gem|
19
19
  gem.license = 'MIT'
20
20
 
21
21
  gem.add_runtime_dependency 'randexp', '~> 0.1.7'
22
- gem.add_runtime_dependency 'hashie', '~> 2.0'
23
22
  gem.add_runtime_dependency 'rspec', '~> 2.12'
24
23
  gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
25
24
  gem.add_runtime_dependency 'rack-test', '~> 0.6.2'
@@ -33,4 +32,5 @@ Gem::Specification.new do |gem|
33
32
  gem.add_development_dependency 'webmock', '~> 1.9.3'
34
33
  gem.add_development_dependency 'pry'
35
34
  gem.add_development_dependency 'fakefs', '~> 0.4'
35
+ gem.add_development_dependency 'hashie', '~> 2.0'
36
36
  end
@@ -96,80 +96,8 @@ describe "A service consumer side of a pact", :pact => true do
96
96
 
97
97
  expect(bob_post_response.code).to eql '200'
98
98
  expect(bob_post_response.body).to eql([{"name" => "Roger", "age" => 20}].to_json)
99
- expect{ bob_service.verify('goes a little something like this') }.to raise_error /do not match/
100
- end
101
-
102
- context "with a provider state" do
103
- before do
104
- Pact.clear_configuration
105
-
106
- Pact.service_consumer "Consumer" do
107
- has_pact_with "Zebra Service" do
108
- mock_service :zebra_service do
109
- verify false
110
- port 1235
111
- end
112
- end
113
- end
114
- end
115
-
116
- it "goes like this" do
117
- zebra_service.
118
- given(:the_zebras_are_here).
119
- upon_receiving("a retrieve Mallory request").with({
120
- method: :get,
121
- path: '/mallory',
122
- headers: {'Accept' => 'text/html'}
123
- }).
124
- will_respond_with({
125
- status: 200,
126
- headers: { 'Content-Type' => 'text/html' },
127
- body: Pact::Term.new(matcher: /Mallory/, generate: 'That is some good Mallory.')
128
- })
129
-
130
- interactions = Pact::ConsumerContract.from_json(File.read(zebra_service.consumer_contract.pactfile_path)).interactions
131
- interactions.first.provider_state.should eq("the_zebras_are_here")
132
- sleep 1
133
- end
134
- end
135
-
136
- context "with a async interaction with provider" do
137
- before do
138
- Pact.clear_configuration
139
-
140
- Pact.service_consumer "Consumer" do | config |
141
- has_pact_with "Zebra Service" do
142
- mock_service :zebra_service do
143
- verify true
144
- port 1239
145
- end
146
- end
147
- end
148
- end
149
-
150
- it "goes like this" do
151
- zebra_service.
152
- given(:the_zebras_are_here).
153
- upon_receiving("a retrieve Mallory request").with({
154
- method: :get,
155
- path: '/mallory'
156
- }).
157
- will_respond_with({
158
- status: 200
159
- })
160
-
161
- async_interaction { Net::HTTP.get_response(URI('http://localhost:1239/mallory'))}
162
-
163
- zebra_service.wait_for_interactions wait_max_seconds: 1, poll_interval: 0.1
164
- end
165
-
166
- def async_interaction
167
- Thread.new do
168
- sleep 0.2
169
- yield
170
- end
171
- end
172
99
 
100
+ expect{ bob_service.verify('goes a little something like this') }.to raise_error /do not match/
173
101
  end
174
102
 
175
103
  end
@@ -40,6 +40,7 @@ module Pact::Provider
40
40
  pact = Pact::ConsumerContract.from_json <<-EOS
41
41
  {
42
42
  "consumer" : { "name" : "a consumer"},
43
+ "provider" : { "name" : "a provider"},
43
44
  "interactions" : [
44
45
  {
45
46
  "description": "donut creation request",
@@ -89,6 +90,7 @@ module Pact::Provider
89
90
  consumer_contract = Pact::ConsumerContract.from_json <<-EOS
90
91
  {
91
92
  "consumer" : { "name" : "the-wild-beast-store"},
93
+ "provider" : { "name" : "provider"},
92
94
  "interactions" : [
93
95
  {
94
96
  "description": "donut creation request",
@@ -120,6 +122,7 @@ module Pact::Provider
120
122
  consumer_contract = Pact::ConsumerContract.from_json <<-EOS
121
123
  {
122
124
  "consumer" : { "name" : "some consumer"},
125
+ "provider" : { "name" : "provider"},
123
126
  "interactions" : [
124
127
  {
125
128
  "description": "donut creation request",
@@ -0,0 +1,170 @@
1
+ require 'net/http'
2
+ require 'pact/consumer'
3
+ require 'pact/consumer/rspec'
4
+
5
+ describe "A service consumer side of a pact", :pact => true do
6
+
7
+ context "with more than one matching interaction found" do
8
+ let(:expected_response) do
9
+ {"message"=>"Multiple interaction found for GET /path", "matching_interactions"=>[{"description"=>"a request", "request"=>{"method"=>"get", "path"=>"/path", "body"=>{"a"=>"some body"}, "headers"=>{"Content-Type"=>"application/json"}}}, {"description"=>"an identical request", "request"=>{"method"=>"get", "path"=>"/path", "body"=>{"a"=>"some body"}, "headers"=>{"Content-Type"=>"application/json"}}}]}
10
+ end
11
+
12
+ it "returns an error" do
13
+ Pact.clear_configuration
14
+
15
+ Pact.service_consumer "Consumer" do
16
+ has_pact_with "Mary Service" do
17
+ mock_service :mary_service do
18
+ verify false
19
+ port 1237
20
+ end
21
+ end
22
+ end
23
+
24
+ mary_service
25
+ .given("something")
26
+ .upon_receiving("a request")
27
+ .with(method: 'get', path: '/path', body: {a: 'some body'}, headers: {'Content-Type' => 'application/json'})
28
+ .will_respond_with(status: 200)
29
+
30
+
31
+ mary_service
32
+ .upon_receiving("an identical request")
33
+ .with(method: 'get', path: '/path', body: {a: 'some body'}, headers: {'Content-Type' => 'application/json'})
34
+ .will_respond_with(status: 200)
35
+
36
+ uri = URI('http://localhost:1237/path')
37
+ post_req = Net::HTTP::Get.new(uri.path)
38
+ post_req['Content-Type'] = "application/json"
39
+ post_req.body = {a: "some body"}.to_json
40
+ response = Net::HTTP.start(uri.hostname, uri.port) do |http|
41
+ http.request post_req
42
+ end
43
+
44
+ expect(JSON.load(response.body)).to eq expected_response
45
+ end
46
+
47
+ end
48
+
49
+ context "with no matching interaction found" do
50
+
51
+ let(:expected_response) do
52
+ {
53
+ "message"=>"No interaction found for GET /path",
54
+ "interaction_diffs"=>[{
55
+ "description" => "a request that will not be properly matched",
56
+ "provider_state" => "something",
57
+ "body"=>{
58
+ "a"=>{
59
+ "expected"=>"some body",
60
+ "actual"=>"not matching body"
61
+ }
62
+ }
63
+ }]
64
+ }
65
+ end
66
+
67
+ it "returns an error" do
68
+ Pact.clear_configuration
69
+
70
+ Pact.service_consumer "Consumer" do
71
+ has_pact_with "Mary Service" do
72
+ mock_service :mary_service do
73
+ verify false
74
+ port 1236
75
+ end
76
+ end
77
+ end
78
+
79
+ mary_service
80
+ .given("something")
81
+ .upon_receiving("a request that will not be properly matched")
82
+ .with(method: 'get', path: '/path', body: {a: 'some body'}, headers: {'Content-Type' => 'application/json'})
83
+ .will_respond_with(status: 200)
84
+
85
+ uri = URI('http://localhost:1236/path')
86
+ post_req = Net::HTTP::Get.new(uri.path)
87
+ post_req['Content-Type'] = "application/json"
88
+ post_req.body = {a: "not matching body"}.to_json
89
+ response = Net::HTTP.start(uri.hostname, uri.port) do |http|
90
+ http.request post_req
91
+ end
92
+
93
+ expect(JSON.load(response.body)).to eq expected_response
94
+ end
95
+ end
96
+
97
+ context "with a provider state" do
98
+ before do
99
+ Pact.clear_configuration
100
+
101
+ Pact.service_consumer "Consumer" do
102
+ has_pact_with "Zebra Service" do
103
+ mock_service :zebra_service do
104
+ verify false
105
+ port 1235
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ it "goes like this" do
112
+ zebra_service.
113
+ given(:the_zebras_are_here).
114
+ upon_receiving("a retrieve Mallory request").with({
115
+ method: :get,
116
+ path: '/mallory',
117
+ headers: {'Accept' => 'text/html'}
118
+ }).
119
+ will_respond_with({
120
+ status: 200,
121
+ headers: { 'Content-Type' => 'text/html' },
122
+ body: Pact::Term.new(matcher: /Mallory/, generate: 'That is some good Mallory.')
123
+ })
124
+
125
+ interactions = Pact::ConsumerContract.from_json(File.read(zebra_service.consumer_contract.pactfile_path)).interactions
126
+ interactions.first.provider_state.should eq("the_zebras_are_here")
127
+ sleep 1
128
+ end
129
+ end
130
+
131
+ context "with a async interaction with provider" do
132
+ before do
133
+ Pact.clear_configuration
134
+
135
+ Pact.service_consumer "Consumer" do | config |
136
+ has_pact_with "Zebra Service" do
137
+ mock_service :zebra_service do
138
+ verify true
139
+ port 1239
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ it "goes like this" do
146
+ zebra_service.
147
+ given(:the_zebras_are_here).
148
+ upon_receiving("a retrieve Mallory request").
149
+ with({
150
+ method: :get,
151
+ path: '/mallory'
152
+ }).
153
+ will_respond_with({status: 200})
154
+
155
+ async_interaction { Net::HTTP.get_response(URI('http://localhost:1239/mallory'))}
156
+
157
+ zebra_service.wait_for_interactions wait_max_seconds: 1, poll_interval: 0.1
158
+ end
159
+
160
+ def async_interaction
161
+ Thread.new do
162
+ sleep 0.2
163
+ yield
164
+ end
165
+ end
166
+
167
+ end
168
+
169
+ end
170
+
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'pact/configuration'
3
- require 'pact/consumer/dsl'
3
+ require 'pact/consumer/configuration'
4
4
 
5
5
  describe "consumer side" do
6
6
  describe "configure" do
@@ -1,16 +1,17 @@
1
1
  require 'spec_helper'
2
- require 'pact/consumer/dsl'
2
+ require 'pact/consumer/configuration'
3
3
 
4
- module Pact::Consumer::DSL
4
+ module Pact::Consumer::Configuration
5
5
 
6
- describe Service do
6
+ describe MockService do
7
7
  before do
8
8
  Pact.clear_configuration
9
9
  Pact::Consumer::AppManager.instance.stub(:register_mock_service_for)
10
10
  end
11
11
  describe "configure_consumer_contract_builder" do
12
+ let(:consumer_name) {'consumer'}
12
13
  subject {
13
- Service.new :mock_service do
14
+ MockService.build :mock_service, consumer_name, provider_name do
14
15
  port 1234
15
16
  standalone true
16
17
  verify true
@@ -18,12 +19,12 @@ module Pact::Consumer::DSL
18
19
  }
19
20
 
20
21
  let(:provider_name) { 'Mock Provider'}
21
- let(:consumer_contract_builder) { double('Pact::Consumer::ConsumerContractBuilder').as_null_object}
22
+ let(:consumer_contract_builder) { instance_double('Pact::Consumer::ConsumerContractBuilder')}
22
23
  let(:url) { "http://localhost:1234"}
23
24
 
24
25
  it "adds a verification to the Pact configuration" do
25
26
  Pact::Consumer::ConsumerContractBuilder.stub(:new).and_return(consumer_contract_builder)
26
- subject.configure_consumer_contract_builder({})
27
+ subject.finalize
27
28
  consumer_contract_builder.should_receive(:verify)
28
29
  Pact.configuration.provider_verifications.first.call
29
30
  end
@@ -31,12 +32,12 @@ module Pact::Consumer::DSL
31
32
  context "when standalone" do
32
33
  it "does not register the app with the AppManager" do
33
34
  Pact::Consumer::AppManager.instance.should_not_receive(:register_mock_service_for)
34
- subject.configure_consumer_contract_builder({})
35
+ subject.finalize
35
36
  end
36
37
  end
37
38
  context "when not standalone" do
38
39
  subject {
39
- Service.new :mock_service do
40
+ MockService.build :mock_service, consumer_name, provider_name do
40
41
  port 1234
41
42
  standalone false
42
43
  verify true
@@ -44,7 +45,7 @@ module Pact::Consumer::DSL
44
45
  }
45
46
  it "registers the app with the AppManager" do
46
47
  Pact::Consumer::AppManager.instance.should_receive(:register_mock_service_for).with(provider_name, url)
47
- subject.configure_consumer_contract_builder({:provider_name => provider_name })
48
+ subject.finalize
48
49
  end
49
50
  end
50
51
  end
@@ -119,9 +119,9 @@ module Pact
119
119
  }
120
120
  }
121
121
 
122
- let(:interaction_json) { interaction.to_json_for_mock_service }
122
+ let(:interaction_json) { {} }
123
123
 
124
- let(:interaction) { Pact::Interaction.from_hash(JSON.parse(interaction_hash.to_json)) }
124
+ let(:interaction) { Pact::Interaction.from_hash(JSON.load(interaction_hash.to_json)) }
125
125
 
126
126
  before do
127
127
  stub_request(:post, 'localhost:2222/interactions')
@@ -49,7 +49,7 @@ module Pact
49
49
  let(:expected_request) { {an: 'expected_request'} }
50
50
 
51
51
  it "sets the request on the interaction as a instance of Request::Expected" do
52
- Request::Expected.should_receive(:from_hash).with(request).and_return(expected_request)
52
+ Pact::Request::Expected.should_receive(:from_hash).with(request).and_return(expected_request)
53
53
  interaction.should_receive(:request=).with(expected_request)
54
54
  subject.with(request)
55
55
  end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'pact/consumer/mock_service/interaction_list'
3
+
4
+ module Pact::Consumer
5
+
6
+ describe InteractionList do
7
+ shared_context "unexpected requests and missed interactions" do
8
+ let(:expected_interaction) { InteractionFactory.create }
9
+ let(:unexpected_request) { RequestFactory.create_actual }
10
+ subject {
11
+ interactionList = InteractionList.new
12
+ interactionList.add expected_interaction
13
+ interactionList.register_unexpected_request unexpected_request
14
+ interactionList
15
+ }
16
+ end
17
+
18
+ shared_context "no unexpected requests or missed interactions exist" do
19
+ let(:expected_interaction) { InteractionFactory.create }
20
+ let(:unexpected_request) { RequestFactory.create_actual }
21
+ subject {
22
+ interactionList = InteractionList.new
23
+ interactionList.add expected_interaction
24
+ interactionList.register_matched expected_interaction
25
+ interactionList
26
+ }
27
+ end
28
+
29
+ describe "interaction_diffs" do
30
+ context "when unexpected requests and missed interactions exist" do
31
+ include_context "unexpected requests and missed interactions"
32
+ let(:expected_diff) {
33
+ {:missing_interactions=>[expected_interaction.as_json], :unexpected_requests=>[unexpected_request.as_json]}
34
+ }
35
+ it "returns the unexpected requests and missed interactions" do
36
+ expect(subject.interaction_diffs).to eq expected_diff
37
+ end
38
+ end
39
+
40
+ context "when no unexpected requests or missed interactions exist" do
41
+ include_context "no unexpected requests or missed interactions exist"
42
+ let(:expected_diff) {
43
+ {}
44
+ }
45
+ it "returns an empty hash" do
46
+ expect(subject.interaction_diffs).to eq expected_diff
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "all_matched?" do
52
+ context "when unexpected requests or missed interactions exist" do
53
+ include_context "unexpected requests and missed interactions"
54
+ it "returns false" do
55
+ expect(subject.all_matched?).to be_false
56
+ end
57
+ end
58
+ context "when unexpected requests or missed interactions do not exist" do
59
+ include_context "no unexpected requests or missed interactions exist"
60
+ it "returns false" do
61
+ expect(subject.all_matched?).to be_true
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end