resourceful 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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