pacto 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,87 @@
1
+ describe Pacto do
2
+ let(:host) { 'http://localhost' }
3
+ let(:contract_name) { 'contract' }
4
+ let(:contract) { double('contract') }
5
+
6
+ after do
7
+ described_class.unregister_all!
8
+ end
9
+
10
+ describe '.register' do
11
+ context 'by default' do
12
+ it 'should register a contract under a given name' do
13
+ described_class.register(contract_name, contract)
14
+ described_class.registered[contract_name].should == contract
15
+ end
16
+ end
17
+
18
+ context 'when a contract has already been registered with the same name' do
19
+ it 'should raise an argument error' do
20
+ described_class.register(contract_name, contract)
21
+ expect { described_class.register(contract_name, contract) }.to raise_error(ArgumentError)
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '.build_from_file' do
27
+ let(:contract_path) { File.join('spec', 'data', "#{contract_name}.json") }
28
+ let(:file_pre_processor) { double('file_pre_processor') }
29
+ let(:file_content) {File.read(contract_path)}
30
+
31
+ it 'should build a contract given a JSON file path and a host' do
32
+ file_pre_processor.stub(:process).and_return(file_content)
33
+ described_class.build_from_file(contract_path, host, file_pre_processor).
34
+ should be_a_kind_of(Pacto::Contract)
35
+ end
36
+
37
+ it 'should process files using File Pre Processor module' do
38
+ file_pre_processor.should_receive(:process).with(file_content).and_return(file_content)
39
+ described_class.build_from_file(contract_path, host, file_pre_processor)
40
+ end
41
+ end
42
+
43
+ describe '.use' do
44
+ before do
45
+ described_class.register(contract_name, contract)
46
+ end
47
+
48
+ context 'by default' do
49
+ let(:instantiated_contract) { double('instantiated contract', :response_body => response_body)}
50
+ let(:response_body) { double('response_body') }
51
+
52
+ before do
53
+ described_class.registered[contract_name].stub(:instantiate => instantiated_contract)
54
+ instantiated_contract.stub(:stub!)
55
+ end
56
+
57
+ it 'should instantiate a contract with default values' do
58
+ described_class.registered[contract_name].should_receive(:instantiate).with(nil).and_return(instantiated_contract)
59
+ described_class.use(contract_name)
60
+ end
61
+
62
+ it 'should return the instantiated contract' do
63
+ described_class.use(contract_name).should == instantiated_contract
64
+ end
65
+
66
+ it 'should stub further requests with the instantiated contract' do
67
+ instantiated_contract.should_receive(:stub!)
68
+ described_class.use(contract_name)
69
+ end
70
+
71
+ end
72
+
73
+ context 'when contract has not been registered' do
74
+ it 'should raise an argument error' do
75
+ expect { described_class.use('unregistered') }.to raise_error ArgumentError
76
+ end
77
+ end
78
+ end
79
+
80
+ describe '.unregister_all!' do
81
+ it 'should unregister all previously registered contracts' do
82
+ described_class.register(contract_name, contract)
83
+ described_class.unregister_all!
84
+ described_class.registered.should be_empty
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,73 @@
1
+ module Pacto
2
+ describe Request do
3
+ let(:host) { 'http://localhost' }
4
+ let(:method) { 'GET' }
5
+ let(:path) { '/hello_world' }
6
+ let(:headers) { {'accept' => 'application/json'} }
7
+ let(:params) { {'foo' => 'bar'} }
8
+
9
+ let(:request) do
10
+ described_class.new(host, {
11
+ 'method' => method,
12
+ 'path' => path,
13
+ 'headers' => headers,
14
+ 'params' => params
15
+ })
16
+ end
17
+ subject { request }
18
+
19
+ its(:method) { should == :get }
20
+ its(:path) { should == path }
21
+ its(:headers) { should == headers }
22
+ its(:params) { should == params }
23
+
24
+ describe '#execute' do
25
+ let(:connection) { double('connection') }
26
+ let(:response) { double('response') }
27
+ let(:adapted_response) { double('adapted response') }
28
+
29
+ context 'for a GET request' do
30
+ it 'should make a GET request and return the response' do
31
+ HTTParty.should_receive(:get).
32
+ with(host + path, {:query => params, :headers => headers}).
33
+ and_return(response)
34
+ ResponseAdapter.should_receive(:new).with(response).and_return(adapted_response)
35
+ request.execute.should == adapted_response
36
+ end
37
+ end
38
+
39
+ context 'for a POST request' do
40
+ let(:method) { 'POST' }
41
+
42
+ it 'should make a POST request and return the response' do
43
+ HTTParty.should_receive(:post).
44
+ with(host + path, {:body => params.to_json, :headers => headers}).
45
+ and_return(response)
46
+ ResponseAdapter.should_receive(:new).with(response).and_return(adapted_response)
47
+ request.execute.should == adapted_response
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#absolute_uri" do
53
+ it "should be equal to the host followed by the path" do
54
+ request.absolute_uri.should == "http://localhost/hello_world"
55
+ end
56
+ end
57
+
58
+ describe "#full_uri" do
59
+ context "when the query (params) exists" do
60
+ it "should be equal to the host followed by the path and the query" do
61
+ request.full_uri.should == "http://localhost/hello_world?foo=bar"
62
+ end
63
+ end
64
+
65
+ context "when the query (params) does not exists" do
66
+ let(:params) { {} }
67
+ it "should be equal to the host followed by the path" do
68
+ request.full_uri.should == "http://localhost/hello_world"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,27 @@
1
+ module Pacto
2
+ describe ResponseAdapter do
3
+ let(:response) do
4
+ double({
5
+ :code => 200,
6
+ :headers => {'foo' => ['bar', 'baz'], 'hello' => ['world']},
7
+ :body => double('body')
8
+ })
9
+ end
10
+
11
+ before do
12
+ @response_adapter = described_class.new(response)
13
+ end
14
+
15
+ it 'should have a status' do
16
+ @response_adapter.status.should == response.code
17
+ end
18
+
19
+ it 'should have a body' do
20
+ @response_adapter.body.should == response.body
21
+ end
22
+
23
+ it 'should normalize headers values according to RFC2616' do
24
+ @response_adapter.headers.should == {'foo' => 'bar,baz', 'hello' => 'world'}
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,114 @@
1
+ module Pacto
2
+ describe Response do
3
+ let(:definition) do
4
+ {
5
+ 'status' => 200,
6
+ 'headers' => {'Content-Type' => 'application/json'},
7
+ 'body' => double('definition body')
8
+ }
9
+ end
10
+
11
+ describe '#instantiate' do
12
+ let(:generated_body) { double('generated body') }
13
+
14
+ it 'should instantiate a response with a body that matches the given definition' do
15
+ JSON::Generator.should_receive(:generate).
16
+ with(definition['body']).
17
+ and_return(generated_body)
18
+
19
+ response = described_class.new(definition).instantiate
20
+ response.status.should == definition['status']
21
+ response.headers.should == definition['headers']
22
+ response.body.should == generated_body
23
+ end
24
+ end
25
+
26
+ describe '#validate' do
27
+ let(:status) { 200 }
28
+ let(:headers) { {'Content-Type' => 'application/json', 'Age' => '60'} }
29
+ let(:fake_response) do
30
+ double({
31
+ :status => status,
32
+ :headers => headers,
33
+ :body => 'body'
34
+ })
35
+ end
36
+
37
+ context 'when status, headers and body match' do
38
+ it 'should not return any errors' do
39
+ JSON::Validator.should_receive(:fully_validate).
40
+ with(definition['body'], fake_response.body).
41
+ and_return([])
42
+
43
+ response = described_class.new(definition)
44
+ response.validate(fake_response).should == []
45
+ end
46
+ end
47
+ context 'when status, headers and body match' do
48
+ it 'should not return any errors' do
49
+ JSON::Validator.should_receive(:fully_validate).
50
+ with(definition['body'], fake_response.body).
51
+ and_return([])
52
+
53
+ response = described_class.new(definition)
54
+ response.validate(fake_response).should == []
55
+ end
56
+ end
57
+
58
+ context 'when status does not match' do
59
+ let(:status) { 500 }
60
+
61
+ it 'should return a status error' do
62
+ JSON::Validator.stub(:fully_validate).and_return([])
63
+
64
+ response = described_class.new(definition)
65
+ response.validate(fake_response).should == ["Invalid status: expected #{definition['status']} but got #{status}"]
66
+ end
67
+ end
68
+
69
+ context 'when headers do not match' do
70
+ let(:headers) { {'Content-Type' => 'text/html'} }
71
+
72
+ it 'should return a header error' do
73
+ JSON::Validator.stub(:fully_validate).and_return([])
74
+
75
+ response = described_class.new(definition)
76
+ response.validate(fake_response).should == ["Invalid headers: expected #{definition['headers'].inspect} to be a subset of #{headers.inspect}"]
77
+ end
78
+ end
79
+
80
+ context 'when headers are a subset of expected headers' do
81
+ let(:headers) { {'Content-Type' => 'application/json'} }
82
+
83
+ it 'should not return any errors' do
84
+ JSON::Validator.stub(:fully_validate).and_return([])
85
+
86
+ response = described_class.new(definition)
87
+ response.validate(fake_response).should == []
88
+ end
89
+ end
90
+
91
+ context 'when headers values match but keys have different case' do
92
+ let(:headers) { {'content-type' => 'application/json'} }
93
+
94
+ it 'should not return any errors' do
95
+ JSON::Validator.stub(:fully_validate).and_return([])
96
+
97
+ response = described_class.new(definition)
98
+ response.validate(fake_response).should == []
99
+ end
100
+ end
101
+
102
+ context 'when body does not match' do
103
+ let(:errors) { [double('error1'), double('error2')] }
104
+
105
+ it 'should return a list of errors' do
106
+ JSON::Validator.stub(:fully_validate).and_return(errors)
107
+
108
+ response = described_class.new(definition)
109
+ response.validate(fake_response).should == errors
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,4 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require 'pacto'
metadata ADDED
@@ -0,0 +1,295 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pacto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ThoughtWorks & Abril
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: webmock
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: json-schema
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.4
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.4
62
+ - !ruby/object:Gem::Dependency
63
+ name: json-generator
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: hash-deep-merge
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: httparty
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: addressable
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: coveralls
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :runtime
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rake
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ - !ruby/object:Gem::Dependency
159
+ name: rspec
160
+ requirement: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ! '>='
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ - !ruby/object:Gem::Dependency
175
+ name: guard-rspec
176
+ requirement: !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ! '>='
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ type: :development
183
+ prerelease: false
184
+ version_requirements: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ - !ruby/object:Gem::Dependency
191
+ name: rb-fsevent
192
+ requirement: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ! '>='
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ none: false
202
+ requirements:
203
+ - - ! '>='
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ - !ruby/object:Gem::Dependency
207
+ name: terminal-notifier-guard
208
+ requirement: !ruby/object:Gem::Requirement
209
+ none: false
210
+ requirements:
211
+ - - ! '>='
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ type: :development
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ none: false
218
+ requirements:
219
+ - - ! '>='
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
222
+ description: Pacto is a Ruby implementation of the [Consumer-Driven Contracts](http://martinfowler.com/articles/consumerDrivenContracts.html)
223
+ pattern for evolving services
224
+ email:
225
+ - abril_vejasp_dev@thoughtworks.com
226
+ executables: []
227
+ extensions: []
228
+ extra_rdoc_files: []
229
+ files:
230
+ - .gitignore
231
+ - .rspec
232
+ - .travis.yml
233
+ - Gemfile
234
+ - Guardfile
235
+ - LICENSE.txt
236
+ - README.md
237
+ - Rakefile
238
+ - TODO.md
239
+ - lib/pacto.rb
240
+ - lib/pacto/contract.rb
241
+ - lib/pacto/extensions.rb
242
+ - lib/pacto/file_pre_processor.rb
243
+ - lib/pacto/instantiated_contract.rb
244
+ - lib/pacto/rake_task.rb
245
+ - lib/pacto/request.rb
246
+ - lib/pacto/response.rb
247
+ - lib/pacto/response_adapter.rb
248
+ - lib/pacto/version.rb
249
+ - pacto.gemspec
250
+ - spec/data/contract.json
251
+ - spec/pacto/contract_spec.rb
252
+ - spec/pacto/extensions_spec.rb
253
+ - spec/pacto/file_pre_processor_spec.rb
254
+ - spec/pacto/instantiated_contract_spec.rb
255
+ - spec/pacto/pacto_spec.rb
256
+ - spec/pacto/request_spec.rb
257
+ - spec/pacto/response_adapter_spec.rb
258
+ - spec/pacto/response_spec.rb
259
+ - spec/spec_helper.rb
260
+ homepage: https://github.com/thoughtworks/pacto
261
+ licenses:
262
+ - MIT
263
+ post_install_message:
264
+ rdoc_options: []
265
+ require_paths:
266
+ - lib
267
+ required_ruby_version: !ruby/object:Gem::Requirement
268
+ none: false
269
+ requirements:
270
+ - - ! '>='
271
+ - !ruby/object:Gem::Version
272
+ version: '0'
273
+ required_rubygems_version: !ruby/object:Gem::Requirement
274
+ none: false
275
+ requirements:
276
+ - - ! '>='
277
+ - !ruby/object:Gem::Version
278
+ version: '0'
279
+ requirements: []
280
+ rubyforge_project:
281
+ rubygems_version: 1.8.25
282
+ signing_key:
283
+ specification_version: 3
284
+ summary: Consumer-Driven Contracts implementation
285
+ test_files:
286
+ - spec/data/contract.json
287
+ - spec/pacto/contract_spec.rb
288
+ - spec/pacto/extensions_spec.rb
289
+ - spec/pacto/file_pre_processor_spec.rb
290
+ - spec/pacto/instantiated_contract_spec.rb
291
+ - spec/pacto/pacto_spec.rb
292
+ - spec/pacto/request_spec.rb
293
+ - spec/pacto/response_adapter_spec.rb
294
+ - spec/pacto/response_spec.rb
295
+ - spec/spec_helper.rb