Instiki 0.16.3: Rails 2.3.0

Instiki now runs on the Rails 2.3.0 Candidate Release.
Among other improvements, this means that it now 
automagically selects between WEBrick and Mongrel.

Just run

    ./instiki --daemon
This commit is contained in:
Jacques Distler 2009-02-04 14:26:08 -06:00
parent 43aadecc99
commit 4e14ccc74d
893 changed files with 71965 additions and 28511 deletions

View file

@ -0,0 +1,20 @@
server.modules = ("mod_fastcgi", "mod_cgi")
server.document-root = "."
server.errorlog = "lighttpd.errors"
server.port = 9203
server.event-handler = "select"
cgi.assign = ("/test" => "",
# ".ru" => ""
)
fastcgi.server = ("test.fcgi" => ("localhost" =>
("min-procs" => 1,
"socket" => "/tmp/rack-test-fcgi",
"bin-path" => "test.fcgi")),
"test.ru" => ("localhost" =>
("min-procs" => 1,
"socket" => "/tmp/rack-test-ru-fcgi",
"bin-path" => "test.ru")),
)

9
vendor/plugins/rack/test/cgi/test vendored Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env ruby
# -*- ruby -*-
$: << File.join(File.dirname(__FILE__), "..", "..", "lib")
require 'rack'
require '../testrequest'
Rack::Handler::CGI.run(Rack::Lint.new(TestRequest.new))

8
vendor/plugins/rack/test/cgi/test.fcgi vendored Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env ruby
# -*- ruby -*-
$:.unshift '../../lib'
require 'rack'
require '../testrequest'
Rack::Handler::FastCGI.run(Rack::Lint.new(TestRequest.new))

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

@ -0,0 +1,7 @@
#!/usr/bin/env ../../bin/rackup
#\ -E deployment -I ../../lib
# -*- ruby -*-
require '../testrequest'
run TestRequest.new

View file

@ -0,0 +1,69 @@
require 'test/spec'
require 'rack/auth/basic'
require 'rack/mock'
context 'Rack::Auth::Basic' do
def realm
'WallysWorld'
end
def unprotected_app
lambda { |env| [ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ] }
end
def protected_app
app = Rack::Auth::Basic.new(unprotected_app) { |username, password| 'Boss' == username }
app.realm = realm
app
end
setup do
@request = Rack::MockRequest.new(protected_app)
end
def request_with_basic_auth(username, password, &block)
request 'HTTP_AUTHORIZATION' => 'Basic ' + ["#{username}:#{password}"].pack("m*"), &block
end
def request(headers = {})
yield @request.get('/', headers)
end
def assert_basic_auth_challenge(response)
response.should.be.a.client_error
response.status.should.equal 401
response.should.include 'WWW-Authenticate'
response.headers['WWW-Authenticate'].should =~ /Basic realm="/
response.body.should.be.empty
end
specify 'should challenge correctly when no credentials are specified' do
request do |response|
assert_basic_auth_challenge response
end
end
specify 'should rechallenge if incorrect credentials are specified' do
request_with_basic_auth 'joe', 'password' do |response|
assert_basic_auth_challenge response
end
end
specify 'should return application output if correct credentials are specified' do
request_with_basic_auth 'Boss', 'password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Boss'
end
end
specify 'should return 400 Bad Request if different auth scheme used' do
request 'HTTP_AUTHORIZATION' => 'Digest params' do |response|
response.should.be.a.client_error
response.status.should.equal 400
response.should.not.include 'WWW-Authenticate'
end
end
end

View file

@ -0,0 +1,169 @@
require 'test/spec'
require 'rack/auth/digest/md5'
require 'rack/mock'
context 'Rack::Auth::Digest::MD5' do
def realm
'WallysWorld'
end
def unprotected_app
lambda do |env|
[ 200, {'Content-Type' => 'text/plain'}, ["Hi #{env['REMOTE_USER']}"] ]
end
end
def protected_app
app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
{ 'Alice' => 'correct-password' }[username]
end
app.realm = realm
app.opaque = 'this-should-be-secret'
app
end
def protected_app_with_hashed_passwords
app = Rack::Auth::Digest::MD5.new(unprotected_app) do |username|
username == 'Alice' ? Digest::MD5.hexdigest("Alice:#{realm}:correct-password") : nil
end
app.realm = realm
app.opaque = 'this-should-be-secret'
app.passwords_hashed = true
app
end
setup do
@request = Rack::MockRequest.new(protected_app)
end
def request(path, headers = {}, &block)
response = @request.get(path, headers)
block.call(response) if block
return response
end
class MockDigestRequest
def initialize(params)
@params = params
end
def method_missing(sym)
if @params.has_key? k = sym.to_s
return @params[k]
end
super
end
def method
'GET'
end
def response(password)
Rack::Auth::Digest::MD5.new(nil).send :digest, self, password
end
end
def request_with_digest_auth(path, username, password, options = {}, &block)
response = request('/')
return response unless response.status == 401
if wait = options.delete(:wait)
sleep wait
end
challenge = response['WWW-Authenticate'].split(' ', 2).last
params = Rack::Auth::Digest::Params.parse(challenge)
params['username'] = username
params['nc'] = '00000001'
params['cnonce'] = 'nonsensenonce'
params['uri'] = path
params.update options
params['response'] = MockDigestRequest.new(params).response(password)
request(path, { 'HTTP_AUTHORIZATION' => "Digest #{params}" }, &block)
end
def assert_digest_auth_challenge(response)
response.should.be.a.client_error
response.status.should.equal 401
response.should.include 'WWW-Authenticate'
response.headers['WWW-Authenticate'].should =~ /^Digest /
response.body.should.be.empty
end
def assert_bad_request(response)
response.should.be.a.client_error
response.status.should.equal 400
response.should.not.include 'WWW-Authenticate'
end
specify 'should challenge when no credentials are specified' do
request '/' do |response|
assert_digest_auth_challenge response
end
end
specify 'should return application output if correct credentials given' do
request_with_digest_auth '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'should return application output if correct credentials given (hashed passwords)' do
@request = Rack::MockRequest.new(protected_app_with_hashed_passwords)
request_with_digest_auth '/', 'Alice', 'correct-password' do |response|
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Alice'
end
end
specify 'should rechallenge if incorrect username given' do
request_with_digest_auth '/', 'Bob', 'correct-password' do |response|
assert_digest_auth_challenge response
end
end
specify 'should rechallenge if incorrect password given' do
request_with_digest_auth '/', 'Alice', 'wrong-password' do |response|
assert_digest_auth_challenge response
end
end
specify 'should rechallenge with stale parameter if nonce is stale' do
begin
Rack::Auth::Digest::Nonce.time_limit = 1
request_with_digest_auth '/', 'Alice', 'correct-password', :wait => 2 do |response|
assert_digest_auth_challenge response
response.headers['WWW-Authenticate'].should =~ /\bstale=true\b/
end
ensure
Rack::Auth::Digest::Nonce.time_limit = nil
end
end
specify 'should return 400 Bad Request if incorrect qop given' do
request_with_digest_auth '/', 'Alice', 'correct-password', 'qop' => 'auth-int' do |response|
assert_bad_request response
end
end
specify 'should return 400 Bad Request if incorrect uri given' do
request_with_digest_auth '/', 'Alice', 'correct-password', 'uri' => '/foo' do |response|
assert_bad_request response
end
end
specify 'should return 400 Bad Request if different auth scheme used' do
request '/', 'HTTP_AUTHORIZATION' => 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==' do |response|
assert_bad_request response
end
end
end

View file

@ -0,0 +1,137 @@
require 'test/spec'
begin
# requires the ruby-openid gem
require 'rack/auth/openid'
context "Rack::Auth::OpenID" do
OID = Rack::Auth::OpenID
realm = 'http://path/arf'
ruri = %w{arf arf/blargh}
auri = ruri.map{|u|'/'+u}
furi = auri.map{|u|'http://path'+u}
specify 'realm uri should be absolute and have a path' do
lambda{OID.new('/path')}.
should.raise ArgumentError
lambda{OID.new('http://path')}.
should.raise ArgumentError
lambda{OID.new('http://path/')}.
should.not.raise
lambda{OID.new('http://path/arf')}.
should.not.raise
end
specify 'uri options should be absolute' do
[:login_good, :login_fail, :login_quit, :return_to].each do |param|
ruri.each do |uri|
lambda{OID.new(realm, {param=>uri})}.
should.raise ArgumentError
end
auri.each do |uri|
lambda{OID.new(realm, {param=>uri})}.
should.raise ArgumentError
end
furi.each do |uri|
lambda{OID.new(realm, {param=>uri})}.
should.not.raise
end
end
end
specify 'return_to should be absolute and be under the realm' do
lambda{OID.new(realm, {:return_to => 'http://path'})}.
should.raise ArgumentError
lambda{OID.new(realm, {:return_to => 'http://path/'})}.
should.raise ArgumentError
lambda{OID.new(realm, {:return_to => 'http://path/arf'})}.
should.not.raise
lambda{OID.new(realm, {:return_to => 'http://path/arf/'})}.
should.not.raise
lambda{OID.new(realm, {:return_to => 'http://path/arf/blargh'})}.
should.not.raise
end
specify 'extensions should be a module' do
ext = Object.new
lambda{OID.new(realm).add_extension(ext)}.
should.raise(TypeError).
message.should.match(/not a module/)
ext2 = Module.new
lambda{OID.new(realm).add_extension(ext2)}.
should.raise(ArgumentError).
message.should.not.match(/not a module/)
end
specify 'extensions should have required constants defined' do
ext = Module.new
lambda{OID.new(realm).add_extension(ext)}.
should.raise(ArgumentError).
message.should.match(/missing/)
ext::Request = nil
lambda{OID.new(realm).add_extension(ext)}.
should.raise(ArgumentError).
message.should.match(/missing/).
should.not.match(/Request/)
ext::Response = nil
lambda{OID.new(realm).add_extension(ext)}.
should.raise(ArgumentError).
message.should.match(/missing/).
should.not.match(/Response/)
ext::NS_URI = nil
lambda{OID.new(realm).add_extension(ext)}.
should.raise(TypeError).
message.should.not.match(/missing/)
end
specify 'extensions should have Request and Response defined and inherit from OpenID::Extension' do
$-w, w = nil, $-w # yuck
ext = Module.new
ext::Request = nil
ext::Response = nil
ext::NS_URI = nil
lambda{OID.new(realm).add_extension(ext)}.
should.raise(TypeError).
message.should.match(/not a class/)
ext::Request = Class.new()
lambda{OID.new(realm).add_extension(ext)}.
should.raise(TypeError).
message.should.match(/not a class/)
ext::Response = Class.new()
lambda{OID.new(realm).add_extension(ext)}.
should.raise(ArgumentError).
message.should.match(/not a decendant/)
ext::Request = Class.new(::OpenID::Extension)
lambda{OID.new(realm).add_extension(ext)}.
should.raise(ArgumentError).
message.should.match(/not a decendant/)
ext::Response = Class.new(::OpenID::Extension)
lambda{OID.new(realm).add_extension(ext)}.
should.raise(TypeError).
message.should.match(/NS_URI/)
$-w = w
end
specify 'extensions should have NS_URI defined and be a string of an absolute http uri' do
$-w, w = nil, $-w # yuck
ext = Module.new
ext::Request = Class.new(::OpenID::Extension)
ext::Response = Class.new(::OpenID::Extension)
ext::NS_URI = nil
lambda{OID.new(realm).add_extension(ext)}.
should.raise(TypeError).
message.should.match(/not a string/)
ext::NS_URI = 'openid.net'
lambda{OID.new(realm).add_extension(ext)}.
should.raise(ArgumentError).
message.should.match(/not an http uri/)
ext::NS_URI = 'http://openid.net'
lambda{OID.new(realm).add_extension(ext)}.
should.not.raise
$-w = w
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Auth::OpenID tests (ruby-openid 2 is required). `gem install ruby-openid` and try again."
end

