resourceful 0.2

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.
@@ -0,0 +1,199 @@
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
+ it 'should know if it is a redirect' do
62
+ Resourceful::Response.new(@uri, 301, {}, "").is_redirect?.should == true
63
+ Resourceful::Response.new(@uri, 302, {}, "").is_redirect?.should == true
64
+ Resourceful::Response.new(@uri, 303, {}, "").is_redirect?.should == true
65
+ Resourceful::Response.new(@uri, 307, {}, "").is_redirect?.should == true
66
+
67
+ #aliased as was_redirect?
68
+ Resourceful::Response.new(@uri, 301, {}, "").was_redirect?.should == true
69
+ end
70
+
71
+ it 'should know if it is a permanent redirect' do
72
+ Resourceful::Response.new(@uri, 301, {}, "").is_permanent_redirect?.should == true
73
+ end
74
+
75
+ it 'should know if it is a temporary redirect' do
76
+ Resourceful::Response.new(@uri, 303, {}, "").is_temporary_redirect?.should == true
77
+ end
78
+
79
+ it 'should know if its authoritative' do
80
+ @response.should respond_to(:authoritative?)
81
+ end
82
+
83
+ it 'should allow authoritative to be set' do
84
+ @response.authoritative = true
85
+ @response.authoritative?.should be_true
86
+ end
87
+
88
+ it 'should know if it is authorized' do
89
+ @response.should respond_to(:is_not_authorized?)
90
+ Resourceful::Response.new(@uri, 200, {}, "").is_not_authorized?.should == false
91
+ Resourceful::Response.new(@uri, 401, {}, "").is_not_authorized?.should == true
92
+ end
93
+
94
+ describe 'caching and expiration' do
95
+ before do
96
+ Time.stub!(:now).and_return(Time.utc(2008,5,15,18,0,1), Time.utc(2008,5,15,20,0,0))
97
+
98
+ @response = Resourceful::Response.new(@uri, 0, {'Date' => ['Thu, 15 May 2008 18:00:00 GMT']}, "")
99
+ @response.request_time = Time.utc(2008,5,15,17,59,59)
100
+ end
101
+
102
+ it 'should know if its #stale?' do
103
+ @response.should respond_to(:stale?)
104
+ end
105
+
106
+ it 'should be stale if it is expired' do
107
+ @response.should_receive(:expired?).and_return(true)
108
+ @response.should be_stale
109
+ end
110
+
111
+ it 'should know if its #expired?' do
112
+ @response.should respond_to(:expired?)
113
+ end
114
+
115
+ it 'should be expired if Now is after the "Expire" header' do
116
+ Time.stub!(:now).and_return(Time.utc(2008,5,23,18,0))
117
+ @response.header['Expire'] = [(Time.now - 60*60).httpdate]
118
+
119
+ @response.should be_expired
120
+ end
121
+
122
+ it 'should have a #current_age' do
123
+ @response.should respond_to(:current_age)
124
+ end
125
+
126
+ it 'should calculate the #current_age' do
127
+ @response.current_age.should == (2 * 60 * 60 + 2)
128
+ end
129
+
130
+ it 'should know if its #cachable?' do
131
+ @response.should respond_to(:cachable?)
132
+ end
133
+
134
+ it 'should normally be cachable' do
135
+ @response.cachable?.should be_true
136
+ end
137
+
138
+ def response_with_header(header = {})
139
+ Resourceful::Response.new(@uri, 200, header, "")
140
+ end
141
+
142
+ it 'should not be cachable if the vary header has "*"' do
143
+ r = response_with_header('Vary' => ['*'])
144
+ r.cachable?.should be_false
145
+ end
146
+
147
+ it 'should not be cachable if the Cache-Control header is set to no-store' do
148
+ r = response_with_header('Cache-Control' => ['no-store'])
149
+ r.cachable?.should be_false
150
+ end
151
+
152
+ it 'should be stale if the Cache-Control header is set to must-revalidate' do
153
+ r = response_with_header('Cache-Control' => ['must-revalidate'])
154
+ r.should be_stale
155
+ end
156
+
157
+ it 'should be stale if the Cache-Control header is set to no-cache' do
158
+ r = response_with_header('Cache-Control' => ['no-cache'])
159
+ r.should be_stale
160
+ end
161
+ end
162
+
163
+
164
+ describe '#body' do
165
+ it 'should have a body method' do
166
+ @response.should respond_to(:body)
167
+ end
168
+
169
+ require 'zlib'
170
+ ['gzip', ' gzip', ' gzip ', 'GZIP', 'gzIP'].each do |gzip|
171
+ it "ungzip the body if content-encoding header field is #{gzip}" do
172
+ compressed_date = StringIO.new.tap do |out|
173
+ Zlib::GzipWriter.new(out).tap do |zout|
174
+ zout << "This is a test"
175
+ zout.close
176
+ end
177
+ end.string
178
+
179
+ @response = Resourceful::Response.new(@uri, 0, {'Content-Encoding' => [gzip]}, compressed_date)
180
+
181
+ @response.body.should == "This is a test"
182
+ end
183
+ end
184
+
185
+ it 'should leave body unmolested if Content-Encoding missing' do
186
+ @response = Resourceful::Response.new(@uri, 0, {}, "This is a test")
187
+ @response.body.should == "This is a test"
188
+ end
189
+
190
+ it 'should raise error if Content-Encoding is not supported' do
191
+ @response = Resourceful::Response.new(@uri, 0, {'Content-Encoding' => ['broken-identity']}, "This is a test")
192
+ lambda {
193
+ @response.body
194
+ }.should raise_error(Resourceful::UnsupportedContentCoding)
195
+ end
196
+ end
197
+
198
+ end
199
+
@@ -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,151 @@
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 quesy 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
+ # redirect. /redirect/{301|302}?{url}
52
+ Redirector = lambda do |env|
53
+ code = env['PATH_INFO'] =~ /([\d]+)/ ? Integer($1) : 404
54
+ location = env['QUERY_STRING']
55
+ body = [location]
56
+
57
+ [ code, {'Content-Type' => 'text/plain', 'Location' => location, 'Content-Length' => body.join.size.to_s}, body ]
58
+ end unless defined? Redirector
59
+
60
+ # Returns 304 if 'If-Modified-Since' is after given mod time
61
+ ModifiedResponder = lambda do |env|
62
+ modtime = Time.httpdate(URI.unescape(env['QUERY_STRING']))
63
+
64
+ code = 200
65
+ if env['HTTP_IF_MODIFIED_SINCE']
66
+ code = 304
67
+ end
68
+ body = [modtime.to_s]
69
+
70
+ header = {'Content-Type' => 'text/plain',
71
+ 'Content-Length' => body.join.size.to_s,
72
+ 'Last-Modified' => modtime.httpdate,
73
+ 'Cache-Control' => 'must-revalidate'}
74
+
75
+ [ code, header, body ]
76
+ end unless defined? ModifiedResponder
77
+
78
+ require 'rubygems'
79
+ require 'httpauth'
80
+ AuthorizationResponder = lambda do |env|
81
+ authtype = env['QUERY_STRING']
82
+ header = {}
83
+ if auth_string = env['HTTP_AUTHORIZATION']
84
+ if authtype == "basic" &&
85
+ ['admin', 'secret'] == HTTPAuth::Basic.unpack_authorization(auth_string)
86
+ code = 200
87
+ body = ["Authorized"]
88
+ elsif authtype == "digest" #&&
89
+ credentials = HTTPAuth::Digest::Credentials.from_header(auth_string) &&
90
+ credentials &&
91
+ credentials.validate(:password => 'secret', :method => 'GET')
92
+ code = 200
93
+ body = ["Authorized"]
94
+ else
95
+ code = 401
96
+ body = ["Not Authorized"]
97
+ end
98
+ else
99
+ code = 401
100
+ body = ["Not Authorized"]
101
+ if authtype == "basic"
102
+ header = {'WWW-Authenticate' => HTTPAuth::Basic.pack_challenge('Test Auth')}
103
+ elsif authtype == "digest"
104
+ chal = HTTPAuth::Digest::Credentials.new(:realm => 'Test Auth', :qop => 'auth')
105
+ header = {'WWW-Authenticate' => chal.to_header}
106
+ end
107
+ end
108
+
109
+ [ code, header.merge({'Content-Type' => 'text/plain', 'Content-Length' => body.join.size.to_s}), body ]
110
+ end unless defined? AuthorizationResponder
111
+
112
+ describe 'simple http server', :shared => true do
113
+ before(:all) do
114
+ #setup a thin http server we can connect to
115
+ require 'thin'
116
+ require 'rack'
117
+ require 'rack/lobster'
118
+
119
+ app = Rack::Builder.new do |env|
120
+ use Rack::ShowExceptions
121
+
122
+ map( '/get' ){ run SimpleGet }
123
+ map( '/post' ){ run SimplePost }
124
+ map( '/put' ){ run SimplePut }
125
+ map( '/delete' ){ run SimpleDel }
126
+
127
+ map( '/method' ){ run MethodResponder }
128
+ map( '/code' ){ run CodeResponder }
129
+ map( '/redirect' ){ run Redirector }
130
+ map( '/header' ){ run HeaderResponder }
131
+ map( '/modified' ){ run ModifiedResponder }
132
+ map( '/auth' ){ run AuthorizationResponder }
133
+ end
134
+
135
+ #spawn the server in a separate thread
136
+ @httpd = Thread.new do
137
+ Thin::Logging.silent = true
138
+ #Thin::Logging.debug = true
139
+ Thin::Server.start(app)
140
+ end
141
+ #give the server a chance to initialize
142
+ sleep 0.05
143
+ end
144
+
145
+ after(:all) do
146
+ # kill the server thread
147
+ @httpd.exit
148
+ end
149
+
150
+
151
+ end
@@ -0,0 +1,195 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname + 'spec_helper'
3
+ require 'rubygems'
4
+ require 'addressable/uri'
5
+
6
+ require 'resourceful/net_http_adapter'
7
+
8
+ describe 'http server' do
9
+ it_should_behave_like 'simple http server'
10
+
11
+ it 'should have a response code of 200 if the path is /get' do
12
+ Resourceful::NetHttpAdapter.make_request(:get, 'http://localhost:3000/get')[0].should == 200
13
+ end
14
+
15
+ it 'should reply with the posted document in the body if the path is /post' do
16
+ resp = Resourceful::NetHttpAdapter.make_request(:get, 'http://localhost:3000/post', 'Hello from POST!')
17
+ resp[2].should == 'Hello from POST!'
18
+ resp[0].should == 201
19
+ end
20
+
21
+ it 'should reply with the puted document in the body if the path is /put' do
22
+ resp = Resourceful::NetHttpAdapter.make_request(:put, 'http://localhost:3000/put', 'Hello from PUT!')
23
+ resp[2].should == 'Hello from PUT!'
24
+ resp[0].should == 200
25
+ end
26
+
27
+ it 'should reply with "KABOOM!" in the body if the path is /delete' do
28
+ resp = Resourceful::NetHttpAdapter.make_request(:delete, 'http://localhost:3000/delete')
29
+ resp[2].should == 'KABOOM!'
30
+ resp[0].should == 200
31
+ end
32
+
33
+ it 'should have a response code of whatever the path is' do
34
+ Resourceful::NetHttpAdapter.make_request(:get, 'http://localhost:3000/code/304')[0].should == 304
35
+ end
36
+
37
+ it 'should redirect to a given url' do
38
+ resp = Resourceful::NetHttpAdapter.make_request(:get, 'http://localhost:3000/redirect/301?http://localhost:3000/get')
39
+
40
+ resp[0].should == 301
41
+ resp[1]['Location'].should == ['http://localhost:3000/get']
42
+ end
43
+
44
+ it 'should respond with the request method in the body' do
45
+ resp = Resourceful::NetHttpAdapter.make_request(:delete, 'http://localhost:3000/method')
46
+
47
+ resp[0].should == 200
48
+ resp[2].should == "DELETE"
49
+ end
50
+
51
+ it 'should respond with the header set from the query string' do
52
+ uri = URI.escape('http://localhost:3000/header?{Foo: "bar"}')
53
+ resp = Resourceful::NetHttpAdapter.make_request(:get, uri)
54
+
55
+ resp[1].should have_key('Foo')
56
+ resp[1]['Foo'].should == ['bar']
57
+ end
58
+
59
+ it 'should parse escaped uris properly' do
60
+ uri = URI.escape("http://localhost:3000/header?{Expire: \"#{Time.now.httpdate}\"}")
61
+
62
+ resp = Resourceful::NetHttpAdapter.make_request(:get, uri)
63
+
64
+ resp[1].should have_key('Expire')
65
+ resp[1]['Expire'].first.should_not =~ /%/
66
+ end
67
+
68
+ describe '/modified' do
69
+ it 'should be 200 if no I-M-S header' do
70
+ uri = URI.escape("http://localhost:3000/modified?#{(Time.now + 3600).httpdate}")
71
+
72
+ resp = Resourceful::NetHttpAdapter.make_request(:get, uri)
73
+
74
+ resp[0].should == 200
75
+ end
76
+
77
+ it 'should be 304 if I-M-S header is set' do
78
+ now = Time.utc(2008,5,29,12,00)
79
+ uri = URI.escape("http://localhost:3000/modified?#{(now + 3600).httpdate}")
80
+
81
+ resp = Resourceful::NetHttpAdapter.make_request(:get, uri, nil, {'If-Modified-Since' => now.httpdate})
82
+
83
+ resp[0].should == 304
84
+ end
85
+
86
+ end
87
+
88
+ describe '/auth' do
89
+ before do
90
+ @uri = "http://localhost:3000/auth?basic"
91
+ end
92
+
93
+ it 'should return a 401 if no auth info is provided' do
94
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
95
+ resp[0].should == 401
96
+ end
97
+
98
+ describe 'basic' do
99
+ before do
100
+ @uri = "http://localhost:3000/auth?basic"
101
+ end
102
+
103
+ it 'should return a 401 if no auth info is provided' do
104
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
105
+ resp[0].should == 401
106
+ end
107
+
108
+ it 'should provide a WWW-Authenticate header when 401' do
109
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
110
+ header = resp[1]
111
+ header.should have_key('WWW-Authenticate')
112
+ end
113
+
114
+ it 'should set the scheme to "Basic"' do
115
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
116
+ auth = resp[1]['WWW-Authenticate'].first
117
+ auth.should =~ /^Basic/
118
+ end
119
+
120
+ it 'should set the realm to "Test Auth"' do
121
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
122
+ auth = resp[1]['WWW-Authenticate'].first
123
+ auth.should =~ /realm="Test Auth"/
124
+ end
125
+
126
+ it 'should authorize on u/p:admin/secret' do
127
+ creds = HTTPAuth::Basic.pack_authorization('admin', 'secret')
128
+ header = {'Authorization' => creds}
129
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri, nil, header)
130
+ resp[0].should == 200
131
+ end
132
+
133
+ it 'should authorize if u/p is incorrect' do
134
+ creds = HTTPAuth::Basic.pack_authorization('admin', 'not secret')
135
+ header = {'Authorization' => creds}
136
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri, nil, header)
137
+ resp[0].should == 401
138
+ end
139
+
140
+ end
141
+
142
+ describe 'digest' do
143
+ before do
144
+ @uri = "http://localhost:3000/auth?digest"
145
+ end
146
+
147
+ it 'should return a 401 if no auth info is provided' do
148
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
149
+ resp[0].should == 401
150
+ end
151
+
152
+ it 'should provide a WWW-Authenticate header when 401' do
153
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
154
+ header = resp[1]
155
+ header.should have_key('WWW-Authenticate')
156
+ end
157
+
158
+ it 'should set the scheme to "Digest"' do
159
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
160
+ auth = resp[1]['WWW-Authenticate'].first
161
+ auth.should =~ /^Digest/
162
+ end
163
+
164
+ it 'should set the realm to "Test Auth"' do
165
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
166
+ auth = resp[1]['WWW-Authenticate'].first
167
+ auth.should =~ /realm="Test Auth"/
168
+ end
169
+
170
+ def challenge
171
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri)
172
+ HTTPAuth::Digest::Challenge.from_header(resp[1]['WWW-Authenticate'].first)
173
+ end
174
+
175
+ it 'should authorize on u/p:admin/secret' do
176
+ creds = HTTPAuth::Digest::Credentials.from_challenge(challenge, :username => 'admin', :password => 'secret', :uri => @uri)
177
+ header = {'Authorization' => creds.to_header}
178
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri, nil, header)
179
+ resp[0].should == 200
180
+ end
181
+
182
+ it 'should not authorize if u/p is incorrect' do
183
+ pending
184
+ creds = HTTPAuth::Digest::Credentials.from_challenge(challenge, :username => 'admin', :password => 'not secret', :uri => @uri)
185
+ header = {'Authorization' => creds.to_header}
186
+ resp = Resourceful::NetHttpAdapter.make_request(:get, @uri, nil, header)
187
+ resp[0].should == 401
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+
194
+ end
195
+