1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/test/journey/router_test.rb
eileencodes 437ab2031e
Convert route params array into object
This PR converts the route params array into an object and moves the
error generation code into its own object as well. This is a refactoring
of internal code to make it easier to change and simplier for internals
to interface with. We have two new objects:

1) `RouteWithParams`

Previously `#generate` returned an array of parameterized parts for a
route. We have now turned this into an object with a `path` and a
`params methods` to be able to access the parts we need.

2) `MissingRoute`

`#generate` was also responsible for constructing the message for the
error. We've moved this code into the new `MissingRoute` object so it
does that work instead.

This change makes these methods reusable throughout the code and easier
for future refactoring we plan to do.

Co-authored-by: Aaron Patterson <aaron.patterson@gmail.com>
2020-06-05 13:18:49 -04:00

538 lines
16 KiB
Ruby

# frozen_string_literal: true
require "abstract_unit"
require "rack/utils"
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
@route_set.url_for({ controller: "foo", action: "bar", id: "10" }, nil)
end
end
def test_required_parts_are_verified_when_building
get "/foo/:id", id: /\d+/, anchor: false, to: "foo#bar"
path, _ = _generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
assert_equal "/foo/10", path
assert_raises(ActionController::UrlGenerationError) do
_generate(nil, { id: "aa" }, {})
end
end
def test_only_required_parts_are_verified
get "/foo(/:id)", id: /\d/, to: "foo#bar"
path, _ = _generate(nil, { controller: "foo", action: "bar", id: "10" }, {})
assert_equal "/foo/10", path
path, _ = _generate(nil, { controller: "foo", action: "bar" }, {})
assert_equal "/foo", path
path, _ = _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
_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
_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, _ = _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, _ = _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, _ = _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, _ = _generate(nil, params, extras)
assert_equal "/tasks/index", path
end
def test_generate_slash
params = [ [:controller, "tasks"],
[:action, "show"] ]
get "/", Hash[params]
path, _ = _generate(nil, Hash[params], {})
assert_equal "/", path
end
def test_generate_id
get "/:controller(/:action)", to: "foo#bar"
path, params = _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, _ = _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, _ = _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 = _generate(
nil, { id: 1,
controller: "tasks",
action: "show",
relative_url_root: nil
}, {})
assert_equal "/tasks/show", path
assert_equal({ id: "1" }, 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
_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 = _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 = _generate(
"tasks",
{ controller: "tasks" },
{ controller: "tasks", action: "index" })
assert_equal "/tasks/index", 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 _generate(route_name, options, recall)
if recall
options = options.merge(_recall: recall)
end
path = @route_set.path_for(options, route_name)
uri = URI.parse path
params = Rack::Utils.parse_nested_query(uri.query).symbolize_keys
[uri.path, params]
end
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