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