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