Update Vendored Rack to 1.2.0

Also update tests for itextomml 1.3.25.
This commit is contained in:
Jacques Distler 2010-06-13 23:09:24 -05:00
parent 4f8759cdf3
commit 6491d70326
107 changed files with 1463 additions and 2008 deletions

View file

@ -8,12 +8,12 @@ module Rack
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={}) def self.run(app, options={})
options[:BindAddress] = options.delete(:Host) if options[:Host] options[:BindAddress] = options.delete(:Host) if options[:Host]
server = ::WEBrick::HTTPServer.new(options) @server = ::WEBrick::HTTPServer.new(options)
server.mount "/", Rack::Handler::WEBrick, app @server.mount "/", Rack::Handler::WEBrick, app
trap(:INT) { server.shutdown } trap(:INT) { @server.shutdown }
trap(:TERM) { server.shutdown } trap(:TERM) { @server.shutdown }
yield server if block_given? yield @server if block_given?
server.start @server.start
end end
end end
end end
@ -39,7 +39,7 @@ module Rack
entire_buffer_written_out = false entire_buffer_written_out = false
while !entire_buffer_written_out while !entire_buffer_written_out
written = @rewindable_io.write(buffer) written = @rewindable_io.write(buffer)
entire_buffer_written_out = written == buffer.size entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
if !entire_buffer_written_out if !entire_buffer_written_out
buffer.slice!(0 .. written - 1) buffer.slice!(0 .. written - 1)
end end

View file

