Decouple specs from global state to isolated tests

This commit is contained in:
Johannes Müller 2017-07-18 00:42:05 +02:00 committed by sdogruyol
parent 29b18c927c
commit 2e42b3f48c
19 changed files with 336 additions and 304 deletions

View file

@ -1,81 +1,62 @@
require "./spec_helper"
describe "Context" do
context "headers" do
it "sets content type" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
env.response.content_type = "application/json"
"Hello"
end
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.headers["Content-Type"].should eq("application/json")
end
it "parses headers" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
name = env.request.headers["name"]
"Hello #{name}"
end
headers = HTTP::Headers.new
headers["name"] = "kemal"
request = HTTP::Request.new("GET", "/", headers)
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should eq "Hello kemal"
end
it "sets response headers" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
env.response.headers.add "Accept-Language", "tr"
end
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.headers["Accept-Language"].should eq "tr"
end
end
context "storage" do
it "can store primitive types" do
before_get "/" do |env|
env.set "before_get", "Kemal"
env.set "before_get_int", 123
env.set "before_get_float", 3.5
end
get "/" do |env|
{
before_get: env.get("before_get"),
before_get_int: env.get("before_get_int"),
before_get_float: env.get("before_get_float"),
}
end
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
Kemal::FilterHandler::INSTANCE.call(context)
Kemal::RouteHandler::INSTANCE.call(context)
context.get("before_get").should eq "Kemal"
context.get("before_get_int").should eq 123
context.get("before_get_float").should eq 3.5
end
it "can store custom types" do
before_get "/" do |env|
it "can store variables" do
app = Kemal::Base.new
app.before_get "/" do |env|
t = TestContextStorageType.new
t.id = 32
a = AnotherContextStorageType.new
env.set "key", "value"
env.set "before_get", "Kemal"
env.set "before_get_int", 123
env.set "before_get_context_test", t
env.set "another_context_test", a
env.set "before_get_float", 3.5
end
get "/" do |env|
app.get "/" do |env|
env.set "key", "value"
{
key: env.get("key"),
before_get: env.get("before_get"),
before_get_int: env.get("before_get_int"),
before_get_float: env.get("before_get_float"),
before_get_context_test: env.get("before_get_context_test"),
another_context_test: env.get("another_context_test"),
}
end
@ -83,20 +64,9 @@ describe "Context" do
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
Kemal::FilterHandler::INSTANCE.call(context)
Kemal::RouteHandler::INSTANCE.call(context)
context.get("before_get_context_test").as(TestContextStorageType).id.should eq 32
context.get("another_context_test").as(AnotherContextStorageType).name.should eq "kemal-context"
end
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
Kemal.application.filter_handler.call(context)
Kemal.application.route_handler.call(context)
context.app = app
app.filter_handler.call(context)
app.route_handler.call(context)
context.store["key"].should eq "value"
context.store["before_get"].should eq "Kemal"
context.store["before_get_int"].should eq 123

58
spec/dsl_helper.cr Normal file
View file

