rack-test 0.1.0

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