@ -226,6 +226,15 @@ END_THM
def test_have_latest_itex2mml def test_have_latest_itex2mml
assert_markup_parsed_as(
%{<p>boxed equation <math class='maruku-mathml' } +
%{display='inline' xmlns='http://www.w3.org/1998} +
%{/Math/MathML'><menclose notation='box'><mrow><} +
%{menclose notation='updiagonalstrike'><mi>D</mi} +
%{></menclose><mi>\317\210</mi><mo>=</mo><mn>0</} +
%{mn></mrow></menclose></math></p>},
"boxed equation $\\boxed{\\slash{D}\\psi=0}$")
assert_markup_parsed_as( assert_markup_parsed_as(
%{<p>equation <math class='maruku-mathml' displa} + %{<p>equation <math class='maruku-mathml' displa} +
%{y='inline' xmlns='http://www.w3.org/1998/Math/} + %{y='inline' xmlns='http://www.w3.org/1998/Math/} +

View file

@ -1,4 +1,4 @@
Copyright (c) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen> Copyright (c) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to of this software and associated documentation files (the "Software"), to

View file

@ -9,16 +9,6 @@ middleware) into a single method call.
The exact details of this are described in the Rack specification, The exact details of this are described in the Rack specification,
which all Rack applications should conform to. which all Rack applications should conform to.
== Specification changes in this release
With Rack 1.1, the Rack specification (found in SPEC) changed in the
following backward-incompatible ways.
* Rack::VERSION has been pushed to [1,1].
* rack.logger is now specified.
* The SPEC now allows subclasses of the required types.
* rack.input has to be opened in binary mode.
== Supported web servers == Supported web servers
The included *handlers* connect all kinds of web servers to Rack: The included *handlers* connect all kinds of web servers to Rack:
@ -46,9 +36,6 @@ changing anything.
== Supported web frameworks == Supported web frameworks
The included *adapters* connect Rack with existing Ruby web frameworks:
* Camping
These frameworks include Rack adapters in their distributions: These frameworks include Rack adapters in their distributions:
* Camping * Camping
* Coset * Coset
@ -143,9 +130,9 @@ at my site:
== Running the tests == Running the tests
Testing Rack requires the test/spec testing framework: Testing Rack requires the bacon testing framework:
gem install test-spec gem install bacon
There are two rake-based test tasks: There are two rake-based test tasks:
@ -153,11 +140,10 @@ There are two rake-based test tasks:
rake fulltest runs all the tests rake fulltest runs all the tests
The fast testsuite has no dependencies outside of the core Ruby The fast testsuite has no dependencies outside of the core Ruby
installation and test-spec. installation and bacon.
To run the test suite completely, you need: To run the test suite completely, you need:
* camping
* fcgi * fcgi
* memcache-client * memcache-client
* mongrel * mongrel
@ -278,7 +264,7 @@ run on port 11211) and memcache-client installed.
* Make sure WEBrick respects the :Host option * Make sure WEBrick respects the :Host option
* Many Ruby 1.9 fixes. * Many Ruby 1.9 fixes.
* December ??th, 2009: Ninth public release 1.1.0. * January 3rd, 2010: Ninth public release 1.1.0.
* Moved Auth::OpenID to rack-contrib. * Moved Auth::OpenID to rack-contrib.
* SPEC change that relaxes Lint slightly to allow subclasses of the * SPEC change that relaxes Lint slightly to allow subclasses of the
required types required types
@ -313,17 +299,26 @@ run on port 11211) and memcache-client installed.
* Enforce binary encoding in RewindableInput * Enforce binary encoding in RewindableInput
* Set correct external_encoding for handlers that don't use RewindableInput * Set correct external_encoding for handlers that don't use RewindableInput
* June 13th, 2010: Tenth public release 1.2.0.
* Removed Camping adapter: Camping 2.0 supports Rack as-is
* Removed parsing of quoted values
* Add Request.trace? and Request.options?
* Add mime-type for .webm and .htc
* Fix HTTP_X_FORWARDED_FOR
* Various multipart fixes
* Switch test suite to bacon
== Contact == Contact
Please post bugs, suggestions and patches to Please post bugs, suggestions and patches to
the bug tracker at <http://rack.lighthouseapp.com/>. the bug tracker at <http://github.com/rack/rack/issues>.
Mailing list archives are available at Mailing list archives are available at
<http://groups.google.com/group/rack-devel>. <http://groups.google.com/group/rack-devel>.
Git repository (send Git patches to the mailing list): Git repository (send Git patches to the mailing list):
* http://github.com/rack/rack * http://github.com/rack/rack
* http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack.git * http://git.vuxu.org/cgi-bin/gitweb.cgi?p=rack-github.git
You are also welcome to join the #rack channel on irc.freenode.net. You are also welcome to join the #rack channel on irc.freenode.net.
@ -345,12 +340,14 @@ would like to thank:
* Tim Fletcher, for the HTTP authentication code. * Tim Fletcher, for the HTTP authentication code.
* Luc Heinrich for the Cookie sessions, the static file handler and bugfixes. * Luc Heinrich for the Cookie sessions, the static file handler and bugfixes.
* Armin Ronacher, for the logo and racktools. * Armin Ronacher, for the logo and racktools.
* Aredridel, Ben Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd, * Alex Beregszaszi, Alexander Kahn, Anil Wadghule, Aredridel, Ben
Tom Robinson, Phil Hagelberg, S. Brent Faulkner, Bosko Milekic, Alpert, Dan Kubb, Daniel Roethlisberger, Matt Todd, Tom Robinson,
Daniel Rodríguez Troitiño, Genki Takiuchi, Geoffrey Grosenbach, Phil Hagelberg, S. Brent Faulkner, Bosko Milekic, Daniel Rodríguez
Julien Sanchez, Kamal Fariz Mahyuddin, Masayoshi Takahashi, Patrick Troitiño, Genki Takiuchi, Geoffrey Grosenbach, Julien Sanchez, Kamal
Aljordm, Mig, and Kazuhiro Nishiyama for bug fixing and other Fariz Mahyuddin, Masayoshi Takahashi, Patrick Aljordm, Mig, Kazuhiro
improvements. Nishiyama, Jon Bardin, Konstantin Haase, Larry Siden, Matias
Korhonen, Sam Ruby, Simon Chiang, Tim Connor, Timur Batyrshin, and
Zach Brock for bug fixing and other improvements.
* Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support * Eric Wong, Hongli Lai, Jeremy Kemper for their continuous support
and API improvements. and API improvements.
* Yehuda Katz and Carl Lerche for refactoring rackup. * Yehuda Katz and Carl Lerche for refactoring rackup.
@ -368,7 +365,7 @@ would like to thank:
== Copyright == Copyright
Copyright (C) 2007, 2008, 2009 Christian Neukirchen <http://purl.org/net/chneukirchen> Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <http://purl.org/net/chneukirchen>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to of this software and associated documentation files (the "Software"), to

View file

@ -1,16 +1,13 @@
# Rakefile for Rack. -*-ruby-*- # Rakefile for Rack. -*-ruby-*-
require 'rake/rdoctask' require 'rake/rdoctask'
require 'rake/testtask'
desc "Run all the tests" desc "Run all the tests"
task :default => [:test] task :default => [:test]
desc "Make an archive as .tar.gz" desc "Make an archive as .tar.gz"
task :dist => [:chmod, :changelog, :rdoc, "SPEC"] do task :dist => [:chmod, :changelog, :rdoc, "SPEC"] do
FileUtils.touch("RDOX")
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar" sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
sh "pax -waf #{release}.tar -s ':^:#{release}/:' RDOX SPEC ChangeLog doc rack.gemspec" sh "pax -waf #{release}.tar -s ':^:#{release}/:' SPEC ChangeLog doc rack.gemspec"
sh "gzip -f -9 #{release}.tar" sh "gzip -f -9 #{release}.tar"
end end
@ -23,19 +20,19 @@ task :officialrelease do
sh "mv stage/#{release}.tar.gz stage/#{release}.gem ." sh "mv stage/#{release}.tar.gz stage/#{release}.gem ."
end end
task :officialrelease_really => [:fulltest, "RDOX", "SPEC", :dist, :gem] do task :officialrelease_really => ["SPEC", :dist, :gem] do
sh "sha1sum #{release}.tar.gz #{release}.gem" sh "sha1sum #{release}.tar.gz #{release}.gem"
end end
def release def release
require File.dirname(__FILE__) + "/lib/rack" require File.dirname(__FILE__) + "/lib/rack"
"rack-#{Rack.release}" "rack-#{Rack.release}.0"
end end
desc "Make binaries executable" desc "Make binaries executable"
task :chmod do task :chmod do
Dir["bin/*"].each { |binary| File.chmod(0775, binary) } Dir["bin/*"].each { |binary| File.chmod(0775, binary) }
Dir["test/cgi/test*"].each { |binary| File.chmod(0775, binary) } Dir["spec/cgi/spec*"].each { |binary| File.chmod(0775, binary) }
end end
desc "Generate a ChangeLog" desc "Generate a ChangeLog"
@ -57,11 +54,6 @@ task :changelog do
end end
desc "Generate RDox"
task "RDOX" do
sh "specrb -Ilib:test -a --rdox >RDOX"
end
desc "Generate Rack Specification" desc "Generate Rack Specification"
task "SPEC" do task "SPEC" do
File.open("SPEC", "wb") { |file| File.open("SPEC", "wb") { |file|
@ -75,24 +67,29 @@ end
desc "Run all the fast tests" desc "Run all the fast tests"
task :test do task :test do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS'] || '-t "^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|rackup)"'}" opts = ENV['TEST'] || '-a'
specopts = ENV['TESTOPTS'] ||
"-q -t '^(?!Rack::Handler|Rack::Adapter|Rack::Session::Memcache|rackup)'"
sh "bacon -I./lib:./spec -w #{opts} #{specopts}"
end end
desc "Run all the tests" desc "Run all the tests"
task :fulltest => [:chmod] do task :fulltest => [:chmod] do
sh "specrb -Ilib:test -w #{ENV['TEST'] || '-a'} #{ENV['TESTOPTS']}" opts = ENV['TEST'] || '-a'
specopts = ENV['TESTOPTS'] || '-q'
sh "bacon -I./lib:./spec -w #{opts} #{specopts}"
end end
task :gem => ["SPEC"] do task :gem => ["SPEC"] do
FileUtils.touch("RDOX")
sh "gem build rack.gemspec" sh "gem build rack.gemspec"
end end
desc "Generate RDoc documentation" desc "Generate RDoc documentation"
task :rdoc do task :rdoc => ["SPEC"] do
sh(*%w{rdoc --line-numbers --main README sh(*%w{rdoc --line-numbers --main README
--title 'Rack\ Documentation' --charset utf-8 -U -o doc} + --title 'Rack\ Documentation' --charset utf-8 -U -o doc} +
%w{README KNOWN-ISSUES SPEC RDOX} + %w{README KNOWN-ISSUES SPEC} +
Dir["lib/**/*.rb"]) Dir["lib/**/*.rb"])
end end

View file

@ -1,2 +1,4 @@
#!/usr/bin/env ruby
require "rack" require "rack"
Rack::Server.start Rack::Server.start

View file

@ -1,4 +1,4 @@
# Copyright (C) 2007, 2008, 2009 Christian Neukirchen <purl.org/net/chneukirchen> # Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
# #
# Rack is freely distributable under the terms of an MIT-style license. # Rack is freely distributable under the terms of an MIT-style license.
# See COPYING or http://www.opensource.org/licenses/mit-license.php. # See COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -20,7 +20,7 @@ module Rack
# Return the Rack release as a dotted string. # Return the Rack release as a dotted string.
def self.release def self.release
"1.1" "1.2"
end end
autoload :Builder, "rack/builder" autoload :Builder, "rack/builder"
@ -78,15 +78,4 @@ module Rack
autoload :Pool, "rack/session/pool" autoload :Pool, "rack/session/pool"
autoload :Memcache, "rack/session/memcache" autoload :Memcache, "rack/session/memcache"
end end
# *Adapters* connect Rack with third party web frameworks.
#
# Rack includes an adapter for Camping, see README for other
# frameworks supporting Rack in their code bases.
#
# Refer to the submodules for framework-specific calling details.
module Adapter
autoload :Camping, "rack/adapter/camping"
end
end end

View file

@ -1,22 +0,0 @@
module Rack
module Adapter
class Camping
def initialize(app)
@app = app
end
def call(env)
env["PATH_INFO"] ||= ""
env["SCRIPT_NAME"] ||= ""
controller = @app.run(env['rack.input'], env)
h = controller.headers
h.each_pair do |k,v|
if v.kind_of? URI
h[k] = v.to_s
end
end
[controller.status, controller.headers, [controller.body.to_s]]
end
end
end
end

View file

@ -35,7 +35,7 @@ module Rack
super k.to_s, v.to_s super k.to_s, v.to_s
end end
UNQUOTED = ['qop', 'nc', 'stale'] UNQUOTED = ['nc', 'stale']
def to_s def to_s
inject([]) do |parts, (k, v)| inject([]) do |parts, (k, v)|

View file

@ -1,480 +0,0 @@
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
gem 'ruby-openid', '~> 2' if defined? Gem
require 'rack/request'
require 'rack/utils'
require 'rack/auth/abstract/handler'
require 'uri'
require 'openid' #gem
require 'openid/extension' #gem
require 'openid/store/memory' #gem
module Rack
class Request
def openid_request
@env['rack.auth.openid.request']
end
def openid_response
@env['rack.auth.openid.response']
end
end
module Auth
# Rack::Auth::OpenID provides a simple method for setting up an OpenID
# Consumer. It requires the ruby-openid library from janrain to operate,
# as well as a rack method of session management.
#
# The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
#
# The OpenID specifications can be found at
# http://openid.net/specs/openid-authentication-1_1.html
# and
# http://openid.net/specs/openid-authentication-2_0.html. Documentation
# for published OpenID extensions and related topics can be found at
# http://openid.net/developers/specs/.
#
# It is recommended to read through the OpenID spec, as well as
# ruby-openid's documentation, to understand what exactly goes on. However
# a setup as simple as the presented examples is enough to provide
# Consumer functionality.
#
# This library strongly intends to utilize the OpenID 2.0 features of the
# ruby-openid library, which provides OpenID 1.0 compatiblity.
#
# NOTE: Due to the amount of data that this library stores in the
# session, Rack::Session::Cookie may fault.
class OpenID
class NoSession < RuntimeError; end
class BadExtension < RuntimeError; end
# Required for ruby-openid
ValidStatus = [:success, :setup_needed, :cancel, :failure]
# = Arguments
#
# The first argument is the realm, identifying the site they are trusting
# with their identity. This is required, also treated as the trust_root
# in OpenID 1.x exchanges.
#
# The optional second argument is a hash of options.
#
# == Options
#
# <tt>:return_to</tt> defines the url to return to after the client
# authenticates with the openid service provider. This url should point
# to where Rack::Auth::OpenID is mounted. If <tt>:return_to</tt> is not
# provided, return_to will be the current url which allows flexibility
# with caveats.
#
# <tt>:session_key</tt> defines the key to the session hash in the env.
# It defaults to 'rack.session'.
#
# <tt>:openid_param</tt> defines at what key in the request parameters to
# find the identifier to resolve. As per the 2.0 spec, the default is
# 'openid_identifier'.
#
# <tt>:store</tt> defined what OpenID Store to use for persistant
# information. By default a Store::Memory will be used.
#
# <tt>:immediate</tt> as true will make initial requests to be of an
# immediate type. This is false by default. See OpenID specification
# documentation.
#
# <tt>:extensions</tt> should be a hash of openid extension
# implementations. The key should be the extension main module, the value
# should be an array of arguments for extension::Request.new.
# The hash is iterated over and passed to #add_extension for processing.
# Please see #add_extension for further documentation.
#
# == Examples
#
# simple_oid = OpenID.new('http://mysite.com/')
#
# return_oid = OpenID.new('http://mysite.com/', {
# :return_to => 'http://mysite.com/openid'
# })
#
# complex_oid = OpenID.new('http://mysite.com/',
# :immediate => true,
# :extensions => {
# ::OpenID::SReg => [['email'],['nickname']]
# }
# )
#
# = Advanced
#
# Most of the functionality of this library is encapsulated such that
# expansion and overriding functions isn't difficult nor tricky.
# Alternately, to avoid opening up singleton objects or subclassing, a
# wrapper rack middleware can be composed to act upon Auth::OpenID's
# responses. See #check and #finish for locations of pertinent data.
#
# == Responses
#
# To change the responses that Auth::OpenID returns, override the methods
# #redirect, #bad_request, #unauthorized, #access_denied, and
# #foreign_server_failure.
#
# Additionally #confirm_post_params is used when the URI would exceed
# length limits on a GET request when doing the initial verification
# request.
#
# == Processing
#
# To change methods of processing completed transactions, override the
# methods #success, #setup_needed, #cancel, and #failure. Please ensure
# the returned object is a rack compatible response.
#
# The first argument is an OpenID::Response, the second is a
# Rack::Request of the current request, the last is the hash used in
# ruby-openid handling, which can be found manually at
# env['rack.session'][:openid].
#
# This is useful if you wanted to expand the processing done, such as
# setting up user accounts.
#
# oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
# def oid_app.success oid, request, session
# user = Models::User[oid.identity_url]
# user ||= Models::User.create_from_openid oid
# request['rack.session'][:user] = user.id
# redirect MyApp.site_home
# end
#
# site_map['/openid'] = oid_app
# map = Rack::URLMap.new site_map
# ...
def initialize(realm, options={})
realm = URI(realm)
raise ArgumentError, "Invalid realm: #{realm}" \
unless realm.absolute? \
and realm.fragment.nil? \
and realm.scheme =~ /^https?$/ \
and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
realm.path = '/' if realm.path.empty?
@realm = realm.to_s
if ruri = options[:return_to]
ruri = URI(ruri)
raise ArgumentError, "Invalid return_to: #{ruri}" \
unless ruri.absolute? \
and ruri.scheme =~ /^https?$/ \
and ruri.fragment.nil?
raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
unless self.within_realm?(ruri)
@return_to = ruri.to_s
end
@session_key = options[:session_key] || 'rack.session'
@openid_param = options[:openid_param] || 'openid_identifier'
@store = options[:store] || ::OpenID::Store::Memory.new
@immediate = !!options[:immediate]
@extensions = {}
if extensions = options.delete(:extensions)
extensions.each do |ext, args|
add_extension ext, *args
end
end
# Undocumented, semi-experimental
@anonymous = !!options[:anonymous]
end
attr_reader :realm, :return_to, :session_key, :openid_param, :store,
:immediate, :extensions
# Sets up and uses session data at <tt>:openid</tt> within the session.
# Errors in this setup will raise a NoSession exception.
#
# If the parameter 'openid.mode' is set, which implies a followup from
# the openid server, processing is passed to #finish and the result is
# returned. However, if there is no appropriate openid information in the
# session, a 400 error is returned.
#
# If the parameter specified by <tt>options[:openid_param]</tt> is
# present, processing is passed to #check and the result is returned.
#
# If neither of these conditions are met, #unauthorized is called.
def call(env)
env['rack.auth.openid'] = self
env_session = env[@session_key]
unless env_session and env_session.is_a?(Hash)
raise NoSession, 'No compatible session'
end
# let us work in our own namespace...
session = (env_session[:openid] ||= {})
unless session and session.is_a?(Hash)
raise NoSession, 'Incompatible openid session'
end
request = Rack::Request.new(env)
consumer = ::OpenID::Consumer.new(session, @store)
if mode = request.GET['openid.mode']
if session.key?(:openid_param)
finish(consumer, session, request)
else
bad_request
end
elsif request.GET[@openid_param]
check(consumer, session, request)
else
unauthorized
end
end
# As the first part of OpenID consumer action, #check retrieves the data
# required for completion.
#
# If all parameters fit within the max length of a URI, a 303 redirect
# will be returned. Otherwise #confirm_post_params will be called.
#
# Any messages from OpenID's request are logged to env['rack.errors']
#
# <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
# instance.
#
# <tt>session[:openid_param]</tt> is set to the openid identifier
# provided by the user.
#
# <tt>session[:return_to]</tt> is set to the return_to uri given to the
# identity provider.
def check(consumer, session, req)
oid = consumer.begin(req.GET[@openid_param], @anonymous)
req.env['rack.auth.openid.request'] = oid
req.env['rack.errors'].puts(oid.message)
p oid if $DEBUG
## Extension support
extensions.each do |ext,args|
oid.add_extension(ext::Request.new(*args))
end
session[:openid_param] = req.GET[openid_param]
return_to_uri = return_to ? return_to : req.url
session[:return_to] = return_to_uri
immediate = session.key?(:setup_needed) ? false : immediate
if oid.send_redirect?(realm, return_to_uri, immediate)
uri = oid.redirect_url(realm, return_to_uri, immediate)
redirect(uri)
else
confirm_post_params(oid, realm, return_to_uri, immediate)
end
rescue ::OpenID::DiscoveryFailure => e
# thrown from inside OpenID::Consumer#begin by yadis stuff
req.env['rack.errors'].puts([e.message, *e.backtrace]*"\n")
return foreign_server_failure
end
# This is the final portion of authentication.
# If successful, a redirect to the realm is be returned.
# Data gathered from extensions are stored in session[:openid] with the
# extension's namespace uri as the key.
#
# Any messages from OpenID's response are logged to env['rack.errors']
#
# <tt>env['rack.auth.openid.response']</tt> will contain the openid
# response.
def finish(consumer, session, req)
oid = consumer.complete(req.GET, req.url)
req.env['rack.auth.openid.response'] = oid
req.env['rack.errors'].puts(oid.message)
p oid if $DEBUG
raise unless ValidStatus.include?(oid.status)
__send__(oid.status, oid, req, session)
end
# The first argument should be the main extension module.
# The extension module should contain the constants:
# * class Request, should have OpenID::Extension as an ancestor
# * class Response, should have OpenID::Extension as an ancestor
# * string NS_URI, which defining the namespace of the extension
#
# All trailing arguments will be passed to extension::Request.new in
# #check.
# The openid response will be passed to
# extension::Response#from_success_response, #get_extension_args will be
# called on the result to attain the gathered data.
#
# This method returns the key at which the response data will be found in
# the session, which is the namespace uri by default.
def add_extension(ext, *args)
raise BadExtension unless valid_extension?(ext)
extensions[ext] = args
return ext::NS_URI
end
# Checks the validitity, in the context of usage, of a submitted
# extension.
def valid_extension?(ext)
if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
raise ArgumentError, 'Extension is missing constants.'
elsif not ext::Response.respond_to?(:from_success_response)
raise ArgumentError, 'Response is missing required method.'
end
return true
rescue
return false
end
# Checks the provided uri to ensure it'd be considered within the realm.
# is currently not compatible with wildcard realms.
def within_realm? uri
uri = URI.parse(uri.to_s)
realm = URI.parse(self.realm)
return false unless uri.absolute?
return false unless uri.path[0, realm.path.size] == realm.path
return false unless uri.host == realm.host or realm.host[/^\*\./]
# for wildcard support, is awkward with URI limitations
realm_match = Regexp.escape(realm.host).
sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
return false unless uri.host.match(realm_match)
return true
end
alias_method :include?, :within_realm?
protected
### These methods define some of the boilerplate responses.
# Returns an html form page for posting to an Identity Provider if the
# GET request would exceed the upper URI length limit.
def confirm_post_params(oid, realm, return_to, immediate)
Rack::Response.new.finish do |r|
r.write '<html><head><title>Confirm...</title></head><body>'
r.write oid.form_markup(realm, return_to, immediate)
r.write '</body></html>'
end
end
# Returns a 303 redirect with the destination of that provided by the
# argument.
def redirect(uri)
[ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain',
'Location' => uri},
[] ]
end
# Returns an empty 400 response.
def bad_request
[ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
[''] ]
end
# Returns a basic unauthorized 401 response.
def unauthorized
[ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
['Unauthorized.'] ]
end
# Returns a basic access denied 403 response.
def access_denied
[ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
['Access denied.'] ]
end
# Returns a 503 response to be used if communication with the remote
# OpenID server fails.
def foreign_server_failure
[ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
['Foreign server failure.'] ]
end
private
### These methods are called after a transaction is completed, depending
# on its outcome. These should all return a rack compatible response.
# You'd want to override these to provide additional functionality.
# Called to complete processing on a successful transaction.
# Within the openid session, :openid_identity and :openid_identifier are
# set to the user friendly and the standard representation of the
# validated identity. All other data in the openid session is cleared.
def success(oid, request, session)
session.clear
session[:openid_identity] = oid.display_identifier
session[:openid_identifier] = oid.identity_url
extensions.keys.each do |ext|
label = ext.name[/[^:]+$/].downcase
response = ext::Response.from_success_response(oid)
session[label] = response.data
end
redirect(realm)
end
# Called if the Identity Provider indicates further setup by the user is
# required.
# The identifier is retrived from the openid session at :openid_param.
# And :setup_needed is set to true to prevent looping.
def setup_needed(oid, request, session)
identifier = session[:openid_param]
session[:setup_needed] = true
redirect req.script_name + '?' + openid_param + '=' + identifier
end
# Called if the user indicates they wish to cancel identification.
# Data within openid session is cleared.
def cancel(oid, request, session)
session.clear
access_denied
end
# Called if the Identity Provider indicates the user is unable to confirm
# their identity. Data within the openid session is left alone, in case
# of swarm auth attacks.
def failure(oid, request, session)
unauthorized
end
end
# A class developed out of the request to use OpenID as an authentication
# middleware. The request will be sent to the OpenID instance unless the
# block evaluates to true. For example in rackup, you can use it as such:
#
# use Rack::Session::Pool
# use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
# env['rack.session'][:authkey] == a_string
# end
# run RackApp
#
# Or simply:
#
# app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
class OpenIDAuth < Rack::Auth::AbstractHandler
attr_reader :oid
def initialize(app, realm, options={}, &auth)
@oid = OpenID.new(realm, options)
super(app, &auth)
end
def call(env)
to = auth.call(env) ? @app : @oid
to.call env
end
end
end
end

View file

@ -13,7 +13,7 @@ module Rack
status, headers, body = @app.call(env) status, headers, body = @app.call(env)
headers = HeaderHash.new(headers) headers = HeaderHash.new(headers)
if !STATUS_WITH_NO_ENTITY_BODY.include?(status) && if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
!headers['Content-Length'] && !headers['Content-Length'] &&
!headers['Transfer-Encoding'] && !headers['Transfer-Encoding'] &&
(body.respond_to?(:to_ary) || body.respond_to?(:to_str)) (body.respond_to?(:to_ary) || body.respond_to?(:to_str))

View file

@ -11,13 +11,22 @@ module Rack
status, headers, body = @app.call(env) status, headers, body = @app.call(env)
if !headers.has_key?('ETag') if !headers.has_key?('ETag')
parts = [] digest, body = digest_body(body)
body.each { |part| parts << part.to_s } headers['ETag'] = %("#{digest}")
headers['ETag'] = %("#{Digest::MD5.hexdigest(parts.join(""))}") end
[status, headers, parts]
else
[status, headers, body] [status, headers, body]
end end
private
def digest_body(body)
digest = Digest::MD5.new
parts = []
body.each do |part|
digest << part
parts << part
end
[digest.hexdigest, parts]
end end
end end
end end

View file

@ -3,8 +3,10 @@ require 'rack/utils'
require 'rack/mime' require 'rack/mime'
module Rack module Rack
# Rack::File serves files below the +root+ given, according to the # Rack::File serves files below the +root+ directory given, according to the
# path info of the Rack request. # path info of the Rack request.
# e.g. when Rack::File.new("/etc") is used, you can access 'passwd' file
# as http://localhost:9292/passwd
# #
# Handlers can detect if bodies are a Rack::File, and use mechanisms # Handlers can detect if bodies are a Rack::File, and use mechanisms
# like sendfile on the +path+. # like sendfile on the +path+.

View file

@ -1,9 +1,11 @@
require 'rack/content_length' require 'rack/content_length'
require 'rack/rewindable_input'
module Rack module Rack
module Handler module Handler
class CGI class CGI
def self.run(app, options=nil) def self.run(app, options=nil)
$stdin.binmode
serve app serve app
end end
@ -15,8 +17,8 @@ module Rack
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({"rack.version" => [1,1], env.update({"rack.version" => Rack::VERSION,
"rack.input" => $stdin, "rack.input" => Rack::RewindableInput.new($stdin),
"rack.errors" => $stderr, "rack.errors" => $stderr,
"rack.multithread" => false, "rack.multithread" => false,
@ -40,20 +42,20 @@ module Rack
end end
def self.send_headers(status, headers) def self.send_headers(status, headers)
STDOUT.print "Status: #{status}\r\n" $stdout.print "Status: #{status}\r\n"
headers.each { |k, vs| headers.each { |k, vs|
vs.split("\n").each { |v| vs.split("\n").each { |v|
STDOUT.print "#{k}: #{v}\r\n" $stdout.print "#{k}: #{v}\r\n"
} }
} }
STDOUT.print "\r\n" $stdout.print "\r\n"
STDOUT.flush $stdout.flush
end end
def self.send_body(body) def self.send_body(body)
body.each { |part| body.each { |part|
STDOUT.print part $stdout.print part
STDOUT.flush $stdout.flush
} }
end end
end end

View file

@ -36,7 +36,7 @@ module Rack
rack_input = RewindableInput.new(request.in) rack_input = RewindableInput.new(request.in)
env.update({"rack.version" => [1,1], env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => request.err, "rack.errors" => request.err,

View file

@ -20,7 +20,7 @@ module Rack
rack_input = RewindableInput.new($stdin.read.to_s) rack_input = RewindableInput.new($stdin.read.to_s)
env.update( env.update(
"rack.version" => [1,1], "rack.version" => Rack::VERSION,
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,
"rack.multithread" => false, "rack.multithread" => false,

View file

@ -52,7 +52,7 @@ module Rack
rack_input = request.body || StringIO.new('') rack_input = request.body || StringIO.new('')
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,1], env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,

View file

@ -17,9 +17,6 @@ module Rack
def initialize(settings = {}) def initialize(settings = {})
@app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app])) @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
@log = Object.new
def @log.info(*args); end
def @log.error(*args); end
super(settings) super(settings)
end end
@ -36,7 +33,7 @@ module Rack
rack_input = StringIO.new(input_body) rack_input = StringIO.new(input_body)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,1], env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,
"rack.multithread" => true, "rack.multithread" => true,

View file

@ -7,12 +7,15 @@ module Rack
class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet class WEBrick < ::WEBrick::HTTPServlet::AbstractServlet
def self.run(app, options={}) def self.run(app, options={})
options[:BindAddress] = options.delete(:Host) if options[:Host] options[:BindAddress] = options.delete(:Host) if options[:Host]
server = ::WEBrick::HTTPServer.new(options) @server = ::WEBrick::HTTPServer.new(options)
server.mount "/", Rack::Handler::WEBrick, app @server.mount "/", Rack::Handler::WEBrick, app
trap(:INT) { server.shutdown } yield @server if block_given?
trap(:TERM) { server.shutdown } @server.start
yield server if block_given? end
server.start
def self.shutdown
@server.shutdown
@server = nil
end end
def initialize(server, app) def initialize(server, app)
@ -27,7 +30,7 @@ module Rack
rack_input = StringIO.new(req.body.to_s) rack_input = StringIO.new(req.body.to_s)
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding) rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
env.update({"rack.version" => [1,1], env.update({"rack.version" => Rack::VERSION,
"rack.input" => rack_input, "rack.input" => rack_input,
"rack.errors" => $stderr, "rack.errors" => $stderr,

View file

@ -7,6 +7,7 @@ module Rack
class Lint class Lint
def initialize(app) def initialize(app)
@app = app @app = app
@content_length = nil
end end
# :stopdoc: # :stopdoc:
@ -51,15 +52,16 @@ module Rack
check_headers headers check_headers headers
## and the *body*. ## and the *body*.
check_content_type status, headers check_content_type status, headers
check_content_length status, headers, env check_content_length status, headers
@head_request = env["REQUEST_METHOD"] == "HEAD"
[status, headers, self] [status, headers, self]
end end
## == The Environment ## == The Environment
def check_env(env) def check_env(env)
## The environment must be an true instance of Hash (no ## The environment must be an instance of Hash that includes
## subclassing allowed) that includes CGI-like headers. ## CGI-like headers. The application is free to modify the
## The application is free to modify the environment. ## environment.
assert("env #{env.inspect} is not a Hash, but #{env.class}") { assert("env #{env.inspect} is not a Hash, but #{env.class}") {
env.kind_of? Hash env.kind_of? Hash
} }
@ -288,10 +290,6 @@ module Rack
@input = input @input = input
end end
def size
@input.size
end
## * +gets+ must be called without arguments and return a string, ## * +gets+ must be called without arguments and return a string,
## or +nil+ on EOF. ## or +nil+ on EOF.
def gets(*args) def gets(*args)
@ -481,7 +479,7 @@ module Rack
end end
## === The Content-Length ## === The Content-Length
def check_content_length(status, headers, env) def check_content_length(status, headers)
headers.each { |key, value| headers.each { |key, value|
if key.downcase == 'content-length' if key.downcase == 'content-length'
## There must not be a <tt>Content-Length</tt> header when the ## There must not be a <tt>Content-Length</tt> header when the
@ -489,49 +487,43 @@ module Rack
assert("Content-Length header found in #{status} response, not allowed") { assert("Content-Length header found in #{status} response, not allowed") {
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
} }
@content_length = value
bytes = 0 end
string_body = true }
if @body.respond_to?(:to_ary)
@body.each { |part|
unless part.kind_of?(String)
string_body = false
break
end end
bytes += Rack::Utils.bytesize(part) def verify_content_length(bytes)
} if @head_request
if env["REQUEST_METHOD"] == "HEAD"
assert("Response body was given for HEAD request, but should be empty") { assert("Response body was given for HEAD request, but should be empty") {
bytes == 0 bytes == 0
} }
else elsif @content_length
if string_body assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
assert("Content-Length header was #{value}, but should be #{bytes}") { @content_length == bytes.to_s
value == bytes.to_s
} }
end end
end end
end
return
end
}
end
## === The Body ## === The Body
def each def each
@closed = false @closed = false
bytes = 0
## The Body must respond to +each+ ## The Body must respond to +each+
assert("Response body must respond to each") do
@body.respond_to?(:each)
end
@body.each { |part| @body.each { |part|
## and must only yield String values. ## and must only yield String values.
assert("Body yielded non-string value #{part.inspect}") { assert("Body yielded non-string value #{part.inspect}") {
part.kind_of? String part.kind_of? String
} }
bytes += Rack::Utils.bytesize(part)
yield part yield part
} }
verify_content_length(bytes)
## ##
## The Body itself should not be an instance of String, as this will ## The Body itself should not be an instance of String, as this will
## break in Ruby 1.9. ## break in Ruby 1.9.

View file

@ -87,6 +87,7 @@ module Rack
".gif" => "image/gif", ".gif" => "image/gif",
".gz" => "application/x-gzip", ".gz" => "application/x-gzip",
".h" => "text/x-c", ".h" => "text/x-c",
".htc" => "text/x-component",
".hh" => "text/x-c", ".hh" => "text/x-c",
".htm" => "text/html", ".htm" => "text/html",
".html" => "text/html", ".html" => "text/html",
@ -186,6 +187,7 @@ module Rack
".vrml" => "model/vrml", ".vrml" => "model/vrml",
".war" => "application/java-archive", ".war" => "application/java-archive",
".wav" => "audio/x-wav", ".wav" => "audio/x-wav",
".webm" => "video/webm",
".wma" => "audio/x-ms-wma", ".wma" => "audio/x-ms-wma",
".wmv" => "video/x-ms-wmv", ".wmv" => "video/x-ms-wmv",
".wmx" => "video/x-ms-wmx", ".wmx" => "video/x-ms-wmx",

View file

@ -1,5 +1,6 @@
require 'uri' require 'uri'
require 'stringio' require 'stringio'
require 'rack'
require 'rack/lint' require 'rack/lint'
require 'rack/utils' require 'rack/utils'
require 'rack/response' require 'rack/response'
@ -40,7 +41,7 @@ module Rack
end end
DEFAULT_ENV = { DEFAULT_ENV = {
"rack.version" => [1,1], "rack.version" => Rack::VERSION,
"rack.input" => StringIO.new, "rack.input" => StringIO.new,
"rack.errors" => StringIO.new, "rack.errors" => StringIO.new,
"rack.multithread" => true, "rack.multithread" => true,

View file

@ -35,6 +35,10 @@ module Rack
end end
def call(env) def call(env)
dup._call(env)
end
def _call(env)
@script_name = env["SCRIPT_NAME"] @script_name = env["SCRIPT_NAME"]
@app.call(env.merge('rack.recursive.include' => method(:include))) @app.call(env.merge('rack.recursive.include' => method(:include)))
rescue ForwardRequest => req rescue ForwardRequest => req

View file

@ -80,11 +80,13 @@ module Rack
def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end def script_name=(s); @env["SCRIPT_NAME"] = s.to_s end
def path_info=(s); @env["PATH_INFO"] = s.to_s end def path_info=(s); @env["PATH_INFO"] = s.to_s end
def delete?; request_method == "DELETE" end
def get?; request_method == "GET" end def get?; request_method == "GET" end
def head?; request_method == "HEAD" end
def options?; request_method == "OPTIONS" end
def post?; request_method == "POST" end def post?; request_method == "POST" end
def put?; request_method == "PUT" end def put?; request_method == "PUT" end
def delete?; request_method == "DELETE" end def trace?; request_method == "TRACE" end
def head?; request_method == "HEAD" end
# The set of form-data media-types. Requests that do not indicate # The set of form-data media-types. Requests that do not indicate
# one of the media types presents in this list will not be eligible # one of the media types presents in this list will not be eligible
@ -253,7 +255,7 @@ module Rack
def ip def ip
if addr = @env['HTTP_X_FORWARDED_FOR'] if addr = @env['HTTP_X_FORWARDED_FOR']
addr.split(',').last.strip (addr.split(',').grep(/\d\./).first || @env['REMOTE_ADDR']).to_s.strip
else else
@env['REMOTE_ADDR'] @env['REMOTE_ADDR']
end end

View file

@ -1,5 +1,6 @@
require 'rack/request' require 'rack/request'
require 'rack/utils' require 'rack/utils'
require 'time'
module Rack module Rack
# Rack::Response provides a convenient interface to create a Rack # Rack::Response provides a convenient interface to create a Rack

View file

@ -1,4 +1,6 @@
# -*- encoding: binary -*-
require 'tempfile' require 'tempfile'
require 'rack/utils'
module Rack module Rack
# Class which can make any IO object rewindable, including non-rewindable ones. It does # Class which can make any IO object rewindable, including non-rewindable ones. It does
@ -74,8 +76,9 @@ module Rack
@rewindable_io.chmod(0000) @rewindable_io.chmod(0000)
@rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding) @rewindable_io.set_encoding(Encoding::BINARY) if @rewindable_io.respond_to?(:set_encoding)
@rewindable_io.binmode @rewindable_io.binmode
if filesystem_has_posix_semantics? && !tempfile_unlink_contains_bug? if filesystem_has_posix_semantics?
@rewindable_io.unlink @rewindable_io.unlink
raise 'Unlink failed. IO closed.' if @rewindable_io.closed?
@unlinked = true @unlinked = true
end end
@ -84,7 +87,7 @@ module Rack
entire_buffer_written_out = false entire_buffer_written_out = false
while !entire_buffer_written_out while !entire_buffer_written_out
written = @rewindable_io.write(buffer) written = @rewindable_io.write(buffer)
entire_buffer_written_out = written == buffer.size entire_buffer_written_out = written == Rack::Utils.bytesize(buffer)
if !entire_buffer_written_out if !entire_buffer_written_out
buffer.slice!(0 .. written - 1) buffer.slice!(0 .. written - 1)
end end
@ -96,15 +99,5 @@ module Rack
def filesystem_has_posix_semantics? def filesystem_has_posix_semantics?
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/ RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
end end
def tempfile_unlink_contains_bug?
# The tempfile library as included in Ruby 1.9.1-p152 and later
# contains a bug: unlinking an open Tempfile object also closes
# it, which breaks our expected POSIX semantics. This problem
# has been fixed in Ruby 1.9.2, but the Ruby team chose not to
# include the bug fix in later versions of the 1.9.1 series.
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : "ruby"
ruby_engine == "ruby" && RUBY_VERSION == "1.9.1" && RUBY_PATCHLEVEL >= 152
end
end end
end end

View file

@ -2,8 +2,10 @@ require 'rack/file'
module Rack module Rack
class File #:nodoc: class File #:nodoc:
unless instance_methods(false).include?('to_path')
alias :to_path :path alias :to_path :path
end end
end
# = Sendfile # = Sendfile
# #
@ -11,7 +13,7 @@ module Rack
# served from a file and replaces it with a server specific X-Sendfile # served from a file and replaces it with a server specific X-Sendfile
# header. The web server is then responsible for writing the file contents # header. The web server is then responsible for writing the file contents
# to the client. This can dramatically reduce the amount of work required # to the client. This can dramatically reduce the amount of work required
# by the Ruby backend and takes advantage of the web servers optimized file # by the Ruby backend and takes advantage of the web server's optimized file
# delivery code. # delivery code.
# #
# In order to take advantage of this middleware, the response body must # In order to take advantage of this middleware, the response body must
@ -31,19 +33,19 @@ module Rack
# a private "/files/" area, enable X-Accel-Redirect, and pass the special # a private "/files/" area, enable X-Accel-Redirect, and pass the special
# X-Sendfile-Type and X-Accel-Mapping headers to the backend: # X-Sendfile-Type and X-Accel-Mapping headers to the backend:
# #
# location /files/ { # location ~ /files/(.*) {
# internal; # internal;
# alias /var/www/; # alias /var/www/$1;
# } # }
# #
# location / { # location / {
# proxy_redirect false; # proxy_redirect off;
# #
# proxy_set_header Host $host; # proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# #
# proxy_set_header X-Sendfile-Type X-Accel-Redirect # proxy_set_header X-Sendfile-Type X-Accel-Redirect;
# proxy_set_header X-Accel-Mapping /files/=/var/www/; # proxy_set_header X-Accel-Mapping /files/=/var/www/;
# #
# proxy_pass http://127.0.0.1:8080/; # proxy_pass http://127.0.0.1:8080/;

View file

@ -57,7 +57,7 @@ module Rack
} }
opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f| opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
options[:pid] = ::File.expand_path(f) options[:pid] = f
} }
opts.separator "" opts.separator ""
@ -74,17 +74,66 @@ module Rack
end end
end end
opt_parser.parse! args opt_parser.parse! args
options[:rack_file] = args.last if args.last options[:config] = args.last if args.last
options options
end end
end end
def self.start # Start a new rack server (like running rackup). This will parse ARGV and
new.start # provide standard ARGV rackup options, defaulting to load 'config.ru'.
#
# Providing an options hash will prevent ARGV parsing and will not include
# any default options.
#
# This method can be used to very easily launch a CGI application, for
# example:
#
# Rack::Server.start(
# :app => lambda do |e|
# [200, {'Content-Type' => 'text/html'}, ['hello world']]
# end,
# :server => 'cgi'
# )
#
# Further options available here are documented on Rack::Server#initialize
def self.start(options = nil)
new(options).start
end end
attr_accessor :options attr_writer :options
# Options may include:
# * :app
# a rack application to run (overrides :config)
# * :config
# a rackup configuration file path to load (.ru)
# * :environment
# this selects the middleware that will be wrapped around
# your application. Default options available are:
# - development: CommonLogger, ShowExceptions, and Lint
# - deployment: CommonLogger
# - none: no extra middleware
# note: when the server is a cgi server, CommonLogger is not included.
# * :server
# choose a specific Rack::Handler, e.g. cgi, fcgi, webrick
# * :daemonize
# if true, the server will daemonize itself (fork, detach, etc)
# * :pid
# path to write a pid file after daemonize
# * :Host
# the host address to bind to (used by supporting Rack::Handler)
# * :Port
# the port to bind to (used by supporting Rack::Handler)
# * :AccessLog
# webrick acess log options (or supporting Rack::Handler)
# * :debug
# turn on debug output ($DEBUG = true)
# * :warn
# turn on warnings ($-w = true)
# * :include
# add given paths to $LOAD_PATH
# * :require
# require the given libraries
def initialize(options = nil) def initialize(options = nil)
@options = options @options = options
end end
@ -100,17 +149,17 @@ module Rack
:Port => 9292, :Port => 9292,
:Host => "0.0.0.0", :Host => "0.0.0.0",
:AccessLog => [], :AccessLog => [],
:rack_file => ::File.expand_path("config.ru") :config => "config.ru"
} }
end end
def app def app
@app ||= begin @app ||= begin
if !::File.exist? options[:rack_file] if !::File.exist? options[:config]
abort "configuration #{options[:rack_file]} not found" abort "configuration #{options[:config]} not found"
end end
app, options = Rack::Builder.parse_file(self.options[:rack_file], opt_parser) app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
self.options.merge! options self.options.merge! options
app app
end end
@ -119,7 +168,7 @@ module Rack
def self.middleware def self.middleware
@middleware ||= begin @middleware ||= begin
m = Hash.new {|h,k| h[k] = []} m = Hash.new {|h,k| h[k] = []}
m["deployment"].concat [lambda {|server| server.server =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }] m["deployment"].concat [lambda {|server| server.server.name =~ /CGI/ ? nil : [Rack::CommonLogger, $stderr] }]
m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]] m["development"].concat m["deployment"] + [[Rack::ShowExceptions], [Rack::Lint]]
m m
end end
@ -143,7 +192,7 @@ module Rack
end end
if includes = options[:include] if includes = options[:include]
$LOAD_PATH.unshift *includes $LOAD_PATH.unshift(*includes)
end end
if library = options[:require] if library = options[:require]
@ -152,11 +201,20 @@ module Rack
daemonize_app if options[:daemonize] daemonize_app if options[:daemonize]
write_pid if options[:pid] write_pid if options[:pid]
trap(:INT) do
if server.respond_to?(:shutdown)
server.shutdown
else
exit
end
end
server.run wrapped_app, options server.run wrapped_app, options
end end
def server def server
@_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
end end
private private
@ -168,6 +226,7 @@ module Rack
args.clear if ENV.include?("REQUEST_METHOD") args.clear if ENV.include?("REQUEST_METHOD")
options.merge! opt_parser.parse! args options.merge! opt_parser.parse! args
ENV["RACK_ENV"] = options[:environment]
options options
end end

View file

@ -31,7 +31,7 @@ module Rack
@mutex = Mutex.new @mutex = Mutex.new
mserv = @default_options[:memcache_server] mserv = @default_options[:memcache_server]
mopts = @default_options. mopts = @default_options.
reject{|k,v| MemCache::DEFAULT_OPTIONS.include? k } reject{|k,v| !MemCache::DEFAULT_OPTIONS.include? k }
@pool = MemCache.new mserv, mopts @pool = MemCache.new mserv, mopts
unless @pool.active? and @pool.servers.any?{|c| c.alive? } unless @pool.active? and @pool.servers.any?{|c| c.alive? }
raise 'No memcache servers' raise 'No memcache servers'

View file

@ -35,21 +35,20 @@ module Rack
end end
def call(env) def call(env)
path = env["PATH_INFO"].to_s path = env["PATH_INFO"]
script_name = env['SCRIPT_NAME'] script_name = env['SCRIPT_NAME']
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT') hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
@mapping.each { |host, location, match, app| @mapping.each { |host, location, match, app|
next unless (hHost == host || sName == host \ next unless (hHost == host || sName == host \
|| (host.nil? && (hHost == sName || hHost == sName+':'+sPort))) || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
next unless path =~ match && rest = $1 next unless path.to_s =~ match && rest = $1
next unless rest.empty? || rest[0] == ?/ next unless rest.empty? || rest[0] == ?/
env.merge!('SCRIPT_NAME' => (script_name + location), 'PATH_INFO' => rest)
return app.call( return app.call(env)
env.merge(
'SCRIPT_NAME' => (script_name + location),
'PATH_INFO' => rest))
} }
[404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]] [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
ensure
env.merge! 'PATH_INFO' => path, 'SCRIPT_NAME' => script_name
end end
end end
end end

View file

@ -1,5 +1,6 @@
# -*- encoding: binary -*- # -*- encoding: binary -*-
require 'fileutils'
require 'set' require 'set'
require 'tempfile' require 'tempfile'
@ -38,9 +39,6 @@ module Rack
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p| (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
k, v = p.split('=', 2).map { |x| unescape(x) } k, v = p.split('=', 2).map { |x| unescape(x) }
if v =~ /^("|')(.*)\1$/
v = $2.gsub('\\'+$1, $1)
end
if cur = params[k] if cur = params[k]
if cur.class == Array if cur.class == Array
params[k] << v params[k] << v
@ -69,9 +67,6 @@ module Rack
module_function :parse_nested_query module_function :parse_nested_query
def normalize_params(params, name, v = nil) def normalize_params(params, name, v = nil)
if v and v =~ /^("|')(.*)\1$/
v = $2.gsub('\\'+$1, $1)
end
name =~ %r(\A[\[\]]*([^\[\]]+)\]*) name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
k = $1 || '' k = $1 || ''
after = $' || '' after = $' || ''
@ -133,13 +128,18 @@ module Rack
end end
module_function :build_nested_query module_function :build_nested_query
ESCAPE_HTML = {
"&" => "&amp;",
"<" => "&lt;",
">" => "&gt;",
"'" => "&#39;",
'"' => "&quot;",
}
ESCAPE_HTML_PATTERN = Regexp.union(ESCAPE_HTML.keys)
# Escape ampersands, brackets and quotes to their HTML/XML entities. # Escape ampersands, brackets and quotes to their HTML/XML entities.
def escape_html(string) def escape_html(string)
string.to_s.gsub("&", "&amp;"). string.to_s.gsub(ESCAPE_HTML_PATTERN){|c| ESCAPE_HTML[c] }
gsub("<", "&lt;").
gsub(">", "&gt;").
gsub("'", "&#39;").
gsub('"', "&quot;")
end end
module_function :escape_html module_function :escape_html
@ -180,8 +180,8 @@ module Rack
path = "; path=" + value[:path] if value[:path] path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here. # According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces... # N.B.: cgi.rb uses spaces...
expires = "; expires=" + value[:expires].clone.gmtime. expires = "; expires=" +
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] rfc2822(value[:expires].clone.gmtime) if value[:expires]
secure = "; secure" if value[:secure] secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly] httponly = "; HttpOnly" if value[:httponly]
value = value[:value] value = value[:value]
@ -192,12 +192,12 @@ module Rack
"#{domain}#{path}#{expires}#{secure}#{httponly}" "#{domain}#{path}#{expires}#{secure}#{httponly}"
case header["Set-Cookie"] case header["Set-Cookie"]
when Array when nil, ''
header["Set-Cookie"] << cookie
when String
header["Set-Cookie"] = [header["Set-Cookie"], cookie]
when nil
header["Set-Cookie"] = cookie header["Set-Cookie"] = cookie
when String
header["Set-Cookie"] = [header["Set-Cookie"], cookie].join("\n")
when Array
header["Set-Cookie"] = (header["Set-Cookie"] + [cookie]).join("\n")
end end
nil nil
@ -205,14 +205,25 @@ module Rack
module_function :set_cookie_header! module_function :set_cookie_header!
def delete_cookie_header!(header, key, value = {}) def delete_cookie_header!(header, key, value = {})
unless Array === header["Set-Cookie"] case header["Set-Cookie"]
header["Set-Cookie"] = [header["Set-Cookie"]].compact when nil, ''
cookies = []
when String
cookies = header["Set-Cookie"].split("\n")
when Array
cookies = header["Set-Cookie"]
end end
header["Set-Cookie"].reject! { |cookie| cookies.reject! { |cookie|
if value[:domain]
cookie =~ /\A#{escape(key)}=.*domain=#{value[:domain]}/
else
cookie =~ /\A#{escape(key)}=/ cookie =~ /\A#{escape(key)}=/
end
} }
header["Set-Cookie"] = cookies.join("\n")
set_cookie_header!(header, key, set_cookie_header!(header, key,
{:value => '', :path => nil, :domain => nil, {:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value)) :expires => Time.at(0) }.merge(value))
@ -234,6 +245,22 @@ module Rack
end end
module_function :bytesize module_function :bytesize
# Modified version of stdlib time.rb Time#rfc2822 to use '%d-%b-%Y' instead
# of '% %b %Y'.
# It assumes that the time is in GMT to comply to the RFC 2109.
#
# NOTE: I'm not sure the RFC says it requires GMT, but is ambigous enough
# that I'm certain someone implemented only that option.
# Do not use %a and %b from Time.strptime, it would use localized names for
# weekday and month.
#
def rfc2822(time)
wday = Time::RFC2822_DAY_NAME[time.wday]
mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
time.strftime("#{wday}, %d-#{mon}-%Y %T GMT")
end
module_function :rfc2822
# Context allows the use of a compatible middleware at different points # Context allows the use of a compatible middleware at different points
# in a request handling stack. A compatible middleware must define # in a request handling stack. A compatible middleware must define
# #context which should take the arguments env and app. The first of which # #context which should take the arguments env and app. The first of which
@ -291,7 +318,8 @@ module Rack
end end
def [](k) def [](k)
super(@names[k] ||= @names[k.downcase]) super(@names[k]) if @names[k]
super(@names[k.downcase])
end end
def []=(k, v) def []=(k, v)
@ -478,11 +506,31 @@ module Rack
head = buf.slice!(0, i+2) # First \r\n head = buf.slice!(0, i+2) # First \r\n
buf.slice!(0, 2) # Second \r\n buf.slice!(0, 2) # Second \r\n
filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1] token = /[^\s()<>,;:\\"\/\[\]?=]+/
condisp = /Content-Disposition:\s*#{token}\s*/i
dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/
rfc2183 = /^#{condisp}(#{dispparm})+$/i
broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i
if head =~ rfc2183
filename = Hash[head.scan(dispparm)]['filename']
filename = $1 if filename and filename =~ /^"(.*)"$/
elsif head =~ broken_quoted
filename = $1
elsif head =~ broken_unquoted
filename = $1
end
if filename && filename !~ /\\[^\\"]/
filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
end
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1] content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1] name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
if content_type || filename if filename
body = Tempfile.new("RackMultipart") body = Tempfile.new("RackMultipart")
body.binmode if body.respond_to?(:binmode) body.binmode if body.respond_to?(:binmode)
end end
@ -519,8 +567,7 @@ module Rack
# This handles the full Windows paths given by Internet Explorer # This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting # (and perhaps other broken user agents) without affecting
# those which give the lone filename. # those which give the lone filename.
filename =~ /^(?:.*[:\\\/])?(.*)/m filename = filename.split(/[\/\\]/).last
filename = $1
data = {:filename => filename, :type => content_type, data = {:filename => filename, :type => content_type,
:name => name, :tempfile => body, :head => head} :name => name, :tempfile => body, :head => head}

View file

@ -1,6 +1,6 @@
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "rack" s.name = "rack"
s.version = "1.1.0" s.version = "1.2.0"
s.platform = Gem::Platform::RUBY s.platform = Gem::Platform::RUBY
s.summary = "a modular Ruby webserver interface" s.summary = "a modular Ruby webserver interface"
@ -14,23 +14,23 @@ middleware) into a single method call.
Also see http://rack.rubyforge.org. Also see http://rack.rubyforge.org.
EOF EOF
s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*}'] + s.files = Dir['{bin/*,contrib/*,example/*,lib/**/*,spec/**/*}'] +
%w(COPYING KNOWN-ISSUES rack.gemspec RDOX README SPEC) %w(COPYING KNOWN-ISSUES rack.gemspec README SPEC)
s.bindir = 'bin' s.bindir = 'bin'
s.executables << 'rackup' s.executables << 'rackup'
s.require_path = 'lib' s.require_path = 'lib'
s.has_rdoc = true s.has_rdoc = true
s.extra_rdoc_files = ['README', 'SPEC', 'KNOWN-ISSUES'] s.extra_rdoc_files = ['README', 'SPEC', 'KNOWN-ISSUES']
s.test_files = Dir['test/{test,spec}_*.rb'] s.test_files = Dir['spec/spec_*.rb']
s.author = 'Christian Neukirchen' s.author = 'Christian Neukirchen'
s.email = 'chneukirchen@gmail.com' s.email = 'chneukirchen@gmail.com'
s.homepage = 'http://rack.rubyforge.org' s.homepage = 'http://rack.rubyforge.org'
s.rubyforge_project = 'rack' s.rubyforge_project = 'rack'
s.add_development_dependency 'test-spec' s.add_development_dependency 'bacon'
s.add_development_dependency 'rake'
s.add_development_dependency 'camping'
s.add_development_dependency 'fcgi' s.add_development_dependency 'fcgi'
s.add_development_dependency 'memcache-client' s.add_development_dependency 'memcache-client'
s.add_development_dependency 'mongrel' s.add_development_dependency 'mongrel'

