paul-resourceful 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/Manifest.txt +34 -0
  3. data/README.markdown +86 -0
  4. data/Rakefile +14 -0
  5. data/lib/resourceful.rb +29 -0
  6. data/lib/resourceful/authentication_manager.rb +107 -0
  7. data/lib/resourceful/cache_manager.rb +174 -0
  8. data/lib/resourceful/header.rb +31 -0
  9. data/lib/resourceful/http_accessor.rb +85 -0
  10. data/lib/resourceful/net_http_adapter.rb +60 -0
  11. data/lib/resourceful/options_interpreter.rb +78 -0
  12. data/lib/resourceful/request.rb +63 -0
  13. data/lib/resourceful/resource.rb +266 -0
  14. data/lib/resourceful/response.rb +175 -0
  15. data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
  16. data/lib/resourceful/util.rb +6 -0
  17. data/lib/resourceful/version.rb +1 -0
  18. data/resourceful.gemspec +30 -0
  19. data/spec/acceptance_shared_specs.rb +49 -0
  20. data/spec/acceptance_spec.rb +408 -0
  21. data/spec/resourceful/authentication_manager_spec.rb +249 -0
  22. data/spec/resourceful/cache_manager_spec.rb +211 -0
  23. data/spec/resourceful/header_spec.rb +38 -0
  24. data/spec/resourceful/http_accessor_spec.rb +125 -0
  25. data/spec/resourceful/net_http_adapter_spec.rb +96 -0
  26. data/spec/resourceful/options_interpreter_spec.rb +94 -0
  27. data/spec/resourceful/request_spec.rb +186 -0
  28. data/spec/resourceful/resource_spec.rb +600 -0
  29. data/spec/resourceful/response_spec.rb +238 -0
  30. data/spec/resourceful/stubbed_resource_proxy_spec.rb +58 -0
  31. data/spec/simple_http_server_shared_spec.rb +160 -0
  32. data/spec/simple_http_server_shared_spec_spec.rb +212 -0
  33. data/spec/spec.opts +3 -0
  34. data/spec/spec_helper.rb +14 -0
  35. metadata +98 -0
