paul-resourceful 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +21 -0
- data/Manifest.txt +34 -0
- data/README.markdown +86 -0
- data/Rakefile +14 -0
- data/lib/resourceful.rb +29 -0
- data/lib/resourceful/authentication_manager.rb +107 -0
- data/lib/resourceful/cache_manager.rb +174 -0
- data/lib/resourceful/header.rb +31 -0
- data/lib/resourceful/http_accessor.rb +85 -0
- data/lib/resourceful/net_http_adapter.rb +60 -0
- data/lib/resourceful/options_interpreter.rb +78 -0
- data/lib/resourceful/request.rb +63 -0
- data/lib/resourceful/resource.rb +266 -0
- data/lib/resourceful/response.rb +175 -0
- data/lib/resourceful/stubbed_resource_proxy.rb +47 -0
- data/lib/resourceful/util.rb +6 -0
- data/lib/resourceful/version.rb +1 -0
- data/resourceful.gemspec +30 -0
- data/spec/acceptance_shared_specs.rb +49 -0
- data/spec/acceptance_spec.rb +408 -0
- data/spec/resourceful/authentication_manager_spec.rb +249 -0
- data/spec/resourceful/cache_manager_spec.rb +211 -0
- data/spec/resourceful/header_spec.rb +38 -0
- data/spec/resourceful/http_accessor_spec.rb +125 -0
- data/spec/resourceful/net_http_adapter_spec.rb +96 -0
- data/spec/resourceful/options_interpreter_spec.rb +94 -0
- data/spec/resourceful/request_spec.rb +186 -0
- data/spec/resourceful/resource_spec.rb +600 -0
- data/spec/resourceful/response_spec.rb +238 -0
- data/spec/resourceful/stubbed_resource_proxy_spec.rb +58 -0
- data/spec/simple_http_server_shared_spec.rb +160 -0
- data/spec/simple_http_server_shared_spec_spec.rb +212 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +14 -0
- metadata +98 -0
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname + '../spec_helper'
|
3
|
+
|
4
|
+
require 'resourceful/authentication_manager'
|
5
|
+
|
6
|
+
describe Resourceful::AuthenticationManager do
|
7
|
+
|
8
|
+
before do
|
9
|
+
@authmgr = Resourceful::AuthenticationManager.new
|
10
|
+
|
11
|
+
@authenticator = mock('Authenticator')
|
12
|
+
end
|
13
|
+
|
14
|
+
[:add_auth_handler, :associate_auth_info, :add_credentials].each do |meth|
|
15
|
+
it "should have ##{meth}" do
|
16
|
+
@authmgr.should respond_to(meth)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should add an authenticator to its list' do
|
21
|
+
@authmgr.add_auth_handler(@authenticator)
|
22
|
+
@authmgr.instance_variable_get("@authenticators").should include(@authenticator)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'associating authenticators with challanges' do
|
26
|
+
before do
|
27
|
+
@authmgr.add_auth_handler(@authenticator)
|
28
|
+
@authenticator.stub!(:valid_for?).and_return(true)
|
29
|
+
@authenticator.stub!(:update_credentials)
|
30
|
+
@challenge = mock('Response')
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should check that an authenticator is valid for a challenge' do
|
34
|
+
@authenticator.should_receive(:valid_for?).with(@challenge).and_return(true)
|
35
|
+
@authmgr.associate_auth_info(@challenge)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should update the credentials of the authenticator if it is valid for the challenge' do
|
39
|
+
@authenticator.should_receive(:update_credentials).with(@challenge)
|
40
|
+
@authmgr.associate_auth_info(@challenge)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should not update the credentials of the authenticator if it is not valid for the challenge' do
|
44
|
+
@authenticator.stub!(:valid_for?).and_return(false)
|
45
|
+
@authenticator.should_not_receive(:update_credentials).with(@challenge)
|
46
|
+
@authmgr.associate_auth_info(@challenge)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'adding credentials to a request' do
|
52
|
+
before do
|
53
|
+
@authmgr.add_auth_handler(@authenticator)
|
54
|
+
@authenticator.stub!(:can_handle?).and_return(true)
|
55
|
+
@authenticator.stub!(:add_credentials_to)
|
56
|
+
@request = mock('Request')
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should find an authenticator that can handle the request' do
|
60
|
+
@authenticator.should_receive(:can_handle?).with(@request).and_return(true)
|
61
|
+
@authmgr.add_credentials(@request)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should add the authenticators credentials to the request' do
|
65
|
+
@authenticator.should_receive(:add_credentials_to).with(@request)
|
66
|
+
@authmgr.add_credentials(@request)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not add the authenticators credentials to the request if it cant handle it' do
|
70
|
+
@authenticator.should_receive(:can_handle?).with(@request).and_return(false)
|
71
|
+
@authenticator.should_not_receive(:add_credentials_to).with(@request)
|
72
|
+
@authmgr.add_credentials(@request)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
describe Resourceful::BasicAuthenticator do
|
80
|
+
before do
|
81
|
+
@auth = Resourceful::BasicAuthenticator.new('Test Auth', 'admin', 'secret')
|
82
|
+
end
|
83
|
+
|
84
|
+
{:realm => 'Test Auth', :username => 'admin', :password => 'secret'}.each do |meth,val|
|
85
|
+
it "should initialize with a #{meth}" do
|
86
|
+
@auth.instance_variable_get("@#{meth}").should == val
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "Updating from a challenge response" do
|
91
|
+
before do
|
92
|
+
@header = {'WWW-Authenticate' => ['Basic realm="Test Auth"']}
|
93
|
+
@chal = mock('response', :header => @header, :uri => 'http://example.com/foo/bar')
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should be valid for a challenge response with scheme "Basic" and the same realm' do
|
97
|
+
@auth.valid_for?(@chal).should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should be valid for a challenge response with multiple schemes including matchin "Basic" challenge' do
|
101
|
+
@header = {'WWW-Authenticate' => ['Digest some other stuff', 'Basic realm="Test Auth"', 'Weird scheme']}
|
102
|
+
|
103
|
+
@auth.valid_for?(@chal).should be_true
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should not be sensitive to case variances in the scheme' do
|
107
|
+
@header['WWW-Authenticate'] = ['bAsIc realm="Test Auth"']
|
108
|
+
@auth.valid_for?(@chal).should be_true
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should not be sensitive to case variances in the realm directive' do
|
112
|
+
@header['WWW-Authenticate'] = ['Basic rEaLm="Test Auth"']
|
113
|
+
@auth.valid_for?(@chal).should be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should not be sensitive to case variances in the realm value' do
|
117
|
+
@header['WWW-Authenticate'] = ['Basic realm="test auth"']
|
118
|
+
@auth.valid_for?(@chal).should be_true
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should not be valid if the scheme is not "Basic"' do
|
122
|
+
@header['WWW-Authenticate'] = ["Digest"]
|
123
|
+
@auth.valid_for?(@chal).should be_false
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should not be valid if the realm does not match' do
|
127
|
+
@header['WWW-Authenticate'] = ['Basic realm="not test auth"']
|
128
|
+
@auth.valid_for?(@chal).should be_false
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'should not be valid if the header is unreadable' do
|
132
|
+
@header['WWW-Authenticate'] = nil
|
133
|
+
@auth.valid_for?(@chal).should be_false
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should set the valid domain from the host part of the challenge uri' do
|
137
|
+
@auth.update_credentials(@chal)
|
138
|
+
@auth.instance_variable_get("@domain").should == 'example.com'
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'updating a request with credentials' do
|
144
|
+
before do
|
145
|
+
@auth.instance_variable_set("@domain", 'example.com')
|
146
|
+
@header = {}
|
147
|
+
@req = mock('request', :uri => 'http://example.com/bar/foo', :header => @header)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should be able to handle a request for the matching domain' do
|
151
|
+
@auth.can_handle?(@req).should be_true
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'should add credentials to a request' do
|
155
|
+
@header.should_receive(:[]=).with('Authorization', 'Basic YWRtaW46c2VjcmV0')
|
156
|
+
@auth.add_credentials_to(@req)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should build the credentials string for the header' do
|
160
|
+
@auth.credentials.should == 'Basic YWRtaW46c2VjcmV0'
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
describe Resourceful::DigestAuthenticator do
|
167
|
+
|
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
|
+
|
177
|
+
@auth = Resourceful::DigestAuthenticator.new('Test Auth', 'admin', 'secret')
|
178
|
+
end
|
179
|
+
|
180
|
+
{:realm => 'Test Auth', :username => 'admin', :password => 'secret'}.each do |meth,val|
|
181
|
+
it "should initialize with a #{meth}" do
|
182
|
+
@auth.instance_variable_get("@#{meth}").should == val
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
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'
|
191
|
+
end
|
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
|
201
|
+
it 'should be valid for a challenge response with scheme "Digest" and the same realm' do
|
202
|
+
@auth.valid_for?(@chal).should be_true
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should not be valid if the scheme is not "Digest"' do
|
206
|
+
@header['WWW-Authenticate'] = ["Basic"]
|
207
|
+
@auth.valid_for?(@chal).should be_false
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'should not be valid if the realm does not match' do
|
211
|
+
@header['WWW-Authenticate'] = ['Digest realm="not test auth"']
|
212
|
+
@auth.valid_for?(@chal).should be_false
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'should not be valid if the header is unreadable' do
|
216
|
+
@header['WWW-Authenticate'] = nil
|
217
|
+
@auth.valid_for?(@chal).should be_false
|
218
|
+
end
|
219
|
+
end
|
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
|
+
|
249
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require Pathname(__FILE__).dirname + '../spec_helper'
|
3
|
+
|
4
|
+
require 'resourceful/cache_manager'
|
5
|
+
|
6
|
+
describe Resourceful::CacheManager do
|
7
|
+
before do
|
8
|
+
@cm = Resourceful::InMemoryCacheManager.new #cheat, because I cant new a real one.
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be initializable' do
|
12
|
+
lambda { Resourceful::CacheManager.new }.should raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have a lookup method' do
|
16
|
+
@cm.should respond_to(:lookup)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should have a store method' do
|
20
|
+
@cm.should respond_to(:store)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should have a invalidate method' do
|
24
|
+
@cm.should respond_to(:invalidate)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#select_request_headers' do
|
28
|
+
before do
|
29
|
+
@req_header = mock('header', :[] => nil)
|
30
|
+
@request = mock('request', :header => @req_header)
|
31
|
+
|
32
|
+
@resp_header = mock('header', :[] => nil)
|
33
|
+
@response = mock('response', :header => @resp_header)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should select the request headers from the Vary header' do
|
37
|
+
@resp_header.should_receive(:[]).with('Vary')
|
38
|
+
@cm.select_request_headers(@request, @response)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should pull the values from the request that match keys in the vary header' do
|
42
|
+
@resp_header.should_receive(:[]).with('Vary').twice.and_return(['foo, bar'])
|
43
|
+
@req_header.should_receive(:[]).with('foo').and_return('oof')
|
44
|
+
@req_header.should_receive(:[]).with('bar').and_return('rab')
|
45
|
+
|
46
|
+
header = @cm.select_request_headers(@request, @response)
|
47
|
+
header['foo'].should == 'oof'
|
48
|
+
header['bar'].should == 'rab'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should return a new Header object' do
|
52
|
+
@cm.select_request_headers(@request, @response).should be_kind_of(Resourceful::Header)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
describe Resourceful::NullCacheManager do
|
59
|
+
before do
|
60
|
+
@ncm = Resourceful::NullCacheManager.new
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should not find anything' do
|
64
|
+
@ncm.lookup(:stuff).should be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should not store anything' do
|
68
|
+
@ncm.should respond_to(:store)
|
69
|
+
|
70
|
+
lambda { @ncm.store(:foo, :bar) }.should_not raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
describe Resourceful::InMemoryCacheManager do
|
76
|
+
before do
|
77
|
+
@request = mock('request', :resource => mock('resource'),
|
78
|
+
:request_time => Time.utc(2008,5,22,15,00),
|
79
|
+
:uri => 'uri')
|
80
|
+
@response = mock('response', :header => {}, :cachable? => true)
|
81
|
+
|
82
|
+
@entry = mock('cache entry', :response => @response, :valid_for? => true)
|
83
|
+
Resourceful::InMemoryCacheManager::CacheEntry.stub!(:new).and_return(@entry)
|
84
|
+
|
85
|
+
@imcm = Resourceful::InMemoryCacheManager.new
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'finding' do
|
89
|
+
before do
|
90
|
+
@response.stub!(:authoritative=)
|
91
|
+
@imcm.instance_variable_set("@collection", {'uri' => {@request => @entry}})
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should lookup the response by request' do
|
95
|
+
@imcm.lookup(@request).should == @response
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should set the response to non-authoritative' do
|
99
|
+
@response.should_receive(:authoritative=).with(false)
|
100
|
+
@imcm.lookup(@request)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'saving' do
|
105
|
+
it 'should make a new cache entry' do
|
106
|
+
Resourceful::InMemoryCacheManager::CacheEntry.should_receive(:new).with(
|
107
|
+
Time.utc(2008,5,22,15,00),
|
108
|
+
{},
|
109
|
+
@response
|
110
|
+
)
|
111
|
+
|
112
|
+
@imcm.store(@request, @response)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should store the response entity by request' do
|
116
|
+
@imcm.store(@request, @response)
|
117
|
+
col = @imcm.instance_variable_get("@collection")
|
118
|
+
col['uri'][@request].response.should == @response
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should check if the response is cachable' do
|
122
|
+
@response.should_receive(:cachable?).and_return(true)
|
123
|
+
@imcm.store(@request, @response)
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should not store an entry if the response is not cachable' do
|
127
|
+
@response.should_receive(:cachable?).and_return(false)
|
128
|
+
@imcm.store(@request, @response)
|
129
|
+
col = @imcm.instance_variable_get("@collection")
|
130
|
+
col['uri'][@request].should be_nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe 'invalidating' do
|
135
|
+
it 'should remove an entry from the cache by uri' do
|
136
|
+
@imcm.store(@request, @response)
|
137
|
+
@imcm.invalidate('uri')
|
138
|
+
col = @imcm.instance_variable_get("@collection")
|
139
|
+
col.should_not have_key('uri')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
describe Resourceful::InMemoryCacheManager::CacheEntryCollection do
|
146
|
+
before do
|
147
|
+
@entry_valid = mock('entry', :valid_for? => true)
|
148
|
+
@entry_invalid = mock('entry', :valid_for? => false)
|
149
|
+
|
150
|
+
@request = mock('request')
|
151
|
+
|
152
|
+
@collection = Resourceful::InMemoryCacheManager::CacheEntryCollection.new
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should find the right entry for a request' do
|
156
|
+
@collection.instance_variable_set('@entries', [@entry_valid, @entry_invalid])
|
157
|
+
@entry_valid.should_receive(:valid_for?).with(@request).and_return(true)
|
158
|
+
@collection[@request].should == @entry_valid
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should be nil if no matching entry was found' do
|
162
|
+
@collection.instance_variable_set('@entries', [@entry_invalid])
|
163
|
+
@entry_invalid.should_receive(:valid_for?).with(@request).and_return(false)
|
164
|
+
@collection[@request].should == nil
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'should store an entry' do
|
168
|
+
@collection[@request] = @entry_valid
|
169
|
+
@collection.instance_variable_get("@entries").should include(@entry_valid)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should replace an existing entry if the existing entry matches the request' do
|
173
|
+
@new_entry = mock('entry', :valid_for? => true)
|
174
|
+
|
175
|
+
@collection[@request] = @entry_valid
|
176
|
+
@collection[@request] = @new_entry
|
177
|
+
|
178
|
+
@collection.instance_variable_get("@entries").should include(@new_entry)
|
179
|
+
@collection.instance_variable_get("@entries").should_not include(@entry_valid)
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
describe Resourceful::InMemoryCacheManager::CacheEntry do
|
185
|
+
before do
|
186
|
+
@entry = Resourceful::InMemoryCacheManager::CacheEntry.new(
|
187
|
+
Time.utc(2008,5,16,0,0,0), {'Content-Type' => 'text/plain'}, mock('response')
|
188
|
+
)
|
189
|
+
|
190
|
+
@request = mock('request')
|
191
|
+
end
|
192
|
+
|
193
|
+
[:request_time, :request_vary_headers, :response, :valid_for?].each do |method|
|
194
|
+
it "should respond to ##{method}" do
|
195
|
+
@entry.should respond_to(method)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should be valid for a request if all the vary headers match' do
|
200
|
+
@request.stub!(:header).and_return({'Content-Type' => 'text/plain'})
|
201
|
+
@entry.valid_for?(@request).should be_true
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should not be valid for a request if not all the vary headers match' do
|
205
|
+
@request.stub!(:header).and_return({'Content-Type' => 'text/html'})
|
206
|
+
@entry.valid_for?(@request).should be_false
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
|