474 lines
13 KiB
Crystal
474 lines
13 KiB
Crystal
require "./spec_helper"
|
|
|
|
describe "Kemal::Router" do
|
|
describe "basic routing" do
|
|
it "routes GET request with prefix" do
|
|
router = Kemal::Router.new
|
|
router.get "/users" do
|
|
"users list"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("GET", "/api/users")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("users list")
|
|
end
|
|
|
|
it "routes POST request with prefix" do
|
|
router = Kemal::Router.new
|
|
router.post "/users" do
|
|
"user created"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("POST", "/api/users")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("user created")
|
|
end
|
|
|
|
it "routes PUT request with prefix" do
|
|
router = Kemal::Router.new
|
|
router.put "/users/:id" do |env|
|
|
"user #{env.params.url["id"]} updated"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("PUT", "/api/users/123")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("user 123 updated")
|
|
end
|
|
|
|
it "routes DELETE request with prefix" do
|
|
router = Kemal::Router.new
|
|
router.delete "/users/:id" do |env|
|
|
"user #{env.params.url["id"]} deleted"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("DELETE", "/api/users/456")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("user 456 deleted")
|
|
end
|
|
|
|
it "routes PATCH request" do
|
|
router = Kemal::Router.new
|
|
router.patch "/users/:id" do
|
|
"user patched"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("PATCH", "/api/users/1")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("user patched")
|
|
end
|
|
|
|
it "routes OPTIONS request" do
|
|
router = Kemal::Router.new
|
|
router.options "/users" do |env|
|
|
env.response.headers["Allow"] = "GET, POST, OPTIONS"
|
|
""
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("OPTIONS", "/api/users")
|
|
client_response = call_request_on_app(request)
|
|
client_response.headers["Allow"].should eq("GET, POST, OPTIONS")
|
|
end
|
|
|
|
it "mounts router without prefix" do
|
|
router = Kemal::Router.new
|
|
router.get "/status" do
|
|
"ok"
|
|
end
|
|
|
|
mount router
|
|
|
|
request = HTTP::Request.new("GET", "/status")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("ok")
|
|
end
|
|
|
|
it "works alongside global DSL routes" do
|
|
# Global DSL route
|
|
get "/global" do
|
|
"global route"
|
|
end
|
|
|
|
# Router route
|
|
router = Kemal::Router.new
|
|
router.get "/local" do
|
|
"router route"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
# Test global route
|
|
global_request = HTTP::Request.new("GET", "/global")
|
|
global_response = call_request_on_app(global_request)
|
|
global_response.body.should eq("global route")
|
|
|
|
# Test router route
|
|
router_request = HTTP::Request.new("GET", "/api/local")
|
|
router_response = call_request_on_app(router_request)
|
|
router_response.body.should eq("router route")
|
|
end
|
|
end
|
|
|
|
describe "router-scoped filters" do
|
|
it "applies before filter to router routes" do
|
|
router = Kemal::Router.new
|
|
|
|
router.before do |env|
|
|
env.set "filtered", "yes"
|
|
end
|
|
|
|
router.get "/test" do |env|
|
|
env.get("filtered").to_s
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("GET", "/api/test")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("yes")
|
|
end
|
|
|
|
it "applies after filter to router routes" do
|
|
router = Kemal::Router.new
|
|
|
|
router.after do |env|
|
|
env.response.headers["X-After-Filter"] = "applied"
|
|
end
|
|
|
|
router.get "/test" do
|
|
"test"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("GET", "/api/test")
|
|
client_response = call_request_on_app(request)
|
|
client_response.headers["X-After-Filter"].should eq("applied")
|
|
end
|
|
|
|
it "applies method-specific before filter" do
|
|
router = Kemal::Router.new
|
|
|
|
router.before_post do |env|
|
|
env.set "method", "post"
|
|
end
|
|
|
|
router.post "/test" do |env|
|
|
env.get("method").to_s
|
|
end
|
|
|
|
router.get "/test" do |env|
|
|
env.get?("method").to_s
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
post_request = HTTP::Request.new("POST", "/api/test")
|
|
post_response = call_request_on_app(post_request)
|
|
post_response.body.should eq("post")
|
|
end
|
|
|
|
it "applies filter to specific path" do
|
|
router = Kemal::Router.new
|
|
|
|
router.before "/protected" do |env|
|
|
env.set "auth", "required"
|
|
end
|
|
|
|
router.get "/protected" do |env|
|
|
env.get("auth").to_s
|
|
end
|
|
|
|
router.get "/public" do |env|
|
|
env.get?("auth").to_s
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
protected_request = HTTP::Request.new("GET", "/api/protected")
|
|
protected_response = call_request_on_app(protected_request)
|
|
protected_response.body.should eq("required")
|
|
end
|
|
|
|
it "applies namespace filters only within the namespace" do
|
|
router = Kemal::Router.new
|
|
|
|
router.namespace "/admin" do
|
|
before do |env|
|
|
halt env, 401, "unauthorized" unless env.request.headers["X-Admin"]? == "true"
|
|
end
|
|
|
|
get "/dashboard" do |env|
|
|
env.get("path").to_s
|
|
end
|
|
end
|
|
|
|
router.get "/public" do |env|
|
|
env.get("path").to_s
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
before_all do |env|
|
|
env.set "path", env.request.path
|
|
end
|
|
|
|
get "/public" do |env|
|
|
env.get("path").to_s
|
|
end
|
|
|
|
unauthorized_request = HTTP::Request.new("GET", "/api/admin/dashboard")
|
|
unauthorized_response = call_request_on_app(unauthorized_request)
|
|
unauthorized_response.status_code.should eq(401)
|
|
unauthorized_response.body.should eq("unauthorized")
|
|
|
|
authorized_request = HTTP::Request.new(
|
|
"GET",
|
|
"/api/admin/dashboard",
|
|
headers: HTTP::Headers{"X-Admin" => "true"},
|
|
)
|
|
authorized_response = call_request_on_app(authorized_request)
|
|
authorized_response.status_code.should eq(200)
|
|
authorized_response.body.should eq("/api/admin/dashboard")
|
|
|
|
api_public_request = HTTP::Request.new("GET", "/api/public")
|
|
api_public_response = call_request_on_app(api_public_request)
|
|
api_public_response.status_code.should eq(200)
|
|
api_public_response.body.should eq("/api/public")
|
|
|
|
public_request = HTTP::Request.new("GET", "/public")
|
|
public_response = call_request_on_app(public_request)
|
|
public_response.status_code.should eq(200)
|
|
public_response.body.should eq("/public")
|
|
end
|
|
end
|
|
|
|
describe "nested routers" do
|
|
it "namespaces routes correctly" do
|
|
router = Kemal::Router.new
|
|
|
|
router.namespace "/users" do
|
|
get "/" do
|
|
"users index"
|
|
end
|
|
|
|
get "/:id" do |env|
|
|
"user #{env.params.url["id"]}"
|
|
end
|
|
end
|
|
|
|
mount "/api/v1", router
|
|
|
|
index_request = HTTP::Request.new("GET", "/api/v1/users")
|
|
index_response = call_request_on_app(index_request)
|
|
index_response.body.should eq("users index")
|
|
|
|
show_request = HTTP::Request.new("GET", "/api/v1/users/42")
|
|
show_response = call_request_on_app(show_request)
|
|
show_response.body.should eq("user 42")
|
|
end
|
|
|
|
it "supports multiple namespaces" do
|
|
router = Kemal::Router.new
|
|
|
|
router.namespace "/users" do
|
|
get "/" do
|
|
"users"
|
|
end
|
|
end
|
|
|
|
router.namespace "/posts" do
|
|
get "/" do
|
|
"posts"
|
|
end
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
users_request = HTTP::Request.new("GET", "/api/users")
|
|
users_response = call_request_on_app(users_request)
|
|
users_response.body.should eq("users")
|
|
|
|
posts_request = HTTP::Request.new("GET", "/api/posts")
|
|
posts_response = call_request_on_app(posts_request)
|
|
posts_response.body.should eq("posts")
|
|
end
|
|
|
|
it "supports deeply nested routers" do
|
|
router = Kemal::Router.new
|
|
|
|
router.namespace "/api" do
|
|
namespace "/v1" do
|
|
namespace "/users" do
|
|
get "/" do
|
|
"deeply nested users"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
mount router
|
|
|
|
request = HTTP::Request.new("GET", "/api/v1/users")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("deeply nested users")
|
|
end
|
|
|
|
it "mounts sub-router with mount method" do
|
|
users_router = Kemal::Router.new
|
|
users_router.get "/" do
|
|
"users from sub-router"
|
|
end
|
|
users_router.get "/:id" do |env|
|
|
"user #{env.params.url["id"]} from sub-router"
|
|
end
|
|
|
|
api_router = Kemal::Router.new
|
|
api_router.mount "/users", users_router
|
|
|
|
mount "/api", api_router
|
|
|
|
index_request = HTTP::Request.new("GET", "/api/users")
|
|
index_response = call_request_on_app(index_request)
|
|
index_response.body.should eq("users from sub-router")
|
|
|
|
show_request = HTTP::Request.new("GET", "/api/users/99")
|
|
show_response = call_request_on_app(show_request)
|
|
show_response.body.should eq("user 99 from sub-router")
|
|
end
|
|
|
|
it "applies namespace filters correctly" do
|
|
router = Kemal::Router.new
|
|
|
|
router.namespace "/admin" do
|
|
before do |env|
|
|
env.set "admin", "true"
|
|
end
|
|
|
|
get "/dashboard" do |env|
|
|
"admin: #{env.get("admin")}"
|
|
end
|
|
end
|
|
|
|
mount router
|
|
|
|
request = HTTP::Request.new("GET", "/admin/dashboard")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("admin: true")
|
|
end
|
|
end
|
|
|
|
describe "websocket support" do
|
|
it "registers websocket route with prefix" do
|
|
router = Kemal::Router.new
|
|
router.ws "/chat" do |socket|
|
|
socket.send("connected")
|
|
end
|
|
|
|
mount "/ws", router
|
|
|
|
handler = Kemal::WebSocketHandler::INSTANCE
|
|
headers = HTTP::Headers{
|
|
"Upgrade" => "websocket",
|
|
"Connection" => "Upgrade",
|
|
"Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
|
|
"Sec-WebSocket-Version" => "13",
|
|
}
|
|
request = HTTP::Request.new("GET", "/ws/chat", headers)
|
|
|
|
io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0]
|
|
io_with_context.to_s.should contain("101 Switching Protocols")
|
|
end
|
|
|
|
it "websocket route with url parameters" do
|
|
router = Kemal::Router.new
|
|
router.ws "/room/:id" do |socket|
|
|
socket.send("room")
|
|
end
|
|
|
|
mount "/ws", router
|
|
|
|
handler = Kemal::WebSocketHandler::INSTANCE
|
|
headers = HTTP::Headers{
|
|
"Upgrade" => "websocket",
|
|
"Connection" => "Upgrade",
|
|
"Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
|
|
"Sec-WebSocket-Version" => "13",
|
|
}
|
|
request = HTTP::Request.new("GET", "/ws/room/123", headers)
|
|
|
|
io_with_context = create_ws_request_and_return_io_and_context(handler, request)[0]
|
|
io_with_context.to_s.should contain("101 Switching Protocols")
|
|
end
|
|
end
|
|
|
|
describe "router with prefix" do
|
|
it "initializes router with prefix" do
|
|
router = Kemal::Router.new("/v2")
|
|
router.get "/status" do
|
|
"v2 status"
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("GET", "/api/v2/status")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("v2 status")
|
|
end
|
|
end
|
|
|
|
describe "edge cases" do
|
|
it "handles trailing slashes correctly" do
|
|
router = Kemal::Router.new
|
|
router.get "/users/" do
|
|
"users with trailing slash"
|
|
end
|
|
|
|
mount "/api/", router
|
|
|
|
request = HTTP::Request.new("GET", "/api/users/")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("users with trailing slash")
|
|
end
|
|
|
|
it "handles root path in namespace" do
|
|
router = Kemal::Router.new
|
|
|
|
router.namespace "/users" do
|
|
get "" do
|
|
"users root"
|
|
end
|
|
end
|
|
|
|
mount "/api", router
|
|
|
|
request = HTTP::Request.new("GET", "/api/users")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("users root")
|
|
end
|
|
|
|
it "returns non-string values as empty string" do
|
|
router = Kemal::Router.new
|
|
router.get "/number" do
|
|
42
|
|
end
|
|
|
|
mount router
|
|
|
|
request = HTTP::Request.new("GET", "/number")
|
|
client_response = call_request_on_app(request)
|
|
client_response.body.should eq("")
|
|
end
|
|
end
|
|
end
|