View file

@ -0,0 +1,84 @@
require 'test/spec'
require 'rack/builder'
require 'rack/mock'
require 'rack/showexceptions'
require 'rack/auth/basic'
context "Rack::Builder" do
specify "chains apps by default" do
app = Rack::Builder.new do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
end.to_app
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
end
specify "has implicit #to_app" do
app = Rack::Builder.new do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
end
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
end
specify "supports blocks on use" do
app = Rack::Builder.new do
use Rack::ShowExceptions
use Rack::Auth::Basic do |username, password|
'secret' == password
end
run lambda { |env| [200, {}, 'Hi Boss'] }
end
response = Rack::MockRequest.new(app).get("/")
response.should.be.client_error
response.status.should.equal 401
# with auth...
response = Rack::MockRequest.new(app).get("/",
'HTTP_AUTHORIZATION' => 'Basic ' + ["joe:secret"].pack("m*"))
response.status.should.equal 200
response.body.to_s.should.equal 'Hi Boss'
end
specify "has explicit #to_app" do
app = Rack::Builder.app do
use Rack::ShowExceptions
run lambda { |env| raise "bzzzt" }
end
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
Rack::MockRequest.new(app).get("/").should.be.server_error
end
specify "apps are initialized once" do
app = Rack::Builder.new do
class AppClass
def initialize
@called = 0
end
def call(env)
raise "bzzzt" if @called > 0
@called += 1
[200, {'Content-Type' => 'text/plain'}, 'OK']
end
end
use Rack::ShowExceptions
run AppClass.new
end
Rack::MockRequest.new(app).get("/").status.should.equal 200
Rack::MockRequest.new(app).get("/").should.be.server_error
end
end

View file

@ -0,0 +1,51 @@
require 'test/spec'
require 'stringio'
require 'uri'
begin
require 'rack/mock'
$-w, w = nil, $-w # yuck
require 'camping'
require 'rack/adapter/camping'
Camping.goes :CampApp
module CampApp
module Controllers
class HW < R('/')
def get
@headers["X-Served-By"] = URI("http://rack.rubyforge.org")
"Camping works!"
end
def post
"Data: #{input.foo}"
end
end
end
end
$-w = w
context "Rack::Adapter::Camping" do
specify "works with GET" do
res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
get("/")
res.should.be.ok
res["Content-Type"].should.equal "text/html"
res["X-Served-By"].should.equal "http://rack.rubyforge.org"
res.body.should.equal "Camping works!"
end
specify "works with POST" do
res = Rack::MockRequest.new(Rack::Adapter::Camping.new(CampApp)).
post("/", :input => "foo=bar")
res.should.be.ok
res.body.should.equal "Data: bar"
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Adapter::Camping tests (Camping is required). `gem install camping` and try again."
end

View file

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

View file

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

View file

@ -0,0 +1,32 @@
require 'test/spec'
require 'stringio'
require 'rack/commonlogger'
require 'rack/lobster'
require 'rack/mock'
context "Rack::CommonLogger" do
app = lambda { |env|
[200,
{"Content-Type" => "text/html"},
["foo"]]}
specify "should log to rack.errors by default" do
log = StringIO.new
res = Rack::MockRequest.new(Rack::CommonLogger.new(app)).get("/")
res.errors.should.not.be.empty
res.errors.should =~ /GET /
res.errors.should =~ / 200 / # status
res.errors.should =~ / 3 / # length
end
specify "should log to anything with <<" do
log = ""
res = Rack::MockRequest.new(Rack::CommonLogger.new(app, log)).get("/")
log.should =~ /GET /
log.should =~ / 200 / # status
log.should =~ / 3 / # length
end
end

View file

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

View file

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

View file

@ -0,0 +1,105 @@
require 'test/spec'
require 'rack/mock'
require 'rack/deflater'
require 'stringio'
require 'time' # for Time#httpdate
context "Rack::Deflater" do
def build_response(status, body, accept_encoding, headers = {})
app = lambda { |env| [status, {}, body] }
request = Rack::MockRequest.env_for("", headers.merge("HTTP_ACCEPT_ENCODING" => accept_encoding))
response = Rack::Deflater.new(app).call(request)
return response
end
specify "should be able to deflate bodies that respond to each" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
response = build_response(200, body, "deflate")
response[0].should.equal(200)
response[1].should.equal({ "Content-Encoding" => "deflate", "Vary" => "Accept-Encoding" })
response[2].to_s.should.equal("K\313\317OJ,\002\000")
end
# TODO: This is really just a special case of the above...
specify "should be able to deflate String bodies" do
response = build_response(200, "Hello world!", "deflate")
response[0].should.equal(200)
response[1].should.equal({ "Content-Encoding" => "deflate", "Vary" => "Accept-Encoding" })
response[2].to_s.should.equal("\363H\315\311\311W(\317/\312IQ\004\000")
end
specify "should be able to gzip bodies that respond to each" do
body = Object.new
class << body; def each; yield("foo"); yield("bar"); end; end
response = build_response(200, body, "gzip")
response[0].should.equal(200)
response[1].should.equal({ "Content-Encoding" => "gzip", "Vary" => "Accept-Encoding" })
io = StringIO.new(response[2].to_s)
gz = Zlib::GzipReader.new(io)
gz.read.should.equal("foobar")
gz.close
end
specify "should be able to fallback to no deflation" do
response = build_response(200, "Hello world!", "superzip")
response[0].should.equal(200)
response[1].should.equal({ "Vary" => "Accept-Encoding" })
response[2].should.equal("Hello world!")
end
specify "should be able to skip when there is no response entity body" do
response = build_response(304, [], "gzip")
response[0].should.equal(304)
response[1].should.equal({})
response[2].should.equal([])
end
specify "should handle the lack of an acceptable encoding" do
response1 = build_response(200, "Hello world!", "identity;q=0", "PATH_INFO" => "/")
response1[0].should.equal(406)
response1[1].should.equal({"Content-Type" => "text/plain"})
response1[2].should.equal(["An acceptable encoding for the requested resource / could not be found."])
response2 = build_response(200, "Hello world!", "identity;q=0", "SCRIPT_NAME" => "/foo", "PATH_INFO" => "/bar")
response2[0].should.equal(406)
response2[1].should.equal({"Content-Type" => "text/plain"})
response2[2].should.equal(["An acceptable encoding for the requested resource /foo/bar could not be found."])
end
specify "should handle gzip response with Last-Modified header" do
last_modified = Time.now.httpdate
app = lambda { |env| [200, { "Last-Modified" => last_modified }, "Hello World!"] }
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
response = Rack::Deflater.new(app).call(request)
response[0].should.equal(200)
response[1].should.equal({ "Content-Encoding" => "gzip", "Vary" => "Accept-Encoding", "Last-Modified" => last_modified })
io = StringIO.new(response[2].to_s)
gz = Zlib::GzipReader.new(io)
gz.read.should.equal("Hello World!")
gz.close
end
specify "should do nothing when no-transform Cache-Control directive present" do
app = lambda { |env| [200, {'Cache-Control' => 'no-transform'}, ['Hello World!']] }
request = Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => "gzip")
response = Rack::Deflater.new(app).call(request)
response[0].should.equal(200)
response[1].should.not.include "Content-Encoding"
response[2].join.should.equal("Hello World!")
end
end

View file

@ -0,0 +1,61 @@
require 'test/spec'
require 'rack/directory'
require 'rack/lint'
require 'rack/mock'
context "Rack::Directory" do
DOCROOT = File.expand_path(File.dirname(__FILE__))
FILE_CATCH = proc{|env| [200, {'Content-Type'=>'text/plain', "Content-Length" => "7"}, 'passed!'] }
app = Rack::Directory.new DOCROOT, FILE_CATCH
specify "serves directory indices" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/")
res.should.be.ok
res.should =~ /<html><head>/
end
specify "passes to app if file found" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/test")
res.should.be.ok
res.should =~ /passed!/
end
specify "serves uri with URL encoded filenames" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/%63%67%69/") # "/cgi/test"
res.should.be.ok
res.should =~ /<html><head>/
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/%74%65%73%74") # "/cgi/test"
res.should.be.ok
res.should =~ /passed!/
end
specify "does not allow directory traversal" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/../test")
res.should.be.forbidden
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/%2E%2E/test")
res.should.be.forbidden
end
specify "404s if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(app)).
get("/cgi/blubb")
res.should.be.not_found
end
end

