# frozen_string_literal: true
require "abstract_unit"
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
attr_reader :mapper, :routes, :route_set, :router
def setup
@app = Routing::RouteSet::Dispatcher.new({})
@route_set = ActionDispatch::Routing::RouteSet.new
@routes = @route_set.router.routes
@router = @route_set.router
@formatter = @route_set.formatter
@mapper = ActionDispatch::Routing::Mapper.new @route_set
end
def test_dashes
get "/foo-bar-baz", to: "foo#bar"
env = rails_env "PATH_INFO" => "/foo-bar-baz"
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_unicode
get "/ほげ", to: "foo#bar"
# match the escaped version of /ほげ
env = rails_env "PATH_INFO" => "/%E3%81%BB%E3%81%92"
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_regexp_first_precedence
get "/whois/:domain", domain: /\w+\.[\w\.]+/, to: "foo#bar"
get "/whois/:id(.:format)", to: "foo#baz"
env = rails_env "PATH_INFO" => "/whois/example.com"
list = []
router.recognize(env) do |r, params|
list << r
end
assert_equal 2, list.length
r = list.first
assert_equal "/whois/:domain(.:format)", r.path.spec.to_s
end
def test_required_parts_verified_are_anchored
get "/foo/:id", id: /\d/, anchor: false, to: "foo#bar"
assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
end
end
def test_required_parts_are_verified_when_building
get "/foo/:id", id: /\d+/, anchor: false, to: "foo#bar"
path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
assert_equal "/foo/10", path
assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(nil, { id: "aa" }, {})
end
end
def test_only_required_parts_are_verified
get "/foo(/:id)", id: /\d/, to: "foo#bar"
path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
assert_equal "/foo/10", path
path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, {})
assert_equal "/foo", path
path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", id: "aa" }, {})
assert_equal "/foo/aa", path
end
def test_knows_what_parts_are_missing_from_named_route
route_name = "gorby_thunderhorse"
get "/foo/:id", as: route_name, id: /\d+/, to: "foo#bar"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, {}, {})
end
assert_match(/missing required keys: \[:id\]/, error.message)
end
def test_does_not_include_missing_keys_message
route_name = "gorby_thunderhorse"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(route_name, {}, {})
end
assert_no_match(/missing required keys: \[\]/, error.message)
end
def test_X_Cascade
get "/messages(.:format)", to: "foo#bar"
resp = router.serve(rails_env("REQUEST_METHOD" => "GET", "PATH_INFO" => "/lol"))
assert_equal ["Not Found"], resp.last
assert_equal "pass", resp[1]["X-Cascade"]
assert_equal 404, resp.first
end
def test_clear_trailing_slash_from_script_name_on_root_unanchored_routes
app = lambda { |env| [200, {}, ["success!"]] }
get "/weblog", to: app
env = rack_env("SCRIPT_NAME" => "", "PATH_INFO" => "/weblog")
resp = route_set.call env
assert_equal ["success!"], resp.last
assert_equal "", env["SCRIPT_NAME"]
end
def test_defaults_merge_correctly
get "/foo(/:id)", to: "foo#bar", id: nil
env = rails_env "PATH_INFO" => "/foo/10"
router.recognize(env) do |r, params|
assert_equal({ id: "10", controller: "foo", action: "bar" }, params)
end
env = rails_env "PATH_INFO" => "/foo"
router.recognize(env) do |r, params|
assert_equal({ id: nil, controller: "foo", action: "bar" }, params)
end
end
def test_recognize_with_unbound_regexp
get "/foo", anchor: false, to: "foo#bar"
env = rails_env "PATH_INFO" => "/foo/bar"
router.recognize(env) { |*_| }
assert_equal "/foo", env.env["SCRIPT_NAME"]
assert_equal "/bar", env.env["PATH_INFO"]
end
def test_bound_regexp_keeps_path_info
get "/foo", to: "foo#bar"
env = rails_env "PATH_INFO" => "/foo"
before = env.env["SCRIPT_NAME"]
router.recognize(env) { |*_| }
assert_equal before, env.env["SCRIPT_NAME"]
assert_equal "/foo", env.env["PATH_INFO"]
end
def test_path_not_found
[
"/messages(.:format)",
"/messages/new(.:format)",
"/messages/:id/edit(.:format)",
"/messages/:id(.:format)"
].each do |path|
get path, to: "foo#bar"
end
env = rails_env "PATH_INFO" => "/messages/unknown/path"
yielded = false
router.recognize(env) do |*whatever|
yielded = true
end
assert_not yielded
end
def test_required_part_in_recall
get "/messages/:a/:b", to: "foo#bar"
path, _ = @formatter.generate(nil, { controller: "foo", action: "bar", a: "a" }, { b: "b" })
assert_equal "/messages/a/b", path
end
def test_splat_in_recall
get "/*path", to: "foo#bar"
path, _ = @formatter.generate(nil, { controller: "foo", action: "bar" }, { path: "b" })
assert_equal "/b", path
end
def test_recall_should_be_used_when_scoring
get "/messages/:action(/:id(.:format))", to: "foo#bar"
get "/messages/:id(.:format)", to: "bar#baz"
path, _ = @formatter.generate(nil, { controller: "foo", id: 10 }, { action: "index" })
assert_equal "/messages/index/10", path
end
def test_nil_path_parts_are_ignored
get "/:controller(/:action(.:format))", to: "tasks#lol"
params = { controller: "tasks", format: nil }
extras = { action: "lol" }
path, _ = @formatter.generate(nil, params, extras)
assert_equal "/tasks", path
end
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
get "/", Hash[params]
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal "/", path
end
def test_generate_calls_param_proc
get "/:controller(/:action)", to: "foo#bar"
parameterized = []
params = [ [:controller, "tasks"],
[:action, "show"] ]
@formatter.generate(
nil,
Hash[params],
{},
lambda { |k, v| parameterized << [k, v]; v })
assert_equal params.map(&:to_s).sort, parameterized.map(&:to_s).sort
end
def test_generate_id
get "/:controller(/:action)", to: "foo#bar"
path, params = @formatter.generate(
nil, { id: 1, controller: "tasks", action: "show" }, {})
assert_equal "/tasks/show", path
assert_equal({ id: 1 }, params)
end
def test_generate_escapes
get "/:controller(/:action)", to: "foo#bar"
path, _ = @formatter.generate(nil,
{ controller: "tasks",
action: "a/b c+d",
}, {})
assert_equal "/tasks/a%2Fb%20c+d", path
end
def test_generate_escapes_with_namespaced_controller
get "/:controller(/:action)", to: "foo#bar"
path, _ = @formatter.generate(
nil, { controller: "admin/tasks",
action: "a/b c+d",
}, {})
assert_equal "/admin/tasks/a%2Fb%20c+d", path
end
def test_generate_extra_params
get "/:controller(/:action)", to: "foo#bar"
path, params = @formatter.generate(
nil, { id: 1,
controller: "tasks",
action: "show",
relative_url_root: nil
}, {})
assert_equal "/tasks/show", path
assert_equal({ id: 1, relative_url_root: nil }, params)
end
def test_generate_missing_keys_no_matches_different_format_keys
get "/:controller/:action/:name", to: "foo#bar"
primarty_parameters = {
id: 1,
controller: "tasks",
action: "show",
relative_url_root: nil
}
redirection_parameters = {
"action" => "show",
}
missing_key = "name"
missing_parameters = {
missing_key => "task_1"
}
request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters)
message = "No route matches #{Hash[request_parameters.sort_by { |k, v|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}"
error = assert_raises(ActionController::UrlGenerationError) do
@formatter.generate(
nil, request_parameters, request_parameters)
end
assert_equal message, error.message
end
def test_generate_uses_recall_if_needed
get "/:controller(/:action(/:id))", to: "foo#bar"
path, params = @formatter.generate(
nil,
{ controller: "tasks", id: 10 },
{ action: "index" })
assert_equal "/tasks/index/10", path
assert_equal({}, params)
end
def test_generate_with_name
get "/:controller(/:action)", to: "foo#bar", as: "tasks"
path, params = @formatter.generate(
"tasks",
{ controller: "tasks" },
{ controller: "tasks", action: "index" })
assert_equal "/tasks", path
assert_equal({}, params)
end
{
"/content" => { controller: "content" },
"/content/list" => { controller: "content", action: "list" },
"/content/show/10" => { controller: "content", action: "show", id: "10" },
}.each do |request_path, expected|
define_method("test_recognize_#{expected.keys.map(&:to_s).join('_')}") do
get "/:controller(/:action(/:id))", to: "foo#bar"
route = @routes.first
env = rails_env "PATH_INFO" => request_path
called = false
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal({ action: "bar" }.merge(expected), params)
called = true
end
assert called
end
end
{
segment: ["/a%2Fb%20c+d/splat", { segment: "a/b c+d", splat: "splat" }],
splat: ["/segment/a/b%20c+d", { segment: "segment", splat: "a/b c+d" }]
}.each do |name, (request_path, expected)|
define_method("test_recognize_#{name}") do
get "/:segment/*splat", to: "foo#bar"
env = rails_env "PATH_INFO" => request_path
called = false
route = @routes.first
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected.merge(controller: "foo", action: "bar"), params)
called = true
end
assert called
end
end
def test_namespaced_controller
get "/:controller(/:action(/:id))", controller: /.+?/
route = @routes.first
env = rails_env "PATH_INFO" => "/admin/users/show/10"
called = false
expected = {
controller: "admin/users",
action: "show",
id: "10"
}
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
end
assert called
end
def test_recognize_literal
get "/books(/:action(.:format))", controller: "books"
route = @routes.first
env = rails_env "PATH_INFO" => "/books/list.rss"
expected = { controller: "books", action: "list", format: "rss" }
called = false
router.recognize(env) do |r, params|
assert_equal route, r
assert_equal(expected, params)
called = true
end
assert called
end
def test_recognize_head_route
match "/books(/:action(.:format))", via: "head", to: "foo#bar"
env = rails_env(
"PATH_INFO" => "/books/list.rss",
"REQUEST_METHOD" => "HEAD"
)
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_recognize_head_request_as_get_route
get "/books(/:action(.:format))", to: "foo#bar"
env = rails_env "PATH_INFO" => "/books/list.rss",
"REQUEST_METHOD" => "HEAD"
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_recognize_cares_about_get_verbs
match "/books(/:action(.:format))", to: "foo#bar", via: :get
env = rails_env "PATH_INFO" => "/books/list.rss",
"REQUEST_METHOD" => "POST"
called = false
router.recognize(env) do |r, params|
called = true
end
assert_not called
end
def test_recognize_cares_about_post_verbs
match "/books(/:action(.:format))", to: "foo#bar", via: :post
env = rails_env "PATH_INFO" => "/books/list.rss",
"REQUEST_METHOD" => "POST"
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
def test_multi_verb_recognition
match "/books(/:action(.:format))", to: "foo#bar", via: [:post, :get]
%w( POST GET ).each do |verb|
env = rails_env "PATH_INFO" => "/books/list.rss",
"REQUEST_METHOD" => verb
called = false
router.recognize(env) do |r, params|
called = true
end
assert called
end
env = rails_env "PATH_INFO" => "/books/list.rss",
"REQUEST_METHOD" => "PUT"
called = false
router.recognize(env) do |r, params|
called = true
end
assert_not called
end
def test_eager_load_with_routes
get "/foo-bar", to: "foo#bar"
assert_nil router.eager_load!
end
def test_eager_load_without_routes
assert_nil router.eager_load!
end
private
def get(*args)
ActiveSupport::Deprecation.silence do
mapper.get(*args)
end
end
def match(*args)
ActiveSupport::Deprecation.silence do
mapper.match(*args)
end
end
def rails_env(env, klass = ActionDispatch::Request)
klass.new(rack_env(env))
end
def rack_env(env)
{
"rack.version" => [1, 1],
"rack.input" => StringIO.new,
"rack.errors" => StringIO.new,
"rack.multithread" => true,
"rack.multiprocess" => true,
"rack.run_once" => false,
"REQUEST_METHOD" => "GET",
"SERVER_NAME" => "example.org",
"SERVER_PORT" => "80",
"QUERY_STRING" => "",
"PATH_INFO" => "/content",
"rack.url_scheme" => "http",
"HTTPS" => "off",
"SCRIPT_NAME" => "",
"CONTENT_LENGTH" => "0"
}.merge env
end
end
end
end