rack-test 0.6.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.md +243 -0
- data/README.md +148 -0
- data/lib/rack/mock_session.rb +6 -9
- data/lib/rack/test/cookie_jar.rb +39 -27
- data/lib/rack/test/methods.rb +22 -22
- data/lib/rack/test/mock_digest_request.rb +1 -5
- data/lib/rack/test/uploaded_file.rb +51 -19
- data/lib/rack/test/utils.rb +37 -43
- data/lib/rack/test/version.rb +5 -0
- data/lib/rack/test.rb +94 -85
- metadata +139 -60
- data/.document +0 -4
- data/.gitignore +0 -6
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -41
- data/History.txt +0 -179
- data/README.rdoc +0 -85
- data/Rakefile +0 -33
- data/Thorfile +0 -114
- data/rack-test.gemspec +0 -77
- data/spec/fixtures/bar.txt +0 -1
- data/spec/fixtures/config.ru +0 -3
- data/spec/fixtures/fake_app.rb +0 -143
- data/spec/fixtures/foo.txt +0 -1
- data/spec/rack/test/cookie_spec.rb +0 -219
- data/spec/rack/test/digest_auth_spec.rb +0 -46
- data/spec/rack/test/multipart_spec.rb +0 -145
- data/spec/rack/test/uploaded_file_spec.rb +0 -24
- data/spec/rack/test/utils_spec.rb +0 -193
- data/spec/rack/test_spec.rb +0 -550
- data/spec/spec_helper.rb +0 -69
- data/spec/support/matchers/body.rb +0 -9
- data/spec/support/matchers/challenge.rb +0 -11
data/lib/rack/test/cookie_jar.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'uri'
|
2
|
+
require 'time'
|
3
3
|
|
4
4
|
module Rack
|
5
5
|
module Test
|
6
|
-
|
7
6
|
class Cookie # :nodoc:
|
8
7
|
include Rack::Utils
|
9
8
|
|
@@ -21,8 +20,8 @@ module Rack
|
|
21
20
|
@name, @value = parse_query(@name_value_raw, ';').to_a.first
|
22
21
|
@options = parse_query(options, ';')
|
23
22
|
|
24
|
-
@options[
|
25
|
-
@options[
|
23
|
+
@options['domain'] ||= (uri.host || default_host)
|
24
|
+
@options['path'] ||= uri.path.sub(/\/[^\/]*\Z/, '')
|
26
25
|
end
|
27
26
|
|
28
27
|
def replaces?(other)
|
@@ -41,21 +40,25 @@ module Rack
|
|
41
40
|
|
42
41
|
# :api: private
|
43
42
|
def domain
|
44
|
-
@options[
|
43
|
+
@options['domain']
|
45
44
|
end
|
46
45
|
|
47
46
|
def secure?
|
48
|
-
@options.
|
47
|
+
@options.key?('secure')
|
48
|
+
end
|
49
|
+
|
50
|
+
def http_only?
|
51
|
+
@options.key?('HttpOnly')
|
49
52
|
end
|
50
53
|
|
51
54
|
# :api: private
|
52
55
|
def path
|
53
|
-
|
56
|
+
([*@options['path']].first.split(',').first || '/').strip
|
54
57
|
end
|
55
58
|
|
56
59
|
# :api: private
|
57
60
|
def expires
|
58
|
-
Time.parse(@options[
|
61
|
+
Time.parse(@options['expires']) if @options['expires']
|
59
62
|
end
|
60
63
|
|
61
64
|
# :api: private
|
@@ -67,19 +70,17 @@ module Rack
|
|
67
70
|
def valid?(uri)
|
68
71
|
uri ||= default_uri
|
69
72
|
|
70
|
-
if uri.host.nil?
|
71
|
-
uri.host = @default_host
|
72
|
-
end
|
73
|
+
uri.host = @default_host if uri.host.nil?
|
73
74
|
|
74
75
|
real_domain = domain =~ /^\./ ? domain[1..-1] : domain
|
75
|
-
(!secure? || (secure? && uri.scheme ==
|
76
|
-
|
77
|
-
|
76
|
+
(!secure? || (secure? && uri.scheme == 'https')) &&
|
77
|
+
uri.host =~ Regexp.new("#{Regexp.escape(real_domain)}$", Regexp::IGNORECASE) &&
|
78
|
+
uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
|
78
79
|
end
|
79
80
|
|
80
81
|
# :api: private
|
81
82
|
def matches?(uri)
|
82
|
-
!
|
83
|
+
!expired? && valid?(uri)
|
83
84
|
end
|
84
85
|
|
85
86
|
# :api: private
|
@@ -88,15 +89,24 @@ module Rack
|
|
88
89
|
[name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
|
89
90
|
end
|
90
91
|
|
91
|
-
|
92
|
+
def to_h
|
93
|
+
@options.merge(
|
94
|
+
'value' => @value,
|
95
|
+
'HttpOnly' => http_only?,
|
96
|
+
'secure' => secure?
|
97
|
+
)
|
98
|
+
end
|
99
|
+
alias to_hash to_h
|
100
|
+
|
101
|
+
protected
|
92
102
|
|
93
103
|
def default_uri
|
94
|
-
URI.parse(
|
104
|
+
URI.parse('//' + @default_host + '/')
|
95
105
|
end
|
96
|
-
|
97
106
|
end
|
98
107
|
|
99
108
|
class CookieJar # :nodoc:
|
109
|
+
DELIMITER = '; '.freeze
|
100
110
|
|
101
111
|
# :api: private
|
102
112
|
def initialize(cookies = [], default_host = DEFAULT_HOST)
|
@@ -108,13 +118,17 @@ module Rack
|
|
108
118
|
def [](name)
|
109
119
|
cookies = hash_for(nil)
|
110
120
|
# TODO: Should be case insensitive
|
111
|
-
cookies[name] && cookies[name].value
|
121
|
+
cookies[name.to_s] && cookies[name.to_s].value
|
112
122
|
end
|
113
123
|
|
114
124
|
def []=(name, value)
|
115
125
|
merge("#{name}=#{Rack::Utils.escape(value)}")
|
116
126
|
end
|
117
127
|
|
128
|
+
def get_cookie(name)
|
129
|
+
hash_for(nil).fetch(name, nil)
|
130
|
+
end
|
131
|
+
|
118
132
|
def delete(name)
|
119
133
|
@cookies.reject! do |cookie|
|
120
134
|
cookie.name == name
|
@@ -126,7 +140,7 @@ module Rack
|
|
126
140
|
|
127
141
|
if raw_cookies.is_a? String
|
128
142
|
raw_cookies = raw_cookies.split("\n")
|
129
|
-
raw_cookies.reject!
|
143
|
+
raw_cookies.reject!(&:empty?)
|
130
144
|
end
|
131
145
|
|
132
146
|
raw_cookies.each do |raw_cookie|
|
@@ -146,7 +160,7 @@ module Rack
|
|
146
160
|
|
147
161
|
# :api: private
|
148
162
|
def for(uri)
|
149
|
-
hash_for(uri).values.map
|
163
|
+
hash_for(uri).values.map(&:raw).join(DELIMITER)
|
150
164
|
end
|
151
165
|
|
152
166
|
def to_hash
|
@@ -156,10 +170,10 @@ module Rack
|
|
156
170
|
cookies[name] = cookie.value
|
157
171
|
end
|
158
172
|
|
159
|
-
|
173
|
+
cookies
|
160
174
|
end
|
161
175
|
|
162
|
-
|
176
|
+
protected
|
163
177
|
|
164
178
|
def hash_for(uri = nil)
|
165
179
|
cookies = {}
|
@@ -173,10 +187,8 @@ module Rack
|
|
173
187
|
cookies[cookie.name] = cookie if !uri || cookie.matches?(uri)
|
174
188
|
end
|
175
189
|
|
176
|
-
|
190
|
+
cookies
|
177
191
|
end
|
178
|
-
|
179
192
|
end
|
180
|
-
|
181
193
|
end
|
182
194
|
end
|
data/lib/rack/test/methods.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require 'forwardable'
|
2
2
|
|
3
3
|
module Rack
|
4
4
|
module Test
|
5
|
-
|
6
5
|
# This module serves as the primary integration point for using Rack::Test
|
7
6
|
# in a testing environment. It depends on an app method being defined in the
|
8
7
|
# same context, and provides the Rack::Test API methods (see Rack::Test::Session
|
@@ -56,26 +55,27 @@ module Rack
|
|
56
55
|
@_current_session_names ||= [:default]
|
57
56
|
end
|
58
57
|
|
59
|
-
METHODS = [
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
58
|
+
METHODS = %i[
|
59
|
+
request
|
60
|
+
get
|
61
|
+
post
|
62
|
+
put
|
63
|
+
patch
|
64
|
+
delete
|
65
|
+
options
|
66
|
+
head
|
67
|
+
custom_request
|
68
|
+
follow_redirect!
|
69
|
+
header
|
70
|
+
env
|
71
|
+
set_cookie
|
72
|
+
clear_cookies
|
73
|
+
authorize
|
74
|
+
basic_authorize
|
75
|
+
digest_authorize
|
76
|
+
last_response
|
77
|
+
last_request
|
78
|
+
].freeze
|
79
79
|
|
80
80
|
def_delegators :current_session, *METHODS
|
81
81
|
end
|
@@ -1,14 +1,12 @@
|
|
1
1
|
module Rack
|
2
2
|
module Test
|
3
|
-
|
4
3
|
class MockDigestRequest # :nodoc:
|
5
|
-
|
6
4
|
def initialize(params)
|
7
5
|
@params = params
|
8
6
|
end
|
9
7
|
|
10
8
|
def method_missing(sym)
|
11
|
-
if @params.
|
9
|
+
if @params.key? k = sym.to_s
|
12
10
|
return @params[k]
|
13
11
|
end
|
14
12
|
|
@@ -22,8 +20,6 @@ module Rack
|
|
22
20
|
def response(password)
|
23
21
|
Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
|
24
22
|
end
|
25
|
-
|
26
23
|
end
|
27
|
-
|
28
24
|
end
|
29
25
|
end
|
@@ -1,16 +1,15 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require 'tempfile'
|
3
4
|
|
4
5
|
module Rack
|
5
6
|
module Test
|
6
|
-
|
7
7
|
# Wraps a Tempfile with a content type. Including one or more UploadedFile's
|
8
8
|
# in the params causes Rack::Test to build and issue a multipart request.
|
9
9
|
#
|
10
10
|
# Example:
|
11
11
|
# post "/photos", "file" => Rack::Test::UploadedFile.new("me.jpg", "image/jpeg")
|
12
12
|
class UploadedFile
|
13
|
-
|
14
13
|
# The filename, *not* including the path, of the "uploaded" file
|
15
14
|
attr_reader :original_filename
|
16
15
|
|
@@ -20,34 +19,67 @@ module Rack
|
|
20
19
|
# The content type of the "uploaded" file
|
21
20
|
attr_accessor :content_type
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
# Creates a new UploadedFile instance.
|
23
|
+
#
|
24
|
+
# @param content [IO, Pathname, String, StringIO] a path to a file, or an {IO} or {StringIO} object representing the
|
25
|
+
# file.
|
26
|
+
# @param content_type [String]
|
27
|
+
# @param binary [Boolean] an optional flag that indicates whether the file should be open in binary mode or not.
|
28
|
+
# @param original_filename [String] an optional parameter that provides the original filename if `content` is a StringIO
|
29
|
+
# object. Not used for other kind of `content` objects.
|
30
|
+
def initialize(content, content_type = 'text/plain', binary = false, original_filename: nil)
|
31
|
+
if original_filename
|
32
|
+
initialize_from_stringio(content, original_filename)
|
33
|
+
else
|
34
|
+
initialize_from_file_path(content)
|
35
|
+
end
|
26
36
|
@content_type = content_type
|
27
|
-
@original_filename = ::File.basename(path)
|
28
|
-
|
29
|
-
@tempfile = Tempfile.new(@original_filename)
|
30
|
-
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
31
37
|
@tempfile.binmode if binary
|
32
|
-
|
33
|
-
FileUtils.copy_file(path, @tempfile.path)
|
34
38
|
end
|
35
39
|
|
36
40
|
def path
|
37
|
-
|
41
|
+
tempfile.path
|
38
42
|
end
|
39
43
|
|
40
|
-
|
44
|
+
alias local_path path
|
41
45
|
|
42
46
|
def method_missing(method_name, *args, &block) #:nodoc:
|
43
|
-
|
47
|
+
tempfile.public_send(method_name, *args, &block)
|
44
48
|
end
|
45
49
|
|
46
|
-
def
|
47
|
-
|
50
|
+
def respond_to_missing?(method_name, include_private = false) #:nodoc:
|
51
|
+
tempfile.respond_to?(method_name, include_private) || super
|
48
52
|
end
|
49
53
|
|
50
|
-
|
54
|
+
def self.finalize(file)
|
55
|
+
proc { actually_finalize file }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.actually_finalize(file)
|
59
|
+
file.close
|
60
|
+
file.unlink
|
61
|
+
end
|
51
62
|
|
63
|
+
private
|
64
|
+
|
65
|
+
def initialize_from_stringio(stringio, original_filename)
|
66
|
+
@tempfile = stringio
|
67
|
+
@original_filename = original_filename || raise(ArgumentError, 'Missing `original_filename` for StringIO object')
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize_from_file_path(path)
|
71
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
72
|
+
|
73
|
+
@original_filename = ::File.basename(path)
|
74
|
+
extension = ::File.extname(@original_filename)
|
75
|
+
|
76
|
+
@tempfile = Tempfile.new([::File.basename(@original_filename, extension), extension])
|
77
|
+
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
78
|
+
|
79
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@tempfile))
|
80
|
+
|
81
|
+
FileUtils.copy_file(path, @tempfile.path)
|
82
|
+
end
|
83
|
+
end
|
52
84
|
end
|
53
85
|
end
|
data/lib/rack/test/utils.rb
CHANGED
@@ -1,38 +1,36 @@
|
|
1
1
|
module Rack
|
2
2
|
module Test
|
3
|
-
|
4
3
|
module Utils # :nodoc:
|
5
4
|
include Rack::Utils
|
5
|
+
extend Rack::Utils
|
6
6
|
|
7
7
|
def build_nested_query(value, prefix = nil)
|
8
8
|
case value
|
9
9
|
when Array
|
10
|
-
value.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
10
|
+
if value.empty?
|
11
|
+
"#{prefix}[]="
|
12
|
+
else
|
13
|
+
value.map do |v|
|
14
|
+
prefix = "#{prefix}[]" unless unescape(prefix) =~ /\[\]$/
|
15
|
+
build_nested_query(v, prefix.to_s)
|
16
|
+
end.join('&')
|
17
|
+
end
|
16
18
|
when Hash
|
17
19
|
value.map do |k, v|
|
18
20
|
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
19
|
-
end.join(
|
21
|
+
end.join('&')
|
20
22
|
when NilClass
|
21
23
|
prefix.to_s
|
22
24
|
else
|
23
25
|
"#{prefix}=#{escape(value)}"
|
24
26
|
end
|
25
27
|
end
|
26
|
-
|
27
28
|
module_function :build_nested_query
|
28
29
|
|
29
|
-
def build_multipart(params, first = true)
|
30
|
+
def build_multipart(params, first = true, multipart = false)
|
30
31
|
if first
|
31
|
-
unless params.is_a?(Hash)
|
32
|
-
raise ArgumentError, "value must be a Hash"
|
33
|
-
end
|
32
|
+
raise ArgumentError, 'value must be a Hash' unless params.is_a?(Hash)
|
34
33
|
|
35
|
-
multipart = false
|
36
34
|
query = lambda { |value|
|
37
35
|
case value
|
38
36
|
when Array
|
@@ -47,7 +45,7 @@ module Rack
|
|
47
45
|
return nil unless multipart
|
48
46
|
end
|
49
47
|
|
50
|
-
flattened_params =
|
48
|
+
flattened_params = {}
|
51
49
|
|
52
50
|
params.each do |key, value|
|
53
51
|
k = first ? key.to_s : "[#{key}]"
|
@@ -55,23 +53,21 @@ module Rack
|
|
55
53
|
case value
|
56
54
|
when Array
|
57
55
|
value.map do |v|
|
58
|
-
|
59
|
-
if (v.is_a?(Hash))
|
56
|
+
if v.is_a?(Hash)
|
60
57
|
nested_params = {}
|
61
|
-
build_multipart(v, false).each
|
58
|
+
build_multipart(v, false).each do |subkey, subvalue|
|
62
59
|
nested_params[subkey] = subvalue
|
63
|
-
|
60
|
+
end
|
64
61
|
flattened_params["#{k}[]"] ||= []
|
65
62
|
flattened_params["#{k}[]"] << nested_params
|
66
63
|
else
|
67
64
|
flattened_params["#{k}[]"] = value
|
68
65
|
end
|
69
|
-
|
70
66
|
end
|
71
67
|
when Hash
|
72
|
-
build_multipart(value, false).each
|
68
|
+
build_multipart(value, false).each do |subkey, subvalue|
|
73
69
|
flattened_params[k + subkey] = subvalue
|
74
|
-
|
70
|
+
end
|
75
71
|
else
|
76
72
|
flattened_params[k] = value
|
77
73
|
end
|
@@ -83,27 +79,28 @@ module Rack
|
|
83
79
|
flattened_params
|
84
80
|
end
|
85
81
|
end
|
86
|
-
|
87
82
|
module_function :build_multipart
|
88
83
|
|
89
|
-
|
84
|
+
private
|
85
|
+
|
90
86
|
def build_parts(parameters)
|
91
87
|
get_parts(parameters).join + "--#{MULTIPART_BOUNDARY}--\r"
|
92
88
|
end
|
89
|
+
module_function :build_parts
|
93
90
|
|
94
91
|
def get_parts(parameters)
|
95
|
-
parameters.map
|
96
|
-
if name =~ /\[\]\Z/ && value.is_a?(Array) && value.all? {|v| v.is_a?(Hash)}
|
97
|
-
value.map
|
92
|
+
parameters.map do |name, value|
|
93
|
+
if name =~ /\[\]\Z/ && value.is_a?(Array) && value.all? { |v| v.is_a?(Hash) }
|
94
|
+
value.map do |hash|
|
98
95
|
new_value = {}
|
99
|
-
hash.each { |k, v| new_value[name+k] = v }
|
96
|
+
hash.each { |k, v| new_value[name + k] = v }
|
100
97
|
get_parts(new_value).join
|
101
|
-
|
98
|
+
end.join
|
102
99
|
else
|
103
100
|
if value.respond_to?(:original_filename)
|
104
101
|
build_file_part(name, value)
|
105
102
|
|
106
|
-
elsif value.is_a?(Array)
|
103
|
+
elsif value.is_a?(Array) && value.all? { |v| v.respond_to?(:original_filename) }
|
107
104
|
value.map do |v|
|
108
105
|
build_file_part(name, v)
|
109
106
|
end.join
|
@@ -113,15 +110,14 @@ module Rack
|
|
113
110
|
Rack::Test.encoding_aware_strings? ? primitive_part.force_encoding('BINARY') : primitive_part
|
114
111
|
end
|
115
112
|
end
|
116
|
-
|
113
|
+
end
|
117
114
|
end
|
115
|
+
module_function :get_parts
|
118
116
|
|
119
117
|
def build_primitive_part(parameter_name, value)
|
120
|
-
unless value.is_a? Array
|
121
|
-
value = [value]
|
122
|
-
end
|
118
|
+
value = [value] unless value.is_a? Array
|
123
119
|
value.map do |v|
|
124
|
-
<<-EOF
|
120
|
+
<<-EOF
|
125
121
|
--#{MULTIPART_BOUNDARY}\r
|
126
122
|
Content-Disposition: form-data; name="#{parameter_name}"\r
|
127
123
|
\r
|
@@ -129,22 +125,20 @@ Content-Disposition: form-data; name="#{parameter_name}"\r
|
|
129
125
|
EOF
|
130
126
|
end.join
|
131
127
|
end
|
128
|
+
module_function :build_primitive_part
|
132
129
|
|
133
130
|
def build_file_part(parameter_name, uploaded_file)
|
134
|
-
|
135
|
-
|
136
|
-
<<-EOF
|
131
|
+
uploaded_file.set_encoding(Encoding::BINARY) if uploaded_file.respond_to?(:set_encoding)
|
132
|
+
<<-EOF
|
137
133
|
--#{MULTIPART_BOUNDARY}\r
|
138
134
|
Content-Disposition: form-data; name="#{parameter_name}"; filename="#{escape(uploaded_file.original_filename)}"\r
|
139
135
|
Content-Type: #{uploaded_file.content_type}\r
|
140
|
-
Content-Length: #{
|
136
|
+
Content-Length: #{uploaded_file.size}\r
|
141
137
|
\r
|
142
|
-
#{
|
138
|
+
#{uploaded_file.read}\r
|
143
139
|
EOF
|
144
|
-
end
|
145
140
|
end
|
146
|
-
|
141
|
+
module_function :build_file_part
|
147
142
|
end
|
148
|
-
|
149
143
|
end
|
150
144
|
end
|