View file

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

View file

@ -0,0 +1,64 @@
require 'test/spec'
require 'rack/file'
require 'rack/lint'
require 'rack/mock'
context "Rack::File" do
DOCROOT = File.expand_path(File.dirname(__FILE__))
specify "serves files" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/test")
res.should.be.ok
res.should =~ /ruby/
end
specify "sets Last-Modified header" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/test")
path = File.join(DOCROOT, "/cgi/test")
res.should.be.ok
res["Last-Modified"].should.equal File.mtime(path).httpdate
end
specify "serves files with URL encoded filenames" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/%74%65%73%74") # "/cgi/test"
res.should.be.ok
res.should =~ /ruby/
end
specify "does not allow directory traversal" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/../test")
res.should.be.forbidden
end
specify "does not allow directory traversal with encoded periods" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/%2E%2E/README")
res.should.be.forbidden
end
specify "404s if it can't find the file" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi/blubb")
res.should.be.not_found
end
specify "detects SystemCallErrors" do
res = Rack::MockRequest.new(Rack::Lint.new(Rack::File.new(DOCROOT))).
get("/cgi")
res.should.be.not_found
end
end

View file

@ -0,0 +1,24 @@
require 'test/spec'
require 'rack/handler'
class Rack::Handler::Lobster; end
class RockLobster; end
context "Rack::Handler" do
specify "has registered default handlers" do
Rack::Handler.get('cgi').should.equal Rack::Handler::CGI
Rack::Handler.get('fastcgi').should.equal Rack::Handler::FastCGI
Rack::Handler.get('mongrel').should.equal Rack::Handler::Mongrel
Rack::Handler.get('webrick').should.equal Rack::Handler::WEBrick
end
specify "should get unregistered handler by name" do
Rack::Handler.get('lobster').should.equal Rack::Handler::Lobster
end
specify "should register custom handler" do
Rack::Handler.register('rock_lobster', 'RockLobster')
Rack::Handler.get('rock_lobster').should.equal RockLobster
end
end

View file

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

View file

