123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515 |
- require 'stringio'
- require 'rack/lint'
- require 'rack/mock'
- describe Rack::Lint do
- def env(*args)
- Rack::MockRequest.env_for("/", *args)
- end
- should "pass valid request" do
- lambda {
- Rack::Lint.new(lambda { |env|
- [200, {"Content-type" => "test/plain", "Content-length" => "3"}, ["foo"]]
- }).call(env({}))
- }.should.not.raise
- end
- should "notice fatal errors" do
- lambda { Rack::Lint.new(nil).call }.should.raise(Rack::Lint::LintError).
- message.should.match(/No env given/)
- end
- should "notice 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("rack.session" => []))
- }.should.raise(Rack::Lint::LintError).
- message.should.equal("session [] must respond to store and []=")
- lambda {
- Rack::Lint.new(nil).call(env("rack.logger" => []))
- }.should.raise(Rack::Lint::LintError).
- message.should.equal("logger [] must respond to info")
- 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
- should "notice 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/)
- lambda {
- input = Object.new
- def input.binmode?
- false
- end
- Rack::Lint.new(nil).call(env("rack.input" => input))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/is not opened in binary mode/)
- lambda {
- input = Object.new
- def input.external_encoding
- result = Object.new
- def result.name
- "US-ASCII"
- end
- result
- end
- Rack::Lint.new(nil).call(env("rack.input" => input))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/does not have ASCII-8BIT as its external encoding/)
- end
- should "notice 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
- should "notice 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
- should "notice 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("a header value must be a String, but the value of 'Foo' is a Object")
- lambda {
- Rack::Lint.new(lambda { |env|
- [200, {"Foo" => [1, 2, 3]}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.equal("a header value must be a String, but the value of 'Foo' is a Array")
- lambda {
- Rack::Lint.new(lambda { |env|
- [200, {"Foo-Bar" => "text\000plain"}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/invalid header/)
- # line ends (010) should be allowed in header values.
- lambda {
- Rack::Lint.new(lambda { |env|
- [200, {"Foo-Bar" => "one\ntwo\nthree", "Content-Length" => "0", "Content-Type" => "text/plain" }, []]
- }).call(env({}))
- }.should.not.raise(Rack::Lint::LintError)
- end
- should "notice 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, 205, 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
- should "notice content-length errors" do
- [100, 101, 204, 205, 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" => "1"}, []]
- }).call(env({}))[2].each { }
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/Content-Length header was 1, but should be 0/)
- end
- should "notice body errors" do
- lambda {
- body = Rack::Lint.new(lambda { |env|
- [200, {"Content-type" => "text/plain","Content-length" => "3"}, [1,2,3]]
- }).call(env({}))[2]
- body.each { |part| }
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/yielded non-string/)
- end
- should "notice 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(1, 2, 3)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/read called with too many 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 and non-nil length/)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(-1)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/read called with a negative length/)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(nil, nil)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/read called with non-String buffer/)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(nil, 1)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/read called with non-String buffer/)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].rewind(0)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({}))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/rewind called with arguments/)
- weirdio = Object.new
- class << weirdio
- def gets
- 42
- end
- def read
- 23
- end
- def each
- yield 23
- yield 42
- end
- def rewind
- raise Errno::ESPIPE, "Errno::ESPIPE"
- end
- end
- eof_weirdio = Object.new
- class << eof_weirdio
- def gets
- nil
- end
- def read(*args)
- nil
- end
- def each
- end
- def rewind
- 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 nil or a String/)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env("rack.input" => eof_weirdio))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/read\(nil\) returned nil on EOF/)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].rewind
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env("rack.input" => weirdio))
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/rewind raised Errno::ESPIPE/)
- 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
- should "notice 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
- should "notice 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"}))[2].each { }
- }.should.raise(Rack::Lint::LintError).
- message.should.match(/body was given for HEAD/)
- end
- should "pass valid read calls" do
- hello_str = "hello world"
- hello_str.force_encoding("ASCII-8BIT") if hello_str.respond_to? :force_encoding
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({"rack.input" => StringIO.new(hello_str)}))
- }.should.not.raise(Rack::Lint::LintError)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(0)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({"rack.input" => StringIO.new(hello_str)}))
- }.should.not.raise(Rack::Lint::LintError)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(1)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({"rack.input" => StringIO.new(hello_str)}))
- }.should.not.raise(Rack::Lint::LintError)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(nil)
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({"rack.input" => StringIO.new(hello_str)}))
- }.should.not.raise(Rack::Lint::LintError)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(nil, '')
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({"rack.input" => StringIO.new(hello_str)}))
- }.should.not.raise(Rack::Lint::LintError)
- lambda {
- Rack::Lint.new(lambda { |env|
- env["rack.input"].read(1, '')
- [201, {"Content-type" => "text/plain", "Content-length" => "0"}, []]
- }).call(env({"rack.input" => StringIO.new(hello_str)}))
- }.should.not.raise(Rack::Lint::LintError)
- end
- end
- describe "Rack::Lint::InputWrapper" do
- should "delegate :rewind to underlying IO object" do
- io = StringIO.new("123")
- wrapper = Rack::Lint::InputWrapper.new(io)
- wrapper.read.should.equal "123"
- wrapper.read.should.equal ""
- wrapper.rewind
- wrapper.read.should.equal "123"
- end
- end
|