openlogic-resourceful 1.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.
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
+