pact 0.1.28

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 (72) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +83 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +238 -0
  7. data/Rakefile +33 -0
  8. data/bin/pact +4 -0
  9. data/lib/pact/app.rb +32 -0
  10. data/lib/pact/configuration.rb +54 -0
  11. data/lib/pact/consumer/app_manager.rb +177 -0
  12. data/lib/pact/consumer/configuration_dsl.rb +71 -0
  13. data/lib/pact/consumer/consumer_contract_builder.rb +79 -0
  14. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  15. data/lib/pact/consumer/dsl.rb +98 -0
  16. data/lib/pact/consumer/interaction.rb +70 -0
  17. data/lib/pact/consumer/mock_service.rb +340 -0
  18. data/lib/pact/consumer/rspec.rb +43 -0
  19. data/lib/pact/consumer/run_condor.rb +4 -0
  20. data/lib/pact/consumer/run_mock_contract_service.rb +13 -0
  21. data/lib/pact/consumer/service_consumer.rb +22 -0
  22. data/lib/pact/consumer/service_producer.rb +23 -0
  23. data/lib/pact/consumer.rb +7 -0
  24. data/lib/pact/consumer_contract.rb +110 -0
  25. data/lib/pact/json_warning.rb +23 -0
  26. data/lib/pact/logging.rb +14 -0
  27. data/lib/pact/matchers/matchers.rb +85 -0
  28. data/lib/pact/matchers.rb +1 -0
  29. data/lib/pact/producer/configuration_dsl.rb +62 -0
  30. data/lib/pact/producer/matchers.rb +22 -0
  31. data/lib/pact/producer/pact_spec_runner.rb +57 -0
  32. data/lib/pact/producer/producer_state.rb +81 -0
  33. data/lib/pact/producer/rspec.rb +127 -0
  34. data/lib/pact/producer/test_methods.rb +89 -0
  35. data/lib/pact/producer.rb +1 -0
  36. data/lib/pact/rake_task.rb +64 -0
  37. data/lib/pact/reification.rb +26 -0
  38. data/lib/pact/request.rb +109 -0
  39. data/lib/pact/term.rb +40 -0
  40. data/lib/pact/verification_task.rb +57 -0
  41. data/lib/pact/version.rb +3 -0
  42. data/lib/pact.rb +5 -0
  43. data/lib/tasks/pact.rake +6 -0
  44. data/pact.gemspec +36 -0
  45. data/scratchpad.txt +36 -0
  46. data/spec/features/consumption_spec.rb +146 -0
  47. data/spec/features/producer_states/zebras.rb +28 -0
  48. data/spec/features/production_spec.rb +160 -0
  49. data/spec/integration/pact/configuration_spec.rb +65 -0
  50. data/spec/lib/pact/configuration_spec.rb +35 -0
  51. data/spec/lib/pact/consumer/app_manager_spec.rb +41 -0
  52. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +87 -0
  53. data/spec/lib/pact/consumer/dsl_spec.rb +52 -0
  54. data/spec/lib/pact/consumer/interaction_spec.rb +108 -0
  55. data/spec/lib/pact/consumer/mock_service_spec.rb +147 -0
  56. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  57. data/spec/lib/pact/consumer_contract_spec.rb +125 -0
  58. data/spec/lib/pact/matchers/matchers_spec.rb +354 -0
  59. data/spec/lib/pact/producer/configuration_dsl_spec.rb +101 -0
  60. data/spec/lib/pact/producer/producer_state_spec.rb +103 -0
  61. data/spec/lib/pact/producer/rspec_spec.rb +48 -0
  62. data/spec/lib/pact/reification_spec.rb +43 -0
  63. data/spec/lib/pact/request_spec.rb +316 -0
  64. data/spec/lib/pact/term_spec.rb +36 -0
  65. data/spec/lib/pact/verification_task_spec.rb +64 -0
  66. data/spec/spec_helper.rb +5 -0
  67. data/spec/support/a_consumer-a_producer.json +34 -0
  68. data/spec/support/pact_rake_support.rb +41 -0
  69. data/spec/support/test_app_fail.json +22 -0
  70. data/spec/support/test_app_pass.json +21 -0
  71. data/tasks/pact-test.rake +19 -0
  72. metadata +381 -0
