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.
- data/Manifest +24 -28
- data/Rakefile +44 -14
- data/lib/resourceful.rb +11 -21
- data/lib/resourceful/authentication_manager.rb +3 -2
- data/lib/resourceful/cache_manager.rb +58 -1
- data/lib/resourceful/exceptions.rb +34 -0
- data/lib/resourceful/header.rb +95 -0
- data/lib/resourceful/http_accessor.rb +0 -2
- data/lib/resourceful/memcache_cache_manager.rb +3 -13
- data/lib/resourceful/net_http_adapter.rb +15 -5
- data/lib/resourceful/request.rb +180 -18
- data/lib/resourceful/resource.rb +38 -141
- data/lib/resourceful/response.rb +142 -95
- data/resourceful.gemspec +9 -7
- data/spec/acceptance/authorization_spec.rb +16 -0
- data/spec/acceptance/caching_spec.rb +192 -0
- data/spec/acceptance/header_spec.rb +24 -0
- data/spec/acceptance/redirecting_spec.rb +12 -0
- data/spec/acceptance/resource_spec.rb +84 -0
- data/spec/acceptance_shared_specs.rb +12 -17
- data/spec/{acceptance_spec.rb → old_acceptance_specs.rb} +27 -57
- data/spec/simple_sinatra_server.rb +74 -0
- data/spec/simple_sinatra_server_spec.rb +98 -0
- data/spec/spec_helper.rb +21 -7
- metadata +50 -42
- data/spec/resourceful/authentication_manager_spec.rb +0 -249
- data/spec/resourceful/cache_manager_spec.rb +0 -223
- data/spec/resourceful/header_spec.rb +0 -38
- data/spec/resourceful/http_accessor_spec.rb +0 -164
- data/spec/resourceful/memcache_cache_manager_spec.rb +0 -111
- data/spec/resourceful/net_http_adapter_spec.rb +0 -96
- data/spec/resourceful/options_interpreter_spec.rb +0 -102
- data/spec/resourceful/request_spec.rb +0 -186
- data/spec/resourceful/resource_spec.rb +0 -600
- data/spec/resourceful/response_spec.rb +0 -238
- data/spec/resourceful/stubbed_resource_proxy_spec.rb +0 -58
- data/spec/simple_http_server_shared_spec.rb +0 -162
- 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,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.
|
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
|
-
|
12
|
-
|
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.
|
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
|
-
|
25
|
-
|
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
|
-
|
32
|
-
|
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.
|
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.
|
44
|
-
|
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
|
112
|
-
@
|
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
|
-
@
|
118
|
-
|
119
|
-
|
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.
|
216
|
+
resp.should be_authoritative
|
264
217
|
|
265
218
|
resp2 = resource.get
|
266
|
-
resp2.
|
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
|