View file

@ -1,6 +1,6 @@
server.modules = ("mod_fastcgi", "mod_cgi") server.modules = ("mod_fastcgi", "mod_cgi")
server.document-root = "." server.document-root = "."
server.errorlog = "lighttpd.errors" server.errorlog = var.CWD + "/lighttpd.errors"
server.port = 9203 server.port = 9203
server.event-handler = "select" server.event-handler = "select"
@ -9,7 +9,8 @@ cgi.assign = ("/test" => "",
# ".ru" => "" # ".ru" => ""
) )
fastcgi.server = ("test.fcgi" => ("localhost" => fastcgi.server = (
"test.fcgi" => ("localhost" =>
("min-procs" => 1, ("min-procs" => 1,
"socket" => "/tmp/rack-test-fcgi", "socket" => "/tmp/rack-test-fcgi",
"bin-path" => "test.fcgi")), "bin-path" => "test.fcgi")),
@ -17,4 +18,8 @@ fastcgi.server = ("test.fcgi" => ("localhost" =>
("min-procs" => 1, ("min-procs" => 1,
"socket" => "/tmp/rack-test-ru-fcgi", "socket" => "/tmp/rack-test-ru-fcgi",
"bin-path" => "test.ru")), "bin-path" => "test.ru")),
"sample_rackup.ru" => ("localhost" =>
("min-procs" => 1,
"socket" => "/tmp/rack-test-rackup-fcgi",
"bin-path" => CWD + "/rackup_stub.rb sample_rackup.ru")),
) )

6
vendor/plugins/rack/spec/cgi/rackup_stub.rb vendored Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env ruby
# -*- ruby -*-
$:.unshift '../../lib'
require 'rack'
Rack::Server.start

View file

@ -0,0 +1,5 @@
# -*- ruby -*-
require '../testrequest'
run TestRequest.new

5
vendor/plugins/rack/spec/cgi/test.ru vendored Executable file
View file

@ -0,0 +1,5 @@
#!../../bin/rackup
# -*- ruby -*-
require '../testrequest'
run TestRequest.new

View file

@ -0,0 +1,7 @@
--AaB03x
Content-Type: image/jpeg
Content-Disposition: attachment; name="files"; filename=genome.jpeg; modification-date="Wed, 12 Feb 1997 16:29:51 -0500";
Content-Description: a complete map of the human genome
contents
--AaB03x--

View file

@ -0,0 +1,6 @@
--AaB03x
Content-Disposition: form-data; name="files"; filename="escape \"quotes"
Content-Type: application/octet-stream
contents
--AaB03x--

View file

@ -0,0 +1,7 @@
--AaB03x
Content-Type: image/jpeg
Content-Disposition: attachment; name="files"; filename=""human" genome.jpeg"; modification-date="Wed, 12 Feb 1997 16:29:51 -0500";
Content-Description: a complete map of the human genome
contents
--AaB03x--

View file

@ -0,0 +1,6 @@
--AaB03x
Content-Disposition: form-data; name="files"; filename="escape %22quotes"
Content-Type: application/octet-stream
contents
--AaB03x--

View file

@ -0,0 +1,6 @@
--AaB03x
Content-Disposition: form-data; name="files"; filename="escape "quotes"
Content-Type: application/octet-stream
contents
--AaB03x--

View file

@ -1,10 +1,7 @@
require 'test/spec'
require 'rack/auth/basic' require 'rack/auth/basic'
require 'rack/mock' require 'rack/mock'
context 'Rack::Auth::Basic' do describe Rack::Auth::Basic do
def realm def realm
'WallysWorld' 'WallysWorld'
end end
@ -19,7 +16,7 @@ context 'Rack::Auth::Basic' do
app app
end end
setup do before do
@request = Rack::MockRequest.new(protected_app) @request = Rack::MockRequest.new(protected_app)
end end
@ -39,26 +36,26 @@ context 'Rack::Auth::Basic' do
response.body.should.be.empty response.body.should.be.empty
end end
specify 'should challenge correctly when no credentials are specified' do should 'challenge correctly when no credentials are specified' do
request do |response| request do |response|
assert_basic_auth_challenge response assert_basic_auth_challenge response
end end
end end
specify 'should rechallenge if incorrect credentials are specified' do should 'rechallenge if incorrect credentials are specified' do
request_with_basic_auth 'joe', 'password' do |response| request_with_basic_auth 'joe', 'password' do |response|
assert_basic_auth_challenge response assert_basic_auth_challenge response
end end
end end
specify 'should return application output if correct credentials are specified' do should 'return application output if correct credentials are specified' do
request_with_basic_auth 'Boss', 'password' do |response| request_with_basic_auth 'Boss', 'password' do |response|
response.status.should.equal 200 response.status.should.equal 200
response.body.to_s.should.equal 'Hi Boss' response.body.to_s.should.equal 'Hi Boss'
end end
end end
specify 'should return 400 Bad Request if different auth scheme used' do should 'return 400 Bad Request if different auth scheme used' do
request 'HTTP_AUTHORIZATION' => 'Digest params' do |response| request 'HTTP_AUTHORIZATION' => 'Digest params' do |response|
response.should.be.a.client_error response.should.be.a.client_error
response.status.should.equal 400 response.status.should.equal 400
@ -66,8 +63,8 @@ context 'Rack::Auth::Basic' do
end end
end end
specify 'realm as optional constructor arg' do it 'takes realm as optional constructor arg' do
app = Rack::Auth::Basic.new(unprotected_app, realm) { true } app = Rack::Auth::Basic.new(unprotected_app, realm) { true }
assert_equal realm, app.realm realm.should == app.realm
end end
end end

View file

@ -1,10 +1,7 @@
require 'test/spec'
require 'rack/auth/digest/md5' require 'rack/auth/digest/md5'
require 'rack/mock' require 'rack/mock'
context 'Rack::Auth::Digest::MD5' do describe Rack::Auth::Digest::MD5 do
def realm def realm
'WallysWorld' 'WallysWorld'
end end
@ -45,7 +42,7 @@ context 'Rack::Auth::Digest::MD5' do
Rack::MethodOverride.new(protected_app) Rack::MethodOverride.new(protected_app)
end end
setup do before do
@request = Rack::MockRequest.new(protected_app) @request = Rack::MockRequest.new(protected_app)
end end
@ -117,20 +114,20 @@ context 'Rack::Auth::Digest::MD5' do
response.should.not.include 'WWW-Authenticate' response.should.not.include 'WWW-Authenticate'
end end
specify 'should challenge when no credentials are specified' do should 'challenge when no credentials are specified' do
request 'GET', '/' do |response| request 'GET', '/' do |response|
assert_digest_auth_challenge response assert_digest_auth_challenge response
end end
end end
specify 'should return application output if correct credentials given' do should 'return application output if correct credentials given' do
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response| request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200 response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice' response.body.to_s.should.equal 'Hi Alice'
end end
end end
specify 'should return application output if correct credentials given (hashed passwords)' do should 'return application output if correct credentials given (hashed passwords)' do
@request = Rack::MockRequest.new(protected_app_with_hashed_passwords) @request = Rack::MockRequest.new(protected_app_with_hashed_passwords)
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response| request_with_digest_auth 'GET', '/', 'Alice', 'correct-password' do |response|
@ -139,19 +136,19 @@ context 'Rack::Auth::Digest::MD5' do
end end
end end
specify 'should rechallenge if incorrect username given' do should 'rechallenge if incorrect username given' do
request_with_digest_auth 'GET', '/', 'Bob', 'correct-password' do |response| request_with_digest_auth 'GET', '/', 'Bob', 'correct-password' do |response|
assert_digest_auth_challenge response assert_digest_auth_challenge response
end end
end end
specify 'should rechallenge if incorrect password given' do should 'rechallenge if incorrect password given' do
request_with_digest_auth 'GET', '/', 'Alice', 'wrong-password' do |response| request_with_digest_auth 'GET', '/', 'Alice', 'wrong-password' do |response|
assert_digest_auth_challenge response assert_digest_auth_challenge response
end end
end end
specify 'should rechallenge with stale parameter if nonce is stale' do should 'rechallenge with stale parameter if nonce is stale' do
begin begin
Rack::Auth::Digest::Nonce.time_limit = 1 Rack::Auth::Digest::Nonce.time_limit = 1
@ -164,39 +161,39 @@ context 'Rack::Auth::Digest::MD5' do
end end
end end
specify 'should return 400 Bad Request if incorrect qop given' do should 'return 400 Bad Request if incorrect qop given' do
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response| request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response|
assert_bad_request response assert_bad_request response
end end
end end
specify 'should return 400 Bad Request if incorrect uri given' do should 'return 400 Bad Request if incorrect uri given' do
request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response| request_with_digest_auth 'GET', '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response|
assert_bad_request response assert_bad_request response
end end
end end
specify 'should return 400 Bad Request if different auth scheme used' do should 'return 400 Bad Request if different auth scheme used' do
request 'GET', '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response| request 'GET', '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response|
assert_bad_request response assert_bad_request response
end end
end end
specify 'should not require credentials for unprotected path' do should 'not require credentials for unprotected path' do
@request = Rack::MockRequest.new(partially_protected_app) @request = Rack::MockRequest.new(partially_protected_app)
request 'GET', '/' do |response| request 'GET', '/' do |response|
response.should.be.ok response.should.be.ok
end end
end end
specify 'should challenge when no credentials are specified for protected path' do should 'challenge when no credentials are specified for protected path' do
@request = Rack::MockRequest.new(partially_protected_app) @request = Rack::MockRequest.new(partially_protected_app)
request 'GET', '/protected' do |response| request 'GET', '/protected' do |response|
assert_digest_auth_challenge response assert_digest_auth_challenge response
end end
end end
specify 'should return application output if correct credentials given for protected path' do should 'return application output if correct credentials given for protected path' do
@request = Rack::MockRequest.new(partially_protected_app) @request = Rack::MockRequest.new(partially_protected_app)
request_with_digest_auth 'GET', '/protected', 'Alice', 'correct-password' do |response| request_with_digest_auth 'GET', '/protected', 'Alice', 'correct-password' do |response|
response.status.should.equal 200 response.status.should.equal 200
@ -204,14 +201,14 @@ context 'Rack::Auth::Digest::MD5' do
end end
end end
specify 'should return application output if correct credentials given for POST' do should 'return application output if correct credentials given for POST' do
request_with_digest_auth 'POST', '/', 'Alice', 'correct-password' do |response| request_with_digest_auth 'POST', '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200 response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice' response.body.to_s.should.equal 'Hi Alice'
end end
end end
specify 'should return application output if correct credentials given for PUT (using method override of POST)' do should 'return application output if correct credentials given for PUT (using method override of POST)' do
@request = Rack::MockRequest.new(protected_app_with_method_override) @request = Rack::MockRequest.new(protected_app_with_method_override)
request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', :input => "_method=put" do |response| request_with_digest_auth 'POST', '/', 'Alice', 'correct-password', :input => "_method=put" do |response|
response.status.should.equal 200 response.status.should.equal 200
@ -219,8 +216,8 @@ context 'Rack::Auth::Digest::MD5' do
end end
end end
specify 'realm as optional constructor arg' do it 'takes realm as optional constructor arg' do
app = Rack::Auth::Digest::MD5.new(unprotected_app, realm) { true } app = Rack::Auth::Digest::MD5.new(unprotected_app, realm) { true }
assert_equal realm, app.realm realm.should == app.realm
end end
end end

View file

@ -1,12 +1,51 @@
require 'test/spec'
require 'rack/builder' require 'rack/builder'
require 'rack/mock' require 'rack/mock'
require 'rack/showexceptions' require 'rack/showexceptions'
require 'rack/auth/basic' require 'rack/urlmap'
context "Rack::Builder" do class NothingMiddleware
specify "chains apps by default" do def initialize(app)
@app = app
end
def call(env)
@@env = env
response = @app.call(env)
response
end
def self.env
@@env
end
end
describe Rack::Builder do
it "supports mapping" do
app = Rack::Builder.new do
map '/' do |outer_env|
run lambda { |inner_env| [200, {}, ['root']] }
end
map '/sub' do
run lambda { |inner_env| [200, {}, ['sub']] }
end
end.to_app
Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
Rack::MockRequest.new(app).get("/sub").body.to_s.should.equal 'sub'
end
it "doesn't dupe env even when mapping" do
app = Rack::Builder.new do
use NothingMiddleware
map '/' do |outer_env|
run lambda { |inner_env|
inner_env['new_key'] = 'new_value'
[200, {}, ['root']]
}
end
end.to_app
Rack::MockRequest.new(app).get("/").body.to_s.should.equal 'root'
NothingMiddleware.env['new_key'].should.equal 'new_value'
end
it "chains apps by default" do
app = Rack::Builder.new do app = Rack::Builder.new do
use Rack::ShowExceptions use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" } run lambda { |env| raise "bzzzt" }
@ -17,7 +56,7 @@ context "Rack::Builder" do
Rack::MockRequest.new(app).get("/").should.be.server_error Rack::MockRequest.new(app).get("/").should.be.server_error
end end
specify "has implicit #to_app" do it "has implicit #to_app" do
app = Rack::Builder.new do app = Rack::Builder.new do
use Rack::ShowExceptions use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" } run lambda { |env| raise "bzzzt" }
@ -28,7 +67,7 @@ context "Rack::Builder" do
Rack::MockRequest.new(app).get("/").should.be.server_error Rack::MockRequest.new(app).get("/").should.be.server_error
end end
specify "supports blocks on use" do it "supports blocks on use" do
app = Rack::Builder.new do app = Rack::Builder.new do
use Rack::ShowExceptions use Rack::ShowExceptions
use Rack::Auth::Basic do |username, password| use Rack::Auth::Basic do |username, password|
@ -49,7 +88,7 @@ context "Rack::Builder" do
response.body.to_s.should.equal 'Hi Boss' response.body.to_s.should.equal 'Hi Boss'
end end
specify "has explicit #to_app" do it "has explicit #to_app" do
app = Rack::Builder.app do app = Rack::Builder.app do
use Rack::ShowExceptions use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" } run lambda { |env| raise "bzzzt" }
@ -60,7 +99,7 @@ context "Rack::Builder" do
Rack::MockRequest.new(app).get("/").should.be.server_error Rack::MockRequest.new(app).get("/").should.be.server_error
end end
specify "apps are initialized once" do should "initialize apps once" do
app = Rack::Builder.new do app = Rack::Builder.new do
class AppClass class AppClass
def initialize def initialize

View file

@ -1,12 +1,9 @@
require 'test/spec'
require 'rack/cascade' require 'rack/cascade'
require 'rack/file'
require 'rack/urlmap'
require 'rack/mock' require 'rack/mock'
require 'rack/urlmap' describe Rack::Cascade do
require 'rack/file'
context "Rack::Cascade" do
docroot = File.expand_path(File.dirname(__FILE__)) docroot = File.expand_path(File.dirname(__FILE__))
app1 = Rack::File.new(docroot) app1 = Rack::File.new(docroot)
@ -15,7 +12,7 @@ context "Rack::Cascade" do
app3 = Rack::URLMap.new("/foo" => lambda { |env| app3 = Rack::URLMap.new("/foo" => lambda { |env|
[200, { "Content-Type" => "text/plain"}, [""]]}) [200, { "Content-Type" => "text/plain"}, [""]]})
specify "should dispatch onward on 404 by default" do should "dispatch onward on 404 by default" do
cascade = Rack::Cascade.new([app1, app2, app3]) cascade = Rack::Cascade.new([app1, app2, app3])
Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok Rack::MockRequest.new(cascade).get("/cgi/test").should.be.ok
Rack::MockRequest.new(cascade).get("/foo").should.be.ok Rack::MockRequest.new(cascade).get("/foo").should.be.ok
@ -23,16 +20,16 @@ context "Rack::Cascade" do
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.forbidden
end end
specify "should dispatch onward on whatever is passed" do should "dispatch onward on whatever is passed" do
cascade = Rack::Cascade.new([app1, app2, app3], [404, 403]) cascade = Rack::Cascade.new([app1, app2, app3], [404, 403])
Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found Rack::MockRequest.new(cascade).get("/cgi/../bla").should.be.not_found
end end
specify "should return 404 if empty" do should "return 404 if empty" do
Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found Rack::MockRequest.new(Rack::Cascade.new([])).get('/').should.be.not_found
end end
specify "should append new app" do should "append new app" do
cascade = Rack::Cascade.new([], [404, 403]) cascade = Rack::Cascade.new([], [404, 403])
Rack::MockRequest.new(cascade).get('/').should.be.not_found Rack::MockRequest.new(cascade).get('/').should.be.not_found
cascade << app2 cascade << app2

View file

@ -1,53 +1,55 @@
require 'test/spec' require File.expand_path('../testrequest', __FILE__)
require 'testrequest' require 'rack/handler/cgi'
context "Rack::Handler::CGI" do describe Rack::Handler::CGI do
include TestRequest::Helpers extend TestRequest::Helpers
setup do
@host = '0.0.0.0' @host = '0.0.0.0'
@port = 9203 @port = 9203
end
# Keep this first. # Keep this first.
specify "startup" do
$pid = fork { $pid = fork {
Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi")) ENV['RACK_ENV'] = 'deployment'
ENV['RUBYLIB'] = [
File.expand_path('../../lib', __FILE__),
ENV['RUBYLIB'],
].compact.join(':')
Dir.chdir(File.expand_path("../cgi", __FILE__)) do
exec "lighttpd -D -f lighttpd.conf" exec "lighttpd -D -f lighttpd.conf"
end
} }
end
specify "should respond" do should "respond" do
sleep 1 sleep 1
lambda {
GET("/test") GET("/test")
}.should.not.raise response.should.not.be.nil
end end
specify "should be a lighttpd" do should "be a lighttpd" do
GET("/test") GET("/test")
status.should.be 200 status.should.equal 200
response["SERVER_SOFTWARE"].should =~ /lighttpd/ response["SERVER_SOFTWARE"].should =~ /lighttpd/
response["HTTP_VERSION"].should.equal "HTTP/1.1" response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal @port.to_s response["SERVER_PORT"].should.equal @port.to_s
response["SERVER_NAME"].should =~ @host response["SERVER_NAME"].should.equal @host
end end
specify "should have rack headers" do should "have rack headers" do
GET("/test") GET("/test")
response["rack.version"].should.equal [1,1] response["rack.version"].should.equal([1,1])
response["rack.multithread"].should.be false response["rack.multithread"].should.be.false
response["rack.multiprocess"].should.be true response["rack.multiprocess"].should.be.true
response["rack.run_once"].should.be true response["rack.run_once"].should.be.true
end end
specify "should have CGI headers on GET" do should "have CGI headers on GET" do
GET("/test") GET("/test")
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test" response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/" response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.equal "" response["PATH_INFO"].should.be.nil
response["QUERY_STRING"].should.equal "" response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal "" response["test.postdata"].should.equal ""
@ -59,7 +61,7 @@ context "Rack::Handler::CGI" do
response["QUERY_STRING"].should.equal "quux=1" response["QUERY_STRING"].should.equal "quux=1"
end end
specify "should have CGI headers on POST" do should "have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200 status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST" response["REQUEST_METHOD"].should.equal "POST"
@ -70,20 +72,20 @@ context "Rack::Handler::CGI" do
response["test.postdata"].should.equal "rack-form-data=23" response["test.postdata"].should.equal "rack-form-data=23"
end end
specify "should support HTTP auth" do should "support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"}) GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end end
specify "should set status" do should "set status" do
GET("/test?secret") GET("/test?secret")
status.should.equal 403 status.should.equal 403
response["rack.url_scheme"].should.equal "http" response["rack.url_scheme"].should.equal "http"
end end
# Keep this last. # Keep this last.
specify "shutdown" do should "shutdown" do
Process.kill 15, $pid Process.kill 15, $pid
Process.wait($pid).should.equal $pid Process.wait($pid).should == $pid
end end
end end

View file

@ -1,15 +1,13 @@
require 'rack/mock'
require 'rack/chunked' require 'rack/chunked'
require 'rack/utils' require 'rack/mock'
context "Rack::Chunked" do
describe Rack::Chunked do
before do before do
@env = Rack::MockRequest. @env = Rack::MockRequest.
env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET') env_for('/', 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET')
end end
specify 'chunks responses with no Content-Length' do should 'chunk responses with no Content-Length' do
app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] } app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env)) response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
response.headers.should.not.include 'Content-Length' response.headers.should.not.include 'Content-Length'
@ -17,7 +15,7 @@ context "Rack::Chunked" do
response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n" response.body.should.equal "5\r\nHello\r\n1\r\n \r\n6\r\nWorld!\r\n0\r\n\r\n"
end end
specify 'chunks empty bodies properly' do should 'chunks empty bodies properly' do
app = lambda { |env| [200, {}, []] } app = lambda { |env| [200, {}, []] }
response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env)) response = Rack::MockResponse.new(*Rack::Chunked.new(app).call(@env))
response.headers.should.not.include 'Content-Length' response.headers.should.not.include 'Content-Length'
@ -25,7 +23,7 @@ context "Rack::Chunked" do
response.body.should.equal "0\r\n\r\n" response.body.should.equal "0\r\n\r\n"
end end
specify 'does not modify response when Content-Length header present' do should 'not modify response when Content-Length header present' do
app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] } app = lambda { |env| [200, {'Content-Length'=>'12'}, ['Hello', ' ', 'World!']] }
status, headers, body = Rack::Chunked.new(app).call(@env) status, headers, body = Rack::Chunked.new(app).call(@env)
status.should.equal 200 status.should.equal 200
@ -34,7 +32,7 @@ context "Rack::Chunked" do
body.join.should.equal 'Hello World!' body.join.should.equal 'Hello World!'
end end
specify 'does not modify response when client is HTTP/1.0' do should 'not modify response when client is HTTP/1.0' do
app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] } app = lambda { |env| [200, {}, ['Hello', ' ', 'World!']] }
@env['HTTP_VERSION'] = 'HTTP/1.0' @env['HTTP_VERSION'] = 'HTTP/1.0'
status, headers, body = Rack::Chunked.new(app).call(@env) status, headers, body = Rack::Chunked.new(app).call(@env)
@ -43,7 +41,7 @@ context "Rack::Chunked" do
body.join.should.equal 'Hello World!' body.join.should.equal 'Hello World!'
end end
specify 'does not modify response when Transfer-Encoding header already present' do should 'not modify response when Transfer-Encoding header already present' do
app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] } app = lambda { |env| [200, {'Transfer-Encoding' => 'identity'}, ['Hello', ' ', 'World!']] }
status, headers, body = Rack::Chunked.new(app).call(@env) status, headers, body = Rack::Chunked.new(app).call(@env)
status.should.equal 200 status.should.equal 200
@ -52,7 +50,7 @@ context "Rack::Chunked" do
end end
[100, 204, 304].each do |status_code| [100, 204, 304].each do |status_code|
specify "does not modify response when status code is #{status_code}" do should "not modify response when status code is #{status_code}" do
app = lambda { |env| [status_code, {}, []] } app = lambda { |env| [status_code, {}, []] }
status, headers, body = Rack::Chunked.new(app).call(@env) status, headers, body = Rack::Chunked.new(app).call(@env)
status.should.equal status_code status.should.equal status_code

