openlogic-resourceful 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/History.txt +45 -0
  2. data/MIT-LICENSE +21 -0
  3. data/Manifest +46 -0
  4. data/README.markdown +92 -0
  5. data/Rakefile +91 -0
  6. data/lib/resourceful.rb +27 -0
  7. data/lib/resourceful/abstract_form_data.rb +30 -0
  8. data/lib/resourceful/authentication_manager.rb +107 -0
  9. data/lib/resourceful/cache_manager.rb +242 -0
  10. data/lib/resourceful/exceptions.rb +34 -0
  11. data/lib/resourceful/header.rb +355 -0
  12. data/lib/resourceful/http_accessor.rb +103 -0
  13. data/lib/resourceful/memcache_cache_manager.rb +75 -0
  14. data/lib/resourceful/multipart_form_data.rb +46 -0
  15. data/lib/resourceful/net_http_adapter.rb +84 -0
  16. data/lib/resourceful/promiscuous_basic_authenticator.rb +18 -0
  17. data/lib/resourceful/request.rb +235 -0
  18. data/lib/resourceful/resource.rb +179 -0
  19. data/lib/resourceful/response.rb +221 -0
  20. data/lib/resourceful/simple.rb +36 -0
  21. data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
  22. data/lib/resourceful/urlencoded_form_data.rb +19 -0
  23. data/lib/resourceful/util.rb +6 -0
  24. data/openlogic-resourceful.gemspec +51 -0
  25. data/resourceful.gemspec +51 -0
  26. data/spec/acceptance/authorization_spec.rb +16 -0
  27. data/spec/acceptance/caching_spec.rb +190 -0
  28. data/spec/acceptance/header_spec.rb +24 -0
  29. data/spec/acceptance/redirecting_spec.rb +12 -0
  30. data/spec/acceptance/resource_spec.rb +84 -0
  31. data/spec/acceptance/resourceful_spec.rb +56 -0
  32. data/spec/acceptance_shared_specs.rb +44 -0
  33. data/spec/caching_spec.rb +89 -0
  34. data/spec/old_acceptance_specs.rb +378 -0
  35. data/spec/resourceful/header_spec.rb +153 -0
  36. data/spec/resourceful/http_accessor_spec.rb +56 -0
  37. data/spec/resourceful/multipart_form_data_spec.rb +84 -0
  38. data/spec/resourceful/promiscuous_basic_authenticator_spec.rb +30 -0
  39. data/spec/resourceful/resource_spec.rb +20 -0
  40. data/spec/resourceful/response_spec.rb +51 -0
  41. data/spec/resourceful/urlencoded_form_data_spec.rb +64 -0
  42. data/spec/resourceful_spec.rb +79 -0
  43. data/spec/simple_sinatra_server.rb +74 -0
  44. data/spec/simple_sinatra_server_spec.rb +98 -0
  45. data/spec/spec.opts +3 -0
  46. data/spec/spec_helper.rb +31 -0
  47. metadata +192 -0