@ -0,0 +1,58 @@
require "./spec_helper"
require "../src/kemal/dsl"
include Kemal
class CustomLogHandler < Kemal::BaseLogHandler
def call(env)
call_next env
end
def write(message)
end
end
class TestContextStorageType
property id
@id = 1
def to_s
@id
end
end
class AnotherContextStorageType
property name
@name = "kemal-context"
end
add_context_storage_type(TestContextStorageType)
add_context_storage_type(AnotherContextStorageType)
def create_request_and_return_io(handler, request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
handler.call(context)
response.close
io.rewind
io
end
def call_request_on_app(request)
call_request_on_app(Kemal.application, request)
end
def build_main_handler
build_main_handler(Kemal.application)
end
Spec.before_each do
config = Kemal.config
config.env = "development"
end
Spec.after_each do
Kemal.application.clear
end

View file

@ -1,18 +1,13 @@
require "./spec_helper"
private INSTANCE = Kemal::ExceptionHandler.new
require "./dsl_helper"
describe "Kemal::ExceptionHandler" do
it "renders 404 on route not found" do
get "/" do
"Hello"
end
request = HTTP::Request.new("GET", "/asd")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
INSTANCE.call(context)
subject = Kemal::ExceptionHandler.new(Kemal::Base.new)
subject.call(context)
response.close
io.rewind
response = HTTP::Client::Response.from_io(io, decompress: false)
@ -20,19 +15,21 @@ describe "Kemal::ExceptionHandler" do
end
it "renders custom error" do
error 403 do
"403 error"
end
get "/" do |env|
env.response.status_code = 403
end
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
INSTANCE.next = Kemal::RouteHandler.new
INSTANCE.call(context)
app = Kemal::Base.new
app.error 403 do
"403 error"
end
app.get "/" do |env|
env.response.status_code = 403
end
context.app = app
subject = Kemal::ExceptionHandler.new(app)
subject.next = Kemal::RouteHandler.new
subject.call(context)
response.close
io.rewind
response = HTTP::Client::Response.from_io(io, decompress: false)
@ -42,19 +39,21 @@ describe "Kemal::ExceptionHandler" do
end
it "renders custom 500 error" do
error 500 do
"Something happened"
end
get "/" do |env|
env.response.status_code = 500
end
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
INSTANCE.next = Kemal::RouteHandler.new
INSTANCE.call(context)
app = Kemal::Base.new
app.error 500 do |env|
"Something happened"
end
app.get "/" do |env|
env.response.status_code = 500
end
context.app = app
subject = Kemal::ExceptionHandler.new(app)
subject.next = Kemal::RouteHandler.new
subject.call(context)
response.close
io.rewind
response = HTTP::Client::Response.from_io(io, decompress: false)
@ -64,20 +63,22 @@ describe "Kemal::ExceptionHandler" do
end
it "keeps the specified error Content-Type" do
error 500 do
"Something happened"
end
get "/" do |env|
env.response.content_type = "application/json"
env.response.status_code = 500
end
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
INSTANCE.next = Kemal::RouteHandler.new
INSTANCE.call(context)
app = Kemal::Base.new
app.error 500 do |env|
"Something happened"
end
app.get "/" do |env|
env.response.content_type = "application/json"
env.response.status_code = 500
end
context.app = app
subject = Kemal::ExceptionHandler.new(app)
subject.next = Kemal::RouteHandler.new
subject.call(context)
response.close
io.rewind
response = HTTP::Client::Response.from_io(io, decompress: false)
@ -87,20 +88,22 @@ describe "Kemal::ExceptionHandler" do
end
it "renders custom error with env and error" do
error 500 do |_, err|
err.message
end
get "/" do |env|
env.response.content_type = "application/json"
env.response.status_code = 500
end
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
INSTANCE.next = Kemal::RouteHandler.new
INSTANCE.call(context)
app = Kemal::Base.new
app.error 500 do |env, err|
err.message
end
app.get "/" do |env|
env.response.content_type = "application/json"
env.response.status_code = 500
end
context.app = app
subject = Kemal::ExceptionHandler.new(Kemal::Base.new)
subject.next = Kemal::RouteHandler.new
subject.call(context)
response.close
io.rewind
response = HTTP::Client::Response.from_io(io, decompress: false)

View file

@ -78,81 +78,88 @@ describe "Handler" do
filter_middleware._add_route_filter("GET", "/", :before) do |env|
env.response << " so"
end
Kemal.application.add_filter_handler filter_middleware
app = Kemal::Base.new
app.add_filter_handler filter_middleware
add_handler CustomTestHandler.new
app.add_handler CustomTestHandler.new
get "/" do
app.get "/" do |env|
" Great"
end
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.status_code.should eq(200)
client_response.body.should eq("Kemal is so Great")
end
it "runs specified only_routes in middleware" do
get "/only" do
app = Kemal::Base.new
app.get "/only" do |env|
"Get"
end
add_handler OnlyHandler.new
app.add_handler OnlyHandler.new
request = HTTP::Request.new("GET", "/only")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should eq "OnlyGet"
end
it "doesn't run specified exclude_routes in middleware" do
get "/" do
app = Kemal::Base.new
app.get "/" do |env|
"Get"
end
get "/exclude" do
app.get "/exclude" do
"Exclude"
end
add_handler ExcludeHandler.new
app.add_handler ExcludeHandler.new
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should eq "ExcludeGet"
end
it "runs specified only_routes with method in middleware" do
post "/only" do
app = Kemal::Base.new
app.post "/only" do
"Post"
end
get "/only" do
app.get "/only" do
"Get"
end
add_handler PostOnlyHandler.new
app.add_handler PostOnlyHandler.new
request = HTTP::Request.new("POST", "/only")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should eq "OnlyPost"
end
it "doesn't run specified exclude_routes with method in middleware" do
post "/exclude" do
app = Kemal::Base.new
app.post "/exclude" do
"Post"
end
post "/only" do
app.post "/only" do
"Post"
end
add_handler PostOnlyHandler.new
add_handler PostExcludeHandler.new
app.add_handler PostOnlyHandler.new
app.add_handler PostExcludeHandler.new
request = HTTP::Request.new("POST", "/only")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should eq "OnlyExcludePost"
end
it "adds a handler at given position" do
post_handler = PostOnlyHandler.new
add_handler post_handler, 1
Kemal.application.setup
Kemal.application.handlers[1].should eq post_handler
app = Kemal::Base.new
app.add_handler post_handler, 1
app.setup
app.handlers[1].should eq post_handler
end
it "assigns custom handlers" do
post_only_handler = PostOnlyHandler.new
post_exclude_handler = PostExcludeHandler.new
Kemal.application.handlers = [post_only_handler, post_exclude_handler]
Kemal.application.handlers.should eq [post_only_handler, post_exclude_handler]
app = Kemal::Base.new
app.handlers = [post_only_handler, post_exclude_handler]
app.handlers.should eq [post_only_handler, post_exclude_handler]
end
it "is able to use %w in macros" do

View file

@ -10,9 +10,10 @@ describe "Macros" do
describe "#add_handler" do
it "adds a custom handler" do
add_handler CustomTestHandler.new
Kemal.application.setup
Kemal.application.handlers.size.should eq 8
app = Kemal::Application.new
app.add_handler CustomTestHandler.new
app.setup
app.handlers.size.should eq 8
end
end
@ -23,7 +24,6 @@ describe "Macros" do
end
it "sets a custom logger" do
config = Kemal.config
logger CustomLogHandler.new
Kemal.application.logger.should be_a(CustomLogHandler)
end
@ -31,32 +31,34 @@ describe "Macros" do
describe "#halt" do
it "can break block with halt macro" do
get "/non-breaking" do
app = Kemal::Base.new
app.get "/non-breaking" do |env|
"hello"
"world"
end
request = HTTP::Request.new("GET", "/non-breaking")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.status_code.should eq(200)
client_response.body.should eq("world")
get "/breaking" do |env|
app.get "/breaking" do |env|
halt env, 404, "hello"
"world"
end
request = HTTP::Request.new("GET", "/breaking")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.status_code.should eq(404)
client_response.body.should eq("hello")
end
it "can break block with halt macro using default values" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
halt env
"world"
end
request = HTTP::Request.new("GET", "/")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.status_code.should eq(200)
client_response.body.should eq("")
end
@ -64,7 +66,8 @@ describe "Macros" do
describe "#headers" do
it "can add headers" do
get "/headers" do |env|
app = Kemal::Base.new
app.get "/headers" do |env|
env.response.headers.add "Content-Type", "image/png"
headers env, {
"Access-Control-Allow-Origin" => "*",
@ -72,7 +75,7 @@ describe "Macros" do
}
end
request = HTTP::Request.new("GET", "/headers")
response = call_request_on_app(request)
response = call_request_on_app(app, request)
response.headers["Access-Control-Allow-Origin"].should eq("*")
response.headers["Content-Type"].should eq("text/plain")
end
@ -80,36 +83,39 @@ describe "Macros" do
describe "#send_file" do
it "sends file with given path and default mime-type" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
send_file env, "./spec/asset/hello.ecr"
end
request = HTTP::Request.new("GET", "/")
response = call_request_on_app(request)
response = call_request_on_app(app, request)
response.status_code.should eq(200)
response.headers["Content-Type"].should eq("application/octet-stream")
response.headers["Content-Length"].should eq("18")
end
it "sends file with given path and given mime-type" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
send_file env, "./spec/asset/hello.ecr", "image/jpeg"
end
request = HTTP::Request.new("GET", "/")
response = call_request_on_app(request)
response = call_request_on_app(app, request)
response.status_code.should eq(200)
response.headers["Content-Type"].should eq("image/jpeg")
response.headers["Content-Length"].should eq("18")
end
it "sends file with binary stream" do
get "/" do |env|
app = Kemal::Base.new
app.get "/" do |env|
send_file env, "Serdar".to_slice
end
request = HTTP::Request.new("GET", "/")
response = call_request_on_app(request)
response = call_request_on_app(app, request)
response.status_code.should eq(200)
response.headers["Content-Type"].should eq("application/octet-stream")
response.headers["Content-Length"].should eq("6")

View file

@ -1,4 +1,4 @@
require "./spec_helper"
require "./dsl_helper"
describe "ParamParser" do
it "parses query params" do

View file

@ -1,4 +1,4 @@
require "./spec_helper"
require "./dsl_helper"
describe "Kemal::RouteHandler" do
it "routes" do

View file

@ -1,4 +1,4 @@
require "./spec_helper"
require "./dsl_helper"
describe "Route" do
describe "match?" do

View file

@ -1,4 +1,4 @@
require "./spec_helper"
require "./dsl_helper"
private def run(code)
code = <<-CR

View file

@ -1,89 +1,24 @@
require "spec"
require "../src/**"
require "../src/kemal/base"
require "../src/kemal/dsl"
require "../src/kemal"
include Kemal
class CustomLogHandler < Kemal::BaseLogHandler
def call(env)
call_next env
end
def write(message)
end
end
class TestContextStorageType
property id
@id = 1
def to_s
@id
end
end
class AnotherContextStorageType
property name
@name = "kemal-context"
end
add_context_storage_type(TestContextStorageType)
add_context_storage_type(AnotherContextStorageType)
def create_request_and_return_io_and_context(handler, request)
def call_request_on_app(app, request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
handler.call(context)
response.close
io.rewind
{io, context}
end
def create_ws_request_and_return_io_and_context(handler, request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
begin
handler.call context
rescue IO::Error
# Raises because the IO::Memory is empty
end
io.rewind
{io, context}
end
def call_request_on_app(request)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
main_handler = build_main_handler
main_handler = build_main_handler(app)
main_handler.call context
response.close
io.rewind
HTTP::Client::Response.from_io(io, decompress: false)
end
def build_main_handler
Kemal.application.setup
main_handler = Kemal.application.handlers.first
def build_main_handler(app)
app.setup
main_handler = app.handlers.first
current_handler = main_handler
Kemal.application.handlers.each_with_index do |handler, index|
app.handlers.each_with_index do |handler, index|
current_handler.next = handler
current_handler = handler
end
main_handler
end
Spec.before_each do
config = Kemal.config
config.env = "development"
config.logging = false
end
Spec.after_each do
Kemal.application.clear
end

View file

@ -1,17 +1,22 @@
require "./spec_helper"
private def handle(request, fallthrough = true)
private def handle(request, config = default_config, fallthrough = true)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
handler = Kemal::StaticFileHandler.new "#{__DIR__}/static", fallthrough
handler = Kemal::StaticFileHandler.new config, fallthrough
handler.call context
response.close
io.rewind
HTTP::Client::Response.from_io(io)
end
private def default_config
Kemal::Config.new.tap do |config|
config.public_folder = "#{__DIR__}/static"
end
end
describe Kemal::StaticFileHandler do
file = File.open "#{__DIR__}/static/dir/test.txt"
file_size = file.size
@ -37,38 +42,43 @@ describe Kemal::StaticFileHandler do
end
it "should not list directory's entries" do
serve_static({"gzip" => true, "dir_listing" => false})
response = handle HTTP::Request.new("GET", "/dir/")
config = default_config
config.serve_static = {"gzip" => true, "dir_listing" => false}
response = handle HTTP::Request.new("GET", "/dir/"), config
response.status_code.should eq(404)
end
it "should list directory's entries when config is set" do
serve_static({"gzip" => true, "dir_listing" => true})
response = handle HTTP::Request.new("GET", "/dir/")
config = default_config
config.serve_static = {"gzip" => true, "dir_listing" => true}
response = handle HTTP::Request.new("GET", "/dir/"), config
response.status_code.should eq(200)
response.body.should match(/test.txt/)
end
it "should gzip a file if config is true, headers accept gzip and file is > 880 bytes" do
serve_static({"gzip" => true, "dir_listing" => true})
config = default_config
config.serve_static = {"gzip" => true, "dir_listing" => true}
headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"}
response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers)
response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers), config
response.status_code.should eq(200)
response.headers["Content-Encoding"].should eq "gzip"
end
it "should not gzip a file if config is true, headers accept gzip and file is < 880 bytes" do
serve_static({"gzip" => true, "dir_listing" => true})
config = default_config
config.serve_static = {"gzip" => true, "dir_listing" => true}
headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"}
response = handle HTTP::Request.new("GET", "/dir/test.txt", headers)
response = handle HTTP::Request.new("GET", "/dir/test.txt", headers), config
response.status_code.should eq(200)
response.headers["Content-Encoding"]?.should be_nil
end
it "should not gzip a file if config is false, headers accept gzip and file is > 880 bytes" do
serve_static({"gzip" => false, "dir_listing" => true})
config = default_config
config.serve_static = {"gzip" => false, "dir_listing" => true}
headers = HTTP::Headers{"Accept-Encoding" => "gzip, deflate, sdch, br"}
response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers)
response = handle HTTP::Request.new("GET", "/dir/bigger.txt", headers), config
response.status_code.should eq(200)
response.headers["Content-Encoding"]?.should be_nil
end
@ -97,7 +107,7 @@ describe Kemal::StaticFileHandler do
%w(POST PUT DELETE).each do |method|
response = handle HTTP::Request.new(method, "/dir/test.txt")
response.status_code.should eq(404)
response = handle HTTP::Request.new(method, "/dir/test.txt"), false
response = handle HTTP::Request.new(method, "/dir/test.txt"), fallthrough: false
response.status_code.should eq(405)
response.headers["Allow"].should eq("GET, HEAD")
end
@ -133,22 +143,21 @@ describe Kemal::StaticFileHandler do
end
it "should handle setting custom headers" do
headers = Proc(HTTP::Server::Response, String, File::Info, Void).new do |response, path, stat|
config = default_config
config.static_headers = Proc(HTTP::Server::Response, String, File::Info, Void).new do |response, path, stat|
if path =~ /\.html$/
response.headers.add("Access-Control-Allow-Origin", "*")
end
response.headers.add("Content-Size", stat.size.to_s)
end
static_headers(&headers)
response = handle HTTP::Request.new("GET", "/dir/test.txt")
response = handle HTTP::Request.new("GET", "/dir/test.txt"), config
response.headers.has_key?("Access-Control-Allow-Origin").should be_false
response.headers["Content-Size"].should eq(
File.info("#{__DIR__}/static/dir/test.txt").size.to_s
)
response = handle HTTP::Request.new("GET", "/dir/index.html")
response = handle HTTP::Request.new("GET", "/dir/index.html"), config
response.headers["Access-Control-Allow-Origin"].should eq("*")
end
end

