mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
c81af6ae72
We sometimes say "✂️ newline after `private`" in a code review (e.g. https://github.com/rails/rails/pull/18546#discussion_r23188776, https://github.com/rails/rails/pull/34832#discussion_r244847195). Now `Layout/EmptyLinesAroundAccessModifier` cop have new enforced style `EnforcedStyle: only_before` (https://github.com/rubocop-hq/rubocop/pull/7059). That cop and enforced style will reduce the our code review cost.
543 lines
16 KiB
Ruby
543 lines
16 KiB
Ruby
# 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"
|
|
primary_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 = primary_parameters.merge(redirection_parameters).merge(missing_parameters)
|
|
|
|
message = "No route matches #{Hash[request_parameters.sort_by { |k, _|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
|