diff options
Diffstat (limited to 'actionpack/test/controller/routing_test.rb')
-rw-r--r-- | actionpack/test/controller/routing_test.rb | 2105 |
1 files changed, 2105 insertions, 0 deletions
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb new file mode 100644 index 0000000000..b378bb80b8 --- /dev/null +++ b/actionpack/test/controller/routing_test.rb @@ -0,0 +1,2105 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "controller/fake_controllers" +require "active_support/core_ext/object/with_options" +require "active_support/core_ext/object/json" + +class MilestonesController < ActionController::Base + def index() head :ok end + alias_method :show, :index +end + +# See RFC 3986, section 3.3 for allowed path characters. +class UriReservedCharactersRoutingTest < ActiveSupport::TestCase + include RoutingTestHelpers + + def setup + @set = ActionDispatch::Routing::RouteSet.new + @set.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:variable/*additional" + end + end + + safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ]) + hex = unsafe.map { |char| "%" + char.unpack1("H2").upcase } + + @segment = "#{safe.join}#{unsafe.join}" + @escaped = "#{safe.join}#{hex.join}" + end + + def test_route_generation_escapes_unsafe_path_characters + assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", + url_for(@set, + controller: "content", + action: "act#{@segment}ion", + variable: "var#{@segment}iable", + additional: ["add#{@segment}itional-1", "add#{@segment}itional-2"]) + end + + def test_route_recognition_unescapes_path_components + options = { controller: "content", + action: "act#{@segment}ion", + variable: "var#{@segment}iable", + additional: "add#{@segment}itional-1/add#{@segment}itional-2" } + assert_equal options, @set.recognize_path("/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2") + end + + def test_route_generation_allows_passing_non_string_values_to_generated_helper + assert_equal "/content/action/variable/1/2", + url_for(@set, + controller: "content", + action: "action", + variable: "variable", + additional: [1, 2]) + end +end + +class MockController + def self.build(helpers, additional_options = {}) + Class.new do + define_method :url_options do + options = super() + options[:protocol] ||= "http" + options[:host] ||= "test.host" + options.merge(additional_options) + end + + include helpers + end + end +end + +class LegacyRouteSetTests < ActiveSupport::TestCase + include RoutingTestHelpers + include ActionDispatch::RoutingVerbs + + attr_reader :rs + attr_accessor :controller + alias :routes :rs + + def setup + @rs = make_set + @response = nil + end + + def test_symbols_with_dashes + rs.draw do + get "/:artist/:song-omg", to: lambda { |env| + resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters + [200, {}, [resp]] + } + end + + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/faithfully-omg")) + assert_equal({ "artist" => "journey", "song" => "faithfully" }, hash) + end + + def test_id_encoding + rs.draw do + get "/journey/:id", to: lambda { |env| + param = ActionDispatch::Request.new(env).path_parameters + resp = ActiveSupport::JSON.encode param + [200, {}, [resp]] + } + end + + # The encoding of the URL in production is *binary*, so we add a + # .b here. + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/%E5%A4%AA%E9%83%8E".b)) + assert_equal({ "id" => "太郎" }, hash) + assert_equal ::Encoding::UTF_8, hash["id"].encoding + end + + def test_id_with_dash + rs.draw do + get "/journey/:id", to: lambda { |env| + resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters + [200, {}, [resp]] + } + end + + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/faithfully-omg")) + assert_equal({ "id" => "faithfully-omg" }, hash) + end + + def test_dash_with_custom_regexp + rs.draw do + get "/:artist/:song-omg", constraints: { song: /\d+/ }, to: lambda { |env| + resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters + [200, {}, [resp]] + } + end + + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/123-omg")) + assert_equal({ "artist" => "journey", "song" => "123" }, hash) + assert_equal "Not Found", get(URI("http://example.org/journey/faithfully-omg")) + end + + def test_pre_dash + rs.draw do + get "/:artist/omg-:song", to: lambda { |env| + resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters + [200, {}, [resp]] + } + end + + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/omg-faithfully")) + assert_equal({ "artist" => "journey", "song" => "faithfully" }, hash) + end + + def test_pre_dash_with_custom_regexp + rs.draw do + get "/:artist/omg-:song", constraints: { song: /\d+/ }, to: lambda { |env| + resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters + [200, {}, [resp]] + } + end + + hash = ActiveSupport::JSON.decode get(URI("http://example.org/journey/omg-123")) + assert_equal({ "artist" => "journey", "song" => "123" }, hash) + assert_equal "Not Found", get(URI("http://example.org/journey/omg-faithfully")) + end + + def test_star_paths_are_greedy + rs.draw do + get "/*path", to: lambda { |env| + x = env["action_dispatch.request.path_parameters"][:path] + [200, {}, [x]] + }, format: false + end + + u = URI("http://example.org/foo/bar.html") + assert_equal u.path.sub(/^\//, ""), get(u) + end + + def test_star_paths_are_greedy_but_not_too_much + rs.draw do + get "/*path", to: lambda { |env| + x = ActiveSupport::JSON.encode env["action_dispatch.request.path_parameters"] + [200, {}, [x]] + } + end + + expected = { "path" => "foo/bar", "format" => "html" } + u = URI("http://example.org/foo/bar.html") + assert_equal expected, ActiveSupport::JSON.decode(get(u)) + end + + def test_optional_star_paths_are_greedy + rs.draw do + get "/(*filters)", to: lambda { |env| + x = env["action_dispatch.request.path_parameters"][:filters] + [200, {}, [x]] + }, format: false + end + + u = URI("http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794") + assert_equal u.path.sub(/^\//, ""), get(u) + end + + def test_optional_star_paths_are_greedy_but_not_too_much + rs.draw do + get "/(*filters)", to: lambda { |env| + x = ActiveSupport::JSON.encode env["action_dispatch.request.path_parameters"] + [200, {}, [x]] + } + end + + expected = { "filters" => "ne_27.065938,-80.6092/sw_25.489856,-82", + "format" => "542794" } + u = URI("http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794") + assert_equal expected, ActiveSupport::JSON.decode(get(u)) + end + + def test_regexp_precedence + rs.draw do + get "/whois/:domain", constraints: { + domain: /\w+\.[\w\.]+/ }, + to: lambda { |env| [200, {}, %w{regexp}] } + + get "/whois/:id", to: lambda { |env| [200, {}, %w{id}] } + end + + assert_equal "regexp", get(URI("http://example.org/whois/example.org")) + assert_equal "id", get(URI("http://example.org/whois/123")) + end + + def test_class_and_lambda_constraints + subdomain = Class.new { + def matches?(request) + request.subdomain.present? && request.subdomain != "clients" + end + } + + rs.draw do + get "/", constraints: subdomain.new, + to: lambda { |env| [200, {}, %w{default}] } + get "/", constraints: { subdomain: "clients" }, + to: lambda { |env| [200, {}, %w{clients}] } + end + + assert_equal "default", get(URI("http://www.example.org/")) + assert_equal "clients", get(URI("http://clients.example.org/")) + end + + def test_lambda_constraints + rs.draw do + get "/", constraints: lambda { |req| + req.subdomain.present? && req.subdomain != "clients" }, + to: lambda { |env| [200, {}, %w{default}] } + + get "/", constraints: lambda { |req| + req.subdomain.present? && req.subdomain == "clients" }, + to: lambda { |env| [200, {}, %w{clients}] } + end + + assert_equal "default", get(URI("http://www.example.org/")) + assert_equal "clients", get(URI("http://clients.example.org/")) + end + + def test_scoped_lambda + scope_called = false + rs.draw do + scope "/foo", constraints: lambda { |req| scope_called = true } do + get "/", to: lambda { |env| [200, {}, %w{default}] } + end + end + + assert_equal "default", get(URI("http://www.example.org/foo/")) + assert scope_called, "scope constraint should be called" + end + + def test_scoped_lambda_with_get_lambda + inner_called = false + + rs.draw do + scope "/foo", constraints: lambda { |req| flunk "should not be called" } do + get "/", constraints: lambda { |req| inner_called = true }, + to: lambda { |env| [200, {}, %w{default}] } + end + end + + assert_equal "default", get(URI("http://www.example.org/foo/")) + assert inner_called, "inner constraint should be called" + end + + def test_empty_string_match + rs.draw do + get "/:username", constraints: { username: /[^\/]+/ }, + to: lambda { |e| [200, {}, ["foo"]] } + end + assert_equal "Not Found", get(URI("http://example.org/")) + assert_equal "foo", get(URI("http://example.org/hello")) + end + + def test_non_greedy_glob_regexp + params = nil + rs.draw do + get "/posts/:id(/*filters)", constraints: { filters: /.+?/ }, + to: lambda { |e| + params = e["action_dispatch.request.path_parameters"] + [200, {}, ["foo"]] + } + end + assert_equal "foo", get(URI("http://example.org/posts/1/foo.js")) + assert_equal({ id: "1", filters: "foo", format: "js" }, params) + end + + def test_specific_controller_action_failure + rs.draw do + mount lambda { } => "/foo" + end + + assert_raises(ActionController::UrlGenerationError) do + url_for(rs, controller: "omg", action: "lol") + end + end + + def test_default_setup + rs.draw { ActiveSupport::Deprecation.silence { get "/:controller(/:action(/:id))" } } + assert_equal({ controller: "content", action: "index" }, rs.recognize_path("/content")) + assert_equal({ controller: "content", action: "list" }, rs.recognize_path("/content/list")) + assert_equal({ controller: "content", action: "show", id: "10" }, rs.recognize_path("/content/show/10")) + + assert_equal({ controller: "admin/user", action: "show", id: "10" }, rs.recognize_path("/admin/user/show/10")) + + assert_equal "/admin/user/show/10", url_for(rs, controller: "admin/user", action: "show", id: 10) + + get URI("http://test.host/admin/user/list/10") + + assert_equal({ controller: "admin/user", action: "list", id: "10" }, + controller.request.path_parameters) + + assert_equal "/admin/user/show", controller.url_for(action: "show", only_path: true) + assert_equal "/admin/user/list/10", controller.url_for(only_path: true) + + assert_equal "/admin/stuff", controller.url_for(controller: "stuff", only_path: true) + assert_equal "/stuff", controller.url_for(controller: "/stuff", only_path: true) + end + + def test_route_with_colon_first + rs.draw do + ActiveSupport::Deprecation.silence do + get "/:controller/:action/:id", action: "index", id: nil + end + + get ":url", controller: "content", action: "translate" + end + + assert_equal({ controller: "content", action: "translate", url: "example" }, rs.recognize_path("/example")) + end + + def test_route_with_regexp_for_action + rs.draw { ActiveSupport::Deprecation.silence { get "/:controller/:action", action: /auth[-|_].+/ } } + + assert_equal({ action: "auth_google", controller: "content" }, rs.recognize_path("/content/auth_google")) + assert_equal({ action: "auth-twitter", controller: "content" }, rs.recognize_path("/content/auth-twitter")) + + assert_equal "/content/auth_google", url_for(rs, controller: "content", action: "auth_google") + assert_equal "/content/auth-twitter", url_for(rs, controller: "content", action: "auth-twitter") + end + + def test_route_with_regexp_for_controller + rs.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:admintoken(/:action(/:id))", controller: /admin\/.+/ + get "/:controller(/:action(/:id))" + end + end + + assert_equal({ controller: "admin/user", admintoken: "foo", action: "index" }, + rs.recognize_path("/admin/user/foo")) + assert_equal({ controller: "content", action: "foo" }, + rs.recognize_path("/content/foo")) + + assert_equal "/admin/user/foo", url_for(rs, controller: "admin/user", admintoken: "foo", action: "index") + assert_equal "/content/foo", url_for(rs, controller: "content", action: "foo") + end + + def test_route_with_regexp_and_captures_for_controller + rs.draw do + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))", controller: /admin\/(accounts|users)/ + end + end + assert_equal({ controller: "admin/accounts", action: "index" }, rs.recognize_path("/admin/accounts")) + assert_equal({ controller: "admin/users", action: "index" }, rs.recognize_path("/admin/users")) + assert_raise(ActionController::RoutingError) { rs.recognize_path("/admin/products") } + end + + def test_route_with_regexp_and_dot + rs.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:file", + controller: /admin|user/, + action: /upload|download/, + defaults: { file: nil }, + constraints: { file: %r{[^/]+(\.[^/]+)?} } + end + end + # Without a file extension + assert_equal "/user/download/file", + url_for(rs, controller: "user", action: "download", file: "file") + + assert_equal({ controller: "user", action: "download", file: "file" }, + rs.recognize_path("/user/download/file")) + + # Now, let's try a file with an extension, really a dot (.) + assert_equal "/user/download/file.jpg", + url_for(rs, controller: "user", action: "download", file: "file.jpg") + + assert_equal({ controller: "user", action: "download", file: "file.jpg" }, + rs.recognize_path("/user/download/file.jpg")) + end + + def test_basic_named_route + rs.draw do + root to: "content#list", as: "home" + end + assert_equal("http://test.host/", setup_for_named_route.send(:home_url)) + end + + def test_named_route_with_option + rs.draw do + get "page/:title" => "content#show_page", :as => "page" + end + + assert_equal("http://test.host/page/new%20stuff", + setup_for_named_route.send(:page_url, title: "new stuff")) + end + + def test_named_route_with_default + rs.draw do + get "page/:title" => "content#show_page", :title => "AboutPage", :as => "page" + end + + assert_equal("http://test.host/page/AboutRails", + setup_for_named_route.send(:page_url, title: "AboutRails")) + end + + def test_named_route_with_path_prefix + rs.draw do + scope "my" do + get "page" => "content#show_page", :as => "page" + end + end + + assert_equal("http://test.host/my/page", + setup_for_named_route.send(:page_url)) + end + + def test_named_route_with_blank_path_prefix + rs.draw do + scope "" do + get "page" => "content#show_page", :as => "page" + end + end + + assert_equal("http://test.host/page", + setup_for_named_route.send(:page_url)) + end + + def test_named_route_with_nested_controller + rs.draw do + get "admin/user" => "admin/user#index", :as => "users" + end + + assert_equal("http://test.host/admin/user", + setup_for_named_route.send(:users_url)) + end + + def test_optimised_named_route_with_host + rs.draw do + get "page" => "content#show_page", :as => "pages", :host => "foo.com" + end + routes = setup_for_named_route + assert_equal "http://foo.com/page", routes.pages_url + end + + def setup_for_named_route(options = {}) + MockController.build(rs.url_helpers, options).new + end + + def test_named_route_without_hash + rs.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id", as: "normal" + end + end + end + + def test_named_route_root + rs.draw do + root to: "hello#index" + end + routes = setup_for_named_route + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("/", routes.send(:root_path)) + end + + def test_named_route_root_without_hash + rs.draw do + root "hello#index" + end + routes = setup_for_named_route + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("/", routes.send(:root_path)) + end + + def test_named_route_root_with_hash + rs.draw do + root "hello#index", as: :index + end + + routes = setup_for_named_route + assert_equal("http://test.host/", routes.send(:index_url)) + assert_equal("/", routes.send(:index_path)) + end + + def test_root_without_path_raises_argument_error + assert_raises ArgumentError do + rs.draw { root nil } + end + end + + def test_named_route_root_with_trailing_slash + rs.draw do + root "hello#index" + end + + routes = setup_for_named_route(trailing_slash: true) + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("http://test.host/?foo=bar", routes.send(:root_url, foo: :bar)) + end + + def test_named_route_with_regexps + rs.draw do + get "page/:year/:month/:day/:title" => "page#show", :as => "article", + :year => /\d+/, :month => /\d+/, :day => /\d+/ + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + routes = setup_for_named_route + + assert_equal "http://test.host/page/2005/6/10/hi", + routes.send(:article_url, title: "hi", day: 10, year: 2005, month: 6) + end + + def test_changing_controller + rs.draw { ActiveSupport::Deprecation.silence { get ":controller/:action/:id" } } + + get URI("http://test.host/admin/user/index/10") + + assert_equal "/admin/stuff/show/10", + controller.url_for(controller: "stuff", action: "show", id: 10, only_path: true) + end + + def test_paths_escaped + rs.draw do + get "file/*path" => "content#show_file", :as => "path" + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + # No + to space in URI escaping, only for query params. + results = rs.recognize_path "/file/hello+world/how+are+you%3F" + assert results, "Recognition should have succeeded" + assert_equal "hello+world/how+are+you?", results[:path] + + # Use %20 for space instead. + results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F" + assert results, "Recognition should have succeeded" + assert_equal "hello world/how are you?", results[:path] + end + + def test_paths_slashes_unescaped_with_ordered_parameters + rs.draw do + get "/file/*path" => "content#index", :as => "path" + end + + # No / to %2F in URI, only for query params. + assert_equal("/file/hello/world", setup_for_named_route.send(:path_path, ["hello", "world"])) + end + + def test_non_controllers_cannot_be_matched + rs.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } + end + + def test_should_list_options_diff_when_routing_constraints_dont_match + rs.draw do + get "post/:id" => "post#show", :constraints => { id: /\d+/ }, :as => "post" + end + assert_raise(ActionController::UrlGenerationError) do + url_for(rs, controller: "post", action: "show", bad_param: "foo", use_route: "post") + end + end + + def test_dynamic_path_allowed + rs.draw do + get "*path" => "content#show_file" + end + + assert_equal "/pages/boo", + url_for(rs, controller: "content", action: "show_file", path: %w(pages boo)) + end + + def test_dynamic_recall_paths_allowed + rs.draw do + get "*path" => "content#show_file" + end + + get URI("http://test.host/pages/boo") + assert_equal({ controller: "content", action: "show_file", path: "pages/boo" }, + controller.request.path_parameters) + + assert_equal "/pages/boo", + controller.url_for(only_path: true) + end + + def test_backwards + rs.draw do + ActiveSupport::Deprecation.silence do + get "page/:id(/:action)" => "pages#show" + get ":controller(/:action(/:id))" + end + end + + get URI("http://test.host/pages/show") + assert_equal "/page/20", controller.url_for(id: 20, only_path: true) + assert_equal "/page/20", url_for(rs, controller: "pages", id: 20, action: "show") + assert_equal "/pages/boo", url_for(rs, controller: "pages", action: "boo") + end + + def test_route_with_integer_default + rs.draw do + get "page(/:id)" => "content#show_page", :id => 1 + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal "/page", url_for(rs, controller: "content", action: "show_page") + assert_equal "/page", url_for(rs, controller: "content", action: "show_page", id: 1) + assert_equal "/page", url_for(rs, controller: "content", action: "show_page", id: "1") + assert_equal "/page/10", url_for(rs, controller: "content", action: "show_page", id: 10) + + assert_equal({ controller: "content", action: "show_page", id: 1 }, rs.recognize_path("/page")) + assert_equal({ controller: "content", action: "show_page", id: "1" }, rs.recognize_path("/page/1")) + assert_equal({ controller: "content", action: "show_page", id: "10" }, rs.recognize_path("/page/10")) + end + + # For newer revision + def test_route_with_text_default + rs.draw do + get "page/:id" => "content#show_page", :id => 1 + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal "/page/foo", url_for(rs, controller: "content", action: "show_page", id: "foo") + assert_equal({ controller: "content", action: "show_page", id: "foo" }, rs.recognize_path("/page/foo")) + + token = +"\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian + token.force_encoding(Encoding::BINARY) + escaped_token = CGI.escape(token) + + assert_equal "/page/" + escaped_token, url_for(rs, controller: "content", action: "show_page", id: token) + assert_equal({ controller: "content", action: "show_page", id: token }, rs.recognize_path("/page/#{escaped_token}")) + end + + def test_action_expiry + rs.draw { ActiveSupport::Deprecation.silence { get ":controller(/:action(/:id))" } } + get URI("http://test.host/content/show") + assert_equal "/content", controller.url_for(controller: "content", only_path: true) + end + + def test_requirement_should_prevent_optional_id + rs.draw do + get "post/:id" => "post#show", :constraints => { id: /\d+/ }, :as => "post" + end + + assert_equal "/post/10", url_for(rs, controller: "post", action: "show", id: 10) + + assert_raise(ActionController::UrlGenerationError) do + url_for(rs, controller: "post", action: "show") + end + end + + def test_both_requirement_and_optional + rs.draw do + get("test(/:year)" => "post#show", :as => "blog", + :defaults => { year: nil }, + :constraints => { year: /\d{4}/ } + ) + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal "/test", url_for(rs, controller: "post", action: "show") + assert_equal "/test", url_for(rs, controller: "post", action: "show", year: nil) + + assert_equal("http://test.host/test", setup_for_named_route.send(:blog_url)) + end + + def test_set_to_nil_forgets + rs.draw do + get "pages(/:year(/:month(/:day)))" => "content#list_pages", :month => nil, :day => nil + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal "/pages/2005", + url_for(rs, controller: "content", action: "list_pages", year: 2005) + assert_equal "/pages/2005/6", + url_for(rs, controller: "content", action: "list_pages", year: 2005, month: 6) + assert_equal "/pages/2005/6/12", + url_for(rs, controller: "content", action: "list_pages", year: 2005, month: 6, day: 12) + + get URI("http://test.host/pages/2005/6/12") + assert_equal({ controller: "content", action: "list_pages", year: "2005", month: "6", day: "12" }, + controller.request.path_parameters) + + assert_equal "/pages/2005/6/4", + controller.url_for(day: 4, only_path: true) + + assert_equal "/pages/2005/6", + controller.url_for(day: nil, only_path: true) + + assert_equal "/pages/2005", + controller.url_for(day: nil, month: nil, only_path: true) + end + + def test_root_url_generation_with_controller_and_action + rs.draw do + root to: "content#index" + end + + assert_equal "/", url_for(rs, controller: "content", action: "index") + assert_equal "/", url_for(rs, controller: "content") + end + + def test_named_root_url_generation_with_controller_and_action + rs.draw do + root to: "content#index", as: "home" + end + + assert_equal "/", url_for(rs, controller: "content", action: "index") + assert_equal "/", url_for(rs, controller: "content") + + assert_equal("http://test.host/", setup_for_named_route.send(:home_url)) + end + + def test_named_route_method + rs.draw do + get "categories" => "content#categories", :as => "categories" + + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end + end + + assert_equal "/categories", url_for(rs, controller: "content", action: "categories") + assert_equal "/content/hi", url_for(rs, controller: "content", action: "hi") + end + + def test_named_routes_array + test_named_route_method + assert_equal [:categories], rs.named_routes.names + end + + def test_nil_defaults + rs.draw do + get "journal" => "content#list_journal", + :date => nil, :user_id => nil + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal "/journal", url_for(rs, + controller: "content", + action: "list_journal", + date: nil, + user_id: nil) + end + + def setup_request_method_routes_for(method) + rs.draw do + match "/match" => "books##{method}", :via => method.to_sym + end + end + + %w(GET PATCH POST PUT DELETE).each do |request_method| + define_method("test_request_method_recognized_with_#{request_method}") do + setup_request_method_routes_for(request_method.downcase) + params = rs.recognize_path("/match", method: request_method) + assert_equal request_method.downcase, params[:action] + end + end + + def test_recognize_array_of_methods + rs.draw do + match "/match" => "books#get_or_post", :via => [:get, :post] + put "/match" => "books#not_get_or_post" + end + + params = rs.recognize_path("/match", method: :post) + assert_equal "get_or_post", params[:action] + + params = rs.recognize_path("/match", method: :put) + assert_equal "not_get_or_post", params[:action] + end + + def test_subpath_recognized + rs.draw do + ActiveSupport::Deprecation.silence do + get "/books/:id/edit" => "subpath_books#edit" + get "/items/:id/:action" => "subpath_books" + get "/posts/new/:action" => "subpath_books" + get "/posts/:id" => "subpath_books#show" + end + end + + hash = rs.recognize_path "/books/17/edit" + assert_not_nil hash + assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]] + + hash = rs.recognize_path "/items/3/complete" + assert_not_nil hash + assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]] + + hash = rs.recognize_path "/posts/new/preview" + assert_not_nil hash + assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]] + + hash = rs.recognize_path "/posts/7" + assert_not_nil hash + assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]] + end + + def test_subpath_generated + rs.draw do + ActiveSupport::Deprecation.silence do + get "/books/:id/edit" => "subpath_books#edit" + get "/items/:id/:action" => "subpath_books" + get "/posts/new/:action" => "subpath_books" + end + end + + assert_equal "/books/7/edit", url_for(rs, controller: "subpath_books", id: 7, action: "edit") + assert_equal "/items/15/complete", url_for(rs, controller: "subpath_books", id: 15, action: "complete") + assert_equal "/posts/new/preview", url_for(rs, controller: "subpath_books", action: "preview") + end + + def test_failed_constraints_raises_exception_with_violated_constraints + rs.draw do + get "foos/:id" => "foos#show", :as => "foo_with_requirement", :constraints => { id: /\d+/ } + end + + assert_raise(ActionController::UrlGenerationError) do + setup_for_named_route.send(:foo_with_requirement_url, "I am Against the constraints") + end + end + + def test_routes_changed_correctly_after_clear + rs = ::ActionDispatch::Routing::RouteSet.new + rs.draw do + get "ca" => "ca#aa" + get "cb" => "cb#ab" + get "cc" => "cc#ac" + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + get ":controller/:action/:id.:format" + end + end + + hash = rs.recognize_path "/cc" + + assert_not_nil hash + assert_equal %w(cc ac), [hash[:controller], hash[:action]] + + rs.draw do + get "cb" => "cb#ab" + get "cc" => "cc#ac" + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + get ":controller/:action/:id.:format" + end + end + + hash = rs.recognize_path "/cc" + + assert_not_nil hash + assert_equal %w(cc ac), [hash[:controller], hash[:action]] + end +end + +class RouteSetTest < ActiveSupport::TestCase + include RoutingTestHelpers + include ActionDispatch::RoutingVerbs + + attr_reader :set + alias :routes :set + attr_accessor :controller + + def setup + super + @set = make_set + end + + def request + @request ||= ActionController::TestRequest.new + end + + def default_route_set + @default_route_set ||= begin + set = ActionDispatch::Routing::RouteSet.new + set.draw do + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))" + end + end + set + end + end + + def test_generate_extras + set.draw { ActiveSupport::Deprecation.silence { get ":controller/(:action(/:id))" } } + path, extras = set.generate_extras(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") + assert_equal "/foo/bar/15", path + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_extra_keys + set.draw { ActiveSupport::Deprecation.silence { get ":controller/:action/:id" } } + extras = set.extra_keys(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_generate_extras_not_first + set.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id.:format" + get ":controller/:action/:id" + end + end + path, extras = set.generate_extras(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") + assert_equal "/foo/bar/15", path + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_generate_not_first + set.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id.:format" + get ":controller/:action/:id" + end + end + assert_equal "/foo/bar/15?this=hello", + url_for(set, controller: "foo", action: "bar", id: 15, this: "hello") + end + + def test_extra_keys_not_first + set.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id.:format" + get ":controller/:action/:id" + end + end + extras = set.extra_keys(controller: "foo", action: "bar", id: 15, this: "hello", that: "world") + assert_equal %w(that this), extras.map(&:to_s).sort + end + + def test_draw + assert_equal 0, set.routes.size + set.draw do + get "/hello/world" => "a#b" + end + assert_equal 1, set.routes.size + end + + def test_draw_symbol_controller_name + assert_equal 0, set.routes.size + set.draw do + get "/users/index" => "users#index" + end + set.recognize_path("/users/index", method: :get) + assert_equal 1, set.routes.size + end + + def test_named_draw + assert_equal 0, set.routes.size + set.draw do + get "/hello/world" => "a#b", :as => "hello" + end + assert_equal 1, set.routes.size + assert_equal set.routes.first, set.named_routes[:hello] + end + + def test_duplicate_named_route_raises_rather_than_pick_precedence + assert_raise ArgumentError do + set.draw do + get "/hello/world" => "a#b", :as => "hello" + get "/hello" => "a#b", :as => "hello" + end + end + end + + def setup_named_route_test + set.draw do + get "/people(/:id)" => "people#show", :as => "show" + get "/people" => "people#index", :as => "index" + get "/people/go/:foo/:bar/joe(/:id)" => "people#multi", :as => "multi" + get "/admin/users" => "admin/users#index", :as => "users" + end + + get URI("http://test.host/people") + controller + end + + def test_named_route_url_method + controller = setup_named_route_test + + assert_equal "http://test.host/people/5", controller.send(:show_url, id: 5) + assert_equal "/people/5", controller.send(:show_path, id: 5) + + assert_equal "http://test.host/people", controller.send(:index_url) + assert_equal "/people", controller.send(:index_path) + + assert_equal "http://test.host/admin/users", controller.send(:users_url) + assert_equal "/admin/users", controller.send(:users_path) + end + + def test_named_route_url_method_with_anchor + controller = setup_named_route_test + + assert_equal "http://test.host/people/5#location", controller.send(:show_url, id: 5, anchor: "location") + assert_equal "/people/5#location", controller.send(:show_path, id: 5, anchor: "location") + + assert_equal "http://test.host/people#location", controller.send(:index_url, anchor: "location") + assert_equal "/people#location", controller.send(:index_path, anchor: "location") + + assert_equal "http://test.host/admin/users#location", controller.send(:users_url, anchor: "location") + assert_equal "/admin/users#location", controller.send(:users_path, anchor: "location") + + assert_equal "http://test.host/people/go/7/hello/joe/5#location", + controller.send(:multi_url, 7, "hello", 5, anchor: "location") + + assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar#location", + controller.send(:multi_url, 7, "hello", 5, baz: "bar", anchor: "location") + + assert_equal "http://test.host/people?baz=bar#location", + controller.send(:index_url, baz: "bar", anchor: "location") + + assert_equal "http://test.host/people", controller.send(:index_url, anchor: nil) + assert_equal "http://test.host/people", controller.send(:index_url, anchor: false) + end + + def test_named_route_url_method_with_port + controller = setup_named_route_test + assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, port: 8080) + end + + def test_named_route_url_method_with_host + controller = setup_named_route_test + assert_equal "http://some.example.com/people/5", controller.send(:show_url, 5, host: "some.example.com") + end + + def test_named_route_url_method_with_protocol + controller = setup_named_route_test + assert_equal "https://test.host/people/5", controller.send(:show_url, 5, protocol: "https") + end + + def test_named_route_url_method_with_ordered_parameters + controller = setup_named_route_test + assert_equal "http://test.host/people/go/7/hello/joe/5", + controller.send(:multi_url, 7, "hello", 5) + end + + def test_named_route_url_method_with_ordered_parameters_and_hash + controller = setup_named_route_test + assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar", + controller.send(:multi_url, 7, "hello", 5, baz: "bar") + end + + def test_named_route_url_method_with_ordered_parameters_and_empty_hash + controller = setup_named_route_test + assert_equal "http://test.host/people/go/7/hello/joe/5", + controller.send(:multi_url, 7, "hello", 5, {}) + end + + def test_named_route_url_method_with_no_positional_arguments + controller = setup_named_route_test + assert_equal "http://test.host/people?baz=bar", + controller.send(:index_url, baz: "bar") + end + + def test_draw_default_route + set.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal 1, set.routes.size + + assert_equal "/users/show/10", url_for(set, controller: "users", action: "show", id: 10) + assert_equal "/users/index/10", url_for(set, controller: "users", id: 10) + + assert_equal({ controller: "users", action: "index", id: "10" }, set.recognize_path("/users/index/10")) + assert_equal({ controller: "users", action: "index", id: "10" }, set.recognize_path("/users/index/10/")) + end + + def test_route_with_parameter_shell + set.draw do + get "page/:id" => "pages#show", :id => /\d+/ + + ActiveSupport::Deprecation.silence do + get "/:controller(/:action(/:id))" + end + end + + assert_equal({ controller: "pages", action: "index" }, request_path_params("/pages")) + assert_equal({ controller: "pages", action: "index" }, request_path_params("/pages/index")) + assert_equal({ controller: "pages", action: "list" }, request_path_params("/pages/list")) + + assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/pages/show/10")) + assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/page/10")) + end + + def test_route_constraints_on_request_object_with_anchors_are_valid + assert_nothing_raised do + set.draw do + get "page/:id" => "pages#show", :constraints => { host: /^foo$/ } + end + end + end + + def test_route_constraints_with_anchor_chars_are_invalid + assert_raise ArgumentError do + set.draw do + get "page/:id" => "pages#show", :id => /^\d+/ + end + end + assert_raise ArgumentError do + set.draw do + get "page/:id" => "pages#show", :id => /\A\d+/ + end + end + assert_raise ArgumentError do + set.draw do + get "page/:id" => "pages#show", :id => /\d+$/ + end + end + assert_raise ArgumentError do + set.draw do + get "page/:id" => "pages#show", :id => /\d+\Z/ + end + end + assert_raise ArgumentError do + set.draw do + get "page/:id" => "pages#show", :id => /\d+\z/ + end + end + end + + def test_route_constraints_with_options_method_condition_is_valid + assert_nothing_raised do + set.draw do + match "valid/route" => "pages#show", :via => :options + end + end + end + + def test_route_error_with_missing_controller + set.draw do + get "/people" => "missing#index" + end + + assert_raises(ActionController::RoutingError) { request_path_params "/people" } + end + + def test_recognize_with_encoded_id_and_regex + set.draw do + get "page/:id" => "pages#show", :id => /[a-zA-Z0-9\+]+/ + end + + assert_equal({ controller: "pages", action: "show", id: "10" }, request_path_params("/page/10")) + assert_equal({ controller: "pages", action: "show", id: "hello+world" }, request_path_params("/page/hello+world")) + end + + def test_recognize_with_http_methods + set.draw do + get "/people" => "people#index", :as => "people" + post "/people" => "people#create" + get "/people/:id" => "people#show", :as => "person" + put "/people/:id" => "people#update" + patch "/people/:id" => "people#update" + delete "/people/:id" => "people#destroy" + end + + params = request_path_params("/people", method: :get) + assert_equal("index", params[:action]) + + params = request_path_params("/people", method: :post) + assert_equal("create", params[:action]) + + params = request_path_params("/people/5", method: :put) + assert_equal("update", params[:action]) + + params = request_path_params("/people/5", method: :patch) + assert_equal("update", params[:action]) + + assert_raise(ActionController::UnknownHttpMethod) { + request_path_params("/people", method: :bacon) + } + + params = request_path_params("/people/5", method: :get) + assert_equal("show", params[:action]) + assert_equal("5", params[:id]) + + params = request_path_params("/people/5", method: :put) + assert_equal("update", params[:action]) + assert_equal("5", params[:id]) + + params = request_path_params("/people/5", method: :patch) + assert_equal("update", params[:action]) + assert_equal("5", params[:id]) + + params = request_path_params("/people/5", method: :delete) + assert_equal("destroy", params[:action]) + assert_equal("5", params[:id]) + + assert_raise(ActionController::RoutingError) { + request_path_params("/people/5", method: :post) + } + end + + def test_recognize_with_alias_in_conditions + set.draw do + match "/people" => "people#index", :as => "people", :via => :get + root to: "people#index" + end + + params = request_path_params("/people", method: :get) + assert_equal("people", params[:controller]) + assert_equal("index", params[:action]) + + params = request_path_params("/", method: :get) + assert_equal("people", params[:controller]) + assert_equal("index", params[:action]) + end + + def test_typo_recognition + set.draw do + get "articles/:year/:month/:day/:title" => "articles#permalink", + :year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/ + end + + params = request_path_params("/articles/2005/11/05/a-very-interesting-article", method: :get) + assert_equal("permalink", params[:action]) + assert_equal("2005", params[:year]) + assert_equal("11", params[:month]) + assert_equal("05", params[:day]) + assert_equal("a-very-interesting-article", params[:title]) + end + + def test_routing_traversal_does_not_load_extra_classes + assert_not Object.const_defined?("Profiler__"), "Profiler should not be loaded" + set.draw do + get "/profile" => "profile#index" + end + + request_path_params("/profile") rescue nil + + assert_not Object.const_defined?("Profiler__"), "Profiler should not be loaded" + end + + def test_recognize_with_conditions_and_format + set.draw do + get "people/:id" => "people#show", :as => "person" + put "people/:id" => "people#update" + patch "people/:id" => "people#update" + get "people/:id(.:format)" => "people#show" + end + + params = request_path_params("/people/5", method: :get) + assert_equal("show", params[:action]) + assert_equal("5", params[:id]) + + params = request_path_params("/people/5", method: :put) + assert_equal("update", params[:action]) + + params = request_path_params("/people/5", method: :patch) + assert_equal("update", params[:action]) + + params = request_path_params("/people/5.png", method: :get) + assert_equal("show", params[:action]) + assert_equal("5", params[:id]) + assert_equal("png", params[:format]) + end + + def test_generate_with_default_action + set.draw do + get "/people", controller: "people", action: "index" + get "/people/list", controller: "people", action: "list" + end + + url = url_for(set, controller: "people", action: "list") + assert_equal "/people/list", url + end + + def test_root_map + set.draw { root to: "people#index" } + + params = request_path_params("", method: :get) + assert_equal("people", params[:controller]) + assert_equal("index", params[:action]) + end + + def test_namespace + set.draw do + namespace "api" do + get "inventory" => "products#inventory" + end + end + + params = request_path_params("/api/inventory", method: :get) + assert_equal("api/products", params[:controller]) + assert_equal("inventory", params[:action]) + end + + def test_namespaced_root_map + set.draw do + namespace "api" do + root to: "products#index" + end + end + + params = request_path_params("/api", method: :get) + assert_equal("api/products", params[:controller]) + assert_equal("index", params[:action]) + end + + def test_namespace_with_path_prefix + set.draw do + scope module: "api", path: "prefix" do + get "inventory" => "products#inventory" + end + end + + params = request_path_params("/prefix/inventory", method: :get) + assert_equal("api/products", params[:controller]) + assert_equal("inventory", params[:action]) + end + + def test_namespace_with_blank_path_prefix + set.draw do + scope module: "api", path: "" do + get "inventory" => "products#inventory" + end + end + + params = request_path_params("/inventory", method: :get) + assert_equal("api/products", params[:controller]) + assert_equal("inventory", params[:action]) + end + + def test_id_is_sticky_when_it_ought_to_be + @set = make_set false + + set.draw do + ActiveSupport::Deprecation.silence do + get ":controller/:id/:action" + end + end + + get URI("http://test.host/people/7/show") + + assert_equal "/people/7/destroy", controller.url_for(action: "destroy", only_path: true) + end + + def test_use_static_path_when_possible + @set = make_set false + + set.draw do + get "about" => "welcome#about" + + ActiveSupport::Deprecation.silence do + get ":controller/:id/:action" + end + end + + get URI("http://test.host/welcom/get/7") + + assert_equal "/about", controller.url_for(controller: "welcome", + action: "about", + only_path: true) + end + + def test_generate + set.draw { ActiveSupport::Deprecation.silence { get ":controller/:action/:id" } } + + args = { controller: "foo", action: "bar", id: "7", x: "y" } + assert_equal "/foo/bar/7?x=y", url_for(set, args) + assert_equal ["/foo/bar/7", [:x]], set.generate_extras(args) + assert_equal [:x], set.extra_keys(args) + end + + def test_generate_with_path_prefix + set.draw do + scope "my" do + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end + end + end + + args = { controller: "foo", action: "bar", id: "7", x: "y" } + assert_equal "/my/foo/bar/7?x=y", url_for(set, args) + end + + def test_generate_with_blank_path_prefix + set.draw do + scope "" do + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end + end + end + + args = { controller: "foo", action: "bar", id: "7", x: "y" } + assert_equal "/foo/bar/7?x=y", url_for(set, args) + end + + def test_named_routes_are_never_relative_to_modules + @set = make_set false + + set.draw do + ActiveSupport::Deprecation.silence do + get "/connection/manage(/:action)" => "connection/manage#index" + get "/connection/connection" => "connection/connection#index" + get "/connection" => "connection#index", :as => "family_connection" + end + end + + assert_equal({ controller: "connection/manage", + action: "index", }, request_path_params("/connection/manage")) + + url = controller.url_for(controller: "connection", only_path: true) + assert_equal "/connection/connection", url + + url = controller.url_for(use_route: "family_connection", + controller: "connection", only_path: true) + assert_equal "/connection", url + end + + def test_action_left_off_when_id_is_recalled + @set = make_set false + + set.draw do + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end + end + + get URI("http://test.host/books/show/10") + + assert_equal "/books", controller.url_for(controller: "books", + only_path: true, + action: "index") + end + + def test_query_params_will_be_shown_when_recalled + @set = make_set false + + set.draw do + get "show_weblog/:parameter" => "weblog#show" + + ActiveSupport::Deprecation.silence do + get ":controller(/:action(/:id))" + end + end + + get URI("http://test.host/weblog/show/1") + + assert_equal "/weblog/edit?parameter=1", controller.url_for( + action: "edit", parameter: 1, only_path: true) + end + + def test_format_is_not_inherit + set.draw do + get "/posts(.:format)" => "posts#index" + end + + get URI("http://test.host/posts.xml") + assert_equal({ controller: "posts", action: "index", format: "xml" }, + controller.request.path_parameters) + + assert_equal "/posts", controller.url_for( + controller: "posts", only_path: true) + + assert_equal "/posts.xml", controller.url_for( + controller: "posts", format: "xml", only_path: true) + end + + def test_expiry_determination_should_consider_values_with_to_param + @set = make_set false + + set.draw { ActiveSupport::Deprecation.silence { get "projects/:project_id/:controller/:action" } } + + get URI("http://test.host/projects/1/weblog/show") + + assert_equal( + { controller: "weblog", action: "show", project_id: "1" }, + controller.request.path_parameters) + + assert_equal "/projects/1/weblog/show", + controller.url_for(action: "show", project_id: 1, only_path: true) + end + + def test_named_route_in_nested_resource + set.draw do + resources :projects do + member do + get "milestones" => "milestones#index", :as => "milestones" + end + end + end + + params = set.recognize_path("/projects/1/milestones", method: :get) + assert_equal("milestones", params[:controller]) + assert_equal("index", params[:action]) + end + + def test_setting_root_in_namespace_using_symbol + assert_nothing_raised do + set.draw do + namespace :admin do + root to: "home#index" + end + end + end + end + + def test_setting_root_in_namespace_using_string + assert_nothing_raised do + set.draw do + namespace "admin" do + root to: "home#index" + end + end + end + end + + def test_route_constraints_with_unsupported_regexp_options_must_error + assert_raise ArgumentError do + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/m } + end + end + end + + def test_route_constraints_with_supported_options_must_not_error + assert_nothing_raised do + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/i } + end + end + assert_nothing_raised do + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/x } + end + end + end + + def test_route_with_subdomain_and_constraints_must_receive_params + name_param = nil + set.draw do + get "page/:name" => "pages#show", :constraints => lambda { |request| + name_param = request.params[:name] + return true + } + end + assert_equal({ controller: "pages", action: "show", name: "mypage" }, + set.recognize_path("http://subdomain.example.org/page/mypage")) + assert_equal(name_param, "mypage") + end + + def test_route_requirement_recognize_with_ignore_case + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/i } + end + assert_equal({ controller: "pages", action: "show", name: "jamis" }, set.recognize_path("/page/jamis")) + assert_raise ActionController::RoutingError do + set.recognize_path("/page/davidjamis") + end + assert_equal({ controller: "pages", action: "show", name: "DAVID" }, set.recognize_path("/page/DAVID")) + end + + def test_route_requirement_generate_with_ignore_case + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: /(david|jamis)/i } + end + + url = url_for(set, controller: "pages", action: "show", name: "david") + assert_equal "/page/david", url + assert_raise(ActionController::UrlGenerationError) do + url_for(set, controller: "pages", action: "show", name: "davidjamis") + end + url = url_for(set, controller: "pages", action: "show", name: "JAMIS") + assert_equal "/page/JAMIS", url + end + + def test_route_requirement_recognize_with_extended_syntax + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/x } + end + assert_equal({ controller: "pages", action: "show", name: "jamis" }, set.recognize_path("/page/jamis")) + assert_equal({ controller: "pages", action: "show", name: "david" }, set.recognize_path("/page/david")) + assert_raise ActionController::RoutingError do + set.recognize_path("/page/david #The Creator") + end + assert_raise ActionController::RoutingError do + set.recognize_path("/page/David") + end + end + + def test_route_requirement_with_xi_modifiers + set.draw do + get "page/:name" => "pages#show", + :constraints => { name: / # Desperately overcommented regexp + ( #Either + david #The Creator + | #Or + jamis #The Deployer + )/xi } + end + + assert_equal({ controller: "pages", action: "show", name: "JAMIS" }, + set.recognize_path("/page/JAMIS")) + + assert_equal "/page/JAMIS", + url_for(set, controller: "pages", action: "show", name: "JAMIS") + end + + def test_routes_with_symbols + set.draw do + get "unnamed", controller: :pages, action: :show, name: :as_symbol + get "named", controller: :pages, action: :show, name: :as_symbol, as: :named + end + assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/unnamed")) + assert_equal({ controller: "pages", action: "show", name: :as_symbol }, set.recognize_path("/named")) + end + + def test_regexp_chunk_should_add_question_mark_for_optionals + set.draw do + get "/" => "foo#index" + get "/hello" => "bar#index" + end + + assert_equal "/", url_for(set, controller: "foo") + assert_equal "/hello", url_for(set, controller: "bar") + + assert_equal({ controller: "foo", action: "index" }, set.recognize_path("/")) + assert_equal({ controller: "bar", action: "index" }, set.recognize_path("/hello")) + end + + def test_assign_route_options_with_anchor_chars + set.draw do + ActiveSupport::Deprecation.silence do + get "/cars/:action/:person/:car/", controller: "cars" + end + end + + assert_equal "/cars/buy/1/2", url_for(set, controller: "cars", action: "buy", person: "1", car: "2") + + assert_equal({ controller: "cars", action: "buy", person: "1", car: "2" }, set.recognize_path("/cars/buy/1/2")) + end + + def test_segmentation_of_dot_path + set.draw do + ActiveSupport::Deprecation.silence do + get "/books/:action.rss", controller: "books" + end + end + + assert_equal "/books/list.rss", url_for(set, controller: "books", action: "list") + + assert_equal({ controller: "books", action: "list" }, set.recognize_path("/books/list.rss")) + end + + def test_segmentation_of_dynamic_dot_path + set.draw do + ActiveSupport::Deprecation.silence do + get "/books(/:action(.:format))", controller: "books" + end + end + + assert_equal "/books/list.rss", url_for(set, controller: "books", action: "list", format: "rss") + assert_equal "/books/list.xml", url_for(set, controller: "books", action: "list", format: "xml") + assert_equal "/books/list", url_for(set, controller: "books", action: "list") + assert_equal "/books", url_for(set, controller: "books", action: "index") + + assert_equal({ controller: "books", action: "list", format: "rss" }, set.recognize_path("/books/list.rss")) + assert_equal({ controller: "books", action: "list", format: "xml" }, set.recognize_path("/books/list.xml")) + assert_equal({ controller: "books", action: "list" }, set.recognize_path("/books/list")) + assert_equal({ controller: "books", action: "index" }, set.recognize_path("/books")) + end + + def test_slashes_are_implied + set.draw { ActiveSupport::Deprecation.silence { get("/:controller(/:action(/:id))") } } + + assert_equal "/content", url_for(set, controller: "content", action: "index") + assert_equal "/content/list", url_for(set, controller: "content", action: "list") + assert_equal "/content/show/1", url_for(set, controller: "content", action: "show", id: "1") + + assert_equal({ controller: "content", action: "index" }, set.recognize_path("/content")) + assert_equal({ controller: "content", action: "index" }, set.recognize_path("/content/index")) + assert_equal({ controller: "content", action: "list" }, set.recognize_path("/content/list")) + assert_equal({ controller: "content", action: "show", id: "1" }, set.recognize_path("/content/show/1")) + end + + def test_default_route_recognition + expected = { controller: "pages", action: "show", id: "10" } + assert_equal expected, default_route_set.recognize_path("/pages/show/10") + assert_equal expected, default_route_set.recognize_path("/pages/show/10/") + + expected[:id] = "jamis" + assert_equal expected, default_route_set.recognize_path("/pages/show/jamis/") + + expected.delete :id + assert_equal expected, default_route_set.recognize_path("/pages/show") + assert_equal expected, default_route_set.recognize_path("/pages/show/") + + expected[:action] = "index" + assert_equal expected, default_route_set.recognize_path("/pages/") + assert_equal expected, default_route_set.recognize_path("/pages") + + assert_raise(ActionController::RoutingError) { default_route_set.recognize_path("/") } + assert_raise(ActionController::RoutingError) { default_route_set.recognize_path("/pages/how/goood/it/is/to/be/free") } + end + + def test_default_route_should_omit_default_action + assert_equal "/accounts", url_for(default_route_set, controller: "accounts", action: "index") + end + + def test_default_route_should_include_default_action_when_id_present + assert_equal "/accounts/index/20", url_for(default_route_set, controller: "accounts", action: "index", id: "20") + end + + def test_default_route_should_work_with_action_but_no_id + assert_equal "/accounts/list_all", url_for(default_route_set, controller: "accounts", action: "list_all") + end + + def test_default_route_should_uri_escape_pluses + expected = { controller: "pages", action: "show", id: "hello world" } + assert_equal expected, default_route_set.recognize_path("/pages/show/hello%20world") + assert_equal "/pages/show/hello%20world", url_for(default_route_set, expected) + + expected[:id] = "hello+world" + assert_equal expected, default_route_set.recognize_path("/pages/show/hello+world") + assert_equal expected, default_route_set.recognize_path("/pages/show/hello%2Bworld") + assert_equal "/pages/show/hello+world", url_for(default_route_set, expected) + end + + def test_build_empty_query_string + assert_uri_equal "/foo", url_for(default_route_set, controller: "foo") + end + + def test_build_query_string_with_nil_value + assert_uri_equal "/foo", url_for(default_route_set, controller: "foo", x: nil) + end + + def test_simple_build_query_string + assert_uri_equal "/foo?x=1&y=2", url_for(default_route_set, controller: "foo", x: "1", y: "2") + end + + def test_convert_ints_build_query_string + assert_uri_equal "/foo?x=1&y=2", url_for(default_route_set, controller: "foo", x: 1, y: 2) + end + + def test_escape_spaces_build_query_string + assert_uri_equal "/foo?x=hello+world&y=goodbye+world", url_for(default_route_set, controller: "foo", x: "hello world", y: "goodbye world") + end + + def test_expand_array_build_query_string + assert_uri_equal "/foo?x%5B%5D=1&x%5B%5D=2", url_for(default_route_set, controller: "foo", x: [1, 2]) + end + + def test_escape_spaces_build_query_string_selected_keys + assert_uri_equal "/foo?x=hello+world", url_for(default_route_set, controller: "foo", x: "hello world") + end + + def test_generate_with_default_params + set.draw do + get "dummy/page/:page" => "dummy#show" + get "dummy/dots/page.:page" => "dummy#dots" + get "ibocorp(/:page)" => "ibocorp#show", + :constraints => { page: /\d+/ }, + :defaults => { page: 1 } + + ActiveSupport::Deprecation.silence do + get ":controller/:action/:id" + end + end + + assert_equal "/ibocorp", url_for(set, controller: "ibocorp", action: "show", page: 1) + end + + include ActionDispatch::RoutingVerbs + + alias :routes :set + + def test_generate_with_optional_params_recalls_last_request + @set = make_set false + + set.draw do + get "blog/", controller: "blog", action: "index" + + get "blog(/:year(/:month(/:day)))", + controller: "blog", + action: "show_date", + constraints: { year: /(19|20)\d\d/, month: /[01]?\d/, day: /[0-3]?\d/ }, + day: nil, month: nil + + get "blog/show/:id", controller: "blog", action: "show", id: /\d+/ + + ActiveSupport::Deprecation.silence do + get "blog/:controller/:action(/:id)" + end + + get "*anything", controller: "blog", action: "unknown_request" + end + + recognize_path = ->(path) { + get(URI("http://example.org" + path)) + controller.request.path_parameters + } + + assert_equal({ controller: "blog", action: "index" }, recognize_path.("/blog")) + assert_equal({ controller: "blog", action: "show", id: "123" }, recognize_path.("/blog/show/123")) + assert_equal({ controller: "blog", action: "show_date", year: "2004", day: nil, month: nil }, recognize_path.("/blog/2004")) + assert_equal({ controller: "blog", action: "show_date", year: "2004", month: "12", day: nil }, recognize_path.("/blog/2004/12")) + assert_equal({ controller: "blog", action: "show_date", year: "2004", month: "12", day: "25" }, recognize_path.("/blog/2004/12/25")) + assert_equal({ controller: "articles", action: "edit", id: "123" }, recognize_path.("/blog/articles/edit/123")) + assert_equal({ controller: "articles", action: "show_stats" }, recognize_path.("/blog/articles/show_stats")) + assert_equal({ controller: "blog", action: "unknown_request", anything: "blog/wibble" }, recognize_path.("/blog/wibble")) + assert_equal({ controller: "blog", action: "unknown_request", anything: "junk" }, recognize_path.("/junk")) + + get URI("http://example.org/blog/2006/07/28") + + assert_equal({ controller: "blog", action: "show_date", year: "2006", month: "07", day: "28" }, controller.request.path_parameters) + assert_equal("/blog/2006/07/25", controller.url_for(day: 25, only_path: true)) + assert_equal("/blog/2005", controller.url_for(year: 2005, only_path: true)) + assert_equal("/blog/show/123", controller.url_for(action: "show", id: 123, only_path: true)) + assert_equal("/blog/2006", controller.url_for(year: 2006, only_path: true)) + assert_equal("/blog/2006", controller.url_for(year: 2006, month: nil, only_path: true)) + end + + private + def assert_uri_equal(expected, actual) + assert_equal(sort_query_string_params(expected), sort_query_string_params(actual)) + end + + def sort_query_string_params(uri) + path, qs = uri.split("?") + qs = qs.split("&").sort.join("&") if qs + qs ? "#{path}?#{qs}" : path + end +end + +class RackMountIntegrationTests < ActiveSupport::TestCase + include RoutingTestHelpers + + Model = Struct.new(:to_param) + + Mapping = lambda { + namespace :admin do + resources :users, :posts + end + + namespace "api" do + root to: "users#index" + end + + get "/blog(/:year(/:month(/:day)))" => "posts#show_date", + :constraints => { + year: /(19|20)\d\d/, + month: /[01]?\d/, + day: /[0-3]?\d/ + }, + :day => nil, + :month => nil + + get "archive/:year", controller: "archive", action: "index", + defaults: { year: nil }, + constraints: { year: /\d{4}/ }, + as: "blog" + + resources :people + get "legacy/people" => "people#index", :legacy => "true" + + get "symbols", controller: :symbols, action: :show, name: :as_symbol + get "id_default(/:id)" => "foo#id_default", :id => 1 + match "get_or_post" => "foo#get_or_post", :via => [:get, :post] + get "optional/:optional" => "posts#index" + get "projects/:project_id" => "project#index", :as => "project" + get "clients" => "projects#index" + + get "ignorecase/geocode/:postalcode" => "geocode#show", :postalcode => /hx\d\d-\d[a-z]{2}/i + get "extended/geocode/:postalcode" => "geocode#show", :constraints => { + postalcode: /# Postcode format + \d{5} #Prefix + (-\d{4})? #Suffix + /x + }, :as => "geocode" + + get "news(.:format)" => "news#index" + + ActiveSupport::Deprecation.silence do + get "comment/:id(/:action)" => "comments#show" + get "ws/:controller(/:action(/:id))", ws: true + get "account(/:action)" => "account#subscription" + get "pages/:page_id/:controller(/:action(/:id))" + get ":controller/ping", action: "ping" + end + + get "こんにちは/世界", controller: "news", action: "index" + + ActiveSupport::Deprecation.silence do + match ":controller(/:action(/:id))(.:format)", via: :all + end + + root to: "news#index" + } + + attr_reader :routes + attr_reader :controller + + def setup + @routes = ActionDispatch::Routing::RouteSet.new + @routes.draw(&Mapping) + end + + def test_recognize_path + assert_equal({ controller: "admin/users", action: "index" }, @routes.recognize_path("/admin/users", method: :get)) + assert_equal({ controller: "admin/users", action: "create" }, @routes.recognize_path("/admin/users", method: :post)) + assert_equal({ controller: "admin/users", action: "new" }, @routes.recognize_path("/admin/users/new", method: :get)) + assert_equal({ controller: "admin/users", action: "show", id: "1" }, @routes.recognize_path("/admin/users/1", method: :get)) + assert_equal({ controller: "admin/users", action: "update", id: "1" }, @routes.recognize_path("/admin/users/1", method: :put)) + assert_equal({ controller: "admin/users", action: "destroy", id: "1" }, @routes.recognize_path("/admin/users/1", method: :delete)) + assert_equal({ controller: "admin/users", action: "edit", id: "1" }, @routes.recognize_path("/admin/users/1/edit", method: :get)) + + assert_equal({ controller: "admin/posts", action: "index" }, @routes.recognize_path("/admin/posts", method: :get)) + assert_equal({ controller: "admin/posts", action: "new" }, @routes.recognize_path("/admin/posts/new", method: :get)) + + assert_equal({ controller: "api/users", action: "index" }, @routes.recognize_path("/api", method: :get)) + assert_equal({ controller: "api/users", action: "index" }, @routes.recognize_path("/api/", method: :get)) + + assert_equal({ controller: "posts", action: "show_date", year: "2009", month: nil, day: nil }, @routes.recognize_path("/blog/2009", method: :get)) + assert_equal({ controller: "posts", action: "show_date", year: "2009", month: "01", day: nil }, @routes.recognize_path("/blog/2009/01", method: :get)) + assert_equal({ controller: "posts", action: "show_date", year: "2009", month: "01", day: "01" }, @routes.recognize_path("/blog/2009/01/01", method: :get)) + + assert_equal({ controller: "archive", action: "index", year: "2010" }, @routes.recognize_path("/archive/2010")) + assert_equal({ controller: "archive", action: "index" }, @routes.recognize_path("/archive")) + + assert_equal({ controller: "people", action: "index" }, @routes.recognize_path("/people", method: :get)) + assert_equal({ controller: "people", action: "index", format: "xml" }, @routes.recognize_path("/people.xml", method: :get)) + assert_equal({ controller: "people", action: "create" }, @routes.recognize_path("/people", method: :post)) + assert_equal({ controller: "people", action: "new" }, @routes.recognize_path("/people/new", method: :get)) + assert_equal({ controller: "people", action: "show", id: "1" }, @routes.recognize_path("/people/1", method: :get)) + assert_equal({ controller: "people", action: "show", id: "1", format: "xml" }, @routes.recognize_path("/people/1.xml", method: :get)) + assert_equal({ controller: "people", action: "update", id: "1" }, @routes.recognize_path("/people/1", method: :put)) + assert_equal({ controller: "people", action: "destroy", id: "1" }, @routes.recognize_path("/people/1", method: :delete)) + assert_equal({ controller: "people", action: "edit", id: "1" }, @routes.recognize_path("/people/1/edit", method: :get)) + assert_equal({ controller: "people", action: "edit", id: "1", format: "xml" }, @routes.recognize_path("/people/1/edit.xml", method: :get)) + + assert_equal({ controller: "symbols", action: "show", name: :as_symbol }, @routes.recognize_path("/symbols")) + assert_equal({ controller: "foo", action: "id_default", id: "1" }, @routes.recognize_path("/id_default/1")) + assert_equal({ controller: "foo", action: "id_default", id: "2" }, @routes.recognize_path("/id_default/2")) + assert_equal({ controller: "foo", action: "id_default", id: 1 }, @routes.recognize_path("/id_default")) + assert_equal({ controller: "foo", action: "get_or_post" }, @routes.recognize_path("/get_or_post", method: :get)) + assert_equal({ controller: "foo", action: "get_or_post" }, @routes.recognize_path("/get_or_post", method: :post)) + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/get_or_post", method: :put) } + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/get_or_post", method: :delete) } + + assert_equal({ controller: "posts", action: "index", optional: "bar" }, @routes.recognize_path("/optional/bar")) + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/optional") } + + assert_equal({ controller: "posts", action: "show", id: "1", ws: true }, @routes.recognize_path("/ws/posts/show/1", method: :get)) + assert_equal({ controller: "posts", action: "list", ws: true }, @routes.recognize_path("/ws/posts/list", method: :get)) + assert_equal({ controller: "posts", action: "index", ws: true }, @routes.recognize_path("/ws/posts", method: :get)) + + assert_equal({ controller: "account", action: "subscription" }, @routes.recognize_path("/account", method: :get)) + assert_equal({ controller: "account", action: "subscription" }, @routes.recognize_path("/account/subscription", method: :get)) + assert_equal({ controller: "account", action: "billing" }, @routes.recognize_path("/account/billing", method: :get)) + + assert_equal({ page_id: "1", controller: "notes", action: "index" }, @routes.recognize_path("/pages/1/notes", method: :get)) + assert_equal({ page_id: "1", controller: "notes", action: "list" }, @routes.recognize_path("/pages/1/notes/list", method: :get)) + assert_equal({ page_id: "1", controller: "notes", action: "show", id: "2" }, @routes.recognize_path("/pages/1/notes/show/2", method: :get)) + + assert_equal({ controller: "posts", action: "ping" }, @routes.recognize_path("/posts/ping", method: :get)) + assert_equal({ controller: "posts", action: "index" }, @routes.recognize_path("/posts", method: :get)) + assert_equal({ controller: "posts", action: "index" }, @routes.recognize_path("/posts/index", method: :get)) + assert_equal({ controller: "posts", action: "show" }, @routes.recognize_path("/posts/show", method: :get)) + assert_equal({ controller: "posts", action: "show", id: "1" }, @routes.recognize_path("/posts/show/1", method: :get)) + assert_equal({ controller: "posts", action: "create" }, @routes.recognize_path("/posts/create", method: :post)) + + assert_equal({ controller: "geocode", action: "show", postalcode: "hx12-1az" }, @routes.recognize_path("/ignorecase/geocode/hx12-1az")) + assert_equal({ controller: "geocode", action: "show", postalcode: "hx12-1AZ" }, @routes.recognize_path("/ignorecase/geocode/hx12-1AZ")) + assert_equal({ controller: "geocode", action: "show", postalcode: "12345-1234" }, @routes.recognize_path("/extended/geocode/12345-1234")) + assert_equal({ controller: "geocode", action: "show", postalcode: "12345" }, @routes.recognize_path("/extended/geocode/12345")) + + assert_equal({ controller: "news", action: "index" }, @routes.recognize_path("/", method: :get)) + assert_equal({ controller: "news", action: "index", format: "rss" }, @routes.recognize_path("/news.rss", method: :get)) + + assert_raise(ActionController::RoutingError) { @routes.recognize_path("/none", method: :get) } + end + + def test_generate_extras + assert_equal ["/people", []], @routes.generate_extras(controller: "people") + assert_equal ["/people", [:foo]], @routes.generate_extras(controller: "people", foo: "bar") + assert_equal ["/people", []], @routes.generate_extras(controller: "people", action: "index") + assert_equal ["/people", [:foo]], @routes.generate_extras(controller: "people", action: "index", foo: "bar") + assert_equal ["/people/new", []], @routes.generate_extras(controller: "people", action: "new") + assert_equal ["/people/new", [:foo]], @routes.generate_extras(controller: "people", action: "new", foo: "bar") + assert_equal ["/people/1", []], @routes.generate_extras(controller: "people", action: "show", id: "1") + assert_equal ["/people/1", [:bar, :foo]], sort_extras!(@routes.generate_extras(controller: "people", action: "show", id: "1", foo: "2", bar: "3")) + assert_equal ["/people", [:person]], @routes.generate_extras(controller: "people", action: "create", person: { first_name: "Josh", last_name: "Peek" }) + assert_equal ["/people", [:people]], @routes.generate_extras(controller: "people", action: "create", people: ["Josh", "Dave"]) + + assert_equal ["/posts/show/1", []], @routes.generate_extras(controller: "posts", action: "show", id: "1") + assert_equal ["/posts/show/1", [:bar, :foo]], sort_extras!(@routes.generate_extras(controller: "posts", action: "show", id: "1", foo: "2", bar: "3")) + assert_equal ["/posts", []], @routes.generate_extras(controller: "posts", action: "index") + assert_equal ["/posts", [:foo]], @routes.generate_extras(controller: "posts", action: "index", foo: "bar") + end + + def test_extras + params = { controller: "people" } + assert_equal [], @routes.extra_keys(params) + assert_equal({ controller: "people", action: "index" }, params) + + params = { controller: "people", foo: "bar" } + assert_equal [:foo], @routes.extra_keys(params) + assert_equal({ controller: "people", action: "index", foo: "bar" }, params) + + params = { controller: "people", action: "create", person: { name: "Josh" } } + assert_equal [:person], @routes.extra_keys(params) + assert_equal({ controller: "people", action: "create", person: { name: "Josh" } }, params) + end + + def test_unicode_path + assert_equal({ controller: "news", action: "index" }, @routes.recognize_path(URI.parser.escape("こんにちは/世界"), method: :get)) + end + + def test_downcased_unicode_path + assert_equal({ controller: "news", action: "index" }, @routes.recognize_path(URI.parser.escape("こんにちは/世界").downcase, method: :get)) + end + + private + def sort_extras!(extras) + if extras.length == 2 + extras[1].sort! { |a, b| a.to_s <=> b.to_s } + end + extras + end +end |