pact 1.0.9 → 1.0.10

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 (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