View file

@ -1,11 +1,10 @@
require 'test/spec'
require 'stringio'
require 'rack/commonlogger' require 'rack/commonlogger'
require 'rack/lobster'
require 'rack/mock' require 'rack/mock'
context "Rack::CommonLogger" do describe Rack::CommonLogger do
obj = 'foobar'
length = obj.size
app = lambda { |env| app = lambda { |env|
[200, [200,
{"Content-Type" => "text/html", "Content-Length" => length.to_s}, {"Content-Type" => "text/html", "Content-Length" => length.to_s},
@ -19,28 +18,28 @@ context "Rack::CommonLogger" do
{"Content-Type" => "text/html", "Content-Length" => "0"}, {"Content-Type" => "text/html", "Content-Length" => "0"},
[]]} []]}
specify "should log to rack.errors by default" do should "log to rack.errors by default" do
res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/") res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
res.errors.should.not.be.empty res.errors.should.not.be.empty
res.errors.should =~ /"GET \/ " 200 #{length} / res.errors.should =~ /"GET \/ " 200 #{length} /
end end
specify "should log to anything with +write+" do should "log to anything with +write+" do
log = StringIO.new log = StringIO.new
res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/") res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
log.string.should =~ /"GET \/ " 200 #{length} / log.string.should =~ /"GET \/ " 200 #{length} /
end end
specify "should log - content length if header is missing" do should "log - content length if header is missing" do
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/") res = Rack::MockRequest.new(Rack::CommonLogger.new(app_without_length)).get("/")
res.errors.should.not.be.empty res.errors.should.not.be.empty
res.errors.should =~ /"GET \/ " 200 - / res.errors.should =~ /"GET \/ " 200 - /
end end
specify "should log - content length if header is zero" do should "log - content length if header is zero" do
res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/") res = Rack::MockRequest.new(Rack::CommonLogger.new(app_with_zero_length)).get("/")
res.errors.should.not.be.empty res.errors.should.not.be.empty
@ -48,10 +47,6 @@ context "Rack::CommonLogger" do
end end
def length def length
self.class.length
end
def self.length
123 123
end end

View file

@ -1,11 +1,9 @@
require 'test/spec'
require 'time' require 'time'
require 'rack/mock'
require 'rack/conditionalget' require 'rack/conditionalget'
require 'rack/mock'
context "Rack::ConditionalGet" do describe Rack::ConditionalGet do
specify "should set a 304 status and truncate body when If-Modified-Since hits" do should "set a 304 status and truncate body when If-Modified-Since hits" do
timestamp = Time.now.httpdate timestamp = Time.now.httpdate
app = Rack::ConditionalGet.new(lambda { |env| app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Last-Modified'=>timestamp}, ['TEST']] }) [200, {'Last-Modified'=>timestamp}, ['TEST']] })
@ -17,7 +15,7 @@ context "Rack::ConditionalGet" do
response.body.should.be.empty response.body.should.be.empty
end end
specify "should set a 304 status and truncate body when If-None-Match hits" do should "set a 304 status and truncate body when If-None-Match hits" do
app = Rack::ConditionalGet.new(lambda { |env| app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Etag'=>'1234'}, ['TEST']] }) [200, {'Etag'=>'1234'}, ['TEST']] })
@ -28,7 +26,7 @@ context "Rack::ConditionalGet" do
response.body.should.be.empty response.body.should.be.empty
end end
specify "should not affect non-GET/HEAD requests" do should "not affect non-GET/HEAD requests" do
app = Rack::ConditionalGet.new(lambda { |env| app = Rack::ConditionalGet.new(lambda { |env|
[200, {'Etag'=>'1234'}, ['TEST']] }) [200, {'Etag'=>'1234'}, ['TEST']] })

View file

@ -1,12 +1,11 @@
require 'test/spec'
require 'rack/mock'
require 'rack/builder' require 'rack/builder'
require 'rack/content_length'
require 'rack/config' require 'rack/config'
require 'rack/content_length'
require 'rack/lint'
require 'rack/mock'
context "Rack::Config" do describe Rack::Config do
should "accept a block that modifies the environment" do
specify "should accept a block that modifies the environment" do
app = Rack::Builder.new do app = Rack::Builder.new do
use Rack::Lint use Rack::Lint
use Rack::ContentLength use Rack::ContentLength
@ -17,8 +16,8 @@ context "Rack::Config" do
[200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']] [200, {'Content-Type' => 'text/plain'}, [env['greeting'] || '']]
} }
end end
response = Rack::MockRequest.new(app).get('/') response = Rack::MockRequest.new(app).get('/')
response.body.should.equal('hello') response.body.should.equal('hello')
end end
end end

View file

@ -1,20 +1,19 @@
require 'rack/mock'
require 'rack/content_length' require 'rack/content_length'
context "Rack::ContentLength" do describe Rack::ContentLength do
specify "sets Content-Length on String bodies if none is set" do should "set Content-Length on String bodies if none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
response = Rack::ContentLength.new(app).call({}) response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal '13' response[1]['Content-Length'].should.equal '13'
end end
specify "sets Content-Length on Array bodies if none is set" do should "set Content-Length on Array bodies if none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
response = Rack::ContentLength.new(app).call({}) response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal '13' response[1]['Content-Length'].should.equal '13'
end end
specify "does not set Content-Length on variable length bodies" do should "not set Content-Length on variable length bodies" do
body = lambda { "Hello World!" } body = lambda { "Hello World!" }
def body.each ; yield call ; end def body.each ; yield call ; end
@ -23,19 +22,19 @@ context "Rack::ContentLength" do
response[1]['Content-Length'].should.be.nil response[1]['Content-Length'].should.be.nil
end end
specify "does not change Content-Length if it is already set" do should "not change Content-Length if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] } app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'Content-Length' => '1'}, "Hello, World!"] }
response = Rack::ContentLength.new(app).call({}) response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal '1' response[1]['Content-Length'].should.equal '1'
end end
specify "does not set Content-Length on 304 responses" do should "not set Content-Length on 304 responses" do
app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] } app = lambda { |env| [304, {'Content-Type' => 'text/plain'}, []] }
response = Rack::ContentLength.new(app).call({}) response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal nil response[1]['Content-Length'].should.equal nil
end end
specify "does not set Content-Length when Transfer-Encoding is chunked" do should "not set Content-Length when Transfer-Encoding is chunked" do
app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] } app = lambda { |env| [200, {'Transfer-Encoding' => 'chunked'}, []] }
response = Rack::ContentLength.new(app).call({}) response = Rack::ContentLength.new(app).call({})
response[1]['Content-Length'].should.equal nil response[1]['Content-Length'].should.equal nil

View file

@ -1,27 +1,26 @@
require 'rack/mock'
require 'rack/content_type' require 'rack/content_type'
context "Rack::ContentType" do describe Rack::ContentType do
specify "sets Content-Type to default text/html if none is set" do should "set Content-Type to default text/html if none is set" do
app = lambda { |env| [200, {}, "Hello, World!"] } app = lambda { |env| [200, {}, "Hello, World!"] }
status, headers, body = Rack::ContentType.new(app).call({}) status, headers, body = Rack::ContentType.new(app).call({})
headers['Content-Type'].should.equal 'text/html' headers['Content-Type'].should.equal 'text/html'
end end
specify "sets Content-Type to chosen default if none is set" do should "set Content-Type to chosen default if none is set" do
app = lambda { |env| [200, {}, "Hello, World!"] } app = lambda { |env| [200, {}, "Hello, World!"] }
status, headers, body = status, headers, body =
Rack::ContentType.new(app, 'application/octet-stream').call({}) Rack::ContentType.new(app, 'application/octet-stream').call({})
headers['Content-Type'].should.equal 'application/octet-stream' headers['Content-Type'].should.equal 'application/octet-stream'
end end
specify "does not change Content-Type if it is already set" do should "not change Content-Type if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] } app = lambda { |env| [200, {'Content-Type' => 'foo/bar'}, "Hello, World!"] }
status, headers, body = Rack::ContentType.new(app).call({}) status, headers, body = Rack::ContentType.new(app).call({})
headers['Content-Type'].should.equal 'foo/bar' headers['Content-Type'].should.equal 'foo/bar'
end end
specify "case insensitive detection of Content-Type" do should "detect Content-Type case insensitive" do
app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] } app = lambda { |env| [200, {'CONTENT-Type' => 'foo/bar'}, "Hello, World!"] }
status, headers, body = Rack::ContentType.new(app).call({}) status, headers, body = Rack::ContentType.new(app).call({})
headers.to_a.select { |k,v| k.downcase == "content-type" }. headers.to_a.select { |k,v| k.downcase == "content-type" }.

View file

@ -1,11 +1,9 @@
require 'test/spec'
require 'rack/mock'
require 'rack/deflater'
require 'stringio' require 'stringio'
require 'time' # for Time#httpdate require 'time' # for Time#httpdate
require 'rack/deflater'
require 'rack/mock'
context "Rack::Deflater" do describe Rack::Deflater do
def build_response(status, body, accept_encoding, headers = {}) def build_response(status, body, accept_encoding, headers = {})
body = [body] if body.respond_to? :to_str body = [body] if body.respond_to? :to_str
app = lambda { |env| [status, {}, body] } app = lambda { |env| [status, {}, body] }
@ -15,7 +13,7 @@ context "Rack::Deflater" do
return response return response
end end
specify "should be able to deflate bodies that respond to each" do should "be able to deflate bodies that respond to each" do
body = Object.new body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end class << body; def each; yield("foo"); yield("bar"); end; end
@ -32,7 +30,7 @@ context "Rack::Deflater" do
end end
# TODO: This is really just a special case of the above... # TODO: This is really just a special case of the above...
specify "should be able to deflate String bodies" do should "be able to deflate String bodies" do
response = build_response(200, "Hello world!", "deflate") response = build_response(200, "Hello world!", "deflate")
response[0].should.equal(200) response[0].should.equal(200)
@ -45,7 +43,7 @@ context "Rack::Deflater" do
buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000") buf.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
end end
specify "should be able to gzip bodies that respond to each" do should "be able to gzip bodies that respond to each" do
body = Object.new body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end class << body; def each; yield("foo"); yield("bar"); end; end
@ -65,7 +63,7 @@ context "Rack::Deflater" do
gz.close gz.close
end end
specify "should be able to fallback to no deflation" do should "be able to fallback to no deflation" do
response = build_response(200, "Hello world!", "superzip") response = build_response(200, "Hello world!", "superzip")
response[0].should.equal(200) response[0].should.equal(200)
@ -73,7 +71,7 @@ context "Rack::Deflater" do
response[2].should.equal(["Hello world!"]) response[2].should.equal(["Hello world!"])
end end
specify "should be able to skip when there is no response entity body" do should "be able to skip when there is no response entity body" do
response = build_response(304, [], "gzip") response = build_response(304, [], "gzip")
response[0].should.equal(304) response[0].should.equal(304)
@ -81,7 +79,7 @@ context "Rack::Deflater" do
response[2].should.equal([]) response[2].should.equal([])
end end
specify "should handle the lack of an acceptable encoding" do should "handle the lack of an acceptable encoding" do
response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/") response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
response1[0].should.equal(406) response1[0].should.equal(406)
response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"}) response1[1].should.equal({"Content-Type" => "text/plain", "Content-Length" => "71"})
@ -93,7 +91,7 @@ context "Rack::Deflater" do
response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."]) response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
end end
specify "should handle gzip response with Last-Modified header" do should "handle gzip response with Last-Modified header" do
last_modified = Time.now.httpdate last_modified = Time.now.httpdate
app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] } app = lambda { |env| [200, { "Last-Modified" => last_modified }, ["Hello World!"]] }
@ -115,7 +113,7 @@ context "Rack::Deflater" do
gz.close gz.close
end end
specify "should do nothing when no-transform Cache-Control directive present" do should "do nothing when no-transform Cache-Control directive present" do
app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] } app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip") request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
response = Rack::Deflater.new(app).call(request) response = Rack::Deflater.new(app).call(request)

View file

@ -1,16 +1,12 @@
require 'test/spec'
require 'rack/directory' require 'rack/directory'
require 'rack/lint'
require 'rack/mock' require 'rack/mock'
context "Rack::Directory" do describe Rack::Directory do
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] } FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, ['passed!']] }
app = Rack::Directory.new DOCROOT, FILE_CATCH app = Rack::Directory.new DOCROOT, FILE_CATCH
specify "serves directory indices" do should "serve directory indices" do
res = Rack::MockRequest.new(Rack::Lint.new(app)). res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/") get("/cgi/")
@ -18,7 +14,7 @@ context "Rack::Directory" do
res.should =~ /<html><head>/ res.should =~ /<html><head>/
end end
specify "passes to app if file found" do should "pass to app if file found" do
res = Rack::MockRequest.new(Rack::Lint.new(app)). res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/test") get("/cgi/test")
@ -26,7 +22,7 @@ context "Rack::Directory" do
res.should =~ /passed!/ res.should =~ /passed!/
end end
specify "serves uri with URL encoded filenames" do should "serve uri with URL encoded filenames" do
res = Rack::MockRequest.new(Rack::Lint.new(app)). res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/%63%67%69/") # "/cgi/test" get("/%63%67%69/") # "/cgi/test"
@ -40,7 +36,7 @@ context "Rack::Directory" do
res.should =~ /passed!/ res.should =~ /passed!/
end end
specify "does not allow directory traversal" do should "not allow directory traversal" do
res = Rack::MockRequest.new(Rack::Lint.new(app)). res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/../test") get("/cgi/../test")
@ -52,7 +48,7 @@ context "Rack::Directory" do
res.should.be.forbidden res.should.be.forbidden
end end
specify "404s if it can't find the file" do should "404 if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(app)). res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/blubb") get("/cgi/blubb")

View file

@ -1,15 +1,13 @@
require 'test/spec'
require 'rack/mock'
require 'rack/etag' require 'rack/etag'
context "Rack::ETag" do describe Rack::ETag do
specify "sets ETag if none is set" do should "set ETag if none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] } app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
response = Rack::ETag.new(app).call({}) response = Rack::ETag.new(app).call({})
response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\"" response[1]['ETag'].should.equal "\"65a8e27d8879283831b664bd8b7f0ad4\""
end end
specify "does not change ETag if it is already set" do should "not change ETag if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] } app = lambda { |env| [200, {'Content-Type' => 'text/plain', 'ETag' => '"abc"'}, ["Hello, World!"]] }
response = Rack::ETag.new(app).call({}) response = Rack::ETag.new(app).call({})
response[1]['ETag'].should.equal "\"abc\"" response[1]['ETag'].should.equal "\"abc\""

View file

@ -1,48 +1,55 @@
require 'test/spec' require File.expand_path('../testrequest', __FILE__)
require 'testrequest' require 'rack/handler/fastcgi'
context "Rack::Handler::FastCGI" do describe Rack::Handler::FastCGI do
include TestRequest::Helpers extend TestRequest::Helpers
setup do
@host = '0.0.0.0' @host = '0.0.0.0'
@port = 9203 @port = 9203
end
# Keep this first. # Keep this first.
specify "startup" do
$pid = fork { $pid = fork {
Dir.chdir(File.join(File.dirname(__FILE__), "..", "test", "cgi")) ENV['RACK_ENV'] = 'deployment'
ENV['RUBYLIB'] = [
File.expand_path('../../lib', __FILE__),
ENV['RUBYLIB'],
].compact.join(':')
Dir.chdir(File.expand_path("../cgi", __FILE__)) do
exec "lighttpd -D -f lighttpd.conf" exec "lighttpd -D -f lighttpd.conf"
end
} }
end
specify "should respond" do should "respond" do
sleep 1 sleep 1
lambda { GET("/test")
GET("/test.fcgi") response.should.not.be.nil
}.should.not.raise
end end
specify "should be a lighttpd" do should "respond via rackup server" do
GET("/sample_rackup.ru")
status.should.equal 200
end
should "be a lighttpd" do
GET("/test.fcgi") GET("/test.fcgi")
status.should.be 200 status.should.equal 200
response["SERVER_SOFTWARE"].should =~ /lighttpd/ response["SERVER_SOFTWARE"].should =~ /lighttpd/
response["HTTP_VERSION"].should.equal "HTTP/1.1" response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal @port.to_s response["SERVER_PORT"].should.equal @port.to_s
response["SERVER_NAME"].should =~ @host response["SERVER_NAME"].should.equal @host
end end
specify "should have rack headers" do should "have rack headers" do
GET("/test.fcgi") GET("/test.fcgi")
response["rack.version"].should.equal [1,1] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be false response["rack.multithread"].should.be.false
response["rack.multiprocess"].should.be true response["rack.multiprocess"].should.be.true
response["rack.run_once"].should.be false response["rack.run_once"].should.be.false
end end
specify "should have CGI headers on GET" do should "have CGI headers on GET" do
GET("/test.fcgi") GET("/test.fcgi")
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test.fcgi" response["SCRIPT_NAME"].should.equal "/test.fcgi"
@ -59,7 +66,7 @@ context "Rack::Handler::FastCGI" do
response["QUERY_STRING"].should.equal "quux=1" response["QUERY_STRING"].should.equal "quux=1"
end end
specify "should have CGI headers on POST" do should "have CGI headers on POST" do
POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) POST("/test.fcgi", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200 status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST" response["REQUEST_METHOD"].should.equal "POST"
@ -70,19 +77,19 @@ context "Rack::Handler::FastCGI" do
response["test.postdata"].should.equal "rack-form-data=23" response["test.postdata"].should.equal "rack-form-data=23"
end end
specify "should support HTTP auth" do should "support HTTP auth" do
GET("/test.fcgi", {:user => "ruth", :passwd => "secret"}) GET("/test.fcgi", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end end
specify "should set status" do should "set status" do
GET("/test.fcgi?secret") GET("/test.fcgi?secret")
status.should.equal 403 status.should.equal 403
response["rack.url_scheme"].should.equal "http" response["rack.url_scheme"].should.equal "http"
end end
# Keep this last. # Keep this last.
specify "shutdown" do should "shutdown" do
Process.kill 15, $pid Process.kill 15, $pid
Process.wait($pid).should.equal $pid Process.wait($pid).should.equal $pid
end end

View file

@ -1,14 +1,10 @@
require 'test/spec'
require 'rack/file' require 'rack/file'
require 'rack/lint'
require 'rack/mock' require 'rack/mock'
context "Rack::File" do describe Rack::File do
DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT DOCROOT = File.expand_path(File.dirname(__FILE__)) unless defined? DOCROOT
specify "serves files" do should "serve files" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/test") get("/cgi/test")
@ -16,7 +12,7 @@ context "Rack::File" do
res.should =~ /ruby/ res.should =~ /ruby/
end end
specify "sets Last-Modified header" do should "set Last-Modified header" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/test") get("/cgi/test")
@ -26,7 +22,7 @@ context "Rack::File" do
res["Last-Modified"].should.equal File.mtime(path).httpdate res["Last-Modified"].should.equal File.mtime(path).httpdate
end end
specify "serves files with URL encoded filenames" do should "serve files with URL encoded filenames" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/%74%65%73%74") # "/cgi/test" get("/cgi/%74%65%73%74") # "/cgi/test"
@ -34,35 +30,35 @@ context "Rack::File" do
res.should =~ /ruby/ res.should =~ /ruby/
end end
specify "does not allow directory traversal" do should "not allow directory traversal" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/../test") get("/cgi/../test")
res.should.be.forbidden res.should.be.forbidden
end end
specify "does not allow directory traversal with encoded periods" do should "not allow directory traversal with encoded periods" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/%2E%2E/README") get("/%2E%2E/README")
res.should.be.forbidden res.should.be.forbidden
end end
specify "404s if it can't find the file" do should "404 if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/blubb") get("/cgi/blubb")
res.should.be.not_found res.should.be.not_found
end end
specify "detects SystemCallErrors" do should "detect SystemCallErrors" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))). res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi") get("/cgi")
res.should.be.not_found res.should.be.not_found
end end
specify "returns bodies that respond to #to_path" do should "return bodies that respond to #to_path" do
env = Rack::MockRequest.env_for("/cgi/test") env = Rack::MockRequest.env_for("/cgi/test")
status, headers, body = Rack::File.new(DOCROOT).call(env) status, headers, body = Rack::File.new(DOCROOT).call(env)

View file

@ -1,43 +1,41 @@
require 'test/spec'
require 'rack/handler' require 'rack/handler'
class Rack::Handler::Lobster; end class Rack::Handler::Lobster; end
class RockLobster; end class RockLobster; end
context "Rack::Handler" do describe Rack::Handler do
specify "has registered default handlers" do it "has registered default handlers" do
Rack::Handler.get('cgi').should.equal Rack::Handler::CGI Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
end end
specify "handler that doesn't exist should raise a NameError" do should "raise NameError if handler doesn't exist" do
lambda { lambda {
Rack::Handler.get('boom') Rack::Handler.get('boom')
}.should.raise(NameError) }.should.raise(NameError)
end end
specify "should get unregistered, but already required, handler by name" do should "get unregistered, but already required, handler by name" do
Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster Rack::Handler.get('Lobster').should.equal Rack::Handler::Lobster
end end
specify "should register custom handler" do should "register custom handler" do
Rack::Handler.register('rock_lobster', 'RockLobster') Rack::Handler.register('rock_lobster', 'RockLobster')
Rack::Handler.get('rock_lobster').should.equal RockLobster Rack::Handler.get('rock_lobster').should.equal RockLobster
end end
specify "should not need registration for properly coded handlers even if not already required" do should "not need registration for properly coded handlers even if not already required" do
begin begin
$:.push "test/unregistered_handler" $LOAD_PATH.push File.expand_path('../unregistered_handler', __FILE__)
Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered Rack::Handler.get('Unregistered').should.equal Rack::Handler::Unregistered
lambda { lambda {
Rack::Handler.get('UnRegistered') Rack::Handler.get('UnRegistered')
}.should.raise(NameError) }.should.raise(NameError)
Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne Rack::Handler.get('UnregisteredLongOne').should.equal Rack::Handler::UnregisteredLongOne
ensure ensure
$:.delete "test/unregistered_handler" $LOAD_PATH.delete File.expand_path('../unregistered_handler', __FILE__)
end end
end end
end end

View file

@ -1,7 +1,7 @@
require 'rack/head' require 'rack/head'
require 'rack/mock' require 'rack/mock'
context "Rack::Head" do describe Rack::Head do
def test_response(headers = {}) def test_response(headers = {})
app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] } app = lambda { |env| [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] }
request = Rack::MockRequest.env_for("/", headers) request = Rack::MockRequest.env_for("/", headers)
@ -10,7 +10,7 @@ context "Rack::Head" do
return response return response
end end
specify "passes GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do should "pass GET, POST, PUT, DELETE, OPTIONS, TRACE requests" do
%w[GET POST PUT DELETE OPTIONS TRACE].each do |type| %w[GET POST PUT DELETE OPTIONS TRACE].each do |type|
resp = test_response("REQUEST_METHOD" => type) resp = test_response("REQUEST_METHOD" => type)
@ -20,7 +20,7 @@ context "Rack::Head" do
end end
end end
specify "removes body from HEAD requests" do should "remove body from HEAD requests" do
resp = test_response("REQUEST_METHOD" => "HEAD") resp = test_response("REQUEST_METHOD" => "HEAD")
resp[0].should.equal(200) resp[0].should.equal(200)

View file