View file

@ -1,4 +1,4 @@
require "./spec_helper"
require "./dsl_helper"
macro render_with_base_and_layout(filename)
render "spec/asset/#{{{filename}}}", "spec/asset/layout.ecr"
@ -6,56 +6,61 @@ end
describe "Views" do
it "renders file" do
get "/view/:name" do |env|
app = Kemal::Base.new
app.get "/view/:name" do |env|
name = env.params.url["name"]
render "spec/asset/hello.ecr"
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should contain("Hello world")
end
it "renders file with dynamic variables" do
get "/view/:name" do |env|
app = Kemal::Base.new
app.get "/view/:name" do |env|
name = env.params.url["name"]
render_with_base_and_layout "hello.ecr"
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should contain("Hello world")
end
it "renders layout" do
get "/view/:name" do |env|
app = Kemal::Base.new
app.get "/view/:name" do |env|
name = env.params.url["name"]
render "spec/asset/hello.ecr", "spec/asset/layout.ecr"
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should contain("<html>Hello world")
end
it "renders layout with variables" do
get "/view/:name" do |env|
app = Kemal::Base.new
app.get "/view/:name" do |env|
name = env.params.url["name"]
var1 = "serdar"
var2 = "kemal"
render "spec/asset/hello_with_content_for.ecr", "spec/asset/layout_with_yield_and_vars.ecr"
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should contain("Hello world")
client_response.body.should contain("serdar")
client_response.body.should contain("kemal")
end
it "renders layout with content_for" do
get "/view/:name" do |env|
app = Kemal::Base.new
app.get "/view/:name" do |env|
name = env.params.url["name"]
render "spec/asset/hello_with_content_for.ecr", "spec/asset/layout_with_yield.ecr"
end
request = HTTP::Request.new("GET", "/view/world")
client_response = call_request_on_app(request)
client_response = call_request_on_app(app, request)
client_response.body.should contain("Hello world")
client_response.body.should contain("<h1>Hello from otherside</h1>")
end

