josh-rack-test 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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