@ -1,15 +1,13 @@
require 'test/spec'
require 'stringio' require 'stringio'
require 'rack/lint' require 'rack/lint'
require 'rack/mock' require 'rack/mock'
context "Rack::Lint" do describe Rack::Lint do
def env(*args) def env(*args)
Rack::MockRequest.env_for("/", *args) Rack::MockRequest.env_for("/", *args)
end end
specify "passes valid request" do should "pass valid request" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
@ -17,12 +15,12 @@ context "Rack::Lint" do
}.should.not.raise }.should.not.raise
end end
specify "notices fatal errors" do should "notice fatal errors" do
lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError). lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
message.should.match(/No env given/) message.should.match(/No env given/)
end end
specify "notices environment errors" do should "notice environment errors" do
lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError). lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
message.should.match(/not a Hash/) message.should.match(/not a Hash/)
@ -110,7 +108,7 @@ context "Rack::Lint" do
message.should.match(/cannot be .* make it ''/) message.should.match(/cannot be .* make it ''/)
end end
specify "notices input errors" do should "notice input errors" do
lambda { lambda {
Rack::Lint.new(nil).call(env("rack.input" => "")) Rack::Lint.new(nil).call(env("rack.input" => ""))
}.should.raise(Rack::Lint::LintError). }.should.raise(Rack::Lint::LintError).
@ -139,14 +137,14 @@ context "Rack::Lint" do
message.should.match(/does not have ASCII-8BIT as its external encoding/) message.should.match(/does not have ASCII-8BIT as its external encoding/)
end end
specify "notices error errors" do should "notice error errors" do
lambda { lambda {
Rack::Lint.new(nil).call(env("rack.errors" => "")) Rack::Lint.new(nil).call(env("rack.errors" => ""))
}.should.raise(Rack::Lint::LintError). }.should.raise(Rack::Lint::LintError).
message.should.match(/does not respond to #puts/) message.should.match(/does not respond to #puts/)
end end
specify "notices status errors" do should "notice status errors" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
["cc", {}, ""] ["cc", {}, ""]
@ -162,7 +160,7 @@ context "Rack::Lint" do
message.should.match(/must be >=100 seen as integer/) message.should.match(/must be >=100 seen as integer/)
end end
specify "notices header errors" do should "notice header errors" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
[200, Object.new, []] [200, Object.new, []]
@ -235,7 +233,7 @@ context "Rack::Lint" do
}.should.not.raise(Rack::Lint::LintError) }.should.not.raise(Rack::Lint::LintError)
end end
specify "notices content-type errors" do should "notice content-type errors" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
[200, {"Content-length" => "0"}, []] [200, {"Content-length" => "0"}, []]
@ -253,7 +251,7 @@ context "Rack::Lint" do
end end
end end
specify "notices content-length errors" do should "notice content-length errors" do
[100, 101, 204, 304].each do |status| [100, 101, 204, 304].each do |status|
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
@ -266,12 +264,12 @@ context "Rack::Lint" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []] [200, {"Content-type" => "text/plain", "Content-Length" => "1"}, []]
}).call(env({})) }).call(env({}))[2].each { }
}.should.raise(Rack::Lint::LintError). }.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Length header was 1, but should be 0/) message.should.match(/Content-Length header was 1, but should be 0/)
end end
specify "notices body errors" do should "notice body errors" do
lambda { lambda {
status, header, body = Rack::Lint.new(lambda { |env| status, header, body = Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]] [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
@ -281,7 +279,7 @@ context "Rack::Lint" do
message.should.match(/yielded non-string/) message.should.match(/yielded non-string/)
end end
specify "notices input handling errors" do should "notice input handling errors" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.input"].gets("\r\n") env["rack.input"].gets("\r\n")
@ -425,7 +423,7 @@ context "Rack::Lint" do
message.should.match(/close must not be called/) message.should.match(/close must not be called/)
end end
specify "notices error handling errors" do should "notice error handling errors" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
env["rack.errors"].write(42) env["rack.errors"].write(42)
@ -443,7 +441,7 @@ context "Rack::Lint" do
message.should.match(/close must not be called/) message.should.match(/close must not be called/)
end end
specify "notices HEAD errors" do should "notice HEAD errors" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []] [200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
@ -453,12 +451,12 @@ context "Rack::Lint" do
lambda { lambda {
Rack::Lint.new(lambda { |env| Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]] [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
}).call(env({"REQUEST_METHOD" => "HEAD"})) }).call(env({"REQUEST_METHOD" => "HEAD"}))[2].each { }
}.should.raise(Rack::Lint::LintError). }.should.raise(Rack::Lint::LintError).
message.should.match(/body was given for HEAD/) message.should.match(/body was given for HEAD/)
end end
specify "passes valid read calls" do should "pass valid read calls" do
hello_str = "hello world" hello_str = "hello world"
hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
lambda { lambda {
@ -505,19 +503,8 @@ context "Rack::Lint" do
end end
end end
context "Rack::Lint::InputWrapper" do describe "Rack::Lint::InputWrapper" do
specify "delegates :size to underlying IO object" do should "delegate :rewind to underlying IO object" do
class IOMock
def size
101
end
end
wrapper = Rack::Lint::InputWrapper.new(IOMock.new)
wrapper.size.should == 101
end
specify "delegates :rewind to underlying IO object" do
io = StringIO.new("123") io = StringIO.new("123")
wrapper = Rack::Lint::InputWrapper.new(io) wrapper = Rack::Lint::InputWrapper.new(io)
wrapper.read.should.equal "123" wrapper.read.should.equal "123"

View file

@ -1,29 +1,27 @@
require 'test/spec'
require 'rack/lobster' require 'rack/lobster'
require 'rack/mock' require 'rack/mock'
context "Rack::Lobster::LambdaLobster" do describe Rack::Lobster::LambdaLobster do
specify "should be a single lambda" do should "be a single lambda" do
Rack::Lobster::LambdaLobster.should.be.kind_of Proc Rack::Lobster::LambdaLobster.should.be.kind_of Proc
end end
specify "should look like a lobster" do should "look like a lobster" do
res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/") res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/")
res.should.be.ok res.should.be.ok
res.body.should.include "(,(,,(,,,(" res.body.should.include "(,(,,(,,,("
res.body.should.include "?flip" res.body.should.include "?flip"
end end
specify "should be flippable" do should "be flippable" do
res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/?flip") res = Rack::MockRequest.new(Rack::Lobster::LambdaLobster).get("/?flip")
res.should.be.ok res.should.be.ok
res.body.should.include "(,,,(,,(,(" res.body.should.include "(,,,(,,(,("
end end
end end
context "Rack::Lobster" do describe Rack::Lobster do
specify "should look like a lobster" do should "look like a lobster" do
res = Rack::MockRequest.new(Rack::Lobster.new).get("/") res = Rack::MockRequest.new(Rack::Lobster.new).get("/")
res.should.be.ok res.should.be.ok
res.body.should.include "(,(,,(,,,(" res.body.should.include "(,(,,(,,,("
@ -31,13 +29,13 @@ context "Rack::Lobster" do
res.body.should.include "crash" res.body.should.include "crash"
end end
specify "should be flippable" do should "be flippable" do
res = Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=left") res = Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=left")
res.should.be.ok res.should.be.ok
res.body.should.include "(,,,(,,(,(" res.body.should.include "(,,,(,,(,("
end end
specify "should provide crashing for testing purposes" do should "provide crashing for testing purposes" do
lambda { lambda {
Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=crash") Rack::MockRequest.new(Rack::Lobster.new).get("/?flip=crash")
}.should.raise }.should.raise

View file

@ -1,10 +1,7 @@
require 'test/spec'
require 'rack/mock'
require 'rack/lock' require 'rack/lock'
require 'rack/mock'
context "Rack::Lock" do class Lock
class Lock
attr_reader :synchronized attr_reader :synchronized
def initialize def initialize
@ -15,23 +12,24 @@ context "Rack::Lock" do
@synchronized = true @synchronized = true
yield yield
end end
end end
specify "should call synchronize on lock" do describe Rack::Lock do
should "call synchronize on lock" do
lock = Lock.new lock = Lock.new
env = Rack::MockRequest.env_for("/") env = Rack::MockRequest.env_for("/")
app = Rack::Lock.new(lambda { |env| }, lock) app = Rack::Lock.new(lambda { |inner_env| }, lock)
lock.synchronized.should.equal false lock.synchronized.should.equal false
app.call(env) app.call(env)
lock.synchronized.should.equal true lock.synchronized.should.equal true
end end
specify "should set multithread flag to false" do should "set multithread flag to false" do
app = Rack::Lock.new(lambda { |env| env['rack.multithread'] }) app = Rack::Lock.new(lambda { |env| env['rack.multithread'] })
app.call(Rack::MockRequest.env_for("/")).should.equal false app.call(Rack::MockRequest.env_for("/")).should.equal false
end end
specify "should reset original multithread flag when exiting lock" do should "reset original multithread flag when exiting lock" do
app = Rack::Lock.new(lambda { |env| env }) app = Rack::Lock.new(lambda { |env| env })
app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true
end end

View file

@ -1,9 +1,8 @@
require 'rack/logger'
require 'rack/lint'
require 'stringio' require 'stringio'
require 'rack/logger'
context "Rack::Logger" do describe Rack::Logger do
specify "logs to rack.errors" do should "log to rack.errors" do
app = lambda { |env| app = lambda { |env|
log = env['rack.logger'] log = env['rack.logger']
log.debug("Created logger") log.debug("Created logger")
@ -14,8 +13,8 @@ context "Rack::Logger" do
} }
errors = StringIO.new errors = StringIO.new
Rack::Logger.new(app).call({'rack.errors' => errors}) Rack::Logger.new(app).call('rack.errors' => errors)
errors.string.should.match "INFO -- : Program started" errors.string.should.match(/INFO -- : Program started/)
errors.string.should.match "WARN -- : Nothing to do" errors.string.should.match(/WARN -- : Nothing to do/)
end end
end end

View file

@ -1,58 +1,56 @@
require 'test/spec'
require 'rack/mock'
require 'rack/methodoverride'
require 'stringio' require 'stringio'
require 'rack/methodoverride'
require 'rack/mock'
context "Rack::MethodOverride" do describe Rack::MethodOverride do
specify "should not affect GET requests" do should "not affect GET requests" do
env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET") env = Rack::MockRequest.env_for("/?_method=delete", :method => "GET")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
req = app.call(env) req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "GET" req.env["REQUEST_METHOD"].should.equal "GET"
end end
specify "_method parameter should modify REQUEST_METHOD for POST requests" do should "modify REQUEST_METHOD for POST requests when _method parameter is set" do
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put") env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=put")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
req = app.call(env) req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "PUT" req.env["REQUEST_METHOD"].should.equal "PUT"
end end
specify "X-HTTP-Method-Override header should modify REQUEST_METHOD for POST requests" do should "modify REQUEST_METHOD for POST requests when X-HTTP-Method-Override is set" do
env = Rack::MockRequest.env_for("/", env = Rack::MockRequest.env_for("/",
:method => "POST", :method => "POST",
"HTTP_X_HTTP_METHOD_OVERRIDE" => "PUT" "HTTP_X_HTTP_METHOD_OVERRIDE" => "PUT"
) )
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
req = app.call(env) req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "PUT" req.env["REQUEST_METHOD"].should.equal "PUT"
end end
specify "should not modify REQUEST_METHOD if the method is unknown" do should "not modify REQUEST_METHOD if the method is unknown" do
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo") env = Rack::MockRequest.env_for("/", :method => "POST", :input => "_method=foo")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
req = app.call(env) req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "POST" req.env["REQUEST_METHOD"].should.equal "POST"
end end
specify "should not modify REQUEST_METHOD when _method is nil" do should "not modify REQUEST_METHOD when _method is nil" do
env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar") env = Rack::MockRequest.env_for("/", :method => "POST", :input => "foo=bar")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
req = app.call(env) req = app.call(env)
req.env["REQUEST_METHOD"].should.equal "POST" req.env["REQUEST_METHOD"].should.equal "POST"
end end
specify "should store the original REQUEST_METHOD prior to overriding" do should "store the original REQUEST_METHOD prior to overriding" do
env = Rack::MockRequest.env_for("/", env = Rack::MockRequest.env_for("/",
:method => "POST", :method => "POST",
:input => "_method=options") :input => "_method=options")
app = Rack::MethodOverride.new(lambda { |env| Rack::Request.new(env) }) app = Rack::MethodOverride.new(lambda{|envx| Rack::Request.new(envx) })
req = app.call(env) req = app.call(env)
req.env["rack.methodoverride.original_method"].should.equal "POST" req.env["rack.methodoverride.original_method"].should.equal "POST"

View file

@ -1,7 +1,5 @@
require 'yaml' require 'yaml'
require 'rack/mock' require 'rack/mock'
require 'rack/request'
require 'rack/response'
app = lambda { |env| app = lambda { |env|
req = Rack::Request.new(env) req = Rack::Request.new(env)
@ -17,19 +15,19 @@ app = lambda { |env|
"Content-Type" => "text/yaml").finish "Content-Type" => "text/yaml").finish
} }
context "Rack::MockRequest" do describe Rack::MockRequest do
specify "should return a MockResponse" do should "return a MockResponse" do
res = Rack::MockRequest.new(app).get("") res = Rack::MockRequest.new(app).get("")
res.should.be.kind_of Rack::MockResponse res.should.be.kind_of Rack::MockResponse
end end
specify "should be able to only return the environment" do should "be able to only return the environment" do
env = Rack::MockRequest.env_for("") env = Rack::MockRequest.env_for("")
env.should.be.kind_of Hash env.should.be.kind_of Hash
env.should.include "rack.version" env.should.include "rack.version"
end end
specify "should provide sensible defaults" do should "provide sensible defaults" do
res = Rack::MockRequest.new(app).request res = Rack::MockRequest.new(app).request
env = YAML.load(res.body) env = YAML.load(res.body)
@ -43,7 +41,7 @@ context "Rack::MockRequest" do
env["mock.postdata"].should.be.empty env["mock.postdata"].should.be.empty
end end
specify "should allow GET/POST/PUT/DELETE" do should "allow GET/POST/PUT/DELETE" do
res = Rack::MockRequest.new(app).get("", :input => "foo") res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body) env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET" env["REQUEST_METHOD"].should.equal "GET"
@ -64,12 +62,12 @@ context "Rack::MockRequest" do
should.equal "OPTIONS" should.equal "OPTIONS"
end end
specify "should set content length" do should "set content length" do
env = Rack::MockRequest.env_for("/", :input => "foo") env = Rack::MockRequest.env_for("/", :input => "foo")
env["CONTENT_LENGTH"].should.equal "3" env["CONTENT_LENGTH"].should.equal "3"
end end
specify "should allow posting" do should "allow posting" do
res = Rack::MockRequest.new(app).get("", :input => "foo") res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body) env = YAML.load(res.body)
env["mock.postdata"].should.equal "foo" env["mock.postdata"].should.equal "foo"
@ -79,7 +77,7 @@ context "Rack::MockRequest" do
env["mock.postdata"].should.equal "foo" env["mock.postdata"].should.equal "foo"
end end
specify "should use all parts of an URL" do should "use all parts of an URL" do
res = Rack::MockRequest.new(app). res = Rack::MockRequest.new(app).
get("https://bla.example.org:9292/meh/foo?bar") get("https://bla.example.org:9292/meh/foo?bar")
res.should.be.kind_of Rack::MockResponse res.should.be.kind_of Rack::MockResponse
@ -93,7 +91,7 @@ context "Rack::MockRequest" do
env["rack.url_scheme"].should.equal "https" env["rack.url_scheme"].should.equal "https"
end end
specify "should set SSL port and HTTP flag on when using https" do should "set SSL port and HTTP flag on when using https" do
res = Rack::MockRequest.new(app). res = Rack::MockRequest.new(app).
get("https://example.org/foo") get("https://example.org/foo")
res.should.be.kind_of Rack::MockResponse res.should.be.kind_of Rack::MockResponse
@ -108,7 +106,7 @@ context "Rack::MockRequest" do
env["HTTPS"].should.equal "on" env["HTTPS"].should.equal "on"
end end
specify "should prepend slash to uri path" do should "prepend slash to uri path" do
res = Rack::MockRequest.new(app). res = Rack::MockRequest.new(app).
get("foo") get("foo")
res.should.be.kind_of Rack::MockResponse res.should.be.kind_of Rack::MockResponse
@ -122,33 +120,33 @@ context "Rack::MockRequest" do
env["rack.url_scheme"].should.equal "http" env["rack.url_scheme"].should.equal "http"
end end
specify "should properly convert method name to an uppercase string" do should "properly convert method name to an uppercase string" do
res = Rack::MockRequest.new(app).request(:get) res = Rack::MockRequest.new(app).request(:get)
env = YAML.load(res.body) env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET" env["REQUEST_METHOD"].should.equal "GET"
end end
specify "should accept params and build query string for GET requests" do should "accept params and build query string for GET requests" do
res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => {:foo => {:bar => "1"}}) res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => {:foo => {:bar => "1"}})
env = YAML.load(res.body) env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET" env["REQUEST_METHOD"].should.equal "GET"
env["QUERY_STRING"].should.match "baz=2" env["QUERY_STRING"].should.include "baz=2"
env["QUERY_STRING"].should.match "foo[bar]=1" env["QUERY_STRING"].should.include "foo[bar]=1"
env["PATH_INFO"].should.equal "/foo" env["PATH_INFO"].should.equal "/foo"
env["mock.postdata"].should.equal "" env["mock.postdata"].should.equal ""
end end
specify "should accept raw input in params for GET requests" do should "accept raw input in params for GET requests" do
res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => "foo[bar]=1") res = Rack::MockRequest.new(app).get("/foo?baz=2", :params => "foo[bar]=1")
env = YAML.load(res.body) env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET" env["REQUEST_METHOD"].should.equal "GET"
env["QUERY_STRING"].should.match "baz=2" env["QUERY_STRING"].should.include "baz=2"
env["QUERY_STRING"].should.match "foo[bar]=1" env["QUERY_STRING"].should.include "foo[bar]=1"
env["PATH_INFO"].should.equal "/foo" env["PATH_INFO"].should.equal "/foo"
env["mock.postdata"].should.equal "" env["mock.postdata"].should.equal ""
end end
specify "should accept params and build url encoded params for POST requests" do should "accept params and build url encoded params for POST requests" do
res = Rack::MockRequest.new(app).post("/foo", :params => {:foo => {:bar => "1"}}) res = Rack::MockRequest.new(app).post("/foo", :params => {:foo => {:bar => "1"}})
env = YAML.load(res.body) env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST" env["REQUEST_METHOD"].should.equal "POST"
@ -158,7 +156,7 @@ context "Rack::MockRequest" do
env["mock.postdata"].should.equal "foo[bar]=1" env["mock.postdata"].should.equal "foo[bar]=1"
end end
specify "should accept raw input in params for POST requests" do should "accept raw input in params for POST requests" do
res = Rack::MockRequest.new(app).post("/foo", :params => "foo[bar]=1") res = Rack::MockRequest.new(app).post("/foo", :params => "foo[bar]=1")
env = YAML.load(res.body) env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST" env["REQUEST_METHOD"].should.equal "POST"
@ -168,7 +166,7 @@ context "Rack::MockRequest" do
env["mock.postdata"].should.equal "foo[bar]=1" env["mock.postdata"].should.equal "foo[bar]=1"
end end
specify "should accept params and build multipart encoded params for POST requests" do should "accept params and build multipart encoded params for POST requests" do
files = Rack::Utils::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt")) files = Rack::Utils::Multipart::UploadedFile.new(File.join(File.dirname(__FILE__), "multipart", "file1.txt"))
res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files }) res = Rack::MockRequest.new(app).post("/foo", :params => { "submit-name" => "Larry", "files" => files })
env = YAML.load(res.body) env = YAML.load(res.body)
@ -179,7 +177,7 @@ context "Rack::MockRequest" do
env["mock.postdata"].length.should.equal 206 env["mock.postdata"].length.should.equal 206
end end
specify "should behave valid according to the Rack spec" do should "behave valid according to the Rack spec" do
lambda { lambda {
res = Rack::MockRequest.new(app). res = Rack::MockRequest.new(app).
get("https://bla.example.org:9292/meh/foo?bar", :lint => true) get("https://bla.example.org:9292/meh/foo?bar", :lint => true)
@ -187,8 +185,8 @@ context "Rack::MockRequest" do
end end
end end
context "Rack::MockResponse" do describe Rack::MockResponse do
specify "should provide access to the HTTP status" do should "provide access to the HTTP status" do
res = Rack::MockRequest.new(app).get("") res = Rack::MockRequest.new(app).get("")
res.should.be.successful res.should.be.successful
res.should.be.ok res.should.be.ok
@ -209,18 +207,18 @@ context "Rack::MockResponse" do
res.should.be.empty res.should.be.empty
end end
specify "should provide access to the HTTP headers" do should "provide access to the HTTP headers" do
res = Rack::MockRequest.new(app).get("") res = Rack::MockRequest.new(app).get("")
res.should.include "Content-Type" res.should.include "Content-Type"
res.headers["Content-Type"].should.equal "text/yaml" res.headers["Content-Type"].should.equal "text/yaml"
res.original_headers["Content-Type"].should.equal "text/yaml" res.original_headers["Content-Type"].should.equal "text/yaml"
res["Content-Type"].should.equal "text/yaml" res["Content-Type"].should.equal "text/yaml"
res.content_type.should.equal "text/yaml" res.content_type.should.equal "text/yaml"
res.content_length.should.be 414 # needs change often. res.content_length.should.be > 0
res.location.should.be.nil res.location.should.be.nil
end end
specify "should provide access to the HTTP body" do should "provide access to the HTTP body" do
res = Rack::MockRequest.new(app).get("") res = Rack::MockRequest.new(app).get("")
res.body.should =~ /rack/ res.body.should =~ /rack/
res.should =~ /rack/ res.should =~ /rack/
@ -228,14 +226,14 @@ context "Rack::MockResponse" do
res.should.satisfy { |r| r.match(/rack/) } res.should.satisfy { |r| r.match(/rack/) }
end end
specify "should provide access to the Rack errors" do should "provide access to the Rack errors" do
res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true) res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true)
res.should.be.ok res.should.be.ok
res.errors.should.not.be.empty res.errors.should.not.be.empty
res.errors.should.include "foo" res.errors.should.include "foo"
end end
specify "should optionally make Rack errors fatal" do should "optionally make Rack errors fatal" do
lambda { lambda {
Rack::MockRequest.new(app).get("/?error=foo", :fatal => true) Rack::MockRequest.new(app).get("/?error=foo", :fatal => true)
}.should.raise(Rack::MockRequest::FatalWarning) }.should.raise(Rack::MockRequest::FatalWarning)

View file

