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 ADDED
@@ -0,0 +1,4 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ History.txt
4
+ MIT-LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg
2
+ doc
3
+ coverage
data/History.txt ADDED
@@ -0,0 +1,75 @@
1
+ == Git
2
+
3
+ * Minor enhancements
4
+
5
+ * Stringify and upcase request method (e.g. :post => "POST") (Josh Peek)
6
+
7
+ * Bug fixes
8
+
9
+ * Requiring Rack-Test never modifies the Ruby load path anymore (Josh Peek)
10
+ * Fixed using multiple cookies in a string on Ruby 1.8 (Tuomas Kareinen and Hermanni Hyytiälä)
11
+
12
+ == 0.4.1 / 2009-08-06
13
+
14
+ * Minor enhancements
15
+
16
+ * Support initializing a Rack::Test::Session with an app in addition to
17
+ a Rack::MockSession
18
+ * Allow CONTENT_TYPE to be specified in the env and not overwritten when
19
+ sending a POST or PUT
20
+
21
+ == 0.4.0 / 2009-06-25
22
+
23
+ * Minor enhancements
24
+
25
+ * Expose hook for building Rack::MockSessions for frameworks that need
26
+ to configure them before use
27
+ * Support passing in arrays of raw cookies in addition to a newline
28
+ separated string
29
+ * Support after_request callbacks in MockSession for things like running
30
+ background jobs
31
+ * Allow multiple named sessions using with_session
32
+ * Initialize Rack::Test::Sessions with Rack::MockSessions instead of apps.
33
+ This change should help integration with other Ruby web frameworks
34
+ (like Merb).
35
+ * Support sending bodies for PUT requests (Larry Diehl)
36
+
37
+ == 0.3.0 / 2009-05-17
38
+
39
+ * Major enhancements
40
+
41
+ * Ruby 1.9 compatible (Simon Rozet, Michael Fellinger)
42
+
43
+ * Minor enhancements
44
+
45
+ * Add CookieJar#[] and CookieJar#[]= methods
46
+ * Make the default host configurable
47
+ * Use Rack::Lint and fix errors (Simon Rozet)
48
+ * Extract Rack::MockSession from Rack::Test::Session to handle tracking
49
+ the last request and response and the cookie jar
50
+ * Add #set_cookie and #clear_cookies methods
51
+ * Rename #authorize to #basic_authorize (#authorize remains as an alias)
52
+ (Simon Rozet)
53
+
54
+ == 0.2.0 / 2009-04-26
55
+
56
+ Because #last_response is now a MockResponse instead of a Rack::Response,
57
+ #last_response.body now returns a string instead of an array.
58
+
59
+ * Major enhancements
60
+
61
+ * Support multipart requests via the UploadedFile class (thanks, Rails)
62
+
63
+ * Minor enhancements
64
+
65
+ * Updated for Rack 1.0
66
+ * Don't require rubygems (See http://gist.github.com/54177)
67
+ * Support HTTP Digest authentication with the #digest_authorize method
68
+ * #last_response returns a MockResponse instead of a Response
69
+ (Michael Fellinger)
70
+
71
+ == 0.1.0 / 2009-03-02
72
+
73
+ * 1 major enhancement
74
+
75
+ * Birthday!
data/MIT-LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008-2009 Bryan Helmkamp, Engine Yard Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
1
+ = Rack::Test
2
+
3
+ - http://gitrdoc.com/brynary/rack-test
4
+ - http://github.com/brynary/rack-test
5
+
6
+ == Description
7
+
8
+ Rack::Test is a small, simple testing API for Rack apps. It can be used on its
9
+ own or as a reusable starting point for Web frameworks and testing libraries
10
+ to build on. Most of its initial functionality is an extraction of Merb 1.0's
11
+ request helpers feature.
12
+
13
+ == Features
14
+
15
+ * Maintains a cookie jar across requests
16
+ * Easily follow redirects when desired
17
+ * Set request headers to be used by all subsequent requests
18
+ * Small footprint. Approximately 200 LOC
19
+
20
+ == Example
21
+
22
+ require "rack/test"
23
+
24
+ class HomepageTest < Test::Unit::TestCase
25
+ include Rack::Test::Methods
26
+
27
+ def app
28
+ MyApp.new
29
+ end
30
+
31
+ def test_redirect_logged_in_users_to_dashboard
32
+ authorize "bryan", "secret"
33
+ get "/"
34
+ follow_redirect!
35
+
36
+ assert_equal "http://example.org/redirected", last_request.url
37
+ assert last_response.ok?
38
+ end
39
+
40
+ end
41
+
42
+ == Install
43
+
44
+ To install the latest release as a gem:
45
+
46
+ sudo gem install rack-test
47
+
48
+ == Authors
49
+
50
+ - Maintained by {Bryan Helmkamp}[mailto:bryan@brynary.com]
51
+ - Contributions from Simon Rozet and Pat Nakajima
52
+ - Much of the original code was extracted from Merb 1.0's request helper
53
+
54
+ == License
55
+
56
+ Copyright (c) 2008-2009 Bryan Helmkamp, Engine Yard Inc.
57
+ See MIT-LICENSE.txt in this directory.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require "rubygems"
2
+ require "rake/rdoctask"
3
+ require "spec/rake/spectask"
4
+
5
+ begin
6
+ require "jeweler"
7
+
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = "rack-test"
10
+ s.author = "Bryan Helmkamp"
11
+ s.email = "bryan" + "@" + "brynary.com"
12
+ s.homepage = "http://github.com/brynary/rack-test"
13
+ s.summary = "Simple testing API built on Rack"
14
+ # s.description = "TODO"
15
+ s.rubyforge_project = "rack-test"
16
+ s.extra_rdoc_files = %w[README.rdoc MIT-LICENSE.txt]
17
+ end
18
+
19
+ Jeweler::RubyforgeTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ Spec::Rake::SpecTask.new do |t|
25
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
26
+ t.libs << 'lib'
27
+ t.libs << 'spec'
28
+ end
29
+
30
+ desc "Run all specs in spec directory with RCov"
31
+ Spec::Rake::SpecTask.new(:rcov) do |t|
32
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
33
+ t.rcov = true
34
+ t.rcov_opts = lambda do
35
+ IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
36
+ end
37
+ end
38
+
39
+ desc "Generate RDoc"
40
+ task :docs do
41
+ FileUtils.rm_rf("doc")
42
+ system "hanna --title 'Rack::Test #{Rack::Test::VERSION} API Documentation'"
43
+ end
44
+
45
+ desc 'Removes trailing whitespace'
46
+ task :whitespace do
47
+ sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
48
+ end
49
+
50
+ task :spec => :check_dependencies if defined?(Jeweler)
51
+
52
+ desc "Run the specs"
53
+ task :default => :spec
@@ -0,0 +1,57 @@
1
+ module Rack
2
+
3
+ class MockSession
4
+ attr_writer :cookie_jar
5
+ attr_reader :last_response
6
+ attr_reader :default_host
7
+
8
+ def initialize(app, default_host = Rack::Test::DEFAULT_HOST)
9
+ @app = app
10
+ @after_request = []
11
+ @default_host = default_host
12
+ end
13
+
14
+ def after_request(&block)
15
+ @after_request << block
16
+ end
17
+
18
+ def clear_cookies
19
+ @cookie_jar = Rack::Test::CookieJar.new([], @default_host)
20
+ end
21
+
22
+ def set_cookie(cookie, uri = nil)
23
+ cookie_jar.merge(cookie, uri)
24
+ end
25
+
26
+ def request(uri, env)
27
+ env["HTTP_COOKIE"] ||= cookie_jar.for(uri)
28
+ @last_request = Rack::Request.new(env)
29
+ status, headers, body = @app.call(@last_request.env)
30
+ @last_response = MockResponse.new(status, headers, body, env["rack.errors"].flush)
31
+ cookie_jar.merge(last_response.headers["Set-Cookie"], uri)
32
+
33
+ @after_request.each { |hook| hook.call }
34
+ @last_response
35
+ end
36
+
37
+ # Return the last request issued in the session. Raises an error if no
38
+ # requests have been sent yet.
39
+ def last_request
40
+ raise Rack::Test::Error.new("No request yet. Request a page first.") unless @last_request
41
+ @last_request
42
+ end
43
+
44
+ # Return the last response received in the session. Raises an error if
45
+ # no requests have been sent yet.
46
+ def last_response
47
+ raise Rack::Test::Error.new("No response yet. Request a page first.") unless @last_response
48
+ @last_response
49
+ end
50
+
51
+ def cookie_jar
52
+ @cookie_jar ||= Rack::Test::CookieJar.new([], @default_host)
53
+ end
54
+
55
+ end
56
+
57
+ end
data/lib/rack/test.rb ADDED
@@ -0,0 +1,245 @@
1
+ require "uri"
2
+ require "rack"
3
+ require "rack/mock_session"
4
+ require "rack/test/cookie_jar"
5
+ require "rack/test/mock_digest_request"
6
+ require "rack/test/utils"
7
+ require "rack/test/methods"
8
+ require "rack/test/uploaded_file"
9
+
10
+ module Rack
11
+ module Test
12
+ VERSION = "0.4.1"
13
+
14
+ DEFAULT_HOST = "example.org"
15
+ MULTIPART_BOUNDARY = "----------XnJLe9ZIbbGUYtzPQJ16u1"
16
+
17
+ # The common base class for exceptions raised by Rack::Test
18
+ class Error < StandardError; end
19
+
20
+ class Session
21
+ extend Forwardable
22
+ include Rack::Test::Utils
23
+
24
+ def_delegators :@rack_mock_session, :clear_cookies, :set_cookie, :last_response, :last_request
25
+
26
+ # Initialize a new session for the given Rack app
27
+ def initialize(mock_session)
28
+ @headers = {}
29
+
30
+ if mock_session.is_a?(MockSession)
31
+ @rack_mock_session = mock_session
32
+ else
33
+ @rack_mock_session = MockSession.new(mock_session)
34
+ end
35
+
36
+ @default_host = @rack_mock_session.default_host
37
+ end
38
+
39
+ # Issue a GET request for the given URI with the given params and Rack
40
+ # environment. Stores the issues request object in #last_request and
41
+ # the app's response in #last_response. Yield #last_response to a block
42
+ # if given.
43
+ #
44
+ # Example:
45
+ # get "/"
46
+ def get(uri, params = {}, env = {}, &block)
47
+ env = env_for(uri, env.merge(:method => "GET", :params => params))
48
+ process_request(uri, env, &block)
49
+ end
50
+
51
+ # Issue a POST request for the given URI. See #get
52
+ #
53
+ # Example:
54
+ # post "/signup", "name" => "Bryan"
55
+ def post(uri, params = {}, env = {}, &block)
56
+ env = env_for(uri, env.merge(:method => "POST", :params => params))
57
+ process_request(uri, env, &block)
58
+ end
59
+
60
+ # Issue a PUT request for the given URI. See #get
61
+ #
62
+ # Example:
63
+ # put "/"
64
+ def put(uri, params = {}, env = {}, &block)
65
+ env = env_for(uri, env.merge(:method => "PUT", :params => params))
66
+ process_request(uri, env, &block)
67
+ end
68
+
69
+ # Issue a DELETE request for the given URI. See #get
70
+ #
71
+ # Example:
72
+ # delete "/"
73
+ def delete(uri, params = {}, env = {}, &block)
74
+ env = env_for(uri, env.merge(:method => "DELETE", :params => params))
75
+ process_request(uri, env, &block)
76
+ end
77
+
78
+ # Issue a HEAD request for the given URI. See #get
79
+ #
80
+ # Example:
81
+ # head "/"
82
+ def head(uri, params = {}, env = {}, &block)
83
+ env = env_for(uri, env.merge(:method => "HEAD", :params => params))
84
+ process_request(uri, env, &block)
85
+ end
86
+
87
+ # Issue a request to the Rack app for the given URI and optional Rack
88
+ # environment. Stores the issues request object in #last_request and
89
+ # the app's response in #last_response. Yield #last_response to a block
90
+ # if given.
91
+ #
92
+ # Example:
93
+ # request "/"
94
+ def request(uri, env = {}, &block)
95
+ env = env_for(uri, env)
96
+ process_request(uri, env, &block)
97
+ end
98
+
99
+ # Set a header to be included on all subsequent requests through the
100
+ # session. Use a value of nil to remove a previously configured header.
101
+ #
102
+ # Example:
103
+ # header "User-Agent", "Firefox"
104
+ def header(name, value)
105
+ if value.nil?
106
+ @headers.delete(name)
107
+ else
108
+ @headers[name] = value
109
+ end
110
+ end
111
+
112
+ # Set the username and password for HTTP Basic authorization, to be
113
+ # included in subsequent requests in the HTTP_AUTHORIZATION header.
114
+ #
115
+ # Example:
116
+ # basic_authorize "bryan", "secret"
117
+ def basic_authorize(username, password)
118
+ encoded_login = ["#{username}:#{password}"].pack("m*")
119
+ header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
120
+ end
121
+
122
+ alias_method :authorize, :basic_authorize
123
+
124
+ def digest_authorize(username, password)
125
+ @digest_username = username
126
+ @digest_password = password
127
+ end
128
+
129
+ # Rack::Test will not follow any redirects automatically. This method
130
+ # will follow the redirect returned in the last response. If the last
131
+ # response was not a redirect, an error will be raised.
132
+ def follow_redirect!
133
+ unless last_response.redirect?
134
+ raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
135
+ end
136
+
137
+ get(last_response["Location"])
138
+ end
139
+
140
+ private
141
+
142
+ def env_for(path, env)
143
+ uri = URI.parse(path)
144
+ uri.host ||= @default_host
145
+
146
+ env = default_env.merge(env)
147
+
148
+ env.update("HTTPS" => "on") if URI::HTTPS === uri
149
+ env["X-Requested-With"] = "XMLHttpRequest" if env[:xhr]
150
+
151
+ # TODO: Remove this after Rack 1.1 has been released.
152
+ # Stringifying and upcasing methods has be commit upstream
153
+ env["REQUEST_METHOD"] ||= env[:method] ? env[:method].to_s.upcase : "GET"
154
+
155
+ if env["REQUEST_METHOD"] == "GET"
156
+ params = env[:params] || {}
157
+ params.update(parse_query(uri.query))
158
+
159
+ uri.query = build_nested_query(params)
160
+ elsif !env.has_key?(:input)
161
+ env["CONTENT_TYPE"] ||= "application/x-www-form-urlencoded"
162
+
163
+ if env[:params].is_a?(Hash)
164
+ if data = build_multipart(env[:params])
165
+ env[:input] = data
166
+ env["CONTENT_LENGTH"] ||= data.length.to_s
167
+ env["CONTENT_TYPE"] = "multipart/form-data; boundary=#{MULTIPART_BOUNDARY}"
168
+ else
169
+ env[:input] = params_to_string(env[:params])
170
+ end
171
+ else
172
+ env[:input] = env[:params]
173
+ end
174
+ end
175
+
176
+ if env.has_key?(:cookie)
177
+ set_cookie(env.delete(:cookie), uri)
178
+ end
179
+
180
+ Rack::MockRequest.env_for(uri.to_s, env)
181
+ end
182
+
183
+ def process_request(uri, env)
184
+ uri = URI.parse(uri)
185
+ uri.host ||= @default_host
186
+
187
+ @rack_mock_session.request(uri, env)
188
+
189
+ if retry_with_digest_auth?(env)
190
+ auth_env = env.merge({
191
+ "HTTP_AUTHORIZATION" => digest_auth_header,
192
+ "rack-test.digest_auth_retry" => true
193
+ })
194
+ auth_env.delete('rack.request')
195
+ process_request(uri.path, auth_env)
196
+ else
197
+ yield last_response if block_given?
198
+
199
+ last_response
200
+ end
201
+ end
202
+
203
+ def digest_auth_header
204
+ challenge = last_response["WWW-Authenticate"].split(" ", 2).last
205
+ params = Rack::Auth::Digest::Params.parse(challenge)
206
+
207
+ params.merge!({
208
+ "username" => @digest_username,
209
+ "nc" => "00000001",
210
+ "cnonce" => "nonsensenonce",
211
+ "uri" => last_request.path_info,
212
+ "method" => last_request.env["REQUEST_METHOD"],
213
+ })
214
+
215
+ params["response"] = MockDigestRequest.new(params).response(@digest_password)
216
+
217
+ "Digest #{params}"
218
+ end
219
+
220
+ def retry_with_digest_auth?(env)
221
+ last_response.status == 401 &&
222
+ digest_auth_configured? &&
223
+ !env["rack-test.digest_auth_retry"]
224
+ end
225
+
226
+ def digest_auth_configured?
227
+ @digest_username
228
+ end
229
+
230
+ def default_env
231
+ { "rack.test" => true, "REMOTE_ADDR" => "127.0.0.1" }.merge(@headers)
232
+ end
233
+
234
+ def params_to_string(params)
235
+ case params
236
+ when Hash then build_nested_query(params)
237
+ when nil then ""
238
+ else params
239
+ end
240
+ end
241
+
242
+ end
243
+
244
+ end
245
+ end