resourceful 0.3.1 → 0.5.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 (38) hide show
  1. data/Manifest +24 -28
  2. data/Rakefile +44 -14
  3. data/lib/resourceful.rb +11 -21
  4. data/lib/resourceful/authentication_manager.rb +3 -2
  5. data/lib/resourceful/cache_manager.rb +58 -1
  6. data/lib/resourceful/exceptions.rb +34 -0
  7. data/lib/resourceful/header.rb +95 -0
  8. data/lib/resourceful/http_accessor.rb +0 -2
  9. data/lib/resourceful/memcache_cache_manager.rb +3 -13
  10. data/lib/resourceful/net_http_adapter.rb +15 -5
  11. data/lib/resourceful/request.rb +180 -18
  12. data/lib/resourceful/resource.rb +38 -141
  13. data/lib/resourceful/response.rb +142 -95
  14. data/resourceful.gemspec +9 -7
  15. data/spec/acceptance/authorization_spec.rb +16 -0
  16. data/spec/acceptance/caching_spec.rb +192 -0
  17. data/spec/acceptance/header_spec.rb +24 -0
  18. data/spec/acceptance/redirecting_spec.rb +12 -0
  19. data/spec/acceptance/resource_spec.rb +84 -0
  20. data/spec/acceptance_shared_specs.rb +12 -17
  21. data/spec/{acceptance_spec.rb → old_acceptance_specs.rb} +27 -57
  22. data/spec/simple_sinatra_server.rb +74 -0
  23. data/spec/simple_sinatra_server_spec.rb +98 -0
  24. data/spec/spec_helper.rb +21 -7
  25. metadata +50 -42
  26. data/spec/resourceful/authentication_manager_spec.rb +0 -249
  27. data/spec/resourceful/cache_manager_spec.rb +0 -223
  28. data/spec/resourceful/header_spec.rb +0 -38
  29. data/spec/resourceful/http_accessor_spec.rb +0 -164
  30. data/spec/resourceful/memcache_cache_manager_spec.rb +0 -111
  31. data/spec/resourceful/net_http_adapter_spec.rb +0 -96
  32. data/spec/resourceful/options_interpreter_spec.rb +0 -102
  33. data/spec/resourceful/request_spec.rb +0 -186
  34. data/spec/resourceful/resource_spec.rb +0 -600
  35. data/spec/resourceful/response_spec.rb +0 -238
  36. data/spec/resourceful/stubbed_resource_proxy_spec.rb +0 -58
  37. data/spec/simple_http_server_shared_spec.rb +0 -162
  38. data/spec/simple_http_server_shared_spec_spec.rb +0 -212
@@ -0,0 +1,24 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'resourceful'
3
+
4
+ describe Resourceful do
5
+
6
+ describe 'setting headers' do
7
+ before do
8
+ @http = Resourceful::HttpAccessor.new
9
+ @resource = @http.resource("http://localhost:3000/header")
10
+ end
11
+
12
+ it 'should handle "Content-Type"' do
13
+ resp = @resource.post("asdf", :content_type => 'foo/bar')
14
+
15
+ header = YAML.load(resp.body)
16
+
17
+ header.should have_key('CONTENT_TYPE')
18
+ header['CONTENT_TYPE'].should == 'foo/bar'
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+
@@ -0,0 +1,12 @@
1
+
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+ require 'resourceful'
4
+
5
+ describe Resourceful do
6
+
7
+ describe "redirects" do
8
+
9
+ end
10
+
11
+ end
12
+
@@ -0,0 +1,84 @@
1
+
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+ require 'resourceful'
4
+
5
+ describe Resourceful do
6
+
7
+ describe "working with a resource" do
8
+ before do
9
+ @http = Resourceful::HttpAccessor.new
10
+ @resource = @http.resource('http://localhost:3000/')
11
+ end
12
+
13
+ it 'should make the original uri available' do
14
+ @resource.effective_uri.should == 'http://localhost:3000/'
15
+ @resource.uri.should == 'http://localhost:3000/'
16
+ end
17
+
18
+ it 'should set the user agent string on the default header' do
19
+ @resource.default_header.should have_key('User-Agent')
20
+ @resource.default_header['User-Agent'].should == Resourceful::RESOURCEFUL_USER_AGENT_TOKEN
21
+ end
22
+
23
+ describe "GET" do
24
+
25
+ it "should be performable on a resource and return a response" do
26
+ response = @resource.get
27
+ response.should be_kind_of(Resourceful::Response)
28
+ end
29
+
30
+ end
31
+
32
+ describe "POST" do
33
+
34
+ it "should be performable on a resource and return a response" do
35
+ response = @resource.post
36
+ response.should be_kind_of(Resourceful::Response)
37
+ end
38
+
39
+ it "should require Content-Type be set if a body is provided" do
40
+ lambda {
41
+ @resource.post("some text")
42
+ }.should raise_error(Resourceful::MissingContentType)
43
+ end
44
+
45
+ end
46
+
47
+ describe "PUT" do
48
+
49
+ it "should be performable on a resource and return a response" do
50
+ response = @resource.put(nil)
51
+ response.should be_kind_of(Resourceful::Response)
52
+ end
53
+
54
+ it "should require Content-Type be set if a body is provided" do
55
+ lambda {
56
+ @resource.put("some text")
57
+ }.should raise_error(Resourceful::MissingContentType)
58
+ end
59
+
60
+ it "should require an entity-body to be set" do
61
+ lambda {
62
+ @resource.put()
63
+ }.should raise_error(ArgumentError)
64
+ end
65
+
66
+ it "should allow the entity-body to be nil" do
67
+ lambda {
68
+ @resource.put(nil)
69
+ }.should_not raise_error(ArgumentError)
70
+ end
71
+ end
72
+
73
+ describe "DELETE" do
74
+
75
+ it "should be performable on a resource and return a response" do
76
+ response = @resource.delete
77
+ response.should be_kind_of(Resourceful::Response)
78
+ end
79
+
80
+ end
81
+
82
+
83
+ end
84
+ end
@@ -2,48 +2,43 @@ describe 'redirect', :shared => true do
2
2
  it 'should be followed by default on GET' do