@@ -0,0 +1,56 @@
1
+
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+ require 'resourceful'
4
+
5
+ describe Resourceful do
6
+
7
+ describe ".get()" do
8
+ it "should be performable on a resource and return a response" do
9
+ response = Resourceful.get('http://localhost:42682/')
10
+ response.should be_kind_of(Resourceful::Response)
11
+ end
12
+ end
13
+
14
+ describe ".post()" do
15
+ it "should be performable on a resource and return a response" do
16
+ response = Resourceful.post('http://localhost:42682/')
17
+ response.should be_kind_of(Resourceful::Response)
18
+ end
19
+
20
+ it "should require Content-Type be set if a body is provided" do
21
+ lambda {
22
+ Resourceful.post('http://localhost:42682/', {}, 'body')
23
+ }.should raise_error(Resourceful::MissingContentType)
24
+ end
25
+
26
+ end
27
+
28
+ describe ".put()" do
29
+
30
+ it "should be performable on a resource and return a response" do
31
+ response = Resourceful.put('http://localhost:42682/')
32
+ response.should be_kind_of(Resourceful::Response)
33
+ end
34
+
35
+ it "should require Content-Type be set if a body is provided" do
36
+ lambda {
37
+ Resourceful.put('http://localhost:42682/', "some text", {})
38
+ }.should raise_error(Resourceful::MissingContentType)
39
+ end
40
+
41
+ it "should allow the entity-body to be nil" do
42
+ lambda {
43
+ Resourceful.put('http://localhost:42682/', nil, {})
44
+ }.should_not raise_error(ArgumentError)
45
+ end
46
+ end
47
+
48
+ describe ".delete()" do
49
+
50
+ it "should be performable on a resource and return a response" do
51
+ response = Resourceful.delete('http://localhost:42682/')
52
+ response.should be_kind_of(Resourceful::Response)
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,44 @@
1
+ describe 'redirect', :shared => true do
2
+ it 'should be followed by default on GET' do
3
+ resp = @resource.get
4
+ resp.should be_instance_of(Resourceful::Response)
5
+ resp.should be_ok
6
+ resp.header['Content-Type'].should == ['text/plain']
7
+ end
8
+
9
+ %w{PUT POST}.each do |method|
10
+ it "should not be followed by default on #{method}" do
11
+ resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
12
+ resp.should be_redirect
13
+ end
14
+
15
+ it "should redirect on #{method.to_s.upcase} if the redirection callback returns true" do
16
+ @resource.on_redirect { true }
17
+ resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
18
+ resp.should be_ok
19
+ end
20
+
21
+ it "should not follow redirect on #{method.to_s.upcase} if the redirection callback returns false" do
22
+ @resource.on_redirect { false }
23
+ resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
24
+ resp.should be_redirect
25
+ end
26
+ end
27
+
28
+ it "should not be followed by default on DELETE" do
29
+ resp = @resource.delete
30
+ resp.should be_redirect
31
+ end
32
+
33
+ it "should redirect on DELETE if vthe redirection callback returns true" do
34
+ @resource.on_redirect { true }
35
+ resp = @resource.delete
36
+ resp.should be_ok
37
+ end
38
+
39
+ it "should not redirect on DELETE if the redirection callback returns false" do
40
+ resp = @resource.delete
41
+ resp.should be_redirect
42
+ end
43
+ end
44
+
@@ -0,0 +1,89 @@
1
+
2
+ require 'rubygems'
3
+ require 'fakeweb'
4
+
5
+ $LOAD_PATH << File.join(File.dirname(__FILE__), "..", "lib")
6
+ require 'resourceful'
7
+
8
+ describe "Caching" do
9
+
10
+ before do
11
+ FakeWeb.allow_net_connect = false
12
+ FakeWeb.clean_registry
13
+
14
+ @http = Resourceful::HttpAccessor.new(:cache_manager => Resourceful::InMemoryCacheManager.new)
15
+ if ENV['SPEC_LOGGING']
16
+ @http.logger = Resourceful::StdOutLogger.new
17
+ end
18
+ end
19
+
20
+ describe "should cache" do
21
+
22
+ before do
23
+ FakeWeb.register_uri(:get, "http://example.com/cache",
24
+ [{:body => "Original response", :cache_control => "private,max-age=15"},
25
+ {:body => "Overrode cached response"}]
26
+ )
27
+
28
+ @resource = @http.resource("http://example.com/cache")
29
+ end
30
+
31
+ it "should cache the response" do
32
+ resp = @resource.get
33
+ resp.body.should == "Original response"
34
+
35
+ resp = @resource.get
36
+ resp.body.should == "Original response"
37
+ end
38
+
39
+ end
40
+
41
+ describe "updating headers" do
42
+ before do
43
+ FakeWeb.register_uri(:get, "http://example.com/override",
44
+ [{:body => "Original response", :cache_control => "private,max-age=0", :x_updateme => "foo"},
45
+ {:body => "Overrode cached response", :status => 304, :x_updateme => "bar"} ]
46
+ )
47
+
48
+ @resource = @http.resource("http://example.com/override")
49
+ end
50
+
51
+ it "should update headers from the 304" do
52
+ resp = @resource.get
53
+ resp.headers['X-Updateme'].should == ["foo"]
54
+
55
+ resp = @resource.get
56
+ resp.headers['X-Updateme'].should == ["bar"]
57
+ resp.headers['Cache-Control'].should == ["private", "max-age=0"]
58
+ end
59
+
60
+ end
61
+
62
+ describe "updating expiration" do
63
+ before do
64
+ FakeWeb.register_uri(:get, "http://example.com/timeout",
65
+ [{:body => "Original response", :cache_control => "private,max-age=1"},
66
+ {:body => "cached response", :cache_control => "private,max-age=1"}]
67
+ )
68
+
69
+ @resource = @http.resource("http://example.com/timeout")
70
+ end
71
+
72
+ it "should refresh the expiration timer" do
73
+ resp = @resource.get
74
+ resp.should_not be_stale
75
+
76
+ sleep 2
77
+
78
+ resp.should be_stale
79
+
80
+ resp = @resource.get
81
+ resp.should_not be_stale
82
+
83
+ resp = @resource.get
84
+ end
85
+
86
+ end
87
+
88
+
89
+ end
@@ -0,0 +1,378 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + 'spec_helper'
3
+ require 'resourceful'
4
+
5
+ require Pathname(__FILE__).dirname + 'acceptance_shared_specs'
6
+
7
+
8
+ describe Resourceful do
9
+
10
+ describe 'working with a resource' do
11
+ before do
12
+ @accessor = Resourceful::HttpAccessor.new
13
+ end
14
+
15
+ it 'should set additional headers on the #get' do
16
+ resource = @accessor.resource('http://localhost:3000/echo_header')
17
+ resp = resource.get(:foo => :bar)
18
+ resp.should be_instance_of(Resourceful::Response)
19
+ resp.code.should == 200
20
+ resp.body.should =~ /"HTTP_FOO"=>"bar"/
21
+ end
22
+
23
+ it 'should take an optional default header for reads' do
24
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
25
+ resp = resource.get
26
+ resp.should be_instance_of(Resourceful::Response)
27
+ resp.code.should == 200
28
+ resp.body.should =~ /"HTTP_FOO"=>"bar"/
29
+ end
30
+
31
+ it 'should take an optional default header for writes' do
32
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
33
+ resp = resource.post("data", :content_type => 'text/plain')
34
+ resp.should be_instance_of(Resourceful::Response)
35
+ resp.code.should == 200
36
+ resp.body.should =~ /"HTTP_FOO"=>"bar"/
37
+ end
38
+
39
+ it 'should override the default header with any set on a read action' do
40
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
41
+ resp = resource.get(:foo => :baz)
42
+ resp.should be_instance_of(Resourceful::Response)
43
+ resp.code.should == 200
44
+ resp.body.should =~ /"HTTP_FOO"=>"baz"/
45
+ end
46
+
47
+ it 'should override the default header with any set on a write action' do
48
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
49
+ resp = resource.post("data", :foo => :baz, :content_type => 'text/plain')
50
+ resp.should be_instance_of(Resourceful::Response)
51
+ resp.code.should == 200
52
+ resp.body.should =~ /"HTTP_FOO"=>"baz"/
53
+ end
54
+
55
+ describe 'redirecting' do
56
+
57
+ describe 'registering callback' do
58
+ before do
59
+ @resource = @accessor.resource('http://localhost:3000/redirect/301?http://localhost:3000/get')
60
+ end
61
+
62
+ it 'should allow a callback to be registered' do
63
+ @resource.should respond_to(:on_redirect)
64
+ end
65
+
66
+ it 'should perform a redirect if the callback is true' do
67
+ @resource.on_redirect { true }
68
+ @resource.get.should be_successful
69
+ end
70
+
71
+ it 'should not perform the redirect if the callback returns false' do
72
+ @resource.on_redirect { false }
73
+ @resource.get
74
+ @resource.get.should be_redirect
75
+ end
76
+ end
77
+
78
+ describe 'permanent redirect' do
79
+ before do
80
+ @redirect_code = 301
81
+ @resource = @accessor.resource('http://localhost:3000/redirect/301?http://localhost:3000/get')
82
+ end
83
+
84
+ it_should_behave_like 'redirect'
85
+
86
+ it 'should change the effective uri of the resource' do
87
+ @resource.get
88
+ @resource.effective_uri.should == 'http://localhost:3000/get'
89
+ end
90
+ end
91
+
92
+ describe 'temporary redirect' do
93
+ before do
94
+ @redirect_code = 302
95
+ @resource = @accessor.resource('http://localhost:3000/redirect/302?http://localhost:3000/get')
96
+ end
97
+
98
+ it_should_behave_like 'redirect'
99
+
100
+ it 'should not change the effective uri of the resource' do
101
+ @resource.get
102
+ @resource.effective_uri.should == 'http://localhost:3000/redirect/302?http://localhost:3000/get'
103
+ end
104
+
105
+ describe '303 See Other' do
106
+ before do
107
+ @redirect_code = 303
108
+ @resource = @accessor.resource('http://localhost:3000/redirect/303?http://localhost:3000/method')
109
+ @resource.on_redirect { true }
110
+ end
111
+
112
+ it 'should GET the redirected resource, regardless of the initial method' do
113
+ resp = @resource.delete
114
+ resp.code.should == 200
115
+ resp.body.should == 'GET'
116
+ end
117
+ end
118
+ end
119
+
120
+ end
121
+
122
+ describe 'caching' do
123
+ before do
124
+ @accessor = Resourceful::HttpAccessor.new(:cache_manager => Resourceful::InMemoryCacheManager.new)
125
+ end
126
+
127
+ it 'should use the cached response' do
128
+ resource = @accessor.resource('http://localhost:3000/get')
129
+ resp = resource.get
130
+ resp.authoritative?.should be_true
131
+
132
+ resp2 = resource.get
133
+ resp2.authoritative?.should be_false
134
+
135
+ resp2.should == resp
136
+ end
137
+
138
+ it 'should not store the representation if the server says not to' do
139
+ resource = @accessor.resource('http://localhost:3000/header?{Vary:%20*}')
140
+ resp = resource.get
141
+ resp.authoritative?.should be_true
142
+ resp.should_not be_cachable
143
+
144
+ resp2 = resource.get
145
+ resp2.should_not == resp
146
+ end
147
+
148
+ it 'should use the cached version of the representation if it has not expired' do
149
+ in_an_hour = (Time.now + (60*60)).httpdate
150
+ uri = URI.escape("http://localhost:3000/header?{Expire: \"#{in_an_hour}\"}")
151
+
152
+ resource = @accessor.resource(uri)
153
+ resp = resource.get
154
+ resp.should be_authoritative
155
+
156
+ resp2 = resource.get
157
+ resp2.should_not be_authoritative
158
+ resp2.should == resp
159
+ end
160
+
161
+ it 'should revalidate the cached response if it has expired' do
162
+ an_hour_ago = (Time.now - (60*60)).httpdate
163
+ uri = URI.escape("http://localhost:3000/header?{Expire: \"#{an_hour_ago}\"}")
164
+
165
+ resource = @accessor.resource(uri)
166
+ resp = resource.get
167
+ resp.should be_authoritative
168
+ resp.should be_expired
169
+
170
+ resp2 = resource.get
171
+ resp2.should be_authoritative
172
+ end
173
+
174
+ it 'should provide the cached version if the server responds with a 304 not modified' do
175
+ in_an_hour = (Time.now + (60*60)).httpdate
176
+ uri = URI.escape("http://localhost:3000/modified?#{in_an_hour}")
177
+
178
+ resource = @accessor.resource(uri)
179
+ resp = resource.get
180
+ resp.should be_authoritative
181
+
182
+ resp2 = resource.get(:cache_control => "max-age=0")
183
+ resp2.should be_authoritative
184
+ resp2.should == resp
185
+ end
186
+
187
+ it 'should not use a cached document for a resource that has been posted to' do
188
+ resource = @accessor.resource('http://localhost:3000/get')
189
+ resp = resource.get
190
+ resp.authoritative?.should be_true
191
+
192
+ resource.post("foo", :content_type => 'text/plain')
193
+
194
+ resp2 = resource.get
195
+ resp2.should_not == resp
196
+ end
197
+
198
+ describe 'Server provided Cache-Control' do
199
+
200
+ it 'should cache anything with "Cache-Control: public"' do
201
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: public}')
202
+ resource = @accessor.resource(uri)
203
+ resp = resource.get
204
+ resp.authoritative?.should be_true
205
+
206
+ resp2 = resource.get
207
+ resp2.authoritative?.should be_false
208
+
209
+ resp2.should == resp
210
+ end
211
+
212
+ it 'should cache anything with "Cache-Control: private"' do
213
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: private}')
214
+ resource = @accessor.resource(uri)
215
+ resp = resource.get
216
+ resp.should be_authoritative
217
+
218
+ resp2 = resource.get
219
+ resp2.should_not be_authoritative
220
+
221
+ resp2.should == resp
222
+ end
223
+
224
+ it 'should cache but revalidate anything with "Cache-Control: no-cache"' do
225
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: no-cache}')
226
+ resource = @accessor.resource(uri)
227
+ resp = resource.get
228
+ resp.authoritative?.should be_true
229
+
230
+ resp2 = resource.get
231
+ resp2.authoritative?.should be_true
232
+ end
233
+
234
+ it 'should revalidate anything that is older than "Cache-Control: max-age" value' do
235
+
236
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: max-age=1, Date: "Mon, 04 Aug 2008 18:00:00 GMT"}')
237
+ resource = @accessor.resource(uri)
238
+ resp = resource.get
239
+ resp.authoritative?.should be_true
240
+
241
+ resp.expired?.should be_true
242
+
243
+ resp2 = resource.get
244
+ resp2.authoritative?.should be_true
245
+ end
246
+
247
+ it 'should cache but revalidate anything with "Cache-Control: must-revalidate"' do
248
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: must-revalidate}')
249
+ resource = @accessor.resource(uri)
250
+ resp = resource.get
251
+ resp.authoritative?.should be_true
252
+
253
+ resp2 = resource.get
254
+ resp2.authoritative?.should be_true
255
+ end
256
+
257
+ it 'should not cache anything with "Cache-Control: no-store"' do
258
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: no-store}')
259
+ resource = @accessor.resource(uri)
260
+ resp = resource.get
261
+ resp.authoritative?.should be_true
262
+
263
+ resp2 = resource.get
264
+ resp2.authoritative?.should be_true
265
+ end
266
+
267
+
268
+ end
269
+
270
+ describe 'Client provided Cache-Control' do
271
+
272
+ it 'should revalidate anything that is older than "Cache-Control: max-age" value' do
273
+ a_minute_ago = (Time.now - 60).httpdate
274
+ uri = URI.escape("http://localhost:3000/header?{Cache-Control: max-age=120, Date: \"#{a_minute_ago}\"}")
275
+ resource = @accessor.resource(uri)
276
+ resp = resource.get
277
+ resp.authoritative?.should be_true
278
+
279
+ resp.expired?.should be_false
280
+
281
+ resp2 = resource.get({:cache_control => "max-age=1"})
282
+ resp2.authoritative?.should be_true
283
+ end
284
+
285
+ end
286
+
287
+ end
288
+
289
+ describe 'authorization' do
290
+ before do
291
+ @uri = 'http://localhost:3000/auth?basic'
292
+ end
293
+
294
+ it 'should automatically add authorization info to the request if its available'
295
+
296
+ it 'should not authenticate if no auth handlers are set' do
297
+ resource = @accessor.resource(@uri)
298
+ lambda {
299
+ resource.get
300
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
301
+ end
302
+
303
+ it 'should not authenticate if no valid auth handlers are available' do
304
+ basic_handler = Resourceful::BasicAuthenticator.new('Not Test Auth', 'admin', 'secret')
305
+ @accessor.auth_manager.add_auth_handler(basic_handler)
306
+ resource = @accessor.resource(@uri)
307
+ lambda {
308
+ resource.get
309
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
310
+ end
311
+
312
+ describe 'basic' do
313
+ before do
314
+ @uri = 'http://localhost:3000/auth?basic'
315
+ end
316
+
317
+ it 'should be able to authenticate basic auth' do
318
+ basic_handler = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'secret')
319
+ @accessor.auth_manager.add_auth_handler(basic_handler)
320
+ resource = @accessor.resource(@uri)
321
+ resp = resource.get
322
+
323
+ resp.code.should == 200
324
+ end
325
+
326
+ it 'should not keep trying to authenticate with incorrect credentials' do
327
+ basic_handler = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'well-known')
328
+ @accessor.auth_manager.add_auth_handler(basic_handler)
329
+ resource = @accessor.resource(@uri)
330
+
331
+ lambda {
332
+ resource.get
333
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
334
+ end
335
+
336
+ end
337
+
338
+ describe 'digest' do
339
+ before do
340
+ @uri = 'http://localhost:3000/auth/digest'
341
+ end
342
+
343
+ it 'should be able to authenticate digest auth' do
344
+ pending
345
+ digest_handler = Resourceful::DigestAuthenticator.new('Test Auth', 'admin', 'secret')
346
+ @accessor.auth_manager.add_auth_handler(digest_handler)
347
+ resource = @accessor.resource(@uri)
348
+ resp = resource.get
349
+
350
+ resp.code.should == 200
351
+ end
352
+
353
+ end
354
+
355
+ end
356
+
357
+ describe 'error checking' do
358
+
359
+ it 'should raise InvalidResponse when response code is invalid'
360
+
361
+ describe 'client errors' do
362
+
363
+ it 'should raise when there is one'
364
+
365
+ end
366
+
367
+ describe 'server errors' do
368
+
369
+ it 'should raise when there is one'
370
+
371
+ end
372
+
373
+ end
374
+
375
+ end
376
+
377
+ end
378
+