rack-test 2.1.0 → 2.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98bb9656717c512790c5f65b20a8f0cfc7c2f14e4d9d98d5069e6e5910b256e5
4
- data.tar.gz: 6305cac1636eef820a082a1431cef2bc24b445b1f902b65c550f976ba84f08e6
3
+ metadata.gz: 434b9a347a037eb6639e5e154affb301394d2067ba701dfaa5229189e7c2c895
4
+ data.tar.gz: 066fcdc1df73ec7bc4ecab58ba971762f6dd40cbf8df1f7e91bb6d72a31304e0
5
5
  SHA512:
6
- metadata.gz: 420e60a08ec2fa5fd92a358d28c0b17e0a943eb30962756360185091753bdd4eb55b7b58005aa84bb1907cfb7e8129166d1aed6282596f45b8b0f42dd1003306
7
- data.tar.gz: b7446d71bf41a0f69b9812e867f7b472488ee0534d6206435b77e8266617486687044a814dd6c085003b823e0e349c83b1ca47ea296f1f23346957d66cbed0bc
6
+ metadata.gz: 60c104aea1a7d8fef5be9a1e0103f5bafb81dcbdc47e3a3a91a3ea2667413ccdcf0cd6a6d9327cbd0a36f27bbb2e4a72ee6b442484b833a88ad24d90d3d1a001
7
+ data.tar.gz: 066d410b73e733a74b0dea06827f7a64b80b2d87d4d3081caf8456f0b63e3c9ba455ed1fc635440d4ba89c7844fe27e62cc745775ebd95869e66d78250384896
data/History.md CHANGED
@@ -1,3 +1,37 @@
1
+ ## 2.3.0 / 2026-10-21
2
+
3
+ * Bug fixes:
4
+ * Do not mutate arguments given to `Rack::Utils.build_multipart`
5
+ (Oskar Eichler #361)
6
+ * Rewind `tempfile` if there are errors when appending in
7
+ `UploadedFile#append_to` (Oskar Eichler #363)
8
+ * Preserve request method and params for 308 redirects (requires
9
+ Rack 2+) (Oskar Eichler #364)
10
+ * Reuse `rack.input` in 307/308 redirect request if it is rewindable,
11
+ fixing cases that use application/json and other content types
12
+ (Oskar Eichler #367)
13
+ * Don't use query string parameters from original request as request
14
+ body parameters in 307/308 redirects (Jeremy Evans)
15
+
16
+ * Minor enhancements:
17
+ * Add `Rack::Test::Session#default_input_content_type` and
18
+ `#convert_input_hash_to_string` private methods to allow for
19
+ easier overriding of default behavior. (Haruki Kobayashi #359)
20
+
21
+ ## 2.2.0 / 2024-12-23
22
+
23
+ * Bug fixes:
24
+ * `Rack::Test::Cookie` now parses cookie parameters using a
25
+ case-insensitive approach (Guillaume Malette #349)
26
+
27
+ * Minor enhancements:
28
+ * Arrays of cookies containing a blank cookie are now handled
29
+ correctly when processing responses. (Martin Emde #343)
30
+ * `Rack::Test::UploadedFile` no longer uses a finalizer for named
31
+ paths to close and unlink the created Tempfile. Tempfile itself
32
+ uses a finalizer to close and unlink itself, so there is no
33
+ reason for `Rack::Test::UploadedFile` to do so (Jeremy Evans #338)
34
+
1
35
  ## 2.1.0 / 2023-03-14
2
36
 
3
37
  * Breaking changes:
data/MIT-LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2008-2009 Bryan Helmkamp, Engine Yard Inc.
2
- Copyright (c) 2022 Jeremy Evans
2
+ Copyright (c) 2022-2026 Jeremy Evans and contributors
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -89,6 +89,22 @@ class TestApp < Test::Unit::TestCase
89
89
  end
90
90
  ```
91
91
 
92
+ If your application does not automatically use the `Rack::Lint` middleware in test mode,
93
+ and you want to test that requests to and responses from your application are compliant
94
+ with the Rack specification, you should manually use the `Rack::Lint` middleware:
95
+
96
+ ```ruby
97
+ class TestApp < Test::Unit::TestCase
98
+ include Rack::Test::Methods
99
+
100
+ APP = Rack::Lint.new(YOUR_APP)
101
+
102
+ def app
103
+ APP
104
+ end
105
+ end
106
+ ```
107
+
92
108
  ## Install
93
109
 
94
110
  To install the latest release as a gem:
@@ -12,7 +12,7 @@ module Rack
12
12
 
13
13
  # The name of the cookie, will be a string
14
14
  attr_reader :name
15
-
15
+
16
16
  # The value of the cookie, will be a string or nil if there is no value.
17
17
  attr_reader :value
18
18
 
@@ -28,7 +28,7 @@ module Rack
28
28
  @raw, options = raw.split(/[;,] */n, 2)
29
29
 
30
30
  @name, @value = parse_query(@raw, ';').to_a.first
31
- @options = parse_query(options, ';')
31
+ @options = Hash[parse_query(options, ';').map { |k, v| [k.downcase, v] }]
32
32
 
33
33
  if domain = @options['domain']
34
34
  @exact_domain_match = false
@@ -45,7 +45,7 @@ module Rack
45
45
  @options['path'] ||= uri.path.sub(/\/[^\/]*\Z/, '')
46
46
  end
47
47
 
48
- # Wether the given cookie can replace the current cookie in the cookie jar.
48
+ # Whether the given cookie can replace the current cookie in the cookie jar.
49
49
  def replaces?(other)
50
50
  [name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
51
51
  end
@@ -69,7 +69,7 @@ module Rack
69
69
  # Whether the cookie has the httponly flag, indicating it is not available via
70
70
  # a javascript API.
71
71
  def http_only?
72
- @options.key?('HttpOnly') || @options.key?('httponly')
72
+ @options.key?('httponly')
73
73
  end
74
74
 
75
75
  # The explicit or implicit path for the cookie.
@@ -93,9 +93,8 @@ module Rack
93
93
 
94
94
  uri.host = @default_host if uri.host.nil?
95
95
 
96
- real_domain = domain =~ /^\./ ? domain[1..-1] : domain
97
96
  !!((!secure? || (secure? && uri.scheme == 'https')) &&
98
- uri.host =~ Regexp.new("#{'^' if @exact_domain_match}#{Regexp.escape(real_domain)}$", Regexp::IGNORECASE))
97
+ uri.host =~ Regexp.new("#{'^' if @exact_domain_match}#{Regexp.escape(domain)}$", Regexp::IGNORECASE))
99
98
  end
100
99
 
101
100
  # Cookies that do not match the URI will not be sent in requests to the URI.
@@ -110,11 +109,13 @@ module Rack
110
109
 
111
110
  # A hash of cookie options, including the cookie value, but excluding the cookie name.
112
111
  def to_h
113
- @options.merge(
112
+ hash = @options.merge(
114
113
  'value' => @value,
115
114
  'HttpOnly' => http_only?,
116
115
  'secure' => secure?
117
116
  )
117
+ hash.delete('httponly')
118
+ hash
118
119
  end
119
120
  alias to_hash to_h
120
121
 
@@ -183,12 +184,10 @@ module Rack
183
184
  def merge(raw_cookies, uri = nil)
184
185
  return unless raw_cookies
185
186
 
186
- if raw_cookies.is_a? String
187
- raw_cookies = raw_cookies.split("\n")
188
- raw_cookies.reject!(&:empty?)
189
- end
187
+ raw_cookies = raw_cookies.split("\n") if raw_cookies.is_a? String
190
188
 
191
189
  raw_cookies.each do |raw_cookie|
190
+ next if raw_cookie.empty?
192
191
  cookie = Cookie.new(raw_cookie, uri, @default_host)
193
192
  self << cookie if cookie.valid?(uri)
194
193
  end
@@ -50,6 +50,7 @@ module Rack
50
50
 
51
51
  # Delegate all methods not handled to the tempfile.
52
52
  def method_missing(method_name, *args, &block)
53
+ # TODO: After Ruby <2.7.3 support is dropped, switch to ...
53
54
  tempfile.public_send(method_name, *args, &block)
54
55
  end
55
56
 
@@ -60,30 +61,19 @@ module Rack
60
61
  def append_to(buffer)
61
62
  tempfile.rewind
62
63
 
63
- buf = String.new
64
- buffer << tempfile.readpartial(65_536, buf) until tempfile.eof?
65
-
66
- tempfile.rewind
67
-
68
- nil
64
+ begin
65
+ buf = String.new
66
+ buffer << tempfile.readpartial(65_536, buf) until tempfile.eof?
67
+ nil
68
+ ensure
69
+ tempfile.rewind
70
+ end
69
71
  end
70
72
 
71
73
  def respond_to_missing?(method_name, include_private = false) #:nodoc:
72
74
  tempfile.respond_to?(method_name, include_private) || super
73
75
  end
74
76
 
75
- # A proc that can be used as a finalizer to close and unlink the tempfile.
76
- def self.finalize(file)
77
- proc { actually_finalize file }
78
- end
79
-
80
- # Close and unlink the given file, used as a finalizer for the tempfile,
81
- # if the tempfile is backed by a file in the filesystem.
82
- def self.actually_finalize(file)
83
- file.close
84
- file.unlink
85
- end
86
-
87
77
  private
88
78
 
89
79
  # Use the StringIO as the tempfile.
@@ -104,8 +94,6 @@ module Rack
104
94
  @tempfile = Tempfile.new([::File.basename(@original_filename, extension), extension])
105
95
  @tempfile.set_encoding(Encoding::BINARY)
106
96
 
107
- ObjectSpace.define_finalizer(self, self.class.finalize(@tempfile))
108
-
109
97
  FileUtils.copy_file(path, @tempfile.path)
110
98
  end
111
99
  end
@@ -6,9 +6,16 @@ module Rack
6
6
  include Rack::Utils
7
7
  extend self
8
8
 
9
+ class << self
10
+ attr_accessor :override_build_nested_query
11
+ end
12
+ self.override_build_nested_query = false
13
+
9
14
  # Build a query string for the given value and prefix. The value
10
15
  # can be an array or hash of parameters.
11
16
  def build_nested_query(value, prefix = nil)
17
+ return super if Rack::Test::Utils.override_build_nested_query
18
+
12
19
  case value
13
20
  when Array
14
21
  if value.empty?
@@ -67,16 +74,10 @@ module Rack
67
74
 
68
75
  case value
69
76
  when Array
70
- value.map do |v|
71
- if v.is_a?(Hash)
72
- nested_params = {}
73
- normalize_multipart_params(v).each do |subkey, subvalue|
74
- nested_params[subkey] = subvalue
75
- end
76
- (flattened_params["#{k}[]"] ||= []) << nested_params
77
- else
78
- flattened_params["#{k}[]"] = value
79
- end
77
+ next if value.empty?
78
+
79
+ flattened_params["#{k}[]"] = value.map do |v|
80
+ v.is_a?(Hash) ? normalize_multipart_params(v) : v
80
81
  end
81
82
  when Hash
82
83
  normalize_multipart_params(value).each do |subkey, subvalue|
@@ -98,15 +99,19 @@ module Rack
98
99
 
99
100
  # Append each multipart parameter value to the buffer.
100
101
  def _build_parts(buffer, parameters)
101
- parameters.map do |name, value|
102
- if name =~ /\[\]\Z/ && value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) }
103
- value.each do |hash|
102
+ parameters.each do |name, value|
103
+ if name =~ /\[\]\z/ && value.is_a?(Array) && value.any? { |v| v.is_a?(Hash) }
104
+ value.each do |item|
104
105
  new_value = {}
105
- hash.each { |k, v| new_value[name + k] = v }
106
+ if item.is_a?(Hash)
107
+ item.each { |k, v| new_value[name + k] = v }
108
+ else
109
+ new_value[name] = item
110
+ end
106
111
  _build_parts(buffer, new_value)
107
112
  end
108
113
  else
109
- [value].flatten.map do |v|
114
+ [value].flatten.each do |v|
110
115
  if v.respond_to?(:original_filename)
111
116
  build_file_part(buffer, name, v)
112
117
  else
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module Test
3
- VERSION = '2.1.0'.freeze
3
+ VERSION = '2.3.0'.freeze
4
4
  end
5
5
  end
data/lib/rack/test.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
- # :nocov:
5
+ # simplecov:disable
6
6
  begin
7
7
  require "rack/version"
8
8
  rescue LoadError
@@ -16,7 +16,7 @@ else
16
16
  require "rack"
17
17
  end
18
18
  end
19
- # :nocov:
19
+ # simplecov:enable
20
20
 
21
21
  require 'forwardable'
22
22
 
@@ -211,12 +211,29 @@ module Rack
211
211
  raise Error, 'Last response was not a redirect. Cannot follow_redirect!'
212
212
  end
213
213
 
214
- if last_response.status == 307
214
+ env = {
215
+ 'HTTP_REFERER' => last_request.url,
216
+ 'rack.session' => last_request.session,
217
+ 'rack.session.options' => last_request.session_options
218
+ }
219
+
220
+ params = {}
221
+ case last_response.status
222
+ when 307, 308
215
223
  request_method = last_request.request_method
216
- params = last_request.params
224
+ if (input = last_request.body)
225
+ if input.respond_to?(:rewind)
226
+ input.rewind
227
+ env[:input] = input
228
+ %w[CONTENT_TYPE CONTENT_LENGTH].each do |key|
229
+ env[key] = last_request.env[key] if last_request.env.key?(key)
230
+ end
231
+ else
232
+ params = last_request.POST
233
+ end
234
+ end
217
235
  else
218
236
  request_method = 'GET'
219
- params = {}
220
237
  end
221
238
 
222
239
  # Compute the next location by appending the location header with the
@@ -228,9 +245,7 @@ module Rack
228
245
  request_method,
229
246
  next_location.to_s,
230
247
  params,
231
- 'HTTP_REFERER' => last_request.url,
232
- 'rack.session' => last_request.session,
233
- 'rack.session.options' => last_request.session_options
248
+ env
234
249
  )
235
250
  end
236
251
 
@@ -255,12 +270,12 @@ module Rack
255
270
 
256
271
  private
257
272
 
258
- # :nocov:
273
+ # simplecov:disable
259
274
  if !defined?(Rack::RELEASE) || Gem::Version.new(Rack::RELEASE) < Gem::Version.new('2.2.2')
260
275
  def close_body(body)
261
276
  body.close if body.respond_to?(:close)
262
277
  end
263
- # :nocov:
278
+ # simplecov:enable
264
279
  else
265
280
  # close() gets called automatically in newer Rack versions.
266
281
  def close_body(body)
@@ -281,11 +296,11 @@ module Rack
281
296
  'REMOTE_ADDR' => '127.0.0.1',
282
297
  'SERVER_PROTOCOL' => 'HTTP/1.0',
283
298
  }
284
- # :nocov:
299
+ # simplecov:disable
285
300
  unless Rack.release >= '2.3'
286
301
  DEFAULT_ENV['HTTP_VERSION'] = DEFAULT_ENV['SERVER_PROTOCOL']
287
302
  end
288
- # :nocov:
303
+ # simplecov:enable
289
304
  DEFAULT_ENV.freeze
290
305
  private_constant :DEFAULT_ENV
291
306
 
@@ -307,7 +322,7 @@ module Rack
307
322
  append_query_params(query_array, params)
308
323
  end
309
324
  elsif !env.key?(:input)
310
- env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded'
325
+ env['CONTENT_TYPE'] ||= default_input_content_type
311
326
  params ||= {}
312
327
  multipart = env.has_key?(:multipart) ? env.delete(:multipart) : env['CONTENT_TYPE'].start_with?('multipart/')
313
328
 
@@ -317,7 +332,7 @@ module Rack
317
332
  env['CONTENT_LENGTH'] ||= data.length.to_s
318
333
  env['CONTENT_TYPE'] = "#{multipart_content_type(env)}; boundary=#{MULTIPART_BOUNDARY}"
319
334
  else
320
- env[:input] = build_nested_query(params)
335
+ env[:input] = convert_input_hash_to_string(params)
321
336
  end
322
337
  else
323
338
  env[:input] = params
@@ -369,6 +384,16 @@ module Rack
369
384
 
370
385
  @last_response
371
386
  end
387
+
388
+ # Return the default content type to use when none is given.
389
+ def default_input_content_type
390
+ 'application/x-www-form-urlencoded'
391
+ end
392
+
393
+ # Convert the given params hash into a request body string.
394
+ def convert_input_hash_to_string(value, prefix = nil)
395
+ build_nested_query(value, prefix)
396
+ end
372
397
  end
373
398
 
374
399
  # Whether the version of rack in use handles encodings.
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-test
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  - Bryan Helmkamp
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2023-03-14 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rack
@@ -95,7 +94,6 @@ metadata:
95
94
  bug_tracker_uri: https://github.com/rack/rack-test/issues
96
95
  mailing_list_uri: https://github.com/rack/rack-test/discussions
97
96
  changelog_uri: https://github.com/rack/rack-test/blob/main/History.md
98
- post_install_message:
99
97
  rdoc_options: []
100
98
  require_paths:
101
99
  - lib
@@ -110,8 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
108
  - !ruby/object:Gem::Version
111
109
  version: '0'
112
110
  requirements: []
113
- rubygems_version: 3.4.6
114
- signing_key:
111
+ rubygems_version: 4.0.20
115
112
  specification_version: 4
116
113
  summary: Simple testing API built on Rack
117
114
  test_files: []