voorhees 0.2.0
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.
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.markdown +240 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/examples/twitter.rb +85 -0
- data/lib/voorhees.rb +6 -0
- data/lib/voorhees/config.rb +87 -0
- data/lib/voorhees/exceptions.rb +9 -0
- data/lib/voorhees/logging.rb +5 -0
- data/lib/voorhees/request.rb +146 -0
- data/lib/voorhees/resource.rb +141 -0
- data/lib/voorhees/response.rb +36 -0
- data/spec/config_spec.rb +144 -0
- data/spec/fixtures/resources.rb +18 -0
- data/spec/fixtures/user.json +32 -0
- data/spec/fixtures/users.json +1 -0
- data/spec/request_spec.rb +455 -0
- data/spec/resource_spec.rb +335 -0
- data/spec/response_spec.rb +93 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/voorhees_spec.rb +1 -0
- data/voorhees.gemspec +65 -0
- metadata +85 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"email":"bt@example.com",
|
3
|
+
"username":"btables",
|
4
|
+
"name":"Bobby Tables",
|
5
|
+
"id":1,
|
6
|
+
"address":{
|
7
|
+
"street":"24 Monkey Close",
|
8
|
+
"city":"Somesville",
|
9
|
+
"country":"Somewhere",
|
10
|
+
"coords":{
|
11
|
+
"lat":52.9876,
|
12
|
+
"lon":12.3456
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"camelCase":"camel",
|
16
|
+
"pet":{
|
17
|
+
"kind":"dog",
|
18
|
+
"name":"spot"
|
19
|
+
},
|
20
|
+
"messages":[
|
21
|
+
{
|
22
|
+
"subject":"QWERTYUIOP",
|
23
|
+
"body":"ASDFHJKOIUFC G F IUH HGJBNM",
|
24
|
+
"id":1
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"subject":"asdoifj sajf askjdfhuaerf kdshf dsaf asdf a skf dksf k.",
|
28
|
+
"body":"adsjfi asdif jaosdj fojasdf jasdjhf hsadkf ahjsdkf jasdhf jasdk;sadfjahsdf ;adfs",
|
29
|
+
"id":2
|
30
|
+
}
|
31
|
+
]
|
32
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
[{"email":"bt@example.com","username":"btables","name":"Bobby Tables","id":1,"messages":[{"subject":"QWERTYUIOP","body":"ASDFHJKOIUFC G F IUH HGJBNM","id":1},{"subject":"asdoifj sajf askjdfhuaerf kdshf dsaf asdf a skf dksf k.","body":"adsjfi asdif jaosdj fojasdf jasdjhf hsadkf ahjsdkf jasdhf jasdk;sadfjahsdf ;adfs","id":2}]},{"email":"rm@example.com","username":"rmoore","name":"Roger Moore","id":2,"messages":[{"subject":"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.","body":"Lorem ipsum dolor sit amet, consectetur adipisicing elit","id":4},{"subject":"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.","body":"Excepteur sint o cat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.","id":5}]}]
|
@@ -0,0 +1,455 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Voorhees::Request do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@caller_class = User
|
7
|
+
@request = Voorhees::Request.new(@caller_class)
|
8
|
+
|
9
|
+
# disable the logger
|
10
|
+
Voorhees::Config.reset
|
11
|
+
Voorhees::Config.logger = mock(:logger, :null_object => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "global defaults" do
|
15
|
+
|
16
|
+
before :each do
|
17
|
+
Voorhees::Config.setup do |c|
|
18
|
+
c.timeout = 1
|
19
|
+
c.retries = 50
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should override the global defaults if overridden" do
|
24
|
+
@request.timeout = 100
|
25
|
+
@request.timeout.should == 100
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should default to the global defaults if not overridden" do
|
29
|
+
@request.retries.should == 50
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "defaults" do
|
35
|
+
|
36
|
+
it "should be included in the parameters" do
|
37
|
+
@request.defaults = {:all => true}
|
38
|
+
@request.parameters = {:order => 'surname'}
|
39
|
+
@request.parameters.should == {:all => true, :order => 'surname'}
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should be overridden by matching parameters" do
|
43
|
+
@request.defaults = {:all => true, :order => 'surname'}
|
44
|
+
@request.parameters = {:all => false}
|
45
|
+
@request.parameters.should == {:all => false, :order => 'surname'}
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
describe "uri" do
|
52
|
+
|
53
|
+
before :each do
|
54
|
+
@base = "http://example.com"
|
55
|
+
@path = "/some/path"
|
56
|
+
Voorhees::Config.base_uri = @base
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should prepend the base_uri if it's given relative path" do
|
60
|
+
@request.path = @path
|
61
|
+
@request.uri.to_s.should == "#{@base}#{@path}"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should not prepend the base_uri if it's given a full URI" do
|
65
|
+
@request.path = "#{@base}#{@path}"
|
66
|
+
@request.uri.to_s.should == "#{@base}#{@path}"
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "with parameters" do
|
70
|
+
|
71
|
+
before :each do
|
72
|
+
@request.parameters = {:monkeys => 2}
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "with a Net::HTTP::POST request" do
|
76
|
+
|
77
|
+
before :each do
|
78
|
+
Voorhees::Config[:http_method] = Net::HTTP::Post
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should not append the query string with the parameters" do
|
82
|
+
@request.path = "#{@base}#{@path}"
|
83
|
+
@request.uri.to_s.should == "#{@base}#{@path}"
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
[Net::HTTP::Get, Net::HTTP::Put, Net::HTTP::Delete].each do |type|
|
89
|
+
describe "with #{type} requests" do
|
90
|
+
|
91
|
+
before :each do
|
92
|
+
Voorhees::Config[:http_method] = type
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should set the query string with the parameters" do
|
96
|
+
@request.path = "#{@base}#{@path}"
|
97
|
+
@request.uri.to_s.should == "#{@base}#{@path}?monkeys=2"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "validation" do
|
106
|
+
|
107
|
+
before :each do
|
108
|
+
@request.required = [:id, :login]
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should raise Voorhees::ParameterMissingError if the params do not contain a required item" do
|
112
|
+
@request.parameters = {:id => 1}
|
113
|
+
|
114
|
+
lambda{
|
115
|
+
@request.send(:validate)
|
116
|
+
}.should raise_error(Voorhees::ParameterMissingError)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should not raise Voorhees::ParameterMissingError if the params contain all required items" do
|
120
|
+
@request.parameters = {:id => 1, :login => 'test'}
|
121
|
+
|
122
|
+
lambda{
|
123
|
+
@request.send(:validate)
|
124
|
+
}.should_not raise_error(Voorhees::ParameterMissingError)
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "perform" do
|
130
|
+
|
131
|
+
before :each do
|
132
|
+
@host = "example.com"
|
133
|
+
@port = 8080
|
134
|
+
@path = "/endpoint"
|
135
|
+
@params = {:bananas => 5}
|
136
|
+
@hierarchy = {:address => Address}
|
137
|
+
|
138
|
+
@request.path = "http://#{@host}:#{@port}#{@path}"
|
139
|
+
@request.timeout = 10
|
140
|
+
@request.retries = 0
|
141
|
+
@request.hierarchy = @hierarchy
|
142
|
+
@request.parameters = @params
|
143
|
+
|
144
|
+
@mock_post = mock(:post, :null_object => true)
|
145
|
+
Net::HTTP::Post.stub!(:new).and_return(@mock_post)
|
146
|
+
|
147
|
+
@mock_get = mock(:get, :null_object => true)
|
148
|
+
Net::HTTP::Get.stub!(:new).and_return(@mock_get)
|
149
|
+
|
150
|
+
@mock_put = mock(:put, :null_object => true)
|
151
|
+
Net::HTTP::Put.stub!(:put).and_return(@mock_put)
|
152
|
+
|
153
|
+
@mock_delete = mock(:delete, :null_object => true)
|
154
|
+
Net::HTTP::Delete.stub!(:delete).and_return(@mock_delete)
|
155
|
+
|
156
|
+
Voorhees::Config[:http_method] = Net::HTTP::Get
|
157
|
+
|
158
|
+
body = '{"something":"result"}'
|
159
|
+
@http_response = Net::HTTPResponse::CODE_TO_OBJ["200"].new("1.1", 200, body)
|
160
|
+
@http_response.stub!(:body).and_return(body)
|
161
|
+
@json_response = body
|
162
|
+
|
163
|
+
@mock_http = MockNetHttp.new
|
164
|
+
@connection = @mock_http.connection
|
165
|
+
@connection.stub!(:request).and_return(@http_response)
|
166
|
+
Net::HTTP.stub!(:new).and_return(@mock_http)
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
def perform_catching_errors
|
171
|
+
@request.perform
|
172
|
+
rescue
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "with GET request" do
|
176
|
+
|
177
|
+
before :each do
|
178
|
+
Voorhees::Config[:http_method] = Net::HTTP::Get
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should perform a HTTP::Get request to the correct path" do
|
182
|
+
Net::HTTP::Get.should_receive(:new).with(@path).and_return(@mock_get)
|
183
|
+
@request.perform
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "with POST request" do
|
189
|
+
|
190
|
+
before :each do
|
191
|
+
Voorhees::Config[:http_method] = Net::HTTP::Post
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "with post_json set" do
|
195
|
+
|
196
|
+
before :each do
|
197
|
+
@param_name = "json_data"
|
198
|
+
Voorhees::Config[:post_json] = true
|
199
|
+
Voorhees::Config[:post_json_parameter] = @param_name
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should set the form_data to a hash with JSON parameters as :post_json_parameter" do
|
203
|
+
@mock_post.should_receive(:form_data=).with({ @param_name => @params.to_json })
|
204
|
+
@request.perform
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "without post_json set" do
|
210
|
+
|
211
|
+
before :each do
|
212
|
+
Voorhees::Config[:post_json] = false
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should set the form_data to the parameters" do
|
216
|
+
@mock_post.should_receive(:form_data=).with(@params)
|
217
|
+
@request.perform
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should perform a HTTP::Post request to the correct path" do
|
222
|
+
Net::HTTP::Post.should_receive(:new).with(@path).and_return(@mock_post)
|
223
|
+
@request.perform
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
describe "with DELETE request" do
|
229
|
+
|
230
|
+
before :each do
|
231
|
+
Voorhees::Config[:http_method] = Net::HTTP::Delete
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should perform a HTTP::Get request to the correct path" do
|
235
|
+
Net::HTTP::Delete.should_receive(:new).with(@path).and_return(@mock_delete)
|
236
|
+
@request.perform
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
describe "with PUT request" do
|
242
|
+
|
243
|
+
before :each do
|
244
|
+
Voorhees::Config[:http_method] = Net::HTTP::Put
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should perform a HTTP::Get request to the correct path" do
|
248
|
+
Net::HTTP::Put.should_receive(:new).with(@path).and_return(@mock_put)
|
249
|
+
@request.perform
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should create a Net::HTTP object with the correct host and port" do
|
255
|
+
Net::HTTP.should_receive(:new).with(@host, @port).and_return(@mock_http)
|
256
|
+
@request.perform
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should set Net::HTTP#open_timeout" do
|
260
|
+
@mock_http.should_receive(:open_timeout=).with(@request.timeout)
|
261
|
+
@request.perform
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should set Net::HTTP#read_timeout" do
|
265
|
+
@mock_http.should_receive(:read_timeout=).with(@request.timeout)
|
266
|
+
@request.perform
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should send one request to Net::HTTP#start" do
|
270
|
+
@connection.should_receive(:request).once.with(@mock_get)
|
271
|
+
@request.perform
|
272
|
+
end
|
273
|
+
|
274
|
+
it "should return the response from the service as a Voorhees::Response" do
|
275
|
+
@connection.stub!(:request).and_return(@http_response)
|
276
|
+
@request.perform.should be_a(Voorhees::Response)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should pass the json body, caller class and hierarcy to the response" do
|
280
|
+
@connection.stub!(:request).and_return(@http_response)
|
281
|
+
|
282
|
+
Voorhees::Response.should_receive(:new).with(@json_response, @caller_class, @hierarchy)
|
283
|
+
@request.perform
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "with TimeoutError" do
|
287
|
+
|
288
|
+
it "should raise a Voorhees::TimeoutError when Timeout::Error is from connection" do
|
289
|
+
@connection.stub!(:request).and_raise(Timeout::Error.new(nil))
|
290
|
+
|
291
|
+
lambda{
|
292
|
+
@request.perform
|
293
|
+
}.should raise_error(Voorhees::TimeoutError)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should raise a Voorhees::TimeoutError when Timeout::Error is from VoorheesTimer" do
|
297
|
+
VoorheesTimer.stub!(:timeout).and_raise(Timeout::Error.new(nil))
|
298
|
+
|
299
|
+
lambda{
|
300
|
+
@request.perform
|
301
|
+
}.should raise_error(Voorhees::TimeoutError)
|
302
|
+
end
|
303
|
+
|
304
|
+
describe "with retries" do
|
305
|
+
|
306
|
+
before :each do
|
307
|
+
@request.retries = 2
|
308
|
+
end
|
309
|
+
|
310
|
+
describe "with subsequent success" do
|
311
|
+
|
312
|
+
it "should post the request 2 times" do
|
313
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered.and_raise(Timeout::Error.new(nil))
|
314
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered
|
315
|
+
@request.perform
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should return the response from the service" do
|
319
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered.and_raise(Timeout::Error.new(nil))
|
320
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered.and_return(@http_response)
|
321
|
+
@request.perform.body.should == @json_response
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
describe "with subseqent failure" do
|
327
|
+
|
328
|
+
before :each do
|
329
|
+
@connection.stub!(:request).and_raise(Timeout::Error.new(nil))
|
330
|
+
end
|
331
|
+
|
332
|
+
it "should post the request 3 times (original + 2 retries)" do
|
333
|
+
@connection.should_receive(:request).with(@mock_get).exactly(3).times.and_raise(Timeout::Error.new(nil))
|
334
|
+
perform_catching_errors
|
335
|
+
end
|
336
|
+
|
337
|
+
it "should raise an Voorhees::TimeoutError exception" do
|
338
|
+
lambda {
|
339
|
+
@request.perform
|
340
|
+
}.should raise_error(Voorhees::TimeoutError)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
|
347
|
+
describe "with Net::HTTPNotFound" do
|
348
|
+
|
349
|
+
it "should raise a Voorhees::NotFoundError" do
|
350
|
+
@connection.stub!(:request).and_return(Net::HTTPNotFound.new(404, 1.1, "Not Found"))
|
351
|
+
|
352
|
+
lambda{
|
353
|
+
@request.perform
|
354
|
+
}.should raise_error(Voorhees::NotFoundError)
|
355
|
+
end
|
356
|
+
|
357
|
+
describe "with retries" do
|
358
|
+
|
359
|
+
before :each do
|
360
|
+
@request.retries = 2
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should not retry" do
|
364
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.and_return(Net::HTTPNotFound.new(404, 1.1, "Not Found"))
|
365
|
+
perform_catching_errors
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
|
371
|
+
describe "with Errno::ECONNREFUSED" do
|
372
|
+
|
373
|
+
it "should raise a Voorhees::UnavailableError" do
|
374
|
+
@connection.stub!(:request).and_raise(Errno::ECONNREFUSED)
|
375
|
+
|
376
|
+
lambda{
|
377
|
+
@request.perform
|
378
|
+
}.should raise_error(Voorhees::UnavailableError)
|
379
|
+
end
|
380
|
+
|
381
|
+
it "should not sleep" do
|
382
|
+
@connection.stub!(:request).and_raise(Errno::ECONNREFUSED)
|
383
|
+
@request.should_not_receive(:sleep)
|
384
|
+
perform_catching_errors
|
385
|
+
end
|
386
|
+
|
387
|
+
describe "with retries" do
|
388
|
+
|
389
|
+
before :each do
|
390
|
+
@request.retries = 2
|
391
|
+
@request.stub!(:sleep)
|
392
|
+
end
|
393
|
+
|
394
|
+
it "should sleep for 1 second before each timeout" do
|
395
|
+
@connection.stub!(:request).and_raise(Errno::ECONNREFUSED)
|
396
|
+
@request.should_receive(:sleep).with(1)
|
397
|
+
perform_catching_errors
|
398
|
+
end
|
399
|
+
|
400
|
+
describe "with subsequent success" do
|
401
|
+
|
402
|
+
it "should post the request 2 times" do
|
403
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered.and_raise(Errno::ECONNREFUSED)
|
404
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered
|
405
|
+
@request.perform
|
406
|
+
end
|
407
|
+
|
408
|
+
it "should return the response from the service" do
|
409
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered.and_raise(Errno::ECONNREFUSED)
|
410
|
+
@connection.should_receive(:request).with(@mock_get).exactly(1).times.ordered.and_return(@http_response)
|
411
|
+
@request.perform.body.should == @json_response
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
describe "with subsequent failure" do
|
416
|
+
|
417
|
+
before :each do
|
418
|
+
@connection.stub!(:request).and_raise(Errno::ECONNREFUSED)
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should post the request 3 times (original + 2 retries)" do
|
422
|
+
@connection.should_receive(:request).with(@mock_get).exactly(3).times.and_raise(Errno::ECONNREFUSED)
|
423
|
+
perform_catching_errors
|
424
|
+
end
|
425
|
+
|
426
|
+
it "should raise an Voorhees::UnavailableError exception" do
|
427
|
+
lambda {
|
428
|
+
@request.perform
|
429
|
+
}.should raise_error(Voorhees::UnavailableError)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
|
438
|
+
|
439
|
+
class MockNetHttp
|
440
|
+
|
441
|
+
attr_accessor :connection
|
442
|
+
|
443
|
+
def initialize(*args)
|
444
|
+
@connection = mock(:connection)
|
445
|
+
end
|
446
|
+
|
447
|
+
def start
|
448
|
+
yield @connection
|
449
|
+
end
|
450
|
+
|
451
|
+
def method_missing(*args)
|
452
|
+
return self
|
453
|
+
end
|
454
|
+
|
455
|
+
end
|