3
3
  resp = @resource.get
4
4
  resp.should be_instance_of(Resourceful::Response)
5
- resp.code.should == 200
5
+ resp.should be_ok
6
6
  resp.header['Content-Type'].should == ['text/plain']
7
7
  end
8
8
 
9
9
  %w{PUT POST}.each do |method|
10
10
  it "should not be followed by default on #{method}" do
11
- lambda {
12
- @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
13
- }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
11
+ resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
12
+ resp.should be_redirect
14
13
  end
15
14
 
16
15
  it "should redirect on #{method.to_s.upcase} if the redirection callback returns true" do
17
16
  @resource.on_redirect { true }
18
17
  resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
19
- resp.code.should == 200
18
+ resp.should be_ok
20
19
  end
21
20
 
22
- it "should not redirect on #{method.to_s.upcase} if the redirection callback returns false" do
21
+ it "should not follow redirect on #{method.to_s.upcase} if the redirection callback returns false" do
23
22
  @resource.on_redirect { false }
24
- lambda {
25
- @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
26
- }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
23
+ resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
24
+ resp.should be_redirect
27
25
  end
28
26
  end
29
27
 
30
28
  it "should not be followed by default on DELETE" do
31
- lambda {
32
- @resource.delete
33
- }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
29
+ resp = @resource.delete
30
+ resp.should be_redirect
34
31
  end
35
32
 
36
33
  it "should redirect on DELETE if vthe redirection callback returns true" do
37
34
  @resource.on_redirect { true }
38
35
  resp = @resource.delete
39
- resp.code.should == 200
36
+ resp.should be_ok
40
37
  end
41
38
 
42
39
  it "should not redirect on DELETE if the redirection callback returns false" do
43
- @resource.on_redirect { false }
44
- lambda {
45
- @resource.delete
46
- }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
40
+ resp = @resource.delete
41
+ resp.should be_redirect
47
42
  end
48
43
  end
49
44
 
@@ -6,23 +6,12 @@ require Pathname(__FILE__).dirname + 'acceptance_shared_specs'
6
6
 
7
7
 
8
8
  describe Resourceful do
9
- it_should_behave_like 'simple http server'
10
9
 
11
10
  describe 'working with a resource' do
12
11
  before do
13
12
  @accessor = Resourceful::HttpAccessor.new
14
13
  end
15
14
 
16
- it 'should #get a resource, and return a response object' do
17
- resource = @accessor.resource('http://localhost:3000/get')
18
- resp = resource.get
19
- resp.should be_instance_of(Resourceful::Response)
20
- resp.code.should == 200
21
- resp.body.should == 'Hello, world!'
22
- resp.header.should be_instance_of(Resourceful::Header)
23
- resp.header['Content-Type'].should == ['text/plain']
24
- end
25
-
26
15
  it 'should set additional headers on the #get' do
27
16
  resource = @accessor.resource('http://localhost:3000/echo_header')
28
17
  resp = resource.get(:foo => :bar)
@@ -31,36 +20,6 @@ describe Resourceful do
31
20
  resp.body.should =~ /"HTTP_FOO"=>"bar"/
32
21
  end
33
22
 