@ -0,0 +1,380 @@
require 'test/spec'
require 'stringio'
require 'rack/lint'
require 'rack/mock'
context "Rack::Lint" do
def env(*args)
Rack::MockRequest.env_for("/", *args)
end
specify "passes valid request" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
}).call(env({}))
}.should.not.raise
end
specify "notices fatal errors" do
lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
message.should.match(/No env given/)
end
specify "notices environment errors" do
lambda { Rack::Lint.new(nil).call 5 }.should.raise(Rack::Lint::LintError).
message.should.match(/not a Hash/)
lambda {
e = env
e.delete("REQUEST_METHOD")
Rack::Lint.new(nil).call(e)
}.should.raise(Rack::Lint::LintError).
message.should.match(/missing required key REQUEST_METHOD/)
lambda {
e = env
e.delete("SERVER_NAME")
Rack::Lint.new(nil).call(e)
}.should.raise(Rack::Lint::LintError).
message.should.match(/missing required key SERVER_NAME/)
lambda {
Rack::Lint.new(nil).call(env("HTTP_CONTENT_TYPE" => "text/plain"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/contains HTTP_CONTENT_TYPE/)
lambda {
Rack::Lint.new(nil).call(env("HTTP_CONTENT_LENGTH" => "42"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/contains HTTP_CONTENT_LENGTH/)
lambda {
Rack::Lint.new(nil).call(env("FOO" => Object.new))
}.should.raise(Rack::Lint::LintError).
message.should.match(/non-string value/)
lambda {
Rack::Lint.new(nil).call(env("rack.version" => "0.2"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must be an Array/)
lambda {
Rack::Lint.new(nil).call(env("rack.url_scheme" => "gopher"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/url_scheme unknown/)
lambda {
Rack::Lint.new(nil).call(env("REQUEST_METHOD" => "FUCKUP?"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/REQUEST_METHOD/)
lambda {
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "howdy"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must start with/)
lambda {
Rack::Lint.new(nil).call(env("PATH_INFO" => "../foo"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must start with/)
lambda {
Rack::Lint.new(nil).call(env("CONTENT_LENGTH" => "xcii"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Invalid CONTENT_LENGTH/)
lambda {
e = env
e.delete("PATH_INFO")
e.delete("SCRIPT_NAME")
Rack::Lint.new(nil).call(e)
}.should.raise(Rack::Lint::LintError).
message.should.match(/One of .* must be set/)
lambda {
Rack::Lint.new(nil).call(env("SCRIPT_NAME" => "/"))
}.should.raise(Rack::Lint::LintError).
message.should.match(/cannot be .* make it ''/)
end
specify "notices input errors" do
lambda {
Rack::Lint.new(nil).call(env("rack.input" => ""))
}.should.raise(Rack::Lint::LintError).
message.should.match(/does not respond to #gets/)
end
specify "notices error errors" do
lambda {
Rack::Lint.new(nil).call(env("rack.errors" => ""))
}.should.raise(Rack::Lint::LintError).
message.should.match(/does not respond to #puts/)
end
specify "notices status errors" do
lambda {
Rack::Lint.new(lambda { |env|
["cc", {}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must be >=100 seen as integer/)
lambda {
Rack::Lint.new(lambda { |env|
[42, {}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must be >=100 seen as integer/)
end
specify "notices header errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, Object.new, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("headers object should respond to #each, but doesn't (got Object as headers)")
lambda {
Rack::Lint.new(lambda { |env|
[200, {true=>false}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("header key must be a string, was TrueClass")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Status" => "404"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must not contain Status/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-Type:" => "text/plain"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must not contain :/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-" => "text/plain"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/must not end/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"..%%quark%%.." => "text/plain"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("invalid header name: ..%%quark%%..")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo" => Object.new}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("header values must respond to #each, but the value of 'Foo' doesn't (is Object)")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo" => [1,2,3]}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.equal("header values must consist of Strings, but 'Foo' also contains a Fixnum")
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Foo-Bar" => "text\000plain"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/invalid header/)
end
specify "notices content-type errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/No Content-Type/)
[100, 101, 204, 304].each do |status|
lambda {
Rack::Lint.new(lambda { |env|
[status, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Type header found/)
end
end
specify "notices content-length errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/No Content-Length/)
[100, 101, 204, 304].each do |status|
lambda {
Rack::Lint.new(lambda { |env|
[status, {"Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Length header found/)
end
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain", "Content-Length" => "0", "Transfer-Encoding" => "chunked"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Length header should not be used/)
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain", "Content-Length" => "1"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/Content-Length header was 1, but should be 0/)
end
specify "notices body errors" do
lambda {
status, header, body = Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
}).call(env({}))
body.each { |part| }
}.should.raise(Rack::Lint::LintError).
message.should.match(/yielded non-string/)
end
specify "notices input handling errors" do
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].gets("\r\n")
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/gets called with arguments/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read("foo")
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read called with non-integer argument/)
weirdio = Object.new
class << weirdio
def gets
42
end
def read
23
end
def each
yield 23
yield 42
end
end
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].gets
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/gets didn't return a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].each { |x| }
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/each didn't yield a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].read
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env("rack.input" => weirdio))
}.should.raise(Rack::Lint::LintError).
message.should.match(/read didn't return a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.input"].close
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/close must not be called/)
end
specify "notices error handling errors" do
lambda {
Rack::Lint.new(lambda { |env|
env["rack.errors"].write(42)
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/write not called with a String/)
lambda {
Rack::Lint.new(lambda { |env|
env["rack.errors"].close
[201, {"Content-type" => "text/plain", "Content-length" => "0"}, ""]
}).call(env({}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/close must not be called/)
end
specify "notices HEAD errors" do
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, []]
}).call(env({"REQUEST_METHOD" => "HEAD"}))
}.should.not.raise
lambda {
Rack::Lint.new(lambda { |env|
[200, {"Content-type" => "test/plain", "Content-length" => "3"}, "foo"]
}).call(env({"REQUEST_METHOD" => "HEAD"}))
}.should.raise(Rack::Lint::LintError).
message.should.match(/body was given for HEAD/)
end
end
context "Rack::Lint::InputWrapper" do
specify "delegates :size 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")
wrapper = Rack::Lint::InputWrapper.new(io)
wrapper.read.should == "123"
wrapper.read.should == ""
wrapper.rewind
wrapper.read.should == "123"
end
end

View file

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

View file

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

View file

@ -0,0 +1,152 @@
require 'yaml'
require 'rack/mock'
require 'rack/request'
require 'rack/response'
app = lambda { |env|
req = Rack::Request.new(env)
env["mock.postdata"] = env["rack.input"].read
if req.GET["error"]
env["rack.errors"].puts req.GET["error"]
env["rack.errors"].flush
end
Rack::Response.new(env.to_yaml,
req.GET["status"] || 200,
"Content-Type" => "text/yaml").finish
}
context "Rack::MockRequest" do
specify "should return a MockResponse" do
res = Rack::MockRequest.new(app).get("")
res.should.be.kind_of Rack::MockResponse
end
specify "should be able to only return the environment" do
env = Rack::MockRequest.env_for("")
env.should.be.kind_of Hash
env.should.include "rack.version"
end
specify "should provide sensible defaults" do
res = Rack::MockRequest.new(app).request
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["SERVER_NAME"].should.equal "example.org"
env["SERVER_PORT"].should.equal "80"
env["QUERY_STRING"].should.equal ""
env["PATH_INFO"].should.equal "/"
env["SCRIPT_NAME"].should.equal ""
env["rack.url_scheme"].should.equal "http"
env["mock.postdata"].should.be.empty
end
specify "should allow GET/POST/PUT/DELETE" do
res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
res = Rack::MockRequest.new(app).post("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "POST"
res = Rack::MockRequest.new(app).put("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "PUT"
res = Rack::MockRequest.new(app).delete("", :input => "foo")
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "DELETE"
Rack::MockRequest.env_for("/", :method => "OPTIONS")["REQUEST_METHOD"].
should.equal "OPTIONS"
end
specify "should allow posting" do
res = Rack::MockRequest.new(app).get("", :input => "foo")
env = YAML.load(res.body)
env["mock.postdata"].should.equal "foo"
res = Rack::MockRequest.new(app).post("", :input => StringIO.new("foo"))
env = YAML.load(res.body)
env["mock.postdata"].should.equal "foo"
end
specify "should use all parts of an URL" do
res = Rack::MockRequest.new(app).
get("https://bla.example.org:9292/meh/foo?bar")
res.should.be.kind_of Rack::MockResponse
env = YAML.load(res.body)
env["REQUEST_METHOD"].should.equal "GET"
env["SERVER_NAME"].should.equal "bla.example.org"
env["SERVER_PORT"].should.equal "9292"
env["QUERY_STRING"].should.equal "bar"
env["PATH_INFO"].should.equal "/meh/foo"
env["rack.url_scheme"].should.equal "https"
end
specify "should behave valid according to the Rack spec" do
lambda {
res = Rack::MockRequest.new(app).
get("https://bla.example.org:9292/meh/foo?bar", :lint => true)
}.should.not.raise(Rack::Lint::LintError)
end
end
context "Rack::MockResponse" do
specify "should provide access to the HTTP status" do
res = Rack::MockRequest.new(app).get("")
res.should.be.successful
res.should.be.ok
res = Rack::MockRequest.new(app).get("/?status=404")
res.should.not.be.successful
res.should.be.client_error
res.should.be.not_found
res = Rack::MockRequest.new(app).get("/?status=501")
res.should.not.be.successful
res.should.be.server_error
res = Rack::MockRequest.new(app).get("/?status=307")
res.should.be.redirect
res = Rack::MockRequest.new(app).get("/?status=201", :lint => true)
res.should.be.empty
end
specify "should provide access to the HTTP headers" do
res = Rack::MockRequest.new(app).get("")
res.should.include "Content-Type"
res.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_length.should.be 381 # needs change often.
res.location.should.be.nil
end
specify "should provide access to the HTTP body" do
res = Rack::MockRequest.new(app).get("")
res.body.should =~ /rack/
res.should =~ /rack/
res.should.match(/rack/)
res.should.satisfy { |r| r.match(/rack/) }
end
specify "should provide access to the Rack errors" do
res = Rack::MockRequest.new(app).get("/?error=foo", :lint => true)
res.should.be.ok
res.errors.should.not.be.empty
res.errors.should.include "foo"
end
specify "should optionally make Rack errors fatal" do
lambda {
Rack::MockRequest.new(app).get("/?error=foo", :fatal => true)
}.should.raise(Rack::MockRequest::FatalWarning)
end
end

View file

@ -0,0 +1,189 @@
require 'test/spec'
begin
require 'rack/handler/mongrel'
require 'rack/urlmap'
require 'rack/lint'
require 'testrequest'
require 'timeout'
Thread.abort_on_exception = true
$tcp_defer_accept_opts = nil
$tcp_cork_opts = nil
context "Rack::Handler::Mongrel" do
include TestRequest::Helpers
setup do
server = Mongrel::HttpServer.new(@host='0.0.0.0', @port=9201)
server.register('/test',
Rack::Handler::Mongrel.new(Rack::Lint.new(TestRequest.new)))
server.register('/stream',
Rack::Handler::Mongrel.new(Rack::Lint.new(StreamingRequest)))
@acc = server.run
end
specify "should respond" do
lambda {
GET("/test")
}.should.not.raise
end
specify "should be a Mongrel" do
GET("/test")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /Mongrel/
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal "9201"
response["SERVER_NAME"].should.equal "0.0.0.0"
end
specify "should have rack headers" do
GET("/test")
response["rack.version"].should.equal [0,1]
response["rack.multithread"].should.be true
response["rack.multiprocess"].should.be false
response["rack.run_once"].should.be false
end
specify "should have CGI headers on GET" do
GET("/test")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test"
response["PATH_INFO"].should.be.nil
response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal ""
GET("/test/foo?quux=1")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test/foo"
response["PATH_INFO"].should.equal "/foo"
response["QUERY_STRING"].should.equal "quux=1"
end
specify "should have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/test"
response["QUERY_STRING"].should.equal ""
response["HTTP_X_TEST_HEADER"].should.equal "42"
response["test.postdata"].should.equal "rack-form-data=23"
end
specify "should support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end
specify "should set status" do
GET("/test?secret")
status.should.equal 403
response["rack.url_scheme"].should.equal "http"
end
specify "should provide a .run" do
block_ran = false
Thread.new {
Rack::Handler::Mongrel.run(lambda {}, {:Port => 9211}) { |server|
server.should.be.kind_of Mongrel::HttpServer
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should provide a .run that maps a hash" do
block_ran = false
Thread.new {
map = {'/'=>lambda{},'/foo'=>lambda{}}
Rack::Handler::Mongrel.run(map, :map => true, :Port => 9221) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.be 2
server.classifier.uris.should.not.include '/arf'
server.classifier.uris.should.include '/'
server.classifier.uris.should.include '/foo'
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should provide a .run that maps a urlmap" do
block_ran = false
Thread.new {
map = Rack::URLMap.new({'/'=>lambda{},'/bar'=>lambda{}})
Rack::Handler::Mongrel.run(map, {:map => true, :Port => 9231}) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.size.should.be 2
server.classifier.uris.should.not.include '/arf'
server.classifier.uris.should.include '/'
server.classifier.uris.should.include '/bar'
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should provide a .run that maps a urlmap restricting by host" do
block_ran = false
Thread.new {
map = Rack::URLMap.new({
'/' => lambda{},
'/foo' => lambda{},
'/bar' => lambda{},
'http://localhost/' => lambda{},
'http://localhost/bar' => lambda{},
'http://falsehost/arf' => lambda{},
'http://falsehost/qux' => lambda{}
})
opt = {:map => true, :Port => 9241, :Host => 'localhost'}
Rack::Handler::Mongrel.run(map, opt) { |server|
server.should.be.kind_of Mongrel::HttpServer
server.classifier.uris.should.include '/'
server.classifier.handler_map['/'].size.should.be 2
server.classifier.uris.should.include '/foo'
server.classifier.handler_map['/foo'].size.should.be 1
server.classifier.uris.should.include '/bar'
server.classifier.handler_map['/bar'].size.should.be 2
server.classifier.uris.should.not.include '/qux'
server.classifier.uris.should.not.include '/arf'
server.classifier.uris.size.should.be 3
block_ran = true
}
}
sleep 1
block_ran.should.be true
end
specify "should stream #each part of the response" do
body = ''
begin
Timeout.timeout(1) do
Net::HTTP.start(@host, @port) do |http|
get = Net::HTTP::Get.new('/stream')
http.request(get) do |response|
response.read_body { |part| body << part }
end
end
end
rescue Timeout::Error
end
body.should.not.be.empty
end
teardown do
@acc.raise Mongrel::StopServer
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Handler::Mongrel tests (Mongrel is required). `gem install mongrel` and try again."
end

View file

@ -0,0 +1,77 @@
require 'test/spec'
require 'rack/recursive'
require 'rack/urlmap'
require 'rack/response'
require 'rack/mock'
context "Rack::Recursive" do
setup do
@app1 = lambda { |env|
res = Rack::Response.new
res["X-Path-Info"] = env["PATH_INFO"]
res["X-Query-String"] = env["QUERY_STRING"]
res.finish do |res|
res.write "App1"
end
}
@app2 = lambda { |env|
Rack::Response.new.finish do |res|
res.write "App2"
_, _, body = env['rack.recursive.include'].call(env, "/app1")
body.each { |b|
res.write b
}
end
}
@app3 = lambda { |env|
raise Rack::ForwardRequest.new("/app1")
}
@app4 = lambda { |env|
raise Rack::ForwardRequest.new("http://example.org/app1/quux?meh")
}
end
specify "should allow for subrequests" do
res = Rack::MockRequest.new(Rack::Recursive.new(
Rack::URLMap.new("/app1" => @app1,
"/app2" => @app2))).
get("/app2")
res.should.be.ok
res.body.should.equal "App2App1"
end
specify "should raise error on requests not below the app" do
app = Rack::URLMap.new("/app1" => @app1,
"/app" => Rack::Recursive.new(
Rack::URLMap.new("/1" => @app1,
"/2" => @app2)))
lambda {
Rack::MockRequest.new(app).get("/app/2")
}.should.raise(ArgumentError).
message.should =~ /can only include below/
end
specify "should support forwarding" do
app = Rack::Recursive.new(Rack::URLMap.new("/app1" => @app1,
"/app3" => @app3,
"/app4" => @app4))
res = Rack::MockRequest.new(app).get("/app3")
res.should.be.ok
res.body.should.equal "App1"
res = Rack::MockRequest.new(app).get("/app4")
res.should.be.ok
res.body.should.equal "App1"
res["X-Path-Info"].should.equal "/quux"
res["X-Query-String"].should.equal "meh"
end
end

View file

@ -0,0 +1,446 @@
require 'test/spec'
require 'stringio'
require 'rack/request'
require 'rack/mock'
context "Rack::Request" do
specify "wraps the rack variables" do
req = Rack::Request.new(Rack::MockRequest.env_for("http://example.com:8080/"))
req.body.should.respond_to? :gets
req.scheme.should.equal "http"
req.request_method.should.equal "GET"
req.should.be.get
req.should.not.be.post
req.should.not.be.put
req.should.not.be.delete
req.should.not.be.head
req.script_name.should.equal ""
req.path_info.should.equal "/"
req.query_string.should.equal ""
req.host.should.equal "example.com"
req.port.should.equal 8080
req.content_length.should.be.nil
req.content_type.should.be.nil
end
specify "can figure out the correct host" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_HOST" => "www2.example.org")
req.host.should.equal "www2.example.org"
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "SERVER_NAME" => "example.org:9292")
req.host.should.equal "example.org"
end
specify "can parse the query string" do
req = Rack::Request.new(Rack::MockRequest.env_for("/?foo=bar&quux=bla"))
req.query_string.should.equal "foo=bar&quux=bla"
req.GET.should.equal "foo" => "bar", "quux" => "bla"
req.POST.should.be.empty
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "can parse POST data" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", :input => "foo=bar&quux=bla")
req.content_type.should.be.nil
req.media_type.should.be.nil
req.query_string.should.equal "foo=quux"
req.GET.should.equal "foo" => "quux"
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "can parse POST data with explicit content type" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
:input => "foo=bar&quux=bla")
req.content_type.should.equal 'application/x-www-form-urlencoded;foo=bar'
req.media_type.should.equal 'application/x-www-form-urlencoded'
req.media_type_params['foo'].should.equal 'bar'
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "does not parse POST data when media type is not form-data" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux",
"CONTENT_TYPE" => 'text/plain;charset=utf-8',
:input => "foo=bar&quux=bla")
req.content_type.should.equal 'text/plain;charset=utf-8'
req.media_type.should.equal 'text/plain'
req.media_type_params['charset'].should.equal 'utf-8'
req.POST.should.be.empty
req.params.should.equal "foo" => "quux"
req.body.read.should.equal "foo=bar&quux=bla"
end
specify "rewinds input after parsing POST data" do
input = StringIO.new("foo=bar&quux=bla")
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
:input => input)
req.params.should.equal "foo" => "bar", "quux" => "bla"
input.read.should.equal "foo=bar&quux=bla"
end
specify "does not rewind unwindable CGI input" do
input = StringIO.new("foo=bar&quux=bla")
input.instance_eval "undef :rewind"
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'application/x-www-form-urlencoded;foo=bar',
:input => input)
req.params.should.equal "foo" => "bar", "quux" => "bla"
end
specify "can get value by key from params with #[]" do
req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=quux")
req['foo'].should.equal 'quux'
req[:foo].should.equal 'quux'
end
specify "can set value to key on params with #[]=" do
req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=duh")
req['foo'].should.equal 'duh'
req[:foo].should.equal 'duh'
req.params.should.equal 'foo' => 'duh'
req['foo'] = 'bar'
req.params.should.equal 'foo' => 'bar'
req['foo'].should.equal 'bar'
req[:foo].should.equal 'bar'
req[:foo] = 'jaz'
req.params.should.equal 'foo' => 'jaz'
req['foo'].should.equal 'jaz'
req[:foo].should.equal 'jaz'
end
specify "values_at answers values by keys in order given" do
req = Rack::Request.new \
Rack::MockRequest.env_for("?foo=baz&wun=der&bar=ful")
req.values_at('foo').should.equal ['baz']
req.values_at('foo', 'wun').should.equal ['baz', 'der']
req.values_at('bar', 'foo', 'wun').should.equal ['ful', 'baz', 'der']
end
specify "referrer should be extracted correct" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/", "HTTP_REFERER" => "/some/path")
req.referer.should.equal "/some/path"
req = Rack::Request.new \
Rack::MockRequest.env_for("/")
req.referer.should.equal "/"
end
specify "can cache, but invalidates the cache" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/?foo=quux", :input => "foo=bar&quux=bla")
req.GET.should.equal "foo" => "quux"
req.GET.should.equal "foo" => "quux"
req.env["QUERY_STRING"] = "bla=foo"
req.GET.should.equal "bla" => "foo"
req.GET.should.equal "bla" => "foo"
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.POST.should.equal "foo" => "bar", "quux" => "bla"
req.env["rack.input"] = StringIO.new("foo=bla&quux=bar")
req.POST.should.equal "foo" => "bla", "quux" => "bar"
req.POST.should.equal "foo" => "bla", "quux" => "bar"
end
specify "can figure out if called via XHR" do
req = Rack::Request.new(Rack::MockRequest.env_for(""))
req.should.not.be.xhr
req = Rack::Request.new \
Rack::MockRequest.env_for("", "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")
req.should.be.xhr
end
specify "can parse cookies" do
req = Rack::Request.new \
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"
req.env.delete("HTTP_COOKIE")
req.cookies.should.equal({})
end
specify "parses cookies according to RFC 2109" do
req = Rack::Request.new \
Rack::MockRequest.env_for('', 'HTTP_COOKIE' => 'foo=bar;foo=car')
req.cookies.should.equal 'foo' => 'bar'
end
specify "provides setters" do
req = Rack::Request.new(e=Rack::MockRequest.env_for(""))
req.script_name.should.equal ""
req.script_name = "/foo"
req.script_name.should.equal "/foo"
e["SCRIPT_NAME"].should.equal "/foo"
req.path_info.should.equal "/"
req.path_info = "/foo"
req.path_info.should.equal "/foo"
e["PATH_INFO"].should.equal "/foo"
end
specify "provides the original env" do
req = Rack::Request.new(e=Rack::MockRequest.env_for(""))
req.env.should.be e
end
specify "can restore the URL" do
Rack::Request.new(Rack::MockRequest.env_for("")).url.
should.equal "http://example.org/"
Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).url.
should.equal "http://example.org/foo/"
Rack::Request.new(Rack::MockRequest.env_for("/foo")).url.
should.equal "http://example.org/foo"
Rack::Request.new(Rack::MockRequest.env_for("?foo")).url.
should.equal "http://example.org/?foo"
Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).url.
should.equal "http://example.org:8080/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).url.
should.equal "https://example.org/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).url.
should.equal "https://example.com:8080/foo?foo"
end
specify "can restore the full path" do
Rack::Request.new(Rack::MockRequest.env_for("")).fullpath.
should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("", "SCRIPT_NAME" => "/foo")).fullpath.
should.equal "/foo/"
Rack::Request.new(Rack::MockRequest.env_for("/foo")).fullpath.
should.equal "/foo"
Rack::Request.new(Rack::MockRequest.env_for("?foo")).fullpath.
should.equal "/?foo"
Rack::Request.new(Rack::MockRequest.env_for("http://example.org:8080/")).fullpath.
should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.org/")).fullpath.
should.equal "/"
Rack::Request.new(Rack::MockRequest.env_for("https://example.com:8080/foo?foo")).fullpath.
should.equal "/foo?foo"
end
specify "can handle multiple media type parameters" do
req = Rack::Request.new \
Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => 'text/plain; foo=BAR,baz=bizzle dizzle;BLING=bam')
req.should.not.be.form_data
req.media_type_params.should.include 'foo'
req.media_type_params['foo'].should.equal 'BAR'
req.media_type_params.should.include 'baz'
req.media_type_params['baz'].should.equal 'bizzle dizzle'
req.media_type_params.should.not.include 'BLING'
req.media_type_params.should.include 'bling'
req.media_type_params['bling'].should.equal 'bam'
end
specify "can parse multipart form data" do
# Adapted from RFC 1867.
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="reply"\r
\r
yes\r
--AaB03x\r
content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
Content-Type: image/jpeg\r
Content-Transfer-Encoding: base64\r
\r
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\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)
req.POST.should.include "fileupload"
req.POST.should.include "reply"
req.should.be.form_data
req.content_length.should.equal input.size
req.media_type.should.equal 'multipart/form-data'
req.media_type_params.should.include 'boundary'
req.media_type_params['boundary'].should.equal 'AaB03x'
req.POST["reply"].should.equal "yes"
f = req.POST["fileupload"]
f.should.be.kind_of Hash
f[:type].should.equal "image/jpeg"
f[:filename].should.equal "dj.jpg"
f.should.include :tempfile
f[:tempfile].size.should.equal 76
end
specify "can parse big multipart form data" do
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
#{"x"*32768}\r
--AaB03x\r
content-disposition: form-data; name="mean"; filename="mean"\r
\r
--AaB03xha\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)
req.POST["huge"][:tempfile].size.should.equal 32768
req.POST["mean"][:tempfile].size.should.equal 10
req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
end
specify "can detect invalid multipart form data" do
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\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.raise(EOFError)
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
foo\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.raise(EOFError)
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
foo\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.raise(EOFError)
end
specify "should work around buggy 1.8.* Tempfile equality" do
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="huge"; filename="huge"\r
\r
foo\r
--AaB03x--
EOF
rack_input = Tempfile.new("rackspec")
rack_input.write(input)
rack_input.rewind
req = Rack::Request.new Rack::MockRequest.env_for("/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size,
:input => rack_input)
lambda {req.POST}.should.not.raise
lambda {req.POST}.should.blaming("input re-processed!").not.raise
end
specify "does conform to the Rack spec" do
app = lambda { |env|
content = Rack::Request.new(env).POST["file"].inspect
size = content.respond_to?(:bytesize) ? content.bytesize : content.size
[200, {"Content-Type" => "text/html", "Content-Length" => size.to_s}, content]
}
input = <<EOF
--AaB03x\r
content-disposition: form-data; name="reply"\r
\r
yes\r
--AaB03x\r
content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
Content-Type: image/jpeg\r
Content-Transfer-Encoding: base64\r
\r
/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
--AaB03x--\r
EOF
res = Rack::MockRequest.new(Rack::Lint.new(app)).get "/",
"CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
"CONTENT_LENGTH" => input.size.to_s, "rack.input" => StringIO.new(input)
res.should.be.ok
end
specify "should parse Accept-Encoding correctly" do
parser = lambda do |x|
Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => x)).accept_encoding
end
parser.call(nil).should.equal([])
parser.call("compress, gzip").should.equal([["compress", 1.0], ["gzip", 1.0]])
parser.call("").should.equal([])
parser.call("*").should.equal([["*", 1.0]])
parser.call("compress;q=0.5, gzip;q=1.0").should.equal([["compress", 0.5], ["gzip", 1.0]])
parser.call("gzip;q=1.0, identity; q=0.5, *;q=0").should.equal([["gzip", 1.0], ["identity", 0.5], ["*", 0] ])
lambda { parser.call("gzip ; q=1.0") }.should.raise(RuntimeError)
end
specify 'should provide ip information' do
app = lambda { |env|
request = Rack::Request.new(env)
response = Rack::Response.new
response.write request.ip
response.finish
}
mock = Rack::MockRequest.new(Rack::Lint.new(app))
res = mock.get '/', 'REMOTE_ADDR' => '123.123.123.123'
res.body.should == '123.123.123.123'
res = mock.get '/',
'REMOTE_ADDR' => '123.123.123.123',
'HTTP_X_FORWARDED_FOR' => '234.234.234.234'
res.body.should == '234.234.234.234'
res = mock.get '/',
'REMOTE_ADDR' => '123.123.123.123',
'HTTP_X_FORWARDED_FOR' => '234.234.234.234,212.212.212.212'
res.body.should == '212.212.212.212'
end
end

View file

@ -0,0 +1,174 @@
require 'test/spec'
require 'set'
require 'rack/response'
context "Rack::Response" do
specify "has sensible default values" do
response = Rack::Response.new
status, header, body = response.finish
status.should.equal 200
header.should.equal "Content-Type" => "text/html", "Content-Length" => "0"
body.each { |part|
part.should.equal ""
}
response = Rack::Response.new
status, header, body = *response
status.should.equal 200
header.should.equal "Content-Type" => "text/html", "Content-Length" => "0"
body.each { |part|
part.should.equal ""
}
end
specify "can be written to" do
response = Rack::Response.new
status, header, body = response.finish do
response.write "foo"
response.write "bar"
response.write "baz"
end
parts = []
body.each { |part| parts << part }
parts.should.equal ["foo", "bar", "baz"]
end
specify "can set and read headers" do
response = Rack::Response.new
response["Content-Type"].should.equal "text/html"
response["Content-Type"] = "text/plain"
response["Content-Type"].should.equal "text/plain"
end
specify "can set cookies" do
response = Rack::Response.new
response.set_cookie "foo", "bar"
response["Set-Cookie"].should.equal "foo=bar"
response.set_cookie "foo2", "bar2"
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"]
response.set_cookie "foo3", "bar3"
response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
end
specify "formats the Cookie expiration date accordingly to RFC 2109" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
response["Set-Cookie"].should.match(
/expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../)
end
specify "can set secure cookies" do
response = Rack::Response.new
response.set_cookie "foo", {:value => "bar", :secure => true}
response["Set-Cookie"].should.equal "foo=bar; secure"
end
specify "can delete cookies" do
response = Rack::Response.new
response.set_cookie "foo", "bar"
response.set_cookie "foo2", "bar2"
response.delete_cookie "foo"
response["Set-Cookie"].should.equal ["foo2=bar2",
"foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
end
specify "has a useful constructor" do
r = Rack::Response.new("foo")
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foo"
r = Rack::Response.new(["foo", "bar"])
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foobar"
r = Rack::Response.new(["foo", "bar"].to_set)
r.write "foo"
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foobarfoo"
r = Rack::Response.new([], 500)
r.status.should.equal 500
end
specify "has a constructor that can take a block" do
r = Rack::Response.new { |res|
res.status = 404
res.write "foo"
}
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.equal "foo"
status.should.equal 404
end
specify "doesn't return invalid responses" do
r = Rack::Response.new(["foo", "bar"], 204)
status, header, body = r.finish
str = ""; body.each { |part| str << part }
str.should.be.empty
header["Content-Type"].should.equal nil
lambda {
Rack::Response.new(Object.new)
}.should.raise(TypeError).
message.should =~ /stringable or iterable required/
end
specify "knows if it's empty" do
r = Rack::Response.new
r.should.be.empty
r.write "foo"
r.should.not.be.empty
r = Rack::Response.new
r.should.be.empty
r.finish
r.should.be.empty
r = Rack::Response.new
r.should.be.empty
r.finish { }
r.should.not.be.empty
end
specify "should provide access to the HTTP status" do
res = Rack::Response.new
res.status = 200
res.should.be.successful
res.should.be.ok
res.status = 404
res.should.not.be.successful
res.should.be.client_error
res.should.be.not_found
res.status = 501
res.should.not.be.successful
res.should.be.server_error
res.status = 307
res.should.be.redirect
end
specify "should provide access to the HTTP headers" do
res = Rack::Response.new
res["Content-Type"] = "text/yaml"
res.should.include "Content-Type"
res.headers["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.nil
res.location.should.be.nil
end
end

View file

@ -0,0 +1,82 @@
require 'test/spec'
require 'rack/session/cookie'
require 'rack/mock'
require 'rack/response'
context "Rack::Session::Cookie" do
incrementor = lambda { |env|
env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1
Rack::Response.new(env["rack.session"].inspect).to_a
}
specify "creates a new cookie" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
res["Set-Cookie"].should.match("rack.session=")
res.body.should.equal '{"counter"=>1}'
end
specify "loads from a cookie" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>2}'
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>3}'
end
specify "survives broken cookies" do
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
res.body.should.equal '{"counter"=>1}'
end
bigcookie = lambda { |env|
env["rack.session"]["cookie"] = "big" * 3000
Rack::Response.new(env["rack.session"].inspect).to_a
}
specify "barks on too big cookies" do
lambda {
Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
get("/", :fatal => true)
}.should.raise(Rack::MockRequest::FatalWarning)
end
specify "creates a new cookie with 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("/")
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>2}'
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>3}'
end
specify "ignores tampered with session cookies" do
app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
response1 = Rack::MockRequest.new(app).get("/")
_, digest = response1["Set-Cookie"].split("--")
tampered_with_cookie = "hackerman-was-here" + "--" + digest
response2 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" =>
tampered_with_cookie)
# The tampered-with cookie is ignored, so we get back an identical Set-Cookie
response2["Set-Cookie"].should.equal(response1["Set-Cookie"])
end
end

View file

@ -0,0 +1,132 @@
require 'test/spec'
begin
require 'rack/session/memcache'
require 'rack/mock'
require 'rack/response'
require 'thread'
context "Rack::Session::Memcache" do
incrementor = lambda { |env|
env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1
Rack::Response.new(env["rack.session"].inspect).to_a
}
# Keep this first.
specify "startup" do
$pid = fork {
exec "memcached"
}
sleep 1
end
specify "faults on no connection" do
lambda do
Rack::Session::Memcache.new(incrementor, :memcache_server => '')
end.should.raise
end
specify "creates a new cookie" do
cache = Rack::Session::Memcache.new(incrementor)
res = Rack::MockRequest.new(cache).get("/")
res["Set-Cookie"].should.match("rack.session=")
res.body.should.equal '{"counter"=>1}'
end
specify "determines session from a cookie" do
cache = Rack::Session::Memcache.new(incrementor)
res = Rack::MockRequest.new(cache).get("/")
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(cache).get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>2}'
res = Rack::MockRequest.new(cache).get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>3}'
end
specify "survives broken cookies" do
cache = Rack::Session::Memcache.new(incrementor)
res = Rack::MockRequest.new(cache).
get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
res.body.should.equal '{"counter"=>1}'
end
specify "maintains freshness" do
cache = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
res = Rack::MockRequest.new(cache).get('/')
res.body.should.include '"counter"=>1'
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
res["Set-Cookie"].should.equal cookie
res.body.should.include '"counter"=>2'
puts 'Sleeping to expire session' if $DEBUG
sleep 4
res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
res["Set-Cookie"].should.not.equal cookie
res.body.should.include '"counter"=>1'
end
specify "multithread: should cleanly merge sessions" do
cache = Rack::Session::Memcache.new(incrementor)
drop_counter = Rack::Session::Memcache.new(proc do |env|
env['rack.session'].delete 'counter'
env['rack.session']['foo'] = 'bar'
[200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
end)
res = Rack::MockRequest.new(cache).get('/')
res.body.should.equal '{"counter"=>1}'
cookie = res["Set-Cookie"]
sess_id = cookie[/#{cache.key}=([^,;]+)/, 1]
res = Rack::MockRequest.new(cache).get('/', "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>2}'
r = Array.new(rand(7).to_i+2) do |i|
app = proc do |env|
env['rack.session'][i] = Time.now
sleep 2
env['rack.session'] = env['rack.session'].dup
env['rack.session'][i] -= Time.now
incrementor.call(env)
end
Thread.new(cache.context(app)) do |run|
Rack::MockRequest.new(run).
get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
end
end
r.reverse!
r.map! do |t|
p t if $DEBUG
t.join.value
end
r.each do |res|
res['Set-Cookie'].should.equal cookie
res.body.should.include '"counter"=>3'
end
session = cache.pool[sess_id]
session.size.should.be r.size+1
session['counter'].should.be 3
res = Rack::MockRequest.new(drop_counter).get('/', "HTTP_COOKIE" => cookie)
res.body.should.include '"foo"=>"bar"'
session = cache.pool[sess_id]
session.size.should.be r.size+1
session['counter'].should.be.nil?
session['foo'].should.equal 'bar'
end
# Keep this last.
specify "shutdown" do
Process.kill 15, $pid
Process.wait($pid).should.equal $pid
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again."
end

View file

@ -0,0 +1,84 @@
require 'test/spec'
require 'rack/session/pool'
require 'rack/mock'
require 'rack/response'
require 'thread'
context "Rack::Session::Pool" do
incrementor = lambda { |env|
env["rack.session"]["counter"] ||= 0
env["rack.session"]["counter"] += 1
Rack::Response.new(env["rack.session"].inspect).to_a
}
specify "creates a new cookie" do
pool = Rack::Session::Pool.new(incrementor)
res = Rack::MockRequest.new(pool).get("/")
res["Set-Cookie"].should.match("rack.session=")
res.body.should.equal '{"counter"=>1}'
end
specify "determines session from a cookie" do
pool = Rack::Session::Pool.new(incrementor)
res = Rack::MockRequest.new(pool).get("/")
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(pool).get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>2}'
res = Rack::MockRequest.new(pool).get("/", "HTTP_COOKIE" => cookie)
res.body.should.equal '{"counter"=>3}'
end
specify "survives broken cookies" do
pool = Rack::Session::Pool.new(incrementor)
res = Rack::MockRequest.new(pool).
get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
res.body.should.equal '{"counter"=>1}'
end
specify "maintains freshness" do
pool = Rack::Session::Pool.new(incrementor, :expire_after => 3)
res = Rack::MockRequest.new(pool).get('/')
res.body.should.include '"counter"=>1'
cookie = res["Set-Cookie"]
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
res["Set-Cookie"].should.equal cookie
res.body.should.include '"counter"=>2'
sleep 4
res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
res["Set-Cookie"].should.not.equal cookie
res.body.should.include '"counter"=>1'
end
specify "multithread: should merge sessions" do
delta_incrementor = lambda do |env|
# emulate disconjoinment of threading
env['rack.session'] = env['rack.session'].dup
Thread.stop
env['rack.session'][(Time.now.usec*rand).to_i] = true
incrementor.call(env)
end
pool = Rack::Session::Pool.new(incrementor)
res = Rack::MockRequest.new(pool).get('/')
res.body.should.equal '{"counter"=>1}'
cookie = res["Set-Cookie"]
sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
pool = pool.context(delta_incrementor)
r = Array.new(rand(7).to_i+3).
map! do
Thread.new do
Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
end
end.
reverse!.
map!{|t| t.run.join.value }
session = pool.for.pool[sess_id] # for is needed by Utils::Context
session.size.should.be r.size+1 # counter
session['counter'].should.be 2 # meeeh
r.each do |res|
res['Set-Cookie'].should.equal cookie
res.body.should.include '"counter"=>2'
end
end
end

View file

@ -0,0 +1,21 @@
require 'test/spec'
require 'rack/showexceptions'
require 'rack/mock'
context "Rack::ShowExceptions" do
specify "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,72 @@
require 'test/spec'
require 'rack/showstatus'
require 'rack/mock'
context "Rack::ShowStatus" do
specify "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
specify "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
specify "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
specify "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
specify "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

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

View file

@ -0,0 +1,90 @@
require 'test/spec'
begin
require 'rack/handler/thin'
require 'testrequest'
require 'timeout'
context "Rack::Handler::Thin" do
include TestRequest::Helpers
setup do
@app = Rack::Lint.new(TestRequest.new)
Thin::Logging.silent = true
@thread = Thread.new do
Rack::Handler::Thin.run(@app, :Host => @host='0.0.0.0', :Port => @port=9201) do |server|
@server = server
end
end
Thread.pass until @server && @server.running?
end
specify "should respond" do
lambda {
GET("/")
}.should.not.raise
end
specify "should be a Thin" do
GET("/")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /thin/
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal "9201"
response["SERVER_NAME"].should.equal "0.0.0.0"
end
specify "should have rack headers" do
GET("/")
response["rack.version"].should.equal [0,3]
response["rack.multithread"].should.be false
response["rack.multiprocess"].should.be false
response["rack.run_once"].should.be false
end
specify "should have CGI headers on GET" do
GET("/")
response["REQUEST_METHOD"].should.equal "GET"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.equal "/"
response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal ""
GET("/test/foo?quux=1")
response["REQUEST_METHOD"].should.equal "GET"
response["REQUEST_PATH"].should.equal "/test/foo"
response["PATH_INFO"].should.equal "/test/foo"
response["QUERY_STRING"].should.equal "quux=1"
end
specify "should have CGI headers on POST" do
POST("/", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST"
response["REQUEST_PATH"].should.equal "/"
response["QUERY_STRING"].should.equal ""
response["HTTP_X_TEST_HEADER"].should.equal "42"
response["test.postdata"].should.equal "rack-form-data=23"
end
specify "should support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end
specify "should set status" do
GET("/test?secret")
status.should.equal 403
response["rack.url_scheme"].should.equal "http"
end
teardown do
@server.stop!
@thread.kill
end
end
rescue LoadError
$stderr.puts "Skipping Rack::Handler::Thin tests (Thin is required). `gem install thin` and try again."
end

View file

@ -0,0 +1,175 @@
require 'test/spec'
require 'rack/urlmap'
require 'rack/mock'
context "Rack::URLMap" do
specify "dispatches paths correctly" do
app = lambda { |env|
[200, {
'X-ScriptName' => env['SCRIPT_NAME'],
'X-PathInfo' => env['PATH_INFO'],
'Content-Type' => 'text/plain'
}, [""]]
}
map = Rack::URLMap.new({
'http://foo.org/bar' => app,
'/foo' => app,
'/foo/bar' => app
})
res = Rack::MockRequest.new(map).get("/")
res.should.be.not_found
res = Rack::MockRequest.new(map).get("/qux")
res.should.be.not_found
res = Rack::MockRequest.new(map).get("/foo")
res.should.be.ok
res["X-ScriptName"].should.equal "/foo"
res["X-PathInfo"].should.equal ""
res = Rack::MockRequest.new(map).get("/foo/")
res.should.be.ok
res["X-ScriptName"].should.equal "/foo"
res["X-PathInfo"].should.equal "/"
res = Rack::MockRequest.new(map).get("/foo/bar")
res.should.be.ok
res["X-ScriptName"].should.equal "/foo/bar"
res["X-PathInfo"].should.equal ""
res = Rack::MockRequest.new(map).get("/foo/bar/")
res.should.be.ok
res["X-ScriptName"].should.equal "/foo/bar"
res["X-PathInfo"].should.equal "/"
res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh")
res.should.be.ok
res["X-ScriptName"].should.equal "/bleh/foo"
res["X-PathInfo"].should.equal "/quux"
res = Rack::MockRequest.new(map).get("/bar", 'HTTP_HOST' => 'foo.org')
res.should.be.ok
res["X-ScriptName"].should.equal "/bar"
res["X-PathInfo"].should.be.empty
res = Rack::MockRequest.new(map).get("/bar/", 'HTTP_HOST' => 'foo.org')
res.should.be.ok
res["X-ScriptName"].should.equal "/bar"
res["X-PathInfo"].should.equal '/'
end
specify "dispatches hosts correctly" do
map = Rack::URLMap.new("http://foo.org/" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "foo.org",
"X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
}, [""]]},
"http://bar.org/" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "bar.org",
"X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
}, [""]]},
"/" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "default.org",
"X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
}, [""]]}
)
res = Rack::MockRequest.new(map).get("/")
res.should.be.ok
res["X-Position"].should.equal "default.org"
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "bar.org")
res.should.be.ok
res["X-Position"].should.equal "bar.org"
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "foo.org")
res.should.be.ok
res["X-Position"].should.equal "foo.org"
res = Rack::MockRequest.new(map).get("http://foo.org/")
res.should.be.ok
res["X-Position"].should.equal "default.org"
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "example.org")
res.should.be.ok
res["X-Position"].should.equal "default.org"
res = Rack::MockRequest.new(map).get("/",
"HTTP_HOST" => "example.org:9292",
"SERVER_PORT" => "9292")
res.should.be.ok
res["X-Position"].should.equal "default.org"
end
specify "should be nestable" do
map = Rack::URLMap.new("/foo" =>
Rack::URLMap.new("/bar" =>
Rack::URLMap.new("/quux" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "/foo/bar/quux",
"X-PathInfo" => env["PATH_INFO"],
"X-ScriptName" => env["SCRIPT_NAME"],
}, [""]]}
)))
res = Rack::MockRequest.new(map).get("/foo/bar")
res.should.be.not_found
res = Rack::MockRequest.new(map).get("/foo/bar/quux")
res.should.be.ok
res["X-Position"].should.equal "/foo/bar/quux"
res["X-PathInfo"].should.equal ""
res["X-ScriptName"].should.equal "/foo/bar/quux"
end
specify "should route root apps correctly" do
map = Rack::URLMap.new("/" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "root",
"X-PathInfo" => env["PATH_INFO"],
"X-ScriptName" => env["SCRIPT_NAME"]
}, [""]]},
"/foo" => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "foo",
"X-PathInfo" => env["PATH_INFO"],
"X-ScriptName" => env["SCRIPT_NAME"]
}, [""]]}
)
res = Rack::MockRequest.new(map).get("/foo/bar")
res.should.be.ok
res["X-Position"].should.equal "foo"
res["X-PathInfo"].should.equal "/bar"
res["X-ScriptName"].should.equal "/foo"
res = Rack::MockRequest.new(map).get("/foo")
res.should.be.ok
res["X-Position"].should.equal "foo"
res["X-PathInfo"].should.equal ""
res["X-ScriptName"].should.equal "/foo"
res = Rack::MockRequest.new(map).get("/bar")
res.should.be.ok
res["X-Position"].should.equal "root"
res["X-PathInfo"].should.equal "/bar"
res["X-ScriptName"].should.equal ""
res = Rack::MockRequest.new(map).get("")
res.should.be.ok
res["X-Position"].should.equal "root"
res["X-PathInfo"].should.equal "/"
res["X-ScriptName"].should.equal ""
end
end