View file

@ -1,10 +1,24 @@
require "./spec_helper"
private def create_ws_request_and_return_io(handler, request, app)
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = app
begin
handler.call context
rescue IO::Error
# Raises because the IO::Memory is empty
end
io
end
describe "Kemal::WebSocketHandler" do
it "doesn't match on wrong route" do
app = Kemal::Base.new
handler = Kemal::WebSocketHandler.new
handler.next = Kemal::RouteHandler.new
ws "/" { }
app.ws "/" { }
headers = HTTP::Headers{
"Upgrade" => "websocket",
"Connection" => "Upgrade",
@ -14,7 +28,7 @@ describe "Kemal::WebSocketHandler" do
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
context.app = app
expect_raises(Kemal::Exceptions::RouteNotFound) do
handler.call context
@ -22,9 +36,10 @@ describe "Kemal::WebSocketHandler" do
end
it "matches on given route" do
app = Kemal::Base.new
handler = Kemal::WebSocketHandler.new
ws "/" { |socket, context| socket.send("Match") }
ws "/no_match" { |socket, context| socket.send "No Match" }
app.ws "/" { |socket, context| socket.send("Match") }
app.ws "/no_match" { |socket, context| socket.send "No Match" }
headers = HTTP::Headers{
"Upgrade" => "websocket",
"Connection" => "Upgrade",
@ -33,13 +48,14 @@ describe "Kemal::WebSocketHandler" do
}
request = HTTP::Request.new("GET", "/", headers)
io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0]
io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n\x81\u0005Match")
io_with_context = create_ws_request_and_return_io(handler, request, app)
io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n\x81\u0005Match")
end
it "fetches named url parameters" do
app = Kemal::Base.new
handler = Kemal::WebSocketHandler.new
ws "/:id" { |s, c| c.params.url["id"] }
app.ws "/:id" { |s, c| c.params.url["id"] }
headers = HTTP::Headers{
"Upgrade" => "websocket",
"Connection" => "Upgrade",
@ -47,20 +63,21 @@ describe "Kemal::WebSocketHandler" do
"Sec-WebSocket-Version" => "13",
}
request = HTTP::Request.new("GET", "/1234", headers)
io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0]
io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n")
io_with_context = create_ws_request_and_return_io(handler, request, app)
io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n")
end
it "matches correct verb" do
app = Kemal::Base.new
handler = Kemal::WebSocketHandler.new
handler.next = Kemal::RouteHandler.new
ws "/" { }
get "/" { "get" }
app.ws "/" { }
app.get "/" { "get" }
request = HTTP::Request.new("GET", "/")
io = IO::Memory.new
response = HTTP::Server::Response.new(io)
context = HTTP::Server::Context.new(request, response)
context.app = Kemal.application
context.app = app
handler.call(context)
response.close
io.rewind

