rack-test 0.1.0 → 0.2.0
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.
- data/History.txt +18 -1
- data/Rakefile +6 -2
- data/lib/rack/test.rb +109 -41
- data/lib/rack/test/methods.rb +11 -22
- data/lib/rack/test/uploaded_file.rb +36 -0
- data/lib/rack/test/utils.rb +53 -5
- metadata +3 -2
data/History.txt
CHANGED
@@ -1,5 +1,22 @@
|
|
1
|
+
== 0.2.0 / 2009-04-26
|
2
|
+
|
3
|
+
Because #last_response is now a MockResponse instead of a Rack::Response,
|
4
|
+
#last_response.body now returns a string instead of an array.
|
5
|
+
|
6
|
+
* Major enhancements
|
7
|
+
|
8
|
+
* Support multipart requests via the UploadedFile class (thanks, Rails)
|
9
|
+
|
10
|
+
* Minor enhancements
|
11
|
+
|
12
|
+
* Updated for Rack 1.0
|
13
|
+
* Don't require rubygems (See http://gist.github.com/54177)
|
14
|
+
* Support HTTP Digest authentication with the #digest_authorize method
|
15
|
+
* #last_response returns a MockResponse instead of a Response
|
16
|
+
(Michael Fellinger)
|
17
|
+
|
1
18
|
== 0.1.0 / 2009-03-02
|
2
19
|
|
3
20
|
* 1 major enhancement
|
4
21
|
|
5
|
-
* Birthday!
|
22
|
+
* Birthday!
|
data/Rakefile
CHANGED
@@ -30,7 +30,7 @@ spec = Gem::Specification.new do |s|
|
|
30
30
|
s.summary = "Simple testing API built on Rack"
|
31
31
|
s.description = s.summary
|
32
32
|
s.files = %w[History.txt Rakefile README.rdoc] + Dir["lib/**/*"]
|
33
|
-
|
33
|
+
|
34
34
|
# rdoc
|
35
35
|
s.has_rdoc = true
|
36
36
|
s.extra_rdoc_files = %w(README.rdoc MIT-LICENSE.txt)
|
@@ -53,6 +53,10 @@ end
|
|
53
53
|
desc 'Install the package as a gem.'
|
54
54
|
task :install => [:clean, :package] do
|
55
55
|
gem = Dir['pkg/*.gem'].first
|
56
|
-
sh "sudo gem install --local #{gem}"
|
56
|
+
sh "sudo gem install --no-rdoc --no-ri --local #{gem}"
|
57
57
|
end
|
58
58
|
|
59
|
+
desc 'Removes trailing whitespace'
|
60
|
+
task :whitespace do
|
61
|
+
sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
|
62
|
+
end
|
data/lib/rack/test.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
|
3
1
|
unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__) + "/.."))
|
4
2
|
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/.."))
|
5
3
|
end
|
@@ -9,16 +7,40 @@ require "rack"
|
|
9
7
|
require "rack/test/cookie_jar"
|
10
8
|
require "rack/test/utils"
|
11
9
|
require "rack/test/methods"
|
10
|
+
require "rack/test/uploaded_file"
|
12
11
|
|
13
12
|
module Rack
|
14
13
|
module Test
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
|
15
|
+
class MockDigestRequest
|
16
|
+
def initialize(params)
|
17
|
+
@params = params
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(sym)
|
21
|
+
if @params.has_key? k = sym.to_s
|
22
|
+
return @params[k]
|
23
|
+
end
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def method
|
29
|
+
@params['method']
|
30
|
+
end
|
31
|
+
|
32
|
+
def response(password)
|
33
|
+
Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
|
34
|
+
end
|
20
35
|
end
|
21
|
-
|
36
|
+
|
37
|
+
VERSION = "0.2.0"
|
38
|
+
|
39
|
+
MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
|
40
|
+
|
41
|
+
# The common base class for exceptions raised by Rack::Test
|
42
|
+
class Error < StandardError; end
|
43
|
+
|
22
44
|
class Session
|
23
45
|
include Rack::Test::Utils
|
24
46
|
|
@@ -27,50 +49,50 @@ module Rack
|
|
27
49
|
raise ArgumentError.new("app must respond_to?(:call)") unless app.respond_to?(:call)
|
28
50
|
|
29
51
|
@headers = {}
|
30
|
-
@app
|
52
|
+
@app = app
|
31
53
|
end
|
32
|
-
|
54
|
+
|
33
55
|
# Issue a GET request for the given URI with the given params and Rack
|
34
56
|
# environment. Stores the issues request object in #last_request and
|
35
57
|
# the app's response in #last_response. Yield #last_response to a block
|
36
58
|
# if given.
|
37
|
-
#
|
59
|
+
#
|
38
60
|
# Example:
|
39
61
|
# get "/"
|
40
62
|
def get(uri, params = {}, env = {}, &block)
|
41
63
|
env = env_for(uri, env.merge(:method => "GET", :params => params))
|
42
64
|
process_request(uri, env, &block)
|
43
65
|
end
|
44
|
-
|
66
|
+
|
45
67
|
# Issue a POST request for the given URI. See #get
|
46
|
-
#
|
68
|
+
#
|
47
69
|
# Example:
|
48
70
|
# post "/signup", "name" => "Bryan"
|
49
71
|
def post(uri, params = {}, env = {}, &block)
|
50
72
|
env = env_for(uri, env.merge(:method => "POST", :params => params))
|
51
73
|
process_request(uri, env, &block)
|
52
74
|
end
|
53
|
-
|
75
|
+
|
54
76
|
# Issue a PUT request for the given URI. See #get
|
55
|
-
#
|
77
|
+
#
|
56
78
|
# Example:
|
57
79
|
# put "/"
|
58
80
|
def put(uri, params = {}, env = {}, &block)
|
59
81
|
env = env_for(uri, env.merge(:method => "PUT", :params => params))
|
60
82
|
process_request(uri, env, &block)
|
61
83
|
end
|
62
|
-
|
84
|
+
|
63
85
|
# Issue a DELETE request for the given URI. See #get
|
64
|
-
#
|
86
|
+
#
|
65
87
|
# Example:
|
66
88
|
# delete "/"
|
67
89
|
def delete(uri, params = {}, env = {}, &block)
|
68
90
|
env = env_for(uri, env.merge(:method => "DELETE", :params => params))
|
69
91
|
process_request(uri, env, &block)
|
70
92
|
end
|
71
|
-
|
93
|
+
|
72
94
|
# Issue a HEAD request for the given URI. See #get
|
73
|
-
#
|
95
|
+
#
|
74
96
|
# Example:
|
75
97
|
# head "/"
|
76
98
|
def head(uri, params = {}, env = {}, &block)
|
@@ -82,7 +104,7 @@ module Rack
|
|
82
104
|
# environment. Stores the issues request object in #last_request and
|
83
105
|
# the app's response in #last_response. Yield #last_response to a block
|
84
106
|
# if given.
|
85
|
-
#
|
107
|
+
#
|
86
108
|
# Example:
|
87
109
|
# request "/"
|
88
110
|
def request(uri, env = {}, &block)
|
@@ -92,7 +114,7 @@ module Rack
|
|
92
114
|
|
93
115
|
# Set a header to be included on all subsequent requests through the
|
94
116
|
# session. Use a value of nil to remove a previously configured header.
|
95
|
-
#
|
117
|
+
#
|
96
118
|
# Example:
|
97
119
|
# header "User-Agent", "Firefox"
|
98
120
|
def header(name, value)
|
@@ -102,17 +124,22 @@ module Rack
|
|
102
124
|
@headers[name] = value
|
103
125
|
end
|
104
126
|
end
|
105
|
-
|
127
|
+
|
106
128
|
# Set the username and password for HTTP Basic authorization, to be
|
107
129
|
# included in subsequent requests in the HTTP_AUTHORIZATION header.
|
108
|
-
#
|
130
|
+
#
|
109
131
|
# Example:
|
110
132
|
# authorize "bryan", "secret"
|
111
133
|
def authorize(username, password)
|
112
134
|
encoded_login = ["#{username}:#{password}"].pack("m*")
|
113
135
|
header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
|
114
136
|
end
|
115
|
-
|
137
|
+
|
138
|
+
def digest_authorize(username, password)
|
139
|
+
@digest_username = username
|
140
|
+
@digest_password = password
|
141
|
+
end
|
142
|
+
|
116
143
|
# Rack::Test will not follow any redirects automatically. This method
|
117
144
|
# will follow the redirect returned in the last response. If the last
|
118
145
|
# response was not a redirect, an error will be raised.
|
@@ -120,7 +147,7 @@ module Rack
|
|
120
147
|
unless last_response.redirect?
|
121
148
|
raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
|
122
149
|
end
|
123
|
-
|
150
|
+
|
124
151
|
get(last_response["Location"])
|
125
152
|
end
|
126
153
|
|
@@ -142,28 +169,34 @@ module Rack
|
|
142
169
|
|
143
170
|
private
|
144
171
|
|
145
|
-
|
146
172
|
def env_for(path, env)
|
147
173
|
uri = URI.parse(path)
|
148
174
|
uri.host ||= "example.org"
|
149
175
|
|
150
176
|
env = default_env.merge(env)
|
151
177
|
|
152
|
-
if URI::HTTPS === uri
|
153
|
-
|
154
|
-
end
|
178
|
+
env.update("HTTPS" => "on") if URI::HTTPS === uri
|
179
|
+
env["X-Requested-With"] = "XMLHttpRequest" if env[:xhr]
|
155
180
|
|
156
|
-
if env[:xhr]
|
157
|
-
env["X-Requested-With"] = "XMLHttpRequest"
|
158
|
-
end
|
159
|
-
|
160
181
|
if (env[:method] == "POST" || env["REQUEST_METHOD"] == "POST") && !env.has_key?(:input)
|
161
182
|
env["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
162
|
-
|
183
|
+
|
184
|
+
multipart = (env[:params] || {}).any? do |k, v|
|
185
|
+
UploadedFile === v
|
186
|
+
end
|
187
|
+
|
188
|
+
if multipart
|
189
|
+
env[:input] = multipart_body(env.delete(:params))
|
190
|
+
env["CONTENT_LENGTH"] ||= env[:input].length.to_s
|
191
|
+
env["CONTENT_TYPE"] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}"
|
192
|
+
else
|
193
|
+
env[:input] = params_to_string(env.delete(:params))
|
194
|
+
end
|
163
195
|
end
|
164
196
|
|
165
197
|
params = env[:params] || {}
|
166
198
|
params.update(parse_query(uri.query))
|
199
|
+
|
167
200
|
uri.query = requestify(params)
|
168
201
|
|
169
202
|
if env.has_key?(:cookie)
|
@@ -175,7 +208,7 @@ module Rack
|
|
175
208
|
|
176
209
|
Rack::MockRequest.env_for(uri.to_s, env)
|
177
210
|
end
|
178
|
-
|
211
|
+
|
179
212
|
def cookie_jar
|
180
213
|
@cookie_jar || Rack::Test::CookieJar.new
|
181
214
|
end
|
@@ -187,12 +220,47 @@ module Rack
|
|
187
220
|
@last_request = Rack::Request.new(env)
|
188
221
|
|
189
222
|
status, headers, body = @app.call(@last_request.env)
|
190
|
-
@last_response =
|
223
|
+
@last_response = MockResponse.new(status, headers, body, env["rack.errors"])
|
224
|
+
|
191
225
|
@cookie_jar = cookie_jar.merge(uri, last_response.headers["Set-Cookie"])
|
192
226
|
|
193
|
-
|
194
|
-
|
195
|
-
|
227
|
+
if retry_with_digest_auth?(env)
|
228
|
+
auth_env = env.merge("HTTP_AUTHORIZATION" => digest_auth_header,
|
229
|
+
"rack-test.digest_auth_retry" => true)
|
230
|
+
auth_env.delete('rack.request')
|
231
|
+
process_request(uri.path, auth_env)
|
232
|
+
else
|
233
|
+
yield @last_response if block_given?
|
234
|
+
|
235
|
+
@last_response
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def digest_auth_header
|
240
|
+
challenge = @last_response["WWW-Authenticate"].split(" ", 2).last
|
241
|
+
params = Rack::Auth::Digest::Params.parse(challenge)
|
242
|
+
|
243
|
+
params.merge!({
|
244
|
+
"username" => @digest_username,
|
245
|
+
"nc" => "00000001",
|
246
|
+
"cnonce" => "nonsensenonce",
|
247
|
+
"uri" => @last_request.path_info,
|
248
|
+
"method" => @last_request.env["REQUEST_METHOD"],
|
249
|
+
})
|
250
|
+
|
251
|
+
params["response"] = MockDigestRequest.new(params).response(@digest_password)
|
252
|
+
|
253
|
+
return "Digest #{params}"
|
254
|
+
end
|
255
|
+
|
256
|
+
def retry_with_digest_auth?(env)
|
257
|
+
@last_response.status == 401 &&
|
258
|
+
digest_auth_configured? &&
|
259
|
+
!env["rack-test.digest_auth_retry"]
|
260
|
+
end
|
261
|
+
|
262
|
+
def digest_auth_configured?
|
263
|
+
@digest_username
|
196
264
|
end
|
197
265
|
|
198
266
|
def default_env
|
@@ -206,8 +274,8 @@ module Rack
|
|
206
274
|
else params
|
207
275
|
end
|
208
276
|
end
|
209
|
-
|
277
|
+
|
210
278
|
end
|
211
|
-
|
279
|
+
|
212
280
|
end
|
213
281
|
end
|
data/lib/rack/test/methods.rb
CHANGED
@@ -1,45 +1,34 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module Test
|
3
|
-
|
4
5
|
module Methods
|
5
|
-
|
6
|
-
|
7
|
-
meths.each do |meth|
|
8
|
-
self.class_eval <<-RUBY
|
9
|
-
def #{meth}(*args, &blk)
|
10
|
-
rack_test_session.#{meth}(*args, &blk)
|
11
|
-
end
|
12
|
-
RUBY
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
6
|
+
extend Forwardable
|
7
|
+
|
16
8
|
def rack_test_session
|
17
9
|
@_rack_test_session ||= Rack::Test::Session.new(app)
|
18
10
|
end
|
19
|
-
|
20
|
-
|
11
|
+
|
12
|
+
METHODS = [
|
21
13
|
:request,
|
22
|
-
|
23
14
|
# HTTP verbs
|
24
15
|
:get,
|
25
16
|
:post,
|
26
17
|
:put,
|
27
18
|
:delete,
|
28
19
|
:head,
|
29
|
-
|
30
20
|
# Redirects
|
31
21
|
:follow_redirect!,
|
32
|
-
|
33
22
|
# Header-related features
|
34
23
|
:header,
|
35
24
|
:authorize,
|
36
|
-
|
25
|
+
:digest_authorize,
|
37
26
|
# Expose the last request and response
|
38
27
|
:last_response,
|
39
28
|
:last_request
|
40
|
-
|
41
|
-
|
29
|
+
]
|
30
|
+
|
31
|
+
def_delegators :rack_test_session, *METHODS
|
42
32
|
end
|
43
|
-
|
44
33
|
end
|
45
|
-
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Test
|
5
|
+
|
6
|
+
class UploadedFile
|
7
|
+
# The filename, *not* including the path, of the "uploaded" file
|
8
|
+
attr_reader :original_filename
|
9
|
+
|
10
|
+
# The content type of the "uploaded" file
|
11
|
+
attr_accessor :content_type
|
12
|
+
|
13
|
+
def initialize(path, content_type = "text/plain", binary = false)
|
14
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
15
|
+
@content_type = content_type
|
16
|
+
@original_filename = ::File.basename(path)
|
17
|
+
@tempfile = Tempfile.new(@original_filename)
|
18
|
+
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
19
|
+
@tempfile.binmode if binary
|
20
|
+
FileUtils.copy_file(path, @tempfile.path)
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
@tempfile.path
|
25
|
+
end
|
26
|
+
|
27
|
+
alias_method :local_path, :path
|
28
|
+
|
29
|
+
def method_missing(method_name, *args, &block) #:nodoc:
|
30
|
+
@tempfile.__send__(method_name, *args, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/lib/rack/test/utils.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Rack
|
2
2
|
module Test
|
3
|
-
|
3
|
+
|
4
4
|
module Utils
|
5
5
|
include Rack::Utils
|
6
|
-
|
6
|
+
|
7
7
|
def requestify(value, prefix = nil)
|
8
8
|
case value
|
9
9
|
when Array
|
@@ -18,10 +18,58 @@ module Rack
|
|
18
18
|
"#{prefix}=#{escape(value)}"
|
19
19
|
end
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
module_function :requestify
|
23
|
-
|
23
|
+
|
24
|
+
def multipart_requestify(params, first=true)
|
25
|
+
p = Hash.new
|
26
|
+
|
27
|
+
params.each do |key, value|
|
28
|
+
k = first ? key.to_s : "[#{key}]"
|
29
|
+
|
30
|
+
if Hash === value
|
31
|
+
multipart_requestify(value, false).each do |subkey, subvalue|
|
32
|
+
p[k + subkey] = subvalue
|
33
|
+
end
|
34
|
+
else
|
35
|
+
p[k] = value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return p
|
40
|
+
end
|
41
|
+
|
42
|
+
module_function :multipart_requestify
|
43
|
+
|
44
|
+
def multipart_body(params)
|
45
|
+
multipart_requestify(params).map do |key, value|
|
46
|
+
if value.respond_to?(:original_filename)
|
47
|
+
::File.open(value.path, "rb") do |f|
|
48
|
+
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
49
|
+
|
50
|
+
<<-EOF
|
51
|
+
--#{MULTIPART_BOUNDARY}\r
|
52
|
+
Content-Disposition: form-data; name="#{key}"; filename="#{escape(value.original_filename)}"\r
|
53
|
+
Content-Type: #{value.content_type}\r
|
54
|
+
Content-Length: #{::File.stat(value.path).size}\r
|
55
|
+
\r
|
56
|
+
#{f.read}\r
|
57
|
+
EOF
|
58
|
+
end
|
59
|
+
else
|
60
|
+
<<-EOF
|
61
|
+
--#{MULTIPART_BOUNDARY}\r
|
62
|
+
Content-Disposition: form-data; name="#{key}"\r
|
63
|
+
\r
|
64
|
+
#{value}\r
|
65
|
+
EOF
|
66
|
+
end
|
67
|
+
end.join("")+"--#{MULTIPART_BOUNDARY}--\r"
|
68
|
+
end
|
69
|
+
|
70
|
+
module_function :multipart_body
|
71
|
+
|
24
72
|
end
|
25
|
-
|
73
|
+
|
26
74
|
end
|
27
75
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-test
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Helmkamp
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-04-26 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- lib/rack/test
|
31
31
|
- lib/rack/test/cookie_jar.rb
|
32
32
|
- lib/rack/test/methods.rb
|
33
|
+
- lib/rack/test/uploaded_file.rb
|
33
34
|
- lib/rack/test/utils.rb
|
34
35
|
- lib/rack/test.rb
|
35
36
|
- MIT-LICENSE.txt
|