View file

@ -0,0 +1,176 @@
require 'test/spec'
require 'rack/utils'
require 'rack/lint'
require 'rack/mock'
context "Rack::Utils" do
specify "should escape correctly" do
Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
Rack::Utils.escape("a space").should.equal "a+space"
Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
end
specify "should unescape correctly" do
Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
Rack::Utils.unescape("a+space").should.equal "a space"
Rack::Utils.unescape("a%20space").should.equal "a space"
Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
should.equal "q1!2\"'w$5&7/z8)?\\"
end
specify "should parse query strings correctly" do
Rack::Utils.parse_query("foo=bar").should.equal "foo" => "bar"
Rack::Utils.parse_query("foo=bar&foo=quux").
should.equal "foo" => ["bar", "quux"]
Rack::Utils.parse_query("foo=1&bar=2").
should.equal "foo" => "1", "bar" => "2"
Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
end
specify "should build query strings correctly" do
Rack::Utils.build_query("foo" => "bar").should.equal "foo=bar"
Rack::Utils.build_query("foo" => ["bar", "quux"]).
should.equal "foo=bar&foo=quux"
Rack::Utils.build_query("foo" => "1", "bar" => "2").
should.equal "foo=1&bar=2"
Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?").
should.equal "my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F"
end
specify "should figure out which encodings are acceptable" do
helper = lambda do |a, b|
request = Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
Rack::Utils.select_best_encoding(a, b)
end
helper.call(%w(), [["x", 1]]).should.equal(nil)
helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil)
helper.call(%w(identity), [["*", 0.0]]).should.equal(nil)
helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity")
helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress")
helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip")
helper.call(%w(foo bar identity), []).should.equal("identity")
helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo")
helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar")
helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
end
end
context "Rack::Utils::HeaderHash" do
specify "should retain header case" do
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
h['ETag'] = 'Boo!'
h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
end
specify "should check existence of keys case insensitively" do
h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
h.should.include 'content-md5'
h.should.not.include 'ETag'
end
specify "should merge case-insensitively" do
h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
end
specify "should overwrite case insensitively and assume the new key's case" do
h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
h["foo-bar"] = "bizzle"
h["FOO-BAR"].should.equal "bizzle"
h.length.should.equal 1
h.to_hash.should.equal "foo-bar" => "bizzle"
end
specify "should be converted to real Hash" do
h = Rack::Utils::HeaderHash.new("foo" => "bar")
h.to_hash.should.be.instance_of Hash
end
end
context "Rack::Utils::Context" do
test_app1 = Object.new
def test_app1.context app
Rack::Utils::Context.new self, app do |env|
app.call env
end
end
test_app2 = Object.new
def test_app2.context env; end
test_app3 = Object.new
test_target1 = proc{|e| e.to_s+' world' }
test_target2 = proc{|e| e.to_i+2 }
test_target3 = proc{|e| nil }
test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
test_target5 = Object.new
specify "should perform checks on both arguments" do
lambda { Rack::Utils::Context.new(nil, nil){} }.should.raise
lambda { Rack::Utils::Context.new(test_app1, nil){} }.should.raise
lambda { Rack::Utils::Context.new(nil, test_target1){} }.should.raise
lambda { Rack::Utils::Context.new(test_app1, test_target1){} }.should.not.raise
lambda { Rack::Utils::Context.new(test_app3, test_target1){} }.should.raise
lambda { Rack::Utils::Context.new(test_app1, test_target5){} }.should.raise
lambda { test_app1.context(nil){} }.should.raise
lambda { test_app1.context(test_target1){} }.should.not.raise
lambda { test_app1.context(test_target5){} }.should.raise
end
specify "should set context correctly" do
c1 = Rack::Utils::Context.new(test_app1, test_target1){}
c1.for.should.equal test_app1
c1.app.should.equal test_target1
c2 = Rack::Utils::Context.new(test_app1, test_target2){}
c2.for.should.equal test_app1
c2.app.should.equal test_target2
c3 = Rack::Utils::Context.new(test_app2, test_target3){}
c3.for.should.equal test_app2
c3.app.should.equal test_target3
c4 = Rack::Utils::Context.new(test_app2, test_target4){}
c4.for.should.equal test_app2
c4.app.should.equal test_target4
end
specify "should alter app on recontexting" do
c1 = Rack::Utils::Context.new(test_app1, test_target1){}
c1.for.should.equal test_app1
c1.app.should.equal test_target1
c2 = c1.context(test_target2)
c2.for.should.equal test_app1
c2.app.should.not.equal test_target1
c2.app.should.equal test_target2
c3 = c2.context(test_target3)
c3.for.should.equal test_app1
c3.app.should.not.equal test_target2
c3.app.should.equal test_target3
c4 = c3.context(test_target4)
c4.for.should.equal test_app1
c4.app.should.not.equal test_target3
c4.app.should.equal test_target4
end
specify "should run different apps" do
c1 = test_app1.context(test_target1)
c2 = c1.context test_target2
c3 = c2.context test_target3
c4 = c3.context test_target4
a4 = Rack::Lint.new c4
r1 = c1.call('hello')
r1.should.equal 'hello world'
r2 = c2.call(2)
r2.should.equal 4
r3 = c3.call(:misc_symbol)
r3.should.be.nil
r4 = Rack::MockRequest.new(a4).get('/')
r4.status.should.be 200
end
end

