rack-test 0.1.0

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.
@@ -0,0 +1,5 @@
1
+ == 0.1.0 / 2009-03-02
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
@@ -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.
@@ -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.
@@ -0,0 +1,58 @@
1
+ require "rubygems"
2
+ require "rake/rdoctask"
3
+ require "rake/gempackagetask"
4
+ require "rake/clean"
5
+ require "spec/rake/spectask"
6
+ require File.expand_path("./lib/rack/test")
7
+
8
+ Spec::Rake::SpecTask.new do |t|
9
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
10
+ end
11
+
12
+ desc "Run all specs in spec directory with RCov"
13
+ Spec::Rake::SpecTask.new(:rcov) do |t|
14
+ t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
15
+ t.rcov = true
16
+ t.rcov_opts = lambda do
17
+ IO.readlines(File.dirname(__FILE__) + "/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
18
+ end
19
+ end
20
+
21
+ desc "Run the specs"
22
+ task :default => :spec
23
+
24
+ spec = Gem::Specification.new do |s|
25
+ s.name = "rack-test"
26
+ s.version = Rack::Test::VERSION
27
+ s.author = "Bryan Helmkamp"
28
+ s.email = "bryan" + "@" + "brynary.com"
29
+ s.homepage = "http://github.com/brynary/rack-test"
30
+ s.summary = "Simple testing API built on Rack"
31
+ s.description = s.summary
32
+ s.files = %w[History.txt Rakefile README.rdoc] + Dir["lib/**/*"]
33
+
34
+ # rdoc
35
+ s.has_rdoc = true
36
+ s.extra_rdoc_files = %w(README.rdoc MIT-LICENSE.txt)
37
+ end
38
+
39
+ Rake::GemPackageTask.new(spec) do |package|
40
+ package.gem_spec = spec
41
+ end
42
+
43
+ desc "Delete generated RDoc"
44
+ task :clobber_docs do
45
+ FileUtils.rm_rf("doc")
46
+ end
47
+
48
+ desc "Generate RDoc"
49
+ task :docs => :clobber_docs do
50
+ system "hanna --title 'Rack::Test #{Rack::Test::VERSION} API Documentation'"
51
+ end
52
+
53
+ desc 'Install the package as a gem.'
54
+ task :install => [:clean, :package] do
55
+ gem = Dir['pkg/*.gem'].first
56
+ sh "sudo gem install --local #{gem}"
57
+ end
58
+
@@ -0,0 +1,213 @@
1
+ require "rubygems"
2
+
3
+ unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__) + "/.."))
4
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/.."))
5
+ end
6
+
7
+ require "uri"
8
+ require "rack"
9
+ require "rack/test/cookie_jar"
10
+ require "rack/test/utils"
11
+ require "rack/test/methods"
12
+
13
+ module Rack
14
+ module Test
15
+
16
+ VERSION = "0.1.0"
17
+
18
+ # The common base class for exceptions raised by Rack::Test
19
+ class Error < StandardError
20
+ end
21
+
22
+ class Session
23
+ include Rack::Test::Utils
24
+
25
+ # Initialize a new session for the given Rack app
26
+ def initialize(app)
27
+ raise ArgumentError.new("app must respond_to?(:call)") unless app.respond_to?(:call)
28
+
29
+ @headers = {}
30
+ @app = app
31
+ end
32
+
33
+ # Issue a GET request for the given URI with the given params and Rack
34
+ # environment. Stores the issues request object in #last_request and
35
+ # the app's response in #last_response. Yield #last_response to a block
36
+ # if given.
37
+ #
38
+ # Example:
39
+ # get "/"
40
+ def get(uri, params = {}, env = {}, &block)
41
+ env = env_for(uri, env.merge(:method => "GET", :params => params))
42
+ process_request(uri, env, &block)
43
+ end
44
+
45
+ # Issue a POST request for the given URI. See #get
46
+ #
47
+ # Example:
48
+ # post "/signup", "name" => "Bryan"
49
+ def post(uri, params = {}, env = {}, &block)
50
+ env = env_for(uri, env.merge(:method => "POST", :params => params))
51
+ process_request(uri, env, &block)
52
+ end
53
+
54
+ # Issue a PUT request for the given URI. See #get
55
+ #
56
+ # Example:
57
+ # put "/"
58
+ def put(uri, params = {}, env = {}, &block)
59
+ env = env_for(uri, env.merge(:method => "PUT", :params => params))
60
+ process_request(uri, env, &block)
61
+ end
62
+
63
+ # Issue a DELETE request for the given URI. See #get
64
+ #
65
+ # Example:
66
+ # delete "/"
67
+ def delete(uri, params = {}, env = {}, &block)
68
+ env = env_for(uri, env.merge(:method => "DELETE", :params => params))
69
+ process_request(uri, env, &block)
70
+ end
71
+
72
+ # Issue a HEAD request for the given URI. See #get
73
+ #
74
+ # Example:
75
+ # head "/"
76
+ def head(uri, params = {}, env = {}, &block)
77
+ env = env_for(uri, env.merge(:method => "HEAD", :params => params))
78
+ process_request(uri, env, &block)
79
+ end
80
+
81
+ # Issue a request to the Rack app for the given URI and optional Rack
82
+ # environment. Stores the issues request object in #last_request and
83
+ # the app's response in #last_response. Yield #last_response to a block
84
+ # if given.
85
+ #
86
+ # Example:
87
+ # request "/"
88
+ def request(uri, env = {}, &block)
89
+ env = env_for(uri, env)
90
+ process_request(uri, env, &block)
91
+ end
92
+
93
+ # Set a header to be included on all subsequent requests through the
94
+ # session. Use a value of nil to remove a previously configured header.
95
+ #
96
+ # Example:
97
+ # header "User-Agent", "Firefox"
98
+ def header(name, value)
99
+ if value.nil?
100
+ @headers.delete(name)
101
+ else
102
+ @headers[name] = value
103
+ end
104
+ end
105
+
106
+ # Set the username and password for HTTP Basic authorization, to be
107
+ # included in subsequent requests in the HTTP_AUTHORIZATION header.
108
+ #
109
+ # Example:
110
+ # authorize "bryan", "secret"
111
+ def authorize(username, password)
112
+ encoded_login = ["#{username}:#{password}"].pack("m*")
113
+ header('HTTP_AUTHORIZATION', "Basic #{encoded_login}")
114
+ end
115
+
116
+ # Rack::Test will not follow any redirects automatically. This method
117
+ # will follow the redirect returned in the last response. If the last
118
+ # response was not a redirect, an error will be raised.
119
+ def follow_redirect!
120
+ unless last_response.redirect?
121
+ raise Error.new("Last response was not a redirect. Cannot follow_redirect!")
122
+ end
123
+
124
+ get(last_response["Location"])
125
+ end
126
+
127
+ # Return the last request issued in the session. Raises an error if no
128
+ # requests have been sent yet.
129
+ def last_request
130
+ raise Error.new("No request yet. Request a page first.") unless @last_request
131
+
132
+ @last_request
133
+ end
134
+
135
+ # Return the last response received in the session. Raises an error if
136
+ # no requests have been sent yet.
137
+ def last_response
138
+ raise Error.new("No response yet. Request a page first.") unless @last_response
139
+
140
+ @last_response
141
+ end
142
+
143
+ private
144
+
145
+
146
+ def env_for(path, env)
147
+ uri = URI.parse(path)
148
+ uri.host ||= "example.org"
149
+
150
+ env = default_env.merge(env)
151
+
152
+ if URI::HTTPS === uri
153
+ env.update("HTTPS" => "on")
154
+ end
155
+
156
+ if env[:xhr]
157
+ env["X-Requested-With"] = "XMLHttpRequest"
158
+ end
159
+
160
+ if (env[:method] == "POST" || env["REQUEST_METHOD"] == "POST") && !env.has_key?(:input)
161
+ env["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
162
+ env[:input] = params_to_string(env.delete(:params))
163
+ end
164
+
165
+ params = env[:params] || {}
166
+ params.update(parse_query(uri.query))
167
+ uri.query = requestify(params)
168
+
169
+ if env.has_key?(:cookie)
170
+ # Add the cookies explicitly set by the user
171
+ env["HTTP_COOKIE"] = cookie_jar.merge(uri, env.delete(:cookie)).for(uri)
172
+ else
173
+ env["HTTP_COOKIE"] = cookie_jar.for(uri)
174
+ end
175
+
176
+ Rack::MockRequest.env_for(uri.to_s, env)
177
+ end
178
+
179
+ def cookie_jar
180
+ @cookie_jar || Rack::Test::CookieJar.new
181
+ end
182
+
183
+ def process_request(uri, env)
184
+ uri = URI.parse(uri)
185
+ uri.host ||= "example.org"
186
+
187
+ @last_request = Rack::Request.new(env)
188
+
189
+ status, headers, body = @app.call(@last_request.env)
190
+ @last_response = Rack::Response.new(body, status, headers)
191
+ @cookie_jar = cookie_jar.merge(uri, last_response.headers["Set-Cookie"])
192
+
193
+ yield @last_response if block_given?
194
+
195
+ @last_response
196
+ end
197
+
198
+ def default_env
199
+ { "rack.test" => true, "REMOTE_ADDR" => "127.0.0.1" }.merge(@headers)
200
+ end
201
+
202
+ def params_to_string(params)
203
+ case params
204
+ when Hash then requestify(params)
205
+ when nil then ""
206
+ else params
207
+ end
208
+ end
209
+
210
+ end
211
+
212
+ end
213
+ end
@@ -0,0 +1,121 @@
1
+ module Rack
2
+ module Test
3
+
4
+ class Cookie
5
+ include Rack::Utils
6
+
7
+ # :api: private
8
+ attr_reader :name, :value
9
+
10
+ # :api: private
11
+ def initialize(raw, default_host)
12
+ # separate the name / value pair from the cookie options
13
+ @name_value_raw, options = raw.split(/[;,] */n, 2)
14
+
15
+ @name, @value = parse_query(@name_value_raw, ';').to_a.first
16
+ @options = parse_query(options, ';')
17
+
18
+ @options.delete_if { |k, v| !v || v.empty? }
19
+
20
+ @options["domain"] ||= default_host
21
+ end
22
+
23
+ # :api: private
24
+ def raw
25
+ @name_value_raw
26
+ end
27
+
28
+ # :api: private
29
+ def empty?
30
+ @value.nil? || @value.empty?
31
+ end
32
+
33
+ # :api: private
34
+ def domain
35
+ @options["domain"]
36
+ end
37
+
38
+ # :api: private
39
+ def path
40
+ @options["path"] || "/"
41
+ end
42
+
43
+ # :api: private
44
+ def expires
45
+ Time.parse(@options["expires"]) if @options["expires"]
46
+ end
47
+
48
+ # :api: private
49
+ def expired?
50
+ expires && expires < Time.now
51
+ end
52
+
53
+ # :api: private
54
+ def valid?(uri)
55
+ uri.host =~ Regexp.new("#{Regexp.escape(domain)}$") &&
56
+ uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
57
+ end
58
+
59
+ # :api: private
60
+ def matches?(uri)
61
+ ! expired? && valid?(uri)
62
+ end
63
+
64
+ # :api: private
65
+ def <=>(other)
66
+ # Orders the cookies from least specific to most
67
+ [name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
68
+ end
69
+
70
+ end
71
+
72
+ class CookieJar
73
+
74
+ # :api: private
75
+ def initialize(cookies = [])
76
+ @jar = cookies
77
+ @jar.sort!
78
+ end
79
+
80
+ def merge(uri, raw_cookies)
81
+ return self unless raw_cookies
82
+
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
89
+
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
95
+ end
96
+
97
+ new_jar.concat cookies
98
+
99
+ return self.class.new(new_jar)
100
+ end
101
+
102
+ # :api: private
103
+ def for(uri)
104
+ cookies = {}
105
+
106
+ # The cookies are sorted by most specific first. So, we loop through
107
+ # all the cookies in order and add it to a hash by cookie name if
108
+ # the cookie can be sent to the current URI. It's added to the hash
109
+ # so that when we are done, the cookies will be unique by name and
110
+ # 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)
113
+ end
114
+
115
+ cookies.values.join(';')
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+ end
@@ -0,0 +1,45 @@
1
+ module Rack
2
+ module Test
3
+
4
+ module Methods
5
+
6
+ def self.delegate_to_rack_test_session(*meths)
7
+ meths.each do |meth|
8
+ self.class_eval <<-RUBY
9
+ def #{meth}(*args, &blk)
10
+ rack_test_session.#{meth}(*args, &blk)
11
+ end
12
+ RUBY
13
+ end
14
+ end
15
+
16
+ def rack_test_session
17
+ @_rack_test_session ||= Rack::Test::Session.new(app)
18
+ end
19
+
20
+ delegate_to_rack_test_session \
21
+ :request,
22
+
23
+ # HTTP verbs
24
+ :get,
25
+ :post,
26
+ :put,
27
+ :delete,
28
+ :head,
29
+
30
+ # Redirects
31
+ :follow_redirect!,
32
+
33
+ # Header-related features
34
+ :header,
35
+ :authorize,
36
+
37
+ # Expose the last request and response
38
+ :last_response,
39
+ :last_request
40
+
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module Rack
2
+ module Test
3
+
4
+ module Utils
5
+ include Rack::Utils
6
+
7
+ def requestify(value, prefix = nil)
8
+ case value
9
+ when Array
10
+ value.map do |v|
11
+ requestify(v, "#{prefix}[]")
12
+ end.join("&")
13
+ when Hash
14
+ value.map do |k, v|
15
+ requestify(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
16
+ end.join("&")
17
+ else
18
+ "#{prefix}=#{escape(value)}"
19
+ end
20
+ end
21
+
22
+ module_function :requestify
23
+
24
+ end
25
+
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bryan Helmkamp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-04 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Simple testing API built on Rack
17
+ email: bryan@brynary.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - MIT-LICENSE.txt
25
+ files:
26
+ - History.txt
27
+ - Rakefile
28
+ - README.rdoc
29
+ - lib/rack
30
+ - lib/rack/test
31
+ - lib/rack/test/cookie_jar.rb
32
+ - lib/rack/test/methods.rb
33
+ - lib/rack/test/utils.rb
34
+ - lib/rack/test.rb
35
+ - MIT-LICENSE.txt
36
+ has_rdoc: true
37
+ homepage: http://github.com/brynary/rack-test
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.3.1
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: Simple testing API built on Rack
62
+ test_files: []
63
+