josh-rack-test 0.4.1
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/.document +4 -0
- data/.gitignore +3 -0
- data/History.txt +75 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.rdoc +57 -0
- data/Rakefile +53 -0
- data/lib/rack/mock_session.rb +57 -0
- data/lib/rack/test.rb +245 -0
- data/lib/rack/test/cookie_jar.rb +170 -0
- data/lib/rack/test/methods.rb +73 -0
- data/lib/rack/test/mock_digest_request.rb +27 -0
- data/lib/rack/test/uploaded_file.rb +36 -0
- data/lib/rack/test/utils.rb +99 -0
- data/rack-test.gemspec +70 -0
- data/spec/fixtures/config.ru +3 -0
- data/spec/fixtures/fake_app.rb +115 -0
- data/spec/fixtures/foo.txt +1 -0
- data/spec/rack/test/cookie_spec.rb +182 -0
- data/spec/rack/test/digest_auth_spec.rb +48 -0
- data/spec/rack/test/multipart_spec.rb +85 -0
- data/spec/rack/test/utils_spec.rb +93 -0
- data/spec/rack/test_spec.rb +368 -0
- data/spec/rcov.opts +1 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +48 -0
- metadata +84 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
require "uri"
|
2
|
+
module Rack
|
3
|
+
module Test
|
4
|
+
|
5
|
+
class Cookie
|
6
|
+
include Rack::Utils
|
7
|
+
|
8
|
+
# :api: private
|
9
|
+
attr_reader :name, :value
|
10
|
+
|
11
|
+
# :api: private
|
12
|
+
def initialize(raw, uri = nil, default_host = DEFAULT_HOST)
|
13
|
+
@default_host = default_host
|
14
|
+
uri ||= default_uri
|
15
|
+
|
16
|
+
# separate the name / value pair from the cookie options
|
17
|
+
@name_value_raw, options = raw.split(/[;,] */n, 2)
|
18
|
+
|
19
|
+
@name, @value = parse_query(@name_value_raw, ';').to_a.first
|
20
|
+
@options = parse_query(options, ';')
|
21
|
+
|
22
|
+
@options["domain"] ||= (uri.host || default_host)
|
23
|
+
@options["path"] ||= uri.path.sub(/\/[^\/]*\Z/, "")
|
24
|
+
end
|
25
|
+
|
26
|
+
def replaces?(other)
|
27
|
+
[name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
|
28
|
+
end
|
29
|
+
|
30
|
+
# :api: private
|
31
|
+
def raw
|
32
|
+
@name_value_raw
|
33
|
+
end
|
34
|
+
|
35
|
+
# :api: private
|
36
|
+
def empty?
|
37
|
+
@value.nil? || @value.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
# :api: private
|
41
|
+
def domain
|
42
|
+
@options["domain"]
|
43
|
+
end
|
44
|
+
|
45
|
+
def secure?
|
46
|
+
@options.has_key?("secure")
|
47
|
+
end
|
48
|
+
|
49
|
+
# :api: private
|
50
|
+
def path
|
51
|
+
@options["path"].strip || "/"
|
52
|
+
end
|
53
|
+
|
54
|
+
# :api: private
|
55
|
+
def expires
|
56
|
+
Time.parse(@options["expires"]) if @options["expires"]
|
57
|
+
end
|
58
|
+
|
59
|
+
# :api: private
|
60
|
+
def expired?
|
61
|
+
expires && expires < Time.now
|
62
|
+
end
|
63
|
+
|
64
|
+
# :api: private
|
65
|
+
def valid?(uri)
|
66
|
+
uri ||= default_uri
|
67
|
+
|
68
|
+
if uri.host.nil?
|
69
|
+
uri.host = @default_host
|
70
|
+
end
|
71
|
+
|
72
|
+
(!secure? || (secure? && uri.scheme == "https")) &&
|
73
|
+
uri.host =~ Regexp.new("#{Regexp.escape(domain)}$", Regexp::IGNORECASE) &&
|
74
|
+
uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
|
75
|
+
end
|
76
|
+
|
77
|
+
# :api: private
|
78
|
+
def matches?(uri)
|
79
|
+
! expired? && valid?(uri)
|
80
|
+
end
|
81
|
+
|
82
|
+
# :api: private
|
83
|
+
def <=>(other)
|
84
|
+
# Orders the cookies from least specific to most
|
85
|
+
[name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def default_uri
|
91
|
+
URI.parse("//" + @default_host + "/")
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
class CookieJar
|
97
|
+
|
98
|
+
# :api: private
|
99
|
+
def initialize(cookies = [], default_host = DEFAULT_HOST)
|
100
|
+
@default_host = default_host
|
101
|
+
@cookies = cookies
|
102
|
+
@cookies.sort!
|
103
|
+
end
|
104
|
+
|
105
|
+
def [](name)
|
106
|
+
cookies = hash_for(nil)
|
107
|
+
# TODO: Should be case insensitive
|
108
|
+
cookies[name] && cookies[name].value
|
109
|
+
end
|
110
|
+
|
111
|
+
def []=(name, value)
|
112
|
+
# TODO: needs proper escaping
|
113
|
+
merge("#{name}=#{value}")
|
114
|
+
end
|
115
|
+
|
116
|
+
def merge(raw_cookies, uri = nil)
|
117
|
+
return unless raw_cookies
|
118
|
+
|
119
|
+
raw_cookies = raw_cookies.split("\n") if raw_cookies.is_a? String
|
120
|
+
raw_cookies.each do |raw_cookie|
|
121
|
+
cookie = Cookie.new(raw_cookie, uri, @default_host)
|
122
|
+
self << cookie if cookie.valid?(uri)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def <<(new_cookie)
|
127
|
+
@cookies.reject! do |existing_cookie|
|
128
|
+
new_cookie.replaces?(existing_cookie)
|
129
|
+
end
|
130
|
+
|
131
|
+
@cookies << new_cookie
|
132
|
+
@cookies.sort!
|
133
|
+
end
|
134
|
+
|
135
|
+
# :api: private
|
136
|
+
def for(uri)
|
137
|
+
hash_for(uri).values.map { |c| c.raw }.join(';')
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_hash
|
141
|
+
cookies = {}
|
142
|
+
|
143
|
+
hash_for(nil).each do |name, cookie|
|
144
|
+
cookies[name] = cookie.value
|
145
|
+
end
|
146
|
+
|
147
|
+
return cookies
|
148
|
+
end
|
149
|
+
|
150
|
+
protected
|
151
|
+
|
152
|
+
def hash_for(uri = nil)
|
153
|
+
cookies = {}
|
154
|
+
|
155
|
+
# The cookies are sorted by most specific first. So, we loop through
|
156
|
+
# all the cookies in order and add it to a hash by cookie name if
|
157
|
+
# the cookie can be sent to the current URI. It's added to the hash
|
158
|
+
# so that when we are done, the cookies will be unique by name and
|
159
|
+
# we'll have grabbed the most specific to the URI.
|
160
|
+
@cookies.each do |cookie|
|
161
|
+
cookies[cookie.name] = cookie if cookie.matches?(uri)
|
162
|
+
end
|
163
|
+
|
164
|
+
return cookies
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Test
|
5
|
+
module Methods
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def rack_mock_session(name = :default)
|
9
|
+
return build_rack_mock_session unless name
|
10
|
+
|
11
|
+
@_rack_mock_sessions ||= {}
|
12
|
+
@_rack_mock_sessions[name] ||= build_rack_mock_session
|
13
|
+
end
|
14
|
+
|
15
|
+
def build_rack_mock_session
|
16
|
+
Rack::MockSession.new(app)
|
17
|
+
end
|
18
|
+
|
19
|
+
def rack_test_session(name = :default)
|
20
|
+
return build_rack_test_session(name) unless name
|
21
|
+
|
22
|
+
@_rack_test_sessions ||= {}
|
23
|
+
@_rack_test_sessions[name] ||= build_rack_test_session(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_rack_test_session(name)
|
27
|
+
Rack::Test::Session.new(rack_mock_session(name))
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_session
|
31
|
+
rack_test_session(_current_session_names.last)
|
32
|
+
end
|
33
|
+
|
34
|
+
def with_session(name)
|
35
|
+
_current_session_names.push(name)
|
36
|
+
yield rack_test_session(name)
|
37
|
+
_current_session_names.pop
|
38
|
+
end
|
39
|
+
|
40
|
+
def _current_session_names
|
41
|
+
@_current_session_names ||= [:default]
|
42
|
+
end
|
43
|
+
|
44
|
+
METHODS = [
|
45
|
+
:request,
|
46
|
+
|
47
|
+
# HTTP verbs
|
48
|
+
:get,
|
49
|
+
:post,
|
50
|
+
:put,
|
51
|
+
:delete,
|
52
|
+
:head,
|
53
|
+
|
54
|
+
# Redirects
|
55
|
+
:follow_redirect!,
|
56
|
+
|
57
|
+
# Header-related features
|
58
|
+
:header,
|
59
|
+
:set_cookie,
|
60
|
+
:clear_cookies,
|
61
|
+
:authorize,
|
62
|
+
:basic_authorize,
|
63
|
+
:digest_authorize,
|
64
|
+
|
65
|
+
# Expose the last request and response
|
66
|
+
:last_response,
|
67
|
+
:last_request
|
68
|
+
]
|
69
|
+
|
70
|
+
def_delegators :current_session, *METHODS
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rack
|
2
|
+
module Test
|
3
|
+
|
4
|
+
class MockDigestRequest
|
5
|
+
def initialize(params)
|
6
|
+
@params = params
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(sym)
|
10
|
+
if @params.has_key? k = sym.to_s
|
11
|
+
return @params[k]
|
12
|
+
end
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def method
|
18
|
+
@params['method']
|
19
|
+
end
|
20
|
+
|
21
|
+
def response(password)
|
22
|
+
Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
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
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Rack
|
2
|
+
module Test
|
3
|
+
|
4
|
+
module Utils
|
5
|
+
include Rack::Utils
|
6
|
+
|
7
|
+
def build_nested_query(value, prefix = nil)
|
8
|
+
case value
|
9
|
+
when Array
|
10
|
+
value.map do |v|
|
11
|
+
build_nested_query(v, "#{prefix}[]")
|
12
|
+
end.join("&")
|
13
|
+
when Hash
|
14
|
+
value.map do |k, v|
|
15
|
+
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
16
|
+
end.join("&")
|
17
|
+
else
|
18
|
+
"#{prefix}=#{escape(value)}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module_function :build_nested_query
|
23
|
+
|
24
|
+
def build_multipart(params, first = true)
|
25
|
+
if first
|
26
|
+
unless params.is_a?(Hash)
|
27
|
+
raise ArgumentError, "value must be a Hash"
|
28
|
+
end
|
29
|
+
|
30
|
+
multipart = false
|
31
|
+
query = lambda { |value|
|
32
|
+
case value
|
33
|
+
when Array
|
34
|
+
value.each(&query)
|
35
|
+
when Hash
|
36
|
+
value.values.each(&query)
|
37
|
+
when UploadedFile
|
38
|
+
multipart = true
|
39
|
+
end
|
40
|
+
}
|
41
|
+
params.values.each(&query)
|
42
|
+
return nil unless multipart
|
43
|
+
end
|
44
|
+
|
45
|
+
flattened_params = Hash.new
|
46
|
+
|
47
|
+
params.each do |key, value|
|
48
|
+
k = first ? key.to_s : "[#{key}]"
|
49
|
+
|
50
|
+
case value
|
51
|
+
when Array
|
52
|
+
value.map { |v|
|
53
|
+
build_multipart(v, false).each { |subkey, subvalue|
|
54
|
+
flattened_params["#{k}[]#{subkey}"] = subvalue
|
55
|
+
}
|
56
|
+
}
|
57
|
+
when Hash
|
58
|
+
build_multipart(value, false).each { |subkey, subvalue|
|
59
|
+
flattened_params[k + subkey] = subvalue
|
60
|
+
}
|
61
|
+
else
|
62
|
+
flattened_params[k] = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if first
|
67
|
+
flattened_params.map { |name, file|
|
68
|
+
if file.respond_to?(:original_filename)
|
69
|
+
::File.open(file.path, "rb") do |f|
|
70
|
+
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
71
|
+
<<-EOF
|
72
|
+
--#{MULTIPART_BOUNDARY}\r
|
73
|
+
Content-Disposition: form-data; name="#{name}"; filename="#{escape(file.original_filename)}"\r
|
74
|
+
Content-Type: #{file.content_type}\r
|
75
|
+
Content-Length: #{::File.stat(file.path).size}\r
|
76
|
+
\r
|
77
|
+
#{f.read}\r
|
78
|
+
EOF
|
79
|
+
end
|
80
|
+
else
|
81
|
+
<<-EOF
|
82
|
+
--#{MULTIPART_BOUNDARY}\r
|
83
|
+
Content-Disposition: form-data; name="#{name}"\r
|
84
|
+
\r
|
85
|
+
#{file}\r
|
86
|
+
EOF
|
87
|
+
end
|
88
|
+
}.join + "--#{MULTIPART_BOUNDARY}--\r"
|
89
|
+
else
|
90
|
+
flattened_params
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module_function :build_multipart
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
data/rack-test.gemspec
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rack-test}
|
8
|
+
s.version = "0.4.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Bryan Helmkamp"]
|
12
|
+
s.date = %q{2009-08-06}
|
13
|
+
s.email = %q{bryan@brynary.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"MIT-LICENSE.txt",
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".document",
|
20
|
+
".gitignore",
|
21
|
+
"History.txt",
|
22
|
+
"MIT-LICENSE.txt",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"lib/rack/mock_session.rb",
|
26
|
+
"lib/rack/test.rb",
|
27
|
+
"lib/rack/test/cookie_jar.rb",
|
28
|
+
"lib/rack/test/methods.rb",
|
29
|
+
"lib/rack/test/mock_digest_request.rb",
|
30
|
+
"lib/rack/test/uploaded_file.rb",
|
31
|
+
"lib/rack/test/utils.rb",
|
32
|
+
"rack-test.gemspec",
|
33
|
+
"spec/fixtures/config.ru",
|
34
|
+
"spec/fixtures/fake_app.rb",
|
35
|
+
"spec/fixtures/foo.txt",
|
36
|
+
"spec/rack/test/cookie_spec.rb",
|
37
|
+
"spec/rack/test/digest_auth_spec.rb",
|
38
|
+
"spec/rack/test/multipart_spec.rb",
|
39
|
+
"spec/rack/test/utils_spec.rb",
|
40
|
+
"spec/rack/test_spec.rb",
|
41
|
+
"spec/rcov.opts",
|
42
|
+
"spec/spec.opts",
|
43
|
+
"spec/spec_helper.rb"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/brynary/rack-test}
|
46
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubyforge_project = %q{rack-test}
|
49
|
+
s.rubygems_version = %q{1.3.4}
|
50
|
+
s.summary = %q{Simple testing API built on Rack}
|
51
|
+
s.test_files = [
|
52
|
+
"spec/fixtures/fake_app.rb",
|
53
|
+
"spec/rack/test/cookie_spec.rb",
|
54
|
+
"spec/rack/test/digest_auth_spec.rb",
|
55
|
+
"spec/rack/test/multipart_spec.rb",
|
56
|
+
"spec/rack/test/utils_spec.rb",
|
57
|
+
"spec/rack/test_spec.rb",
|
58
|
+
"spec/spec_helper.rb"
|
59
|
+
]
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
63
|
+
s.specification_version = 3
|
64
|
+
|
65
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
66
|
+
else
|
67
|
+
end
|
68
|
+
else
|
69
|
+
end
|
70
|
+
end
|