View file

@ -0,0 +1,123 @@
require 'test/spec'
require 'rack/handler/webrick'
require 'rack/lint'
require 'rack/response'
require 'testrequest'
Thread.abort_on_exception = true
context "Rack::Handler::WEBrick" do
include TestRequest::Helpers
setup do
@server = WEBrick::HTTPServer.new(:Host => @host='0.0.0.0',
:Port => @port=9202,
:Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
:AccessLog => [])
@server.mount "/test", Rack::Handler::WEBrick,
Rack::Lint.new(TestRequest.new)
Thread.new { @server.start }
trap(:INT) { @server.shutdown }
end
specify "should respond" do
lambda {
GET("/test")
}.should.not.raise
end
specify "should be a WEBrick" do
GET("/test")
status.should.be 200
response["SERVER_SOFTWARE"].should =~ /WEBrick/
response["HTTP_VERSION"].should.equal "HTTP/1.1"
response["SERVER_PROTOCOL"].should.equal "HTTP/1.1"
response["SERVER_PORT"].should.equal "9202"
response["SERVER_NAME"].should.equal "0.0.0.0"
end
specify "should have rack headers" do
GET("/test")
response["rack.version"].should.equal [0,1]
response["rack.multithread"].should.be true
response["rack.multiprocess"].should.be false
response["rack.run_once"].should.be false
end
specify "should have CGI headers on GET" do
GET("/test")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.be.nil
response["QUERY_STRING"].should.equal ""
response["test.postdata"].should.equal ""
GET("/test/foo?quux=1")
response["REQUEST_METHOD"].should.equal "GET"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/"
response["PATH_INFO"].should.equal "/foo"
response["QUERY_STRING"].should.equal "quux=1"
end
specify "should have CGI headers on POST" do
POST("/test", {"rack-form-data" => "23"}, {'X-test-header' => '42'})
status.should.equal 200
response["REQUEST_METHOD"].should.equal "POST"
response["SCRIPT_NAME"].should.equal "/test"
response["REQUEST_PATH"].should.equal "/"
response["QUERY_STRING"].should.equal ""
response["HTTP_X_TEST_HEADER"].should.equal "42"
response["test.postdata"].should.equal "rack-form-data=23"
end
specify "should support HTTP auth" do
GET("/test", {:user => "ruth", :passwd => "secret"})
response["HTTP_AUTHORIZATION"].should.equal "Basic cnV0aDpzZWNyZXQ="
end
specify "should set status" do
GET("/test?secret")
status.should.equal 403
response["rack.url_scheme"].should.equal "http"
end
specify "should correctly set cookies" do
@server.mount "/cookie-test", Rack::Handler::WEBrick,
Rack::Lint.new(lambda { |req|
res = Rack::Response.new
res.set_cookie "one", "1"
res.set_cookie "two", "2"
res.finish
})
Net::HTTP.start(@host, @port) { |http|
res = http.get("/cookie-test")
res.code.to_i.should.equal 200
res.get_fields("set-cookie").should.equal ["one=1", "two=2"]
}
end
specify "should provide a .run" do
block_ran = false
catch(:done) {
Rack::Handler::WEBrick.run(lambda {},
{:Port => 9210,
:Logger => WEBrick::Log.new(nil, WEBrick::BasicLog::WARN),
:AccessLog => []}) { |server|
block_ran = true
server.should.be.kind_of WEBrick::HTTPServer
@s = server
throw :done
}
}
block_ran.should.be true
@s.shutdown
end
teardown do
@server.shutdown
end
end

