voorhees 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ class Address
2
+ include Voorhees::Resource
3
+ end
4
+
5
+ class LatLon
6
+ include Voorhees::Resource
7
+ end
8
+
9
+ class Message
10
+ include Voorhees::Resource
11
+ end
12
+
13
+ class User
14
+ include Voorhees::Resource
15
+ end
16
+
17
+ class NotResource
18
+ end
@@ -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