@@ -0,0 +1,316 @@
1
+ require 'spec_helper'
2
+ require 'pact/request'
3
+
4
+ shared_examples "a request" do
5
+ describe "building from a hash" do
6
+
7
+ let(:raw_request) do
8
+ {
9
+ 'method' => 'get',
10
+ 'path' => '/mallory',
11
+ 'headers' => {
12
+ 'Content-Type' => 'application/json'
13
+ },
14
+ 'body' => 'hello mallory'
15
+ }
16
+ end
17
+
18
+ subject { described_class.from_hash(raw_request) }
19
+
20
+ its(:method) { should == 'get' }
21
+ its(:path) { should == '/mallory' }
22
+ its(:body) { should == 'hello mallory' }
23
+ its(:query) { should eq Pact::Request::NullExpectation.new }
24
+
25
+ it "blows up if method is absent" do
26
+ raw_request.delete 'method'
27
+ expect { described_class.from_hash(raw_request) }.to raise_error
28
+ end
29
+
30
+ it "blows up if path is absent" do
31
+ raw_request.delete 'path'
32
+ expect { described_class.from_hash(raw_request) }.to raise_error
33
+ end
34
+
35
+ it "does not blow up if body is missing" do
36
+ raw_request.delete 'body'
37
+ expect { described_class.from_hash(raw_request) }.to_not raise_error
38
+ end
39
+
40
+ end
41
+ end
42
+
43
+ module Pact
44
+
45
+ describe Request::Expected do
46
+ it_behaves_like "a request"
47
+
48
+ describe "matching to actual requests" do
49
+
50
+ subject { Request::Expected.new(expected_method, expected_path, expected_headers, expected_body, expected_query) }
51
+
52
+ let(:expected_method) { 'get' }
53
+ let(:expected_path) { '/foo' }
54
+ let(:expected_headers) { nil }
55
+ let(:expected_body) { nil }
56
+ let(:expected_query) { '' }
57
+
58
+ let(:actual_request) { Request::Actual.new(actual_method, actual_path, actual_headers, actual_body, actual_query) }
59
+
60
+ let(:actual_method) { 'get' }
61
+ let(:actual_path) { '/foo' }
62
+ let(:actual_headers) { nil }
63
+ let(:actual_body) { nil }
64
+ let(:actual_query) { '' }
65
+
66
+ it "matches identical requests" do
67
+ expect(subject.match actual_request).to be_true
68
+ end
69
+
70
+ context "when the methods are the same but one is symbolized" do
71
+ let(:expected_method) { :get }
72
+ let(:actual_method) { 'get' }
73
+
74
+ it "matches" do
75
+ expect(subject.match actual_request).to be_true
76
+ end
77
+ end
78
+
79
+ context "when the methods are different" do
80
+ let(:expected_method) { 'get' }
81
+ let(:actual_method) { 'post' }
82
+
83
+ it "does not match" do
84
+ expect(subject.match actual_request).to be_false
85
+ end
86
+ end
87
+
88
+ context "when the paths are different" do
89
+ let(:expected_path) { '/foo' }
90
+ let(:actual_path) { '/bar' }
91
+
92
+ it "does not match" do
93
+ expect(subject.match actual_request).to be_false
94
+ end
95
+ end
96
+
97
+ context "when the paths vary only by a trailing slash" do
98
+ let(:expected_path) { '/foo' }
99
+ let(:actual_path) { '/foo/' }
100
+
101
+ it "matches" do
102
+ expect(subject.match actual_request).to be_true
103
+ end
104
+ end
105
+
106
+ context "when the expected body is nil and the actual body is empty" do
107
+ let(:expected_body) { nil }
108
+ let(:actual_body) { '' }
109
+
110
+ it "does not match" do
111
+ expect(subject.match actual_request).to be_false
112
+ end
113
+ end
114
+
115
+ context "when the expected body has no expectation and the actual body is empty" do
116
+ let(:expected_body) { Request::NullExpectation.new }
117
+ let(:actual_body) { '' }
118
+
119
+ it "matches" do
120
+ expect(subject.match actual_request).to be_true
121
+ end
122
+ end
123
+
124
+ context "when the expected body is nested and the actual body is nil" do
125
+ let(:expected_body) do
126
+ {
127
+ a: 'a'
128
+ }
129
+ end
130
+
131
+ let(:actual_body) { nil }
132
+
133
+ it "does not match" do
134
+ expect(subject.match actual_request).to be_false
135
+ end
136
+ end
137
+
138
+ context "when the bodies are different" do
139
+ let(:expected_body) { 'foo' }
140
+ let(:actual_body) { 'bar' }
141
+
142
+ it "does not match" do
143
+ expect(subject.match actual_request).to be_false
144
+ end
145
+ end
146
+
147
+ context "when the expected body contains matching regexes" do
148
+ let(:expected_body) do
149
+ {
150
+ name: 'Bob',
151
+ customer_id: /CN.*/
152
+ }
153
+ end
154
+
155
+ let(:actual_body) do
156
+ {
157
+ name: 'Bob',
158
+ customer_id: 'CN1234'
159
+ }
160
+ end
161
+
162
+ it "matches" do
163
+ expect(subject.match actual_request).to be_true
164
+ end
165
+ end
166
+
167
+ context "when the expected body contains non-matching regexes" do
168
+ let(:expected_body) do
169
+ {
170
+ name: 'Bob',
171
+ customer_id: /foo/
172
+ }
173
+ end
174
+
175
+ let(:actual_body) do
176
+ {
177
+ name: 'Bob',
178
+ customer_id: 'CN1234'
179
+ }
180
+ end
181
+
182
+ it "does not match" do
183
+ expect(subject.match actual_request).to be_false
184
+ end
185
+ end
186
+
187
+ context "when the expected body contains matching terms" do
188
+ let(:expected_body) do
189
+ {
190
+ name: 'Bob',
191
+ customer_id: Term.new(matcher: /CN.*/)
192
+ }
193
+ end
194
+
195
+ let(:actual_body) do
196
+ {
197
+ name: 'Bob',
198
+ customer_id: 'CN1234'
199
+ }
200
+ end
201
+
202
+ it "matches" do
203
+ expect(subject.match actual_request).to be_true
204
+ end
205
+ end
206
+
207
+ context "when the expected body contains non-matching terms" do
208
+ let(:expected_body) do
209
+ {
210
+ name: 'Bob',
211
+ customer_id: Term.new(matcher: /foo/)
212
+ }
213
+ end
214
+
215
+ let(:actual_body) do
216
+ {
217
+ name: 'Bob',
218
+ customer_id: 'CN1234'
219
+ }
220
+ end
221
+
222
+ it "does not match" do
223
+ expect(subject.match actual_request).to be_false
224
+ end
225
+ end
226
+
227
+ context "when the expected body contains non-matching arrays" do
228
+ let(:expected_body) do
229
+ {
230
+ name: 'Robert',
231
+ nicknames: ['Bob', 'Bobert']
232
+ }
233
+ end
234
+
235
+ let(:actual_body) do
236
+ {
237
+ name: 'Bob',
238
+ nicknames: ['Bob']
239
+ }
240
+ end
241
+
242
+ it "does not match" do
243
+ expect(subject.match actual_request).to be_false
244
+ end
245
+ end
246
+ context "when the expected body contains non-matching hash where one field contains a substring of the other" do
247
+ let(:expected_body) do
248
+ {
249
+ name: 'Robert',
250
+ }
251
+ end
252
+
253
+ let(:actual_body) do
254
+ {
255
+ name: 'Rob'
256
+ }
257
+ end
258
+
259
+ it "does not match" do
260
+ expect(subject.match actual_request).to be_false
261
+ end
262
+ end
263
+
264
+ context "when the expected body contains matching arrays" do
265
+ let(:expected_body) do
266
+ {
267
+ name: 'Robert',
268
+ nicknames: ['Bob', 'Bobert']
269
+ }
270
+ end
271
+
272
+ let(:actual_body) do
273
+ {
274
+ name: 'Robert',
275
+ nicknames: ['Bob', 'Bobert']
276
+ }
277
+ end
278
+
279
+ it "does not match" do
280
+ expect(subject.match actual_request).to be_true
281
+ end
282
+ end
283
+
284
+ context "when the queries are different" do
285
+ let(:expected_query) { 'foo' }
286
+ let(:actual_query) { 'bar' }
287
+
288
+ it "does not match" do
289
+ expect(subject.match actual_request).to be_false
290
+ end
291
+ end
292
+
293
+ context 'when there is no query expectation' do
294
+ let(:expected_query) { Request::NullExpectation.new }
295
+ let(:actual_query) { 'bar' }
296
+
297
+ it 'matches' do
298
+ expect(subject.match actual_request).to be_true
299
+ end
300
+ end
301
+
302
+ context "when a string is expected, but a number is found" do
303
+ let(:actual_body) { { thing: 123} }
304
+ let(:expected_body) { { thing: "123" } }
305
+
306
+ it 'does not match' do
307
+ expect(subject.match actual_request).to be_false
308
+ end
309
+ end
310
+ end
311
+ end
312
+
313
+ describe Request::Actual do
314
+ it_behaves_like "a request"
315
+ end
316
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module Pact
4
+ describe Term do
5
+
6
+ describe "equality" do
7
+ context "when the matcher and generate attrs are the same" do
8
+ let(:this) { Term.new(generate: 'A', matcher: /A/) }
9
+ let(:that) { Term.new(generate: 'A', matcher: /A/) }
10
+
11
+ it "is equal" do
12
+ expect(this).to eq that
13
+ end
14
+ end
15
+
16
+ context "when the generate attrs are different" do
17
+ let(:this) { Term.new(generate: /A/) }
18
+ let(:that) { Term.new(generate: /B/) }
19
+
20
+ it "is not equal" do
21
+ expect(this).to_not eq that
22
+ end
23
+ end
24
+
25
+ context "when the matcher attrs are different" do
26
+ let(:this) { Term.new(matcher: 'A') }
27
+ let(:that) { Term.new(matcher: 'B') }
28
+
29
+ it "is not equal" do
30
+ expect(this).to_not eq that
31
+ end
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+ require 'pact/verification_task'
3
+
4
+ module Pact
5
+ describe VerificationTask do
6
+ before :all do
7
+ @support_file = 'some_file.rb'
8
+ @pact_uri = 'http://example.org/pact.json'
9
+ @task_name = 'pact:verify:pact_rake_spec'
10
+ @consumer = 'some-consumer'
11
+
12
+ VerificationTask.new(:pact_rake_spec) do | pact |
13
+ pact.uri @pact_uri, support_file: @support_file, consumer: @consumer
14
+ end
15
+ end
16
+
17
+ describe '.initialize' do
18
+
19
+ it 'creates the tasks' do
20
+ Rake::Task.tasks.should include_task @task_name
21
+ end
22
+
23
+ end
24
+
25
+ describe 'execute' do
26
+
27
+ let(:consumer_contract) { [ uri: @pact_uri, support_file: @support_file, consumer: @consumer] }
28
+
29
+ it 'verifies the pacts using PactSpecRunner' do
30
+ Producer::PactSpecRunner.should_receive(:run).with(consumer_contract).and_return(0)
31
+ Rake::Task[@task_name].execute
32
+ end
33
+
34
+ context 'when all specs pass' do
35
+
36
+ before do
37
+ Producer::PactSpecRunner.stub(:run).and_return(0)
38
+ end
39
+
40
+ it 'does not raise an exception' do
41
+ Rake::Task[@task_name].execute
42
+ end
43
+ end
44
+
45
+ context 'when one or more specs fail' do
46
+
47
+ before do
48
+ Producer::PactSpecRunner.stub(:run).and_return(1)
49
+ end
50
+
51
+ it 'raises an exception' do
52
+ expect { Rake::Task[@task_name].execute }.to raise_error RuntimeError
53
+ end
54
+
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ RSpec::Matchers.define :include_task do |expected|
61
+ match do |actual|
62
+ actual.any? { |task| task.name == expected }
63
+ end
64
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec'
2
+ require 'pact'
3
+ require 'webmock/rspec'
4
+
5
+ WebMock.disable_net_connect!(allow_localhost: true)
@@ -0,0 +1,34 @@
1
+ {
2
+ "producer": {
3
+ "name": "a producer"
4
+ },
5
+ "consumer": {
6
+ "name": "a consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "request one",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/path_one"
14
+ },
15
+ "response": {
16
+ },
17
+ "producer_state": "state one"
18
+ },
19
+ {
20
+ "description": "request two",
21
+ "request": {
22
+ "method": "get",
23
+ "path": "/path_two"
24
+ },
25
+ "response": {
26
+ }
27
+ }
28
+ ],
29
+ "metadata": {
30
+ "pact_gem": {
31
+ "version": "0.1.24"
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,41 @@
1
+ require 'json'
2
+
3
+ module Pact
4
+ module Test
5
+ class TestApp
6
+ def call env
7
+ if env['PATH_INFO'] == '/weather'
8
+ [200, {'Content-Type' => 'application/json'}, [{message: WEATHER[:current_state]}.to_json]]
9
+ else
10
+ raise "unexpected path #{env['PATH_INFO']}!!!"
11
+ end
12
+ end
13
+ end
14
+
15
+ Pact.configure do | config |
16
+ config.producer do
17
+ name "Some Producer"
18
+ app { TestApp.new }
19
+ end
20
+ end
21
+
22
+
23
+ #one with a top level consumer
24
+ Pact.with_consumer 'some-test-consumer' do
25
+ producer_state "the weather is sunny" do
26
+ set_up do
27
+ WEATHER ||= {}
28
+ WEATHER[:current_state] = 'sunny'
29
+ end
30
+ end
31
+ end
32
+
33
+ #one without a top level consumer
34
+ Pact.producer_state "the weather is cloudy" do
35
+ set_up do
36
+ WEATHER ||= {}
37
+ WEATHER[:current_state] = 'cloudy'
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ {
2
+ "consumer": {
3
+ "name": "an unknown consumer"
4
+ },
5
+ "interactions": [
6
+ {
7
+ "description": "A test request",
8
+ "request": {
9
+ "method": "get",
10
+ "path": "/weather",
11
+ "query": ""
12
+ },
13
+ "response": {
14
+ "status": 200,
15
+ "headers" : {"Content-type": "application/json"},
16
+ "body": {"message" : "this is not the weather you are looking for"}
17
+
18
+ },
19
+ "producer_state": "the weather is cloudy"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "consumer": {
3
+ "name": "some-test-consumer"
4
+ },
5
+ "interactions": [
6
+ {
7
+ "description": "A test request",
8
+ "request": {
9
+ "method": "get",
10
+ "path": "/weather",
11
+ "query": ""
12
+ },
13
+ "response": {
14
+ "status": 200,
15
+ "headers" : {"Content-type": "application/json"},
16
+ "body": {"message" : "sunny"}
17
+ },
18
+ "producer_state": "the weather is sunny"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,19 @@
1
+ require './lib/pact/producer/pact_spec_runner'
2
+
3
+ namespace :pact do
4
+
5
+ desc 'Runs pact tests against a sample application, testing failure and success.'
6
+ task :tests do
7
+ puts "Running task pact:tests"
8
+ # Run these specs silently, otherwise expected failures will be written to stdout and look like unexpected failures.
9
+
10
+ result = Pact::Producer::PactSpecRunner.run([{ uri: './spec/support/test_app_pass.json', support_file: './spec/support/pact_rake_support.rb', consumer: 'some-test-consumer' }], silent: true)
11
+ fail 'Expected pact to pass' unless (result == 0)
12
+
13
+ result = Pact::Producer::PactSpecRunner.run([{ uri: './spec/support/test_app_fail.json', support_file: './spec/support/pact_rake_support.rb' }], silent: true)
14
+ fail 'Expected pact to fail' if (result == 0)
15
+
16
+ puts "Task pact:tests completed succesfully."
17
+ end
18
+
19
+ end