rack-test 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- VERSION = "0.1.0"
17
-
18
- # The common base class for exceptions raised by Rack::Test
19
- class Error < StandardError
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 = 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
- env.update("HTTPS" => "on")
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
- env[:input] = params_to_string(env.delete(:params))
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 = Rack::Response.new(body, status, headers)
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
- yield @last_response if block_given?
194
-
195
- @last_response
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
@@ -1,45 +1,34 @@
1
+ require "forwardable"
2
+
1
3
  module Rack
2
4
  module Test
3
-
4
5
  module Methods
5
-
6
- def self.delegate_to_rack_test_session(*meths)
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
- delegate_to_rack_test_session \
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
@@ -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.1.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-03-04 00:00:00 -05:00
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