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 add_routes router, paths, anchor = true paths.each do |path| if String === path path = Path::Pattern.from_string path else path end add_route @app, path, {}, [], {}, {} 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