View file

@ -99,4 +99,8 @@ class Kemal::Base
raise "Kemal is already stopped."
end
end
def log(message)
logger.write "#{message}\n"
end
end

View file

@ -71,7 +71,7 @@ class Kemal::Base
private def setup_error_handler
if @config.always_rescue?
error_handler = @error_handler ||= Kemal::ExceptionHandler.new
error_handler = @error_handler ||= Kemal::ExceptionHandler.new(self)
@handlers.insert(@handler_position, error_handler)
@handler_position += 1
end
@ -79,7 +79,7 @@ class Kemal::Base
private def setup_static_file_handler
if @config.serve_static.is_a?(Hash)
@handlers.insert(@handler_position, Kemal::StaticFileHandler.new(@config.public_folder))
@handlers.insert(@handler_position, Kemal::StaticFileHandler.new(@config))
@handler_position += 1
end
end

View file

@ -57,6 +57,10 @@ module Kemal
def extra_options(&@extra_options : OptionParser ->)
end
def serve_static?(key)
(h = @serve_static).is_a?(Hash) && h[key]? == true
end
# Create a config with default values
def self.default
new

View file

@ -3,6 +3,13 @@ module Kemal
class ExceptionHandler
include HTTP::Handler
getter app : Kemal::Base
def initialize(@app)
end
delegate log, to: app
def call(context : HTTP::Server::Context)
begin
call_next(context)
@ -14,7 +21,7 @@ module Kemal
log("Exception: #{ex.inspect_with_backtrace}")
return call_exception_with_status_code(context, ex, 500) if context.app.error_handlers.has_key?(500)
verbosity = context.app.config.env == "production" ? false : true
return render_500(context, ex.inspect_with_backtrace, verbosity)
return app.render_500(context, ex.inspect_with_backtrace, verbosity)
end
end

