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.
@@ -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