paul-resourceful 0.2.3

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 (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
+