rack-test 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,20 @@
1
+ == 0.3.0 / 2009-05-17
2
+
3
+ * Major enhancements
4
+
5
+ * Ruby 1.9 compatible (Simon Rozet, Michael Fellinger)
6
+
7
+ * Minor enhancements
8
+
9
+ * Add CookieJar#[] and CookieJar#[]= methods
10
+ * Make the default host configurable
11
+ * Use Rack::Lint and fix errors (Simon Rozet)
12
+ * Extract Rack::MockSession from Rack::Test::Session to handle tracking
13
+ the last request and response and the cookie jar
14
+ * Add #set_cookie and #clear_cookies methods
15
+ * Rename #authorize to #basic_authorize (#authorize remains as an alias)
16
+ (Simon Rozet)
17
+
1
18
  == 0.2.0 / 2009-04-26
2
19
 
3
20
  Because #last_response is now a MockResponse instead of a Rack::Response,
@@ -0,0 +1,50 @@
1
+ module Rack
2
+
3
+ class MockSession
4
+ attr_writer :cookie_jar
5
+ attr_reader :last_response
6
+
7
+ def initialize(app, default_host = Rack::Test::DEFAULT_HOST)
8
+ @app = app
9
+ @default_host = default_host
10
+ end
11
+
12
+ def clear_cookies
13
+ @cookie_jar = Rack::Test::CookieJar.new([], @default_host)
14
+ end
15
+
16
+ def set_cookie(cookie, uri = nil)
17
+ cookie_jar.merge(cookie, uri)
18
+ end
19
+
20
+ def request(uri, env)
21
+ env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
22
+ @last_request = Rack::Request.new(env)
23
+ status, headers, body = @app.call(@last_request.env)
24
+ @last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
25
+ cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
26
+
27
+ @last_response
28
+ end
29
+
30
+ # Return the last request issued in the session. Raises an error if no
31
+ # requests have been sent yet.
32
+ def last_request
33
+ raise Rack::Test::Error.new("No request yet. Request a page first.") unless @last_request
34
+ @last_request
35
+ end
36
+
37
+ # Return the last response received in the session. Raises an error if
38
+ # no requests have been sent yet.
39
+ def last_response
40
+ raise Rack::Test::Error.new("No response yet. Request a page first.") unless @last_response
41
+ @last_response
42
+ end
43
+
44
+ def cookie_jar
45
+ @cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -4,7 +4,9 @@ end
4
4
 
5
5
  require "uri"
6
6
  require "rack"
7
+ require "rack/mock_session"
7
8
  require "rack/test/cookie_jar"
9
+ require "rack/test/mock_digest_request"
8
10
  require "rack/test/utils"
9
11
  require "rack/test/methods"
10
12
  require "rack/test/uploaded_file"
@@ -12,44 +14,25 @@ require "rack/test/uploaded_file"
12
14
  module Rack
13
15
  module Test
14
16
 
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
35
- end
36
-
37
- VERSION = "0.2.0"
17
+ VERSION = "0.3.0"
38
18
 
19
+ DEFAULT_HOST = "example.org"
39
20
  MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
40
21
 
41
22
  # The common base class for exceptions raised by Rack::Test
42
23
  class Error < StandardError; end
43
24
 
44
25
  class Session
26
+ extend Forwardable
45
27
  include Rack::Test::Utils
46
28
 
47
- # Initialize a new session for the given Rack app
48
- def initialize(app)
49
- raise ArgumentError.new("app must respond_to?(:call)") unless app.respond_to?(:call)
29
+ def_delegators :@rack_mock_session, :clear_cookies, :set_cookie, :last_response, :last_request
50
30
 
31
+ # Initialize a new session for the given Rack app
32
+ def initialize(app, default_host = DEFAULT_HOST)
51
33
  @headers = {}
52
- @app = app
34
+ @default_host = default_host
35
+ @rack_mock_session = Rack::MockSession.new(app, default_host)
53
36
  end
54
37
 
55
38
  # Issue a GET request for the given URI with the given params and Rack
@@ -129,12 +112,14 @@ module Rack
129
112
  # included in subsequent requests in the HTTP_AUTHORIZATION header.
130
113
  #
131
114
  # Example:
