resourceful 0.2.1 → 0.3.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 +34 -0
- data/README.markdown +3 -5
- data/Rakefile +23 -75
- data/lib/resourceful/authentication_manager.rb +25 -3
- data/lib/resourceful/cache_manager.rb +113 -104
- data/lib/resourceful/http_accessor.rb +20 -5
- data/lib/resourceful/memcache_cache_manager.rb +85 -0
- data/lib/resourceful/net_http_adapter.rb +1 -0
- data/lib/resourceful/request.rb +12 -0
- data/lib/resourceful/resource.rb +13 -9
- data/lib/resourceful/response.rb +2 -1
- data/resourceful.gemspec +52 -0
- data/spec/acceptance_spec.rb +46 -1
- data/spec/resourceful/authentication_manager_spec.rb +49 -4
- data/spec/resourceful/cache_manager_spec.rb +79 -67
- data/spec/resourceful/http_accessor_spec.rb +55 -11
- data/spec/resourceful/memcache_cache_manager_spec.rb +111 -0
- data/spec/resourceful/options_interpreter_spec.rb +9 -1
- data/spec/resourceful/request_spec.rb +9 -2
- data/spec/resourceful/resource_spec.rb +52 -11
- data/spec/simple_http_server_shared_spec.rb +3 -4
- data/spec/simple_http_server_shared_spec_spec.rb +9 -0
- data/spec/spec_helper.rb +1 -2
- metadata +81 -32
- data/lib/resourceful/version.rb +0 -1
@@ -37,16 +37,17 @@ module Resourceful
|
|
37
37
|
#
|
38
38
|
# Errors will not be logged. Instead an exception will be raised
|
39
39
|
# and the application code should log it if appropriate.
|
40
|
-
attr_accessor :logger
|
41
|
-
|
42
|
-
attr_reader :auth_manager, :cache_manager
|
40
|
+
attr_accessor :logger, :cache_manager
|
43
41
|
|
42
|
+
attr_reader :auth_manager
|
44
43
|
attr_reader :user_agent_tokens
|
45
44
|
|
46
45
|
INIT_OPTIONS = OptionsInterpreter.new do
|
47
46
|
option(:logger, :default => Resourceful::BitBucketLogger.new)
|
48
47
|
option(:user_agent, :default => []) {|ua| [ua].flatten}
|
49
48
|
option(:cache_manager, :default => NullCacheManager.new)
|
49
|
+
option(:authenticator)
|
50
|
+
option(:authenticators, :default => [])
|
50
51
|
end
|
51
52
|
|
52
53
|
# Initializes a new HttpAccessor. Valid options:
|
@@ -56,6 +57,13 @@ module Resourceful
|
|
56
57
|
#
|
57
58
|
# +:user_agent+:: One or more additional user agent tokens to
|
58
59
|
# added to the user agent string.
|
60
|
+
#
|
61
|
+
# +:cache_manager+:: The cache manager this accessor should use.
|
62
|
+
#
|
63
|
+
# +:authenticator+:: Add a single authenticator for this accessor.
|
64
|
+
#
|
65
|
+
# +:authenticators+:: Enumerable of the authenticators for this
|
66
|
+
# accessor.
|
59
67
|
def initialize(options = {})
|
60
68
|
@user_agent_tokens = [RESOURCEFUL_USER_AGENT_TOKEN]
|
61
69
|
|
@@ -64,6 +72,9 @@ module Resourceful
|
|
64
72
|
self.logger = opts[:logger]
|
65
73
|
@auth_manager = AuthenticationManager.new()
|
66
74
|
@cache_manager = opts[:cache_manager]
|
75
|
+
|
76
|
+
add_authenticator(opts[:authenticator]) if opts[:authenticator]
|
77
|
+
opts[:authenticators].each { |a| add_authenticator(a) }
|
67
78
|
end
|
68
79
|
end
|
69
80
|
|
@@ -76,10 +87,14 @@ module Resourceful
|
|
76
87
|
|
77
88
|
# Returns a resource object representing the resource indicated
|
78
89
|
# by the specified URI. A resource object will be created if necessary.
|
79
|
-
def resource(uri)
|
80
|
-
resource = Resource.new(self, uri)
|
90
|
+
def resource(uri, opts = {})
|
91
|
+
resource = Resource.new(self, uri, opts)
|
81
92
|
end
|
82
93
|
alias [] resource
|
83
94
|
|
95
|
+
# Adds an Authenticator to the set used by the accessor.
|
96
|
+
def add_authenticator(an_authenticator)
|
97
|
+
auth_manager.add_auth_handler(an_authenticator)
|
98
|
+
end
|
84
99
|
end
|
85
100
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/cache_manager"
|
2
|
+
|
3
|
+
require 'memcache'
|
4
|
+
require 'facets/kernel/returning'
|
5
|
+
|
6
|
+
module Resourceful
|
7
|
+
class MemcacheCacheManager < AbstractCacheManager
|
8
|
+
|
9
|
+
# Create a new Memcached backed cache manager
|
10
|
+
#
|
11
|
+
# @param [*String] memcache_servers
|
12
|
+
# list of all Memcached servers this cache manager should use.
|
13
|
+
def initialize(*memcache_servers)
|
14
|
+
@memcache = MemCache.new(memcache_servers, :multithread => true)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Finds a previously cached response to the provided request. The
|
18
|
+
# response returned may be stale.
|
19
|
+
#
|
20
|
+
# @param [Resourceful::Request] request
|
21
|
+
# The request for which we are looking for a response.
|
22
|
+
#
|
23
|
+
# @return [Resourceful::Response, nil]
|
24
|
+
# A (possibly stale) response for the request provided or nil if
|
25
|
+
# no matching response is found.
|
26
|
+
def lookup(request)
|
27
|
+
resp = cache_entries_for(request)[request]
|
28
|
+
return if resp.nil?
|
29
|
+
|
30
|
+
resp.authoritative = false
|
31
|
+
|
32
|
+
resp
|
33
|
+
end
|
34
|
+
|
35
|
+
# Store a response in the cache.
|
36
|
+
#
|
37
|
+
# This method is smart enough to not store responses that cannot be
|
38
|
+
# cached (Vary: * or Cache-Control: no-cache, private, ...)
|
39
|
+
#
|
40
|
+
# @param [Resourceful::Request] request
|
41
|
+
# The request used to obtain the response. This is needed so the
|
42
|
+
# values from the response's Vary header can be stored.
|
43
|
+
# @param [Resourceful::Response] response
|
44
|
+
# The response to be stored.
|
45
|
+
def store(request, response)
|
46
|
+
return unless response.cachable?
|
47
|
+
|
48
|
+
@memcache[request.to_mc_key] = returning(cache_entries_for(request)) do |entries|
|
49
|
+
entries[request] = response
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Invalidates a all cached entries for a uri.
|
54
|
+
#
|
55
|
+
# This is used, for example, to invalidate the cache for a resource
|
56
|
+
# that gets POSTed to.
|
57
|
+
#
|
58
|
+
# @param [String] uri
|
59
|
+
# The uri of the resource to be invalidated
|
60
|
+
def invalidate(uri)
|
61
|
+
@memcache.delete(Digest::MD5.hexdigest(uri))
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
##
|
68
|
+
# The memcache proxy.
|
69
|
+
attr_reader :memcache
|
70
|
+
|
71
|
+
def cache_entries_for(a_request)
|
72
|
+
@memcache.get(a_request.to_mc_key) || Resourceful::CacheEntryCollection.new
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module MemCacheKey
|
77
|
+
def to_mc_key
|
78
|
+
Digest::MD5.hexdigest(uri)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Request
|
83
|
+
include MemCacheKey
|
84
|
+
end
|
85
|
+
end
|
data/lib/resourceful/request.rb
CHANGED
@@ -12,11 +12,21 @@ module Resourceful
|
|
12
12
|
attr_accessor :method, :resource, :body, :header
|
13
13
|
attr_reader :request_time
|
14
14
|
|
15
|
+
# @param [Symbol] http_method
|
16
|
+
# :get, :put, :post, :delete or :head
|
17
|
+
# @param [Resourceful::Resource] resource
|
18
|
+
# @param [String] body
|
19
|
+
# @param [Resourceful::Header, Hash] header
|
15
20
|
def initialize(http_method, resource, body = nil, header = nil)
|
16
21
|
@method, @resource, @body = http_method, resource, body
|
17
22
|
@header = header.is_a?(Resourceful::Header) ? header : Resourceful::Header.new(header || {})
|
18
23
|
|
19
24
|
@header['Accept-Encoding'] = 'gzip, identity'
|
25
|
+
# 'Host' is a required HTTP/1.1 header, so set it if it isn't already
|
26
|
+
@header['Host'] ||= Addressable::URI.parse(resource.uri).host
|
27
|
+
|
28
|
+
# Setting the date isn't a bad idea, either
|
29
|
+
@header['Date'] ||= Time.now.httpdate
|
20
30
|
end
|
21
31
|
|
22
32
|
def response
|
@@ -24,6 +34,7 @@ module Resourceful
|
|
24
34
|
|
25
35
|
http_resp = NetHttpAdapter.make_request(@method, @resource.uri, @body, @header)
|
26
36
|
response = Resourceful::Response.new(uri, *http_resp)
|
37
|
+
response.request_time = @request_time
|
27
38
|
|
28
39
|
response.authoritative = true
|
29
40
|
response
|
@@ -44,6 +55,7 @@ module Resourceful
|
|
44
55
|
@header['Cache-Control'] = 'max-age=0' if response.header.has_key?('Cache-Control') and response.header['Cache-Control'].include?('must-revalidate')
|
45
56
|
end
|
46
57
|
|
58
|
+
# @return [String] The URI against which this request will be, or was, made.
|
47
59
|
def uri
|
48
60
|
resource.uri
|
49
61
|
end
|
data/lib/resourceful/resource.rb
CHANGED
@@ -10,8 +10,6 @@ module Resourceful
|
|
10
10
|
attr_reader :http_response, :http_request
|
11
11
|
|
12
12
|
# Initialize new error from the HTTP request and response attributes.
|
13
|
-
#--
|
14
|
-
# @private
|
15
13
|
def initialize(http_request, http_response)
|
16
14
|
super("#{http_request.method} request to <#{http_request.uri}> failed with code #{http_response.code}")
|
17
15
|
@http_request = http_request
|
@@ -21,6 +19,7 @@ module Resourceful
|
|
21
19
|
|
22
20
|
class Resource
|
23
21
|
attr_reader :accessor
|
22
|
+
attr_accessor :default_options
|
24
23
|
|
25
24
|
# Build a new resource for a uri
|
26
25
|
#
|
@@ -28,8 +27,9 @@ module Resourceful
|
|
28
27
|
# The parent http accessor
|
29
28
|
# @param uri<String, Addressable::URI>
|
30
29
|
# The uri for the location of the resource
|
31
|
-
def initialize(accessor, uri)
|
30
|
+
def initialize(accessor, uri, options = {})
|
32
31
|
@accessor, @uris = accessor, [uri]
|
32
|
+
@default_options = options
|
33
33
|
@on_redirect = nil
|
34
34
|
end
|
35
35
|
|
@@ -153,17 +153,17 @@ module Resourceful
|
|
153
153
|
# success, ie the final request returned a 2xx response code
|
154
154
|
#
|
155
155
|
def do_read_request(method, header = {})
|
156
|
-
request = Resourceful::Request.new(method, self, nil, header)
|
156
|
+
request = Resourceful::Request.new(method, self, nil, default_options.merge(header))
|
157
157
|
accessor.auth_manager.add_credentials(request)
|
158
158
|
|
159
159
|
cached_response = accessor.cache_manager.lookup(request)
|
160
160
|
if cached_response
|
161
|
-
logger.
|
161
|
+
logger.info(" Retrieved from cache")
|
162
162
|
if not cached_response.stale?
|
163
163
|
# We're done!
|
164
164
|
return cached_response
|
165
165
|
else
|
166
|
-
logger.
|
166
|
+
logger.info(" Cache entry is stale")
|
167
167
|
request.set_validation_headers(cached_response)
|
168
168
|
end
|
169
169
|
end
|
@@ -171,7 +171,9 @@ module Resourceful
|
|
171
171
|
response = request.response
|
172
172
|
|
173
173
|
if response.is_not_modified?
|
174
|
-
|
174
|
+
logger.info(" Resource not modified")
|
175
|
+
cached_response.header.merge!(response.header)
|
176
|
+
cached_response.request_time = response.request_time
|
175
177
|
response = cached_response
|
176
178
|
response.authoritative = true
|
177
179
|
end
|
@@ -179,9 +181,11 @@ module Resourceful
|
|
179
181
|
if response.is_redirect? and request.should_be_redirected?
|
180
182
|
if response.is_permanent_redirect?
|
181
183
|
@uris.unshift response.header['Location'].first
|
184
|
+
logger.info(" Permanently redirected to #{uri} - Storing new location.")
|
182
185
|
response = do_read_request(method, header)
|
183
186
|
else
|
184
187
|
redirected_resource = Resourceful::Resource.new(self.accessor, response.header['Location'].first)
|
188
|
+
logger.info(" Redirected to #{redirected_resource.uri} - Storing new location.")
|
185
189
|
response = redirected_resource.do_read_request(method, header)
|
186
190
|
end
|
187
191
|
end
|
@@ -189,7 +193,7 @@ module Resourceful
|
|
189
193
|
if response.is_not_authorized? && !@already_tried_with_auth
|
190
194
|
@already_tried_with_auth = true
|
191
195
|
accessor.auth_manager.associate_auth_info(response)
|
192
|
-
logger.
|
196
|
+
logger.info("Authentication Required. Retrying with auth info")
|
193
197
|
response = do_read_request(method, header)
|
194
198
|
end
|
195
199
|
|
@@ -214,7 +218,7 @@ module Resourceful
|
|
214
218
|
# @raise [UnsuccessfulHttpRequestError] unless the request is a
|
215
219
|
# success, ie the final request returned a 2xx response code
|
216
220
|
def do_write_request(method, data = nil, header = {})
|
217
|
-
request = Resourceful::Request.new(method, self, data, header)
|
221
|
+
request = Resourceful::Request.new(method, self, data, default_options.merge(header))
|
218
222
|
accessor.auth_manager.add_credentials(request)
|
219
223
|
|
220
224
|
response = request.response
|
data/lib/resourceful/response.rb
CHANGED
@@ -113,7 +113,8 @@ module Resourceful
|
|
113
113
|
if header['Expire']
|
114
114
|
return true if Time.httpdate(header['Expire'].first) < Time.now
|
115
115
|
end
|
116
|
-
if header['Cache-Control'] and header['Cache-Control'].include?('max-age')
|
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
|
117
118
|
return true if current_age > max_age
|
118
119
|
end
|
119
120
|
|
data/resourceful.gemspec
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{resourceful}
|
5
|
+
s.version = "0.3.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Paul Sadauskas"]
|
9
|
+
s.date = %q{2008-12-05}
|
10
|
+
s.description = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
|
11
|
+
s.email = %q{psadauskas@gmail.com}
|
12
|
+
s.extra_rdoc_files = ["lib/resourceful.rb", "lib/resourceful/authentication_manager.rb", "lib/resourceful/util.rb", "lib/resourceful/resource.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/header.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/options_interpreter.rb", "lib/resourceful/response.rb", "lib/resourceful/request.rb", "README.markdown"]
|
13
|
+
s.files = ["lib/resourceful.rb", "lib/resourceful/authentication_manager.rb", "lib/resourceful/util.rb", "lib/resourceful/resource.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/header.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/options_interpreter.rb", "lib/resourceful/response.rb", "lib/resourceful/request.rb", "spec/acceptance_shared_specs.rb", "spec/spec.opts", "spec/acceptance_spec.rb", "spec/simple_http_server_shared_spec_spec.rb", "spec/spec_helper.rb", "spec/resourceful/header_spec.rb", "spec/resourceful/authentication_manager_spec.rb", "spec/resourceful/memcache_cache_manager_spec.rb", "spec/resourceful/response_spec.rb", "spec/resourceful/options_interpreter_spec.rb", "spec/resourceful/http_accessor_spec.rb", "spec/resourceful/stubbed_resource_proxy_spec.rb", "spec/resourceful/request_spec.rb", "spec/resourceful/resource_spec.rb", "spec/resourceful/cache_manager_spec.rb", "spec/resourceful/net_http_adapter_spec.rb", "spec/simple_http_server_shared_spec.rb", "Manifest", "Rakefile", "README.markdown", "MIT-LICENSE", "resourceful.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://github.com/paul/resourceful}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Resourceful", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{resourceful}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
s.add_runtime_dependency(%q<addressable>, [">= 0"])
|
28
|
+
s.add_runtime_dependency(%q<httpauth>, [">= 0"])
|
29
|
+
s.add_runtime_dependency(%q<rspec>, [">= 0"])
|
30
|
+
s.add_runtime_dependency(%q<facets>, [">= 0"])
|
31
|
+
s.add_runtime_dependency(%q<andand>, [">= 0"])
|
32
|
+
s.add_development_dependency(%q<thin>, [">= 0"])
|
33
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<addressable>, [">= 0"])
|
36
|
+
s.add_dependency(%q<httpauth>, [">= 0"])
|
37
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
38
|
+
s.add_dependency(%q<facets>, [">= 0"])
|
39
|
+
s.add_dependency(%q<andand>, [">= 0"])
|
40
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
41
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
42
|
+
end
|
43
|
+
else
|
44
|
+
s.add_dependency(%q<addressable>, [">= 0"])
|
45
|
+
s.add_dependency(%q<httpauth>, [">= 0"])
|
46
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
47
|
+
s.add_dependency(%q<facets>, [">= 0"])
|
48
|
+
s.add_dependency(%q<andand>, [">= 0"])
|
49
|
+
s.add_dependency(%q<thin>, [">= 0"])
|
50
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
51
|
+
end
|
52
|
+
end
|
data/spec/acceptance_spec.rb
CHANGED
@@ -8,7 +8,7 @@ require Pathname(__FILE__).dirname + 'acceptance_shared_specs'
|
|
8
8
|
describe Resourceful do
|
9
9
|
it_should_behave_like 'simple http server'
|
10
10
|
|
11
|
-
describe '
|
11
|
+
describe 'working with a resource' do
|
12
12
|
before do
|
13
13
|
@accessor = Resourceful::HttpAccessor.new
|
14
14
|
end
|
@@ -61,6 +61,38 @@ describe Resourceful do
|
|
61
61
|
resp.header['Content-Type'].should == ['text/plain']
|
62
62
|
end
|
63
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
|
+
|
64
96
|
describe 'redirecting' do
|
65
97
|
|
66
98
|
describe 'registering callback' do
|
@@ -246,6 +278,19 @@ describe Resourceful do
|
|
246
278
|
resp2.authoritative?.should be_true
|
247
279
|
end
|
248
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
|
+
|
249
294
|
it 'should cache but revalidate anything with "Cache-Control: must-revalidate"' do
|
250
295
|
uri = URI.escape('http://localhost:3000/header?{Cache-Control: must-revalidate}')
|
251
296
|
resource = @accessor.resource(uri)
|
@@ -166,6 +166,14 @@ end
|
|
166
166
|
describe Resourceful::DigestAuthenticator do
|
167
167
|
|
168
168
|
before do
|
169
|
+
@header = {'WWW-Authenticate' => ['Digest realm="Test Auth"']}
|
170
|
+
@chal = mock('response', :header => @header, :uri => 'http://example.com/foo/bar')
|
171
|
+
|
172
|
+
@req_header = {}
|
173
|
+
@req = mock('request', :header => @req_header,
|
174
|
+
:uri => 'http://example.com',
|
175
|
+
:method => 'GET')
|
176
|
+
|
169
177
|
@auth = Resourceful::DigestAuthenticator.new('Test Auth', 'admin', 'secret')
|
170
178
|
end
|
171
179
|
|
@@ -175,12 +183,21 @@ describe Resourceful::DigestAuthenticator do
|
|
175
183
|
end
|
176
184
|
end
|
177
185
|
|
178
|
-
describe "Updating from a challenge response" do
|
179
|
-
|
180
|
-
|
181
|
-
@
|
186
|
+
describe "Updating credentials from a challenge response" do
|
187
|
+
|
188
|
+
it "should set the domain from the host part of the challenge response uri" do
|
189
|
+
@auth.update_credentials(@chal)
|
190
|
+
@auth.domain.should == 'example.com'
|
182
191
|
end
|
183
192
|
|
193
|
+
it "should create an HTTPAuth Digest Challenge from the challenge response WWW-Authenticate header" do
|
194
|
+
HTTPAuth::Digest::Challenge.should_receive(:from_header).with(@header['WWW-Authenticate'].first)
|
195
|
+
@auth.update_credentials(@chal)
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "Validating a challenge" do
|
184
201
|
it 'should be valid for a challenge response with scheme "Digest" and the same realm' do
|
185
202
|
@auth.valid_for?(@chal).should be_true
|
186
203
|
end
|
@@ -201,4 +218,32 @@ describe Resourceful::DigestAuthenticator do
|
|
201
218
|
end
|
202
219
|
end
|
203
220
|
|
221
|
+
it "should be able to handle requests to the same domain" do
|
222
|
+
@auth.instance_variable_set("@domain", 'example.com')
|
223
|
+
@auth.can_handle?(@req).should be_true
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should not handle requests to a different domain" do
|
227
|
+
@auth.instance_variable_set("@domain", 'example2.com')
|
228
|
+
@auth.can_handle?(@req).should be_false
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should add credentials to a request" do
|
232
|
+
@auth.update_credentials(@chal)
|
233
|
+
@auth.add_credentials_to(@req)
|
234
|
+
@req_header.should have_key('Authorization')
|
235
|
+
@req_header['Authorization'].should_not be_blank
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should have HTTPAuth::Digest generate the Authorization header" do
|
239
|
+
@auth.update_credentials(@chal)
|
240
|
+
cred = mock('digest_credentials', :to_header => nil)
|
241
|
+
|
242
|
+
HTTPAuth::Digest::Credentials.should_receive(:from_challenge).with(
|
243
|
+
@auth.challenge, :username => 'admin', :password => 'secret', :method => 'GET', :uri => ''
|
244
|
+
).and_return(cred)
|
245
|
+
|
246
|
+
cred = @auth.credentials_for(@req)
|
247
|
+
end
|
248
|
+
|
204
249
|
end
|