@@ -0,0 +1,175 @@
1
+ require 'net/http'
2
+ require 'time'
3
+ require 'rubygems'
4
+ require 'facets/kernel/ergo'
5
+ require 'zlib'
6
+
7
+ module Resourceful
8
+ # Exception indicating that the server used a content coding scheme
9
+ # that Resourceful is unable to handle.
10
+ class UnsupportedContentCoding < Exception
11
+ end
12
+
13
+ class Response
14
+ REDIRECT_RESPONSE_CODES = [301,302,303,307]
15
+
16
+ attr_reader :uri, :code, :header, :body, :response_time
17
+ alias headers header
18
+
19
+ attr_accessor :authoritative, :request_time
20
+ alias authoritative? authoritative
21
+
22
+ def initialize(uri, code, header, body)
23
+ @uri, @code, @header, @body = uri, code, header, body
24
+ @response_time = Time.now
25
+ end
26
+
27
+ # Is the response code sucessful? True for only 2xx series
28
+ # response codes.
29
+ #
30
+ # @return true|false
31
+ def is_success?
32
+ @code.in? 200..299
33
+ end
34
+ alias was_successful? is_success?
35
+
36
+ # Is the response the result of a server error? True for
37
+ # 5xx series response codes
38
+ #
39
+ # @return true|false
40
+ def is_server_error?
41
+ @code.in? 500..599
42
+ end
43
+ alias was_server_error? is_server_error?
44
+
45
+ # Is the response the result of a client error? True for
46
+ # 4xx series response codes
47
+ #
48
+ # @return true|false
49
+ def is_client_error?
50
+ @code.in? 400..499
51
+ end
52
+ alias was_client_error? is_client_error?
53
+
54
+ # Is the response the result of any kind of error? True for
55
+ # 4xx and 5xx series response codes
56
+ #
57
+ # @return true|false
58
+ def is_error?
59
+ is_server_error? || is_client_error?
60
+ end
61
+ alias was_error? is_error?
62
+
63
+ # Is the response not a success? True for
64
+ # 3xx, 4xx and 5xx series response codes
65
+ #
66
+ # @return true|false
67
+ def is_unsuccesful?
68
+ is_error? || is_redirect?
69
+ end
70
+ alias was_unsuccessful? is_unsuccesful?
71
+
72
+ # Is the response a redirect response code? True for
73
+ # 3xx codes that are redirects (301, 302, 303, 307)
74
+ #
75
+ # @return true|false
76
+ def is_redirect?
77
+ @code.in? REDIRECT_RESPONSE_CODES
78
+ end
79
+ alias was_redirect? is_redirect?
80
+
81
+ # Is the response a Permanent Redirect (301) ?
82
+ #
83
+ # @return true|false
84
+ def is_permanent_redirect?
85
+ @code == 301
86
+ end
87
+
88
+ # Is the response a Temporary Redirect (anything but 301) ?
89
+ #
90
+ # @return true|false
91
+ def is_temporary_redirect?
92
+ is_redirect? and not is_permanent_redirect?
93
+ end
94
+
95
+ # Is the response a client error of Not Authorized (401) ?
96
+ #
97
+ # @return true|false
98
+ def is_not_authorized?
99
+ @code == 401
100
+ end
101
+
102
+ # Is the response not modified (304) ?
103
+ #
104
+ # @return true|false
105
+ def is_not_modified?
106
+ @code == 304
107
+ end
108
+
109
+ # Is this a cached response that has expired?
110
+ #
111
+ # @return true|false
112
+ def expired?
113
+ if header['Expire']
114
+ return true if Time.httpdate(header['Expire'].first) < Time.now
115
+ end
116
+ if header['Cache-Control'] and header['Cache-Control'].first.include?('max-age')
117
+ max_age = header['Cache-Control'].first.split(',').grep(/max-age/).first.split('=').last.to_i
118
+ return true if current_age > max_age
119
+ end
120
+
121
+ false
122
+ end
123
+
124
+ # Is this a cached response that is stale?
125
+ #
126
+ # @return true|false
127
+ def stale?
128
+ return true if expired?
129
+ if header['Cache-Control']
130
+ return true if header['Cache-Control'].include?('must-revalidate')
131
+ return true if header['Cache-Control'].include?('no-cache')
132
+ end
133
+
134
+ false
135
+ end
136
+
137
+ # Is this response cachable?
138
+ #
139
+ # @return true|false
140
+ def cachable?
141
+ return false if header['Vary'] and header['Vary'].include?('*')
142
+ return false if header['Cache-Control'] and header['Cache-Control'].include?('no-store')
143
+
144
+ true
145
+ end
146
+
147
+ # Algorithm taken from RCF2616#13.2.3
148
+ def current_age
149
+ age_value = Time.httpdate(header['Age'].first) if header['Age']
150
+ date_value = Time.httpdate(header['Date'].first)
151
+ now = Time.now
152
+
153
+ apparent_age = [0, response_time - date_value].max
154
+ corrected_received_age = [apparent_age, age_value || 0].max
155
+ current_age = corrected_received_age + (response_time - request_time) + (now - response_time)
156
+ end
157
+
158
+ def body
159
+ case header['Content-Encoding'].ergo.first
160
+ when nil
161
+ # body is identity encoded; just return it
162
+ @body
163
+ when /^\s*gzip\s*$/i
164
+ gz_in = ::Zlib::GzipReader.new(StringIO.new(@body, 'r'))
165
+ @body = gz_in.read
166
+ gz_in.close
167
+ header.delete('Content-Encoding')
168
+ @body
169
+ else
170
+ raise UnsupportedContentCoding, "Resourceful does not support #{header['Content-Encoding'].ergo.first} content coding"
171
+ end
172
+ end
173
+ end
174
+
175
+ end
@@ -0,0 +1,47 @@
1
+ require 'resourceful/resource'
2
+
3
+ module Resourceful
4
+ class StubbedResourceProxy
5
+ def initialize(resource, canned_responses)
6
+ @resource = resource
7
+
8
+ @canned_responses = {}
9
+
10
+ canned_responses.each do |cr|
11
+ mime_type = cr[:mime_type]
12
+ @canned_responses[mime_type] = resp = Net::HTTPOK.new('1.1', '200', 'OK')
13
+ resp['content-type'] = mime_type.to_str
14
+ resp.instance_variable_set(:@read, true)
15
+ resp.instance_variable_set(:@body, cr[:body])
16
+
17
+ end
18
+ end
19
+
20
+ def get_body(*args)
21
+ get(*args).body
22
+ end
23
+
24
+ def get(*args)
25
+ options = args.last.is_a?(Hash) ? args.last : {}
26
+
27
+ if accept = [(options[:accept] || '*/*')].flatten.compact
28
+ accept.each do |mt|
29
+ return canned_response(mt) || next
30
+ end
31
+ @resource.get(*args)
32
+ end
33
+ end
34
+
35
+ def method_missing(method, *args)
36
+ @resource.send(method, *args)
37
+ end
38
+
39
+ protected
40
+
41
+ def canned_response(mime_type)
42
+ mime_type = @canned_responses.keys.first if mime_type == '*/*'
43
+ @canned_responses[mime_type]
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class Object
3
+ def in?(arr)
4
+ arr.include?(self)
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ RESOURCEFUL_VERSION = '0.2'
@@ -0,0 +1,30 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{resourceful}
3
+ s.version = "0.2.3"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Paul Sadauskas", "Peter Williams"]
7
+ s.date = %q{2008-11-22}
8
+ s.email = ["psadauskas@gmail.com", "pezra@barelyenough.org"]
9
+ s.extra_rdoc_files = ["Manifest.txt"]
10
+ s.files = ["MIT-LICENSE", "Manifest.txt", "README.markdown", "Rakefile", "lib/resourceful.rb", "lib/resourceful/authentication_manager.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/header.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpreter.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/response.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/util.rb", "lib/resourceful/version.rb", "resourceful.gemspec", "spec/acceptance_shared_specs.rb", "spec/acceptance_spec.rb", "spec/resourceful/authentication_manager_spec.rb", "spec/resourceful/cache_manager_spec.rb", "spec/resourceful/header_spec.rb", "spec/resourceful/http_accessor_spec.rb", "spec/resourceful/net_http_adapter_spec.rb", "spec/resourceful/options_interpreter_spec.rb", "spec/resourceful/request_spec.rb", "spec/resourceful/resource_spec.rb", "spec/resourceful/response_spec.rb", "spec/resourceful/stubbed_resource_proxy_spec.rb", "spec/simple_http_server_shared_spec.rb", "spec/simple_http_server_shared_spec_spec.rb", "spec/spec.opts", "spec/spec_helper.rb"]
11
+ s.has_rdoc = true
12
+ s.rdoc_options = ["--main", "README.txt"]
13
+ s.require_paths = ["lib"]
14
+ s.rubyforge_project = %q{resourceful}
15
+ s.rubygems_version = %q{1.3.1}
16
+ s.summary = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
17
+
18
+ if s.respond_to? :specification_version then
19
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
20
+ s.specification_version = 2
21
+
22
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
23
+ s.add_development_dependency(%q<hoe>, [">= 1.8.2"])
24
+ else
25
+ s.add_dependency(%q<hoe>, [">= 1.8.2"])
26
+ end
27
+ else
28
+ s.add_dependency(%q<hoe>, [">= 1.8.2"])
29
+ end
30
+ end
@@ -0,0 +1,49 @@
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.code.should == 200
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
+ lambda {
12
+ @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
13
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
14
+ end
15
+
16
+ it "should redirect on #{method.to_s.upcase} if the redirection callback returns true" do
17
+ @resource.on_redirect { true }
18
+ resp = @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
19
+ resp.code.should == 200
20
+ end
21
+
22
+ it "should not redirect on #{method.to_s.upcase} if the redirection callback returns false" do
23
+ @resource.on_redirect { false }
24
+ lambda {
25
+ @resource.send(method.downcase.intern, nil, :content_type => 'text/plain' )
26
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
27
+ end
28
+ end
29
+
30
+ it "should not be followed by default on DELETE" do
31
+ lambda {
32
+ @resource.delete
33
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
34
+ end
35
+
36
+ it "should redirect on DELETE if vthe redirection callback returns true" do
37
+ @resource.on_redirect { true }
38
+ resp = @resource.delete
39
+ resp.code.should == 200
40
+ end
41
+
42
+ 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)
47
+ end
48
+ end
49
+
@@ -0,0 +1,408 @@
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
+ it_should_behave_like 'simple http server'
10
+
11
+ describe 'working with a resource' do
12
+ before do
13
+ @accessor = Resourceful::HttpAccessor.new
14
+ end
15
+
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
+ it 'should set additional headers on the #get' do
27
+ resource = @accessor.resource('http://localhost:3000/echo_header')
28
+ resp = resource.get(:foo => :bar)
29
+ resp.should be_instance_of(Resourceful::Response)
30
+ resp.code.should == 200
31
+ resp.body.should =~ /"HTTP_FOO"=>"bar"/
32
+ end
33
+
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
+ it 'should take an optional default header for reads' do
65
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
66
+ resp = resource.get
67
+ resp.should be_instance_of(Resourceful::Response)
68
+ resp.code.should == 200
69
+ resp.body.should =~ /"HTTP_FOO"=>"bar"/
70
+ end
71
+
72
+ it 'should take an optional default header for writes' do
73
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
74
+ resp = resource.post("data", :content_type => 'text/plain')
75
+ resp.should be_instance_of(Resourceful::Response)
76
+ resp.code.should == 200
77
+ resp.body.should =~ /"HTTP_FOO"=>"bar"/
78
+ end
79
+
80
+ it 'should override the default header with any set on a read action' do
81
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
82
+ resp = resource.get(:foo => :baz)
83
+ resp.should be_instance_of(Resourceful::Response)
84
+ resp.code.should == 200
85
+ resp.body.should =~ /"HTTP_FOO"=>"baz"/
86
+ end
87
+
88
+ it 'should override the default header with any set on a write action' do
89
+ resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
90
+ resp = resource.post("data", :foo => :baz, :content_type => 'text/plain')
91
+ resp.should be_instance_of(Resourceful::Response)
92
+ resp.code.should == 200
93
+ resp.body.should =~ /"HTTP_FOO"=>"baz"/
94
+ end
95
+
96
+ describe 'redirecting' do
97
+
98
+ describe 'registering callback' do
99
+ before do
100
+ @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
+ end
106
+
107
+ it 'should allow a callback to be registered' do
108
+ @resource.should respond_to(:on_redirect)
109
+ end
110
+
111
+ it 'should perform a registered callback on redirect' do
112
+ @callback.should_receive(:call).and_return(true)
113
+ @resource.get
114
+ end
115
+
116
+ 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)
121
+ end
122
+ end
123
+
124
+ describe 'permanent redirect' do
125
+ before do
126
+ @redirect_code = 301
127
+ @resource = @accessor.resource('http://localhost:3000/redirect/301?http://localhost:3000/get')
128
+ end
129
+
130
+ it_should_behave_like 'redirect'
131
+
132
+ it 'should change the effective uri of the resource' do
133
+ @resource.get
134
+ @resource.effective_uri.should == 'http://localhost:3000/get'
135
+ end
136
+ end
137
+
138
+ describe 'temporary redirect' do
139
+ before do
140
+ @redirect_code = 302
141
+ @resource = @accessor.resource('http://localhost:3000/redirect/302?http://localhost:3000/get')
142
+ end
143
+
144
+ it_should_behave_like 'redirect'
145
+
146
+ it 'should not change the effective uri of the resource' do
147
+ @resource.get
148
+ @resource.effective_uri.should == 'http://localhost:3000/redirect/302?http://localhost:3000/get'
149
+ end
150
+
151
+ describe '303 See Other' do
152
+ before do
153
+ @redirect_code = 303
154
+ @resource = @accessor.resource('http://localhost:3000/redirect/303?http://localhost:3000/method')
155
+ @resource.on_redirect { true }
156
+ end
157
+
158
+ it 'should GET the redirected resource, regardless of the initial method' do
159
+ resp = @resource.delete
160
+ resp.code.should == 200
161
+ resp.body.should == 'GET'
162
+ end
163
+ end
164
+ end
165
+
166
+ end
167
+
168
+ describe 'caching' do
169
+ before do
170
+ @accessor = Resourceful::HttpAccessor.new(:cache_manager => Resourceful::InMemoryCacheManager.new)
171
+ end
172
+
173
+ it 'should use the cached response' do
174
+ resource = @accessor.resource('http://localhost:3000/get')
175
+ resp = resource.get
176
+ resp.authoritative?.should be_true
177
+
178
+ resp2 = resource.get
179
+ resp2.authoritative?.should be_false
180
+
181
+ resp2.should == resp
182
+ end
183
+
184
+ it 'should not store the representation if the server says not to' do
185
+ resource = @accessor.resource('http://localhost:3000/header?{Vary:%20*}')
186
+ resp = resource.get
187
+ resp.authoritative?.should be_true
188
+ resp.should_not be_cachable
189
+
190
+ resp2 = resource.get
191
+ resp2.should_not == resp
192
+ end
193
+
194
+ it 'should use the cached version of the representation if it has not expired' do
195
+ in_an_hour = (Time.now + (60*60)).httpdate
196
+ uri = URI.escape("http://localhost:3000/header?{Expire: \"#{in_an_hour}\"}")
197
+
198
+ resource = @accessor.resource(uri)
199
+ resp = resource.get
200
+ resp.should be_authoritative
201
+
202
+ resp2 = resource.get
203
+ resp2.should_not be_authoritative
204
+ resp2.should == resp
205
+ end
206
+
207
+ it 'should revalidate the cached response if it has expired' do
208
+ an_hour_ago = (Time.now - (60*60)).httpdate
209
+ uri = URI.escape("http://localhost:3000/header?{Expire: \"#{an_hour_ago}\"}")
210
+
211
+ resource = @accessor.resource(uri)
212
+ resp = resource.get
213
+ resp.should be_authoritative
214
+ resp.should be_expired
215
+
216
+ resp2 = resource.get
217
+ resp2.should be_authoritative
218
+ end
219
+
220
+ it 'should provide the cached version if the server responds with a 304 not modified' do
221
+ in_an_hour = (Time.now + (60*60)).httpdate
222
+ uri = URI.escape("http://localhost:3000/modified?#{in_an_hour}")
223
+
224
+ resource = @accessor.resource(uri)
225
+ resp = resource.get
226
+ resp.should be_authoritative
227
+ resp.header['Cache-Control'].should include('must-revalidate')
228
+
229
+ resp2 = resource.get
230
+ resp2.should be_authoritative
231
+ resp2.should == resp
232
+ end
233
+
234
+ it 'should not use a cached document for a resource that has been posted to' do
235
+ resource = @accessor.resource('http://localhost:3000/get')
236
+ resp = resource.get
237
+ resp.authoritative?.should be_true
238
+
239
+ resource.post("foo", :content_type => 'text/plain')
240
+
241
+ resp2 = resource.get
242
+ resp2.should_not == resp
243
+ end
244
+
245
+ describe 'Cache-Control' do
246
+
247
+ it 'should cache anything with "Cache-Control: public"' do
248
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: public}')
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_false
255
+
256
+ resp2.should == resp
257
+ end
258
+
259
+ it 'should cache anything with "Cache-Control: private"' do
260
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: private}')
261
+ resource = @accessor.resource(uri)
262
+ resp = resource.get
263
+ resp.authoritative?.should be_true
264
+
265
+ resp2 = resource.get
266
+ resp2.authoritative?.should be_false
267
+
268
+ resp2.should == resp
269
+ end
270
+
271
+ it 'should cache but revalidate anything with "Cache-Control: no-cache"' do
272
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: no-cache}')
273
+ resource = @accessor.resource(uri)
274
+ resp = resource.get
275
+ resp.authoritative?.should be_true
276
+
277
+ resp2 = resource.get
278
+ resp2.authoritative?.should be_true
279
+ end
280
+
281
+ it 'should revalidate anything that is older than "Cache-Control: max-age" value' do
282
+
283
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: max-age=1, Date: "Mon, 04 Aug 2008 18:00:00 GMT"}')
284
+ resource = @accessor.resource(uri)
285
+ resp = resource.get
286
+ resp.authoritative?.should be_true
287
+
288
+ resp.expired?.should be_true
289
+
290
+ resp2 = resource.get
291
+ resp2.authoritative?.should be_true
292
+ end
293
+
294
+ it 'should cache but revalidate anything with "Cache-Control: must-revalidate"' do
295
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: must-revalidate}')
296
+ resource = @accessor.resource(uri)
297
+ resp = resource.get
298
+ resp.authoritative?.should be_true
299
+
300
+ resp2 = resource.get
301
+ resp2.authoritative?.should be_true
302
+ end
303
+
304
+ it 'should not cache anything with "Cache-Control: no-store"' do
305
+ uri = URI.escape('http://localhost:3000/header?{Cache-Control: no-store}')
306
+ resource = @accessor.resource(uri)
307
+ resp = resource.get
308
+ resp.authoritative?.should be_true
309
+
310
+ resp2 = resource.get
311
+ resp2.authoritative?.should be_true
312
+ end
313
+
314
+
315
+ end
316
+
317
+ end
318
+
319
+ describe 'authorization' do
320
+ before do
321
+ @uri = 'http://localhost:3000/auth?basic'
322
+ end
323
+
324
+ it 'should automatically add authorization info to the request if its available'
325
+
326
+ it 'should not authenticate if no auth handlers are set' do
327
+ resource = @accessor.resource(@uri)
328
+ lambda {
329
+ resource.get
330
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
331
+ end
332
+
333
+ it 'should not authenticate if no valid auth handlers are available' do
334
+ basic_handler = Resourceful::BasicAuthenticator.new('Not Test Auth', 'admin', 'secret')
335
+ @accessor.auth_manager.add_auth_handler(basic_handler)
336
+ resource = @accessor.resource(@uri)
337
+ lambda {
338
+ resource.get
339
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
340
+ end
341
+
342
+ describe 'basic' do
343
+ before do
344
+ @uri = 'http://localhost:3000/auth?basic'
345
+ end
346
+
347
+ it 'should be able to authenticate basic auth' do
348
+ basic_handler = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'secret')
349
+ @accessor.auth_manager.add_auth_handler(basic_handler)
350
+ resource = @accessor.resource(@uri)
351
+ resp = resource.get
352
+
353
+ resp.code.should == 200
354
+ end
355
+
356
+ it 'should not keep trying to authenticate with incorrect credentials' do
357
+ basic_handler = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'well-known')
358
+ @accessor.auth_manager.add_auth_handler(basic_handler)
359
+ resource = @accessor.resource(@uri)
360
+
361
+ lambda {
362
+ resource.get
363
+ }.should raise_error(Resourceful::UnsuccessfulHttpRequestError)
364
+ end
365
+
366
+ end
367
+
368
+ describe 'digest' do
369
+ before do
370
+ @uri = 'http://localhost:3000/auth/digest'
371
+ end
372
+
373
+ it 'should be able to authenticate digest auth' do
374
+ pending
375
+ digest_handler = Resourceful::DigestAuthenticator.new('Test Auth', 'admin', 'secret')
376
+ @accessor.auth_manager.add_auth_handler(digest_handler)
377
+ resource = @accessor.resource(@uri)
378
+ resp = resource.get
379
+
380
+ resp.code.should == 200
381
+ end
382
+
383
+ end
384
+
385
+ end
386
+
387
+ describe 'error checking' do
388
+
389
+ it 'should raise InvalidResponse when response code is invalid'
390
+
391
+ describe 'client errors' do
392
+
393
+ it 'should raise when there is one'
394
+
395
+ end
396
+
397
+ describe 'server errors' do
398
+
399
+ it 'should raise when there is one'
400
+
401
+ end
402
+
403
+ end
404
+
405
+ end
406
+
407
+ end
408
+