132
- # authorize "bryan", "secret"
133
- def authorize(username, password)
115
+ # basic_authorize "bryan", "secret"
116
+ def basic_authorize(username, password)
134
117
  encoded_login = ["#{username}:#{password}"].pack("m*")
135
118
  header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
136
119
  end
137
120
 
121
+ alias_method :authorize, :basic_authorize
122
+
138
123
  def digest_authorize(username, password)
139
124
  @digest_username = username
140
125
  @digest_password = password
@@ -151,27 +136,11 @@ module Rack
151
136
  get(last_response["Location"])
152
137
  end
153
138
 
154
- # Return the last request issued in the session. Raises an error if no
155
- # requests have been sent yet.
156
- def last_request
157
- raise Error.new("No request yet. Request a page first.") unless @last_request
158
-
159
- @last_request
160
- end
161
-
162
- # Return the last response received in the session. Raises an error if
163
- # no requests have been sent yet.
164
- def last_response
165
- raise Error.new("No response yet. Request a page first.") unless @last_response
166
-
167
- @last_response
168
- end
169
-
170
139
  private
171
140
 
172
141
  def env_for(path, env)
173
142
  uri = URI.parse(path)
174
- uri.host ||= "example.org"
143
+ uri.host ||= @default_host
175
144
 
176
145
  env = default_env.merge(env)
177
146
 
@@ -181,9 +150,8 @@ module Rack
181
150
  if (env[:method] == "POST" || env["REQUEST_METHOD"] == "POST") && !env.has_key?(:input)