@ -1,37 +1,32 @@
require 'test/spec'
begin begin
require 'rack'
require 'rack/handler/mongrel' require 'rack/handler/mongrel'
require 'rack/urlmap' require File.expand_path('../testrequest', __FILE__)
require 'rack/lint'
require 'testrequest'
require 'timeout' require 'timeout'
Thread.abort_on_exception = true Thread.abort_on_exception = true
$tcp_defer_accept_opts = nil $tcp_defer_accept_opts = nil
$tcp_cork_opts = nil $tcp_cork_opts = nil
context "Rack::Handler::Mongrel" do describe Rack::Handler::Mongrel do
include TestRequest::Helpers extend TestRequest::Helpers
setup do @server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201) @server.register('/test',
server.register('/test',
Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new))) Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
server.register('/stream', @server.register('/stream',
Rack::Handler::Mongrel.new(Rack::Lint.new(StreamingRequest))) Rack::Handler::Mongrel.new(Rack::Lint.new(StreamingRequest)))
@acc = server.run @acc = @server.run
end
specify "should respond" do should "respond" do
lambda { lambda {
GET("/test") GET("/test")
}.should.not.raise }.should.not.raise
end end
specify "should be a Mongrel" do should "be a Mongrel" do
GET("/test") GET("/test")
status.should.be 200 status.should.equal 200
response["SERVER_SOFTWARE"].should =~ /Mongrel/ response["SERVER_SOFTWARE"].should =~ /Mongrel/
response["HTTP_VERSION"].should.equal "HTTP/1.1" response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
@ -39,15 +34,15 @@ context "Rack::Handler::Mongrel" do
response["SERVER_NAME"].should.equal "0.0.0.0" response["SERVER_NAME"].should.equal "0.0.0.0"
end end
specify "should have rack headers" do should "have rack headers" do
GET("/test") GET("/test")
response["rack.version"].should.equal [1,1] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be true response["rack.multithread"].should.be.true
response["rack.multiprocess"].should.be false response["rack.multiprocess"].should.be.false
response["rack.run_once"].should.be false response["rack.run_once"].should.be.false
end end
specify "should have CGI headers on GET" do should "have CGI headers on GET" do
GET("/test") GET("/test")
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test" response["SCRIPT_NAME"].should.equal "/test"
@ -64,7 +59,7 @@ context "Rack::Handler::Mongrel" do
response["QUERY_STRING"].should.equal "quux=1" response["QUERY_STRING"].should.equal "quux=1"
end end
specify "should have CGI headers on POST" do should "have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200 status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST" response["REQUEST_METHOD"].should.equal "POST"
@ -75,18 +70,18 @@ context "Rack::Handler::Mongrel" do
response["test.postdata"].should.equal "rack-form-data=23" response["test.postdata"].should.equal "rack-form-data=23"
end end
specify "should support HTTP auth" do should "support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"}) GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end end
specify "should set status" do should "set status" do
GET("/test?secret") GET("/test?secret")
status.should.equal 403 status.should.equal 403
response["rack.url_scheme"].should.equal "http" response["rack.url_scheme"].should.equal "http"
end end
specify "should provide a .run" do should "provide a .run" do
block_ran = false block_ran = false
Thread.new { Thread.new {
Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server| Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
@ -95,16 +90,16 @@ context "Rack::Handler::Mongrel" do
} }
} }
sleep 1 sleep 1
block_ran.should.be true block_ran.should.be.true
end end
specify "should provide a .run that maps a hash" do should "provide a .run that maps a hash" do
block_ran = false block_ran = false
Thread.new { Thread.new {
map = {'/'=>lambda{},'/foo'=>lambda{}} map = {'/'=>lambda{},'/foo'=>lambda{}}
Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server| Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
server.should.be.kind_of Mongrel::HttpServer server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.be 2 server.classifier.uris.size.should.equal 2
server.classifier.uris.should.not.include '/arf' server.classifier.uris.should.not.include '/arf'
server.classifier.uris.should.include '/' server.classifier.uris.should.include '/'
server.classifier.uris.should.include '/foo' server.classifier.uris.should.include '/foo'
@ -112,16 +107,16 @@ context "Rack::Handler::Mongrel" do
} }
} }
sleep 1 sleep 1
block_ran.should.be true block_ran.should.be.true
end end
specify "should provide a .run that maps a urlmap" do should "provide a .run that maps a urlmap" do
block_ran = false block_ran = false
Thread.new { Thread.new {
map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}}) map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server| Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
server.should.be.kind_of Mongrel::HttpServer server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.be 2 server.classifier.uris.size.should.equal 2
server.classifier.uris.should.not.include '/arf' server.classifier.uris.should.not.include '/arf'
server.classifier.uris.should.include '/' server.classifier.uris.should.include '/'
server.classifier.uris.should.include '/bar' server.classifier.uris.should.include '/bar'
@ -129,10 +124,10 @@ context "Rack::Handler::Mongrel" do
} }
} }
sleep 1 sleep 1
block_ran.should.be true block_ran.should.be.true
end end
specify "should provide a .run that maps a urlmap restricting by host" do should "provide a .run that maps a urlmap restricting by host" do
block_ran = false block_ran = false
Thread.new { Thread.new {
map = Rack::URLMap.new({ map = Rack::URLMap.new({
@ -148,22 +143,22 @@ context "Rack::Handler::Mongrel" do
Rack::Handler::Mongrel.run(map, opt) { |server| Rack::Handler::Mongrel.run(map, opt) { |server|
server.should.be.kind_of Mongrel::HttpServer server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.should.include '/' server.classifier.uris.should.include '/'
server.classifier.handler_map['/'].size.should.be 2 server.classifier.handler_map['/'].size.should.equal 2
server.classifier.uris.should.include '/foo' server.classifier.uris.should.include '/foo'
server.classifier.handler_map['/foo'].size.should.be 1 server.classifier.handler_map['/foo'].size.should.equal 1
server.classifier.uris.should.include '/bar' server.classifier.uris.should.include '/bar'
server.classifier.handler_map['/bar'].size.should.be 2 server.classifier.handler_map['/bar'].size.should.equal 2
server.classifier.uris.should.not.include '/qux' server.classifier.uris.should.not.include '/qux'
server.classifier.uris.should.not.include '/arf' server.classifier.uris.should.not.include '/arf'
server.classifier.uris.size.should.be 3 server.classifier.uris.size.should.equal 3
block_ran = true block_ran = true
} }
} }
sleep 1 sleep 1
block_ran.should.be true block_ran.should.be.true
end end
specify "should stream #each part of the response" do should "stream #each part of the response" do
body = '' body = ''
begin begin
Timeout.timeout(1) do Timeout.timeout(1) do
@ -179,11 +174,10 @@ context "Rack::Handler::Mongrel" do
body.should.not.be.empty body.should.not.be.empty
end end
teardown do
@acc.raise Mongrel::StopServer @acc.raise Mongrel::StopServer
end
end end
rescue LoadError rescue LoadError => ex
$stderr.puts "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again." warn ex
warn "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again."
end end

View file

@ -1,13 +1,12 @@
require 'rack/nulllogger' require 'rack/nulllogger'
require 'rack/lint'
require 'rack/mock'
context "Rack::NullLogger" do describe Rack::NullLogger do
specify "acks as a nop logger" do should "act as a noop logger" do
app = lambda { |env| app = lambda { |env|
env['rack.logger'].warn "b00m" env['rack.logger'].warn "b00m"
[200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]]
} }
Rack::NullLogger.new(app).call({}) logger = Rack::NullLogger.new(app)
lambda{ logger.call({}) }.should.not.raise
end end
end end

View file

@ -1,19 +1,13 @@
require 'test/spec'
require 'rack/recursive' require 'rack/recursive'
require 'rack/urlmap'
require 'rack/response'
require 'rack/mock' require 'rack/mock'
context "Rack::Recursive" do describe Rack::Recursive do
setup do
@app1 = lambda { |env| @app1 = lambda { |env|
res = Rack::Response.new res = Rack::Response.new
res["X-Path-Info"] = env["PATH_INFO"] res["X-Path-Info"] = env["PATH_INFO"]
res["X-Query-String"] = env["QUERY_STRING"] res["X-Query-String"] = env["QUERY_STRING"]
res.finish do |res| res.finish do |inner_res|
res.write "App1" inner_res.write "App1"
end end
} }
@ -35,9 +29,7 @@ context "Rack::Recursive" do
raise Rack::ForwardRequest.new("http://example.org/app1/quux?meh") raise Rack::ForwardRequest.new("http://example.org/app1/quux?meh")
} }
end should "allow for subrequests" do
specify "should allow for subrequests" do
res = Rack::MockRequest.new(Rack::Recursive.new( res = Rack::MockRequest.new(Rack::Recursive.new(
Rack::URLMap.new("/app1" => @app1, Rack::URLMap.new("/app1" => @app1,
"/app2" => @app2))). "/app2" => @app2))).
@ -47,7 +39,7 @@ context "Rack::Recursive" do
res.body.should.equal "App2App1" res.body.should.equal "App2App1"
end end
specify "should raise error on requests not below the app" do should "raise error on requests not below the app" do
app = Rack::URLMap.new("/app1" => @app1, app = Rack::URLMap.new("/app1" => @app1,
"/app" => Rack::Recursive.new( "/app" => Rack::Recursive.new(
Rack::URLMap.new("/1" => @app1, Rack::URLMap.new("/1" => @app1,
@ -59,7 +51,7 @@ context "Rack::Recursive" do
message.should =~ /can only include below/ message.should =~ /can only include below/
end end
specify "should support forwarding" do should "support forwarding" do
app = Rack::Recursive.new(Rack::URLMap.new("/app1" => @app1, app = Rack::Recursive.new(Rack::URLMap.new("/app1" => @app1,
"/app3" => @app3, "/app3" => @app3,
"/app4" => @app4)) "/app4" => @app4))

View file

@ -1,11 +1,10 @@
require 'test/spec'
require 'stringio' require 'stringio'
require 'cgi'
require 'rack/request' require 'rack/request'
require 'rack/mock' require 'rack/mock'
context "Rack::Request" do describe Rack::Request do
specify "wraps the rack variables" do should "wrap the rack variables" do
req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/")) req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/"))
req.body.should.respond_to? :gets req.body.should.respond_to? :gets
@ -29,7 +28,7 @@ context "Rack::Request" do
req.content_type.should.be.nil req.content_type.should.be.nil
end end
specify "can figure out the correct host" do should "figure out the correct host" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org") Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org")
req.host.should.equal "www2.example.org" req.host.should.equal "www2.example.org"
@ -53,7 +52,7 @@ context "Rack::Request" do
req.host.should.equal "" req.host.should.equal ""
end end
specify "can parse the query string" do should "parse the query string" do
req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla")) req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla"))
req.query_string.should.equal "foo=bar&quux=bla" req.query_string.should.equal "foo=bar&quux=bla"
req.GET.should.equal "foo" => "bar", "quux" => "bla" req.GET.should.equal "foo" => "bar", "quux" => "bla"
@ -61,12 +60,12 @@ context "Rack::Request" do
req.params.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla"
end end
specify "raises if rack.input is missing" do should "raise if rack.input is missing" do
req = Rack::Request.new({}) req = Rack::Request.new({})
lambda { req.POST }.should.raise(RuntimeError) lambda { req.POST }.should.raise(RuntimeError)
end end
specify "can parse POST data when method is POST and no Content-Type given" do should "parse POST data when method is POST and no Content-Type given" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'POST', "REQUEST_METHOD" => 'POST',
@ -79,7 +78,7 @@ context "Rack::Request" do
req.params.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla"
end end
specify "can parse POST data with explicit content type regardless of method" do should "parse POST data with explicit content type regardless of method" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar', "CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
@ -91,7 +90,7 @@ context "Rack::Request" do
req.params.should.equal "foo" => "bar", "quux" => "bla" req.params.should.equal "foo" => "bar", "quux" => "bla"
end end
specify "does not parse POST data when media type is not form-data" do should "not parse POST data when media type is not form-data" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'POST', "REQUEST_METHOD" => 'POST',
@ -105,7 +104,7 @@ context "Rack::Request" do
req.body.read.should.equal "foo=bar&quux=bla" req.body.read.should.equal "foo=bar&quux=bla"
end end
specify "can parse POST data on PUT when media type is form-data" do should "parse POST data on PUT when media type is form-data" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", Rack::MockRequest.env_for("/?foo=quux",
"REQUEST_METHOD" => 'PUT', "REQUEST_METHOD" => 'PUT',
@ -115,7 +114,7 @@ context "Rack::Request" do
req.body.read.should.equal "foo=bar&quux=bla" req.body.read.should.equal "foo=bar&quux=bla"
end end
specify "rewinds input after parsing POST data" do should "rewind input after parsing POST data" do
input = StringIO.new("foo=bar&quux=bla") input = StringIO.new("foo=bar&quux=bla")
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", Rack::MockRequest.env_for("/",
@ -125,21 +124,21 @@ context "Rack::Request" do
input.read.should.equal "foo=bar&quux=bla" input.read.should.equal "foo=bar&quux=bla"
end end
specify "cleans up Safari's ajax POST body" do should "clean up Safari's ajax POST body" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", Rack::MockRequest.env_for("/",
'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0") 'REQUEST_METHOD' => 'POST', :input => "foo=bar&quux=bla\0")
req.POST.should.equal "foo" => "bar", "quux" => "bla" req.POST.should.equal "foo" => "bar", "quux" => "bla"
end end
specify "can get value by key from params with #[]" do should "get value by key from params with #[]" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=quux") Rack::MockRequest.env_for("?foo=quux")
req['foo'].should.equal 'quux' req['foo'].should.equal 'quux'
req[:foo].should.equal 'quux' req[:foo].should.equal 'quux'
end end
specify "can set value to key on params with #[]=" do should "set value to key on params with #[]=" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=duh") Rack::MockRequest.env_for("?foo=duh")
req['foo'].should.equal 'duh' req['foo'].should.equal 'duh'
@ -157,7 +156,7 @@ context "Rack::Request" do
req[:foo].should.equal 'jaz' req[:foo].should.equal 'jaz'
end end
specify "values_at answers values by keys in order given" do should "return values for the keys in the order given from values_at" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful") Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful")
req.values_at('foo').should.equal ['baz'] req.values_at('foo').should.equal ['baz']
@ -165,7 +164,7 @@ context "Rack::Request" do
req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der'] req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der']
end end
specify "referrer should be extracted correct" do should "extract referrer correctly" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path") Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path")
req.referer.should.equal "/some/path" req.referer.should.equal "/some/path"
@ -175,7 +174,7 @@ context "Rack::Request" do
req.referer.should.equal "/" req.referer.should.equal "/"
end end
specify "user agent should be extracted correct" do should "extract user agent correctly" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)") Rack::MockRequest.env_for("/", "HTTP_USER_AGENT" => "Mozilla/4.0 (compatible)")
req.user_agent.should.equal "Mozilla/4.0 (compatible)" req.user_agent.should.equal "Mozilla/4.0 (compatible)"
@ -185,7 +184,7 @@ context "Rack::Request" do
req.user_agent.should.equal nil req.user_agent.should.equal nil
end end
specify "can cache, but invalidates the cache" do should "cache, but invalidates the cache" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", Rack::MockRequest.env_for("/?foo=quux",
"CONTENT_TYPE" => "application/x-www-form-urlencoded", "CONTENT_TYPE" => "application/x-www-form-urlencoded",
@ -203,7 +202,7 @@ context "Rack::Request" do
req.POST.should.equal "foo" => "bla", "quux" => "bar" req.POST.should.equal "foo" => "bla", "quux" => "bar"
end end
specify "can figure out if called via XHR" do should "figure out if called via XHR" do
req = Rack::Request.new(Rack::MockRequest.env_for("")) req = Rack::Request.new(Rack::MockRequest.env_for(""))
req.should.not.be.xhr req.should.not.be.xhr
@ -212,7 +211,7 @@ context "Rack::Request" do
req.should.be.xhr req.should.be.xhr
end end
specify "can parse cookies" do should "parse cookies" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m") Rack::MockRequest.env_for("", "HTTP_COOKIE" => "foo=bar;quux=h&m")
req.cookies.should.equal "foo" => "bar", "quux" => "h&m" req.cookies.should.equal "foo" => "bar", "quux" => "h&m"
@ -221,13 +220,13 @@ context "Rack::Request" do
req.cookies.should.equal({}) req.cookies.should.equal({})
end end
specify "parses cookies according to RFC 2109" do should "parse cookies according to RFC 2109" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car') Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car')
req.cookies.should.equal 'foo' => 'bar' req.cookies.should.equal 'foo' => 'bar'
end end
specify "provides setters" do should "provide setters" do
req = Rack::Request.new(e=Rack::MockRequest.env_for("")) req = Rack::Request.new(e=Rack::MockRequest.env_for(""))
req.script_name.should.equal "" req.script_name.should.equal ""
req.script_name = "/foo" req.script_name = "/foo"
@ -240,12 +239,12 @@ context "Rack::Request" do
e["PATH_INFO"].should.equal "/foo" e["PATH_INFO"].should.equal "/foo"
end end
specify "provides the original env" do should "provide the original env" do
req = Rack::Request.new(e=Rack::MockRequest.env_for("")) req = Rack::Request.new(e = Rack::MockRequest.env_for(""))
req.env.should.be e req.env.should == e
end end
specify "can restore the URL" do should "restore the URL" do
Rack::Request.new(Rack::MockRequest.env_for("")).url. Rack::Request.new(Rack::MockRequest.env_for("")).url.
should.equal "http://example.org/" should.equal "http://example.org/"
Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url. Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url.
@ -263,7 +262,7 @@ context "Rack::Request" do
should.equal "https://example.com:8080/foo?foo" should.equal "https://example.com:8080/foo?foo"
end end
specify "can restore the full path" do should "restore the full path" do
Rack::Request.new(Rack::MockRequest.env_for("")).fullpath. Rack::Request.new(Rack::MockRequest.env_for("")).fullpath.
should.equal "/" should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath. Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath.
@ -281,7 +280,7 @@ context "Rack::Request" do
should.equal "/foo?foo" should.equal "/foo?foo"
end end
specify "can handle multiple media type parameters" do should "handle multiple media type parameters" do
req = Rack::Request.new \ req = Rack::Request.new \
Rack::MockRequest.env_for("/", Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam') "CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam')
@ -295,7 +294,7 @@ context "Rack::Request" do
req.media_type_params['bling'].should.equal 'bam' req.media_type_params['bling'].should.equal 'bam'
end end
specify "can parse multipart form data" do should "parse multipart form data" do
# Adapted from RFC 1867. # Adapted from RFC 1867.
input = <<EOF input = <<EOF
--AaB03x\r --AaB03x\r
@ -334,7 +333,7 @@ EOF
f[:tempfile].size.should.equal 76 f[:tempfile].size.should.equal 76
end end
specify "can parse big multipart form data" do should "parse big multipart form data" do
input = <<EOF input = <<EOF
--AaB03x\r --AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r content-disposition: form-data; name="huge"; filename="huge"\r
@ -356,7 +355,7 @@ EOF
req.POST["mean"][:tempfile].read.should.equal "--AaB03xha" req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
end end
specify "can detect invalid multipart form data" do should "detect invalid multipart form data" do
input = <<EOF input = <<EOF
--AaB03x\r --AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r content-disposition: form-data; name="huge"; filename="huge"\r
@ -395,7 +394,8 @@ EOF
lambda { req.POST }.should.raise(EOFError) lambda { req.POST }.should.raise(EOFError)
end end
specify "shouldn't try to interpret binary as utf8" do should "not try to interpret binary as utf8" do
if /regexp/.respond_to?(:kcode) # < 1.9
begin begin
original_kcode = $KCODE original_kcode = $KCODE
$KCODE='UTF8' $KCODE='UTF8'
@ -419,10 +419,27 @@ EOF
ensure ensure
$KCODE = original_kcode $KCODE = original_kcode
end end
else # >= 1.9
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="fileupload"; filename="junk.a"\r
content-type: application/octet-stream\r
\r
#{[0x36,0xCF,0x0A,0xF8].pack('c*')}\r
--AaB03x--\r
EOF
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => input)
lambda{req.POST}.should.not.raise(EOFError)
req.POST["fileupload"][:tempfile].size.should.equal 4
end
end end
should "work around buggy 1.8.* Tempfile equality" do
specify "should work around buggy 1.8.* Tempfile equality" do
input = <<EOF input = <<EOF
--AaB03x\r --AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r content-disposition: form-data; name="huge"; filename="huge"\r
@ -440,11 +457,11 @@ EOF
"CONTENT_LENGTH" => input.size, "CONTENT_LENGTH" => input.size,
:input => rack_input) :input => rack_input)
lambda {req.POST}.should.not.raise lambda{ req.POST }.should.not.raise
lambda {req.POST}.should.blaming("input re-processed!").not.raise lambda{ req.POST }.should.not.raise("input re-processed!")
end end
specify "does conform to the Rack spec" do should "conform to the Rack spec" do
app = lambda { |env| app = lambda { |env|
content = Rack::Request.new(env).POST["file"].inspect content = Rack::Request.new(env).POST["file"].inspect
size = content.respond_to?(:bytesize) ? content.bytesize : content.size size = content.respond_to?(:bytesize) ? content.bytesize : content.size
@ -472,7 +489,7 @@ EOF
res.should.be.ok res.should.be.ok
end end
specify "should parse Accept-Encoding correctly" do should "parse Accept-Encoding correctly" do
parser = lambda do |x| parser = lambda do |x|
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding
end end
@ -488,7 +505,7 @@ EOF
lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError) lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError)
end end
specify 'should provide ip information' do should 'provide ip information' do
app = lambda { |env| app = lambda { |env|
request = Rack::Request.new(env) request = Rack::Request.new(env)
response = Rack::Response.new response = Rack::Response.new
@ -510,7 +527,13 @@ EOF
'REMOTE_ADDR' => '123.123.123.123', 'REMOTE_ADDR' => '123.123.123.123',
'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212' 'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
res.body.should.equal '212.212.212.212' res.body.should.equal '234.234.234.234'
res = mock.get '/',
'REMOTE_ADDR' => '123.123.123.123',
'HTTP_X_FORWARDED_FOR' => 'unknown,234.234.234.234,212.212.212.212'
res.body.should.equal '234.234.234.234'
end end
class MyRequest < Rack::Request class MyRequest < Rack::Request
@ -519,7 +542,7 @@ EOF
end end
end end
specify "should allow subclass request to be instantiated after parent request" do should "allow subclass request to be instantiated after parent request" do
env = Rack::MockRequest.env_for("/?foo=bar") env = Rack::MockRequest.env_for("/?foo=bar")
req1 = Rack::Request.new(env) req1 = Rack::Request.new(env)
@ -531,7 +554,7 @@ EOF
req2.params.should.equal :foo => "bar" req2.params.should.equal :foo => "bar"
end end
specify "should allow parent request to be instantiated after subclass request" do should "allow parent request to be instantiated after subclass request" do
env = Rack::MockRequest.env_for("/?foo=bar") env = Rack::MockRequest.env_for("/?foo=bar")
req1 = MyRequest.new(env) req1 = MyRequest.new(env)
@ -542,4 +565,16 @@ EOF
req2.GET.should.equal "foo" => "bar" req2.GET.should.equal "foo" => "bar"
req2.params.should.equal "foo" => "bar" req2.params.should.equal "foo" => "bar"
end end
(0x20...0x7E).collect { |a|
b = a.chr
c = CGI.escape(b)
should "not strip '#{a}' => '#{c}' => '#{b}' escaped character from parameters when accessed as string" do
url = "/?foo=#{c}bar#{c}"
env = Rack::MockRequest.env_for(url)
req2 = Rack::Request.new(env)
req2.GET.should.equal "foo" => "#{b}bar#{b}"
req2.params.should.equal "foo" => "#{b}bar#{b}"
end
}
end end

View file

@ -1,10 +1,8 @@
require 'test/spec'
require 'set' require 'set'
require 'rack/response' require 'rack/response'
context "Rack::Response" do describe Rack::Response do
specify "has sensible default values" do should "have sensible default values" do
response = Rack::Response.new response = Rack::Response.new
status, header, body = response.finish status, header, body = response.finish
status.should.equal 200 status.should.equal 200
@ -22,7 +20,7 @@ context "Rack::Response" do
} }
end end
specify "can be written to" do it "can be written to" do
response = Rack::Response.new response = Rack::Response.new
status, header, body = response.finish do status, header, body = response.finish do
@ -37,25 +35,32 @@ context "Rack::Response" do
parts.should.equal ["foo", "bar", "baz"] parts.should.equal ["foo", "bar", "baz"]
end end
specify "can set and read headers" do it "can set and read headers" do
response = Rack::Response.new response = Rack::Response.new
response["Content-Type"].should.equal "text/html" response["Content-Type"].should.equal "text/html"
response["Content-Type"] = "text/plain" response["Content-Type"] = "text/plain"
response["Content-Type"].should.equal "text/plain" response["Content-Type"].should.equal "text/plain"
end end
specify "can set cookies" do it "can set cookies" do
response = Rack::Response.new response = Rack::Response.new
response.set_cookie "foo", "bar" response.set_cookie "foo", "bar"
response["Set-Cookie"].should.equal "foo=bar" response["Set-Cookie"].should.equal "foo=bar"
response.set_cookie "foo2", "bar2" response.set_cookie "foo2", "bar2"
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"] response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"].join("\n")
response.set_cookie "foo3", "bar3" response.set_cookie "foo3", "bar3"
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"] response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"].join("\n")
end end
specify "formats the Cookie expiration date accordingly to RFC 2109" do it "can set cookies with the same name for multiple domains" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :domain => "sample.example.com"}
response.set_cookie "foo", {:value => "bar", :domain => ".example.com"}
response["Set-Cookie"].should.equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"].join("\n")
end
it "formats the Cookie expiration date accordingly to RFC 2109" do
response = Rack::Response.new response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :expires => Time.now+10} response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
@ -63,28 +68,42 @@ context "Rack::Response" do
/expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../) /expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../)
end end
specify "can set secure cookies" do it "can set secure cookies" do
response = Rack::Response.new response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :secure => true} response.set_cookie "foo", {:value => "bar", :secure => true}
response["Set-Cookie"].should.equal "foo=bar; secure" response["Set-Cookie"].should.equal "foo=bar; secure"
end end
specify "can set http only cookies" do it "can set http only cookies" do
response = Rack::Response.new response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :httponly => true} response.set_cookie "foo", {:value => "bar", :httponly => true}
response["Set-Cookie"].should.equal "foo=bar; HttpOnly" response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
end end
specify "can delete cookies" do it "can delete cookies" do
response = Rack::Response.new response = Rack::Response.new
response.set_cookie "foo", "bar" response.set_cookie "foo", "bar"
response.set_cookie "foo2", "bar2" response.set_cookie "foo2", "bar2"
response.delete_cookie "foo" response.delete_cookie "foo"
response["Set-Cookie"].should.equal ["foo2=bar2", response["Set-Cookie"].should.equal [
"foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"] "foo2=bar2",
"foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"
].join("\n")
end end
specify "can do redirects" do it "can delete cookies with the same name from multiple domains" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :domain => "sample.example.com"}
response.set_cookie "foo", {:value => "bar", :domain => ".example.com"}
response["Set-Cookie"].should.equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"].join("\n")
response.delete_cookie "foo", :domain => ".example.com"
response["Set-Cookie"].should.equal ["foo=bar; domain=sample.example.com", "foo=; domain=.example.com; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
response.delete_cookie "foo", :domain => "sample.example.com"
response["Set-Cookie"].should.equal ["foo=; domain=.example.com; expires=Thu, 01-Jan-1970 00:00:00 GMT",
"foo=; domain=sample.example.com; expires=Thu, 01-Jan-1970 00:00:00 GMT"].join("\n")
end
it "can do redirects" do
response = Rack::Response.new response = Rack::Response.new
response.redirect "/foo" response.redirect "/foo"
status, header, body = response.finish status, header, body = response.finish
@ -99,7 +118,7 @@ context "Rack::Response" do
status.should.equal 307 status.should.equal 307
end end
specify "has a useful constructor" do it "has a useful constructor" do
r = Rack::Response.new("foo") r = Rack::Response.new("foo")
status, header, body = r.finish status, header, body = r.finish
str = ""; body.each { |part| str << part } str = ""; body.each { |part| str << part }
@ -123,7 +142,7 @@ context "Rack::Response" do
r.status.should.equal 200 r.status.should.equal 200
end end
specify "has a constructor that can take a block" do it "has a constructor that can take a block" do
r = Rack::Response.new { |res| r = Rack::Response.new { |res|
res.status = 404 res.status = 404
res.write "foo" res.write "foo"
@ -134,7 +153,7 @@ context "Rack::Response" do
status.should.equal 404 status.should.equal 404
end end
specify "doesn't return invalid responses" do it "doesn't return invalid responses" do
r = Rack::Response.new(["foo", "bar"], 204) r = Rack::Response.new(["foo", "bar"], 204)
status, header, body = r.finish status, header, body = r.finish
str = ""; body.each { |part| str << part } str = ""; body.each { |part| str << part }
@ -147,7 +166,7 @@ context "Rack::Response" do
message.should =~ /stringable or iterable required/ message.should =~ /stringable or iterable required/
end end
specify "knows if it's empty" do it "knows if it's empty" do
r = Rack::Response.new r = Rack::Response.new
r.should.be.empty r.should.be.empty
r.write "foo" r.write "foo"
@ -164,7 +183,7 @@ context "Rack::Response" do
r.should.not.be.empty r.should.not.be.empty
end end
specify "should provide access to the HTTP status" do should "provide access to the HTTP status" do
res = Rack::Response.new res = Rack::Response.new
res.status = 200 res.status = 200
res.should.be.successful res.should.be.successful
@ -183,7 +202,7 @@ context "Rack::Response" do
res.should.be.redirect res.should.be.redirect
end end
specify "should provide access to the HTTP headers" do should "provide access to the HTTP headers" do
res = Rack::Response.new res = Rack::Response.new
res["Content-Type"] = "text/yaml" res["Content-Type"] = "text/yaml"
@ -195,7 +214,7 @@ context "Rack::Response" do
res.location.should.be.nil res.location.should.be.nil
end end
specify "does not add or change Content-Length when #finish()ing" do it "does not add or change Content-Length when #finish()ing" do
res = Rack::Response.new res = Rack::Response.new
res.status = 200 res.status = 200
res.finish res.finish
@ -208,7 +227,7 @@ context "Rack::Response" do
res.headers["Content-Length"].should.equal "10" res.headers["Content-Length"].should.equal "10"
end end
specify "updates Content-Length when body appended to using #write" do it "updates Content-Length when body appended to using #write" do
res = Rack::Response.new res = Rack::Response.new
res.status = 200 res.status = 200
res.headers["Content-Length"].should.be.nil res.headers["Content-Length"].should.be.nil

View file

@ -0,0 +1,118 @@
require 'stringio'
require 'rack/rewindable_input'
shared "a rewindable IO object" do
before do
@rio = Rack::RewindableInput.new(@io)
end
should "be able to handle to read()" do
@rio.read.should.equal "hello world"
end
should "be able to handle to read(nil)" do
@rio.read(nil).should.equal "hello world"
end
should "be able to handle to read(length)" do
@rio.read(1).should.equal "h"
end
should "be able to handle to read(length, buffer)" do
buffer = ""
result = @rio.read(1, buffer)
result.should.equal "h"
result.object_id.should.equal buffer.object_id
end
should "be able to handle to read(nil, buffer)" do
buffer = ""
result = @rio.read(nil, buffer)
result.should.equal "hello world"
result.object_id.should.equal buffer.object_id
end
should "rewind to the beginning when #rewind is called" do
@rio.read(1)
@rio.rewind
@rio.read.should.equal "hello world"
end
should "be able to handle gets" do
@rio.gets.should == "hello world"
end
should "be able to handle each" do
array = []
@rio.each do |data|
array << data
end
array.should.equal(["hello world"])
end
should "not buffer into a Tempfile if no data has been read yet" do
@rio.instance_variable_get(:@rewindable_io).should.be.nil
end
should "buffer into a Tempfile when data has been consumed for the first time" do
@rio.read(1)
tempfile = @rio.instance_variable_get(:@rewindable_io)
tempfile.should.not.be.nil
@rio.read(1)
tempfile2 = @rio.instance_variable_get(:@rewindable_io)
tempfile2.path.should == tempfile.path
end
should "close the underlying tempfile upon calling #close" do
@rio.read(1)
tempfile = @rio.instance_variable_get(:@rewindable_io)
@rio.close
tempfile.should.be.closed
end
should "be possible to call #close when no data has been buffered yet" do
lambda{ @rio.close }.should.not.raise
end
should "be possible to call #close multiple times" do
lambda{
@rio.close
@rio.close
}.should.not.raise
end
@rio.close
@rio = nil
end
describe Rack::RewindableInput do
describe "given an IO object that is already rewindable" do
before do
@io = StringIO.new("hello world")
end
behaves_like "a rewindable IO object"
end
describe "given an IO object that is not rewindable" do
before do
@io = StringIO.new("hello world")
@io.instance_eval do
undef :rewind
end
end
behaves_like "a rewindable IO object"
end
describe "given an IO object whose rewind method raises Errno::ESPIPE" do
before do
@io = StringIO.new("hello world")
def @io.rewind
raise Errno::ESPIPE, "You can't rewind this!"
end
end
behaves_like "a rewindable IO object"
end
end

View file

@ -1,31 +1,35 @@
require 'test/spec'
require 'rack/mock'
require 'rack/runtime' require 'rack/runtime'
context "Rack::Runtime" do describe Rack::Runtime do
specify "sets X-Runtime is none is set" do it "sets X-Runtime is none is set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
response = Rack::Runtime.new(app).call({}) response = Rack::Runtime.new(app).call({})
response[1]['X-Runtime'].should =~ /[\d\.]+/ response[1]['X-Runtime'].should =~ /[\d\.]+/
end end
specify "does not set the X-Runtime if it is already set" do it "doesn't set the X-Runtime if it is already set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] } app = lambda { |env| [200, {'Content-Type' => 'text/plain', "X-Runtime" => "foobar"}, "Hello, World!"] }
response = Rack::Runtime.new(app).call({}) response = Rack::Runtime.new(app).call({})
response[1]['X-Runtime'].should == "foobar" response[1]['X-Runtime'].should == "foobar"
end end
specify "should allow a suffix to be set" do should "allow a suffix to be set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
response = Rack::Runtime.new(app, "Test").call({}) response = Rack::Runtime.new(app, "Test").call({})
response[1]['X-Runtime-Test'].should =~ /[\d\.]+/ response[1]['X-Runtime-Test'].should =~ /[\d\.]+/
end end
specify "should allow multiple timers to be set" do should "allow multiple timers to be set" do
app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] } app = lambda { |env| sleep 0.1; [200, {'Content-Type' => 'text/plain'}, "Hello, World!"] }
runtime1 = Rack::Runtime.new(app, "App") runtime = Rack::Runtime.new(app, "App")
runtime2 = Rack::Runtime.new(runtime1, "All")
response = runtime2.call({}) # wrap many times to guarantee a measurable difference
100.times do |i|
runtime = Rack::Runtime.new(runtime, i.to_s)
end
runtime = Rack::Runtime.new(runtime, "All")
response = runtime.call({})
response[1]['X-Runtime-App'].should =~ /[\d\.]+/ response[1]['X-Runtime-App'].should =~ /[\d\.]+/
response[1]['X-Runtime-All'].should =~ /[\d\.]+/ response[1]['X-Runtime-All'].should =~ /[\d\.]+/

View file

@ -1,14 +1,13 @@
require 'test/spec'
require 'rack/mock'
require 'rack/sendfile' require 'rack/sendfile'
require 'rack/mock'
context "Rack::File" do describe Rack::File do
specify "should respond to #to_path" do should "respond to #to_path" do
Rack::File.new(Dir.pwd).should.respond_to :to_path Rack::File.new(Dir.pwd).should.respond_to :to_path
end end
end end
context "Rack::Sendfile" do describe Rack::Sendfile do
def sendfile_body def sendfile_body
res = ['Hello World'] res = ['Hello World']
def res.to_path ; "/tmp/hello.txt" ; end def res.to_path ; "/tmp/hello.txt" ; end
@ -23,15 +22,13 @@ context "Rack::Sendfile" do
Rack::Sendfile.new(simple_app(body)) Rack::Sendfile.new(simple_app(body))
end end
setup do
@request = Rack::MockRequest.new(sendfile_app) @request = Rack::MockRequest.new(sendfile_app)
end
def request(headers={}) def request(headers={})
yield @request.get('/', headers) yield @request.get('/', headers)
end end
specify "does nothing when no X-Sendfile-Type header present" do it "does nothing when no X-Sendfile-Type header present" do
request do |response| request do |response|
response.should.be.ok response.should.be.ok
response.body.should.equal 'Hello World' response.body.should.equal 'Hello World'
@ -39,7 +36,7 @@ context "Rack::Sendfile" do
end end
end end
specify "sets X-Sendfile response header and discards body" do it "sets X-Sendfile response header and discards body" do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response| request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
response.should.be.ok response.should.be.ok
response.body.should.be.empty response.body.should.be.empty
@ -47,7 +44,7 @@ context "Rack::Sendfile" do
end end
end end
specify "sets X-Lighttpd-Send-File response header and discards body" do it "sets X-Lighttpd-Send-File response header and discards body" do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response| request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
response.should.be.ok response.should.be.ok
response.body.should.be.empty response.body.should.be.empty
@ -55,7 +52,7 @@ context "Rack::Sendfile" do
end end
end end
specify "sets X-Accel-Redirect response header and discards body" do it "sets X-Accel-Redirect response header and discards body" do
headers = { headers = {
'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect', 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
'HTTP_X_ACCEL_MAPPING' => '/tmp/=/foo/bar/' 'HTTP_X_ACCEL_MAPPING' => '/tmp/=/foo/bar/'
@ -67,7 +64,7 @@ context "Rack::Sendfile" do
end end
end end
specify 'writes to rack.error when no X-Accel-Mapping is specified' do it 'writes to rack.error when no X-Accel-Mapping is specified' do
request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response| request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
response.should.be.ok response.should.be.ok
response.body.should.equal 'Hello World' response.body.should.equal 'Hello World'
@ -76,7 +73,7 @@ context "Rack::Sendfile" do
end end
end end
specify 'does nothing when body does not respond to #to_path' do it 'does nothing when body does not respond to #to_path' do
@request = Rack::MockRequest.new(sendfile_app(['Not a file...'])) @request = Rack::MockRequest.new(sendfile_app(['Not a file...']))
request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response| request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
response.body.should.equal 'Not a file...' response.body.should.equal 'Not a file...'

View file

@ -1,23 +1,20 @@
require 'test/spec'
require 'rack/session/cookie' require 'rack/session/cookie'
require 'rack/mock' require 'rack/mock'
require 'rack/response'
context "Rack::Session::Cookie" do describe Rack::Session::Cookie do
incrementor = lambda { |env| incrementor = lambda do |env|
env["rack.session"]["counter"] ||= 0 env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1 env["rack.session"]["counter"] += 1
Rack::Response.new(env["rack.session"].inspect).to_a Rack::Response.new(env["rack.session"].inspect).to_a
} end
specify "creates a new cookie" do it "creates a new cookie" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/") res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
res["Set-Cookie"].should.match("rack.session=") res["Set-Cookie"].should.include("rack.session=")
res.body.should.equal '{"counter"=>1}' res.body.should.equal '{"counter"=>1}'
end end
specify "loads from a cookie" do it "loads from a cookie" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/") res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
cookie = res["Set-Cookie"] cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)). res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
@ -29,34 +26,25 @@ context "Rack::Session::Cookie" do
res.body.should.equal '{"counter"=>3}' res.body.should.equal '{"counter"=>3}'
end end
specify "survives broken cookies" do it "survives broken cookies" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)). res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => "rack.session=blarghfasel") get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
res.body.should.equal '{"counter"=>1}' res.body.should.equal '{"counter"=>1}'
end end
bigcookie = lambda { |env| bigcookie = lambda do |env|
env["rack.session"]["cookie"] = "big" * 3000 env["rack.session"]["cookie"] = "big" * 3000
Rack::Response.new(env["rack.session"].inspect).to_a Rack::Response.new(env["rack.session"].inspect).to_a
} end
specify "barks on too big cookies" do it "barks on too big cookies" do
lambda { lambda{
Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)). Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
get("/", :fatal => true) get("/", :fatal => true)
}.should.raise(Rack::MockRequest::FatalWarning) }.should.raise(Rack::MockRequest::FatalWarning)
end end
specify "creates a new cookie with integrity hash" do it "loads from a cookie wih integrity hash" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
if RUBY_VERSION < "1.9"
res["Set-Cookie"].should.match("rack.session=BAh7BiIMY291bnRlcmkG%0A--1439b4d37b9d4b04c603848382f712d6fcd31088")
else
res["Set-Cookie"].should.match("rack.session=BAh7BkkiDGNvdW50ZXIGOg1lbmNvZGluZyINVVMtQVNDSUlpBg%3D%3D%0A--d7a6637b94d2728194a96c18484e1f7ed9074a83")
end
end
specify "loads from a cookie wih integrity hash" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/") res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
cookie = res["Set-Cookie"] cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')). res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
@ -68,7 +56,7 @@ context "Rack::Session::Cookie" do
res.body.should.equal '{"counter"=>3}' res.body.should.equal '{"counter"=>3}'
end end
specify "ignores tampered with session cookies" do it "ignores tampered with session cookies" do
app = Rack::Session::Cookie.new(incrementor, :secret => 'test') app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
response1 = Rack::MockRequest.new(app).get("/") response1 = Rack::MockRequest.new(app).get("/")
_, digest = response1["Set-Cookie"].split("--") _, digest = response1["Set-Cookie"].split("--")