57
vendor/plugins/rack/test/testrequest.rb vendored Normal file
View file

@ -0,0 +1,57 @@
require 'yaml'
require 'net/http'
class TestRequest
def call(env)
status = env["QUERY_STRING"] =~ /secret/ ? 403 : 200
env["test.postdata"] = env["rack.input"].read
body = env.to_yaml
size = body.respond_to?(:bytesize) ? body.bytesize : body.size
[status, {"Content-Type" => "text/yaml", "Content-Length" => size.to_s}, [body]]
end
module Helpers
attr_reader :status, :response
def GET(path, header={})
Net::HTTP.start(@host, @port) { |http|
user = header.delete(:user)
passwd = header.delete(:passwd)
get = Net::HTTP::Get.new(path, header)
get.basic_auth user, passwd if user && passwd
http.request(get) { |response|
@status = response.code.to_i
@response = YAML.load(response.body)
}
}
end
def POST(path, formdata={}, header={})
Net::HTTP.start(@host, @port) { |http|
user = header.delete(:user)
passwd = header.delete(:passwd)
post = Net::HTTP::Post.new(path, header)
post.form_data = formdata
post.basic_auth user, passwd if user && passwd
http.request(post) { |response|
@status = response.code.to_i
@response = YAML.load(response.body)
}
}
end
end
end
class StreamingRequest
def self.call(env)
[200, {"Content-Type" => "text/plain"}, new]
end
def each
yield "hello there!\n"
sleep 5
yield "that is all.\n"
end
end