34
- it 'should #post a resource, and return the response' do
35
- resource = @accessor.resource('http://localhost:3000/post')
36
- resp = resource.post('Hello world from POST', :content_type => 'text/plain')
37
- resp.should be_instance_of(Resourceful::Response)
38
- resp.code.should == 201
39
- resp.body.should == 'Hello world from POST'
40
- resp.header.should be_instance_of(Resourceful::Header)
41
- resp.header['Content-Type'].should == ['text/plain']
42
- end
43
-
44
- it 'should #put a resource, and return the response' do
45
- resource = @accessor.resource('http://localhost:3000/put')
46
- resp = resource.put('Hello world from PUT', :content_type => 'text/plain')
47
- resp.should be_instance_of(Resourceful::Response)
48
- resp.code.should == 200
49
- resp.body.should == 'Hello world from PUT'
50
- resp.header.should be_instance_of(Resourceful::Header)
51
- resp.header['Content-Type'].should == ['text/plain']
52
- end
53
-
54
- it 'should #delete a resource, and return a response' do
55
- resource = @accessor.resource('http://localhost:3000/delete')
56
- resp = resource.delete
57
- resp.should be_instance_of(Resourceful::Response)
58
- resp.code.should == 200
59
- resp.body.should == 'KABOOM!'
60
- resp.header.should be_instance_of(Resourceful::Header)
61
- resp.header['Content-Type'].should == ['text/plain']
62
- end
63
-
64
23
  it 'should take an optional default header for reads' do
65
24
  resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
66
25
  resp = resource.get
@@ -98,26 +57,21 @@ describe Resourceful do
98
57
  describe 'registering callback' do
99
58
  before do
100
59
  @resource = @accessor.resource('http://localhost:3000/redirect/301?http://localhost:3000/get')
101
- @callback = mock('callback')
102
- @callback.stub!(:call).and_return(true)
103
-
104
- @resource.on_redirect { @callback.call }
105
60
  end
106
61
 
107
62
  it 'should allow a callback to be registered' do
108
63
  @resource.should respond_to(:on_redirect)
109
64
  end
110
65
 
111
- it 'should perform a registered callback on redirect' do
112
- @callback.should_receive(:call).and_return(true)
113
- @resource.get
66
+ it 'should perform a redirect if the callback is true' do
67
+ @resource.on_redirect { true }
68
+ @resource.get.should be_successful
114
69
  end
115
70
 
116
71
  it 'should not perform the redirect if the callback returns false' do
117
- @callback.should_receive(:call).and_return(false)
118
- lambda {
119
- @resource.get
120
- }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
72
+ @resource.on_redirect { false }
73
+ @resource.get
74
+ @resource.get.should be_redirect
121
75
  end
122
76
  end
123
77
 
@@ -224,9 +178,8 @@ describe Resourceful do
224
178
  resource = @accessor.resource(uri)
225
179
  resp = resource.get
226
180
  resp.should be_authoritative
227
- resp.header['Cache-Control'].should include('must-revalidate')
228
181
 
229
- resp2 = resource.get
182
+ resp2 = resource.get(:cache_control => "max-age=0")
230
183
  resp2.should be_authoritative
231
184
  resp2.should == resp
232
185
  end
@@ -242,7 +195,7 @@ describe Resourceful do
242
195
  resp2.should_not == resp
243
196
  end
244
197
 
245
- describe 'Cache-Control' do
198
+ describe 'Server provided Cache-Control' do
246
199
 
247
200
  it 'should cache anything with "Cache-Control: public"' do
248
201
  uri = URI.escape('http://localhost:3000/header?{Cache-Control: public}')
@@ -260,10 +213,10 @@ describe Resourceful do
260
213
  uri = URI.escape('http://localhost:3000/header?{Cache-Control: private}')
261
214
  resource = @accessor.resource(uri)
262
215
  resp = resource.get
263
- resp.authoritative?.should be_true
216
+ resp.should be_authoritative
264
217
 
265
218
  resp2 = resource.get
266
- resp2.authoritative?.should be_false
219
+ resp2.should_not be_authoritative
267
220
 
268
221
  resp2.should == resp
269
222
  end
@@ -314,6 +267,23 @@ describe Resourceful do
314
267
 
315
268
  end
316
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
+
317
287
  end
318
288
 
319
289
  describe 'authorization' do