View file

@ -1,12 +1,9 @@
require 'test/spec'
begin begin
require 'rack/session/memcache' require 'rack/session/memcache'
require 'rack/mock' require 'rack/mock'
require 'rack/response'
require 'thread' require 'thread'
context "Rack::Session::Memcache" do describe Rack::Session::Memcache do
session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key] session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key]
session_match = /#{session_key}=([0-9a-fA-F]+);/ session_match = /#{session_key}=([0-9a-fA-F]+);/
incrementor = lambda do |env| incrementor = lambda do |env|
@ -27,30 +24,36 @@ begin
incrementor.call(env) incrementor.call(env)
end end
specify "faults on no connection" do it "faults on no connection" do
if RUBY_VERSION < "1.9" if RUBY_VERSION < "1.9"
lambda do lambda{
Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver' Rack::Session::Memcache.new(incrementor, :memcache_server => 'nosuchserver')
end.should.raise }.should.raise
else else
lambda do lambda{
Rack::Session::Memcache.new incrementor, :memcache_server => 'nosuchserver' Rack::Session::Memcache.new(incrementor, :memcache_server => 'nosuchserver')
end.should.raise ArgumentError }.should.raise ArgumentError
end end
end end
specify "connect to existing server" do it "connects to existing server" do
test_pool = MemCache.new incrementor, :namespace => 'test:rack:session' test_pool = MemCache.new(incrementor, :namespace => 'test:rack:session')
test_pool.namespace.should.equal 'test:rack:session'
end end
specify "creates a new cookie" do it "passes options to MemCache" do
pool = Rack::Session::Memcache.new(incrementor, :namespace => 'test:rack:session')
pool.pool.namespace.should.equal 'test:rack:session'
end
it "creates a new cookie" do
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
res = Rack::MockRequest.new(pool).get("/") res = Rack::MockRequest.new(pool).get("/")
res["Set-Cookie"].should.match("#{session_key}=") res["Set-Cookie"].should.include("#{session_key}=")
res.body.should.equal '{"counter"=>1}' res.body.should.equal '{"counter"=>1}'
end end
specify "determines session from a cookie" do it "determines session from a cookie" do
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
res = req.get("/") res = req.get("/")
@ -61,7 +64,7 @@ begin
body.should.equal '{"counter"=>3}' body.should.equal '{"counter"=>3}'
end end
specify "survives nonexistant cookies" do it "survives nonexistant cookies" do
bad_cookie = "rack.session=blarghfasel" bad_cookie = "rack.session=blarghfasel"
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
res = Rack::MockRequest.new(pool). res = Rack::MockRequest.new(pool).
@ -71,7 +74,7 @@ begin
cookie.should.not.match(/#{bad_cookie}/) cookie.should.not.match(/#{bad_cookie}/)
end end
specify "maintains freshness" do it "maintains freshness" do
pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3) pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
res = Rack::MockRequest.new(pool).get('/') res = Rack::MockRequest.new(pool).get('/')
res.body.should.include '"counter"=>1' res.body.should.include '"counter"=>1'
@ -86,7 +89,7 @@ begin
res.body.should.include '"counter"=>1' res.body.should.include '"counter"=>1'
end end
specify "deletes cookies with :drop option" do it "deletes cookies with :drop option" do
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
drop = Rack::Utils::Context.new(pool, drop_session) drop = Rack::Utils::Context.new(pool, drop_session)
@ -109,7 +112,7 @@ begin
res3.body.should.equal '{"counter"=>1}' res3.body.should.equal '{"counter"=>1}'
end end
specify "provides new session id with :renew option" do it "provides new session id with :renew option" do
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
renew = Rack::Utils::Context.new(pool, renew_session) renew = Rack::Utils::Context.new(pool, renew_session)
@ -134,7 +137,7 @@ begin
res3.body.should.equal '{"counter"=>4}' res3.body.should.equal '{"counter"=>4}'
end end
specify "omits cookie with :defer option" do it "omits cookie with :defer option" do
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
defer = Rack::Utils::Context.new(pool, defer_session) defer = Rack::Utils::Context.new(pool, defer_session)
@ -157,7 +160,7 @@ begin
res3.body.should.equal '{"counter"=>4}' res3.body.should.equal '{"counter"=>4}'
end end
specify "deep hashes are correctly updated" do it "updates deep hashes correctly" do
store = nil store = nil
hash_check = proc do |env| hash_check = proc do |env|
session = env['rack.session'] session = env['rack.session']
@ -183,8 +186,11 @@ begin
end end
# anyone know how to do this better? # anyone know how to do this better?
specify "multithread: should cleanly merge sessions" do it "cleanly merges sessions when multithreaded" do
next unless $DEBUG unless $DEBUG
1.should.equal 1 # fake assertion to appease the mighty bacon
next
end
warn 'Running multithread test for Session::Memcache' warn 'Running multithread test for Session::Memcache'
pool = Rack::Session::Memcache.new(incrementor) pool = Rack::Session::Memcache.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
@ -215,8 +221,8 @@ begin
end end
session = pool.pool.get(session_id) session = pool.pool.get(session_id)
session.size.should.be tnum+1 # counter session.size.should.equal tnum+1 # counter
session['counter'].should.be 2 # meeeh session['counter'].should.equal 2 # meeeh
tnum = rand(7).to_i+5 tnum = rand(7).to_i+5
r = Array.new(tnum) do |i| r = Array.new(tnum) do |i|

View file

@ -1,39 +1,40 @@
require 'test/spec'
require 'rack/session/pool'
require 'rack/mock'
require 'rack/response'
require 'thread' require 'thread'
require 'rack/mock'
require 'rack/session/pool'
context "Rack::Session::Pool" do describe Rack::Session::Pool do
session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key] session_key = Rack::Session::Pool::DEFAULT_OPTIONS[:key]
session_match = /#{session_key}=[0-9a-fA-F]+;/ session_match = /#{session_key}=[0-9a-fA-F]+;/
incrementor = lambda do |env| incrementor = lambda do |env|
env["rack.session"]["counter"] ||= 0 env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1 env["rack.session"]["counter"] += 1
Rack::Response.new(env["rack.session"].inspect).to_a Rack::Response.new(env["rack.session"].inspect).to_a
end end
drop_session = proc do |env|
drop_session = lambda do |env|
env['rack.session.options'][:drop] = true env['rack.session.options'][:drop] = true
incrementor.call(env) incrementor.call(env)
end end
renew_session = proc do |env|
renew_session = lambda do |env|
env['rack.session.options'][:renew] = true env['rack.session.options'][:renew] = true
incrementor.call(env) incrementor.call(env)
end end
defer_session = proc do |env|
defer_session = lambda do |env|
env['rack.session.options'][:defer] = true env['rack.session.options'][:defer] = true
incrementor.call(env) incrementor.call(env)
end end
specify "creates a new cookie" do it "creates a new cookie" do
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
res = Rack::MockRequest.new(pool).get("/") res = Rack::MockRequest.new(pool).get("/")
res["Set-Cookie"].should.match session_match res["Set-Cookie"].should.match session_match
res.body.should.equal '{"counter"=>1}' res.body.should.equal '{"counter"=>1}'
end end
specify "determines session from a cookie" do it "determines session from a cookie" do
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
cookie = req.get("/")["Set-Cookie"] cookie = req.get("/")["Set-Cookie"]
@ -43,14 +44,14 @@ context "Rack::Session::Pool" do
body.should.equal '{"counter"=>3}' body.should.equal '{"counter"=>3}'
end end
specify "survives nonexistant cookies" do it "survives nonexistant cookies" do
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
res = Rack::MockRequest.new(pool). res = Rack::MockRequest.new(pool).
get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel") get("/", "HTTP_COOKIE" => "#{session_key}=blarghfasel")
res.body.should.equal '{"counter"=>1}' res.body.should.equal '{"counter"=>1}'
end end
specify "deletes cookies with :drop option" do it "deletes cookies with :drop option" do
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
drop = Rack::Utils::Context.new(pool, drop_session) drop = Rack::Utils::Context.new(pool, drop_session)
@ -59,25 +60,25 @@ context "Rack::Session::Pool" do
res0 = req.get("/") res0 = req.get("/")
session = (cookie = res0["Set-Cookie"])[session_match] session = (cookie = res0["Set-Cookie"])[session_match]
res0.body.should.equal '{"counter"=>1}' res0.body.should.equal '{"counter"=>1}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res1 = req.get("/", "HTTP_COOKIE" => cookie) res1 = req.get("/", "HTTP_COOKIE" => cookie)
res1["Set-Cookie"][session_match].should.equal session res1["Set-Cookie"][session_match].should.equal session
res1.body.should.equal '{"counter"=>2}' res1.body.should.equal '{"counter"=>2}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res2 = dreq.get("/", "HTTP_COOKIE" => cookie) res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
res2["Set-Cookie"].should.equal nil res2["Set-Cookie"].should.equal nil
res2.body.should.equal '{"counter"=>3}' res2.body.should.equal '{"counter"=>3}'
pool.pool.size.should.be 0 pool.pool.size.should.equal 0
res3 = req.get("/", "HTTP_COOKIE" => cookie) res3 = req.get("/", "HTTP_COOKIE" => cookie)
res3["Set-Cookie"][session_match].should.not.equal session res3["Set-Cookie"][session_match].should.not.equal session
res3.body.should.equal '{"counter"=>1}' res3.body.should.equal '{"counter"=>1}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
end end
specify "provides new session id with :renew option" do it "provides new session id with :renew option" do
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
renew = Rack::Utils::Context.new(pool, renew_session) renew = Rack::Utils::Context.new(pool, renew_session)
@ -86,27 +87,27 @@ context "Rack::Session::Pool" do
res0 = req.get("/") res0 = req.get("/")
session = (cookie = res0["Set-Cookie"])[session_match] session = (cookie = res0["Set-Cookie"])[session_match]
res0.body.should.equal '{"counter"=>1}' res0.body.should.equal '{"counter"=>1}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res1 = req.get("/", "HTTP_COOKIE" => cookie) res1 = req.get("/", "HTTP_COOKIE" => cookie)
res1["Set-Cookie"][session_match].should.equal session res1["Set-Cookie"][session_match].should.equal session
res1.body.should.equal '{"counter"=>2}' res1.body.should.equal '{"counter"=>2}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res2 = rreq.get("/", "HTTP_COOKIE" => cookie) res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
new_cookie = res2["Set-Cookie"] new_cookie = res2["Set-Cookie"]
new_session = new_cookie[session_match] new_session = new_cookie[session_match]
new_session.should.not.equal session new_session.should.not.equal session
res2.body.should.equal '{"counter"=>3}' res2.body.should.equal '{"counter"=>3}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res3 = req.get("/", "HTTP_COOKIE" => new_cookie) res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
res3["Set-Cookie"][session_match].should.equal new_session res3["Set-Cookie"][session_match].should.equal new_session
res3.body.should.equal '{"counter"=>4}' res3.body.should.equal '{"counter"=>4}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
end end
specify "omits cookie with :defer option" do it "omits cookie with :defer option" do
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
defer = Rack::Utils::Context.new(pool, defer_session) defer = Rack::Utils::Context.new(pool, defer_session)
@ -115,27 +116,31 @@ context "Rack::Session::Pool" do
res0 = req.get("/") res0 = req.get("/")
session = (cookie = res0["Set-Cookie"])[session_match] session = (cookie = res0["Set-Cookie"])[session_match]
res0.body.should.equal '{"counter"=>1}' res0.body.should.equal '{"counter"=>1}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res1 = req.get("/", "HTTP_COOKIE" => cookie) res1 = req.get("/", "HTTP_COOKIE" => cookie)
res1["Set-Cookie"][session_match].should.equal session res1["Set-Cookie"][session_match].should.equal session
res1.body.should.equal '{"counter"=>2}' res1.body.should.equal '{"counter"=>2}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res2 = dreq.get("/", "HTTP_COOKIE" => cookie) res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
res2["Set-Cookie"].should.equal nil res2["Set-Cookie"].should.equal nil
res2.body.should.equal '{"counter"=>3}' res2.body.should.equal '{"counter"=>3}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
res3 = req.get("/", "HTTP_COOKIE" => cookie) res3 = req.get("/", "HTTP_COOKIE" => cookie)
res3["Set-Cookie"][session_match].should.equal session res3["Set-Cookie"][session_match].should.equal session
res3.body.should.equal '{"counter"=>4}' res3.body.should.equal '{"counter"=>4}'
pool.pool.size.should.be 1 pool.pool.size.should.equal 1
end end
# anyone know how to do this better? # anyone know how to do this better?
specify "multithread: should merge sessions" do it "should merge sessions when multithreaded" do
next unless $DEBUG unless $DEBUG
1.should.equal 1
next
end
warn 'Running multithread tests for Session::Pool' warn 'Running multithread tests for Session::Pool'
pool = Rack::Session::Pool.new(incrementor) pool = Rack::Session::Pool.new(incrementor)
req = Rack::MockRequest.new(pool) req = Rack::MockRequest.new(pool)
@ -166,7 +171,7 @@ context "Rack::Session::Pool" do
end end
session = pool.pool[sess_id] session = pool.pool[sess_id]
session.size.should.be tnum+1 # counter session.size.should.equal tnum+1 # counter
session['counter'].should.be 2 # meeeh session['counter'].should.equal 2 # meeeh
end end
end end

View file

@ -0,0 +1,23 @@
require 'rack/showexceptions'
require 'rack/mock'
describe Rack::ShowExceptions do
it "catches exceptions" do
res = nil
req = Rack::MockRequest.new(
Rack::ShowExceptions.new(
lambda{|env| raise RuntimeError }
))
lambda{
res = req.get("/")
}.should.not.raise
res.should.be.a.server_error
res.status.should.equal 500
res.should =~ /RuntimeError/
res.should =~ /ShowExceptions/
end
end

View file

@ -0,0 +1,79 @@
require 'rack/showstatus'
require 'rack/mock'
describe Rack::ShowStatus do
should "provide a default status message" do
req = Rack::MockRequest.new(
Rack::ShowStatus.new(lambda{|env|
[404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
}))
res = req.get("/", :lint => true)
res.should.be.not_found
res.should.be.not.empty
res["Content-Type"].should.equal("text/html")
res.should =~ /404/
res.should =~ /Not Found/
end
should "let the app provide additional information" do
req = Rack::MockRequest.new(
Rack::ShowStatus.new(
lambda{|env|
env["rack.showstatus.detail"] = "gone too meta."
[404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
}))
res = req.get("/", :lint => true)
res.should.be.not_found
res.should.be.not.empty
res["Content-Type"].should.equal("text/html")
res.should =~ /404/
res.should =~ /Not Found/
res.should =~ /too meta/
end
should "not replace existing messages" do
req = Rack::MockRequest.new(
Rack::ShowStatus.new(
lambda{|env|
[404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]]
}))
res = req.get("/", :lint => true)
res.should.be.not_found
res.body.should == "foo!"
end
should "pass on original headers" do
headers = {"WWW-Authenticate" => "Basic blah"}
req = Rack::MockRequest.new(
Rack::ShowStatus.new(lambda{|env| [401, headers, []] }))
res = req.get("/", :lint => true)
res["WWW-Authenticate"].should.equal("Basic blah")
end
should "replace existing messages if there is detail" do
req = Rack::MockRequest.new(
Rack::ShowStatus.new(
lambda{|env|
env["rack.showstatus.detail"] = "gone too meta."
[404, {"Content-Type" => "text/plain", "Content-Length" => "4"}, ["foo!"]]
}))
res = req.get("/", :lint => true)
res.should.be.not_found
res.should.be.not.empty
res["Content-Type"].should.equal("text/html")
res["Content-Length"].should.not.equal("4")
res.should =~ /404/
res.should =~ /too meta/
res.body.should.not =~ /foo/
end
end

View file

@ -1,5 +1,3 @@
require 'test/spec'
require 'rack/static' require 'rack/static'
require 'rack/mock' require 'rack/mock'
@ -9,26 +7,24 @@ class DummyApp
end end
end end
context "Rack::Static" do describe Rack::Static do
root = File.expand_path(File.dirname(__FILE__)) root = File.expand_path(File.dirname(__FILE__))
OPTIONS = {:urls => ["/cgi"], :root => root} OPTIONS = {:urls => ["/cgi"], :root => root}
setup do
@request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, OPTIONS)) @request = Rack::MockRequest.new(Rack::Static.new(DummyApp.new, OPTIONS))
end
specify "serves files" do it "serves files" do
res = @request.get("/cgi/test") res = @request.get("/cgi/test")
res.should.be.ok res.should.be.ok
res.body.should =~ /ruby/ res.body.should =~ /ruby/
end end
specify "404s if url root is known but it can't find the file" do it "404s if url root is known but it can't find the file" do
res = @request.get("/cgi/foo") res = @request.get("/cgi/foo")
res.should.be.not_found res.should.be.not_found
end end
specify "calls down the chain if url root is not known" do it "calls down the chain if url root is not known" do
res = @request.get("/something/else") res = @request.get("/something/else")
res.should.be.ok res.should.be.ok
res.body.should == "Hello World" res.body.should == "Hello World"

View file

@ -1,34 +1,31 @@
require 'test/spec'
begin begin
require 'rack/handler/thin' require 'rack/handler/thin'
require 'testrequest' require File.expand_path('../testrequest', __FILE__)
require 'timeout' require 'timeout'
context "Rack::Handler::Thin" do describe Rack::Handler::Thin do
include TestRequest::Helpers extend TestRequest::Helpers
setup do
@app = Rack::Lint.new(TestRequest.new) @app = Rack::Lint.new(TestRequest.new)
@server = nil @server = nil
Thin::Logging.silent = true Thin::Logging.silent = true
@thread = Thread.new do @thread = Thread.new do
Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server| Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9204) do |server|
@server = server @server = server
end end
end end
Thread.pass until @server && @server.running? Thread.pass until @server && @server.running?
should "respond" do
GET("/")
response.should.not.be.nil
end end
specify "should respond" do should "be a Thin" do
lambda {
GET("/") GET("/")
}.should.not.raise status.should.equal 200
end
specify "should be a Thin" do
GET("/")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /thin/ response["SERVER_SOFTWARE"].should =~ /thin/
response["HTTP_VERSION"].should.equal "HTTP/1.1" response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
@ -36,15 +33,15 @@ context "Rack::Handler::Thin" do
response["SERVER_NAME"].should.equal "0.0.0.0" response["SERVER_NAME"].should.equal "0.0.0.0"
end end
specify "should have rack headers" do should "have rack headers" do
GET("/") GET("/")
response["rack.version"].should.equal [0,3] response["rack.version"].should.equal [1,0]
response["rack.multithread"].should.be false response["rack.multithread"].should.equal false
response["rack.multiprocess"].should.be false response["rack.multiprocess"].should.equal false
response["rack.run_once"].should.be false response["rack.run_once"].should.equal false
end end
specify "should have CGI headers on GET" do should "have CGI headers on GET" do
GET("/") GET("/")
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["REQUEST_PATH"].should.equal "/" response["REQUEST_PATH"].should.equal "/"
@ -59,7 +56,7 @@ context "Rack::Handler::Thin" do
response["QUERY_STRING"].should.equal "quux=1" response["QUERY_STRING"].should.equal "quux=1"
end end
specify "should have CGI headers on POST" do should "have CGI headers on POST" do
POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200 status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST" response["REQUEST_METHOD"].should.equal "POST"
@ -69,21 +66,19 @@ context "Rack::Handler::Thin" do
response["test.postdata"].should.equal "rack-form-data=23" response["test.postdata"].should.equal "rack-form-data=23"
end end
specify "should support HTTP auth" do should "support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"}) GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end end
specify "should set status" do should "set status" do
GET("/test?secret") GET("/test?secret")
status.should.equal 403 status.should.equal 403
response["rack.url_scheme"].should.equal "http" response["rack.url_scheme"].should.equal "http"
end end
teardown do
@server.stop! @server.stop!
@thread.kill @thread.kill
end
end end
rescue LoadError rescue LoadError

View file

