instiki/vendor/plugins/rack/test/spec_rack_lint.rb

381 lines
12 KiB
Ruby
Raw Normal View History

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