@@ -0,0 +1,74 @@
1
+
2
+ require 'sinatra'
3
+
4
+ def any(path, opts={}, &blk)
5
+ %w[head get post put delete].each do |verb|
6
+ send verb, path, opts, &blk
7
+ end
8
+ end
9
+
10
+ def set_request_params_as_response_header!
11
+ params.each { |k,v| response[k] = v }
12
+ end
13
+
14
+ def set_request_header_in_body!
15
+ response['Content-Type'] ||= "application/yaml"
16
+ headers = request.env.reject { |k,v| !v.is_a?(String) }
17
+ headers.to_yaml
18
+ end
19
+
20
+ get '/' do
21
+ "Hello, world!"
22
+ end
23
+
24
+ post '/' do
25
+ request.body
26
+ end
27
+
28
+ put '/' do
29
+ request.body
30
+ end
31
+
32
+ delete '/' do
33
+ "Deleted"
34
+ end
35
+
36
+ # Responds with the method used for the request
37
+ any '/method' do
38
+ request.env['REQUEST_METHOD']
39
+ end
40
+
41
+ # Responds with the response code in the url
42
+ any '/code/:code' do
43
+ status params[:code]
44
+ set_request_params_as_response_header!
45
+ set_request_header_in_body!
46
+ end
47
+
48
+ # Sets the response header from the query string, and
49
+ # dumps the request header into the body as yaml for inspection
50
+ any '/header' do
51
+ set_request_params_as_response_header!
52
+ set_request_header_in_body!
53
+ end
54
+
55
+ # Takes a modified=httpdate as a query param, and a If-Modified-Since header,
56
+ # and responds 304 if they're the same
57
+ get '/cached' do
58
+ set_request_params_as_response_header!
59
+ set_request_header_in_body!
60
+
61
+ response['Last-Modified'] = params[:modified]
62
+
63
+ modtime = params[:modified]
64
+ imstime = request.env['HTTP_IF_MODIFIED_SINCE']
65
+
66
+ if modtime && imstime && modtime == imstime
67
+ status 304
68
+ end
69
+ end
70
+
71
+ Sinatra::Default.set(
72
+ :port => 3000
73
+ )
74
+
@@ -0,0 +1,98 @@
1
+
2
+ require 'rubygems'
3
+ require 'sinatra'
4
+ require 'sinatra/test/rspec'
5
+
6
+ require File.dirname(__FILE__) + '/spec_helper'
7
+
8
+ describe "GET /" do
9
+ it 'should render "Hello, world!"' do
10
+ get '/'
11
+ @response.should be_ok
12
+ @response.body.should == "Hello, world!"
13
+ end
14
+ end
15
+
16
+ describe "POST /" do
17
+ it 'should be 201 with no body' do
18
+ post '/'
19
+ @response.should be_ok
20
+ @response.body.should == ""
21
+ end
22
+
23
+ it 'should return the request body as the response body' do
24
+ body = "Some text"
25
+ post '/', body
26
+ @response.should be_ok
27
+ @response.body.should == body
28
+ end
29
+ end
30
+
31
+ describe "PUT /" do
32
+ it 'should be 200 with no body' do
33
+ put '/'
34
+ @response.should be_ok
35
+ @response.body.should == ""
36
+ end
37
+
38
+ it 'should return the request body as the response body' do
39
+ body = "Some text"
40
+ put '/', body
41
+ @response.should be_ok
42
+ @response.body.should == body
43
+ end
44
+ end
45
+
46
+ describe "DELETE /" do
47
+ it 'should render "Deleted"' do
48
+ delete '/'
49
+ @response.should be_ok
50
+ @response.body.should == "Deleted"
51
+ end
52
+ end
53
+
54
+ describe "/method" do
55
+ it 'should respond with the method used to make the request' do
56
+ %w[get post put delete].each do |verb|
57
+ send verb, '/method'
58
+ @response.body.should == verb.upcase
59
+ end
60
+ end
61
+ end
62
+
63
+ describe "/code/nnn" do
64
+ it 'should respond with the code provided in the url' do
65
+ # Just try a handful
66
+ [200, 201, 301, 302, 304, 403, 404, 500].each do |code|
67
+ get "/code/#{code}"
68
+ @response.status.should == code
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "/header" do
74
+ it 'should set response headers from the query string' do
75
+ get "/header", "X-Foo" => "Bar"
76
+ @response['X-Foo'].should == "Bar"
77
+ end
78
+
79
+ it 'should dump the request headers into the body as yaml' do
80
+ get '/header', {}, "X-Foo" => "Bar"
81
+ body = YAML.load(@response.body)
82
+ body['X-Foo'].should == "Bar"
83
+ end
84
+ end
85
+
86
+ describe "/cache" do
87
+ it 'should be normal 200 if the modified query param and the ims header dont match' do
88
+ now = Time.now
89
+ get '/cached', {"modified" => now.httpdate}, {"HTTP_IF_MODIFIED_SINCE" => (now - 3600).httpdate}
90
+ @response.should be_ok
91
+ end
92
+
93
+ it 'should be 304 if the modified query param and the ims header are the same' do
94
+ now = Time.now
95
+ get '/cached', {"modified" => now.httpdate}, {"HTTP_IF_MODIFIED_SINCE" => now.httpdate}
96
+ @response.status.should == 304
97
+ end
98
+ end