@ -1,10 +1,8 @@
require 'test/spec'
require 'rack/urlmap' require 'rack/urlmap'
require 'rack/mock' require 'rack/mock'
context "Rack::URLMap" do describe Rack::URLMap do
specify "dispatches paths correctly" do it "dispatches paths correctly" do
app = lambda { |env| app = lambda { |env|
[200, { [200, {
'X-ScriptName' => env['SCRIPT_NAME'], 'X-ScriptName' => env['SCRIPT_NAME'],
@ -67,7 +65,7 @@ context "Rack::URLMap" do
end end
specify "dispatches hosts correctly" do it "dispatches hosts correctly" do
map = Rack::URLMap.new("http://foo.org/" => lambda { |env| map = Rack::URLMap.new("http://foo.org/" => lambda { |env|
[200, [200,
{ "Content-Type" => "text/plain", { "Content-Type" => "text/plain",
@ -125,7 +123,7 @@ context "Rack::URLMap" do
res["X-Position"].should.equal "default.org" res["X-Position"].should.equal "default.org"
end end
specify "should be nestable" do should "be nestable" do
map = Rack::URLMap.new("/foo" => map = Rack::URLMap.new("/foo" =>
Rack::URLMap.new("/bar" => Rack::URLMap.new("/bar" =>
Rack::URLMap.new("/quux" => lambda { |env| Rack::URLMap.new("/quux" => lambda { |env|
@ -147,7 +145,7 @@ context "Rack::URLMap" do
res["X-ScriptName"].should.equal "/foo/bar/quux" res["X-ScriptName"].should.equal "/foo/bar/quux"
end end
specify "should route root apps correctly" do should "route root apps correctly" do
map = Rack::URLMap.new("/" => lambda { |env| map = Rack::URLMap.new("/" => lambda { |env|
[200, [200,
{ "Content-Type" => "text/plain", { "Content-Type" => "text/plain",
@ -189,7 +187,7 @@ context "Rack::URLMap" do
res["X-ScriptName"].should.equal "" res["X-ScriptName"].should.equal ""
end end
specify "should not squeeze slashes" do should "not squeeze slashes" do
map = Rack::URLMap.new("/" => lambda { |env| map = Rack::URLMap.new("/" => lambda { |env|
[200, [200,
{ "Content-Type" => "text/plain", { "Content-Type" => "text/plain",

View file

@ -1,18 +1,15 @@
require 'test/spec'
require 'rack/utils' require 'rack/utils'
require 'rack/lint'
require 'rack/mock' require 'rack/mock'
context "Rack::Utils" do describe Rack::Utils do
specify "should escape correctly" do should "escape correctly" do
Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar" Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
Rack::Utils.escape("a space").should.equal "a+space" Rack::Utils.escape("a space").should.equal "a+space"
Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\"). Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C" should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
end end
specify "should escape correctly for multibyte characters" do should "escape correctly for multibyte characters" do
matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8' Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
@ -21,7 +18,7 @@ context "Rack::Utils" do
Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8' Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
end end
specify "should unescape correctly" do should "unescape correctly" do
Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar" Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
Rack::Utils.unescape("a+space").should.equal "a space" Rack::Utils.unescape("a+space").should.equal "a space"
Rack::Utils.unescape("a%20space").should.equal "a space" Rack::Utils.unescape("a%20space").should.equal "a space"
@ -29,11 +26,11 @@ context "Rack::Utils" do
should.equal "q1!2\"'w$5&7/z8)?\\" should.equal "q1!2\"'w$5&7/z8)?\\"
end end
specify "should parse query strings correctly" do should "parse query strings correctly" do
Rack::Utils.parse_query("foo=bar"). Rack::Utils.parse_query("foo=bar").
should.equal "foo" => "bar" should.equal "foo" => "bar"
Rack::Utils.parse_query("foo=\"bar\""). Rack::Utils.parse_query("foo=\"bar\"").
should.equal "foo" => "bar" should.equal "foo" => "\"bar\""
Rack::Utils.parse_query("foo=bar&foo=quux"). Rack::Utils.parse_query("foo=bar&foo=quux").
should.equal "foo" => ["bar", "quux"] should.equal "foo" => ["bar", "quux"]
Rack::Utils.parse_query("foo=1&bar=2"). Rack::Utils.parse_query("foo=1&bar=2").
@ -43,7 +40,7 @@ context "Rack::Utils" do
Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar" Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar"
end end
specify "should parse nested query strings correctly" do should "parse nested query strings correctly" do
Rack::Utils.parse_nested_query("foo"). Rack::Utils.parse_nested_query("foo").
should.equal "foo" => nil should.equal "foo" => nil
Rack::Utils.parse_nested_query("foo="). Rack::Utils.parse_nested_query("foo=").
@ -51,7 +48,7 @@ context "Rack::Utils" do
Rack::Utils.parse_nested_query("foo=bar"). Rack::Utils.parse_nested_query("foo=bar").
should.equal "foo" => "bar" should.equal "foo" => "bar"
Rack::Utils.parse_nested_query("foo=\"bar\""). Rack::Utils.parse_nested_query("foo=\"bar\"").
should.equal "foo" => "bar" should.equal "foo" => "\"bar\""
Rack::Utils.parse_nested_query("foo=bar&foo=quux"). Rack::Utils.parse_nested_query("foo=bar&foo=quux").
should.equal "foo" => "quux" should.equal "foo" => "quux"
@ -121,7 +118,7 @@ context "Rack::Utils" do
message.should.equal "expected Array (got String) for param `y'" message.should.equal "expected Array (got String) for param `y'"
end end
specify "should build query strings correctly" do should "build query strings correctly" do
Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar" Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
Rack::Utils.build_query("foo" => ["bar", "quux"]). Rack::Utils.build_query("foo" => ["bar", "quux"]).
should.equal "foo=bar&foo=quux" should.equal "foo=bar&foo=quux"
@ -131,7 +128,7 @@ context "Rack::Utils" do
should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F" should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
end end
specify "should build nested query strings correctly" do should "build nested query strings correctly" do
Rack::Utils.build_nested_query("foo" => nil).should.equal "foo" Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
Rack::Utils.build_nested_query("foo" => "").should.equal "foo=" Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar" Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
@ -178,7 +175,7 @@ context "Rack::Utils" do
message.should.equal "value must be a Hash" message.should.equal "value must be a Hash"
end end
specify "should figure out which encodings are acceptable" do should "figure out which encodings are acceptable" do
helper = lambda do |a, b| helper = lambda do |a, b|
request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a)) request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
Rack::Utils.select_best_encoding(a, b) Rack::Utils.select_best_encoding(a, b)
@ -201,43 +198,43 @@ context "Rack::Utils" do
helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity") helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
end end
specify "should return the bytesize of String" do should "return the bytesize of String" do
Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6 Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
end end
specify "should return status code for integer" do should "return status code for integer" do
Rack::Utils.status_code(200).should.equal 200 Rack::Utils.status_code(200).should.equal 200
end end
specify "should return status code for string" do should "return status code for string" do
Rack::Utils.status_code("200").should.equal 200 Rack::Utils.status_code("200").should.equal 200
end end
specify "should return status code for symbol" do should "return status code for symbol" do
Rack::Utils.status_code(:ok).should.equal 200 Rack::Utils.status_code(:ok).should.equal 200
end end
end end
context "Rack::Utils::HeaderHash" do describe Rack::Utils::HeaderHash do
specify "should retain header case" do should "retain header case" do
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...") h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
h['ETag'] = 'Boo!' h['ETag'] = 'Boo!'
h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!' h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
end end
specify "should check existence of keys case insensitively" do should "check existence of keys case insensitively" do
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...") h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
h.should.include 'content-md5' h.should.include 'content-md5'
h.should.not.include 'ETag' h.should.not.include 'ETag'
end end
specify "should merge case-insensitively" do should "merge case-insensitively" do
h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123') h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR') merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR' merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
end end
specify "should overwrite case insensitively and assume the new key's case" do should "overwrite case insensitively and assume the new key's case" do
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz") h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
h["foo-bar"] = "bizzle" h["foo-bar"] = "bizzle"
h["FOO-BAR"].should.equal "bizzle" h["FOO-BAR"].should.equal "bizzle"
@ -245,55 +242,55 @@ context "Rack::Utils::HeaderHash" do
h.to_hash.should.equal "foo-bar" => "bizzle" h.to_hash.should.equal "foo-bar" => "bizzle"
end end
specify "should be converted to real Hash" do should "be converted to real Hash" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.to_hash.should.be.instance_of Hash h.to_hash.should.be.instance_of Hash
end end
specify "should convert Array values to Strings when converting to Hash" do should "convert Array values to Strings when converting to Hash" do
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"]) h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
h.to_hash.should.equal({ "foo" => "bar\nbaz" }) h.to_hash.should.equal({ "foo" => "bar\nbaz" })
end end
specify "should replace hashes correctly" do should "replace hashes correctly" do
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz") h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
j = {"foo" => "bar"} j = {"foo" => "bar"}
h.replace(j) h.replace(j)
h["foo"].should.equal "bar" h["foo"].should.equal "bar"
end end
specify "should be able to delete the given key case-sensitively" do should "be able to delete the given key case-sensitively" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.delete("foo") h.delete("foo")
h["foo"].should.be.nil h["foo"].should.be.nil
h["FOO"].should.be.nil h["FOO"].should.be.nil
end end
specify "should be able to delete the given key case-insensitively" do should "be able to delete the given key case-insensitively" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.delete("FOO") h.delete("FOO")
h["foo"].should.be.nil h["foo"].should.be.nil
h["FOO"].should.be.nil h["FOO"].should.be.nil
end end
specify "should return the deleted value when #delete is called on an existing key" do should "return the deleted value when #delete is called on an existing key" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.delete("Foo").should.equal("bar") h.delete("Foo").should.equal("bar")
end end
specify "should return nil when #delete is called on a non-existant key" do should "return nil when #delete is called on a non-existant key" do
h = Rack::Utils::HeaderHash.new("foo" => "bar") h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.delete("Hello").should.be.nil h.delete("Hello").should.be.nil
end end
specify "should avoid unnecessary object creation if possible" do should "avoid unnecessary object creation if possible" do
a = Rack::Utils::HeaderHash.new("foo" => "bar") a = Rack::Utils::HeaderHash.new("foo" => "bar")
b = Rack::Utils::HeaderHash.new(a) b = Rack::Utils::HeaderHash.new(a)
b.object_id.should.equal(a.object_id) b.object_id.should.equal(a.object_id)
b.should.equal(a) b.should.equal(a)
end end
specify "should convert Array values to Strings when responding to #each" do should "convert Array values to Strings when responding to #each" do
h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"]) h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
h.each do |k,v| h.each do |k,v|
k.should.equal("foo") k.should.equal("foo")
@ -303,7 +300,7 @@ context "Rack::Utils::HeaderHash" do
end end
context "Rack::Utils::Context" do describe Rack::Utils::Context do
class ContextTest class ContextTest
attr_reader :app attr_reader :app
def initialize app; @app=app; end def initialize app; @app=app; end
@ -316,7 +313,7 @@ context "Rack::Utils::Context" do
test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] } test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
test_app = ContextTest.new test_target4 test_app = ContextTest.new test_target4
specify "should set context correctly" do should "set context correctly" do
test_app.app.should.equal test_target4 test_app.app.should.equal test_target4
c1 = Rack::Utils::Context.new(test_app, test_target1) c1 = Rack::Utils::Context.new(test_app, test_target1)
c1.for.should.equal test_app c1.for.should.equal test_app
@ -326,7 +323,7 @@ context "Rack::Utils::Context" do
c2.app.should.equal test_target2 c2.app.should.equal test_target2
end end
specify "should alter app on recontexting" do should "alter app on recontexting" do
c1 = Rack::Utils::Context.new(test_app, test_target1) c1 = Rack::Utils::Context.new(test_app, test_target1)
c2 = c1.recontext(test_target2) c2 = c1.recontext(test_target2)
c2.for.should.equal test_app c2.for.should.equal test_app
@ -336,7 +333,7 @@ context "Rack::Utils::Context" do
c3.app.should.equal test_target3 c3.app.should.equal test_target3
end end
specify "should run different apps" do should "run different apps" do
c1 = Rack::Utils::Context.new test_app, test_target1 c1 = Rack::Utils::Context.new test_app, test_target1
c2 = c1.recontext test_target2 c2 = c1.recontext test_target2
c3 = c2.recontext test_target3 c3 = c2.recontext test_target3
@ -350,21 +347,37 @@ context "Rack::Utils::Context" do
r3 = c3.call(:misc_symbol) r3 = c3.call(:misc_symbol)
r3.should.be.nil r3.should.be.nil
r4 = Rack::MockRequest.new(a4).get('/') r4 = Rack::MockRequest.new(a4).get('/')
r4.status.should.be 200 r4.status.should.equal 200
r5 = Rack::MockRequest.new(a5).get('/') r5 = Rack::MockRequest.new(a5).get('/')
r5.status.should.be 200 r5.status.should.equal 200
r4.body.should.equal r5.body r4.body.should.equal r5.body
end end
end end
context "Rack::Utils::Multipart" do describe Rack::Utils::Multipart do
specify "should return nil if content type is not multipart" do def multipart_fixture(name)
file = multipart_file(name)
data = File.open(file, 'rb') { |io| io.read }
type = "multipart/form-data; boundary=AaB03x"
length = data.respond_to?(:bytesize) ? data.bytesize : data.size
{ "CONTENT_TYPE" => type,
"CONTENT_LENGTH" => length.to_s,
:input => StringIO.new(data) }
end
def multipart_file(name)
File.join(File.dirname(__FILE__), "multipart", name.to_s)
end
should "return nil if content type is not multipart" do
env = Rack::MockRequest.env_for("/", env = Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded') "CONTENT_TYPE" => 'application/x-www-form-urlencoded')
Rack::Utils::Multipart.parse_multipart(env).should.equal nil Rack::Utils::Multipart.parse_multipart(env).should.equal nil
end end
specify "should parse multipart upload with text file" do should "parse multipart upload with text file" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:text)) env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["submit-name"].should.equal "Larry" params["submit-name"].should.equal "Larry"
@ -377,7 +390,7 @@ context "Rack::Utils::Multipart" do
params["files"][:tempfile].read.should.equal "contents" params["files"][:tempfile].read.should.equal "contents"
end end
specify "should parse multipart upload with nested parameters" do should "parse multipart upload with nested parameters" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:nested)) env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["foo"]["submit-name"].should.equal "Larry" params["foo"]["submit-name"].should.equal "Larry"
@ -390,7 +403,7 @@ context "Rack::Utils::Multipart" do
params["foo"]["files"][:tempfile].read.should.equal "contents" params["foo"]["files"][:tempfile].read.should.equal "contents"
end end
specify "should parse multipart upload with binary file" do should "parse multipart upload with binary file" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:binary)) env = Rack::MockRequest.env_for("/", multipart_fixture(:binary))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["submit-name"].should.equal "Larry" params["submit-name"].should.equal "Larry"
@ -403,7 +416,7 @@ context "Rack::Utils::Multipart" do
params["files"][:tempfile].read.length.should.equal 26473 params["files"][:tempfile].read.length.should.equal 26473
end end
specify "should parse multipart upload with empty file" do should "parse multipart upload with empty file" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:empty)) env = Rack::MockRequest.env_for("/", multipart_fixture(:empty))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["submit-name"].should.equal "Larry" params["submit-name"].should.equal "Larry"
@ -416,7 +429,7 @@ context "Rack::Utils::Multipart" do
params["files"][:tempfile].read.should.equal "" params["files"][:tempfile].read.should.equal ""
end end
specify "should parse multipart upload with filename with semicolons" do should "parse multipart upload with filename with semicolons" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon)) env = Rack::MockRequest.env_for("/", multipart_fixture(:semicolon))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "text/plain" params["files"][:type].should.equal "text/plain"
@ -428,7 +441,7 @@ context "Rack::Utils::Multipart" do
params["files"][:tempfile].read.should.equal "contents" params["files"][:tempfile].read.should.equal "contents"
end end
specify "should not include file params if no file was selected" do should "not include file params if no file was selected" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:none)) env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["submit-name"].should.equal "Larry" params["submit-name"].should.equal "Larry"
@ -436,7 +449,7 @@ context "Rack::Utils::Multipart" do
params.keys.should.not.include "files" params.keys.should.not.include "files"
end end
specify "should parse IE multipart upload and clean up filename" do should "parse IE multipart upload and clean up filename" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:ie)) env = Rack::MockRequest.env_for("/", multipart_fixture(:ie))
params = Rack::Utils::Multipart.parse_multipart(env) params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "text/plain" params["files"][:type].should.equal "text/plain"
@ -449,7 +462,76 @@ context "Rack::Utils::Multipart" do
params["files"][:tempfile].read.should.equal "contents" params["files"][:tempfile].read.should.equal "contents"
end end
specify "rewinds input after parsing upload" do should "parse filename and modification param" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_and_modification_param))
params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "image/jpeg"
params["files"][:filename].should.equal "genome.jpeg"
params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
"Content-Disposition: attachment; " +
"name=\"files\"; " +
"filename=genome.jpeg; " +
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
"Content-Description: a complete map of the human genome\r\n"
params["files"][:name].should.equal "files"
params["files"][:tempfile].read.should.equal "contents"
end
should "parse filename with escaped quotes" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes))
params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "application/octet-stream"
params["files"][:filename].should.equal "escape \"quotes"
params["files"][:head].should.equal "Content-Disposition: form-data; " +
"name=\"files\"; " +
"filename=\"escape \\\"quotes\"\r\n" +
"Content-Type: application/octet-stream\r\n"
params["files"][:name].should.equal "files"
params["files"][:tempfile].read.should.equal "contents"
end
should "parse filename with percent escaped quotes" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_quotes))
params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "application/octet-stream"
params["files"][:filename].should.equal "escape \"quotes"
params["files"][:head].should.equal "Content-Disposition: form-data; " +
"name=\"files\"; " +
"filename=\"escape %22quotes\"\r\n" +
"Content-Type: application/octet-stream\r\n"
params["files"][:name].should.equal "files"
params["files"][:tempfile].read.should.equal "contents"
end
should "parse filename with unescaped quotes" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_quotes))
params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "application/octet-stream"
params["files"][:filename].should.equal "escape \"quotes"
params["files"][:head].should.equal "Content-Disposition: form-data; " +
"name=\"files\"; " +
"filename=\"escape \"quotes\"\r\n" +
"Content-Type: application/octet-stream\r\n"
params["files"][:name].should.equal "files"
params["files"][:tempfile].read.should.equal "contents"
end
should "parse filename with escaped quotes and modification param" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_quotes_and_modification_param))
params = Rack::Utils::Multipart.parse_multipart(env)
params["files"][:type].should.equal "image/jpeg"
params["files"][:filename].should.equal "\"human\" genome.jpeg"
params["files"][:head].should.equal "Content-Type: image/jpeg\r\n" +
"Content-Disposition: attachment; " +
"name=\"files\"; " +
"filename=\"\"human\" genome.jpeg\"; " +
"modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";\r\n" +
"Content-Description: a complete map of the human genome\r\n"
params["files"][:name].should.equal "files"
params["files"][:tempfile].read.should.equal "contents"
end
it "rewinds input after parsing upload" do
options = multipart_fixture(:text) options = multipart_fixture(:text)
input = options[:input] input = options[:input]
env = Rack::MockRequest.env_for("/", options) env = Rack::MockRequest.env_for("/", options)
@ -459,7 +541,7 @@ context "Rack::Utils::Multipart" do
input.read.length.should.equal 197 input.read.length.should.equal 197
end end
specify "builds multipart body" do it "builds multipart body" do
files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt")) files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files) data = Rack::Utils::Multipart.build_multipart("submit-name" => "Larry", "files" => files)
@ -475,7 +557,7 @@ context "Rack::Utils::Multipart" do
params["files"][:tempfile].read.should.equal "contents" params["files"][:tempfile].read.should.equal "contents"
end end
specify "builds nested multipart body" do it "builds nested multipart body" do
files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt")) files = Rack::Utils::Multipart::UploadedFile.new(multipart_file("file1.txt"))
data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}]) data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => files}])
@ -491,7 +573,7 @@ context "Rack::Utils::Multipart" do
params["people"][0]["files"][:tempfile].read.should.equal "contents" params["people"][0]["files"][:tempfile].read.should.equal "contents"
end end
specify "can parse fields that end at the end of the buffer" do it "can parse fields that end at the end of the buffer" do
input = File.read(multipart_file("bad_robots")) input = File.read(multipart_file("bad_robots"))
req = Rack::Request.new Rack::MockRequest.env_for("/", req = Rack::Request.new Rack::MockRequest.env_for("/",
@ -503,7 +585,7 @@ context "Rack::Utils::Multipart" do
req.POST['addresses'].should.not.equal nil req.POST['addresses'].should.not.equal nil
end end
specify "builds complete params with the chunk size of 16384 slicing exactly on boundary" do it "builds complete params with the chunk size of 16384 slicing exactly on boundary" do
data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n") data = File.open(multipart_file("fail_16384_nofile")) { |f| f.read }.gsub(/\n/, "\r\n")
options = { options = {
"CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo", "CONTENT_TYPE" => "multipart/form-data; boundary=----WebKitFormBoundaryWsY0GnpbI5U7ztzo",
@ -522,31 +604,14 @@ context "Rack::Utils::Multipart" do
params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017" params["AAAAAAAAAAAAAAAAAAA"]["PLAPLAPLA_MEMMEMMEMM_ATTRATTRER"]["new"]["-2"]["ba_unit_id"].should.equal "1017"
end end
specify "should return nil if no UploadedFiles were used" do should "return nil if no UploadedFiles were used" do
data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}]) data = Rack::Utils::Multipart.build_multipart("people" => [{"submit-name" => "Larry", "files" => "contents"}])
data.should.equal nil data.should.equal nil
end end
specify "should raise ArgumentError if params is not a Hash" do should "raise ArgumentError if params is not a Hash" do
lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }. lambda { Rack::Utils::Multipart.build_multipart("foo=bar") }.
should.raise(ArgumentError). should.raise(ArgumentError).
message.should.equal "value must be a Hash" message.should.equal "value must be a Hash"
end end
private
def multipart_fixture(name)
file = multipart_file(name)
data = File.open(file, 'rb') { |io| io.read }
type = "multipart/form-data; boundary=AaB03x"
length = data.respond_to?(:bytesize) ? data.bytesize : data.size
{ "CONTENT_TYPE" => type,
"CONTENT_LENGTH" => length.to_s,
:input => StringIO.new(data) }
end
def multipart_file(name)
File.join(File.dirname(__FILE__), "multipart", name.to_s)
end
end end

View file

@ -1,16 +1,11 @@
require 'test/spec' require 'rack/mock'
require File.expand_path('../testrequest', __FILE__)
require 'rack/handler/webrick'
require 'rack/lint'
require 'rack/response'
require 'testrequest'
Thread.abort_on_exception = true Thread.abort_on_exception = true
context "Rack::Handler::WEBrick" do describe Rack::Handler::WEBrick do
include TestRequest::Helpers extend TestRequest::Helpers
setup do
@server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0', @server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0',
:Port => @port=9202, :Port => @port=9202,
:Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN), :Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
@ -19,17 +14,16 @@ context "Rack::Handler::WEBrick" do
Rack::Lint.new(TestRequest.new) Rack::Lint.new(TestRequest.new)
Thread.new { @server.start } Thread.new { @server.start }
trap(:INT) { @server.shutdown } trap(:INT) { @server.shutdown }
end
specify "should respond" do should "respond" do
lambda { lambda {
GET("/test") GET("/test")
}.should.not.raise }.should.not.raise
end end
specify "should be a WEBrick" do should "be a WEBrick" do
GET("/test") GET("/test")
status.should.be 200 status.should.equal 200
response["SERVER_SOFTWARE"].should =~ /WEBrick/ response["SERVER_SOFTWARE"].should =~ /WEBrick/
response["HTTP_VERSION"].should.equal "HTTP/1.1" response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1" response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
@ -37,15 +31,15 @@ context "Rack::Handler::WEBrick" do
response["SERVER_NAME"].should.equal "0.0.0.0" response["SERVER_NAME"].should.equal "0.0.0.0"
end end
specify "should have rack headers" do should "have rack headers" do
GET("/test") GET("/test")
response["rack.version"].should.equal [1,1] response["rack.version"].should.equal [1,1]
response["rack.multithread"].should.be true response["rack.multithread"].should.be.true
response["rack.multiprocess"].should.be false response["rack.multiprocess"].should.be.false
response["rack.run_once"].should.be false response["rack.run_once"].should.be.false
end end
specify "should have CGI headers on GET" do should "have CGI headers on GET" do
GET("/test") GET("/test")
response["REQUEST_METHOD"].should.equal "GET" response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test" response["SCRIPT_NAME"].should.equal "/test"
@ -69,7 +63,7 @@ context "Rack::Handler::WEBrick" do
response["QUERY_STRING"].should.equal "quux=1" response["QUERY_STRING"].should.equal "quux=1"
end end
specify "should have CGI headers on POST" do should "have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'}) POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200 status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST" response["REQUEST_METHOD"].should.equal "POST"
@ -80,18 +74,18 @@ context "Rack::Handler::WEBrick" do
response["test.postdata"].should.equal "rack-form-data=23" response["test.postdata"].should.equal "rack-form-data=23"
end end
specify "should support HTTP auth" do should "support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"}) GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ=" response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end end
specify "should set status" do should "set status" do
GET("/test?secret") GET("/test?secret")
status.should.equal 403 status.should.equal 403
response["rack.url_scheme"].should.equal "http" response["rack.url_scheme"].should.equal "http"
end end
specify "should correctly set cookies" do should "correctly set cookies" do
@server.mount "/cookie-test", Rack::Handler::WEBrick, @server.mount "/cookie-test", Rack::Handler::WEBrick,
Rack::Lint.new(lambda { |req| Rack::Lint.new(lambda { |req|
res = Rack::Response.new res = Rack::Response.new
@ -107,7 +101,7 @@ context "Rack::Handler::WEBrick" do
} }
end end
specify "should provide a .run" do should "provide a .run" do
block_ran = false block_ran = false
catch(:done) { catch(:done) {
Rack::Handler::WEBrick.run(lambda {}, Rack::Handler::WEBrick.run(lambda {},
@ -120,11 +114,9 @@ context "Rack::Handler::WEBrick" do
throw :done throw :done
} }
} }
block_ran.should.be true block_ran.should.be.true
@s.shutdown @s.shutdown
end end
teardown do
@server.shutdown @server.shutdown
end
end end

View file

@ -2,10 +2,15 @@ require 'yaml'
require 'net/http' require 'net/http'
class TestRequest class TestRequest
NOSERIALIZE = [Method, Proc]
def call(env) def call(env)
status = env["QUERY_STRING"] =~ /secret/ ? 403 : 200 status = env["QUERY_STRING"] =~ /secret/ ? 403 : 200
env["test.postdata"] = env["rack.input"].read env["test.postdata"] = env["rack.input"].read
body = env.to_yaml minienv = env.dup
# This may in the future want to replace with a dummy value instead.
minienv.delete_if { |k,v| NOSERIALIZE.any? { |c| v.kind_of?(c) } }
body = minienv.to_yaml
size = body.respond_to?(:bytesize) ? body.bytesize : body.size size = body.respond_to?(:bytesize) ? body.bytesize : body.size
[status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]] [status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]]
end end
@ -35,7 +40,7 @@ class TestRequest
@status = response.code.to_i @status = response.code.to_i
begin begin
@response = YAML.load(response.body) @response = YAML.load(response.body)
rescue ArgumentError rescue TypeError, ArgumentError
@response = nil @response = nil
end end
} }

Some files were not shown because too many files have changed in this diff Show more