182
151
  env["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
183
152
 
184
- multipart = (env[:params] || {}).any? do |k, v|
185
- UploadedFile === v
186
- end
153
+ multipart = (Hash === env[:params]) &&
154
+ env[:params].any? { |_, v| UploadedFile === v }
187
155
 
188
156
  if multipart
189
157
  env[:input] = multipart_body(env.delete(:params))
@@ -200,61 +168,51 @@ module Rack
200
168
  uri.query = requestify(params)
201
169
 
202
170
  if env.has_key?(:cookie)
203
- # Add the cookies explicitly set by the user
204
- env["HTTP_COOKIE"] = cookie_jar.merge(uri, env.delete(:cookie)).for(uri)
205
- else
206
- env["HTTP_COOKIE"] = cookie_jar.for(uri)
171
+ set_cookie(env.delete(:cookie), uri)
207
172
  end
208
173
 
209
174
  Rack::MockRequest.env_for(uri.to_s, env)
210
175
  end
211
176
 
212
- def cookie_jar
213
- @cookie_jar || Rack::Test::CookieJar.new
214
- end
215
-
216
177
  def process_request(uri, env)
217
178
  uri = URI.parse(uri)
218
- uri.host ||= "example.org"
219
-
220
- @last_request = Rack::Request.new(env)
221
-
222
- status, headers, body = @app.call(@last_request.env)
223
- @last_response = MockResponse.new(status, headers, body, env["rack.errors"])
179
+ uri.host ||= @default_host
224
180
 
225
- @cookie_jar = cookie_jar.merge(uri, last_response.headers["Set-Cookie"])
181
+ @rack_mock_session.request(uri, env)
226
182
 
227
183
  if retry_with_digest_auth?(env)
228
- auth_env = env.merge("HTTP_AUTHORIZATION" => digest_auth_header,
229
- "rack-test.digest_auth_retry" => true)
184
+ auth_env = env.merge({
185
+ "HTTP_AUTHORIZATION" => digest_auth_header,
186
+ "rack-test.digest_auth_retry" => true
187
+ })
230
188
  auth_env.delete('rack.request')
231
189
  process_request(uri.path, auth_env)
232
190
  else
233
- yield @last_response if block_given?
191
+ yield last_response if block_given?
234
192
 
235
- @last_response
193
+ last_response
236
194
  end
237
195
  end
238
196
 
239
197
  def digest_auth_header
240
- challenge = @last_response["WWW-Authenticate"].split(" ", 2).last
198
+ challenge = last_response["WWW-Authenticate"].split(" ", 2).last
241
199
  params = Rack::Auth::Digest::Params.parse(challenge)
242
200
 
243
201
  params.merge!({
244
202
  "username" => @digest_username,
245
203
  "nc" => "00000001",
246
204
  "cnonce" => "nonsensenonce",
247
- "uri" => @last_request.path_info,
248
- "method" => @last_request.env["REQUEST_METHOD"],
205
+ "uri" => last_request.path_info,
206
+ "method" => last_request.env["REQUEST_METHOD"],
249
207
  })
250
208
 
251
209
  params["response"] = MockDigestRequest.new(params).response(@digest_password)
252
210
 
253
- return "Digest #{params}"
211
+ "Digest #{params}"
254
212
  end
255
213
 
256
214
  def retry_with_digest_auth?(env)
257
- @last_response.status == 401 &&
215
+ last_response.status == 401 &&
258
216
  digest_auth_configured? &&
259
217
  !env["rack-test.digest_auth_retry"]
260
218
  end
@@ -1,3 +1,4 @@
1
+ require "uri"
1
2
  module Rack
2
3
  module Test
3
4
 
@@ -8,16 +9,22 @@ module Rack
8
9
  attr_reader :name, :value
9
10
 
10
11
  # :api: private
11
- def initialize(raw, default_host)
12
+ def initialize(raw, uri = nil, default_host = DEFAULT_HOST)
13
+ @default_host = default_host
14
+ uri ||= default_uri
15
+
12
16
  # separate the name / value pair from the cookie options
13
17
  @name_value_raw, options = raw.split(/[;,] */n, 2)
14
18
 
15
19
  @name, @value = parse_query(@name_value_raw, ';').to_a.first
16
20
  @options = parse_query(options, ';')
17
21
 
18
- @options.delete_if { |k, v| !v || v.empty? }
22
+ @options["domain"] ||= (uri.host || default_host)
23
+ @options["path"] ||= uri.path.sub(/\/[^\/]*\Z/, "")
24
+ end
19
25
 
20
- @options["domain"] ||= default_host
26
+ def replaces?(other)
27
+ [name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
21
28
  end
22
29
 
23
30
  # :api: private
@@ -35,9 +42,13 @@ module Rack
35
42
  @options["domain"]
36
43
  end
37
44
 
45
+ def secure?
46
+ @options.has_key?("secure")
47
+ end
48
+
38
49
  # :api: private
39
50
  def path
40
- @options["path"] || "/"
51
+ @options["path"].strip || "/"
41
52
  end
42
53
 
43
54
  # :api: private
@@ -52,7 +63,14 @@ module Rack
52
63
 
53
64
  # :api: private
54
65
  def valid?(uri)
55
- uri.host =~ Regexp.new("#{Regexp.escape(domain)}$") &&
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) &&
56
74
  uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
57
75
  end
58
76
 
@@ -67,40 +85,70 @@ module Rack
67
85
  [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
68
86
  end
69
87
 
88
+ protected
89
+
90
+ def default_uri
91
+ URI.parse("//" + @default_host + "/")
92
+ end
93
+
70
94
  end
71
95
 
72
96
  class CookieJar
73
97
 
74
98
  # :api: private
75
- def initialize(cookies = [])
76
- @jar = cookies
77
- @jar.sort!
99
+ def initialize(cookies = [], default_host = DEFAULT_HOST)
100
+ @default_host = default_host
101
+ @cookies = cookies
102
+ @cookies.sort!
78
103
  end
79
104
 
80
- def merge(uri, raw_cookies)
81
- return self unless raw_cookies
105
+ def [](name)
106
+ cookies = hash_for(nil)
107
+ # TODO: Should be case insensitive
108
+ cookies[name] && cookies[name].value
109
+ end
82
110
 
83
- # Initialize all the the received cookies
84
- cookies = []
85
- raw_cookies.each do |raw|
86
- c = Cookie.new(raw, uri.host)
87
- cookies << c if c.valid?(uri)
88
- end
111
+ def []=(name, value)
112
+ # TODO: needs proper escaping
113
+ merge("#{name}=#{value}")
114
+ end
89
115
 
90
- # Remove all the cookies that will be updated
91
- new_jar = @jar.reject do |existing|
92
- cookies.find do |c|
93
- [c.name, c.domain, c.path] == [existing.name, existing.domain, existing.path]
94
- end
116
+ def merge(raw_cookies, uri = nil)
117
+ return unless raw_cookies
118
+
119
+ raw_cookies.each_line do |raw_cookie|
120
+ cookie = Cookie.new(raw_cookie, uri, @default_host)
121
+ self << cookie if cookie.valid?(uri)
95
122
  end
123
+ end
96
124
 
97
- new_jar.concat cookies
125
+ def <<(new_cookie)
126
+ @cookies.reject! do |existing_cookie|
127
+ new_cookie.replaces?(existing_cookie)
128
+ end
98
129
 
99
- return self.class.new(new_jar)
130
+ @cookies << new_cookie
131
+ @cookies.sort!
100
132
  end
101
133
 
102
134
  # :api: private
103
135
  def for(uri)
136
+ hash_for(uri).values.map { |c| c.raw }.join(';')
137
+ end
138
+
139
+ def to_hash
140
+ cookies = {}
141
+
142
+ hash_for(nil).each do |name, cookie|
143
+ cookies[name] = cookie.value
144
+ end
145
+
146
+ return cookies
147
+ end
148
+
149
+ protected
150
+
151
+ def hash_for(uri = nil)
104
152
  cookies = {}
105
153
 
106
154
  # The cookies are sorted by most specific first. So, we loop through
@@ -108,11 +156,11 @@ module Rack
108
156
  # the cookie can be sent to the current URI. It's added to the hash
109
157
  # so that when we are done, the cookies will be unique by name and
110
158
  # we'll have grabbed the most specific to the URI.
111
- @jar.each do |cookie|
112
- cookies[cookie.name] = cookie.raw if cookie.matches?(uri)
159
+ @cookies.each do |cookie|
160
+ cookies[cookie.name] = cookie if cookie.matches?(uri)
113
161
  end
114
162
 
115
- cookies.values.join(';')
163
+ return cookies
116
164
  end
117
165
 
118
166
  end
@@ -9,20 +9,31 @@ module Rack
9
9
  @_rack_test_session ||= Rack::Test::Session.new(app)
10
10
  end
11
11
 
12
+ def rack_mock_session
13
+ @_rack_mock_session ||= Rack::MockSession.new(app)
14
+ end
15
+
12
16
  METHODS = [
13
17
  :request,
18
+
14
19
  # HTTP verbs
15
20
  :get,
16
21
  :post,
17
22
  :put,
18
23
  :delete,
19
24
  :head,
25
+
20
26
  # Redirects
21
27
  :follow_redirect!,
28
+
22
29
  # Header-related features
23
30
  :header,
31
+ :set_cookie,
32
+ :clear_cookies,
24
33
  :authorize,
34
+ :basic_authorize,
25
35
  :digest_authorize,
36
+
26
37
  # Expose the last request and response
27
38
  :last_response,
28
39
  :last_request
@@ -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
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.2.0
4
+ version: 0.3.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-04-26 00:00:00 -04:00
12
+ date: 2009-05-17 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -26,16 +26,18 @@ files:
26
26
  - History.txt
27
27
  - Rakefile
28
28
  - README.rdoc
29
- - lib/rack
30
- - lib/rack/test
29
+ - lib/rack/mock_session.rb
31
30
  - lib/rack/test/cookie_jar.rb
32
31
  - lib/rack/test/methods.rb
32
+ - lib/rack/test/mock_digest_request.rb
33
33
  - lib/rack/test/uploaded_file.rb
34
34
  - lib/rack/test/utils.rb
35
35
  - lib/rack/test.rb
36
36
  - MIT-LICENSE.txt
37
37
  has_rdoc: true
38
38
  homepage: http://github.com/brynary/rack-test
39
+ licenses: []
40
+
39
41
  post_install_message:
40
42
  rdoc_options: []
41
43
 
@@ -56,9 +58,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
56
58
  requirements: []
57
59
 
58
60
  rubyforge_project:
59
- rubygems_version: 1.3.1
61
+ rubygems_version: 1.3.3
60
62
  signing_key:
61
- specification_version: 2
63
+ specification_version: 3
62
64
  summary: Simple testing API built on Rack
63
65
  test_files: []
64
66