View file

@ -1,8 +1,5 @@
module Kemal::FileHelpers
def log(message)
logger.write "#{message}\n"
end
extend self
# Send a file with given path and base the mime-type on the file extension
# or default `application/octet-stream` mime_type.
#
@ -15,8 +12,7 @@ module Kemal::FileHelpers
# ```
# send_file env, "./path/to/file", "image/jpeg"
# ```
def send_file(env : HTTP::Server::Context, path : String, mime_type : String? = nil)
config = env.app.config
def send_file(env : HTTP::Server::Context, path : String, config : Kemal::Config, mime_type : String? = nil)
file_path = File.expand_path(path, Dir.current)
mime_type ||= Kemal::Utils.mime_type(file_path)
env.response.content_type = mime_type
@ -28,17 +24,18 @@ module Kemal::FileHelpers
filestat = File.stat(file_path)
config.static_headers.try(&.call(env.response, file_path, filestat))
gzip = config.serve_static?("gzip")
File.open(file_path) do |file|
if env.request.method == "GET" && env.request.headers.has_key?("Range")
next multipart(file, env)
end
if request_headers.includes_word?("Accept-Encoding", "gzip") && config.serve_static?("gzip") && filesize > minsize && Kemal::Utils.zip_types(file_path)
if request_headers.includes_word?("Accept-Encoding", "gzip") && gzip && filesize > minsize && Kemal::Utils.zip_types(file_path)
env.response.headers["Content-Encoding"] = "gzip"
Gzip::Writer.open(env.response) do |deflate|
IO.copy(file, deflate)
end
elsif request_headers.includes_word?("Accept-Encoding", "deflate") && config.serve_static?("gzip") && filesize > minsize && Kemal::Utils.zip_types(file_path)
elsif request_headers.includes_word?("Accept-Encoding", "deflate") && gzip && filesize > minsize && Kemal::Utils.zip_types(file_path)
env.response.headers["Content-Encoding"] = "deflate"
Flate::Writer.open(env.response) do |deflate|
IO.copy(file, deflate)
@ -51,6 +48,10 @@ module Kemal::FileHelpers
return
end
def send_file(env, path : String, mime_type : String? = nil)
send_file(env, path, env.app.config, mime_type)
end
private def multipart(file, env : HTTP::Server::Context)
# See http://httpwg.org/specs/rfc7233.html
fileb = file.size

View file

@ -4,6 +4,12 @@
module Kemal
class StaticFileHandler < HTTP::StaticFileHandler
getter config : Kemal::Config
def initialize(@config, fallthrough = true)
super(@config.public_folder, fallthrough)
end
def call(context : HTTP::Server::Context)
return call_next(context) if context.request.path.not_nil! == "/"
@ -19,7 +25,6 @@ module Kemal
return
end
config = Kemal.config.serve_static
original_path = context.request.path.not_nil!
request_path = URI.unescape(original_path)
@ -48,7 +53,7 @@ module Kemal
end
if Dir.exists?(file_path)
if config.is_a?(Hash) && config["dir_listing"] == true
if @config.serve_static?("dir_listing")
context.response.content_type = "text/html"
directory_listing(context.response, request_path, file_path)
else
@ -62,7 +67,8 @@ module Kemal
context.response.status_code = 304
return
end
send_file(context, file_path)
FileHelpers.send_file(context, file_path, config)
else
call_next(context)
end