require 'abstract_unit'
module ActionDispatch
module Journey
class TestRouter < ActiveSupport::TestCase
attr_reader :routes, :mapper
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
mapper.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
mapper.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
mapper.get "/whois/:domain", :domain => /\w+\.[\w\.]+/, to: "foo#bar"
mapper.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
mapper.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
mapper.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
mapper.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"
mapper = ActionDispatch::Routing::Mapper.new @route_set
mapper.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
mapper.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
route_set = Routing::RouteSet.new
mapper = Routing::Mapper.new route_set
app = lambda { |env| [200, {}, ['success!']] }
mapper.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
mapper.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
mapper.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
mapper.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|
mapper.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
mapper.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
mapper.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
mapper.get "/messages/:action(/:id(.:format))", to: 'foo#bar'
mapper.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
mapper.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"] ]
mapper.get "/", Hash[params]
path, _ = @formatter.generate(nil, Hash[params], {})
assert_equal '/', path
end
def test_generate_calls_param_proc
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
mapper.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
private
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