paul-resourceful 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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,238 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + '../spec_helper'
3
+
4
+ require 'resourceful/response'
5
+
6
+ describe Resourceful::Response do
7
+ before do
8
+ @net_http = mock('net_http')
9
+ Net::HTTP::Get.stub!(:new).and_return(@net_http)
10
+ @uri = 'http://www.example.com'
11
+
12
+ @response = Resourceful::Response.new(@uri, 0, {}, "")
13
+ end
14
+
15
+ describe 'init' do
16
+
17
+ it 'should be instantiatable' do
18
+ @response.should be_instance_of(Resourceful::Response)
19
+ end
20
+
21
+ it 'should take a [uri, code, header, body] array' do
22
+ r = Resourceful::Response.new(@uri, 200, {}, "")
23
+ r.code.should == 200
24
+ r.header.should == {}
25
+ r.body.should == ""
26
+ end
27
+
28
+ end
29
+
30
+ it 'should have a code' do
31
+ @response.should respond_to(:code)
32
+ end
33
+
34
+ it 'should have a header' do
35
+ @response.should respond_to(:header)
36
+ end
37
+
38
+ it 'should have header aliased as headers' do
39
+ @response.should respond_to(:headers)
40
+ @response.headers.should == @response.header
41
+ end
42
+
43
+ describe "#is_success?" do
44
+ it 'should be true for 200' do
45
+ Resourceful::Response.new(@uri, 200, {}, "").is_success?.should == true
46
+ end
47
+
48
+ it 'should be true for any 2xx' do
49
+ Resourceful::Response.new(@uri, 299, {}, "").is_success?.should == true
50
+ end
51
+
52
+ it 'should not be true for 300' do
53
+ Resourceful::Response.new(@uri, 300, {}, "").is_success?.should == false
54
+ end
55
+
56
+ it 'should not be true for 199' do
57
+ Resourceful::Response.new(@uri, 199, {}, "").is_success?.should == false
58
+ end
59
+ end
60
+
61
+ describe '#is_unsuccesful?' do
62
+ it 'should be true for a 4xx series response code' do
63
+ Resourceful::Response.new(@uri, 404, {}, "").is_unsuccesful?.should == true
64
+ end
65
+
66
+ it 'should be true for a 5xx series response code' do
67
+ Resourceful::Response.new(@uri, 500, {}, "").is_unsuccesful?.should == true
68
+ end
69
+
70
+ it 'should be not true for a 2xx series response code' do
71
+ Resourceful::Response.new(@uri, 200, {}, "").is_unsuccesful?.should == false
72
+ end
73
+
74
+ it 'should be true for a 3xx series response code' do
75
+ Resourceful::Response.new(@uri, 302, {}, "").is_unsuccesful?.should == true
76
+ end
77
+ end
78
+
79
+ describe '#is_client_error?' do
80
+ it 'be true for a 4xx series response code' do
81
+ Resourceful::Response.new(@uri, 404, {}, "").is_client_error?.should == true
82
+ end
83
+
84
+ it 'be false for anything else' do
85
+ Resourceful::Response.new(@uri, 200, {}, "").is_client_error?.should == false
86
+ end
87
+ end
88
+
89
+ describe '#is_server_error?' do
90
+ it 'be true for a 5xx series response code' do
91
+ Resourceful::Response.new(@uri, 500, {}, "").is_server_error?.should == true
92
+ end
93
+
94
+ it 'be false for anything else' do
95
+ Resourceful::Response.new(@uri, 200, {}, "").is_server_error?.should == false
96
+ end
97
+ end
98
+
99
+
100
+ it 'should know if it is a redirect' do
101
+ Resourceful::Response.new(@uri, 301, {}, "").is_redirect?.should == true
102
+ Resourceful::Response.new(@uri, 302, {}, "").is_redirect?.should == true
103
+ Resourceful::Response.new(@uri, 303, {}, "").is_redirect?.should == true
104
+ Resourceful::Response.new(@uri, 307, {}, "").is_redirect?.should == true
105
+
106
+ #aliased as was_redirect?
107
+ Resourceful::Response.new(@uri, 301, {}, "").was_redirect?.should == true
108
+ end
109
+
110
+ it 'should know if it is a permanent redirect' do
111
+ Resourceful::Response.new(@uri, 301, {}, "").is_permanent_redirect?.should == true
112
+ end
113
+
114
+ it 'should know if it is a temporary redirect' do
115
+ Resourceful::Response.new(@uri, 303, {}, "").is_temporary_redirect?.should == true
116
+ end
117
+
118
+ it 'should know if its authoritative' do
119
+ @response.should respond_to(:authoritative?)
120
+ end
121
+
122
+ it 'should allow authoritative to be set' do
123
+ @response.authoritative = true
124
+ @response.authoritative?.should be_true
125
+ end
126
+
127
+ it 'should know if it is authorized' do
128
+ @response.should respond_to(:is_not_authorized?)
129
+ Resourceful::Response.new(@uri, 200, {}, "").is_not_authorized?.should == false
130
+ Resourceful::Response.new(@uri, 401, {}, "").is_not_authorized?.should == true
131
+ end
132
+
133
+ describe 'caching and expiration' do
134
+ before do
135
+ Time.stub!(:now).and_return(Time.utc(2008,5,15,18,0,1), Time.utc(2008,5,15,20,0,0))
136
+
137
+ @response = Resourceful::Response.new(@uri, 0, {'Date' => ['Thu, 15 May 2008 18:00:00 GMT']}, "")
138
+ @response.request_time = Time.utc(2008,5,15,17,59,59)
139
+ end
140
+
141
+ it 'should know if its #stale?' do
142
+ @response.should respond_to(:stale?)
143
+ end
144
+
145
+ it 'should be stale if it is expired' do
146
+ @response.should_receive(:expired?).and_return(true)
147
+ @response.should be_stale
148
+ end
149
+
150
+ it 'should know if its #expired?' do
151
+ @response.should respond_to(:expired?)
152
+ end
153
+
154
+ it 'should be expired if Now is after the "Expire" header' do
155
+ Time.stub!(:now).and_return(Time.utc(2008,5,23,18,0))
156
+ @response.header['Expire'] = [(Time.now - 60*60).httpdate]
157
+
158
+ @response.should be_expired
159
+ end
160
+
161
+ it 'should have a #current_age' do
162
+ @response.should respond_to(:current_age)
163
+ end
164
+
165
+ it 'should calculate the #current_age' do
166
+ @response.current_age.should == (2 * 60 * 60 + 2)
167
+ end
168
+
169
+ it 'should know if its #cachable?' do
170
+ @response.should respond_to(:cachable?)
171
+ end
172
+
173
+ it 'should normally be cachable' do
174
+ @response.cachable?.should be_true
175
+ end
176
+
177
+ def response_with_header(header = {})
178
+ Resourceful::Response.new(@uri, 200, header, "")
179
+ end
180
+
181
+ it 'should not be cachable if the vary header has "*"' do
182
+ r = response_with_header('Vary' => ['*'])
183
+ r.cachable?.should be_false
184
+ end
185
+
186
+ it 'should not be cachable if the Cache-Control header is set to no-store' do
187
+ r = response_with_header('Cache-Control' => ['no-store'])
188
+ r.cachable?.should be_false
189
+ end
190
+
191
+ it 'should be stale if the Cache-Control header is set to must-revalidate' do
192
+ r = response_with_header('Cache-Control' => ['must-revalidate'])
193
+ r.should be_stale
194
+ end
195
+
196
+ it 'should be stale if the Cache-Control header is set to no-cache' do
197
+ r = response_with_header('Cache-Control' => ['no-cache'])
198
+ r.should be_stale
199
+ end
200
+ end
201
+
202
+
203
+ describe '#body' do
204
+ it 'should have a body method' do
205
+ @response.should respond_to(:body)
206
+ end
207
+
208
+ require 'zlib'
209
+ ['gzip', ' gzip', ' gzip ', 'GZIP', 'gzIP'].each do |gzip|
210
+ it "ungzip the body if content-encoding header field is #{gzip}" do
211
+ compressed_date = StringIO.new.tap do |out|
212
+ Zlib::GzipWriter.new(out).tap do |zout|
213
+ zout << "This is a test"
214
+ zout.close
215
+ end
216
+ end.string
217
+
218
+ @response = Resourceful::Response.new(@uri, 0, {'Content-Encoding' => [gzip]}, compressed_date)
219
+
220
+ @response.body.should == "This is a test"
221
+ end
222
+ end
223
+
224
+ it 'should leave body unmolested if Content-Encoding missing' do
225
+ @response = Resourceful::Response.new(@uri, 0, {}, "This is a test")
226
+ @response.body.should == "This is a test"
227
+ end
228
+
229
+ it 'should raise error if Content-Encoding is not supported' do
230
+ @response = Resourceful::Response.new(@uri, 0, {'Content-Encoding' => ['broken-identity']}, "This is a test")
231
+ lambda {
232
+ @response.body
233
+ }.should raise_error(Resourceful::UnsupportedContentCoding)
234
+ end
235
+ end
236
+
237
+ end
238
+
@@ -0,0 +1,58 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + '../spec_helper'
3
+
4
+ require 'resourceful/stubbed_resource_proxy'
5
+
6
+ describe Resourceful::StubbedResourceProxy, "init" do
7
+ it 'should require real resource' do
8
+ lambda{
9
+ Resourceful::StubbedResourceProxy.new
10
+ }.should raise_error(ArgumentError)
11
+ end
12
+
13
+ it 'should require canned responses hash' do
14
+ lambda{
15
+ Resourceful::StubbedResourceProxy.new(stub('resource'))
16
+ }.should raise_error(ArgumentError)
17
+ end
18
+
19
+ it 'should be creatable with a Resource and canned responses' do
20
+ Resourceful::StubbedResourceProxy.new(stub('resource'), {})
21
+ end
22
+ end
23
+
24
+ describe Resourceful::StubbedResourceProxy do
25
+ before do
26
+ @resource = stub('resource', :effective_uri => "http://test.invalid/foo")
27
+ @stubbed_resource = Resourceful::StubbedResourceProxy.
28
+ new(@resource, [{:mime_type => 'application/xml',
29
+ :body => '<thing>1</thing>'}])
30
+ end
31
+
32
+ it 'should return canned response body for matching requests' do
33
+ resp = @stubbed_resource.get
34
+ resp.body.should == '<thing>1</thing>'
35
+ resp['content-type'].should == 'application/xml'
36
+ end
37
+
38
+ it 'should pass #get() through to base resource if no matching canned response is defined' do
39
+ @resource.should_receive(:get)
40
+ @stubbed_resource.get(:accept => 'application/unknown')
41
+ end
42
+
43
+ it 'should pass #post() through to base resource if no canned response is defined' do
44
+ @resource.should_receive(:post)
45
+ @stubbed_resource.post
46
+ end
47
+
48
+ it 'should pass #put() through to base resource if no canned response is defined' do
49
+ @resource.should_receive(:put)
50
+ @stubbed_resource.put
51
+ end
52
+
53
+ it 'should pass #effective_uri() through to base resource if no canned response is defined' do
54
+ @resource.should_receive(:effective_uri)
55
+ @stubbed_resource.effective_uri
56
+ end
57
+
58
+ end
@@ -0,0 +1,160 @@
1
+ require 'yaml'
2
+
3
+ # this sets up a very simple http server using thin to be used in specs.
4
+ SimpleGet = lambda do |env|
5
+ body = ["Hello, world!"]
6
+ [ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
7
+ end unless defined? SimpleGet
8
+
9
+ SimplePost = lambda do |env|
10
+ body = [env['rack.input'].string]
11
+ [ 201, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
12
+ end unless defined? SimplePost
13
+
14
+ SimplePut = lambda do |env|
15
+ body = [env['rack.input'].string]
16
+ [ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
17
+ end unless defined? SimplePut
18
+
19
+ SimpleDel = lambda do |env|
20
+ body = ["KABOOM!"]
21
+ [ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
22
+ end unless defined? SimpleDel
23
+
24
+ # has the method used in the body of the response
25
+ MethodResponder = lambda do |env|
26
+ body = [env['REQUEST_METHOD']]
27
+ [ 200, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
28
+ end unless defined? MethodResponder
29
+
30
+ # has a response code of whatever it was given in the url /code/{123}
31
+ CodeResponder = lambda do |env|
32
+ code = env['PATH_INFO'] =~ /([\d]+)/ ? Integer($1) : 404
33
+ body = [code.to_s]
34
+
35
+ [ code, {'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}, body ]
36
+ end unless defined? CodeResponder
37
+
38
+ # YAML-parses the query string (expected hash) and sets the header to that
39
+ HeaderResponder = lambda do |env|
40
+ header = YAML.load(URI.unescape(env['QUERY_STRING']))
41
+ body = [header.inspect]
42
+
43
+ header.merge!({
44
+ 'Content-Type' => 'text/plain',
45
+ 'Content-Length' => body.join.size.to_s
46
+ })
47
+
48
+ [ 200, header, body ]
49
+ end unless defined? HeaderResponder
50
+
51
+ # Echos the request header in the response body
52
+ EchoHeaderResponder = lambda do |env|
53
+ body = [env.inspect]
54
+
55
+ header = {
56
+ 'Content-Type' => 'text/plain',
57
+ 'Content-Length' => body.join.size.to_s
58
+ }
59
+
60
+ [ 200, header, body ]
61
+ end unless defined? EchoHeaderResponder
62
+
63
+ # redirect. /redirect/{301|302}?{url}
64
+ Redirector = lambda do |env|
65
+ code = env['PATH_INFO'] =~ /([\d]+)/ ? Integer($1) : 404
66
+ location = env['QUERY_STRING']
67
+ body = [location]
68
+
69
+ [ code, {'Content-Type' => 'text/plain', 'Location' => location, 'Content-Length' => body.join.size.to_s}, body ]
70
+ end unless defined? Redirector
71
+
72
+ # Returns 304 if 'If-Modified-Since' is after given mod time
73
+ ModifiedResponder = lambda do |env|
74
+ modtime = Time.httpdate(URI.unescape(env['QUERY_STRING']))
75
+
76
+ code = 200
77
+ if env['HTTP_IF_MODIFIED_SINCE']
78
+ code = 304
79
+ end
80
+ body = [modtime.to_s]
81
+
82
+ header = {'Content-Type' => 'text/plain',
83
+ 'Content-Length' => body.join.size.to_s,
84
+ 'Last-Modified' => modtime.httpdate,
85
+ 'Cache-Control' => 'must-revalidate'}
86
+
87
+ [ code, header, body ]
88
+ end unless defined? ModifiedResponder
89
+
90
+ require 'rubygems'
91
+ require 'httpauth'
92
+ AuthorizationResponder = lambda do |env|
93
+ authtype = env['QUERY_STRING']
94
+ header = {}
95
+ if auth_string = env['HTTP_AUTHORIZATION']
96
+ if authtype == "basic" &&
97
+ ['admin', 'secret'] == HTTPAuth::Basic.unpack_authorization(auth_string)
98
+ code = 200
99
+ body = ["Authorized"]
100
+ elsif authtype == "digest" #&&
101
+ credentials = HTTPAuth::Digest::Credentials.from_header(auth_string) &&
102
+ credentials &&
103
+ credentials.validate(:password => 'secret', :method => 'GET')
104
+ code = 200
105
+ body = ["Authorized"]
106
+ else
107
+ code = 401
108
+ body = ["Not Authorized"]
109
+ end
110
+ else
111
+ code = 401
112
+ body = ["Not Authorized"]
113
+ if authtype == "basic"
114
+ header = {'WWW-Authenticate' => HTTPAuth::Basic.pack_challenge('Test Auth')}
115
+ elsif authtype == "digest"
116
+ chal = HTTPAuth::Digest::Credentials.new(:realm => 'Test Auth', :qop => 'auth')
117
+ header = {'WWW-Authenticate' => chal.to_header}
118
+ end
119
+ end
120
+
121
+ [ code, header.merge({'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}), body ]
122
+ end unless defined? AuthorizationResponder
123
+
124
+ describe 'simple http server', :shared => true do
125
+ before(:all) do
126
+ require 'rack'
127
+ require 'mongrel'
128
+
129
+ app = Rack::Builder.new do |env|
130
+ use Rack::ShowExceptions
131
+
132
+ map( '/get' ){ run SimpleGet }
133
+ map( '/post' ){ run SimplePost }
134
+ map( '/put' ){ run SimplePut }
135
+ map( '/delete' ){ run SimpleDel }
136
+
137
+ map( '/method' ){ run MethodResponder }
138
+ map( '/code' ){ run CodeResponder }
139
+ map( '/redirect' ){ run Redirector }
140
+ map( '/header' ){ run HeaderResponder }
141
+ map( '/echo_header' ){ run EchoHeaderResponder }
142
+ map( '/modified' ){ run ModifiedResponder }
143
+ map( '/auth' ){ run AuthorizationResponder }
144
+ end
145
+
146
+ #spawn the server in a separate thread
147
+ @httpd = Thread.new do
148
+ Rack::Handler::Mongrel.run(app, :Host => '127.0.0.1', :Port => 3000)
149
+ end
150
+ #give the server a chance to initialize
151
+ sleep 0.05
152
+ end
153
+
154
+ after(:all) do
155
+ # kill the server thread
156
+ @httpd.exit
157
+ end
158
+
159
+
160
+ end