aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/test
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/test')
-rw-r--r--actionpack/test/abstract/callbacks_test.rb315
-rw-r--r--actionpack/test/abstract/collector_test.rb63
-rw-r--r--actionpack/test/abstract/translation_test.rb50
-rw-r--r--actionpack/test/abstract_unit.rb499
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb63
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb640
-rw-r--r--actionpack/test/controller/assert_select_test.rb356
-rw-r--r--actionpack/test/controller/base_test.rb302
-rw-r--r--actionpack/test/controller/caching_test.rb351
-rw-r--r--actionpack/test/controller/content_type_test.rb167
-rw-r--r--actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb0
-rw-r--r--actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb0
-rw-r--r--actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb0
-rw-r--r--actionpack/test/controller/default_url_options_with_before_action_test.rb29
-rw-r--r--actionpack/test/controller/filters_test.rb1047
-rw-r--r--actionpack/test/controller/flash_hash_test.rb202
-rw-r--r--actionpack/test/controller/flash_test.rb335
-rw-r--r--actionpack/test/controller/force_ssl_test.rb324
-rw-r--r--actionpack/test/controller/helper_test.rb275
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb144
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb289
-rw-r--r--actionpack/test/controller/http_token_authentication_test.rb181
-rw-r--r--actionpack/test/controller/integration_test.rb809
-rw-r--r--actionpack/test/controller/live_stream_test.rb454
-rw-r--r--actionpack/test/controller/localized_templates_test.rb46
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb325
-rw-r--r--actionpack/test/controller/mime/accept_format_test.rb92
-rw-r--r--actionpack/test/controller/mime/respond_to_test.rb784
-rw-r--r--actionpack/test/controller/mime/respond_with_test.rb737
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb153
-rw-r--r--actionpack/test/controller/new_base/base_test.rb132
-rw-r--r--actionpack/test/controller/new_base/content_negotiation_test.rb27
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb112
-rw-r--r--actionpack/test/controller/new_base/metal_test.rb45
-rw-r--r--actionpack/test/controller/new_base/middleware_test.rb110
-rw-r--r--actionpack/test/controller/new_base/render_action_test.rb315
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb170
-rw-r--r--actionpack/test/controller/new_base/render_context_test.rb54
-rw-r--r--actionpack/test/controller/new_base/render_file_test.rb99
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb190
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb57
-rw-r--r--actionpack/test/controller/new_base/render_layout_test.rb127
-rw-r--r--actionpack/test/controller/new_base/render_partial_test.rb63
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb168
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb114
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb230
-rw-r--r--actionpack/test/controller/new_base/render_test.rb136
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb158
-rw-r--r--actionpack/test/controller/new_base/render_xml_test.rb11
-rw-r--r--actionpack/test/controller/output_escaping_test.rb17
-rw-r--r--actionpack/test/controller/parameters/always_permitted_parameters_test.rb29
-rw-r--r--actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb72
-rw-r--r--actionpack/test/controller/parameters/multi_parameter_attributes_test.rb38
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_test.rb187
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb280
-rw-r--r--actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb33
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb362
-rw-r--r--actionpack/test/controller/permitted_params_test.rb25
-rw-r--r--actionpack/test/controller/redirect_test.rb354
-rw-r--r--actionpack/test/controller/render_js_test.rb34
-rw-r--r--actionpack/test/controller/render_json_test.rb136
-rw-r--r--actionpack/test/controller/render_other_test.rb24
-rw-r--r--actionpack/test/controller/render_test.rb530
-rw-r--r--actionpack/test/controller/render_xml_test.rb97
-rw-r--r--actionpack/test/controller/request/test_request_test.rb35
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb501
-rw-r--r--actionpack/test/controller/required_params_test.rb51
-rw-r--r--actionpack/test/controller/rescue_test.rb348
-rw-r--r--actionpack/test/controller/resources_test.rb1334
-rw-r--r--actionpack/test/controller/routing_test.rb2023
-rw-r--r--actionpack/test/controller/runner_test.rb22
-rw-r--r--actionpack/test/controller/selector_test.rb629
-rw-r--r--actionpack/test/controller/send_file_test.rb209
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb112
-rw-r--r--actionpack/test/controller/streaming_test.rb26
-rw-r--r--actionpack/test/controller/test_case_test.rb1007
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb188
-rw-r--r--actionpack/test/controller/url_for_test.rb426
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb90
-rw-r--r--actionpack/test/controller/webservice_test.rb104
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb58
-rw-r--r--actionpack/test/dispatch/cookies_test.rb1070
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb283
-rw-r--r--actionpack/test/dispatch/header_test.rb139
-rw-r--r--actionpack/test/dispatch/live_response_test.rb94
-rw-r--r--actionpack/test/dispatch/mapper_test.rb112
-rw-r--r--actionpack/test/dispatch/middleware_stack/middleware_test.rb77
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb117
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb203
-rw-r--r--actionpack/test/dispatch/mount_test.rb93
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb465
-rw-r--r--actionpack/test/dispatch/rack_cache_test.rb21
-rw-r--r--actionpack/test/dispatch/reloader_test.rb176
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb164
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb186
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb171
-rw-r--r--actionpack/test/dispatch/request/session_test.rb116
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb177
-rw-r--r--actionpack/test/dispatch/request_id_test.rb65
-rw-r--r--actionpack/test/dispatch/request_test.rb1099
-rw-r--r--actionpack/test/dispatch/response_test.rb316
-rw-r--r--actionpack/test/dispatch/routing/concerns_test.rb120
-rw-r--r--actionpack/test/dispatch/routing/inspector_test.rb304
-rw-r--r--actionpack/test/dispatch/routing/route_set_test.rb93
-rw-r--r--actionpack/test/dispatch/routing_assertions_test.rb115
-rw-r--r--actionpack/test/dispatch/routing_test.rb4411
-rw-r--r--actionpack/test/dispatch/session/abstract_store_test.rb56
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb179
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb356
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb201
-rw-r--r--actionpack/test/dispatch/session/test_session_test.rb43
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb115
-rw-r--r--actionpack/test/dispatch/ssl_test.rb219
-rw-r--r--actionpack/test/dispatch/static_test.rb175
-rw-r--r--actionpack/test/dispatch/template_assertions_test.rb98
-rw-r--r--actionpack/test/dispatch/test_request_test.rb99
-rw-r--r--actionpack/test/dispatch/test_response_test.rb21
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb105
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb134
-rw-r--r--actionpack/test/fixtures/_top_level_partial_only.erb1
-rw-r--r--actionpack/test/fixtures/alternate_helpers/foo_helper.rb3
-rw-r--r--actionpack/test/fixtures/bad_customers/_bad_customer.html.erb1
-rw-r--r--actionpack/test/fixtures/company.rb9
-rw-r--r--actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb1
-rw-r--r--actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb1
-rw-r--r--actionpack/test/fixtures/functional_caching/_partial.erb3
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb3
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder5
-rw-r--r--actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb3
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached.html.erb3
-rw-r--r--actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb3
-rw-r--r--actionpack/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb2
-rw-r--r--actionpack/test/fixtures/helpers/abc_helper.rb3
-rw-r--r--actionpack/test/fixtures/helpers/fun/games_helper.rb5
-rw-r--r--actionpack/test/fixtures/helpers/fun/pdf_helper.rb5
-rw-r--r--actionpack/test/fixtures/helpers/just_me_helper.rb3
-rw-r--r--actionpack/test/fixtures/helpers/me_too_helper.rb3
-rw-r--r--actionpack/test/fixtures/helpers1_pack/pack1_helper.rb5
-rw-r--r--actionpack/test/fixtures/helpers2_pack/pack2_helper.rb5
-rw-r--r--actionpack/test/fixtures/layouts/_customers.erb1
-rw-r--r--actionpack/test/fixtures/layouts/block_with_layout.erb3
-rw-r--r--actionpack/test/fixtures/layouts/builder.builder3
-rw-r--r--actionpack/test/fixtures/layouts/partial_with_layout.erb3
-rw-r--r--actionpack/test/fixtures/layouts/standard.html.erb1
-rw-r--r--actionpack/test/fixtures/layouts/talk_from_action.erb2
-rw-r--r--actionpack/test/fixtures/layouts/with_html_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/layouts/xhr.html.erb2
-rw-r--r--actionpack/test/fixtures/layouts/yield.erb2
-rw-r--r--actionpack/test/fixtures/localized/hello_world.de.html1
-rw-r--r--actionpack/test/fixtures/localized/hello_world.en.html1
-rw-r--r--actionpack/test/fixtures/localized/hello_world.it.erb1
-rw-r--r--actionpack/test/fixtures/multipart/binary_filebin0 -> 19820 bytes
-rw-r--r--actionpack/test/fixtures/multipart/boundary_problem_file10
-rw-r--r--actionpack/test/fixtures/multipart/bracketed_param5
-rw-r--r--actionpack/test/fixtures/multipart/bracketed_utf8_param5
-rw-r--r--actionpack/test/fixtures/multipart/empty10
-rw-r--r--actionpack/test/fixtures/multipart/hello.txt1
-rw-r--r--actionpack/test/fixtures/multipart/large_text_file10
-rw-r--r--actionpack/test/fixtures/multipart/mixed_filesbin0 -> 19937 bytes
-rw-r--r--actionpack/test/fixtures/multipart/mona_lisa.jpgbin0 -> 159528 bytes
-rw-r--r--actionpack/test/fixtures/multipart/none9
-rw-r--r--actionpack/test/fixtures/multipart/single_parameter5
-rw-r--r--actionpack/test/fixtures/multipart/single_utf8_param5
-rw-r--r--actionpack/test/fixtures/multipart/text_file10
-rw-r--r--actionpack/test/fixtures/old_content_type/render_default_content_types_for_respond_to.xml.erb1
-rw-r--r--actionpack/test/fixtures/old_content_type/render_default_for_builder.builder1
-rw-r--r--actionpack/test/fixtures/old_content_type/render_default_for_erb.erb1
-rw-r--r--actionpack/test/fixtures/post_test/layouts/post.html.erb1
-rw-r--r--actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb1
-rw-r--r--actionpack/test/fixtures/post_test/post/index.html.erb1
-rw-r--r--actionpack/test/fixtures/post_test/post/index.iphone.erb1
-rw-r--r--actionpack/test/fixtures/post_test/super_post/index.html.erb1
-rw-r--r--actionpack/test/fixtures/post_test/super_post/index.iphone.erb1
-rw-r--r--actionpack/test/fixtures/public/400.html1
-rw-r--r--actionpack/test/fixtures/public/404.html1
-rw-r--r--actionpack/test/fixtures/public/500.da.html1
-rw-r--r--actionpack/test/fixtures/public/500.html1
-rw-r--r--actionpack/test/fixtures/public/foo/bar.html1
-rw-r--r--actionpack/test/fixtures/public/foo/baz.css3
-rw-r--r--actionpack/test/fixtures/public/foo/index.html1
-rw-r--r--actionpack/test/fixtures/public/foo/こんにちは.html1
-rw-r--r--actionpack/test/fixtures/public/index.html1
-rw-r--r--actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/layouts/missing.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/layouts/standard.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults.xml.builder1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/edit.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/new.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb0
-rw-r--r--actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.js.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb1
-rw-r--r--actionpack/test/fixtures/ruby_template.ruby2
-rw-r--r--actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb10
-rw-r--r--actionpack/test/fixtures/shared.html.erb1
-rw-r--r--actionpack/test/fixtures/star_star_mime/index.js.erb1
-rw-r--r--actionpack/test/fixtures/symlink_parent/symlinked_layout.erb5
-rw-r--r--actionpack/test/fixtures/test/_partial.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial.js.erb1
-rw-r--r--actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb1
-rw-r--r--actionpack/test/fixtures/test/formatted_xml_erb.builder1
-rw-r--r--actionpack/test/fixtures/test/formatted_xml_erb.html.erb1
-rw-r--r--actionpack/test/fixtures/test/formatted_xml_erb.xml.erb1
-rw-r--r--actionpack/test/fixtures/test/hello/hello.erb1
-rw-r--r--actionpack/test/fixtures/test/hello_world.erb1
-rw-r--r--actionpack/test/fixtures/test/hello_world_with_partial.html.erb2
-rw-r--r--actionpack/test/fixtures/test/hello_xml_world.builder11
-rw-r--r--actionpack/test/fixtures/test/implicit_content_type.atom.builder2
-rw-r--r--actionpack/test/fixtures/test/render_file_with_ivar.erb1
-rw-r--r--actionpack/test/fixtures/test/render_file_with_locals.erb1
-rw-r--r--actionpack/test/fixtures/公共/foo/bar.html1
-rw-r--r--actionpack/test/fixtures/公共/foo/baz.css3
-rw-r--r--actionpack/test/fixtures/公共/foo/index.html1
-rw-r--r--actionpack/test/fixtures/公共/foo/こんにちは.html1
-rw-r--r--actionpack/test/fixtures/公共/index.html1
-rw-r--r--actionpack/test/journey/gtg/builder_test.rb79
-rw-r--r--actionpack/test/journey/gtg/transition_table_test.rb115
-rw-r--r--actionpack/test/journey/nfa/simulator_test.rb98
-rw-r--r--actionpack/test/journey/nfa/transition_table_test.rb72
-rw-r--r--actionpack/test/journey/nodes/symbol_test.rb17
-rw-r--r--actionpack/test/journey/path/pattern_test.rb284
-rw-r--r--actionpack/test/journey/route/definition/parser_test.rb110
-rw-r--r--actionpack/test/journey/route/definition/scanner_test.rb56
-rw-r--r--actionpack/test/journey/route_test.rb106
-rw-r--r--actionpack/test/journey/router/utils_test.rb38
-rw-r--r--actionpack/test/journey/router_test.rb588
-rw-r--r--actionpack/test/journey/routes_test.rb53
-rw-r--r--actionpack/test/lib/controller/fake_controllers.rb35
-rw-r--r--actionpack/test/lib/controller/fake_models.rb118
-rw-r--r--actionpack/test/routing/helper_test.rb45
-rw-r--r--actionpack/test/tmp/.gitignore0
247 files changed, 34787 insertions, 0 deletions
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
new file mode 100644
index 0000000000..8cba049485
--- /dev/null
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -0,0 +1,315 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+
+ class ControllerWithCallbacks < AbstractController::Base
+ include AbstractController::Callbacks
+ end
+
+ class Callback1 < ControllerWithCallbacks
+ set_callback :process_action, :before, :first
+
+ def first
+ @text = "Hello world"
+ end
+
+ def index
+ self.response_body = @text
+ end
+ end
+
+ class TestCallbacks1 < ActiveSupport::TestCase
+ test "basic callbacks work" do
+ controller = Callback1.new
+ controller.process(:index)
+ assert_equal "Hello world", controller.response_body
+ end
+ end
+
+ class Callback2 < ControllerWithCallbacks
+ before_action :first
+ after_action :second
+ around_action :aroundz
+
+ def first
+ @text = "Hello world"
+ end
+
+ def second
+ @second = "Goodbye"
+ end
+
+ def aroundz
+ @aroundz = "FIRST"
+ yield
+ @aroundz << "SECOND"
+ end
+
+ def index
+ @text ||= nil
+ self.response_body = @text.to_s
+ end
+ end
+
+ class Callback2Overwrite < Callback2
+ before_action :first, except: :index
+ end
+
+ class TestCallbacks2 < ActiveSupport::TestCase
+ def setup
+ @controller = Callback2.new
+ end
+
+ test "before_action works" do
+ @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
+ end
+
+ test "after_action works" do
+ @controller.process(:index)
+ assert_equal "Goodbye", @controller.instance_variable_get("@second")
+ end
+
+ test "around_action works" do
+ @controller.process(:index)
+ assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
+ end
+
+ test "before_action with overwritten condition" do
+ @controller = Callback2Overwrite.new
+ @controller.process(:index)
+ assert_equal "", @controller.response_body
+ end
+ end
+
+ class Callback3 < ControllerWithCallbacks
+ before_action do |c|
+ c.instance_variable_set("@text", "Hello world")
+ end
+
+ after_action do |c|
+ c.instance_variable_set("@second", "Goodbye")
+ end
+
+ def index
+ self.response_body = @text
+ end
+ end
+
+ class TestCallbacks3 < ActiveSupport::TestCase
+ def setup
+ @controller = Callback3.new
+ end
+
+ test "before_action works with procs" do
+ @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
+ end
+
+ test "after_action works with procs" do
+ @controller.process(:index)
+ assert_equal "Goodbye", @controller.instance_variable_get("@second")
+ end
+ end
+
+ class CallbacksWithConditions < ControllerWithCallbacks
+ before_action :list, :only => :index
+ before_action :authenticate, :except => :index
+
+ def index
+ self.response_body = @list.join(", ")
+ end
+
+ def sekrit_data
+ self.response_body = (@list + [@authenticated]).join(", ")
+ end
+
+ private
+ def list
+ @list = ["Hello", "World"]
+ end
+
+ def authenticate
+ @list ||= []
+ @authenticated = "true"
+ end
+ end
+
+ class TestCallbacksWithConditions < ActiveSupport::TestCase
+ def setup
+ @controller = CallbacksWithConditions.new
+ end
+
+ test "when :only is specified, a before action is triggered on that action" do
+ @controller.process(:index)
+ assert_equal "Hello, World", @controller.response_body
+ end
+
+ test "when :only is specified, a before action is not triggered on other actions" do
+ @controller.process(:sekrit_data)
+ assert_equal "true", @controller.response_body
+ end
+
+ test "when :except is specified, an after action is not triggered on that action" do
+ @controller.process(:index)
+ assert !@controller.instance_variable_defined?("@authenticated")
+ end
+ end
+
+ class CallbacksWithArrayConditions < ControllerWithCallbacks
+ before_action :list, only: [:index, :listy]
+ before_action :authenticate, except: [:index, :listy]
+
+ def index
+ self.response_body = @list.join(", ")
+ end
+
+ def sekrit_data
+ self.response_body = (@list + [@authenticated]).join(", ")
+ end
+
+ private
+ def list
+ @list = ["Hello", "World"]
+ end
+
+ def authenticate
+ @list = []
+ @authenticated = "true"
+ end
+ end
+
+ class TestCallbacksWithArrayConditions < ActiveSupport::TestCase
+ def setup
+ @controller = CallbacksWithArrayConditions.new
+ end
+
+ test "when :only is specified with an array, a before action is triggered on that action" do
+ @controller.process(:index)
+ assert_equal "Hello, World", @controller.response_body
+ end
+
+ test "when :only is specified with an array, a before action is not triggered on other actions" do
+ @controller.process(:sekrit_data)
+ assert_equal "true", @controller.response_body
+ end
+
+ test "when :except is specified with an array, an after action is not triggered on that action" do
+ @controller.process(:index)
+ assert !@controller.instance_variable_defined?("@authenticated")
+ end
+ end
+
+ class ChangedConditions < Callback2
+ before_action :first, :only => :index
+
+ def not_index
+ @text ||= nil
+ self.response_body = @text.to_s
+ end
+ end
+
+ class TestCallbacksWithChangedConditions < ActiveSupport::TestCase
+ def setup
+ @controller = ChangedConditions.new
+ end
+
+ test "when a callback is modified in a child with :only, it works for the :only action" do
+ @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
+ end
+
+ test "when a callback is modified in a child with :only, it does not work for other actions" do
+ @controller.process(:not_index)
+ assert_equal "", @controller.response_body
+ end
+ end
+
+ class SetsResponseBody < ControllerWithCallbacks
+ before_action :set_body
+
+ def index
+ self.response_body = "Fail"
+ end
+
+ def set_body
+ self.response_body = "Success"
+ end
+ end
+
+ class TestHalting < ActiveSupport::TestCase
+ test "when a callback sets the response body, the action should not be invoked" do
+ controller = SetsResponseBody.new
+ controller.process(:index)
+ assert_equal "Success", controller.response_body
+ end
+ end
+
+ class CallbacksWithArgs < ControllerWithCallbacks
+ set_callback :process_action, :before, :first
+
+ def first
+ @text = "Hello world"
+ end
+
+ def index(text)
+ self.response_body = @text + text
+ end
+ end
+
+ class TestCallbacksWithArgs < ActiveSupport::TestCase
+ test "callbacks still work when invoking process with multiple arguments" do
+ controller = CallbacksWithArgs.new
+ controller.process(:index, " Howdy!")
+ assert_equal "Hello world Howdy!", controller.response_body
+ end
+ end
+
+ class AliasedCallbacks < ControllerWithCallbacks
+ before_filter :first
+ after_filter :second
+ around_filter :aroundz
+
+ def first
+ @text = "Hello world"
+ end
+
+ def second
+ @second = "Goodbye"
+ end
+
+ def aroundz
+ @aroundz = "FIRST"
+ yield
+ @aroundz << "SECOND"
+ end
+
+ def index
+ @text ||= nil
+ self.response_body = @text.to_s
+ end
+ end
+
+ class TestAliasedCallbacks < ActiveSupport::TestCase
+ def setup
+ @controller = AliasedCallbacks.new
+ end
+
+ test "before_filter works" do
+ @controller.process(:index)
+ assert_equal "Hello world", @controller.response_body
+ end
+
+ test "after_filter works" do
+ @controller.process(:index)
+ assert_equal "Goodbye", @controller.instance_variable_get("@second")
+ end
+
+ test "around_filter works" do
+ @controller.process(:index)
+ assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
+ end
+ end
+ end
+end
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
new file mode 100644
index 0000000000..fc59bf19c4
--- /dev/null
+++ b/actionpack/test/abstract/collector_test.rb
@@ -0,0 +1,63 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+ class MyCollector
+ include AbstractController::Collector
+ attr_accessor :responses
+
+ def initialize
+ @responses = []
+ end
+
+ def custom(mime, *args, &block)
+ @responses << [mime, args, block]
+ end
+ end
+
+ class TestCollector < ActiveSupport::TestCase
+ test "responds to default mime types" do
+ collector = MyCollector.new
+ assert_respond_to collector, :html
+ assert_respond_to collector, :text
+ end
+
+ test "does not respond to unknown mime types" do
+ collector = MyCollector.new
+ assert_not_respond_to collector, :unknown
+ end
+
+ test "register mime types on method missing" do
+ AbstractController::Collector.send(:remove_method, :js)
+ begin
+ collector = MyCollector.new
+ assert_not_respond_to collector, :js
+ collector.js
+ assert_respond_to collector, :js
+ ensure
+ unless AbstractController::Collector.method_defined? :js
+ AbstractController::Collector.generate_method_for_mime :js
+ end
+ end
+ end
+
+ test "does not register unknown mime types" do
+ collector = MyCollector.new
+ assert_raise NoMethodError do
+ collector.unknown
+ end
+ end
+
+ test "generated methods call custom with arguments received" do
+ collector = MyCollector.new
+ collector.html
+ collector.text(:foo)
+ collector.js(:bar) { :baz }
+ assert_equal [Mime::HTML, [], nil], collector.responses[0]
+ assert_equal [Mime::TEXT, [:foo], nil], collector.responses[1]
+ assert_equal [Mime::JS, [:bar]], collector.responses[2][0,2]
+ assert_equal :baz, collector.responses[2][2].call
+ end
+ end
+ end
+end
diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb
new file mode 100644
index 0000000000..4fdc480b43
--- /dev/null
+++ b/actionpack/test/abstract/translation_test.rb
@@ -0,0 +1,50 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+ class TranslationController < AbstractController::Base
+ include AbstractController::Translation
+ end
+
+ class TranslationControllerTest < ActiveSupport::TestCase
+ def setup
+ @controller = TranslationController.new
+ end
+
+ def test_action_controller_base_responds_to_translate
+ assert_respond_to @controller, :translate
+ end
+
+ def test_action_controller_base_responds_to_t
+ assert_respond_to @controller, :t
+ end
+
+ def test_action_controller_base_responds_to_localize
+ assert_respond_to @controller, :localize
+ end
+
+ def test_action_controller_base_responds_to_l
+ assert_respond_to @controller, :l
+ end
+
+ def test_lazy_lookup
+ expected = 'bar'
+ @controller.stubs(action_name: :index)
+ I18n.stubs(:translate).with('abstract_controller.testing.translation.index.foo').returns(expected)
+ assert_equal expected, @controller.t('.foo')
+ end
+
+ def test_default_translation
+ key, expected = 'one.two', 'bar'
+ I18n.stubs(:translate).with(key).returns(expected)
+ assert_equal expected, @controller.t(key)
+ end
+
+ def test_localize
+ time, expected = Time.gm(2000), 'Sat, 01 Jan 2000 00:00:00 +0000'
+ I18n.stubs(:localize).with(time).returns(expected)
+ assert_equal expected, @controller.l(time)
+ end
+ end
+ end
+end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
new file mode 100644
index 0000000000..4e17d57dad
--- /dev/null
+++ b/actionpack/test/abstract_unit.rb
@@ -0,0 +1,499 @@
+require File.expand_path('../../../load_paths', __FILE__)
+
+$:.unshift(File.dirname(__FILE__) + '/lib')
+$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
+$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
+
+ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp')
+
+require 'active_support/core_ext/kernel/reporting'
+
+# These are the normal settings that will be set up by Railties
+# TODO: Have these tests support other combinations of these values
+silence_warnings do
+ Encoding.default_internal = "UTF-8"
+ Encoding.default_external = "UTF-8"
+end
+
+require 'drb'
+require 'drb/unix'
+require 'tempfile'
+
+PROCESS_COUNT = (ENV['N'] || 4).to_i
+
+require 'active_support/testing/autorun'
+require 'abstract_controller'
+require 'action_controller'
+require 'action_view'
+require 'action_view/testing/resolvers'
+require 'action_dispatch'
+require 'active_support/dependencies'
+require 'active_model'
+require 'active_record'
+require 'action_controller/caching'
+
+require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
+
+module Rails
+ class << self
+ def env
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test")
+ end
+ end
+end
+
+ActiveSupport::Dependencies.hook!
+
+Thread.abort_on_exception = true
+
+# Show backtraces for deprecated behavior for quicker cleanup.
+ActiveSupport::Deprecation.debug = true
+
+# Disable available locale checks to avoid warnings running the test suite.
+I18n.enforce_available_locales = false
+
+# Register danish language for testing
+I18n.backend.store_translations 'da', {}
+I18n.backend.store_translations 'pt-BR', {}
+ORIGINAL_LOCALES = I18n.available_locales.map {|locale| locale.to_s }.sort
+
+FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
+FIXTURES = Pathname.new(FIXTURE_LOAD_PATH)
+
+module RackTestUtils
+ def body_to_string(body)
+ if body.respond_to?(:each)
+ str = ""
+ body.each {|s| str << s }
+ str
+ else
+ body
+ end
+ end
+ extend self
+end
+
+SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
+
+module ActionDispatch
+ module SharedRoutes
+ def before_setup
+ @routes = SharedTestRoutes
+ super
+ end
+ end
+
+ # Hold off drawing routes until all the possible controller classes
+ # have been loaded.
+ module DrawOnce
+ class << self
+ attr_accessor :drew
+ end
+ self.drew = false
+
+ def before_setup
+ super
+ return if DrawOnce.drew
+
+ SharedTestRoutes.draw do
+ get ':controller(/:action)'
+ end
+
+ ActionDispatch::IntegrationTest.app.routes.draw do
+ get ':controller(/:action)'
+ end
+
+ DrawOnce.drew = true
+ end
+ end
+end
+
+module ActiveSupport
+ class TestCase
+ include ActionDispatch::DrawOnce
+ if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
+ parallelize_me!
+ end
+ end
+end
+
+class RoutedRackApp
+ attr_reader :routes
+
+ def initialize(routes, &blk)
+ @routes = routes
+ @stack = ActionDispatch::MiddlewareStack.new(&blk).build(@routes)
+ end
+
+ def call(env)
+ @stack.call(env)
+ end
+end
+
+class BasicController
+ attr_accessor :request
+
+ def config
+ @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
+ # VIEW TODO: View tests should not require a controller
+ public_dir = File.expand_path("../fixtures/public", __FILE__)
+ config.assets_dir = public_dir
+ config.javascripts_dir = "#{public_dir}/javascripts"
+ config.stylesheets_dir = "#{public_dir}/stylesheets"
+ config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" })
+ config
+ end
+ end
+end
+
+class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
+ include ActionDispatch::SharedRoutes
+
+ def self.build_app(routes = nil)
+ RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
+ middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ middleware.use "ActionDispatch::DebugExceptions"
+ middleware.use "ActionDispatch::Callbacks"
+ middleware.use "ActionDispatch::ParamsParser"
+ middleware.use "ActionDispatch::Cookies"
+ middleware.use "ActionDispatch::Flash"
+ middleware.use "Rack::Head"
+ yield(middleware) if block_given?
+ end
+ end
+
+ self.app = build_app
+
+ # Stub Rails dispatcher so it does not get controller references and
+ # simply return the controller#action as Rack::Body.
+ class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher
+ protected
+ def controller_reference(controller_param)
+ controller_param
+ end
+
+ def dispatch(controller, action, env)
+ [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]]
+ end
+ end
+
+ def self.stub_controllers
+ old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher }
+ yield ActionDispatch::Routing::RouteSet.new
+ ensure
+ ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher }
+ ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher }
+ end
+
+ def with_routing(&block)
+ temporary_routes = ActionDispatch::Routing::RouteSet.new
+ old_app, self.class.app = self.class.app, self.class.build_app(temporary_routes)
+ old_routes = SharedTestRoutes
+ silence_warnings { Object.const_set(:SharedTestRoutes, temporary_routes) }
+
+ yield temporary_routes
+ ensure
+ self.class.app = old_app
+ silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
+ end
+
+ def with_autoload_path(path)
+ path = File.join(File.dirname(__FILE__), "fixtures", path)
+ if ActiveSupport::Dependencies.autoload_paths.include?(path)
+ yield
+ else
+ begin
+ ActiveSupport::Dependencies.autoload_paths << path
+ yield
+ ensure
+ ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
+ ActiveSupport::Dependencies.clear
+ end
+ end
+ end
+end
+
+# Temporary base class
+class Rack::TestCase < ActionDispatch::IntegrationTest
+ def self.testing(klass = nil)
+ if klass
+ @testing = "/#{klass.name.underscore}".sub!(/_controller$/, '')
+ else
+ @testing
+ end
+ end
+
+ def get(thing, *args)
+ if thing.is_a?(Symbol)
+ super("#{self.class.testing}/#{thing}", *args)
+ else
+ super
+ end
+ end
+
+ def assert_body(body)
+ assert_equal body, Array(response.body).join
+ end
+
+ def assert_status(code)
+ assert_equal code, response.status
+ end
+
+ def assert_response(body, status = 200, headers = {})
+ assert_body body
+ assert_status status
+ headers.each do |header, value|
+ assert_header header, value
+ end
+ end
+
+ def assert_content_type(type)
+ assert_equal type, response.headers["Content-Type"]
+ end
+
+ def assert_header(name, value)
+ assert_equal value, response.headers[name]
+ end
+end
+
+module ActionController
+ class Base
+ # This stub emulates the Railtie including the URL helpers from a Rails application
+ include SharedTestRoutes.url_helpers
+ include SharedTestRoutes.mounted_helpers
+
+ self.view_paths = FIXTURE_LOAD_PATH
+
+ def self.test_routes(&block)
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.draw(&block)
+ include routes.url_helpers
+ end
+ end
+
+ class TestCase
+ include ActionDispatch::TestProcess
+ include ActionDispatch::SharedRoutes
+ end
+end
+
+
+class ::ApplicationController < ActionController::Base
+end
+
+class Workshop
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ attr_accessor :id
+
+ def initialize(id)
+ @id = id
+ end
+
+ def persisted?
+ id.present?
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
+module ActionDispatch
+ class DebugExceptions
+ private
+ remove_method :stderr_logger
+ # Silence logger
+ def stderr_logger
+ nil
+ end
+ end
+end
+
+module ActionDispatch
+ module RoutingVerbs
+ def send_request(uri_or_host, method, path)
+ host = uri_or_host.host unless path
+ path ||= uri_or_host.path
+
+ params = {'PATH_INFO' => path,
+ 'REQUEST_METHOD' => method,
+ 'HTTP_HOST' => host}
+
+ routes.call(params)
+ end
+
+ def request_path_params(path, options = {})
+ method = options[:method] || 'GET'
+ resp = send_request URI('http://localhost' + path), method.to_s.upcase, nil
+ status = resp.first
+ if status == 404
+ raise ActionController::RoutingError, "No route matches #{path.inspect}"
+ end
+ controller.request.path_parameters
+ end
+
+ def get(uri_or_host, path = nil)
+ send_request(uri_or_host, 'GET', path)[2].join
+ end
+
+ def post(uri_or_host, path = nil)
+ send_request(uri_or_host, 'POST', path)[2].join
+ end
+
+ def put(uri_or_host, path = nil)
+ send_request(uri_or_host, 'PUT', path)[2].join
+ end
+
+ def delete(uri_or_host, path = nil)
+ send_request(uri_or_host, 'DELETE', path)[2].join
+ end
+
+ def patch(uri_or_host, path = nil)
+ send_request(uri_or_host, 'PATCH', path)[2].join
+ end
+ end
+end
+
+module RoutingTestHelpers
+ def url_for(set, options)
+ route_name = options.delete :use_route
+ set.url_for options.merge(:only_path => true), route_name
+ end
+
+ def make_set(strict = true)
+ tc = self
+ TestSet.new ->(c) { tc.controller = c }, strict
+ end
+
+ class TestSet < ActionDispatch::Routing::RouteSet
+ attr_reader :strict
+
+ def initialize(block, strict = false)
+ @block = block
+ @strict = strict
+ super()
+ end
+
+ class Dispatcher < ActionDispatch::Routing::RouteSet::Dispatcher
+ def initialize(defaults, set, block)
+ super(defaults)
+ @block = block
+ @set = set
+ end
+
+ def controller(params, default_controller=true)
+ super(params, @set.strict)
+ end
+
+ def controller_reference(controller_param)
+ block = @block
+ set = @set
+ super if @set.strict
+ Class.new(ActionController::Base) {
+ include set.url_helpers
+ define_method(:process) { |name| block.call(self) }
+ def to_a; [200, {}, []]; end
+ }
+ end
+ end
+
+ def dispatcher defaults
+ TestSet::Dispatcher.new defaults, self, @block
+ end
+ end
+end
+
+class ResourcesController < ActionController::Base
+ def index() render :nothing => true end
+ alias_method :show, :index
+end
+
+class ThreadsController < ResourcesController; end
+class MessagesController < ResourcesController; end
+class CommentsController < ResourcesController; end
+class ReviewsController < ResourcesController; end
+class LogosController < ResourcesController; end
+
+class AccountsController < ResourcesController; end
+class AdminController < ResourcesController; end
+class ProductsController < ResourcesController; end
+class ImagesController < ResourcesController; end
+class PreferencesController < ResourcesController; end
+
+module Backoffice
+ class ProductsController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+
+ module Admin
+ class ProductsController < ResourcesController; end
+ class ImagesController < ResourcesController; end
+ end
+end
+
+# Skips the current run on Rubinius using Minitest::Assertions#skip
+def rubinius_skip(message = '')
+ skip message if RUBY_ENGINE == 'rbx'
+end
+# Skips the current run on JRuby using Minitest::Assertions#skip
+def jruby_skip(message = '')
+ skip message if defined?(JRUBY_VERSION)
+end
+
+require 'mocha/setup' # FIXME: stop using mocha
+
+class ForkingExecutor
+ class Server
+ include DRb::DRbUndumped
+
+ def initialize
+ @queue = Queue.new
+ end
+
+ def record reporter, result
+ reporter.record result
+ end
+
+ def << o
+ o[2] = DRbObject.new(o[2]) if o
+ @queue << o
+ end
+ def pop; @queue.pop; end
+ end
+
+ def initialize size
+ @size = size
+ @queue = Server.new
+ file = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tests', 'fd')
+ @url = "drbunix://#{file}"
+ @pool = nil
+ DRb.start_service @url, @queue
+ end
+
+ def << work; @queue << work; end
+
+ def shutdown
+ pool = @size.times.map {
+ fork {
+ DRb.stop_service
+ queue = DRbObject.new_with_uri @url
+ while job = queue.pop
+ klass = job[0]
+ method = job[1]
+ reporter = job[2]
+ result = Minitest.run_one_method klass, method
+ queue.record reporter, result
+ end
+ }
+ }
+ @size.times { @queue << nil }
+ pool.each { |pid| Process.waitpid pid }
+ end
+end
+
+if ActiveSupport::Testing::Isolation.forking_env? && PROCESS_COUNT > 0
+ # Use N processes (N defaults to 4)
+ Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT)
+end
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
new file mode 100644
index 0000000000..5e64cae7e2
--- /dev/null
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -0,0 +1,63 @@
+require 'abstract_unit'
+require 'action_dispatch/testing/assertions/response'
+
+module ActionDispatch
+ module Assertions
+ class ResponseAssertionsTest < ActiveSupport::TestCase
+ include ResponseAssertions
+
+ FakeResponse = Struct.new(:response_code) do
+ [:success, :missing, :redirect, :error].each do |sym|
+ define_method("#{sym}?") do
+ sym == response_code
+ end
+ end
+ end
+
+ def test_assert_response_predicate_methods
+ [:success, :missing, :redirect, :error].each do |sym|
+ @response = FakeResponse.new sym
+ assert_response sym
+
+ assert_raises(Minitest::Assertion) {
+ assert_response :unauthorized
+ }
+ end
+ end
+
+ def test_assert_response_fixnum
+ @response = FakeResponse.new 400
+ assert_response 400
+
+ assert_raises(Minitest::Assertion) {
+ assert_response :unauthorized
+ }
+
+ assert_raises(Minitest::Assertion) {
+ assert_response 500
+ }
+ end
+
+ def test_assert_response_sym_status
+ @response = FakeResponse.new 401
+ assert_response :unauthorized
+
+ assert_raises(Minitest::Assertion) {
+ assert_response :ok
+ }
+
+ assert_raises(Minitest::Assertion) {
+ assert_response :success
+ }
+ end
+
+ def test_assert_response_sym_typo
+ @response = FakeResponse.new 200
+
+ assert_raises(ArgumentError) {
+ assert_response :succezz
+ }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
new file mode 100644
index 0000000000..b6b5a218cc
--- /dev/null
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -0,0 +1,640 @@
+require 'abstract_unit'
+require 'action_view/vendor/html-scanner'
+require 'controller/fake_controllers'
+
+class ActionPackAssertionsController < ActionController::Base
+
+ def nothing() head :ok end
+
+ def hello_world() render :template => "test/hello_world"; end
+ def hello_repeating_in_path() render :template => "test/hello/hello"; end
+
+ def hello_xml_world() render :template => "test/hello_xml_world"; end
+
+ def hello_xml_world_pdf
+ self.content_type = "application/pdf"
+ render :template => "test/hello_xml_world"
+ end
+
+ def hello_xml_world_pdf_header
+ response.headers["Content-Type"] = "application/pdf; charset=utf-8"
+ render :template => "test/hello_xml_world"
+ end
+
+ def partial() render :partial => 'test/partial'; end
+
+ def redirect_internal() redirect_to "/nothing"; end
+
+ def redirect_to_action() redirect_to :action => "flash_me", :id => 1, :params => { "panda" => "fun" }; end
+
+ def redirect_to_controller() redirect_to :controller => "elsewhere", :action => "flash_me"; end
+
+ def redirect_to_controller_with_symbol() redirect_to :controller => :elsewhere, :action => :flash_me; end
+
+ def redirect_to_path() redirect_to '/some/path' end
+
+ def redirect_invalid_external_route() redirect_to 'ht_tp://www.rubyonrails.org' end
+
+ def redirect_to_named_route() redirect_to route_one_url end
+
+ def redirect_external() redirect_to "http://www.rubyonrails.org"; end
+
+ def redirect_external_protocol_relative() redirect_to "//www.rubyonrails.org"; end
+
+ def response404() head '404 AWOL' end
+
+ def response500() head '500 Sorry' end
+
+ def response599() head '599 Whoah!' end
+
+ def flash_me
+ flash['hello'] = 'my name is inigo montoya...'
+ render :text => "Inconceivable!"
+ end
+
+ def flash_me_naked
+ flash.clear
+ render :text => "wow!"
+ end
+
+ def assign_this
+ @howdy = "ho"
+ render :inline => "Mr. Henke"
+ end
+
+ def render_based_on_parameters
+ render :text => "Mr. #{params[:name]}"
+ end
+
+ def render_url
+ render :text => "<div>#{url_for(:action => 'flash_me', :only_path => true)}</div>"
+ end
+
+ def render_text_with_custom_content_type
+ render :text => "Hello!", :content_type => Mime::RSS
+ end
+
+ def render_with_layout
+ @variable_for_layout = nil
+ render "test/hello_world", :layout => "layouts/standard"
+ end
+
+ def render_with_layout_and_partial
+ @variable_for_layout = nil
+ render "test/hello_world_with_partial", :layout => "layouts/standard"
+ end
+
+ def session_stuffing
+ session['xmas'] = 'turkey'
+ render :text => "ho ho ho"
+ end
+
+ def raise_exception_on_get
+ raise "get" if request.get?
+ render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ end
+
+ def raise_exception_on_post
+ raise "post" if request.post?
+ render :text => "request method: #{request.env['REQUEST_METHOD']}"
+ end
+
+ def render_file_absolute_path
+ render :file => File.expand_path('../../../README.rdoc', __FILE__)
+ end
+
+ def render_file_relative_path
+ render :file => 'README.rdoc'
+ end
+end
+
+# Used to test that assert_response includes the exception message
+# in the failure message when an action raises and assert_response
+# is expecting something other than an error.
+class AssertResponseWithUnexpectedErrorController < ActionController::Base
+ def index
+ raise 'FAIL'
+ end
+
+ def show
+ render :text => "Boom", :status => 500
+ end
+end
+
+module Admin
+ class InnerModuleController < ActionController::Base
+ def index
+ render :nothing => true
+ end
+
+ def redirect_to_index
+ redirect_to admin_inner_module_path
+ end
+
+ def redirect_to_absolute_controller
+ redirect_to :controller => '/content'
+ end
+
+ def redirect_to_fellow_controller
+ redirect_to :controller => 'user'
+ end
+
+ def redirect_to_top_level_named_route
+ redirect_to top_level_url(:id => "foo")
+ end
+ end
+end
+
+class ActionPackAssertionsControllerTest < ActionController::TestCase
+
+ def test_assert_tag_and_url_for
+ get :render_url
+ assert_tag :content => "/action_pack_assertions/flash_me"
+ end
+
+ def test_render_file_absolute_path
+ get :render_file_absolute_path
+ assert_match(/\A= Action Pack/, @response.body)
+ end
+
+ def test_render_file_relative_path
+ get :render_file_relative_path
+ assert_match(/\A= Action Pack/, @response.body)
+ end
+
+ def test_get_request
+ assert_raise(RuntimeError) { get :raise_exception_on_get }
+ get :raise_exception_on_post
+ assert_equal @response.body, 'request method: GET'
+ end
+
+ def test_post_request
+ assert_raise(RuntimeError) { post :raise_exception_on_post }
+ post :raise_exception_on_get
+ assert_equal @response.body, 'request method: POST'
+ end
+
+ def test_get_post_request_switch
+ post :raise_exception_on_get
+ assert_equal @response.body, 'request method: POST'
+ get :raise_exception_on_post
+ assert_equal @response.body, 'request method: GET'
+ post :raise_exception_on_get
+ assert_equal @response.body, 'request method: POST'
+ get :raise_exception_on_post
+ assert_equal @response.body, 'request method: GET'
+ end
+
+ def test_string_constraint
+ with_routing do |set|
+ set.draw do
+ get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
+ end
+ end
+ end
+
+ def test_assert_redirect_to_named_route_failure
+ with_routing do |set|
+ set.draw do
+ get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
+ get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
+ get ':controller/:action'
+ end
+ process :redirect_to_named_route
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to 'http://test.host/route_two'
+ end
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to %r(^http://test.host/route_two)
+ end
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
+ end
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to route_two_url
+ end
+ end
+ end
+
+ def test_assert_redirect_to_nested_named_route
+ @controller = Admin::InnerModuleController.new
+
+ with_routing do |set|
+ set.draw do
+ get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
+ get ':controller/:action'
+ end
+ process :redirect_to_index
+ # redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
+ assert_redirected_to admin_inner_module_path
+ end
+ end
+
+ def test_assert_redirected_to_top_level_named_route_from_nested_controller
+ @controller = Admin::InnerModuleController.new
+
+ with_routing do |set|
+ set.draw do
+ get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
+ get ':controller/:action'
+ end
+ process :redirect_to_top_level_named_route
+ # assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
+ assert_redirected_to "/action_pack_assertions/foo"
+ assert_redirected_to %r(/action_pack_assertions/foo)
+ end
+ end
+
+ def test_assert_redirected_to_top_level_named_route_with_same_controller_name_in_both_namespaces
+ @controller = Admin::InnerModuleController.new
+
+ with_routing do |set|
+ set.draw do
+ # this controller exists in the admin namespace as well which is the only difference from previous test
+ get '/user/:id', :to => 'user#index', :as => :top_level
+ get ':controller/:action'
+ end
+ process :redirect_to_top_level_named_route
+ # assert_redirected_to top_level_url('foo') would pass because of exact match early return
+ assert_redirected_to top_level_path('foo')
+ end
+ end
+
+ def test_assert_redirect_failure_message_with_protocol_relative_url
+ begin
+ process :redirect_external_protocol_relative
+ assert_redirected_to "/foo"
+ rescue ActiveSupport::TestCase::Assertion => ex
+ assert_no_match(
+ /#{request.protocol}#{request.host}\/\/www.rubyonrails.org/,
+ ex.message,
+ 'protocol relative url was incorrectly normalized'
+ )
+ end
+ end
+
+ def test_template_objects_exist
+ process :assign_this
+ assert !@controller.instance_variable_defined?(:"@hi")
+ assert @controller.instance_variable_get(:"@howdy")
+ end
+
+ def test_template_objects_missing
+ process :nothing
+ assert !@controller.instance_variable_defined?(:@howdy)
+ end
+
+ def test_empty_flash
+ process :flash_me_naked
+ assert flash.empty?
+ end
+
+ def test_flash_exist
+ process :flash_me
+ assert flash.any?
+ assert flash['hello'].present?
+ end
+
+ def test_flash_does_not_exist
+ process :nothing
+ assert flash.empty?
+ end
+
+ def test_session_exist
+ process :session_stuffing
+ assert_equal session['xmas'], 'turkey'
+ end
+
+ def session_does_not_exist
+ process :nothing
+ assert session.empty?
+ end
+
+ def test_render_template_action
+ process :nothing
+ assert_template nil
+
+ process :hello_world
+ assert_template 'hello_world'
+ end
+
+ def test_redirection_location
+ process :redirect_internal
+ assert_equal 'http://test.host/nothing', @response.redirect_url
+
+ process :redirect_external
+ assert_equal 'http://www.rubyonrails.org', @response.redirect_url
+
+ process :redirect_external_protocol_relative
+ assert_equal '//www.rubyonrails.org', @response.redirect_url
+ end
+
+ def test_no_redirect_url
+ process :nothing
+ assert_nil @response.redirect_url
+ end
+
+ def test_server_error_response_code
+ process :response500
+ assert @response.server_error?
+
+ process :response599
+ assert @response.server_error?
+
+ process :response404
+ assert !@response.server_error?
+ end
+
+ def test_missing_response_code
+ process :response404
+ assert @response.missing?
+ end
+
+ def test_client_error_response_code
+ process :response404
+ assert @response.client_error?
+ end
+
+ def test_redirect_url_match
+ process :redirect_external
+ assert @response.redirect?
+ assert_match(/rubyonrails/, @response.redirect_url)
+ assert !/perloffrails/.match(@response.redirect_url)
+ end
+
+ def test_redirection
+ process :redirect_internal
+ assert @response.redirect?
+
+ process :redirect_external
+ assert @response.redirect?
+
+ process :nothing
+ assert !@response.redirect?
+ end
+
+ def test_successful_response_code
+ process :nothing
+ assert @response.success?
+ end
+
+ def test_response_object
+ process :nothing
+ assert_kind_of ActionController::TestResponse, @response
+ end
+
+ def test_render_based_on_parameters
+ process :render_based_on_parameters, "GET", "name" => "David"
+ assert_equal "Mr. David", @response.body
+ end
+
+ def test_assert_redirection_fails_with_incorrect_controller
+ process :redirect_to_controller
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to :controller => "action_pack_assertions", :action => "flash_me"
+ end
+ end
+
+ def test_assert_redirection_with_extra_controller_option
+ get :redirect_to_action
+ assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' }
+ end
+
+ def test_redirected_to_url_leading_slash
+ process :redirect_to_path
+ assert_redirected_to '/some/path'
+ end
+
+ def test_redirected_to_url_no_leading_slash_fails
+ process :redirect_to_path
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_redirected_to 'some/path'
+ end
+ end
+
+ def test_redirect_invalid_external_route
+ process :redirect_invalid_external_route
+ assert_redirected_to "http://test.hostht_tp://www.rubyonrails.org"
+ end
+
+ def test_redirected_to_url_full_url
+ process :redirect_to_path
+ assert_redirected_to 'http://test.host/some/path'
+ end
+
+ def test_assert_redirection_with_symbol
+ process :redirect_to_controller_with_symbol
+ assert_nothing_raised {
+ assert_redirected_to :controller => "elsewhere", :action => "flash_me"
+ }
+ process :redirect_to_controller_with_symbol
+ assert_nothing_raised {
+ assert_redirected_to :controller => :elsewhere, :action => :flash_me
+ }
+ end
+
+ def test_redirected_to_with_nested_controller
+ @controller = Admin::InnerModuleController.new
+ get :redirect_to_absolute_controller
+ assert_redirected_to :controller => '/content'
+
+ get :redirect_to_fellow_controller
+ assert_redirected_to :controller => 'admin/user'
+ end
+
+ def test_assert_response_uses_exception_message
+ @controller = AssertResponseWithUnexpectedErrorController.new
+ e = assert_raise RuntimeError, 'Expected non-success response' do
+ get :index
+ end
+ assert_response :success
+ assert_includes 'FAIL', e.message
+ end
+
+ def test_assert_response_failure_response_with_no_exception
+ @controller = AssertResponseWithUnexpectedErrorController.new
+ get :show
+ assert_response 500
+ assert_equal 'Boom', response.body
+ end
+end
+
+class AssertTemplateTest < ActionController::TestCase
+ tests ActionPackAssertionsController
+
+ def test_with_invalid_hash_keys_raises_argument_error
+ assert_raise(ArgumentError) do
+ assert_template foo: "bar"
+ end
+ end
+
+ def test_with_partial
+ get :partial
+ assert_template :partial => '_partial'
+ end
+
+ def test_file_with_absolute_path_success
+ get :render_file_absolute_path
+ assert_template :file => File.expand_path('../../../README.rdoc', __FILE__)
+ end
+
+ def test_file_with_relative_path_success
+ get :render_file_relative_path
+ assert_template :file => 'README.rdoc'
+ end
+
+ def test_with_file_failure
+ get :render_file_absolute_path
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :file => 'test/hello_world'
+ end
+ end
+
+ def test_with_nil_passes_when_no_template_rendered
+ get :nothing
+ assert_template nil
+ end
+
+ def test_with_nil_fails_when_template_rendered
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template nil
+ end
+ end
+
+ def test_with_empty_string_fails_when_template_rendered
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template ""
+ end
+ end
+
+ def test_with_empty_string_fails_when_no_template_rendered
+ get :nothing
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template ""
+ end
+ end
+
+ def test_passes_with_correct_string
+ get :hello_world
+ assert_template 'hello_world'
+ assert_template 'test/hello_world'
+ end
+
+ def test_passes_with_correct_symbol
+ get :hello_world
+ assert_template :hello_world
+ end
+
+ def test_fails_with_incorrect_string
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template 'hello_planet'
+ end
+ end
+
+ def test_fails_with_incorrect_string_that_matches
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template 'est/he'
+ end
+ end
+
+ def test_fails_with_repeated_name_in_path
+ get :hello_repeating_in_path
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template 'test/hello'
+ end
+ end
+
+ def test_fails_with_incorrect_symbol
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :hello_planet
+ end
+ end
+
+ def test_fails_with_incorrect_symbol_that_matches
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :"est/he"
+ end
+ end
+
+ def test_fails_with_wrong_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => "application"
+ end
+ end
+
+ def test_fails_expecting_no_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => nil
+ end
+ end
+
+ def test_passes_with_correct_layout
+ get :render_with_layout
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passes_with_layout_and_partial
+ get :render_with_layout_and_partial
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passed_with_no_layout
+ get :hello_world
+ assert_template :layout => nil
+ end
+
+ def test_passed_with_no_layout_false
+ get :hello_world
+ assert_template :layout => false
+ end
+
+ def test_passes_with_correct_layout_without_layouts_prefix
+ get :render_with_layout
+ assert_template :layout => "standard"
+ end
+
+ def test_passes_with_correct_layout_symbol
+ get :render_with_layout
+ assert_template :layout => :standard
+ end
+
+ def test_assert_template_reset_between_requests
+ get :hello_world
+ assert_template 'test/hello_world'
+
+ get :nothing
+ assert_template nil
+ end
+end
+
+class ActionPackHeaderTest < ActionController::TestCase
+ tests ActionPackAssertionsController
+
+ def test_rendering_xml_sets_content_type
+ process :hello_xml_world
+ assert_equal('application/xml; charset=utf-8', @response.headers['Content-Type'])
+ end
+
+ def test_rendering_xml_respects_content_type
+ process :hello_xml_world_pdf
+ assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type'])
+ end
+
+ def test_rendering_xml_respects_content_type_when_set_in_the_header
+ process :hello_xml_world_pdf_header
+ assert_equal('application/pdf; charset=utf-8', @response.headers['Content-Type'])
+ end
+
+ def test_render_text_with_custom_content_type
+ get :render_text_with_custom_content_type
+ assert_equal 'application/rss+xml; charset=utf-8', @response.headers['Content-Type']
+ end
+end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
new file mode 100644
index 0000000000..f07d201563
--- /dev/null
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -0,0 +1,356 @@
+# encoding: utf-8
+#--
+# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
+# Under MIT and/or CC By license.
+#++
+
+require 'abstract_unit'
+require 'controller/fake_controllers'
+
+require 'action_mailer'
+require 'action_view'
+
+ActionMailer::Base.send(:include, ActionView::Layouts)
+ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
+
+class AssertSelectTest < ActionController::TestCase
+ Assertion = ActiveSupport::TestCase::Assertion
+
+ class AssertSelectMailer < ActionMailer::Base
+ def test(html)
+ mail :body => html, :content_type => "text/html",
+ :subject => "Test e-mail", :from => "test@test.host", :to => "test <test@test.host>"
+ end
+ end
+
+ class AssertMultipartSelectMailer < ActionMailer::Base
+ def test(options)
+ mail :subject => "Test e-mail", :from => "test@test.host", :to => "test <test@test.host>" do |format|
+ format.text { render :text => options[:text] }
+ format.html { render :text => options[:html] }
+ end
+ end
+ end
+
+ class AssertSelectController < ActionController::Base
+ def response_with=(content)
+ @content = content
+ end
+
+ def response_with(&block)
+ @update = block
+ end
+
+ def html()
+ render :text=>@content, :layout=>false, :content_type=>Mime::HTML
+ @content = nil
+ end
+
+ def xml()
+ render :text=>@content, :layout=>false, :content_type=>Mime::XML
+ @content = nil
+ end
+ end
+
+ tests AssertSelectController
+
+ def setup
+ super
+ @old_delivery_method = ActionMailer::Base.delivery_method
+ @old_perform_deliveries = ActionMailer::Base.perform_deliveries
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ end
+
+ def teardown
+ super
+ ActionMailer::Base.delivery_method = @old_delivery_method
+ ActionMailer::Base.perform_deliveries = @old_perform_deliveries
+ ActionMailer::Base.deliveries.clear
+ end
+
+ def assert_failure(message, &block)
+ e = assert_raise(Assertion, &block)
+ assert_match(message, e.message) if Regexp === message
+ assert_equal(message, e.message) if String === message
+ end
+
+ #
+ # Test assert select.
+ #
+
+ def test_assert_select
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_select "div", 2
+ assert_failure(/\AExpected at least 1 element matching \"p\", found 0\.$/) { assert_select "p" }
+ end
+
+ def test_equality_integer
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) { assert_select "div", 3 }
+ assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", 0 }
+ end
+
+ def test_equality_true_false
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_nothing_raised { assert_select "div" }
+ assert_raise(Assertion) { assert_select "p" }
+ assert_nothing_raised { assert_select "div", true }
+ assert_raise(Assertion) { assert_select "p", true }
+ assert_raise(Assertion) { assert_select "div", false }
+ assert_nothing_raised { assert_select "p", false }
+ end
+
+ def test_equality_false_message
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_failure(/\AExpected exactly 0 elements matching \"div\", found 2\.$/) { assert_select "div", false }
+ end
+
+ def test_equality_string_and_regexp
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_nothing_raised { assert_select "div", "foo" }
+ assert_raise(Assertion) { assert_select "div", "bar" }
+ assert_failure(/\A<bar> expected but was\n<foo>\.$/) { assert_select "div", "bar" }
+ assert_nothing_raised { assert_select "div", :text=>"foo" }
+ assert_raise(Assertion) { assert_select "div", :text=>"bar" }
+ assert_nothing_raised { assert_select "div", /(foo|bar)/ }
+ assert_raise(Assertion) { assert_select "div", /foobar/ }
+ assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ }
+ assert_raise(Assertion) { assert_select "div", :text=>/foobar/ }
+ assert_raise(Assertion) { assert_select "p", :text=>/foobar/ }
+ end
+
+ def test_equality_of_html
+ render_html %Q{<p>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</p>}
+ text = "\"This is not a big problem,\" he said."
+ html = "<em>\"This is <strong>not</strong> a big problem,\"</em> he said."
+ assert_nothing_raised { assert_select "p", text }
+ assert_raise(Assertion) { assert_select "p", html }
+ assert_nothing_raised { assert_select "p", :html=>html }
+ assert_raise(Assertion) { assert_select "p", :html=>text }
+ assert_failure(/\A<#{text}> expected but was\n<#{html}>\.$/) { assert_select "p", :html=>text }
+ # No stripping for pre.
+ render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>}
+ text = "\n\"This is not a big problem,\" he said.\n"
+ html = "\n<em>\"This is <strong>not</strong> a big problem,\"</em> he said.\n"
+ assert_nothing_raised { assert_select "pre", text }
+ assert_raise(Assertion) { assert_select "pre", html }
+ assert_nothing_raised { assert_select "pre", :html=>html }
+ assert_raise(Assertion) { assert_select "pre", :html=>text }
+ end
+
+ def test_strip_textarea
+ render_html %Q{<textarea>\n\nfoo\n</textarea>}
+ assert_select "textarea", "\nfoo\n"
+ render_html %Q{<textarea>\nfoo</textarea>}
+ assert_select "textarea", "foo"
+ end
+
+ def test_counts
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_nothing_raised { assert_select "div", 2 }
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do
+ assert_select "div", 3
+ end
+ assert_nothing_raised { assert_select "div", 1..2 }
+ assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do
+ assert_select "div", 3..4
+ end
+ assert_nothing_raised { assert_select "div", :count=>2 }
+ assert_failure(/\AExpected exactly 3 elements matching \"div\", found 2\.$/) do
+ assert_select "div", :count=>3
+ end
+ assert_nothing_raised { assert_select "div", :minimum=>1 }
+ assert_nothing_raised { assert_select "div", :minimum=>2 }
+ assert_failure(/\AExpected at least 3 elements matching \"div\", found 2\.$/) do
+ assert_select "div", :minimum=>3
+ end
+ assert_nothing_raised { assert_select "div", :maximum=>2 }
+ assert_nothing_raised { assert_select "div", :maximum=>3 }
+ assert_failure(/\AExpected at most 1 element matching \"div\", found 2\.$/) do
+ assert_select "div", :maximum=>1
+ end
+ assert_nothing_raised { assert_select "div", :minimum=>1, :maximum=>2 }
+ assert_failure(/\AExpected between 3 and 4 elements matching \"div\", found 2\.$/) do
+ assert_select "div", :minimum=>3, :maximum=>4
+ end
+ end
+
+ def test_substitution_values
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_select "div#?", /\d+/ do |elements|
+ assert_equal 2, elements.size
+ end
+ assert_select "div" do
+ assert_select "div#?", /\d+/ do |elements|
+ assert_equal 2, elements.size
+ assert_select "#1"
+ assert_select "#2"
+ end
+ end
+ end
+
+ def test_nested_assert_select
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_select "div" do |elements|
+ assert_equal 2, elements.size
+ assert_select elements[0], "#1"
+ assert_select elements[1], "#2"
+ end
+ assert_select "div" do
+ assert_select "div" do |elements|
+ assert_equal 2, elements.size
+ # Testing in a group is one thing
+ assert_select "#1,#2"
+ # Testing individually is another.
+ assert_select "#1"
+ assert_select "#2"
+ assert_select "#3", false
+ end
+ end
+
+ assert_failure(/\AExpected at least 1 element matching \"#4\", found 0\.$/) do
+ assert_select "div" do
+ assert_select "#4"
+ end
+ end
+ end
+
+ def test_assert_select_text_match
+ render_html %Q{<div id="1"><span>foo</span></div><div id="2"><span>bar</span></div>}
+ assert_select "div" do
+ assert_nothing_raised { assert_select "div", "foo" }
+ assert_nothing_raised { assert_select "div", "bar" }
+ assert_nothing_raised { assert_select "div", /\w*/ }
+ assert_nothing_raised { assert_select "div", :text => /\w*/, :count=>2 }
+ assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 }
+ assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
+ assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" }
+ assert_nothing_raised { assert_select "div", :html=>/\w*/ }
+ assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 }
+ assert_raise(Assertion) { assert_select "div", :html=>"<span>foo</span>", :count=>2 }
+ end
+ end
+
+ def test_elect_with_xml_namespace_attributes
+ render_html %Q{<link xlink:href="http://nowhere.com"></link>}
+ assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" }
+ end
+
+ #
+ # Test css_select.
+ #
+
+ def test_css_select
+ render_html %Q{<div id="1"></div><div id="2"></div>}
+ assert_equal 2, css_select("div").size
+ assert_equal 0, css_select("p").size
+ end
+
+ def test_nested_css_select
+ render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
+ assert_select "div#?", /\d+/ do |elements|
+ assert_equal 1, css_select(elements[0], "div").size
+ assert_equal 1, css_select(elements[1], "div").size
+ end
+ assert_select "div" do
+ assert_equal 2, css_select("div").size
+ css_select("div").each do |element|
+ # Testing as a group is one thing
+ assert !css_select("#1,#2").empty?
+ # Testing individually is another
+ assert !css_select("#1").empty?
+ assert !css_select("#2").empty?
+ end
+ end
+ end
+
+ def test_feed_item_encoded
+ render_xml <<-EOF
+<rss version="2.0">
+ <channel>
+ <item>
+ <description>
+ <![CDATA[
+ <p>Test 1</p>
+ ]]>
+ </description>
+ </item>
+ <item>
+ <description>
+ <![CDATA[
+ <p>Test 2</p>
+ ]]>
+ </description>
+ </item>
+ </channel>
+</rss>
+EOF
+ assert_select "channel item description" do
+ # Test element regardless of wrapper.
+ assert_select_encoded do
+ assert_select "p", :count=>2, :text=>/Test/
+ end
+ # Test through encoded wrapper.
+ assert_select_encoded do
+ assert_select "encoded p", :count=>2, :text=>/Test/
+ end
+ # Use :root instead (recommended)
+ assert_select_encoded do
+ assert_select ":root p", :count=>2, :text=>/Test/
+ end
+ # Test individually.
+ assert_select "description" do |elements|
+ assert_select_encoded elements[0] do
+ assert_select "p", "Test 1"
+ end
+ assert_select_encoded elements[1] do
+ assert_select "p", "Test 2"
+ end
+ end
+ end
+
+ # Test that we only un-encode element itself.
+ assert_select "channel item" do
+ assert_select_encoded do
+ assert_select "p", 0
+ end
+ end
+ end
+
+ #
+ # Test assert_select_email
+ #
+
+ def test_assert_select_email
+ assert_raise(Assertion) { assert_select_email {} }
+ AssertSelectMailer.test("<div><p>foo</p><p>bar</p></div>").deliver
+ assert_select_email do
+ assert_select "div:root" do
+ assert_select "p:first-child", "foo"
+ assert_select "p:last-child", "bar"
+ end
+ end
+ end
+
+ def test_assert_select_email_multipart
+ AssertMultipartSelectMailer.test(:html => "<div><p>foo</p><p>bar</p></div>", :text => 'foo bar').deliver
+ assert_select_email do
+ assert_select "div:root" do
+ assert_select "p:first-child", "foo"
+ assert_select "p:last-child", "bar"
+ end
+ end
+ end
+
+ protected
+ def render_html(html)
+ @controller.response_with = html
+ get :html
+ end
+
+ def render_xml(xml)
+ @controller.response_with = xml
+ get :xml
+ end
+end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
new file mode 100644
index 0000000000..b2bfdae174
--- /dev/null
+++ b/actionpack/test/controller/base_test.rb
@@ -0,0 +1,302 @@
+require 'abstract_unit'
+require 'active_support/logger'
+require 'controller/fake_models'
+require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
+
+# Provide some controller to run the tests on.
+module Submodule
+ class ContainedEmptyController < ActionController::Base
+ end
+
+ class ContainedNonEmptyController < ActionController::Base
+ def public_action
+ render :nothing => true
+ end
+
+ hide_action :hidden_action
+ def hidden_action
+ raise "Noooo!"
+ end
+
+ def another_hidden_action
+ end
+ hide_action :another_hidden_action
+ end
+
+ class SubclassedController < ContainedNonEmptyController
+ hide_action :public_action # Hiding it here should not affect the superclass.
+ end
+end
+
+class EmptyController < ActionController::Base
+end
+
+class NonEmptyController < ActionController::Base
+ def public_action
+ render :nothing => true
+ end
+
+ hide_action :hidden_action
+ def hidden_action
+ end
+end
+
+class DefaultUrlOptionsController < ActionController::Base
+ def from_view
+ render :inline => "<%= #{params[:route]} %>"
+ end
+
+ def default_url_options
+ { :host => 'www.override.com', :action => 'new', :locale => 'en' }
+ end
+end
+
+class UrlOptionsController < ActionController::Base
+ def from_view
+ render :inline => "<%= #{params[:route]} %>"
+ end
+
+ def url_options
+ super.merge(:host => 'www.override.com')
+ end
+end
+
+class RecordIdentifierIncludedController < ActionController::Base
+ include ActionView::RecordIdentifier
+end
+
+class ActionMissingController < ActionController::Base
+ def action_missing(action)
+ render :text => "Response for #{action}"
+ end
+end
+
+class ControllerClassTests < ActiveSupport::TestCase
+
+ def test_controller_path
+ assert_equal 'empty', EmptyController.controller_path
+ assert_equal EmptyController.controller_path, EmptyController.new.controller_path
+ assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
+ assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
+ end
+
+ def test_controller_name
+ assert_equal 'empty', EmptyController.controller_name
+ assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
+ end
+
+ def test_no_deprecation_when_action_view_record_identifier_is_included
+ record = Comment.new
+ record.save
+
+ dom_id = nil
+ assert_not_deprecated do
+ dom_id = RecordIdentifierIncludedController.new.dom_id(record)
+ end
+
+ assert_equal 'comment_1', dom_id
+
+ dom_class = nil
+ assert_not_deprecated do
+ dom_class = RecordIdentifierIncludedController.new.dom_class(record)
+ end
+ assert_equal 'comment', dom_class
+ end
+end
+
+class ControllerInstanceTests < ActiveSupport::TestCase
+ def setup
+ @empty = EmptyController.new
+ @contained = Submodule::ContainedEmptyController.new
+ @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
+
+ @non_empty_controllers = [NonEmptyController.new,
+ Submodule::ContainedNonEmptyController.new]
+ end
+
+ def test_performed?
+ assert !@empty.performed?
+ @empty.response_body = ["sweet"]
+ assert @empty.performed?
+ end
+
+ def test_action_methods
+ @empty_controllers.each do |c|
+ assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!"
+ end
+
+ @non_empty_controllers.each do |c|
+ assert_equal Set.new(%w(public_action)), c.class.action_methods, "#{c.controller_path} should not be empty!"
+ end
+ end
+
+ def test_temporary_anonymous_controllers
+ name = 'ExamplesController'
+ klass = Class.new(ActionController::Base)
+ Object.const_set(name, klass)
+
+ controller = klass.new
+ assert_equal "examples", controller.controller_path
+ end
+end
+
+class PerformActionTest < ActionController::TestCase
+ def use_controller(controller_class)
+ @controller = controller_class.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = ActiveSupport::Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_process_should_be_precise
+ use_controller EmptyController
+ exception = assert_raise AbstractController::ActionNotFound do
+ get :non_existent
+ end
+ assert_equal exception.message, "The action 'non_existent' could not be found for EmptyController"
+ end
+
+ def test_get_on_hidden_should_fail
+ use_controller NonEmptyController
+ assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
+ assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
+ end
+
+ def test_action_missing_should_work
+ use_controller ActionMissingController
+ get :arbitrary_action
+ assert_equal "Response for arbitrary_action", @response.body
+ end
+end
+
+class UrlOptionsTest < ActionController::TestCase
+ tests UrlOptionsController
+
+ def setup
+ super
+ @request.host = 'www.example.com'
+ end
+
+ def test_url_for_query_params_included
+ rs = ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ get 'home' => 'pages#home'
+ end
+
+ options = {
+ :action => "home",
+ :controller => "pages",
+ :only_path => true,
+ :params => { "token" => "secret" }
+ }
+
+ assert_equal '/home?token=secret', rs.url_for(options)
+ end
+
+ def test_url_options_override
+ with_routing do |set|
+ set.draw do
+ get 'from_view', :to => 'url_options#from_view', :as => :from_view
+ get ':controller/:action'
+ end
+
+ get :from_view, :route => "from_view_url"
+
+ assert_equal 'http://www.override.com/from_view', @response.body
+ assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url)
+ assert_equal 'http://www.override.com/default_url_options/index', @controller.url_for(:controller => 'default_url_options')
+ end
+ end
+
+ def test_url_helpers_does_not_become_actions
+ with_routing do |set|
+ set.draw do
+ get "account/overview"
+ end
+
+ assert !@controller.class.action_methods.include?("account_overview_path")
+ end
+ end
+end
+
+class DefaultUrlOptionsTest < ActionController::TestCase
+ tests DefaultUrlOptionsController
+
+ def setup
+ super
+ @request.host = 'www.example.com'
+ end
+
+ def test_default_url_options_override
+ with_routing do |set|
+ set.draw do
+ get 'from_view', :to => 'default_url_options#from_view', :as => :from_view
+ get ':controller/:action'
+ end
+
+ get :from_view, :route => "from_view_url"
+
+ assert_equal 'http://www.override.com/from_view?locale=en', @response.body
+ assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
+ assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
+ end
+ end
+
+ def test_default_url_options_are_used_in_non_positional_parameters
+ with_routing do |set|
+ set.draw do
+ scope("/:locale") do
+ resources :descriptions
+ end
+ get ':controller/:action'
+ end
+
+ get :from_view, :route => "description_path(1)"
+
+ assert_equal '/en/descriptions/1', @response.body
+ assert_equal '/en/descriptions', @controller.send(:descriptions_path)
+ assert_equal '/pl/descriptions', @controller.send(:descriptions_path, "pl")
+ assert_equal '/pl/descriptions', @controller.send(:descriptions_path, :locale => "pl")
+ assert_equal '/pl/descriptions.xml', @controller.send(:descriptions_path, "pl", "xml")
+ assert_equal '/en/descriptions.xml', @controller.send(:descriptions_path, :format => "xml")
+ assert_equal '/en/descriptions/1', @controller.send(:description_path, 1)
+ assert_equal '/pl/descriptions/1', @controller.send(:description_path, "pl", 1)
+ assert_equal '/pl/descriptions/1', @controller.send(:description_path, 1, :locale => "pl")
+ assert_equal '/pl/descriptions/1.xml', @controller.send(:description_path, "pl", 1, "xml")
+ assert_equal '/en/descriptions/1.xml', @controller.send(:description_path, 1, :format => "xml")
+ end
+ end
+
+end
+
+class EmptyUrlOptionsTest < ActionController::TestCase
+ tests NonEmptyController
+
+ def setup
+ super
+ @request.host = 'www.example.com'
+ end
+
+ def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set
+ get :public_action
+ assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for
+ end
+
+ def test_named_routes_with_path_without_doing_a_request_first
+ @controller = EmptyController.new
+ @controller.request = @request
+
+ with_routing do |set|
+ set.draw do
+ resources :things
+ end
+
+ assert_equal '/things', @controller.send(:things_path)
+ end
+ end
+end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
new file mode 100644
index 0000000000..c0e6a2ebd1
--- /dev/null
+++ b/actionpack/test/controller/caching_test.rb
@@ -0,0 +1,351 @@
+require 'fileutils'
+require 'abstract_unit'
+
+CACHE_DIR = 'test_cache'
+# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
+FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
+
+class FragmentCachingMetalTestController < ActionController::Metal
+ abstract!
+
+ include ActionController::Caching
+
+ def some_action; end
+end
+
+class FragmentCachingMetalTest < ActionController::TestCase
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @controller = FragmentCachingMetalTestController.new
+ @controller.perform_caching = true
+ @controller.cache_store = @store
+ @params = { controller: 'posts', action: 'index' }
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller.params = @params
+ @controller.request = @request
+ @controller.response = @response
+ end
+
+ def test_fragment_cache_key
+ assert_equal 'views/what a key', @controller.fragment_cache_key('what a key')
+ end
+end
+
+class CachingController < ActionController::Base
+ abstract!
+
+ self.cache_store = :file_store, FILE_STORE_PATH
+end
+
+class FragmentCachingTestController < CachingController
+ def some_action; end
+end
+
+class FragmentCachingTest < ActionController::TestCase
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @controller = FragmentCachingTestController.new
+ @controller.perform_caching = true
+ @controller.cache_store = @store
+ @params = {:controller => 'posts', :action => 'index'}
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller.params = @params
+ @controller.request = @request
+ @controller.response = @response
+ end
+
+ def test_fragment_cache_key
+ assert_equal 'views/what a key', @controller.fragment_cache_key('what a key')
+ assert_equal "views/test.host/fragment_caching_test/some_action",
+ @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')
+ end
+
+ def test_read_fragment_with_caching_enabled
+ @store.write('views/name', 'value')
+ assert_equal 'value', @controller.read_fragment('name')
+ end
+
+ def test_read_fragment_with_caching_disabled
+ @controller.perform_caching = false
+ @store.write('views/name', 'value')
+ assert_nil @controller.read_fragment('name')
+ end
+
+ def test_fragment_exist_with_caching_enabled
+ @store.write('views/name', 'value')
+ assert @controller.fragment_exist?('name')
+ assert !@controller.fragment_exist?('other_name')
+ end
+
+ def test_fragment_exist_with_caching_disabled
+ @controller.perform_caching = false
+ @store.write('views/name', 'value')
+ assert !@controller.fragment_exist?('name')
+ assert !@controller.fragment_exist?('other_name')
+ end
+
+ def test_write_fragment_with_caching_enabled
+ assert_nil @store.read('views/name')
+ assert_equal 'value', @controller.write_fragment('name', 'value')
+ assert_equal 'value', @store.read('views/name')
+ end
+
+ def test_write_fragment_with_caching_disabled
+ assert_nil @store.read('views/name')
+ @controller.perform_caching = false
+ assert_equal 'value', @controller.write_fragment('name', 'value')
+ assert_nil @store.read('views/name')
+ end
+
+ def test_expire_fragment_with_simple_key
+ @store.write('views/name', 'value')
+ @controller.expire_fragment 'name'
+ assert_nil @store.read('views/name')
+ end
+
+ def test_expire_fragment_with_regexp
+ @store.write('views/name', 'value')
+ @store.write('views/another_name', 'another_value')
+ @store.write('views/primalgrasp', 'will not expire ;-)')
+
+ @controller.expire_fragment(/name/)
+
+ assert_nil @store.read('views/name')
+ assert_nil @store.read('views/another_name')
+ assert_equal 'will not expire ;-)', @store.read('views/primalgrasp')
+ end
+
+ def test_fragment_for
+ @store.write('views/expensive', 'fragment content')
+ fragment_computed = false
+
+ view_context = @controller.view_context
+
+ buffer = 'generated till now -> '.html_safe
+ buffer << view_context.send(:fragment_for, 'expensive') { fragment_computed = true }
+
+ assert !fragment_computed
+ assert_equal 'generated till now -> fragment content', buffer
+ end
+
+ def test_html_safety
+ assert_nil @store.read('views/name')
+ content = 'value'.html_safe
+ assert_equal content, @controller.write_fragment('name', content)
+
+ cached = @store.read('views/name')
+ assert_equal content, cached
+ assert_equal String, cached.class
+
+ html_safe = @controller.read_fragment('name')
+ assert_equal content, html_safe
+ assert html_safe.html_safe?
+ end
+end
+
+class FunctionalCachingController < CachingController
+ def fragment_cached
+ end
+
+ def html_fragment_cached_with_partial
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ def formatted_fragment_cached
+ respond_to do |format|
+ format.html
+ format.xml
+ end
+ end
+
+ def formatted_fragment_cached_with_variant
+ respond_to do |format|
+ format.html.phone
+ format.html
+ end
+ end
+
+ def fragment_cached_without_digest
+ end
+end
+
+class FunctionalFragmentCachingTest < ActionController::TestCase
+ def setup
+ super
+ @store = ActiveSupport::Cache::MemoryStore.new
+ @controller = FunctionalCachingController.new
+ @controller.perform_caching = true
+ @controller.cache_store = @store
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_fragment_caching
+ get :fragment_cached
+ assert_response :success
+ expected_body = <<-CACHED
+Hello
+This bit's fragment cached
+Ciao
+CACHED
+ assert_equal expected_body, @response.body
+
+ assert_equal "This bit's fragment cached",
+ @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}")
+ end
+
+ def test_fragment_caching_in_partials
+ get :html_fragment_cached_with_partial
+ assert_response :success
+ assert_match(/Old fragment caching in a partial/, @response.body)
+
+ assert_match("Old fragment caching in a partial",
+ @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}"))
+ end
+
+ def test_skipping_fragment_cache_digesting
+ get :fragment_cached_without_digest, :format => "html"
+ assert_response :success
+ expected_body = "<body>\n<p>ERB</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+ assert_equal "<p>ERB</p>", @store.read("views/nodigest")
+ end
+
+ def test_render_inline_before_fragment_caching
+ get :inline_fragment_cached
+ assert_response :success
+ assert_match(/Some inline content/, @response.body)
+ assert_match(/Some cached content/, @response.body)
+ assert_match("Some cached content",
+ @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}"))
+ end
+
+ def test_fragment_cache_instrumentation
+ payload = nil
+
+ subscriber = proc do |*args|
+ event = ActiveSupport::Notifications::Event.new(*args)
+ payload = event.payload
+ end
+
+ ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_controller") do
+ get :inline_fragment_cached
+ end
+
+ assert_equal "functional_caching", payload[:controller]
+ assert_equal "inline_fragment_cached", payload[:action]
+ end
+
+ def test_html_formatted_fragment_caching
+ get :formatted_fragment_cached, :format => "html"
+ assert_response :success
+ expected_body = "<body>\n<p>ERB</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+
+ assert_equal "<p>ERB</p>",
+ @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ end
+
+ def test_xml_formatted_fragment_caching
+ get :formatted_fragment_cached, :format => "xml"
+ assert_response :success
+ expected_body = "<body>\n <p>Builder</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+
+ assert_equal " <p>Builder</p>\n",
+ @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}")
+ end
+
+
+ def test_fragment_caching_with_variant
+ @request.variant = :phone
+
+ get :formatted_fragment_cached_with_variant, :format => "html"
+ assert_response :success
+ expected_body = "<body>\n<p>PHONE</p>\n</body>\n"
+
+ assert_equal expected_body, @response.body
+
+ assert_equal "<p>PHONE</p>",
+ @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}")
+ end
+
+ private
+ def template_digest(name)
+ ActionView::Digestor.digest(name: name, finder: @controller.lookup_context)
+ end
+end
+
+class CacheHelperOutputBufferTest < ActionController::TestCase
+
+ class MockController
+ def read_fragment(name, options)
+ return false
+ end
+
+ def write_fragment(name, fragment, options)
+ fragment
+ end
+ end
+
+ def setup
+ super
+ end
+
+ def test_output_buffer
+ output_buffer = ActionView::OutputBuffer.new
+ controller = MockController.new
+ cache_helper = Object.new
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+ cache_helper.expects(:controller).returns(controller).at_least(0)
+ cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0)
+ # if the output_buffer is changed, the new one should be html_safe and of the same type
+ cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0)
+
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil }
+ end
+ end
+
+ def test_safe_buffer
+ output_buffer = ActiveSupport::SafeBuffer.new
+ controller = MockController.new
+ cache_helper = Object.new
+ cache_helper.extend(ActionView::Helpers::CacheHelper)
+ cache_helper.expects(:controller).returns(controller).at_least(0)
+ cache_helper.expects(:output_buffer).returns(output_buffer).at_least(0)
+ # if the output_buffer is changed, the new one should be html_safe and of the same type
+ cache_helper.expects(:output_buffer=).with(responds_with(:html_safe?, true)).with(instance_of(output_buffer.class)).at_least(0)
+
+ assert_nothing_raised do
+ cache_helper.send :fragment_for, 'Test fragment name', 'Test fragment', &Proc.new{ nil }
+ end
+ end
+end
+
+class ViewCacheDependencyTest < ActionController::TestCase
+ class NoDependenciesController < ActionController::Base
+ end
+
+ class HasDependenciesController < ActionController::Base
+ view_cache_dependency { "trombone" }
+ view_cache_dependency { "flute" }
+ end
+
+ def test_view_cache_dependencies_are_empty_by_default
+ assert NoDependenciesController.new.view_cache_dependencies.empty?
+ end
+
+ def test_view_cache_dependencies_are_listed_in_declaration_order
+ assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies
+ end
+end
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
new file mode 100644
index 0000000000..89667df3a4
--- /dev/null
+++ b/actionpack/test/controller/content_type_test.rb
@@ -0,0 +1,167 @@
+require 'abstract_unit'
+
+class OldContentTypeController < ActionController::Base
+ # :ported:
+ def render_content_type_from_body
+ response.content_type = Mime::RSS
+ render :text => "hello world!"
+ end
+
+ # :ported:
+ def render_defaults
+ render :text => "hello world!"
+ end
+
+ # :ported:
+ def render_content_type_from_render
+ render :text => "hello world!", :content_type => Mime::RSS
+ end
+
+ # :ported:
+ def render_charset_from_body
+ response.charset = "utf-16"
+ render :text => "hello world!"
+ end
+
+ # :ported:
+ def render_nil_charset_from_body
+ response.charset = nil
+ render :text => "hello world!"
+ end
+
+ def render_default_for_erb
+ end
+
+ def render_default_for_builder
+ end
+
+ def render_change_for_builder
+ response.content_type = Mime::HTML
+ render :action => "render_default_for_builder"
+ end
+
+ def render_default_content_types_for_respond_to
+ respond_to do |format|
+ format.html { render :text => "hello world!" }
+ format.xml { render :action => "render_default_content_types_for_respond_to" }
+ format.js { render :text => "hello world!" }
+ format.rss { render :text => "hello world!", :content_type => Mime::XML }
+ end
+ end
+end
+
+class ContentTypeTest < ActionController::TestCase
+ tests OldContentTypeController
+
+ def setup
+ super
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = ActiveSupport::Logger.new(nil)
+ end
+
+ # :ported:
+ def test_render_defaults
+ get :render_defaults
+ assert_equal "utf-8", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
+
+ def test_render_changed_charset_default
+ with_default_charset "utf-16" do
+ get :render_defaults
+ assert_equal "utf-16", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
+ end
+
+ # :ported:
+ def test_content_type_from_body
+ get :render_content_type_from_body
+ assert_equal Mime::RSS, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ # :ported:
+ def test_content_type_from_render
+ get :render_content_type_from_render
+ assert_equal Mime::RSS, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ # :ported:
+ def test_charset_from_body
+ get :render_charset_from_body
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-16", @response.charset
+ end
+
+ # :ported:
+ def test_nil_charset_from_body
+ get :render_nil_charset_from_body
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset, @response.headers.inspect
+ end
+
+ def test_nil_default_for_erb
+ with_default_charset nil do
+ get :render_default_for_erb
+ assert_equal Mime::HTML, @response.content_type
+ assert_nil @response.charset, @response.headers.inspect
+ end
+ end
+
+ def test_default_for_erb
+ get :render_default_for_erb
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_default_for_builder
+ get :render_default_for_builder
+ assert_equal Mime::XML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_change_for_builder
+ get :render_change_for_builder
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ private
+
+ def with_default_charset(charset)
+ old_default_charset = ActionDispatch::Response.default_charset
+ ActionDispatch::Response.default_charset = charset
+ yield
+ ensure
+ ActionDispatch::Response.default_charset = old_default_charset
+ end
+end
+
+class AcceptBasedContentTypeTest < ActionController::TestCase
+ tests OldContentTypeController
+
+ def test_render_default_content_types_for_respond_to
+ @request.accept = Mime::HTML.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::HTML, @response.content_type
+
+ @request.accept = Mime::JS.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::JS, @response.content_type
+ end
+
+ def test_render_default_content_types_for_respond_to_with_template
+ @request.accept = Mime::XML.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::XML, @response.content_type
+ end
+
+ def test_render_default_content_types_for_respond_to_with_overwrite
+ @request.accept = Mime::RSS.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::XML, @response.content_type
+ end
+end
diff --git a/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb b/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
diff --git a/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb b/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
diff --git a/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb b/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
diff --git a/actionpack/test/controller/default_url_options_with_before_action_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb
new file mode 100644
index 0000000000..656fd0431e
--- /dev/null
+++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb
@@ -0,0 +1,29 @@
+require 'abstract_unit'
+
+
+class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base
+
+ before_action { I18n.locale = params[:locale] }
+ after_action { I18n.locale = "en" }
+
+ def target
+ render :text => "final response"
+ end
+
+ def redirect
+ redirect_to :action => "target"
+ end
+
+ def default_url_options
+ {:locale => "de"}
+ end
+end
+
+class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::TestCase
+
+ # This test has its roots in issue #1872
+ test "should redirect with correct locale :de" do
+ get :redirect, :locale => "de"
+ assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de"
+ end
+end
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
new file mode 100644
index 0000000000..b2b01b3fa9
--- /dev/null
+++ b/actionpack/test/controller/filters_test.rb
@@ -0,0 +1,1047 @@
+require 'abstract_unit'
+
+class ActionController::Base
+ class << self
+ %w(append_around_action prepend_after_action prepend_around_action prepend_before_action skip_after_action skip_before_action skip_action_callback).each do |pending|
+ define_method(pending) do |*args|
+ $stderr.puts "#{pending} unimplemented: #{args.inspect}"
+ end unless method_defined?(pending)
+ end
+
+ def before_actions
+ filters = _process_action_callbacks.select { |c| c.kind == :before }
+ filters.map! { |c| c.raw_filter }
+ end
+ end
+
+ def assigns(key = nil)
+ assigns = {}
+ instance_variables.each do |ivar|
+ next if ActionController::Base.protected_instance_variables.include?(ivar)
+ assigns[ivar[1..-1]] = instance_variable_get(ivar)
+ end
+
+ key.nil? ? assigns : assigns[key.to_s]
+ end
+end
+
+class FilterTest < ActionController::TestCase
+
+ class TestController < ActionController::Base
+ before_action :ensure_login
+ after_action :clean_up
+
+ def show
+ render :inline => "ran action"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+
+ def clean_up
+ @ran_after_action ||= []
+ @ran_after_action << "clean_up"
+ end
+ end
+
+ class ChangingTheRequirementsController < TestController
+ before_action :ensure_login, :except => [:go_wild]
+
+ def go_wild
+ render :text => "gobble"
+ end
+ end
+
+ class TestMultipleFiltersController < ActionController::Base
+ before_action :try_1
+ before_action :try_2
+ before_action :try_3
+
+ (1..3).each do |i|
+ define_method "fail_#{i}" do
+ render :text => i.to_s
+ end
+ end
+
+ protected
+ (1..3).each do |i|
+ define_method "try_#{i}" do
+ instance_variable_set :@try, i
+ if action_name == "fail_#{i}"
+ head(404)
+ end
+ end
+ end
+ end
+
+ class RenderingController < ActionController::Base
+ before_action :before_action_rendering
+ after_action :unreached_after_action
+
+ def show
+ @ran_action = true
+ render :inline => "ran action"
+ end
+
+ private
+ def before_action_rendering
+ @ran_filter ||= []
+ @ran_filter << "before_action_rendering"
+ render :inline => "something else"
+ end
+
+ def unreached_after_action
+ @ran_filter << "unreached_after_action_after_render"
+ end
+ end
+
+ class RenderingForPrependAfterActionController < RenderingController
+ prepend_after_action :unreached_prepend_after_action
+
+ private
+ def unreached_prepend_after_action
+ @ran_filter << "unreached_preprend_after_action_after_render"
+ end
+ end
+
+ class BeforeActionRedirectionController < ActionController::Base
+ before_action :before_action_redirects
+ after_action :unreached_after_action
+
+ def show
+ @ran_action = true
+ render :inline => "ran show action"
+ end
+
+ def target_of_redirection
+ @ran_target_of_redirection = true
+ render :inline => "ran target_of_redirection action"
+ end
+
+ private
+ def before_action_redirects
+ @ran_filter ||= []
+ @ran_filter << "before_action_redirects"
+ redirect_to(:action => 'target_of_redirection')
+ end
+
+ def unreached_after_action
+ @ran_filter << "unreached_after_action_after_redirection"
+ end
+ end
+
+ class BeforeActionRedirectionForPrependAfterActionController < BeforeActionRedirectionController
+ prepend_after_action :unreached_prepend_after_action_after_redirection
+
+ private
+ def unreached_prepend_after_action_after_redirection
+ @ran_filter << "unreached_prepend_after_action_after_redirection"
+ end
+ end
+
+ class ConditionalFilterController < ActionController::Base
+ def show
+ render :inline => "ran action"
+ end
+
+ def another_action
+ render :inline => "ran action"
+ end
+
+ def show_without_action
+ render :inline => "ran action without action"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+
+ def clean_up_tmp
+ @ran_filter ||= []
+ @ran_filter << "clean_up_tmp"
+ end
+ end
+
+ class ConditionalCollectionFilterController < ConditionalFilterController
+ before_action :ensure_login, :except => [ :show_without_action, :another_action ]
+ end
+
+ class OnlyConditionSymController < ConditionalFilterController
+ before_action :ensure_login, :only => :show
+ end
+
+ class ExceptConditionSymController < ConditionalFilterController
+ before_action :ensure_login, :except => :show_without_action
+ end
+
+ class BeforeAndAfterConditionController < ConditionalFilterController
+ before_action :ensure_login, :only => :show
+ after_action :clean_up_tmp, :only => :show
+ end
+
+ class OnlyConditionProcController < ConditionalFilterController
+ before_action(:only => :show) {|c| c.instance_variable_set(:"@ran_proc_action", true) }
+ end
+
+ class ExceptConditionProcController < ConditionalFilterController
+ before_action(:except => :show_without_action) {|c| c.instance_variable_set(:"@ran_proc_action", true) }
+ end
+
+ class ConditionalClassFilter
+ def self.before(controller) controller.instance_variable_set(:"@ran_class_action", true) end
+ end
+
+ class OnlyConditionClassController < ConditionalFilterController
+ before_action ConditionalClassFilter, :only => :show
+ end
+
+ class ExceptConditionClassController < ConditionalFilterController
+ before_action ConditionalClassFilter, :except => :show_without_action
+ end
+
+ class AnomolousYetValidConditionController < ConditionalFilterController
+ before_action(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_action1", true)}, :except => :show_without_action) { |c| c.instance_variable_set(:"@ran_proc_action2", true)}
+ end
+
+ class OnlyConditionalOptionsFilter < ConditionalFilterController
+ before_action :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) }
+ end
+
+ class ConditionalOptionsFilter < ConditionalFilterController
+ before_action :ensure_login, :if => Proc.new { |c| true }
+ before_action :clean_up_tmp, :if => Proc.new { |c| false }
+ end
+
+ class ConditionalOptionsSkipFilter < ConditionalFilterController
+ before_action :ensure_login
+ before_action :clean_up_tmp
+
+ skip_before_action :ensure_login, if: -> { false }
+ skip_before_action :clean_up_tmp, if: -> { true }
+ end
+
+ class ClassController < ConditionalFilterController
+ before_action ConditionalClassFilter
+ end
+
+ class PrependingController < TestController
+ prepend_before_action :wonderful_life
+ # skip_before_action :fire_flash
+
+ private
+ def wonderful_life
+ @ran_filter ||= []
+ @ran_filter << "wonderful_life"
+ end
+ end
+
+ class SkippingAndLimitedController < TestController
+ skip_before_action :ensure_login
+ before_action :ensure_login, :only => :index
+
+ def index
+ render :text => 'ok'
+ end
+
+ def public
+ render :text => 'ok'
+ end
+ end
+
+ class SkippingAndReorderingController < TestController
+ skip_before_action :ensure_login
+ before_action :find_record
+ before_action :ensure_login
+
+ def index
+ render :text => 'ok'
+ end
+
+ private
+ def find_record
+ @ran_filter ||= []
+ @ran_filter << "find_record"
+ end
+ end
+
+ class ConditionalSkippingController < TestController
+ skip_before_action :ensure_login, :only => [ :login ]
+ skip_after_action :clean_up, :only => [ :login ]
+
+ before_action :find_user, :only => [ :change_password ]
+
+ def login
+ render :inline => "ran action"
+ end
+
+ def change_password
+ render :inline => "ran action"
+ end
+
+ protected
+ def find_user
+ @ran_filter ||= []
+ @ran_filter << "find_user"
+ end
+ end
+
+ class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
+ before_action :conditional_in_parent_before, :only => [:show, :another_action]
+ after_action :conditional_in_parent_after, :only => [:show, :another_action]
+
+ private
+
+ def conditional_in_parent_before
+ @ran_filter ||= []
+ @ran_filter << 'conditional_in_parent_before'
+ end
+
+ def conditional_in_parent_after
+ @ran_filter ||= []
+ @ran_filter << 'conditional_in_parent_after'
+ end
+ end
+
+ class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
+ skip_before_action :conditional_in_parent_before, :only => :another_action
+ skip_after_action :conditional_in_parent_after, :only => :another_action
+ end
+
+ class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
+ skip_before_action :conditional_in_parent_before, :only => :show
+ end
+
+ class ProcController < PrependingController
+ before_action(proc { |c| c.instance_variable_set(:"@ran_proc_action", true) })
+ end
+
+ class ImplicitProcController < PrependingController
+ before_action { |c| c.instance_variable_set(:"@ran_proc_action", true) }
+ end
+
+ class AuditFilter
+ def self.before(controller)
+ controller.instance_variable_set(:"@was_audited", true)
+ end
+ end
+
+ class AroundFilter
+ def before(controller)
+ @execution_log = "before"
+ controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
+ controller.instance_variable_set(:"@before_ran", true)
+ end
+
+ def after(controller)
+ controller.instance_variable_set(:"@execution_log", @execution_log + " and after")
+ controller.instance_variable_set(:"@after_ran", true)
+ controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
+ end
+
+ def around(controller)
+ before(controller)
+ yield
+ after(controller)
+ end
+ end
+
+ class AppendedAroundFilter
+ def before(controller)
+ controller.class.execution_log << " before appended aroundfilter "
+ end
+
+ def after(controller)
+ controller.class.execution_log << " after appended aroundfilter "
+ end
+
+ def around(controller)
+ before(controller)
+ yield
+ after(controller)
+ end
+ end
+
+ class AuditController < ActionController::Base
+ before_action(AuditFilter)
+
+ def show
+ render :text => "hello"
+ end
+ end
+
+ class AroundFilterController < PrependingController
+ around_action AroundFilter.new
+ end
+
+ class BeforeAfterClassFilterController < PrependingController
+ begin
+ filter = AroundFilter.new
+ before_action filter
+ after_action filter
+ end
+ end
+
+ class MixedFilterController < PrependingController
+ cattr_accessor :execution_log
+
+ def initialize
+ @@execution_log = ""
+ super()
+ end
+
+ before_action { |c| c.class.execution_log << " before procfilter " }
+ prepend_around_action AroundFilter.new
+
+ after_action { |c| c.class.execution_log << " after procfilter " }
+ append_around_action AppendedAroundFilter.new
+ end
+
+ class MixedSpecializationController < ActionController::Base
+ class OutOfOrder < StandardError; end
+
+ before_action :first
+ before_action :second, :only => :foo
+
+ def foo
+ render :text => 'foo'
+ end
+
+ def bar
+ render :text => 'bar'
+ end
+
+ protected
+ def first
+ @first = true
+ end
+
+ def second
+ raise OutOfOrder unless @first
+ end
+ end
+
+ class DynamicDispatchController < ActionController::Base
+ before_action :choose
+
+ %w(foo bar baz).each do |action|
+ define_method(action) { render :text => action }
+ end
+
+ private
+ def choose
+ self.action_name = params[:choose]
+ end
+ end
+
+ class PrependingBeforeAndAfterController < ActionController::Base
+ prepend_before_action :before_all
+ prepend_after_action :after_all
+ before_action :between_before_all_and_after_all
+
+ def before_all
+ @ran_filter ||= []
+ @ran_filter << 'before_all'
+ end
+
+ def after_all
+ @ran_filter ||= []
+ @ran_filter << 'after_all'
+ end
+
+ def between_before_all_and_after_all
+ @ran_filter ||= []
+ @ran_filter << 'between_before_all_and_after_all'
+ end
+ def show
+ render :text => 'hello'
+ end
+ end
+
+ class ErrorToRescue < Exception; end
+
+ class RescuingAroundFilterWithBlock
+ def around(controller)
+ yield
+ rescue ErrorToRescue => ex
+ controller.__send__ :render, :text => "I rescued this: #{ex.inspect}"
+ end
+ end
+
+ class RescuedController < ActionController::Base
+ around_action RescuingAroundFilterWithBlock.new
+
+ def show
+ raise ErrorToRescue.new("Something made the bad noise.")
+ end
+ end
+
+ class NonYieldingAroundFilterController < ActionController::Base
+
+ before_action :filter_one
+ around_action :non_yielding_action
+ before_action :action_two
+ after_action :action_three
+
+ def index
+ render :inline => "index"
+ end
+
+ private
+
+ def filter_one
+ @filters ||= []
+ @filters << "filter_one"
+ end
+
+ def action_two
+ @filters << "action_two"
+ end
+
+ def non_yielding_action
+ @filters << "it didn't yield"
+ @filter_return_value
+ end
+
+ def action_three
+ @filters << "action_three"
+ end
+
+ end
+
+ class ImplicitActionsController < ActionController::Base
+ before_action :find_only, :only => :edit
+ before_action :find_except, :except => :edit
+
+ private
+
+ def find_only
+ @only = 'Only'
+ end
+
+ def find_except
+ @except = 'Except'
+ end
+ end
+
+ def test_non_yielding_around_actions_not_returning_false_do_not_raise
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", true
+ assert_nothing_raised do
+ test_process(controller, "index")
+ end
+ end
+
+ def test_non_yielding_around_actions_returning_false_do_not_raise
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", false
+ assert_nothing_raised do
+ test_process(controller, "index")
+ end
+ end
+
+ def test_after_actions_are_not_run_if_around_action_returns_false
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", false
+ test_process(controller, "index")
+ assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
+ end
+
+ def test_after_actions_are_not_run_if_around_action_does_not_yield
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", true
+ test_process(controller, "index")
+ assert_equal ["filter_one", "it didn't yield"], controller.assigns['filters']
+ end
+
+ def test_added_action_to_inheritance_graph
+ assert_equal [ :ensure_login ], TestController.before_actions
+ end
+
+ def test_base_class_in_isolation
+ assert_equal [ ], ActionController::Base.before_actions
+ end
+
+ def test_prepending_action
+ assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_actions
+ end
+
+ def test_running_actions
+ test_process(PrependingController)
+ assert_equal %w( wonderful_life ensure_login ), assigns["ran_filter"]
+ end
+
+ def test_running_actions_with_proc
+ test_process(ProcController)
+ assert assigns["ran_proc_action"]
+ end
+
+ def test_running_actions_with_implicit_proc
+ test_process(ImplicitProcController)
+ assert assigns["ran_proc_action"]
+ end
+
+ def test_running_actions_with_class
+ test_process(AuditController)
+ assert assigns["was_audited"]
+ end
+
+ def test_running_anomolous_yet_valid_condition_actions
+ test_process(AnomolousYetValidConditionController)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ assert assigns["ran_class_action"]
+ assert assigns["ran_proc_action1"]
+ assert assigns["ran_proc_action2"]
+
+ test_process(AnomolousYetValidConditionController, "show_without_action")
+ assert_nil assigns["ran_filter"]
+ assert !assigns["ran_class_action"]
+ assert !assigns["ran_proc_action1"]
+ assert !assigns["ran_proc_action2"]
+ end
+
+ def test_running_conditional_options
+ test_process(ConditionalOptionsFilter)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ end
+
+ def test_running_conditional_skip_options
+ test_process(ConditionalOptionsSkipFilter)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ end
+
+ def test_skipping_class_actions
+ test_process(ClassController)
+ assert_equal true, assigns["ran_class_action"]
+
+ skipping_class_controller = Class.new(ClassController) do
+ skip_before_action ConditionalClassFilter
+ end
+
+ test_process(skipping_class_controller)
+ assert_nil assigns['ran_class_action']
+ end
+
+ def test_running_collection_condition_actions
+ test_process(ConditionalCollectionFilterController)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ test_process(ConditionalCollectionFilterController, "show_without_action")
+ assert_nil assigns["ran_filter"]
+ test_process(ConditionalCollectionFilterController, "another_action")
+ assert_nil assigns["ran_filter"]
+ end
+
+ def test_running_only_condition_actions
+ test_process(OnlyConditionSymController)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ test_process(OnlyConditionSymController, "show_without_action")
+ assert_nil assigns["ran_filter"]
+
+ test_process(OnlyConditionProcController)
+ assert assigns["ran_proc_action"]
+ test_process(OnlyConditionProcController, "show_without_action")
+ assert !assigns["ran_proc_action"]
+
+ test_process(OnlyConditionClassController)
+ assert assigns["ran_class_action"]
+ test_process(OnlyConditionClassController, "show_without_action")
+ assert !assigns["ran_class_action"]
+ end
+
+ def test_running_except_condition_actions
+ test_process(ExceptConditionSymController)
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ test_process(ExceptConditionSymController, "show_without_action")
+ assert_nil assigns["ran_filter"]
+
+ test_process(ExceptConditionProcController)
+ assert assigns["ran_proc_action"]
+ test_process(ExceptConditionProcController, "show_without_action")
+ assert !assigns["ran_proc_action"]
+
+ test_process(ExceptConditionClassController)
+ assert assigns["ran_class_action"]
+ test_process(ExceptConditionClassController, "show_without_action")
+ assert !assigns["ran_class_action"]
+ end
+
+ def test_running_only_condition_and_conditional_options
+ test_process(OnlyConditionalOptionsFilter, "show")
+ assert_not assigns["ran_conditional_index_proc"]
+ end
+
+ def test_running_before_and_after_condition_actions
+ test_process(BeforeAndAfterConditionController)
+ assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"]
+ test_process(BeforeAndAfterConditionController, "show_without_action")
+ assert_nil assigns["ran_filter"]
+ end
+
+ def test_around_action
+ test_process(AroundFilterController)
+ assert assigns["before_ran"]
+ assert assigns["after_ran"]
+ end
+
+ def test_before_after_class_action
+ test_process(BeforeAfterClassFilterController)
+ assert assigns["before_ran"]
+ assert assigns["after_ran"]
+ end
+
+ def test_having_properties_in_around_action
+ test_process(AroundFilterController)
+ assert_equal "before and after", assigns["execution_log"]
+ end
+
+ def test_prepending_and_appending_around_action
+ test_process(MixedFilterController)
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ " after appended aroundfilter after procfilter after aroundfilter ",
+ MixedFilterController.execution_log
+ end
+
+ def test_rendering_breaks_actioning_chain
+ response = test_process(RenderingController)
+ assert_equal "something else", response.body
+ assert !assigns["ran_action"]
+ end
+
+ def test_before_action_rendering_breaks_actioning_chain_for_after_action
+ test_process(RenderingController)
+ assert_equal %w( before_action_rendering ), assigns["ran_filter"]
+ assert !assigns["ran_action"]
+ end
+
+ def test_before_action_redirects_breaks_actioning_chain_for_after_action
+ test_process(BeforeActionRedirectionController)
+ assert_response :redirect
+ assert_equal "http://test.host/filter_test/before_action_redirection/target_of_redirection", redirect_to_url
+ assert_equal %w( before_action_redirects ), assigns["ran_filter"]
+ end
+
+ def test_before_action_rendering_breaks_actioning_chain_for_preprend_after_action
+ test_process(RenderingForPrependAfterActionController)
+ assert_equal %w( before_action_rendering ), assigns["ran_filter"]
+ assert !assigns["ran_action"]
+ end
+
+ def test_before_action_redirects_breaks_actioning_chain_for_preprend_after_action
+ test_process(BeforeActionRedirectionForPrependAfterActionController)
+ assert_response :redirect
+ assert_equal "http://test.host/filter_test/before_action_redirection_for_prepend_after_action/target_of_redirection", redirect_to_url
+ assert_equal %w( before_action_redirects ), assigns["ran_filter"]
+ end
+
+ def test_actions_with_mixed_specialization_run_in_order
+ assert_nothing_raised do
+ response = test_process(MixedSpecializationController, 'bar')
+ assert_equal 'bar', response.body
+ end
+
+ assert_nothing_raised do
+ response = test_process(MixedSpecializationController, 'foo')
+ assert_equal 'foo', response.body
+ end
+ end
+
+ def test_dynamic_dispatch
+ %w(foo bar baz).each do |action|
+ request = ActionController::TestRequest.new
+ request.query_parameters[:choose] = action
+ response = DynamicDispatchController.action(action).call(request.env).last
+ assert_equal action, response.body
+ end
+ end
+
+ def test_running_prepended_before_and_after_action
+ test_process(PrependingBeforeAndAfterController)
+ assert_equal %w( before_all between_before_all_and_after_all after_all ), assigns["ran_filter"]
+ end
+
+ def test_skipping_and_limiting_controller
+ test_process(SkippingAndLimitedController, "index")
+ assert_equal %w( ensure_login ), assigns["ran_filter"]
+ test_process(SkippingAndLimitedController, "public")
+ assert_nil assigns["ran_filter"]
+ end
+
+ def test_skipping_and_reordering_controller
+ test_process(SkippingAndReorderingController, "index")
+ assert_equal %w( find_record ensure_login ), assigns["ran_filter"]
+ end
+
+ def test_conditional_skipping_of_actions
+ test_process(ConditionalSkippingController, "login")
+ assert_nil assigns["ran_filter"]
+ test_process(ConditionalSkippingController, "change_password")
+ assert_equal %w( ensure_login find_user ), assigns["ran_filter"]
+
+ test_process(ConditionalSkippingController, "login")
+ assert !@controller.instance_variable_defined?("@ran_after_action")
+ test_process(ConditionalSkippingController, "change_password")
+ assert_equal %w( clean_up ), @controller.instance_variable_get("@ran_after_action")
+ end
+
+ def test_conditional_skipping_of_actions_when_parent_action_is_also_conditional
+ test_process(ChildOfConditionalParentController)
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
+ test_process(ChildOfConditionalParentController, 'another_action')
+ assert_nil assigns['ran_filter']
+ end
+
+ def test_condition_skipping_of_actions_when_siblings_also_have_conditions
+ test_process(ChildOfConditionalParentController)
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
+ test_process(AnotherChildOfConditionalParentController)
+ assert_equal %w( conditional_in_parent_after ), assigns['ran_filter']
+ test_process(ChildOfConditionalParentController)
+ assert_equal %w( conditional_in_parent_before conditional_in_parent_after ), assigns['ran_filter']
+ end
+
+ def test_changing_the_requirements
+ test_process(ChangingTheRequirementsController, "go_wild")
+ assert_nil assigns['ran_filter']
+ end
+
+ def test_a_rescuing_around_action
+ response = nil
+ assert_nothing_raised do
+ response = test_process(RescuedController)
+ end
+
+ assert response.success?
+ assert_equal("I rescued this: #<FilterTest::ErrorToRescue: Something made the bad noise.>", response.body)
+ end
+
+ def test_actions_obey_only_and_except_for_implicit_actions
+ test_process(ImplicitActionsController, 'show')
+ assert_equal 'Except', assigns(:except)
+ assert_nil assigns(:only)
+ assert_equal 'show', response.body
+
+ test_process(ImplicitActionsController, 'edit')
+ assert_equal 'Only', assigns(:only)
+ assert_nil assigns(:except)
+ assert_equal 'edit', response.body
+ end
+
+ private
+ def test_process(controller, action = "show")
+ @controller = controller.is_a?(Class) ? controller.new : controller
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ process(action)
+ end
+end
+
+class PostsController < ActionController::Base
+ module AroundExceptions
+ class Error < StandardError ; end
+ class Before < Error ; end
+ class After < Error ; end
+ end
+ include AroundExceptions
+
+ class DefaultFilter
+ include AroundExceptions
+ end
+
+ module_eval %w(raises_before raises_after raises_both no_raise no_action).map { |action| "def #{action}; default_action end" }.join("\n")
+
+ private
+ def default_action
+ render :inline => "#{action_name} called"
+ end
+end
+
+class ControllerWithSymbolAsFilter < PostsController
+ around_action :raise_before, :only => :raises_before
+ around_action :raise_after, :only => :raises_after
+ around_action :without_exception, :only => :no_raise
+
+ private
+ def raise_before
+ raise Before
+ yield
+ end
+
+ def raise_after
+ yield
+ raise After
+ end
+
+ def without_exception
+ # Do stuff...
+ wtf = 1 + 1
+
+ yield
+
+ # Do stuff...
+ wtf += 1
+ end
+end
+
+class ControllerWithFilterClass < PostsController
+ class YieldingFilter < DefaultFilter
+ def self.around(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_action YieldingFilter, :only => :raises_after
+end
+
+class ControllerWithFilterInstance < PostsController
+ class YieldingFilter < DefaultFilter
+ def around(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_action YieldingFilter.new, :only => :raises_after
+end
+
+class ControllerWithProcFilter < PostsController
+ around_action(:only => :no_raise) do |c,b|
+ c.instance_variable_set(:"@before", true)
+ b.call
+ c.instance_variable_set(:"@after", true)
+ end
+end
+
+class ControllerWithNestedFilters < ControllerWithSymbolAsFilter
+ around_action :raise_before, :raise_after, :without_exception, :only => :raises_both
+end
+
+class ControllerWithAllTypesOfFilters < PostsController
+ before_action :before
+ around_action :around
+ after_action :after
+ around_action :around_again
+
+ private
+ def before
+ @ran_filter ||= []
+ @ran_filter << 'before'
+ end
+
+ def around
+ @ran_filter << 'around (before yield)'
+ yield
+ @ran_filter << 'around (after yield)'
+ end
+
+ def after
+ @ran_filter << 'after'
+ end
+
+ def around_again
+ @ran_filter << 'around_again (before yield)'
+ yield
+ @ran_filter << 'around_again (after yield)'
+ end
+end
+
+class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
+ skip_action_callback :around_again
+ skip_action_callback :after
+end
+
+class YieldingAroundFiltersTest < ActionController::TestCase
+ include PostsController::AroundExceptions
+
+ def test_base
+ controller = PostsController
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_nothing_raised { test_process(controller,'raises_before') }
+ assert_nothing_raised { test_process(controller,'raises_after') }
+ assert_nothing_raised { test_process(controller,'no_action') }
+ end
+
+ def test_with_symbol
+ controller = ControllerWithSymbolAsFilter
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(Before) { test_process(controller,'raises_before') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ end
+
+ def test_with_class
+ controller = ControllerWithFilterClass
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_instance
+ controller = ControllerWithFilterInstance
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_proc
+ test_process(ControllerWithProcFilter,'no_raise')
+ assert assigns['before']
+ assert assigns['after']
+ end
+
+ def test_nested_actions
+ controller = ControllerWithNestedFilters
+ assert_nothing_raised do
+ begin
+ test_process(controller,'raises_both')
+ rescue Before, After
+ end
+ end
+ assert_raise Before do
+ begin
+ test_process(controller,'raises_both')
+ rescue After
+ end
+ end
+ end
+
+ def test_action_order_with_all_action_types
+ test_process(ControllerWithAllTypesOfFilters,'no_raise')
+ assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) after around (after yield)', assigns['ran_filter'].join(' ')
+ end
+
+ def test_action_order_with_skip_action_method
+ test_process(ControllerWithTwoLessFilters,'no_raise')
+ assert_equal 'before around (before yield) around (after yield)', assigns['ran_filter'].join(' ')
+ end
+
+ def test_first_action_in_multiple_before_action_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_1')
+ assert_equal ' ', response.body
+ assert_equal 1, controller.instance_variable_get(:@try)
+ end
+
+ def test_second_action_in_multiple_before_action_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_2')
+ assert_equal ' ', response.body
+ assert_equal 2, controller.instance_variable_get(:@try)
+ end
+
+ def test_last_action_in_multiple_before_action_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_3')
+ assert_equal ' ', response.body
+ assert_equal 3, controller.instance_variable_get(:@try)
+ end
+
+ protected
+ def test_process(controller, action = "show")
+ @controller = controller.is_a?(Class) ? controller.new : controller
+ process(action)
+ end
+end
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
new file mode 100644
index 0000000000..50b36a0567
--- /dev/null
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -0,0 +1,202 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ class FlashHashTest < ActiveSupport::TestCase
+ def setup
+ @hash = Flash::FlashHash.new
+ end
+
+ def test_set_get
+ @hash[:foo] = 'zomg'
+ assert_equal 'zomg', @hash[:foo]
+ end
+
+ def test_keys
+ assert_equal [], @hash.keys
+
+ @hash['foo'] = 'zomg'
+ assert_equal ['foo'], @hash.keys
+
+ @hash['bar'] = 'zomg'
+ assert_equal ['foo', 'bar'].sort, @hash.keys.sort
+ end
+
+ def test_update
+ @hash['foo'] = 'bar'
+ @hash.update('foo' => 'baz', 'hello' => 'world')
+
+ assert_equal 'baz', @hash['foo']
+ assert_equal 'world', @hash['hello']
+ end
+
+ def test_delete
+ @hash['foo'] = 'bar'
+ @hash.delete 'foo'
+
+ assert !@hash.key?('foo')
+ assert_nil @hash['foo']
+ end
+
+ def test_to_hash
+ @hash['foo'] = 'bar'
+ assert_equal({'foo' => 'bar'}, @hash.to_hash)
+
+ @hash.to_hash['zomg'] = 'aaron'
+ assert !@hash.key?('zomg')
+ assert_equal({'foo' => 'bar'}, @hash.to_hash)
+ end
+
+ def test_to_session_value
+ @hash['foo'] = 'bar'
+ assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value)
+
+ @hash.discard('foo')
+ assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value)
+
+ @hash.now['qux'] = 1
+ assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value)
+
+ @hash.sweep
+ assert_equal(nil, @hash.to_session_value)
+ end
+
+ def test_from_session_value
+ rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoNQGZsYXNoZXN7BkkiDG1lc3NhZ2UGOwBGSSIKSGVsbG8GOwBGOglAbm93MA=='
+ session = Marshal.load(Base64.decode64(rails_3_2_cookie))
+ hash = Flash::FlashHash.from_session_value(session['flash'])
+ assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
+ end
+
+ def test_from_session_value_on_json_serializer
+ decrypted_data = "{ \"session_id\":\"d98bdf6d129618fc2548c354c161cfb5\", \"flash\":{\"discard\":[], \"flashes\":{\"message\":\"hey you\"}} }"
+ session = ActionDispatch::Cookies::JsonSerializer.load(decrypted_data)
+ hash = Flash::FlashHash.from_session_value(session['flash'])
+
+ assert_equal({'discard' => %w[message], 'flashes' => { 'message' => 'hey you'}}, hash.to_session_value)
+ assert_equal "hey you", hash[:message]
+ assert_equal "hey you", hash["message"]
+ end
+
+ def test_empty?
+ assert @hash.empty?
+ @hash['zomg'] = 'bears'
+ assert !@hash.empty?
+ @hash.clear
+ assert @hash.empty?
+ end
+
+ def test_each
+ @hash['hello'] = 'world'
+ @hash['foo'] = 'bar'
+
+ things = []
+ @hash.each do |k,v|
+ things << [k,v]
+ end
+
+ assert_equal([%w{ hello world }, %w{ foo bar }].sort, things.sort)
+ end
+
+ def test_replace
+ @hash['hello'] = 'world'
+ @hash.replace('omg' => 'aaron')
+ assert_equal({'omg' => 'aaron'}, @hash.to_hash)
+ end
+
+ def test_discard_no_args
+ @hash['hello'] = 'world'
+ @hash.discard
+
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
+
+ def test_discard_one_arg
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+
+ @hash.sweep
+ assert_equal({'omg' => 'world'}, @hash.to_hash)
+ end
+
+ def test_keep_sweep
+ @hash['hello'] = 'world'
+
+ @hash.sweep
+ assert_equal({'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_update_sweep
+ @hash['hello'] = 'world'
+ @hash.update({'hi' => 'mom'})
+
+ @hash.sweep
+ assert_equal({'hello' => 'world', 'hi' => 'mom'}, @hash.to_hash)
+ end
+
+ def test_update_delete_sweep
+ @hash['hello'] = 'world'
+ @hash.delete 'hello'
+ @hash.update({'hello' => 'mom'})
+
+ @hash.sweep
+ assert_equal({'hello' => 'mom'}, @hash.to_hash)
+ end
+
+ def test_delete_sweep
+ @hash['hello'] = 'world'
+ @hash['hi'] = 'mom'
+ @hash.delete 'hi'
+
+ @hash.sweep
+ assert_equal({'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_clear_sweep
+ @hash['hello'] = 'world'
+ @hash.clear
+
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
+
+ def test_replace_sweep
+ @hash['hello'] = 'world'
+ @hash.replace({'hi' => 'mom'})
+
+ @hash.sweep
+ assert_equal({'hi' => 'mom'}, @hash.to_hash)
+ end
+
+ def test_discard_then_add
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+ @hash['hello'] = 'world'
+
+ @hash.sweep
+ assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_keep_all_sweep
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+ @hash.keep
+
+ @hash.sweep
+ assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_double_sweep
+ @hash['hello'] = 'world'
+ @hash.sweep
+
+ assert_equal({'hello' => 'world'}, @hash.to_hash)
+
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
+ end
+end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
new file mode 100644
index 0000000000..3720a920d0
--- /dev/null
+++ b/actionpack/test/controller/flash_test.rb
@@ -0,0 +1,335 @@
+require 'abstract_unit'
+require 'active_support/key_generator'
+
+class FlashTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def set_flash
+ flash["that"] = "hello"
+ render :inline => "hello"
+ end
+
+ def set_flash_now
+ flash.now["that"] = "hello"
+ flash.now["foo"] ||= "bar"
+ flash.now["foo"] ||= "err"
+ @flashy = flash.now["that"]
+ @flash_copy = {}.update flash
+ render :inline => "hello"
+ end
+
+ def attempt_to_use_flash_now
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+
+ def use_flash
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+
+ def use_flash_and_keep_it
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ flash.keep
+ render :inline => "hello"
+ end
+
+ def use_flash_and_update_it
+ flash.update("this" => "hello again")
+ @flash_copy = {}.update flash
+ render :inline => "hello"
+ end
+
+ def use_flash_after_reset_session
+ flash["that"] = "hello"
+ @flashy_that = flash["that"]
+ reset_session
+ @flashy_that_reset = flash["that"]
+ flash["this"] = "good-bye"
+ @flashy_this = flash["this"]
+ render :inline => "hello"
+ end
+
+ # methods for test_sweep_after_halted_action_chain
+ before_action :halt_and_redir, only: 'filter_halting_action'
+
+ def std_action
+ @flash_copy = {}.update(flash)
+ render :nothing => true
+ end
+
+ def filter_halting_action
+ @flash_copy = {}.update(flash)
+ end
+
+ def halt_and_redir
+ flash["foo"] = "bar"
+ redirect_to :action => "std_action"
+ @flash_copy = {}.update(flash)
+ end
+
+ def redirect_with_alert
+ redirect_to '/nowhere', :alert => "Beware the nowheres!"
+ end
+
+ def redirect_with_notice
+ redirect_to '/somewhere', :notice => "Good luck in the somewheres!"
+ end
+
+ def render_with_flash_now_alert
+ flash.now.alert = "Beware the nowheres now!"
+ render :inline => "hello"
+ end
+
+ def render_with_flash_now_notice
+ flash.now.notice = "Good luck in the somewheres now!"
+ render :inline => "hello"
+ end
+
+ def redirect_with_other_flashes
+ redirect_to '/wonderland', :flash => { :joyride => "Horses!" }
+ end
+
+ def redirect_with_foo_flash
+ redirect_to "/wonderland", :foo => 'for great justice'
+ end
+ end
+
+ tests TestController
+
+ def test_flash
+ get :set_flash
+
+ get :use_flash
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "hello", assigns["flashy"]
+
+ get :use_flash
+ assert_nil assigns["flash_copy"]["that"], "On second flash"
+ end
+
+ def test_keep_flash
+ get :set_flash
+
+ get :use_flash_and_keep_it
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "hello", assigns["flashy"]
+
+ get :use_flash
+ assert_equal "hello", assigns["flash_copy"]["that"], "On second flash"
+
+ get :use_flash
+ assert_nil assigns["flash_copy"]["that"], "On third flash"
+ end
+
+ def test_flash_now
+ get :set_flash_now
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "bar" , assigns["flash_copy"]["foo"]
+ assert_equal "hello", assigns["flashy"]
+
+ get :attempt_to_use_flash_now
+ assert_nil assigns["flash_copy"]["that"]
+ assert_nil assigns["flash_copy"]["foo"]
+ assert_nil assigns["flashy"]
+ end
+
+ def test_update_flash
+ get :set_flash
+ get :use_flash_and_update_it
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "hello again", assigns["flash_copy"]["this"]
+ get :use_flash
+ assert_nil assigns["flash_copy"]["that"], "On second flash"
+ assert_equal "hello again", assigns["flash_copy"]["this"], "On second flash"
+ end
+
+ def test_flash_after_reset_session
+ get :use_flash_after_reset_session
+ assert_equal "hello", assigns["flashy_that"]
+ assert_equal "good-bye", assigns["flashy_this"]
+ assert_nil assigns["flashy_that_reset"]
+ end
+
+ def test_does_not_set_the_session_if_the_flash_is_empty
+ get :std_action
+ assert_nil session["flash"]
+ end
+
+ def test_sweep_after_halted_action_chain
+ get :std_action
+ assert_nil assigns["flash_copy"]["foo"]
+ get :filter_halting_action
+ assert_equal "bar", assigns["flash_copy"]["foo"]
+ get :std_action # follow redirection
+ assert_equal "bar", assigns["flash_copy"]["foo"]
+ get :std_action
+ assert_nil assigns["flash_copy"]["foo"]
+ end
+
+ def test_keep_and_discard_return_values
+ flash = ActionDispatch::Flash::FlashHash.new
+ flash.update(:foo => :foo_indeed, :bar => :bar_indeed)
+
+ assert_equal(:foo_indeed, flash.discard(:foo)) # valid key passed
+ assert_nil flash.discard(:unknown) # non existent key passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.discard(nil).to_hash) # nothing passed
+
+ assert_equal(:foo_indeed, flash.keep(:foo)) # valid key passed
+ assert_nil flash.keep(:unknown) # non existent key passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep().to_hash) # nothing passed
+ assert_equal({"foo" => :foo_indeed, "bar" => :bar_indeed}, flash.keep(nil).to_hash) # nothing passed
+ end
+
+ def test_redirect_to_with_alert
+ get :redirect_with_alert
+ assert_equal "Beware the nowheres!", @controller.send(:flash)[:alert]
+ end
+
+ def test_redirect_to_with_notice
+ get :redirect_with_notice
+ assert_equal "Good luck in the somewheres!", @controller.send(:flash)[:notice]
+ end
+
+ def test_render_with_flash_now_alert
+ get :render_with_flash_now_alert
+ assert_equal "Beware the nowheres now!", @controller.send(:flash)[:alert]
+ end
+
+ def test_render_with_flash_now_notice
+ get :render_with_flash_now_notice
+ assert_equal "Good luck in the somewheres now!", @controller.send(:flash)[:notice]
+ end
+
+ def test_redirect_to_with_other_flashes
+ get :redirect_with_other_flashes
+ assert_equal "Horses!", @controller.send(:flash)[:joyride]
+ end
+
+ def test_redirect_to_with_adding_flash_types
+ original_controller = @controller
+ test_controller_with_flash_type_foo = Class.new(TestController) do
+ add_flash_types :foo
+ end
+ @controller = test_controller_with_flash_type_foo.new
+ get :redirect_with_foo_flash
+ assert_equal "for great justice", @controller.send(:flash)[:foo]
+ ensure
+ @controller = original_controller
+ end
+
+ def test_add_flash_type_to_subclasses
+ test_controller_with_flash_type_foo = Class.new(TestController) do
+ add_flash_types :foo
+ end
+ subclass_controller_with_no_flash_type = Class.new(test_controller_with_flash_type_foo)
+ assert subclass_controller_with_no_flash_type._flash_types.include?(:foo)
+ end
+
+ def test_does_not_add_flash_type_to_parent_class
+ Class.new(TestController) do
+ add_flash_types :bar
+ end
+ assert_not TestController._flash_types.include?(:bar)
+ end
+end
+
+class FlashIntegrationTest < ActionDispatch::IntegrationTest
+ SessionKey = '_myapp_session'
+ Generator = ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33')
+
+ class TestController < ActionController::Base
+ add_flash_types :bar
+
+ def set_flash
+ flash["that"] = "hello"
+ head :ok
+ end
+
+ def set_flash_now
+ flash.now["that"] = "hello"
+ head :ok
+ end
+
+ def use_flash
+ render :inline => "flash: #{flash["that"]}"
+ end
+
+ def set_bar
+ flash[:bar] = "for great justice"
+ head :ok
+ end
+ end
+
+ def test_flash
+ with_test_route_set do
+ get '/set_flash'
+ assert_response :success
+ assert_equal "hello", @request.flash["that"]
+
+ get '/use_flash'
+ assert_response :success
+ assert_equal "flash: hello", @response.body
+ end
+ end
+
+ def test_just_using_flash_does_not_stream_a_cookie_back
+ with_test_route_set do
+ get '/use_flash'
+ assert_response :success
+ assert_nil @response.headers["Set-Cookie"]
+ assert_equal "flash: ", @response.body
+ end
+ end
+
+ def test_setting_flash_does_not_raise_in_following_requests
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash', nil, env
+ get '/set_flash', nil, env
+ end
+ end
+
+ def test_setting_flash_now_does_not_raise_in_following_requests
+ with_test_route_set do
+ env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
+ get '/set_flash_now', nil, env
+ get '/set_flash_now', nil, env
+ end
+ end
+
+ def test_added_flash_types_method
+ with_test_route_set do
+ get '/set_bar'
+ assert_response :success
+ assert_equal 'for great justice', @controller.bar
+ end
+ end
+
+ private
+
+ # Overwrite get to send SessionSecret in env hash
+ def get(path, parameters = nil, env = {})
+ env["action_dispatch.key_generator"] ||= Generator
+ super
+ end
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => FlashIntegrationTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey
+ middleware.use ActionDispatch::Flash
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
new file mode 100644
index 0000000000..00d4612ac9
--- /dev/null
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -0,0 +1,324 @@
+require 'abstract_unit'
+
+class ForceSSLController < ActionController::Base
+ def banana
+ render :text => "monkey"
+ end
+
+ def cheeseburger
+ render :text => "sikachu"
+ end
+end
+
+class ForceSSLControllerLevel < ForceSSLController
+ force_ssl
+end
+
+class ForceSSLCustomOptions < ForceSSLController
+ force_ssl :host => "secure.example.com", :only => :redirect_host
+ force_ssl :port => 8443, :only => :redirect_port
+ force_ssl :subdomain => 'secure', :only => :redirect_subdomain
+ force_ssl :domain => 'secure.com', :only => :redirect_domain
+ force_ssl :path => '/foo', :only => :redirect_path
+ force_ssl :status => :found, :only => :redirect_status
+ force_ssl :flash => { :message => 'Foo, Bar!' }, :only => :redirect_flash
+ force_ssl :alert => 'Foo, Bar!', :only => :redirect_alert
+ force_ssl :notice => 'Foo, Bar!', :only => :redirect_notice
+
+ def force_ssl_action
+ render :text => action_name
+ end
+
+ alias_method :redirect_host, :force_ssl_action
+ alias_method :redirect_port, :force_ssl_action
+ alias_method :redirect_subdomain, :force_ssl_action
+ alias_method :redirect_domain, :force_ssl_action
+ alias_method :redirect_path, :force_ssl_action
+ alias_method :redirect_status, :force_ssl_action
+ alias_method :redirect_flash, :force_ssl_action
+ alias_method :redirect_alert, :force_ssl_action
+ alias_method :redirect_notice, :force_ssl_action
+
+ def use_flash
+ render :text => flash[:message]
+ end
+
+ def use_alert
+ render :text => flash[:alert]
+ end
+
+ def use_notice
+ render :text => flash[:notice]
+ end
+end
+
+class ForceSSLOnlyAction < ForceSSLController
+ force_ssl :only => :cheeseburger
+end
+
+class ForceSSLExceptAction < ForceSSLController
+ force_ssl :except => :banana
+end
+
+class ForceSSLIfCondition < ForceSSLController
+ force_ssl :if => :use_force_ssl?
+
+ def use_force_ssl?
+ action_name == 'cheeseburger'
+ end
+end
+
+class ForceSSLFlash < ForceSSLController
+ force_ssl :except => [:banana, :set_flash, :use_flash]
+
+ def set_flash
+ flash["that"] = "hello"
+ redirect_to '/force_ssl_flash/cheeseburger'
+ end
+
+ def use_flash
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+end
+
+class RedirectToSSL < ForceSSLController
+ def banana
+ force_ssl_redirect || render(:text => 'monkey')
+ end
+ def cheeseburger
+ force_ssl_redirect('secure.cheeseburger.host') || render(:text => 'ihaz')
+ end
+end
+
+class ForceSSLControllerLevelTest < ActionController::TestCase
+ def test_banana_redirects_to_https
+ get :banana
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url
+ end
+
+ def test_banana_redirects_to_https_with_extra_params
+ get :banana, :token => "secret"
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLCustomOptionsTest < ActionController::TestCase
+ def setup
+ @request.env['HTTP_HOST'] = 'www.example.com:80'
+ end
+
+ def test_redirect_to_custom_host
+ get :redirect_host
+ assert_response 301
+ assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_host", redirect_to_url
+ end
+
+ def test_redirect_to_custom_port
+ get :redirect_port
+ assert_response 301
+ assert_equal "https://www.example.com:8443/force_ssl_custom_options/redirect_port", redirect_to_url
+ end
+
+ def test_redirect_to_custom_subdomain
+ get :redirect_subdomain
+ assert_response 301
+ assert_equal "https://secure.example.com/force_ssl_custom_options/redirect_subdomain", redirect_to_url
+ end
+
+ def test_redirect_to_custom_domain
+ get :redirect_domain
+ assert_response 301
+ assert_equal "https://www.secure.com/force_ssl_custom_options/redirect_domain", redirect_to_url
+ end
+
+ def test_redirect_to_custom_path
+ get :redirect_path
+ assert_response 301
+ assert_equal "https://www.example.com/foo", redirect_to_url
+ end
+
+ def test_redirect_to_custom_status
+ get :redirect_status
+ assert_response 302
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_status", redirect_to_url
+ end
+
+ def test_redirect_to_custom_flash
+ get :redirect_flash
+ assert_response 301
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_flash", redirect_to_url
+
+ get :use_flash
+ assert_response 200
+ assert_equal "Foo, Bar!", @response.body
+ end
+
+ def test_redirect_to_custom_alert
+ get :redirect_alert
+ assert_response 301
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_alert", redirect_to_url
+
+ get :use_alert
+ assert_response 200
+ assert_equal "Foo, Bar!", @response.body
+ end
+
+ def test_redirect_to_custom_notice
+ get :redirect_notice
+ assert_response 301
+ assert_equal "https://www.example.com/force_ssl_custom_options/redirect_notice", redirect_to_url
+
+ get :use_notice
+ assert_response 200
+ assert_equal "Foo, Bar!", @response.body
+ end
+end
+
+class ForceSSLOnlyActionTest < ActionController::TestCase
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_only_action/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLExceptActionTest < ActionController::TestCase
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_except_action/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLIfConditionTest < ActionController::TestCase
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_if_condition/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLFlashTest < ActionController::TestCase
+ def test_cheeseburger_redirects_to_https
+ get :set_flash
+ assert_response 302
+ assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+
+ # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists
+ @request.env.delete('PATH_INFO')
+
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+
+ # FIXME: AC::TestCase#build_request_uri doesn't build a new uri if PATH_INFO exists
+ @request.env.delete('PATH_INFO')
+
+ get :use_flash
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "hello", assigns["flashy"]
+ end
+end
+
+class ForceSSLDuplicateRoutesTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_force_ssl_redirects_to_same_path
+ with_routing do |set|
+ set.draw do
+ get '/foo', :to => 'force_ssl_controller_level#banana'
+ get '/bar', :to => 'force_ssl_controller_level#banana'
+ end
+
+ @request.env['PATH_INFO'] = '/bar'
+
+ get :banana
+ assert_response 301
+ assert_equal 'https://test.host/bar', redirect_to_url
+ end
+ end
+end
+
+class ForceSSLFormatTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_force_ssl_redirects_to_same_format
+ with_routing do |set|
+ set.draw do
+ get '/foo', :to => 'force_ssl_controller_level#banana'
+ end
+
+ get :banana, :format => :json
+ assert_response 301
+ assert_equal 'https://test.host/foo.json', redirect_to_url
+ end
+ end
+end
+
+class ForceSSLOptionalSegmentsTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_force_ssl_redirects_to_same_format
+ with_routing do |set|
+ set.draw do
+ scope '(:locale)' do
+ defaults :locale => 'en' do
+ get '/foo', :to => 'force_ssl_controller_level#banana'
+ end
+ end
+ end
+
+ @request.env['PATH_INFO'] = '/en/foo'
+ get :banana, :locale => 'en'
+ assert_equal 'en', @controller.params[:locale]
+ assert_response 301
+ assert_equal 'https://test.host/en/foo', redirect_to_url
+ end
+ end
+end
+
+class RedirectToSSLTest < ActionController::TestCase
+ def test_banana_redirects_to_https_if_not_https
+ get :banana
+ assert_response 301
+ assert_equal "https://test.host/redirect_to_ssl/banana", redirect_to_url
+ end
+
+ def test_cheeseburgers_redirects_to_https_with_new_host_if_not_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://secure.cheeseburger.host/redirect_to_ssl/cheeseburger", redirect_to_url
+ end
+
+ def test_banana_does_not_redirect_if_already_https
+ request.env['HTTPS'] = 'on'
+ get :cheeseburger
+ assert_response 200
+ assert_equal 'ihaz', response.body
+ end
+end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
new file mode 100644
index 0000000000..20f99f19ee
--- /dev/null
+++ b/actionpack/test/controller/helper_test.rb
@@ -0,0 +1,275 @@
+require 'abstract_unit'
+
+ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__)
+
+module Fun
+ class GamesController < ActionController::Base
+ def render_hello_world
+ render :inline => "hello: <%= stratego %>"
+ end
+ end
+
+ class PdfController < ActionController::Base
+ def test
+ render :inline => "test: <%= foobar %>"
+ end
+ end
+end
+
+class AllHelpersController < ActionController::Base
+ helper :all
+end
+
+module ImpressiveLibrary
+ extend ActiveSupport::Concern
+ included do
+ helper_method :useful_function
+ end
+
+ def useful_function() end
+end
+
+ActionController::Base.send :include, ImpressiveLibrary
+
+class JustMeController < ActionController::Base
+ clear_helpers
+
+ def flash
+ render :inline => "<h1><%= notice %></h1>"
+ end
+
+ def lib
+ render :inline => '<%= useful_function %>'
+ end
+end
+
+class MeTooController < JustMeController
+end
+
+class HelpersPathsController < ActionController::Base
+ paths = ["helpers2_pack", "helpers1_pack"].map do |path|
+ File.join(File.expand_path('../../fixtures', __FILE__), path)
+ end
+ $:.unshift(*paths)
+
+ self.helpers_path = paths
+ helper :all
+
+ def index
+ render :inline => "<%= conflicting_helper %>"
+ end
+end
+
+module LocalAbcHelper
+ def a() end
+ def b() end
+ def c() end
+end
+
+class HelperPathsTest < ActiveSupport::TestCase
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_helpers_paths_priority
+ request = ActionController::TestRequest.new
+ responses = HelpersPathsController.action(:index).call(request.env)
+
+ # helpers1_pack was given as a second path, so pack1_helper should be
+ # included as the second one
+ assert_equal "pack1", responses.last.body
+ end
+end
+
+class HelperTest < ActiveSupport::TestCase
+ class TestController < ActionController::Base
+ attr_accessor :delegate_attr
+ def delegate_method() end
+ end
+
+ def setup
+ # Increment symbol counter.
+ @symbol = (@@counter ||= 'A0').succ!.dup
+
+ # Generate new controller class.
+ controller_class_name = "Helper#{@symbol}Controller"
+ eval("class #{controller_class_name} < TestController; end")
+ @controller_class = self.class.const_get(controller_class_name)
+
+ # Set default test helper.
+ self.test_helper = LocalAbcHelper
+ end
+
+ def test_helper
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper TestHelper }
+ assert_equal [], missing_methods
+ end
+
+ def test_helper_method
+ assert_nothing_raised { @controller_class.helper_method :delegate_method }
+ assert master_helper_methods.include?(:delegate_method)
+ end
+
+ def test_helper_attr
+ assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
+ assert master_helper_methods.include?(:delegate_attr)
+ assert master_helper_methods.include?(:delegate_attr=)
+ end
+
+ def call_controller(klass, action)
+ request = ActionController::TestRequest.new
+ klass.action(action).call(request.env)
+ end
+
+ def test_helper_for_nested_controller
+ assert_equal 'hello: Iz guuut!',
+ call_controller(Fun::GamesController, "render_hello_world").last.body
+ # request = ActionController::TestRequest.new
+ #
+ # resp = Fun::GamesController.action(:render_hello_world).call(request.env)
+ # assert_equal 'hello: Iz guuut!', resp.last.body
+ end
+
+ def test_helper_for_acronym_controller
+ assert_equal "test: baz", call_controller(Fun::PdfController, "test").last.body
+ #
+ # request = ActionController::TestRequest.new
+ # response = ActionController::TestResponse.new
+ # request.action = 'test'
+ #
+ # assert_equal 'test: baz', Fun::PdfController.process(request, response).body
+ end
+
+ def test_default_helpers_only
+ assert_equal [JustMeHelper], JustMeController._helpers.ancestors.reject(&:anonymous?)
+ assert_equal [MeTooHelper, JustMeHelper], MeTooController._helpers.ancestors.reject(&:anonymous?)
+ end
+
+ def test_base_helper_methods_after_clear_helpers
+ assert_nothing_raised do
+ call_controller(JustMeController, "flash")
+ end
+ end
+
+ def test_lib_helper_methods_after_clear_helpers
+ assert_nothing_raised do
+ call_controller(JustMeController, "lib")
+ end
+ end
+
+ def test_all_helpers
+ methods = AllHelpersController._helpers.instance_methods
+
+ # abc_helper.rb
+ assert methods.include?(:bare_a)
+
+ # fun/games_helper.rb
+ assert methods.include?(:stratego)
+
+ # fun/pdf_helper.rb
+ assert methods.include?(:foobar)
+ end
+
+ def test_all_helpers_with_alternate_helper_dir
+ @controller_class.helpers_path = File.expand_path('../../fixtures/alternate_helpers', __FILE__)
+
+ # Reload helpers
+ @controller_class._helpers = Module.new
+ @controller_class.helper :all
+
+ # helpers/abc_helper.rb should not be included
+ assert !master_helper_methods.include?(:bare_a)
+
+ # alternate_helpers/foo_helper.rb
+ assert master_helper_methods.include?(:baz)
+ end
+
+ def test_helper_proxy
+ methods = AllHelpersController.helpers.methods
+
+ # Action View
+ assert methods.include?(:pluralize)
+
+ # abc_helper.rb
+ assert methods.include?(:bare_a)
+
+ # fun/games_helper.rb
+ assert methods.include?(:stratego)
+
+ # fun/pdf_helper.rb
+ assert methods.include?(:foobar)
+ end
+
+ def test_helper_proxy_config
+ AllHelpersController.config.my_var = 'smth'
+
+ assert_equal 'smth', AllHelpersController.helpers.config.my_var
+ end
+
+ private
+ def expected_helper_methods
+ TestHelper.instance_methods
+ end
+
+ def master_helper_methods
+ @controller_class._helpers.instance_methods
+ end
+
+ def missing_methods
+ expected_helper_methods - master_helper_methods
+ end
+
+ def test_helper=(helper_module)
+ silence_warnings { self.class.const_set('TestHelper', helper_module) }
+ end
+end
+
+
+class IsolatedHelpersTest < ActiveSupport::TestCase
+ class A < ActionController::Base
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ class B < A
+ helper { def shout; 'B' end }
+
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ class C < A
+ helper { def shout; 'C' end }
+
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ def call_controller(klass, action)
+ request = ActionController::TestRequest.new
+ klass.action(action).call(request.env)
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.action = 'index'
+ end
+
+ def test_helper_in_a
+ assert_raise(ActionView::Template::Error) { call_controller(A, "index") }
+ end
+
+ def test_helper_in_b
+ assert_equal 'B', call_controller(B, "index").last.body
+ end
+
+ def test_helper_in_c
+ assert_equal 'C', call_controller(C, "index").last.body
+ end
+end
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
new file mode 100644
index 0000000000..9052fc6962
--- /dev/null
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -0,0 +1,144 @@
+require 'abstract_unit'
+
+class HttpBasicAuthenticationTest < ActionController::TestCase
+ class DummyController < ActionController::Base
+ before_action :authenticate, only: :index
+ before_action :authenticate_with_request, only: :display
+ before_action :authenticate_long_credentials, only: :show
+
+ http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ def display
+ render :text => 'Definitely Maybe'
+ end
+
+ def show
+ render :text => 'Only for loooooong credentials'
+ end
+
+ def search
+ render :text => 'All inline'
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_basic do |username, password|
+ username == 'lifo' && password == 'world'
+ end
+ end
+
+ def authenticate_with_request
+ if authenticate_with_http_basic { |username, password| username == 'pretty' && password == 'please' }
+ @logged_in = true
+ else
+ request_http_basic_authentication("SuperSecret")
+ end
+ end
+
+ def authenticate_long_credentials
+ authenticate_or_request_with_http_basic do |username, password|
+ username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890'
+ end
+ end
+ end
+
+ AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
+
+ tests DummyController
+
+ AUTH_HEADERS.each do |header|
+ test "successful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials('lifo', 'world')
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
+ end
+ test "successful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890')
+ get :show
+
+ assert_response :success
+ assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials"
+ end
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "unsuccessful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials('h4x0r', 'world')
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
+ end
+ test "unsuccessful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld')
+ get :show
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
+ end
+ end
+
+ def test_encode_credentials_has_no_newline
+ username = 'laskjdfhalksdjfhalkjdsfhalksdjfhklsdjhalksdjfhalksdjfhlakdsjfh'
+ password = 'kjfhueyt9485osdfasdkljfh4lkjhakldjfhalkdsjf'
+ result = ActionController::HttpAuthentication::Basic.encode_credentials(
+ username, password)
+ assert_no_match(/\n/, result)
+ end
+
+ test "authentication request without credential" do
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
+ end
+
+ test "authentication request with invalid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'foo')
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert_equal 'Basic realm="SuperSecret"', @response.headers['WWW-Authenticate']
+ end
+
+ test "authentication request with valid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('pretty', 'please')
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authenticate with class method" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath')
+ get :search
+ assert_response :success
+
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'WRONG!')
+ get :search
+ assert_response :unauthorized
+ end
+
+ test "authentication request with wrong scheme" do
+ header = 'Bearer ' + encode_credentials('David', 'Goliath').split(' ', 2)[1]
+ @request.env['HTTP_AUTHORIZATION'] = header
+ get :search
+ assert_response :unauthorized
+ end
+
+ private
+
+ def encode_credentials(username, password)
+ "Basic #{::Base64.encode64("#{username}:#{password}")}"
+ end
+end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
new file mode 100644
index 0000000000..52a0bc9aa3
--- /dev/null
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -0,0 +1,289 @@
+require 'abstract_unit'
+require 'active_support/key_generator'
+
+class HttpDigestAuthenticationTest < ActionController::TestCase
+ class DummyDigestController < ActionController::Base
+ before_action :authenticate, only: :index
+ before_action :authenticate_with_request, only: :display
+
+ USERS = { 'lifo' => 'world', 'pretty' => 'please',
+ 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))}
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ def display
+ render :text => 'Definitely Maybe'
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_digest("SuperSecret") do |username|
+ # Returns the password
+ USERS[username]
+ end
+ end
+
+ def authenticate_with_request
+ if authenticate_with_http_digest("SuperSecret") { |username| USERS[username] }
+ @logged_in = true
+ else
+ request_http_digest_authentication("SuperSecret", "Authentication Failed")
+ end
+ end
+ end
+
+ AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
+
+ tests DummyDigestController
+
+ setup do
+ # Used as secret in generating nonce to prevent tampering of timestamp
+ @secret = "4fb45da9e4ab4ddeb7580d6a35503d99"
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(@secret)
+ end
+
+ teardown do
+ # ActionController::Base.session_options[:secret] = @old_secret
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "successful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials(:username => 'lifo', :password => 'world')
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
+ end
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "unsuccessful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials(:username => 'h4x0r', :password => 'world')
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
+ end
+ end
+
+ test "authentication request without credential" do
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ credentials = decode_credentials(@response.headers['WWW-Authenticate'])
+ assert_equal 'SuperSecret', credentials[:realm]
+ end
+
+ test "authentication request with nil credentials" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil)
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Digest: Access denied.\n", @response.body, "Authentication didn't fail for request"
+ assert_not_equal 'Hello Secret', @response.body, "Authentication didn't fail for request"
+ end
+
+ test "authentication request with invalid password" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo')
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid nonce" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :nonce => "xxyyzz")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid opaque" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :opaque => "xxyyzz")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with invalid realm" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'foo', :realm => "NotSecret")
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "Authentication Failed", @response.body
+ end
+
+ test "authentication request with valid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with valid credential and nil session" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with request-uri that doesn't match credentials digest-uri" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ @request.env['PATH_INFO'] = "/proxied/uri"
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute request uri (as in webrick)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ @request.env["SERVER_NAME"] = "test.host"
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in credentials (as in IE)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
+ :username => 'pretty', :password => 'please')
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
+ :username => 'pretty', :password => 'please')
+ @request.env['SERVER_NAME'] = "test.host"
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with password stored as ha1 digest hash" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'dhh',
+ :password => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")),
+ :password_is_ha1 => true)
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with _method" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please', :method => :post)
+ @request.env['rack.methodoverride.original_method'] = 'POST'
+ put :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "validate_digest_response should fail with nil returning password_procedure" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => nil, :password => nil)
+ assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil}
+ end
+
+ test "authentication request with request-uri ending in '/'" do
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/"
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+
+ # simulate normalizing PATH_INFO
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+ get :display
+
+ assert_response :success
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with request-uri ending in '?'" do
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/?"
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+
+ # simulate normalizing PATH_INFO
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+ get :display
+
+ assert_response :success
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in credentials (as in IE) ending with /" do
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/"
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/",
+ :username => 'pretty', :password => 'please')
+
+ # simulate normalizing PATH_INFO
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "when sent a basic auth header, returns Unauthorized" do
+ @request.env['HTTP_AUTHORIZATION'] = 'Basic Gwf2aXq8ZLF3Hxq='
+
+ get :display
+
+ assert_response :unauthorized
+ end
+
+ private
+
+ def encode_credentials(options)
+ options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b", :password_is_ha1 => false)
+ password = options.delete(:password)
+
+ # Perform unauthenticated request to retrieve digest parameters to use on subsequent request
+ method = options.delete(:method) || 'GET'
+
+ case method.to_s.upcase
+ when 'GET'
+ get :index
+ when 'POST'
+ post :index
+ end
+
+ assert_response :unauthorized
+
+ credentials = decode_credentials(@response.headers['WWW-Authenticate'])
+ credentials.merge!(options)
+ path_info = @request.env['PATH_INFO'].to_s
+ uri = options[:uri] || path_info
+ credentials.merge!(:uri => uri)
+ @request.env["ORIGINAL_FULLPATH"] = path_info
+ ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
+ end
+
+ def decode_credentials(header)
+ ActionController::HttpAuthentication::Digest.decode_credentials(header)
+ end
+end
diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb
new file mode 100644
index 0000000000..8c6c8a0aa7
--- /dev/null
+++ b/actionpack/test/controller/http_token_authentication_test.rb
@@ -0,0 +1,181 @@
+require 'abstract_unit'
+
+class HttpTokenAuthenticationTest < ActionController::TestCase
+ class DummyController < ActionController::Base
+ before_action :authenticate, only: :index
+ before_action :authenticate_with_request, only: :display
+ before_action :authenticate_long_credentials, only: :show
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ def display
+ render :text => 'Definitely Maybe'
+ end
+
+ def show
+ render :text => 'Only for loooooong credentials'
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_token do |token, _|
+ token == 'lifo'
+ end
+ end
+
+ def authenticate_with_request
+ if authenticate_with_http_token { |token, options| token == '"quote" pretty' && options[:algorithm] == 'test' }
+ @logged_in = true
+ else
+ request_http_token_authentication("SuperSecret")
+ end
+ end
+
+ def authenticate_long_credentials
+ authenticate_or_request_with_http_token do |token, options|
+ token == '1234567890123456789012345678901234567890' && options[:algorithm] == 'test'
+ end
+ end
+ end
+
+ AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
+
+ tests DummyController
+
+ AUTH_HEADERS.each do |header|
+ test "successful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials('lifo')
+ get :index
+
+ assert_response :success
+ assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
+ end
+ test "successful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', :algorithm => 'test')
+ get :show
+
+ assert_response :success
+ assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials"
+ end
+ end
+
+ AUTH_HEADERS.each do |header|
+ test "unsuccessful authentication with #{header.downcase}" do
+ @request.env[header] = encode_credentials('h4x0r')
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
+ end
+ test "unsuccessful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r')
+ get :show
+
+ assert_response :unauthorized
+ assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
+ end
+ end
+
+ test "authentication request with badly formatted header" do
+ @request.env['HTTP_AUTHORIZATION'] = "Token foobar"
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Token: Access denied.\n", @response.body, "Authentication header was not properly parsed"
+ end
+
+ test "authentication request without credential" do
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
+ end
+
+ test "authentication request with invalid credential" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('"quote" pretty')
+ get :display
+
+ assert_response :unauthorized
+ assert_equal "HTTP Token: Access denied.\n", @response.body
+ assert_equal 'Token realm="SuperSecret"', @response.headers['WWW-Authenticate']
+ end
+
+ test "token_and_options returns correct token" do
+ token = "rcHu+HzSFw89Ypyhn/896A=="
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns correct token with value after the equal sign" do
+ token = 'rcHu+=HzSFw89Ypyhn/896A==f34'
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns correct token with slashes" do
+ token = 'rcHu+\\\\"/896A'
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns correct token with quotes" do
+ token = '\"quote\" pretty'
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns empty string with empty token" do
+ token = ''
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first
+ expected = token
+ assert_equal(expected, actual)
+ end
+
+ test "token_and_options returns correct token with nounce option" do
+ token = "rcHu+HzSFw89Ypyhn/896A="
+ nonce_hash = {nonce: "123abc"}
+ actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token, nonce_hash))
+ expected_token = token
+ expected_nonce = {"nonce" => nonce_hash[:nonce]}
+ assert_equal(expected_token, actual.first)
+ assert_equal(expected_nonce, actual.last)
+ end
+
+ test "token_and_options returns nil with no value after the equal sign" do
+ actual = ActionController::HttpAuthentication::Token.token_and_options(malformed_request).first
+ expected = nil
+ assert_equal(expected, actual)
+ end
+
+ test "raw_params returns a tuple of two key value pair strings" do
+ auth = sample_request("rcHu+HzSFw89Ypyhn/896A=").authorization.to_s
+ actual = ActionController::HttpAuthentication::Token.raw_params(auth)
+ expected = ["token=\"rcHu+HzSFw89Ypyhn/896A=\"", "nonce=\"def\""]
+ assert_equal(expected, actual)
+ end
+
+ private
+
+ def sample_request(token, options = {nonce: "def"})
+ authorization = options.inject([%{Token token="#{token}"}]) do |arr, (k, v)|
+ arr << "#{k}=\"#{v}\""
+ end.join(", ")
+ @sample_request ||= OpenStruct.new authorization: authorization
+ end
+
+ def malformed_request
+ @malformed_request ||= OpenStruct.new authorization: %{Token token=}
+ end
+
+ def encode_credentials(token, options = {})
+ ActionController::HttpAuthentication::Token.encode_credentials(token, options)
+ end
+end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
new file mode 100644
index 0000000000..78cce3aa64
--- /dev/null
+++ b/actionpack/test/controller/integration_test.rb
@@ -0,0 +1,809 @@
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'action_view/vendor/html-scanner'
+require 'rails/engine'
+
+class SessionTest < ActiveSupport::TestCase
+ StubApp = lambda { |env|
+ [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]]
+ }
+
+ def setup
+ @session = ActionDispatch::Integration::Session.new(StubApp)
+ end
+
+ def test_https_bang_works_and_sets_truth_by_default
+ assert !@session.https?
+ @session.https!
+ assert @session.https?
+ @session.https! false
+ assert !@session.https?
+ end
+
+ def test_host!
+ assert_not_equal "glu.ttono.us", @session.host
+ @session.host! "rubyonrails.com"
+ assert_equal "rubyonrails.com", @session.host
+ end
+
+ def test_follow_redirect_raises_when_no_redirect
+ @session.stubs(:redirect?).returns(false)
+ assert_raise(RuntimeError) { @session.follow_redirect! }
+ end
+
+ def test_request_via_redirect_uses_given_method
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
+ @session.expects(:process).with(:put, path, args, headers)
+ @session.stubs(:redirect?).returns(false)
+ @session.request_via_redirect(:put, path, args, headers)
+ end
+
+ def test_request_via_redirect_follows_redirects
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
+ @session.stubs(:redirect?).returns(true, true, false)
+ @session.expects(:follow_redirect!).times(2)
+ @session.request_via_redirect(:get, path, args, headers)
+ end
+
+ def test_request_via_redirect_returns_status
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue"}
+ @session.stubs(:redirect?).returns(false)
+ @session.stubs(:status).returns(200)
+ assert_equal 200, @session.request_via_redirect(:get, path, args, headers)
+ end
+
+ def test_get_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:get, path, args, headers)
+ @session.get_via_redirect(path, args, headers)
+ end
+
+ def test_post_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:post, path, args, headers)
+ @session.post_via_redirect(path, args, headers)
+ end
+
+ def test_patch_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:patch, path, args, headers)
+ @session.patch_via_redirect(path, args, headers)
+ end
+
+ def test_put_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:put, path, args, headers)
+ @session.put_via_redirect(path, args, headers)
+ end
+
+ def test_delete_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:delete, path, args, headers)
+ @session.delete_via_redirect(path, args, headers)
+ end
+
+ def test_get
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:get,path,params,headers)
+ @session.get(path,params,headers)
+ end
+
+ def test_post
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:post,path,params,headers)
+ @session.post(path,params,headers)
+ end
+
+ def test_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:patch,path,params,headers)
+ @session.patch(path,params,headers)
+ end
+
+ def test_put
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:put,path,params,headers)
+ @session.put(path,params,headers)
+ end
+
+ def test_delete
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:delete,path,params,headers)
+ @session.delete(path,params,headers)
+ end
+
+ def test_head
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:head,path,params,headers)
+ @session.head(path,params,headers)
+ end
+
+ def test_xml_http_request_get
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:get,path,params,headers_after_xhr)
+ @session.xml_http_request(:get,path,params,headers)
+ end
+
+ def test_xml_http_request_post
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:post,path,params,headers_after_xhr)
+ @session.xml_http_request(:post,path,params,headers)
+ end
+
+ def test_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:patch,path,params,headers_after_xhr)
+ @session.xml_http_request(:patch,path,params,headers)
+ end
+
+ def test_xml_http_request_put
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:put,path,params,headers_after_xhr)
+ @session.xml_http_request(:put,path,params,headers)
+ end
+
+ def test_xml_http_request_delete
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:delete,path,params,headers_after_xhr)
+ @session.xml_http_request(:delete,path,params,headers)
+ end
+
+ def test_xml_http_request_head
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:head,path,params,headers_after_xhr)
+ @session.xml_http_request(:head,path,params,headers)
+ end
+
+ def test_xml_http_request_override_accept
+ path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest"
+ )
+ @session.expects(:process).with(:post,path,params,headers_after_xhr)
+ @session.xml_http_request(:post,path,params,headers)
+ end
+end
+
+class IntegrationTestTest < ActiveSupport::TestCase
+ def setup
+ @test = ::ActionDispatch::IntegrationTest.new(:app)
+ @test.class.stubs(:fixture_table_names).returns([])
+ @session = @test.open_session
+ end
+
+ def test_opens_new_session
+ session1 = @test.open_session { |sess| }
+ session2 = @test.open_session # implicit session
+
+ assert_respond_to session1, :assert_template, "open_session makes assert_template available"
+ assert_respond_to session2, :assert_template, "open_session makes assert_template available"
+ assert !session1.equal?(session2)
+ end
+
+ # RSpec mixes Matchers (which has a #method_missing) into
+ # IntegrationTest's superclass. Make sure IntegrationTest does not
+ # try to delegate these methods to the session object.
+ def test_does_not_prevent_method_missing_passing_up_to_ancestors
+ mixin = Module.new do
+ def method_missing(name, *args)
+ name.to_s == 'foo' ? 'pass' : super
+ end
+ end
+ @test.class.superclass.__send__(:include, mixin)
+ begin
+ assert_equal 'pass', @test.foo
+ ensure
+ # leave other tests as unaffected as possible
+ mixin.__send__(:remove_method, :method_missing)
+ end
+ end
+end
+
+# Tests that integration tests don't call Controller test methods for processing.
+# Integration tests have their own setup and teardown.
+class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
+ def self.fixture_table_names
+ []
+ end
+
+ def test_integration_methods_called
+ reset!
+ @integration_session.stubs(:generic_url_rewriter)
+ @integration_session.stubs(:process)
+
+ %w( get post head patch put delete ).each do |verb|
+ assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
+ end
+ end
+end
+
+class IntegrationProcessTest < ActionDispatch::IntegrationTest
+ class IntegrationController < ActionController::Base
+ def get
+ respond_to do |format|
+ format.html { render :text => "OK", :status => 200 }
+ format.js { render :text => "JS OK", :status => 200 }
+ end
+ end
+
+ def get_with_params
+ render :text => "foo: #{params[:foo]}", :status => 200
+ end
+
+ def post
+ render :text => "Created", :status => 201
+ end
+
+ def method
+ render :text => "method: #{request.method.downcase}"
+ end
+
+ def cookie_monster
+ cookies["cookie_1"] = nil
+ cookies["cookie_3"] = "chocolate"
+ render :text => "Gone", :status => 410
+ end
+
+ def set_cookie
+ cookies["foo"] = 'bar'
+ head :ok
+ end
+
+ def get_cookie
+ render :text => cookies["foo"]
+ end
+
+ def redirect
+ redirect_to action_url('get')
+ end
+ end
+
+ def test_get
+ with_test_route_set do
+ get '/get'
+ assert_equal 200, status
+ assert_equal "OK", status_message
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal({}, cookies.to_hash)
+ assert_equal "OK", body
+ assert_equal "OK", response.body
+ assert_kind_of HTML::Document, html_document
+ assert_equal 1, request_count
+ end
+ end
+
+ def test_post
+ with_test_route_set do
+ post '/post'
+ assert_equal 201, status
+ assert_equal "Created", status_message
+ assert_response 201
+ assert_response :success
+ assert_response :created
+ assert_equal({}, cookies.to_hash)
+ assert_equal "Created", body
+ assert_equal "Created", response.body
+ assert_kind_of HTML::Document, html_document
+ assert_equal 1, request_count
+ end
+ end
+
+ test 'response cookies are added to the cookie jar for the next request' do
+ with_test_route_set do
+ self.cookies['cookie_1'] = "sugar"
+ self.cookies['cookie_2'] = "oatmeal"
+ get '/cookie_monster'
+ assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"]
+ assert_equal({"cookie_1"=>"", "cookie_2"=>"oatmeal", "cookie_3"=>"chocolate"}, cookies.to_hash)
+ end
+ end
+
+ test 'cookie persist to next request' do
+ with_test_route_set do
+ get '/set_cookie'
+ assert_response :success
+
+ assert_equal "foo=bar; path=/", headers["Set-Cookie"]
+ assert_equal({"foo"=>"bar"}, cookies.to_hash)
+
+ get '/get_cookie'
+ assert_response :success
+ assert_equal "bar", body
+
+ assert_equal nil, headers["Set-Cookie"]
+ assert_equal({"foo"=>"bar"}, cookies.to_hash)
+ end
+ end
+
+ test 'cookie persist to next request on another domain' do
+ with_test_route_set do
+ host! "37s.backpack.test"
+
+ get '/set_cookie'
+ assert_response :success
+
+ assert_equal "foo=bar; path=/", headers["Set-Cookie"]
+ assert_equal({"foo"=>"bar"}, cookies.to_hash)
+
+ get '/get_cookie'
+ assert_response :success
+ assert_equal "bar", body
+
+ assert_equal nil, headers["Set-Cookie"]
+ assert_equal({"foo"=>"bar"}, cookies.to_hash)
+ end
+ end
+
+ def test_redirect
+ with_test_route_set do
+ get '/redirect'
+ assert_equal 302, status
+ assert_equal "Found", status_message
+ assert_response 302
+ assert_response :redirect
+ assert_response :found
+ assert_equal "<html><body>You are being <a href=\"http://www.example.com/get\">redirected</a>.</body></html>", response.body
+ assert_kind_of HTML::Document, html_document
+ assert_equal 1, request_count
+
+ follow_redirect!
+ assert_response :success
+ assert_equal "/get", path
+
+ get '/moved'
+ assert_response :redirect
+ assert_redirected_to '/method'
+ end
+ end
+
+ def test_xml_http_request_get
+ with_test_route_set do
+ xhr :get, '/get'
+ assert_equal 200, status
+ assert_equal "OK", status_message
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal "JS OK", response.body
+ end
+ end
+
+ def test_request_with_bad_format
+ with_test_route_set do
+ xhr :get, '/get.php'
+ assert_equal 406, status
+ assert_response 406
+ assert_response :not_acceptable
+ end
+ end
+
+ def test_get_with_query_string
+ with_test_route_set do
+ get '/get_with_params?foo=bar'
+ assert_equal '/get_with_params?foo=bar', request.env["REQUEST_URI"]
+ assert_equal '/get_with_params?foo=bar', request.fullpath
+ assert_equal "foo=bar", request.env["QUERY_STRING"]
+ assert_equal 'foo=bar', request.query_string
+ assert_equal 'bar', request.parameters['foo']
+
+ assert_equal 200, status
+ assert_equal "foo: bar", response.body
+ end
+ end
+
+ def test_get_with_parameters
+ with_test_route_set do
+ get '/get_with_params', :foo => "bar"
+ assert_equal '/get_with_params', request.env["PATH_INFO"]
+ assert_equal '/get_with_params', request.path_info
+ assert_equal 'foo=bar', request.env["QUERY_STRING"]
+ assert_equal 'foo=bar', request.query_string
+ assert_equal 'bar', request.parameters['foo']
+
+ assert_equal 200, status
+ assert_equal "foo: bar", response.body
+ end
+ end
+
+ def test_head
+ with_test_route_set do
+ head '/get'
+ assert_equal 200, status
+ assert_equal "", body
+
+ head '/post'
+ assert_equal 201, status
+ assert_equal "", body
+
+ get '/get/method'
+ assert_equal 200, status
+ assert_equal "method: get", body
+
+ head '/get/method'
+ assert_equal 200, status
+ assert_equal "", body
+ end
+ end
+
+ def test_generate_url_with_controller
+ assert_equal 'http://www.example.com/foo', url_for(:controller => "foo")
+ end
+
+ def test_port_via_host!
+ with_test_route_set do
+ host! 'www.example.com:8080'
+ get '/get'
+ assert_equal 8080, request.port
+ end
+ end
+
+ def test_port_via_process
+ with_test_route_set do
+ get 'http://www.example.com:8080/get'
+ assert_equal 8080, request.port
+ end
+ end
+
+ def test_https_and_port_via_host_and_https!
+ with_test_route_set do
+ host! 'www.example.com'
+ https! true
+
+ get '/get'
+ assert_equal 443, request.port
+ assert_equal true, request.ssl?
+
+ host! 'www.example.com:443'
+ https! true
+
+ get '/get'
+ assert_equal 443, request.port
+ assert_equal true, request.ssl?
+
+ host! 'www.example.com:8443'
+ https! true
+
+ get '/get'
+ assert_equal 8443, request.port
+ assert_equal true, request.ssl?
+ end
+ end
+
+ def test_https_and_port_via_process
+ with_test_route_set do
+ get 'https://www.example.com/get'
+ assert_equal 443, request.port
+ assert_equal true, request.ssl?
+
+ get 'https://www.example.com:8443/get'
+ assert_equal 8443, request.port
+ assert_equal true, request.ssl?
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ controller = ::IntegrationProcessTest::IntegrationController.clone
+ controller.class_eval do
+ include set.url_helpers
+ end
+
+ set.draw do
+ get 'moved' => redirect('/method')
+
+ match ':action', :to => controller, :via => [:get, :post], :as => :action
+ get 'get/:action', :to => controller, :as => :get_action
+ end
+
+ self.singleton_class.send(:include, set.url_helpers)
+
+ yield
+ end
+ end
+end
+
+class MetalIntegrationTest < ActionDispatch::IntegrationTest
+ include SharedTestRoutes.url_helpers
+
+ class Poller
+ def self.call(env)
+ if env["PATH_INFO"] =~ /^\/success/
+ [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]]
+ else
+ [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []]
+ end
+ end
+ end
+
+ def setup
+ @app = Poller
+ end
+
+ def test_successful_get
+ get "/success"
+ assert_response 200
+ assert_response :success
+ assert_response :ok
+ assert_equal "Hello World!", response.body
+ end
+
+ def test_failed_get
+ get "/failure"
+ assert_response 404
+ assert_response :not_found
+ assert_equal '', response.body
+ end
+
+ def test_generate_url_without_controller
+ assert_equal 'http://www.example.com/foo', url_for(:controller => "foo")
+ end
+
+ def test_pass_headers
+ get "/success", {}, "Referer" => "http://www.example.com/foo", "Host" => "http://nohost.com"
+
+ assert_equal "http://nohost.com", @request.env["HTTP_HOST"]
+ assert_equal "http://www.example.com/foo", @request.env["HTTP_REFERER"]
+ end
+
+ def test_pass_env
+ get "/success", {}, "HTTP_REFERER" => "http://test.com/", "HTTP_HOST" => "http://test.com"
+
+ assert_equal "http://test.com", @request.env["HTTP_HOST"]
+ assert_equal "http://test.com/", @request.env["HTTP_REFERER"]
+ end
+
+end
+
+class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def index
+ render :text => "index"
+ end
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ class MountedApp < Rails::Engine
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw do
+ get 'baz', :to => 'application_integration_test/test#index', :as => :baz
+ end
+
+ def self.call(*)
+ end
+ end
+
+ routes.draw do
+ get '', :to => 'application_integration_test/test#index', :as => :empty_string
+
+ get 'foo', :to => 'application_integration_test/test#index', :as => :foo
+ get 'bar', :to => 'application_integration_test/test#index', :as => :bar
+
+ mount MountedApp => '/mounted', :as => "mounted"
+ end
+
+ def app
+ self.class
+ end
+
+ test "includes route helpers" do
+ assert_equal '/', empty_string_path
+ assert_equal '/foo', foo_path
+ assert_equal '/bar', bar_path
+ end
+
+ test "includes mounted helpers" do
+ assert_equal '/mounted/baz', mounted.baz_path
+ end
+
+ test "route helpers after controller access" do
+ get '/'
+ assert_equal '/', empty_string_path
+
+ get '/foo'
+ assert_equal '/foo', foo_path
+
+ get '/bar'
+ assert_equal '/bar', bar_path
+ end
+
+ test "missing route helper before controller access" do
+ assert_raise(NameError) { missing_path }
+ end
+
+ test "missing route helper after controller access" do
+ get '/foo'
+ assert_raise(NameError) { missing_path }
+ end
+
+ test "process do not modify the env passed as argument" do
+ env = { :SERVER_NAME => 'server', 'action_dispatch.custom' => 'custom' }
+ old_env = env.dup
+ get '/foo', nil, env
+ assert_equal old_env, env
+ end
+end
+
+class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def post
+ render :text => "Created", :status => 201
+ end
+ end
+
+ def self.call(env)
+ env["action_dispatch.parameter_filter"] = [:password]
+ routes.call(env)
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw do
+ match '/post', :to => 'environment_filter_integration_test/test#post', :via => :post
+ end
+
+ def app
+ self.class
+ end
+
+ test "filters rack request form vars" do
+ post "/post", :username => 'cjolly', :password => 'secret'
+
+ assert_equal 'cjolly', request.filtered_parameters['username']
+ assert_equal '[FILTERED]', request.filtered_parameters['password']
+ assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars']
+ end
+end
+
+class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def index
+ render :text => "foo#index"
+ end
+
+ def show
+ render :text => "foo#show"
+ end
+
+ def edit
+ render :text => "foo#show"
+ end
+ end
+
+ class BarController < ActionController::Base
+ def default_url_options
+ { :host => "bar.com" }
+ end
+
+ def index
+ render :text => "foo#index"
+ end
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def app
+ self.class
+ end
+
+ routes.draw do
+ default_url_options :host => "foo.com"
+
+ scope :module => "url_options_integration_test" do
+ get "/foo" => "foo#index", :as => :foos
+ get "/foo/:id" => "foo#show", :as => :foo
+ get "/foo/:id/edit" => "foo#edit", :as => :edit_foo
+ get "/bar" => "bar#index", :as => :bars
+ end
+ end
+
+ test "session uses default url options from routes" do
+ assert_equal "http://foo.com/foo", foos_url
+ end
+
+ test "current host overrides default url options from routes" do
+ get "/foo"
+ assert_response :success
+ assert_equal "http://www.example.com/foo", foos_url
+ end
+
+ test "controller can override default url options from request" do
+ get "/bar"
+ assert_response :success
+ assert_equal "http://bar.com/foo", foos_url
+ end
+
+ def test_can_override_default_url_options
+ original_host = default_url_options.dup
+
+ default_url_options[:host] = "foobar.com"
+ assert_equal "http://foobar.com/foo", foos_url
+
+ get "/bar"
+ assert_response :success
+ assert_equal "http://foobar.com/foo", foos_url
+ ensure
+ ActionDispatch::Integration::Session.default_url_options = self.default_url_options = original_host
+ end
+
+ test "current request path parameters are recalled" do
+ get "/foo/1"
+ assert_response :success
+ assert_equal "/foo/1/edit", url_for(:action => 'edit', :only_path => true)
+ end
+end
+
+class HeadWithStatusActionIntegrationTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def status
+ head :ok
+ end
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def app
+ self.class
+ end
+
+ routes.draw do
+ get "/foo/status" => 'head_with_status_action_integration_test/foo#status'
+ end
+
+ test "get /foo/status with head result does not cause stack overflow error" do
+ assert_nothing_raised do
+ get '/foo/status'
+ end
+ assert_response :ok
+ end
+end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
new file mode 100644
index 0000000000..0500b7c789
--- /dev/null
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -0,0 +1,454 @@
+require 'abstract_unit'
+require 'active_support/concurrency/latch'
+Thread.abort_on_exception = true
+
+module ActionController
+ class SSETest < ActionController::TestCase
+ class SSETestController < ActionController::Base
+ include ActionController::Live
+
+ def basic_sse
+ response.headers['Content-Type'] = 'text/event-stream'
+ sse = SSE.new(response.stream)
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" })
+ ensure
+ sse.close
+ end
+
+ def sse_with_event
+ sse = SSE.new(response.stream, event: "send-name")
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" })
+ ensure
+ sse.close
+ end
+
+ def sse_with_retry
+ sse = SSE.new(response.stream, retry: 1000)
+ sse.write("{\"name\":\"John\"}")
+ sse.write({ name: "Ryan" }, retry: 1500)
+ ensure
+ sse.close
+ end
+
+ def sse_with_id
+ sse = SSE.new(response.stream)
+ sse.write("{\"name\":\"John\"}", id: 1)
+ sse.write({ name: "Ryan" }, id: 2)
+ ensure
+ sse.close
+ end
+
+ def sse_with_multiple_line_message
+ sse = SSE.new(response.stream)
+ sse.write("first line.\nsecond line.")
+ ensure
+ sse.close
+ end
+ end
+
+ tests SSETestController
+
+ def wait_for_response_stream_close
+ response.body
+ end
+
+ def test_basic_sse
+ get :basic_sse
+
+ wait_for_response_stream_close
+ assert_match(/data: {\"name\":\"John\"}/, response.body)
+ assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
+ end
+
+ def test_sse_with_event_name
+ get :sse_with_event
+
+ wait_for_response_stream_close
+ assert_match(/data: {\"name\":\"John\"}/, response.body)
+ assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
+ assert_match(/event: send-name/, response.body)
+ end
+
+ def test_sse_with_retry
+ get :sse_with_retry
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n\n")
+ assert_match(/data: {\"name\":\"John\"}/, first_response)
+ assert_match(/retry: 1000/, first_response)
+
+ assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
+ assert_match(/retry: 1500/, second_response)
+ end
+
+ def test_sse_with_id
+ get :sse_with_id
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n\n")
+ assert_match(/data: {\"name\":\"John\"}/, first_response)
+ assert_match(/id: 1/, first_response)
+
+ assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
+ assert_match(/id: 2/, second_response)
+ end
+
+ def test_sse_with_multiple_line_message
+ get :sse_with_multiple_line_message
+
+ wait_for_response_stream_close
+ first_response, second_response = response.body.split("\n")
+ assert_match(/data: first line/, first_response)
+ assert_match(/data: second line/, second_response)
+ end
+ end
+
+ class LiveStreamTest < ActionController::TestCase
+ class Exception < StandardError
+ end
+
+ class TestController < ActionController::Base
+ include ActionController::Live
+
+ attr_accessor :latch, :tc
+
+ def self.controller_path
+ 'test'
+ end
+
+ def set_cookie
+ cookies[:hello] = "world"
+ response.stream.write "hello world"
+ response.close
+ end
+
+ def render_text
+ render :text => 'zomg'
+ end
+
+ def default_header
+ response.stream.write "<html><body>hi</body></html>"
+ response.stream.close
+ end
+
+ def basic_stream
+ response.headers['Content-Type'] = 'text/event-stream'
+ %w{ hello world }.each do |word|
+ response.stream.write word
+ end
+ response.stream.close
+ end
+
+ def blocking_stream
+ response.headers['Content-Type'] = 'text/event-stream'
+ %w{ hello world }.each do |word|
+ response.stream.write word
+ latch.await
+ end
+ response.stream.close
+ end
+
+ def thread_locals
+ tc.assert_equal 'aaron', Thread.current[:setting]
+ tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread]
+
+ response.headers['Content-Type'] = 'text/event-stream'
+ %w{ hello world }.each do |word|
+ response.stream.write word
+ end
+ response.stream.close
+ end
+
+ def with_stale
+ render :text => 'stale' if stale?(:etag => "123")
+ end
+
+ def exception_in_view
+ render 'doesntexist'
+ end
+
+ def exception_in_view_after_commit
+ response.stream.write ""
+ render 'doesntexist'
+ end
+
+ def exception_with_callback
+ response.headers['Content-Type'] = 'text/event-stream'
+
+ response.stream.on_error do
+ response.stream.write %(data: "500 Internal Server Error"\n\n)
+ response.stream.close
+ end
+
+ response.stream.write "" # make sure the response is committed
+ raise 'An exception occurred...'
+ end
+
+ def exception_in_controller
+ raise Exception, 'Exception in controller'
+ end
+
+ def bad_request_error
+ raise ActionController::BadRequest
+ end
+
+ def exception_in_exception_callback
+ response.headers['Content-Type'] = 'text/event-stream'
+ response.stream.on_error do
+ raise 'We need to go deeper.'
+ end
+ response.stream.write ''
+ response.stream.write params[:widget][:didnt_check_for_nil]
+ end
+
+ def overfill_buffer_and_die
+ # Write until the buffer is full. It doesn't expose that
+ # information directly, so we must hard-code its size:
+ 10.times do
+ response.stream.write '.'
+ end
+ # .. plus one more, because the #each frees up a slot:
+ response.stream.write '.'
+
+ latch.release
+
+ # This write will block, and eventually raise
+ response.stream.write 'x'
+
+ 20.times do
+ response.stream.write '.'
+ end
+ end
+
+ def ignore_client_disconnect
+ response.stream.ignore_disconnect = true
+
+ response.stream.write '' # commit
+
+ # These writes will be ignored
+ 15.times do
+ response.stream.write 'x'
+ end
+
+ logger.info 'Work complete'
+ latch.release
+ end
+ end
+
+ tests TestController
+
+ def assert_stream_closed
+ assert response.stream.closed?, 'stream should be closed'
+ assert response.sent?, 'stream should be sent'
+ end
+
+ def capture_log_output
+ output = StringIO.new
+ old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output)
+
+ begin
+ yield output
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
+ def test_set_cookie
+ @controller = TestController.new
+ get :set_cookie
+ assert_equal({'hello' => 'world'}, @response.cookies)
+ assert_equal "hello world", @response.body
+ end
+
+ def test_set_response!
+ @controller.set_response!(@request)
+ assert_kind_of(Live::Response, @controller.response)
+ assert_equal @request, @controller.response.request
+ end
+
+ def test_write_to_stream
+ @controller = TestController.new
+ get :basic_stream
+ assert_equal "helloworld", @response.body
+ assert_equal 'text/event-stream', @response.headers['Content-Type']
+ end
+
+ def test_async_stream
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+ parts = ['hello', 'world']
+
+ @controller.request = @request
+ @controller.response = @response
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ resp.stream.each do |part|
+ assert_equal parts.shift, part
+ ol = @controller.latch
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+ ol.release
+ end
+ }
+
+ @controller.process :blocking_stream
+
+ assert t.join(3), 'timeout expired before the thread terminated'
+ end
+
+ def test_abort_with_full_buffer
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+
+ @request.parameters[:format] = 'plain'
+ @controller.request = @request
+ @controller.response = @response
+
+ got_error = ActiveSupport::Concurrency::Latch.new
+ @response.stream.on_error do
+ ActionController::Base.logger.warn 'Error while streaming'
+ got_error.release
+ end
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do |part|
+ @controller.latch.await
+ body.close
+ break
+ end
+ }
+
+ capture_log_output do |output|
+ @controller.process :overfill_buffer_and_die
+ t.join
+ got_error.await
+ assert_match 'Error while streaming', output.rewind && output.read
+ end
+ end
+
+ def test_ignore_client_disconnect
+ @controller.latch = ActiveSupport::Concurrency::Latch.new
+
+ @controller.request = @request
+ @controller.response = @response
+
+ t = Thread.new(@response) { |resp|
+ resp.await_commit
+ _, _, body = resp.to_a
+ body.each do |part|
+ body.close
+ break
+ end
+ }
+
+ capture_log_output do |output|
+ @controller.process :ignore_client_disconnect
+ t.join
+ Timeout.timeout(3) do
+ @controller.latch.await
+ end
+ assert_match 'Work complete', output.rewind && output.read
+ end
+ end
+
+ def test_thread_locals_get_copied
+ @controller.tc = self
+ Thread.current[:originating_thread] = Thread.current.object_id
+ Thread.current[:setting] = 'aaron'
+
+ get :thread_locals
+ end
+
+ def test_live_stream_default_header
+ @controller.request = @request
+ @controller.response = @response
+ @controller.process :default_header
+ _, headers, _ = @response.prepare!
+ assert headers['Content-Type']
+ end
+
+ def test_render_text
+ get :render_text
+ assert_equal 'zomg', response.body
+ assert_stream_closed
+ end
+
+ def test_exception_handling_html
+ assert_raises(ActionView::MissingTemplate) do
+ get :exception_in_view
+ end
+
+ capture_log_output do |output|
+ get :exception_in_view_after_commit
+ assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
+ assert_match 'Missing template test/doesntexist', output.rewind && output.read
+ assert_stream_closed
+ end
+ assert response.body
+ assert_stream_closed
+ end
+
+ def test_exception_handling_plain_text
+ assert_raises(ActionView::MissingTemplate) do
+ get :exception_in_view, format: :json
+ end
+
+ capture_log_output do |output|
+ get :exception_in_view_after_commit, format: :json
+ assert_equal '', response.body
+ assert_match 'Missing template test/doesntexist', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exception_callback_when_committed
+ capture_log_output do |output|
+ get :exception_with_callback, format: 'text/event-stream'
+ assert_equal %(data: "500 Internal Server Error"\n\n), response.body
+ assert_match 'An exception occurred...', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_exception_in_controller_before_streaming
+ assert_raises(ActionController::LiveStreamTest::Exception) do
+ get :exception_in_controller, format: 'text/event-stream'
+ end
+ end
+
+ def test_bad_request_in_controller_before_streaming
+ assert_raises(ActionController::BadRequest) do
+ get :bad_request_error, format: 'text/event-stream'
+ end
+ end
+
+ def test_exceptions_raised_handling_exceptions_and_committed
+ capture_log_output do |output|
+ get :exception_in_exception_callback, format: 'text/event-stream'
+ assert_equal '', response.body
+ assert_match 'We need to go deeper', output.rewind && output.read
+ assert_stream_closed
+ end
+ end
+
+ def test_stale_without_etag
+ get :with_stale
+ assert_equal 200, @response.status.to_i
+ end
+
+ def test_stale_with_etag
+ @request.if_none_match = Digest::MD5.hexdigest("123")
+ get :with_stale
+ assert_equal 304, @response.status.to_i
+ end
+ end
+
+ class BufferTest < ActionController::TestCase
+ def test_nil_callback
+ buf = ActionController::Live::Buffer.new nil
+ assert buf.call_on_error
+ end
+ end
+end
diff --git a/actionpack/test/controller/localized_templates_test.rb b/actionpack/test/controller/localized_templates_test.rb
new file mode 100644
index 0000000000..27871ef351
--- /dev/null
+++ b/actionpack/test/controller/localized_templates_test.rb
@@ -0,0 +1,46 @@
+require 'abstract_unit'
+
+class LocalizedController < ActionController::Base
+ def hello_world
+ end
+end
+
+class LocalizedTemplatesTest < ActionController::TestCase
+ tests LocalizedController
+
+ setup do
+ @old_locale = I18n.locale
+ end
+
+ teardown do
+ I18n.locale = @old_locale
+ end
+
+ def test_localized_template_is_used
+ I18n.locale = :de
+ get :hello_world
+ assert_equal "Gutten Tag", @response.body
+ end
+
+ def test_default_locale_template_is_used_when_locale_is_missing
+ I18n.locale = :dk
+ get :hello_world
+ assert_equal "Hello World", @response.body
+ end
+
+ def test_use_fallback_locales
+ I18n.locale = :"de-AT"
+ I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ I18n.fallbacks[:"de-AT"] = [:de]
+
+ get :hello_world
+ assert_equal "Gutten Tag", @response.body
+ end
+
+ def test_localized_template_has_correct_header_with_no_format_in_template_name
+ I18n.locale = :it
+ get :hello_world
+ assert_equal "Ciao Mondo", @response.body
+ assert_equal "text/html", @response.content_type
+ end
+end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
new file mode 100644
index 0000000000..49be7caf38
--- /dev/null
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -0,0 +1,325 @@
+require "abstract_unit"
+require "active_support/log_subscriber/test_helper"
+require "action_controller/log_subscriber"
+
+module Another
+ class LogSubscribersController < ActionController::Base
+ wrap_parameters :person, :include => :name, :format => :json
+
+ class SpecialException < Exception
+ end
+
+ rescue_from SpecialException do
+ head :status => 406
+ end
+
+ before_action :redirector, only: :never_executed
+
+ def never_executed
+ end
+
+ def show
+ render :nothing => true
+ end
+
+ def redirector
+ redirect_to "http://foo.bar/"
+ end
+
+ def filterable_redirector
+ redirect_to "http://secret.foo.bar/"
+ end
+
+ def data_sender
+ send_data "cool data", :filename => "file.txt"
+ end
+
+ def file_sender
+ send_file File.expand_path("company.rb", FIXTURE_LOAD_PATH)
+ end
+
+ def with_fragment_cache
+ render :inline => "<%= cache('foo'){ 'bar' } %>"
+ end
+
+ def with_fragment_cache_and_percent_in_key
+ render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>"
+ end
+
+ def with_fragment_cache_if_with_true_condition
+ render :inline => "<%= cache_if(true, 'foo') { 'bar' } %>"
+ end
+
+ def with_fragment_cache_if_with_false_condition
+ render :inline => "<%= cache_if(false, 'foo') { 'bar' } %>"
+ end
+
+ def with_fragment_cache_unless_with_false_condition
+ render :inline => "<%= cache_unless(false, 'foo') { 'bar' } %>"
+ end
+
+ def with_fragment_cache_unless_with_true_condition
+ render :inline => "<%= cache_unless(true, 'foo') { 'bar' } %>"
+ end
+
+ def with_exception
+ raise Exception
+ end
+
+ def with_rescued_exception
+ raise SpecialException
+ end
+
+ def with_action_not_found
+ raise AbstractController::ActionNotFound
+ end
+ end
+end
+
+class ACLogSubscriberTest < ActionController::TestCase
+ tests Another::LogSubscribersController
+ include ActiveSupport::LogSubscriber::TestHelper
+
+ def setup
+ super
+
+ @old_logger = ActionController::Base.logger
+
+ @cache_path = File.join Dir.tmpdir, Dir::Tmpname.make_tmpname('tmp', 'cache')
+ @controller.cache_store = :file_store, @cache_path
+ ActionController::LogSubscriber.attach_to :action_controller
+ end
+
+ def teardown
+ super
+ ActiveSupport::LogSubscriber.log_subscribers.clear
+ FileUtils.rm_rf(@cache_path)
+ ActionController::Base.logger = @old_logger
+ end
+
+ def set_logger(logger)
+ ActionController::Base.logger = logger
+ end
+
+ def test_start_processing
+ get :show
+ wait
+ assert_equal 2, logs.size
+ assert_equal "Processing by Another::LogSubscribersController#show as HTML", logs.first
+ end
+
+ def test_halted_callback
+ get :never_executed
+ wait
+ assert_equal 4, logs.size
+ assert_equal "Filter chain halted as :redirector rendered or redirected", logs.third
+ end
+
+ def test_process_action
+ get :show
+ wait
+ assert_equal 2, logs.size
+ assert_match(/Completed/, logs.last)
+ assert_match(/200 OK/, logs.last)
+ end
+
+ def test_process_action_without_parameters
+ get :show
+ wait
+ assert_nil logs.detect {|l| l =~ /Parameters/ }
+ end
+
+ def test_process_action_with_parameters
+ get :show, :id => '10'
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal 'Parameters: {"id"=>"10"}', logs[1]
+ end
+
+ def test_multiple_process_with_parameters
+ get :show, :id => '10'
+ get :show, :id => '20'
+
+ wait
+
+ assert_equal 6, logs.size
+ assert_equal 'Parameters: {"id"=>"10"}', logs[1]
+ assert_equal 'Parameters: {"id"=>"20"}', logs[4]
+ end
+
+ def test_process_action_with_wrapped_parameters
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :show, :id => '10', :name => 'jose'
+ wait
+
+ assert_equal 3, logs.size
+ assert_match '"person"=>{"name"=>"jose"}', logs[1]
+ end
+
+ def test_process_action_with_view_runtime
+ get :show
+ wait
+ assert_match(/\(Views: [\d.]+ms\)/, logs[1])
+ end
+
+ def test_process_action_with_filter_parameters
+ @request.env["action_dispatch.parameter_filter"] = [:lifo, :amount]
+
+ get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
+ wait
+
+ params = logs[1]
+ assert_match(/"amount"=>"\[FILTERED\]"/, params)
+ assert_match(/"lifo"=>"\[FILTERED\]"/, params)
+ assert_match(/"step"=>"1"/, params)
+ end
+
+ def test_redirect_to
+ get :redirector
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal "Redirected to http://foo.bar/", logs[1]
+ end
+
+ def test_filter_redirect_url_by_string
+ @request.env['action_dispatch.redirect_filter'] = ['secret']
+ get :filterable_redirector
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal "Redirected to [FILTERED]", logs[1]
+ end
+
+ def test_filter_redirect_url_by_regexp
+ @request.env['action_dispatch.redirect_filter'] = [/secret\.foo.+/]
+ get :filterable_redirector
+ wait
+
+ assert_equal 3, logs.size
+ assert_equal "Redirected to [FILTERED]", logs[1]
+ end
+
+ def test_send_data
+ get :data_sender
+ wait
+
+ assert_equal 3, logs.size
+ assert_match(/Sent data file\.txt/, logs[1])
+ end
+
+ def test_send_file
+ get :file_sender
+ wait
+
+ assert_equal 3, logs.size
+ assert_match(/Sent file/, logs[1])
+ assert_match(/test\/fixtures\/company\.rb/, logs[1])
+ end
+
+ def test_with_fragment_cache
+ @controller.config.perform_caching = true
+ get :with_fragment_cache
+ wait
+
+ assert_equal 4, logs.size
+ assert_match(/Read fragment views\/foo/, logs[1])
+ assert_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_if_with_true
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_if_with_true_condition
+ wait
+
+ assert_equal 4, logs.size
+ assert_match(/Read fragment views\/foo/, logs[1])
+ assert_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_if_with_false
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_if_with_false_condition
+ wait
+
+ assert_equal 2, logs.size
+ assert_no_match(/Read fragment views\/foo/, logs[1])
+ assert_no_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_unless_with_true
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_unless_with_true_condition
+ wait
+
+ assert_equal 2, logs.size
+ assert_no_match(/Read fragment views\/foo/, logs[1])
+ assert_no_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_unless_with_false
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_unless_with_false_condition
+ wait
+
+ assert_equal 4, logs.size
+ assert_match(/Read fragment views\/foo/, logs[1])
+ assert_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_with_fragment_cache_and_percent_in_key
+ @controller.config.perform_caching = true
+ get :with_fragment_cache_and_percent_in_key
+ wait
+
+ assert_equal 4, logs.size
+ assert_match(/Read fragment views\/foo/, logs[1])
+ assert_match(/Write fragment views\/foo/, logs[2])
+ ensure
+ @controller.config.perform_caching = true
+ end
+
+ def test_process_action_with_exception_includes_http_status_code
+ begin
+ get :with_exception
+ wait
+ rescue Exception
+ end
+ assert_equal 2, logs.size
+ assert_match(/Completed 500/, logs.last)
+ end
+
+ def test_process_action_with_rescued_exception_includes_http_status_code
+ get :with_rescued_exception
+ wait
+
+ assert_equal 2, logs.size
+ assert_match(/Completed 406/, logs.last)
+ end
+
+ def test_process_action_with_with_action_not_found_logs_404
+ begin
+ get :with_action_not_found
+ wait
+ rescue AbstractController::ActionNotFound
+ end
+
+ assert_equal 2, logs.size
+ assert_match(/Completed 404/, logs.last)
+ end
+
+ def logs
+ @logs ||= @logger.logged(:info)
+ end
+end
diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb
new file mode 100644
index 0000000000..811c507af2
--- /dev/null
+++ b/actionpack/test/controller/mime/accept_format_test.rb
@@ -0,0 +1,92 @@
+require 'abstract_unit'
+
+class StarStarMimeController < ActionController::Base
+ layout nil
+
+ def index
+ render
+ end
+end
+
+class StarStarMimeControllerTest < ActionController::TestCase
+ def test_javascript_with_format
+ @request.accept = "text/javascript"
+ get :index, :format => 'js'
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+ def test_javascript_with_no_format
+ @request.accept = "text/javascript"
+ get :index
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+
+ def test_javascript_with_no_format_only_star_star
+ @request.accept = "*/*"
+ get :index
+ assert_match "function addition(a,b){ return a+b; }", @response.body
+ end
+end
+
+class AbstractPostController < ActionController::Base
+ self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/"
+end
+
+# For testing layouts which are set automatically
+class PostController < AbstractPostController
+ around_action :with_iphone
+
+ def index
+ respond_to(:html, :iphone, :js)
+ end
+
+protected
+
+ def with_iphone
+ request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
+ yield
+ end
+end
+
+class SuperPostController < PostController
+end
+
+class MimeControllerLayoutsTest < ActionController::TestCase
+ tests PostController
+
+ def setup
+ super
+ @request.host = "www.example.com"
+ Mime::Type.register_alias("text/html", :iphone)
+ end
+
+ def teardown
+ super
+ Mime::Type.unregister(:iphone)
+ end
+
+ def test_missing_layout_renders_properly
+ get :index
+ assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
+
+ @request.accept = "text/iphone"
+ get :index
+ assert_equal 'Hello iPhone', @response.body
+ end
+
+ def test_format_with_inherited_layouts
+ @controller = SuperPostController.new
+
+ get :index
+ assert_equal '<html><div id="html">Super Firefox</div></html>', @response.body
+
+ @request.accept = "text/iphone"
+ get :index
+ assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
+ end
+
+ def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout
+ get :index, :format => :js
+ assert_equal "Hello Firefox", @response.body
+ end
+end
diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb
new file mode 100644
index 0000000000..1bc7ad3015
--- /dev/null
+++ b/actionpack/test/controller/mime/respond_to_test.rb
@@ -0,0 +1,784 @@
+require 'abstract_unit'
+
+class RespondToController < ActionController::Base
+ layout :set_layout
+
+ def html_xml_or_rss
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ type.rss { render :text => "RSS" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def js_or_html
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.js { render :text => "JS" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def json_or_yaml
+ respond_to do |type|
+ type.json { render :text => "JSON" }
+ type.yaml { render :text => "YAML" }
+ end
+ end
+
+ def html_or_xml
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def json_xml_or_html
+ respond_to do |type|
+ type.json { render :text => 'JSON' }
+ type.xml { render :xml => 'XML' }
+ type.html { render :text => 'HTML' }
+ end
+ end
+
+
+ def forced_xml
+ request.format = :xml
+
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.xml { render :text => "XML" }
+ end
+ end
+
+ def just_xml
+ respond_to do |type|
+ type.xml { render :text => "XML" }
+ end
+ end
+
+ def using_defaults
+ respond_to do |type|
+ type.html
+ type.xml
+ end
+ end
+
+ def using_defaults_with_type_list
+ respond_to(:html, :xml)
+ end
+
+ def using_defaults_with_all
+ respond_to do |type|
+ type.html
+ type.all{ render text: "ALL" }
+ end
+ end
+
+ def made_for_content_type
+ respond_to do |type|
+ type.rss { render :text => "RSS" }
+ type.atom { render :text => "ATOM" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+ def custom_type_handling
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.custom("application/crazy-xml") { render :text => "Crazy XML" }
+ type.all { render :text => "Nothing" }
+ end
+ end
+
+
+ def custom_constant_handling
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.mobile { render :text => "Mobile" }
+ end
+ end
+
+ def custom_constant_handling_without_block
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.mobile
+ end
+ end
+
+ def handle_any
+ respond_to do |type|
+ type.html { render :text => "HTML" }
+ type.any(:js, :xml) { render :text => "Either JS or XML" }
+ end
+ end
+
+ def handle_any_any
+ respond_to do |type|
+ type.html { render :text => 'HTML' }
+ type.any { render :text => 'Whatever you ask for, I got it' }
+ end
+ end
+
+ def all_types_with_layout
+ respond_to do |type|
+ type.html
+ end
+ end
+
+ def json_with_callback
+ respond_to do |type|
+ type.json { render :json => 'JS', :callback => 'alert' }
+ end
+ end
+
+ def iphone_with_html_response_type
+ request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
+
+ respond_to do |type|
+ type.html { @type = "Firefox" }
+ type.iphone { @type = "iPhone" }
+ end
+ end
+
+ def iphone_with_html_response_type_without_layout
+ request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
+
+ respond_to do |type|
+ type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" }
+ type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" }
+ end
+ end
+
+ def variant_with_implicit_rendering
+ end
+
+ def variant_with_format_and_custom_render
+ request.variant = :mobile
+
+ respond_to do |type|
+ type.html { render text: "mobile" }
+ end
+ end
+
+ def multiple_variants_for_format
+ respond_to do |type|
+ type.html do |html|
+ html.tablet { render text: "tablet" }
+ html.phone { render text: "phone" }
+ end
+ end
+ end
+
+ def variant_plus_none_for_format
+ respond_to do |format|
+ format.html do |variant|
+ variant.phone { render text: "phone" }
+ variant.none
+ end
+ end
+ end
+
+ def variant_inline_syntax
+ respond_to do |format|
+ format.js { render text: "js" }
+ format.html.none { render text: "none" }
+ format.html.phone { render text: "phone" }
+ end
+ end
+
+ def variant_inline_syntax_without_block
+ respond_to do |format|
+ format.js
+ format.html.none
+ format.html.phone
+ end
+ end
+
+ def variant_any
+ respond_to do |format|
+ format.html do |variant|
+ variant.any(:tablet, :phablet){ render text: "any" }
+ variant.phone { render text: "phone" }
+ end
+ end
+ end
+
+ def variant_any_any
+ respond_to do |format|
+ format.html do |variant|
+ variant.any { render text: "any" }
+ variant.phone { render text: "phone" }
+ end
+ end
+ end
+
+ def variant_inline_any
+ respond_to do |format|
+ format.html.any(:tablet, :phablet){ render text: "any" }
+ format.html.phone { render text: "phone" }
+ end
+ end
+
+ def variant_inline_any_any
+ respond_to do |format|
+ format.html.phone { render text: "phone" }
+ format.html.any { render text: "any" }
+ end
+ end
+
+ def variant_any_implicit_render
+ respond_to do |format|
+ format.html.phone
+ format.html.any(:tablet, :phablet)
+ end
+ end
+
+ def variant_any_with_none
+ respond_to do |format|
+ format.html.any(:none, :phone){ render text: "none or phone" }
+ end
+ end
+
+ def format_any_variant_any
+ respond_to do |format|
+ format.html { render text: "HTML" }
+ format.any(:js, :xml) do |variant|
+ variant.phone{ render text: "phone" }
+ variant.any(:tablet, :phablet){ render text: "tablet" }
+ end
+ end
+ end
+
+ protected
+ def set_layout
+ case action_name
+ when "all_types_with_layout", "iphone_with_html_response_type"
+ "respond_to/layouts/standard"
+ when "iphone_with_html_response_type_without_layout"
+ "respond_to/layouts/missing"
+ end
+ end
+end
+
+class RespondToControllerTest < ActionController::TestCase
+ def setup
+ super
+ @request.host = "www.example.com"
+ Mime::Type.register_alias("text/html", :iphone)
+ Mime::Type.register("text/x-mobile", :mobile)
+ end
+
+ def teardown
+ super
+ Mime::Type.unregister(:iphone)
+ Mime::Type.unregister(:mobile)
+ end
+
+ def test_html
+ @request.accept = "text/html"
+ get :js_or_html
+ assert_equal 'HTML', @response.body
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ assert_raises(ActionController::UnknownFormat) do
+ get :just_xml
+ end
+ end
+
+ def test_all
+ @request.accept = "*/*"
+ get :js_or_html
+ assert_equal 'HTML', @response.body # js is not part of all
+
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ get :just_xml
+ assert_equal 'XML', @response.body
+ end
+
+ def test_xml
+ @request.accept = "application/xml"
+ get :html_xml_or_rss
+ assert_equal 'XML', @response.body
+ end
+
+ def test_js_or_html
+ @request.accept = "text/javascript, text/html"
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+
+ @request.accept = "text/javascript, text/html"
+ xhr :get, :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "text/javascript, text/html"
+
+ assert_raises(ActionController::UnknownFormat) do
+ xhr :get, :just_xml
+ end
+ end
+
+ def test_json_or_yaml_with_leading_star_star
+ @request.accept = "*/*, application/json"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "*/* , application/json"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_json_or_yaml
+ xhr :get, :json_or_yaml
+ assert_equal 'JSON', @response.body
+
+ get :json_or_yaml, :format => 'json'
+ assert_equal 'JSON', @response.body
+
+ get :json_or_yaml, :format => 'yaml'
+ assert_equal 'YAML', @response.body
+
+ { 'YAML' => %w(text/yaml),
+ 'JSON' => %w(application/json text/x-json)
+ }.each do |body, content_types|
+ content_types.each do |content_type|
+ @request.accept = content_type
+ get :json_or_yaml
+ assert_equal body, @response.body
+ end
+ end
+ end
+
+ def test_js_or_anything
+ @request.accept = "text/javascript, */*"
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+
+ xhr :get, :html_or_xml
+ assert_equal 'HTML', @response.body
+
+ xhr :get, :just_xml
+ assert_equal 'XML', @response.body
+ end
+
+ def test_using_defaults
+ @request.accept = "*/*"
+ get :using_defaults
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.accept = "application/xml"
+ get :using_defaults
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<p>Hello world!</p>\n", @response.body
+ end
+
+ def test_using_defaults_with_all
+ @request.accept = "*/*"
+ get :using_defaults_with_all
+ assert_equal "HTML!", @response.body.strip
+
+ @request.accept = "text/html"
+ get :using_defaults_with_all
+ assert_equal "HTML!", @response.body.strip
+
+ @request.accept = "application/json"
+ get :using_defaults_with_all
+ assert_equal "ALL", @response.body
+ end
+
+ def test_using_defaults_with_type_list
+ @request.accept = "*/*"
+ get :using_defaults_with_type_list
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.accept = "application/xml"
+ get :using_defaults_with_type_list
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<p>Hello world!</p>\n", @response.body
+ end
+
+ def test_with_atom_content_type
+ @request.accept = ""
+ @request.env["CONTENT_TYPE"] = "application/atom+xml"
+ xhr :get, :made_for_content_type
+ assert_equal "ATOM", @response.body
+ end
+
+ def test_with_rss_content_type
+ @request.accept = ""
+ @request.env["CONTENT_TYPE"] = "application/rss+xml"
+ xhr :get, :made_for_content_type
+ assert_equal "RSS", @response.body
+ end
+
+ def test_synonyms
+ @request.accept = "application/javascript"
+ get :js_or_html
+ assert_equal 'JS', @response.body
+
+ @request.accept = "application/x-xml"
+ get :html_xml_or_rss
+ assert_equal "XML", @response.body
+ end
+
+ def test_custom_types
+ @request.accept = "application/crazy-xml"
+ get :custom_type_handling
+ assert_equal "application/crazy-xml", @response.content_type
+ assert_equal 'Crazy XML', @response.body
+
+ @request.accept = "text/html"
+ get :custom_type_handling
+ assert_equal "text/html", @response.content_type
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_xhtml_alias
+ @request.accept = "application/xhtml+xml,application/xml"
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_firefox_simulation
+ @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ get :html_or_xml
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any
+ @request.accept = "*/*"
+ get :handle_any
+ assert_equal 'HTML', @response.body
+
+ @request.accept = "text/javascript"
+ get :handle_any
+ assert_equal 'Either JS or XML', @response.body
+
+ @request.accept = "text/xml"
+ get :handle_any
+ assert_equal 'Either JS or XML', @response.body
+ end
+
+ def test_handle_any_any
+ @request.accept = "*/*"
+ get :handle_any_any
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any_any_parameter_format
+ get :handle_any_any, {:format=>'html'}
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any_any_explicit_html
+ @request.accept = "text/html"
+ get :handle_any_any
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_handle_any_any_javascript
+ @request.accept = "text/javascript"
+ get :handle_any_any
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
+ def test_handle_any_any_xml
+ @request.accept = "text/xml"
+ get :handle_any_any
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
+ def test_handle_any_any_unkown_format
+ get :handle_any_any, { format: 'php' }
+ assert_equal 'Whatever you ask for, I got it', @response.body
+ end
+
+ def test_browser_check_with_any_any
+ @request.accept = "application/json, application/xml"
+ get :json_xml_or_html
+ assert_equal 'JSON', @response.body
+
+ @request.accept = "application/json, application/xml, */*"
+ get :json_xml_or_html
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_html_type_with_layout
+ @request.accept = "text/html"
+ get :all_types_with_layout
+ assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body
+ end
+
+ def test_json_with_callback_sets_javascript_content_type
+ @request.accept = 'application/json'
+ get :json_with_callback
+ assert_equal '/**/alert(JS)', @response.body
+ assert_equal 'text/javascript', @response.content_type
+ end
+
+ def test_xhr
+ xhr :get, :js_or_html
+ assert_equal 'JS', @response.body
+ end
+
+ def test_custom_constant
+ get :custom_constant_handling, :format => "mobile"
+ assert_equal "text/x-mobile", @response.content_type
+ assert_equal "Mobile", @response.body
+ end
+
+ def test_custom_constant_handling_without_block
+ get :custom_constant_handling_without_block, :format => "mobile"
+ assert_equal "text/x-mobile", @response.content_type
+ assert_equal "Mobile", @response.body
+ end
+
+ def test_forced_format
+ get :html_xml_or_rss
+ assert_equal "HTML", @response.body
+
+ get :html_xml_or_rss, :format => "html"
+ assert_equal "HTML", @response.body
+
+ get :html_xml_or_rss, :format => "xml"
+ assert_equal "XML", @response.body
+
+ get :html_xml_or_rss, :format => "rss"
+ assert_equal "RSS", @response.body
+ end
+
+ def test_internally_forced_format
+ get :forced_xml
+ assert_equal "XML", @response.body
+
+ get :forced_xml, :format => "html"
+ assert_equal "XML", @response.body
+ end
+
+ def test_extension_synonyms
+ get :html_xml_or_rss, :format => "xhtml"
+ assert_equal "HTML", @response.body
+ end
+
+ def test_render_action_for_html
+ @controller.instance_eval do
+ def render(*args)
+ @action = args.first[:action] unless args.empty?
+ @action ||= action_name
+
+ response.body = "#{@action} - #{formats}"
+ end
+ end
+
+ get :using_defaults
+ assert_equal "using_defaults - #{[:html].to_s}", @response.body
+
+ get :using_defaults, :format => "xml"
+ assert_equal "using_defaults - #{[:xml].to_s}", @response.body
+ end
+
+ def test_format_with_custom_response_type
+ get :iphone_with_html_response_type
+ assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
+
+ get :iphone_with_html_response_type, :format => "iphone"
+ assert_equal "text/html", @response.content_type
+ assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
+ end
+
+ def test_format_with_custom_response_type_and_request_headers
+ @request.accept = "text/iphone"
+ get :iphone_with_html_response_type
+ assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_invalid_format
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_defaults, :format => "invalidformat"
+ end
+ end
+
+ def test_invalid_variant
+ @request.variant = :invalid
+ assert_raises(ActionView::MissingTemplate) do
+ get :variant_with_implicit_rendering
+ end
+ end
+
+ def test_variant_not_set_regular_template_missing
+ assert_raises(ActionView::MissingTemplate) do
+ get :variant_with_implicit_rendering
+ end
+ end
+
+ def test_variant_with_implicit_rendering
+ @request.variant = :mobile
+ get :variant_with_implicit_rendering
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_variant_with_format_and_custom_render
+ @request.variant = :phone
+ get :variant_with_format_and_custom_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "mobile", @response.body
+ end
+
+ def test_multiple_variants_for_format
+ @request.variant = :tablet
+ get :multiple_variants_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "tablet", @response.body
+ end
+
+ def test_no_variant_in_variant_setup
+ get :variant_plus_none_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "none", @response.body
+ end
+
+ def test_variant_inline_syntax
+ get :variant_inline_syntax, format: :js
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "js", @response.body
+
+ get :variant_inline_syntax
+ assert_equal "text/html", @response.content_type
+ assert_equal "none", @response.body
+
+ @request.variant = :phone
+ get :variant_inline_syntax
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_inline_syntax_without_block
+ @request.variant = :phone
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_any
+ @request.variant = :phone
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :tablet
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
+ @request.variant = :phablet
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_any_any
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
+ @request.variant = :phone
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :yolo
+ get :variant_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_inline_any
+ @request.variant = :phone
+ get :variant_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :tablet
+ get :variant_inline_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+
+ @request.variant = :phablet
+ get :variant_inline_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_inline_any_any
+ @request.variant = :phone
+ get :variant_inline_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+
+ @request.variant = :yolo
+ get :variant_inline_any_any
+ assert_equal "text/html", @response.content_type
+ assert_equal "any", @response.body
+ end
+
+ def test_variant_any_implicit_render
+ @request.variant = :tablet
+ get :variant_any_implicit_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "tablet", @response.body
+
+ @request.variant = :phablet
+ get :variant_any_implicit_render
+ assert_equal "text/html", @response.content_type
+ assert_equal "phablet", @response.body
+ end
+
+ def test_variant_any_with_none
+ get :variant_any_with_none
+ assert_equal "text/html", @response.content_type
+ assert_equal "none or phone", @response.body
+
+ @request.variant = :phone
+ get :variant_any_with_none
+ assert_equal "text/html", @response.content_type
+ assert_equal "none or phone", @response.body
+ end
+
+ def test_format_any_variant_any
+ @request.variant = :tablet
+ get :format_any_variant_any, format: :js
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "tablet", @response.body
+ end
+
+ def test_variant_negotiation_inline_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_block_syntax
+ @request.variant = [:tablet, :phone]
+ get :variant_plus_none_for_format
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+
+ def test_variant_negotiation_without_block
+ @request.variant = [:tablet, :phone]
+ get :variant_inline_syntax_without_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "phone", @response.body
+ end
+end
diff --git a/actionpack/test/controller/mime/respond_with_test.rb b/actionpack/test/controller/mime/respond_with_test.rb
new file mode 100644
index 0000000000..115f3b2f41
--- /dev/null
+++ b/actionpack/test/controller/mime/respond_with_test.rb
@@ -0,0 +1,737 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+
+class RespondWithController < ActionController::Base
+ class CustomerWithJson < Customer
+ def to_json; super; end
+ end
+
+ respond_to :html, :json, :touch
+ respond_to :xml, :except => :using_resource_with_block
+ respond_to :js, :only => [ :using_resource_with_block, :using_resource, 'using_hash_resource' ]
+
+ def using_resource
+ respond_with(resource)
+ end
+
+ def using_hash_resource
+ respond_with({:result => resource})
+ end
+
+ def using_resource_with_block
+ respond_with(resource) do |format|
+ format.csv { render :text => "CSV" }
+ end
+ end
+
+ def using_resource_with_overwrite_block
+ respond_with(resource) do |format|
+ format.html { render :text => "HTML" }
+ end
+ end
+
+ def using_resource_with_collection
+ respond_with([resource, Customer.new("jamis", 9)])
+ end
+
+ def using_resource_with_parent
+ respond_with(Quiz::Store.new("developer?", 11), Customer.new("david", 13))
+ end
+
+ def using_resource_with_status_and_location
+ respond_with(resource, :location => "http://test.host/", :status => :created)
+ end
+
+ def using_resource_with_json
+ respond_with(CustomerWithJson.new("david", request.delete? ? nil : 13))
+ end
+
+ def using_invalid_resource_with_template
+ respond_with(resource)
+ end
+
+ def using_options_with_template
+ @customer = resource
+ respond_with(@customer, :status => 123, :location => "http://test.host/")
+ end
+
+ def using_resource_with_responder
+ responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
+ respond_with(resource, :responder => responder)
+ end
+
+ def using_resource_with_action
+ respond_with(resource, :action => :foo) do |format|
+ format.html { raise ActionView::MissingTemplate.new([], "bar", ["foo"], {}, false) }
+ end
+ end
+
+ def using_responder_with_respond
+ responder = Class.new(ActionController::Responder) do
+ def respond; @controller.render :text => "respond #{format}"; end
+ end
+ respond_with(resource, :responder => responder)
+ end
+
+ def respond_with_additional_params
+ @params = RespondWithController.params
+ respond_with({:result => resource}, @params)
+ end
+
+protected
+ def self.params
+ {
+ :foo => 'bar'
+ }
+ end
+
+ def resource
+ Customer.new("david", request.delete? ? nil : 13)
+ end
+end
+
+class InheritedRespondWithController < RespondWithController
+ clear_respond_to
+ respond_to :xml, :json
+
+ def index
+ respond_with(resource) do |format|
+ format.json { render :text => "JSON" }
+ end
+ end
+end
+
+class RenderJsonRespondWithController < RespondWithController
+ clear_respond_to
+ respond_to :json
+
+ def index
+ respond_with(resource) do |format|
+ format.json { render :json => RenderJsonTestException.new('boom') }
+ end
+ end
+
+ def create
+ resource = ValidatedCustomer.new(params[:name], 1)
+ respond_with(resource) do |format|
+ format.json do
+ if resource.errors.empty?
+ render :json => { :valid => true }
+ else
+ render :json => { :valid => false }
+ end
+ end
+ end
+ end
+end
+
+class CsvRespondWithController < ActionController::Base
+ respond_to :csv
+
+ class RespondWithCsv
+ def to_csv
+ "c,s,v"
+ end
+ end
+
+ def index
+ respond_with(RespondWithCsv.new)
+ end
+end
+
+class EmptyRespondWithController < ActionController::Base
+ def index
+ respond_with(Customer.new("david", 13))
+ end
+end
+
+class RespondWithControllerTest < ActionController::TestCase
+ def setup
+ super
+ @request.host = "www.example.com"
+ Mime::Type.register_alias('text/html', :iphone)
+ Mime::Type.register_alias('text/html', :touch)
+ Mime::Type.register('text/x-mobile', :mobile)
+ end
+
+ def teardown
+ super
+ Mime::Type.unregister(:iphone)
+ Mime::Type.unregister(:touch)
+ Mime::Type.unregister(:mobile)
+ end
+
+ def test_respond_with_shouldnt_modify_original_hash
+ get :respond_with_additional_params
+ assert_equal RespondWithController.params, assigns(:params)
+ end
+
+ def test_using_resource
+ @request.accept = "application/xml"
+ get :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<name>david</name>", @response.body
+
+ @request.accept = "application/json"
+ assert_raise ActionView::MissingTemplate do
+ get :using_resource
+ end
+ end
+
+ def test_using_resource_with_js_simply_tries_to_render_the_template
+ @request.accept = "text/javascript"
+ get :using_resource
+ assert_equal "text/javascript", @response.content_type
+ assert_equal "alert(\"Hi\");", @response.body
+ end
+
+ def test_using_hash_resource_with_js_raises_an_error_if_template_cant_be_found
+ @request.accept = "text/javascript"
+ assert_raise ActionView::MissingTemplate do
+ get :using_hash_resource
+ end
+ end
+
+ def test_using_hash_resource
+ @request.accept = "application/xml"
+ get :using_hash_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hash>\n <name>david</name>\n</hash>\n", @response.body
+
+ @request.accept = "application/json"
+ get :using_hash_resource
+ assert_equal "application/json", @response.content_type
+ assert @response.body.include?("result")
+ assert @response.body.include?('"name":"david"')
+ assert @response.body.include?('"id":13')
+ end
+
+ def test_using_hash_resource_with_post
+ @request.accept = "application/json"
+ assert_raise ArgumentError, "Nil location provided. Can't build URI." do
+ post :using_hash_resource
+ end
+ end
+
+ def test_using_resource_with_block
+ @request.accept = "*/*"
+ get :using_resource_with_block
+ assert_equal "text/html", @response.content_type
+ assert_equal 'Hello world!', @response.body
+
+ @request.accept = "text/csv"
+ get :using_resource_with_block
+ assert_equal "text/csv", @response.content_type
+ assert_equal "CSV", @response.body
+
+ @request.accept = "application/xml"
+ get :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<name>david</name>", @response.body
+ end
+
+ def test_using_resource_with_overwrite_block
+ get :using_resource_with_overwrite_block
+ assert_equal "text/html", @response.content_type
+ assert_equal "HTML", @response.body
+ end
+
+ def test_not_acceptable
+ @request.accept = "application/xml"
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_block
+ end
+
+ @request.accept = "text/javascript"
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_overwrite_block
+ end
+ end
+
+ def test_using_resource_for_post_with_html_redirects_on_success
+ with_test_route_set do
+ post :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+ end
+ end
+
+ def test_using_resource_for_post_with_html_rerender_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "New world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_post_with_xml_yields_created_on_success
+ with_test_route_set do
+ @request.accept = "application/xml"
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 201, @response.status
+ assert_equal "<name>david</name>", @response.body
+ assert_equal "http://www.example.com/customers/13", @response.location
+ end
+ end
+
+ def test_using_resource_for_post_with_xml_yields_unprocessable_entity_on_failure
+ with_test_route_set do
+ @request.accept = "application/xml"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_post_with_json_yields_unprocessable_entity_on_failure
+ with_test_route_set do
+ @request.accept = "application/json"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/json", @response.content_type
+ assert_equal 422, @response.status
+ errors = {:errors => errors}
+ assert_equal errors.to_json, @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_redirects_on_success
+ with_test_route_set do
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ @request.env["rack.methodoverride.original_method"] = "POST"
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_put_with_html_redirects_on_success
+ with_test_route_set do
+ put :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+ end
+ end
+
+ def test_using_resource_for_put_with_html_rerender_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_put_with_html_rerender_on_failure_even_on_method_override
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ @request.env["rack.methodoverride.original_method"] = "POST"
+ put :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_put_with_xml_yields_no_content_on_success
+ @request.accept = "application/xml"
+ put :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 204, @response.status
+ assert_equal "", @response.body
+ end
+
+ def test_using_resource_for_put_with_json_yields_no_content_on_success
+ @request.accept = "application/json"
+ put :using_resource_with_json
+ assert_equal "application/json", @response.content_type
+ assert_equal 204, @response.status
+ assert_equal "", @response.body
+ end
+
+ def test_using_resource_for_put_with_xml_yields_unprocessable_entity_on_failure
+ @request.accept = "application/xml"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_put_with_json_yields_unprocessable_entity_on_failure
+ @request.accept = "application/json"
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+ assert_equal "application/json", @response.content_type
+ assert_equal 422, @response.status
+ errors = {:errors => errors}
+ assert_equal errors.to_json, @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_delete_with_html_redirects_on_success
+ with_test_route_set do
+ Customer.any_instance.stubs(:destroyed?).returns(true)
+ delete :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers", @response.location
+ end
+ end
+
+ def test_using_resource_for_delete_with_xml_yields_no_content_on_success
+ Customer.any_instance.stubs(:destroyed?).returns(true)
+ @request.accept = "application/xml"
+ delete :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 204, @response.status
+ assert_equal "", @response.body
+ end
+
+ def test_using_resource_for_delete_with_json_yields_no_content_on_success
+ Customer.any_instance.stubs(:destroyed?).returns(true)
+ @request.accept = "application/json"
+ delete :using_resource_with_json
+ assert_equal "application/json", @response.content_type
+ assert_equal 204, @response.status
+ assert_equal "", @response.body
+ end
+
+ def test_using_resource_for_delete_with_html_redirects_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ Customer.any_instance.stubs(:destroyed?).returns(false)
+ delete :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers", @response.location
+ end
+ end
+
+ def test_using_resource_with_parent_for_get
+ @request.accept = "application/xml"
+ get :using_resource_with_parent
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "<name>david</name>", @response.body
+ end
+
+ def test_using_resource_with_parent_for_post
+ with_test_route_set do
+ @request.accept = "application/xml"
+
+ post :using_resource_with_parent
+ assert_equal "application/xml", @response.content_type
+ assert_equal 201, @response.status
+ assert_equal "<name>david</name>", @response.body
+ assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_with_collection
+ @request.accept = "application/xml"
+ get :using_resource_with_collection
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_match(/<name>david<\/name>/, @response.body)
+ assert_match(/<name>jamis<\/name>/, @response.body)
+ end
+
+ def test_using_resource_with_action
+ @controller.instance_eval do
+ def render(params={})
+ self.response_body = "#{params[:action]} - #{formats}"
+ end
+ end
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+
+ post :using_resource_with_action
+ assert_equal "foo - #{[:html].to_s}", @controller.response.body
+ end
+
+ def test_respond_as_responder_entry_point
+ @request.accept = "text/html"
+ get :using_responder_with_respond
+ assert_equal "respond html", @response.body
+
+ @request.accept = "application/xml"
+ get :using_responder_with_respond
+ assert_equal "respond xml", @response.body
+ end
+
+ def test_clear_respond_to
+ @controller = InheritedRespondWithController.new
+ @request.accept = "text/html"
+ assert_raises(ActionController::UnknownFormat) do
+ get :index
+ end
+ end
+
+ def test_first_in_respond_to_has_higher_priority
+ @controller = InheritedRespondWithController.new
+ @request.accept = "*/*"
+ get :index
+ assert_equal "application/xml", @response.content_type
+ assert_equal "<name>david</name>", @response.body
+ end
+
+ def test_block_inside_respond_with_is_rendered
+ @controller = InheritedRespondWithController.new
+ @request.accept = "application/json"
+ get :index
+ assert_equal "JSON", @response.body
+ end
+
+ def test_render_json_object_responds_to_str_still_produce_json
+ @controller = RenderJsonRespondWithController.new
+ @request.accept = "application/json"
+ get :index, :format => :json
+ assert_match(/"message":"boom"/, @response.body)
+ assert_match(/"error":"RenderJsonTestException"/, @response.body)
+ end
+
+ def test_api_response_with_valid_resource_respect_override_block
+ @controller = RenderJsonRespondWithController.new
+ post :create, :name => "sikachu", :format => :json
+ assert_equal '{"valid":true}', @response.body
+ end
+
+ def test_api_response_with_invalid_resource_respect_override_block
+ @controller = RenderJsonRespondWithController.new
+ post :create, :name => "david", :format => :json
+ assert_equal '{"valid":false}', @response.body
+ end
+
+ def test_no_double_render_is_raised
+ @request.accept = "text/html"
+ assert_raise ActionView::MissingTemplate do
+ get :using_resource
+ end
+ end
+
+ def test_using_resource_with_status_and_location
+ @request.accept = "text/html"
+ post :using_resource_with_status_and_location
+ assert @response.redirect?
+ assert_equal "http://test.host/", @response.location
+
+ @request.accept = "application/xml"
+ get :using_resource_with_status_and_location
+ assert_equal 201, @response.status
+ end
+
+ def test_using_resource_with_status_and_location_with_invalid_resource
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+
+ @request.accept = "text/xml"
+
+ post :using_resource_with_status_and_location
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+
+ put :using_resource_with_status_and_location
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+ end
+
+ def test_using_invalid_resource_with_template
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+
+ @request.accept = "text/xml"
+
+ post :using_invalid_resource_with_template
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+
+ put :using_invalid_resource_with_template
+ assert_equal errors.to_xml, @response.body
+ assert_equal 422, @response.status
+ assert_equal nil, @response.location
+ end
+
+ def test_using_options_with_template
+ @request.accept = "text/xml"
+
+ post :using_options_with_template
+ assert_equal "<customer-name>david</customer-name>", @response.body
+ assert_equal 123, @response.status
+ assert_equal "http://test.host/", @response.location
+
+ put :using_options_with_template
+ assert_equal "<customer-name>david</customer-name>", @response.body
+ assert_equal 123, @response.status
+ assert_equal "http://test.host/", @response.location
+ end
+
+ def test_using_resource_with_responder
+ get :using_resource_with_responder
+ assert_equal "Resource name is david", @response.body
+ end
+
+ def test_using_resource_with_set_responder
+ RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" }
+ get :using_resource
+ assert_equal "Resource name is david", @response.body
+ ensure
+ RespondWithController.responder = ActionController::Responder
+ end
+
+ def test_uses_renderer_if_an_api_behavior
+ ActionController::Renderers.add :csv do |obj, options|
+ send_data obj.to_csv, type: Mime::CSV
+ end
+ @controller = CsvRespondWithController.new
+ get :index, format: 'csv'
+ assert_equal Mime::CSV, @response.content_type
+ assert_equal "c,s,v", @response.body
+ ensure
+ ActionController::Renderers.remove :csv
+ end
+
+ def test_raises_missing_renderer_if_an_api_behavior_with_no_renderer
+ @controller = CsvRespondWithController.new
+ assert_raise ActionController::MissingRenderer do
+ get :index, format: 'csv'
+ end
+ end
+
+ def test_removing_renderers
+ ActionController::Renderers.add :csv do |obj, options|
+ send_data obj.to_csv, type: Mime::CSV
+ end
+ @controller = CsvRespondWithController.new
+ @request.accept = "text/csv"
+ get :index, format: 'csv'
+ assert_equal Mime::CSV, @response.content_type
+
+ ActionController::Renderers.remove :csv
+ assert_raise ActionController::MissingRenderer do
+ get :index, format: 'csv'
+ end
+ ensure
+ ActionController::Renderers.remove :csv
+ end
+
+ def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called
+ @controller = EmptyRespondWithController.new
+ @request.accept = "*/*"
+ assert_raise RuntimeError do
+ get :index
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ resources :customers
+ resources :quiz_stores do
+ resources :customers
+ end
+ get ":controller/:action"
+ end
+ yield
+ end
+ end
+end
+
+class FlashResponder < ActionController::Responder
+ def initialize(controller, resources, options={})
+ super
+ end
+
+ def to_html
+ controller.flash[:notice] = 'Success'
+ super
+ end
+end
+
+class FlashResponderController < ActionController::Base
+ self.responder = FlashResponder
+ respond_to :html
+
+ def index
+ respond_with Object.new do |format|
+ format.html { render :text => 'HTML' }
+ end
+ end
+end
+
+class FlashResponderControllerTest < ActionController::TestCase
+ tests FlashResponderController
+
+ def test_respond_with_block_executed
+ get :index
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_flash_responder_executed
+ get :index
+ assert_equal 'Success', flash[:notice]
+ end
+end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
new file mode 100644
index 0000000000..246ba099af
--- /dev/null
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -0,0 +1,153 @@
+require "abstract_unit"
+
+module BareMetalTest
+ class BareController < ActionController::Metal
+ include ActionController::RackDelegation
+
+ def index
+ self.response_body = "Hello world"
+ end
+ end
+
+ class BareTest < ActiveSupport::TestCase
+ test "response body is a Rack-compatible response" do
+ status, headers, body = BareController.action(:index).call(Rack::MockRequest.env_for("/"))
+ assert_equal 200, status
+ string = ""
+
+ body.each do |part|
+ assert part.is_a?(String), "Each part of the body must be a String"
+ string << part
+ end
+
+ assert_kind_of Hash, headers, "Headers must be a Hash"
+ assert headers["Content-Type"], "Content-Type must exist"
+
+ assert_equal "Hello world", string
+ end
+
+ test "response_body value is wrapped in an array when the value is a String" do
+ controller = BareController.new
+ controller.index
+ assert_equal ["Hello world"], controller.response_body
+ end
+ end
+
+ class HeadController < ActionController::Metal
+ include ActionController::Head
+
+ def index
+ head :not_found
+ end
+
+ def continue
+ self.content_type = "text/html"
+ head 100
+ end
+
+ def switching_protocols
+ self.content_type = "text/html"
+ head 101
+ end
+
+ def processing
+ self.content_type = "text/html"
+ head 102
+ end
+
+ def no_content
+ self.content_type = "text/html"
+ head 204
+ end
+
+ def reset_content
+ self.content_type = "text/html"
+ head 205
+ end
+
+ def not_modified
+ self.content_type = "text/html"
+ head 304
+ end
+ end
+
+ class HeadTest < ActiveSupport::TestCase
+ test "head works on its own" do
+ status = HeadController.action(:index).call(Rack::MockRequest.env_for("/")).first
+ assert_equal 404, status
+ end
+
+ test "head :continue (100) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :switching_protocols (101) does not return a content-type header" do
+ headers = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :processing (102) does not return a content-type header" do
+ headers = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :no_content (204) does not return a content-type header" do
+ headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :reset_content (205) does not return a content-type header" do
+ headers = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :not_modified (304) does not return a content-type header" do
+ headers = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :no_content (204) does not return any content" do
+ content = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).third.first
+ assert_empty content
+ end
+
+ test "head :reset_content (205) does not return any content" do
+ content = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).third.first
+ assert_empty content
+ end
+
+ test "head :not_modified (304) does not return any content" do
+ content = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).third.first
+ assert_empty content
+ end
+
+ test "head :continue (100) does not return any content" do
+ content = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).third.first
+ assert_empty content
+ end
+
+ test "head :switching_protocols (101) does not return any content" do
+ content = HeadController.action(:switching_protocols).call(Rack::MockRequest.env_for("/")).third.first
+ assert_empty content
+ end
+
+ test "head :processing (102) does not return any content" do
+ content = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).third.first
+ assert_empty content
+ end
+ end
+
+ class BareControllerTest < ActionController::TestCase
+ test "GET index" do
+ get :index
+ assert_equal "Hello world", @response.body
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
new file mode 100644
index 0000000000..964f22eb03
--- /dev/null
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -0,0 +1,132 @@
+require 'abstract_unit'
+
+# Tests the controller dispatching happy path
+module Dispatching
+ class SimpleController < ActionController::Base
+ before_action :authenticate
+
+ def index
+ render :text => "success"
+ end
+
+ def modify_response_body
+ self.response_body = "success"
+ end
+
+ def modify_response_body_twice
+ ret = (self.response_body = "success")
+ self.response_body = "#{ret}!"
+ end
+
+ def modify_response_headers
+ end
+
+ def show_actions
+ render :text => "actions: #{action_methods.to_a.sort.join(', ')}"
+ end
+
+ protected
+ def authenticate
+ end
+ end
+
+ class EmptyController < ActionController::Base ; end
+ class SubEmptyController < EmptyController ; end
+ class NonDefaultPathController < ActionController::Base
+ def self.controller_path; "i_am_not_default"; end
+ end
+
+ module Submodule
+ class ContainedEmptyController < ActionController::Base ; end
+ class ContainedSubEmptyController < ContainedEmptyController ; end
+ class ContainedNonDefaultPathController < ActionController::Base
+ def self.controller_path; "i_am_extremely_not_default"; end
+ end
+ end
+
+ class BaseTest < Rack::TestCase
+ # :api: plugin
+ test "simple dispatching" do
+ get "/dispatching/simple/index"
+
+ assert_body "success"
+ assert_status 200
+ assert_content_type "text/html; charset=utf-8"
+ end
+
+ # :api: plugin
+ test "directly modifying response body" do
+ get "/dispatching/simple/modify_response_body"
+
+ assert_body "success"
+ end
+
+ # :api: plugin
+ test "directly modifying response body twice" do
+ get "/dispatching/simple/modify_response_body_twice"
+
+ assert_body "success!"
+ end
+
+ test "controller path" do
+ assert_equal 'dispatching/empty', EmptyController.controller_path
+ assert_equal EmptyController.controller_path, EmptyController.new.controller_path
+ end
+
+ test "non-default controller path" do
+ assert_equal 'i_am_not_default', NonDefaultPathController.controller_path
+ assert_equal NonDefaultPathController.controller_path, NonDefaultPathController.new.controller_path
+ end
+
+ test "sub controller path" do
+ assert_equal 'dispatching/sub_empty', SubEmptyController.controller_path
+ assert_equal SubEmptyController.controller_path, SubEmptyController.new.controller_path
+ end
+
+ test "namespaced controller path" do
+ assert_equal 'dispatching/submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
+ assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
+ end
+
+ test "namespaced non-default controller path" do
+ assert_equal 'i_am_extremely_not_default', Submodule::ContainedNonDefaultPathController.controller_path
+ assert_equal Submodule::ContainedNonDefaultPathController.controller_path, Submodule::ContainedNonDefaultPathController.new.controller_path
+ end
+
+ test "namespaced sub controller path" do
+ assert_equal 'dispatching/submodule/contained_sub_empty', Submodule::ContainedSubEmptyController.controller_path
+ assert_equal Submodule::ContainedSubEmptyController.controller_path, Submodule::ContainedSubEmptyController.new.controller_path
+ end
+
+ test "controller name" do
+ assert_equal 'empty', EmptyController.controller_name
+ assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
+ end
+
+ test "non-default path controller name" do
+ assert_equal 'non_default_path', NonDefaultPathController.controller_name
+ assert_equal 'contained_non_default_path', Submodule::ContainedNonDefaultPathController.controller_name
+ end
+
+ test "sub controller name" do
+ assert_equal 'sub_empty', SubEmptyController.controller_name
+ assert_equal 'contained_sub_empty', Submodule::ContainedSubEmptyController.controller_name
+ end
+
+ test "action methods" do
+ assert_equal Set.new(%w(
+ index
+ modify_response_headers
+ modify_response_body_twice
+ modify_response_body
+ show_actions
+ )), SimpleController.action_methods
+
+ assert_equal Set.new, EmptyController.action_methods
+ assert_equal Set.new, Submodule::ContainedEmptyController.action_methods
+
+ get "/dispatching/simple/show_actions"
+ assert_body "actions: index, modify_response_body, modify_response_body_twice, modify_response_headers, show_actions"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb
new file mode 100644
index 0000000000..5fd5946619
--- /dev/null
+++ b/actionpack/test/controller/new_base/content_negotiation_test.rb
@@ -0,0 +1,27 @@
+require 'abstract_unit'
+
+module ContentNegotiation
+
+ # This has no layout and it works
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats.first.to_s %>!"
+ )]
+
+ def all
+ render :text => self.formats.inspect
+ end
+ end
+
+ class TestContentNegotiation < Rack::TestCase
+ test "A */* Accept header will return HTML" do
+ get "/content_negotiation/basic/hello", {}, "HTTP_ACCEPT" => "*/*"
+ assert_body "Hello world */*!"
+ end
+
+ test "Not all mimes are converted to symbol" do
+ get "/content_negotiation/basic/all", {}, "HTTP_ACCEPT" => "text/plain, mime/another"
+ assert_body '[:text, "mime/another"]'
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
new file mode 100644
index 0000000000..9b57641e75
--- /dev/null
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -0,0 +1,112 @@
+require 'abstract_unit'
+
+module ContentType
+ class BaseController < ActionController::Base
+ def index
+ render :text => "Hello world!"
+ end
+
+ def set_on_response_obj
+ response.content_type = Mime::RSS
+ render :text => "Hello world!"
+ end
+
+ def set_on_render
+ render :text => "Hello world!", :content_type => Mime::RSS
+ end
+ end
+
+ class ImpliedController < ActionController::Base
+ # Template's mime type is used if no content_type is specified
+
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "content_type/implied/i_am_html_erb.html.erb" => "Hello world!",
+ "content_type/implied/i_am_xml_erb.xml.erb" => "<xml>Hello world!</xml>",
+ "content_type/implied/i_am_html_builder.html.builder" => "xml.p 'Hello'",
+ "content_type/implied/i_am_xml_builder.xml.builder" => "xml.awesome 'Hello'"
+ )]
+ end
+
+ class CharsetController < ActionController::Base
+ def set_on_response_obj
+ response.charset = "utf-16"
+ render :text => "Hello world!"
+ end
+
+ def set_as_nil_on_response_obj
+ response.charset = nil
+ render :text => "Hello world!"
+ end
+ end
+
+ class ExplicitContentTypeTest < Rack::TestCase
+ test "default response is HTML and UTF8" do
+ with_routing do |set|
+ set.draw do
+ get ':controller', :action => 'index'
+ end
+
+ get "/content_type/base"
+
+ assert_body "Hello world!"
+ assert_header "Content-Type", "text/html; charset=utf-8"
+ end
+ end
+
+ test "setting the content type of the response directly on the response object" do
+ get "/content_type/base/set_on_response_obj"
+
+ assert_body "Hello world!"
+ assert_header "Content-Type", "application/rss+xml; charset=utf-8"
+ end
+
+ test "setting the content type of the response as an option to render" do
+ get "/content_type/base/set_on_render"
+
+ assert_body "Hello world!"
+ assert_header "Content-Type", "application/rss+xml; charset=utf-8"
+ end
+ end
+
+ class ImpliedContentTypeTest < Rack::TestCase
+ test "sets Content-Type as text/html when rendering *.html.erb" do
+ get "/content_type/implied/i_am_html_erb"
+
+ assert_header "Content-Type", "text/html; charset=utf-8"
+ end
+
+ test "sets Content-Type as application/xml when rendering *.xml.erb" do
+ get "/content_type/implied/i_am_xml_erb", "format" => "xml"
+
+ assert_header "Content-Type", "application/xml; charset=utf-8"
+ end
+
+ test "sets Content-Type as text/html when rendering *.html.builder" do
+ get "/content_type/implied/i_am_html_builder"
+
+ assert_header "Content-Type", "text/html; charset=utf-8"
+ end
+
+ test "sets Content-Type as application/xml when rendering *.xml.builder" do
+ get "/content_type/implied/i_am_xml_builder", "format" => "xml"
+
+ assert_header "Content-Type", "application/xml; charset=utf-8"
+ end
+ end
+
+ class ExplicitCharsetTest < Rack::TestCase
+ test "setting the charset of the response directly on the response object" do
+ get "/content_type/charset/set_on_response_obj"
+
+ assert_body "Hello world!"
+ assert_header "Content-Type", "text/html; charset=utf-16"
+ end
+
+ test "setting the charset of the response as nil directly on the response object" do
+ get "/content_type/charset/set_as_nil_on_response_obj"
+
+ assert_body "Hello world!"
+ assert_header "Content-Type", "text/html; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb
new file mode 100644
index 0000000000..45a6619eb4
--- /dev/null
+++ b/actionpack/test/controller/new_base/metal_test.rb
@@ -0,0 +1,45 @@
+require 'abstract_unit'
+
+module MetalTest
+ class MetalMiddleware < ActionController::Middleware
+ def call(env)
+ if env["PATH_INFO"] =~ /authed/
+ app.call(env)
+ else
+ [401, headers, "Not authed!"]
+ end
+ end
+ end
+
+ class Endpoint
+ def call(env)
+ [200, {}, "Hello World"]
+ end
+ end
+
+ class TestMiddleware < ActiveSupport::TestCase
+ include RackTestUtils
+
+ def setup
+ @app = Rack::Builder.new do
+ use MetalTest::MetalMiddleware
+ run MetalTest::Endpoint.new
+ end.to_app
+ end
+
+ test "it can call the next app by using @app" do
+ env = Rack::MockRequest.env_for("/authed")
+ response = @app.call(env)
+
+ assert_equal "Hello World", body_to_string(response[2])
+ end
+
+ test "it can return a response using the normal AC::Metal techniques" do
+ env = Rack::MockRequest.env_for("/")
+ response = @app.call(env)
+
+ assert_equal "Not authed!", body_to_string(response[2])
+ assert_equal 401, response[0]
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/middleware_test.rb b/actionpack/test/controller/new_base/middleware_test.rb
new file mode 100644
index 0000000000..6b7b5e10e3
--- /dev/null
+++ b/actionpack/test/controller/new_base/middleware_test.rb
@@ -0,0 +1,110 @@
+require 'abstract_unit'
+
+module MiddlewareTest
+ class MyMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ result = @app.call(env)
+ result[1]["Middleware-Test"] = "Success"
+ result[1]["Middleware-Order"] = "First"
+ result
+ end
+ end
+
+ class ExclaimerMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ result = @app.call(env)
+ result[1]["Middleware-Order"] << "!"
+ result
+ end
+ end
+
+ class BlockMiddleware
+ attr_accessor :configurable_message
+ def initialize(app, &block)
+ @app = app
+ yield(self) if block_given?
+ end
+
+ def call(env)
+ result = @app.call(env)
+ result[1]["Configurable-Message"] = configurable_message
+ result
+ end
+ end
+
+ class MyController < ActionController::Metal
+ use BlockMiddleware do |config|
+ config.configurable_message = "Configured by block."
+ end
+ use MyMiddleware
+ middleware.insert_before MyMiddleware, ExclaimerMiddleware
+
+ def index
+ self.response_body = "Hello World"
+ end
+ end
+
+ class InheritedController < MyController
+ end
+
+ class ActionsController < ActionController::Metal
+ use MyMiddleware, :only => :show
+ middleware.insert_before MyMiddleware, ExclaimerMiddleware, :except => :index
+
+ def index
+ self.response_body = "index"
+ end
+
+ def show
+ self.response_body = "show"
+ end
+ end
+
+ class TestMiddleware < ActiveSupport::TestCase
+ def setup
+ @app = MyController.action(:index)
+ end
+
+ test "middleware that is 'use'd is called as part of the Rack application" do
+ result = @app.call(env_for("/"))
+ assert_equal "Hello World", RackTestUtils.body_to_string(result[2])
+ assert_equal "Success", result[1]["Middleware-Test"]
+ end
+
+ test "the middleware stack is exposed as 'middleware' in the controller" do
+ result = @app.call(env_for("/"))
+ assert_equal "First!", result[1]["Middleware-Order"]
+ end
+
+ test "middleware stack accepts block arguments" do
+ result = @app.call(env_for("/"))
+ assert_equal "Configured by block.", result[1]["Configurable-Message"]
+ end
+
+ test "middleware stack accepts only and except as options" do
+ result = ActionsController.action(:show).call(env_for("/"))
+ assert_equal "First!", result[1]["Middleware-Order"]
+
+ result = ActionsController.action(:index).call(env_for("/"))
+ assert_nil result[1]["Middleware-Order"]
+ end
+
+ def env_for(url)
+ Rack::MockRequest.env_for(url)
+ end
+ end
+
+ class TestInheritedMiddleware < TestMiddleware
+ def setup
+ @app = InheritedController.action(:index)
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb
new file mode 100644
index 0000000000..475bf9d3c9
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_action_test.rb
@@ -0,0 +1,315 @@
+require 'abstract_unit'
+
+module RenderAction
+ # This has no layout and it works
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_action/basic/hello_world.html.erb" => "Hello world!"
+ )]
+
+ def hello_world
+ render :action => "hello_world"
+ end
+
+ def hello_world_as_string
+ render "hello_world"
+ end
+
+ def hello_world_as_string_with_options
+ render "hello_world", :status => 404
+ end
+
+ def hello_world_as_symbol
+ render :hello_world
+ end
+
+ def hello_world_with_symbol
+ render :action => :hello_world
+ end
+
+ def hello_world_with_layout
+ render :action => "hello_world", :layout => true
+ end
+
+ def hello_world_with_layout_false
+ render :action => "hello_world", :layout => false
+ end
+
+ def hello_world_with_layout_nil
+ render :action => "hello_world", :layout => nil
+ end
+
+ def hello_world_with_custom_layout
+ render :action => "hello_world", :layout => "greetings"
+ end
+
+ end
+
+ class RenderActionTest < Rack::TestCase
+ test "rendering an action using :action => <String>" do
+ get "/render_action/basic/hello_world"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+
+ test "rendering an action using '<action>'" do
+ get "/render_action/basic/hello_world_as_string"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+
+ test "rendering an action using '<action>' and options" do
+ get "/render_action/basic/hello_world_as_string_with_options"
+
+ assert_body "Hello world!"
+ assert_status 404
+ end
+
+ test "rendering an action using :action" do
+ get "/render_action/basic/hello_world_as_symbol"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+
+ test "rendering an action using :action => :hello_world" do
+ get "/render_action/basic/hello_world_with_symbol"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+ end
+
+ class RenderLayoutTest < Rack::TestCase
+ def setup
+ end
+
+ test "rendering with layout => true" do
+ assert_raise(ArgumentError) do
+ get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false
+ end
+ end
+
+ test "rendering with layout => false" do
+ get "/render_action/basic/hello_world_with_layout_false"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+
+ test "rendering with layout => :nil" do
+ get "/render_action/basic/hello_world_with_layout_nil"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+
+ test "rendering with layout => 'greetings'" do
+ assert_raise(ActionView::MissingTemplate) do
+ get "/render_action/basic/hello_world_with_custom_layout", {}, "action_dispatch.show_exceptions" => false
+ end
+ end
+ end
+end
+
+module RenderActionWithApplicationLayout
+ # # ==== Render actions with layouts ====
+ class BasicController < ::ApplicationController
+ # Set the view path to an application view structure with layouts
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_action_with_application_layout/basic/hello_world.html.erb" => "Hello World!",
+ "render_action_with_application_layout/basic/hello.html.builder" => "xml.p 'Hello'",
+ "layouts/application.html.erb" => "Hi <%= yield %> OK, Bye",
+ "layouts/greetings.html.erb" => "Greetings <%= yield %> Bye",
+ "layouts/builder.html.builder" => "xml.html do\n xml << yield\nend"
+ )]
+
+ def hello_world
+ render :action => "hello_world"
+ end
+
+ def hello_world_with_layout
+ render :action => "hello_world", :layout => true
+ end
+
+ def hello_world_with_layout_false
+ render :action => "hello_world", :layout => false
+ end
+
+ def hello_world_with_layout_nil
+ render :action => "hello_world", :layout => nil
+ end
+
+ def hello_world_with_custom_layout
+ render :action => "hello_world", :layout => "greetings"
+ end
+
+ def with_builder_and_layout
+ render :action => "hello", :layout => "builder"
+ end
+ end
+
+ class LayoutTest < Rack::TestCase
+ test "rendering implicit application.html.erb as layout" do
+ get "/render_action_with_application_layout/basic/hello_world"
+
+ assert_body "Hi Hello World! OK, Bye"
+ assert_status 200
+ end
+
+ test "rendering with layout => true" do
+ get "/render_action_with_application_layout/basic/hello_world_with_layout"
+
+ assert_body "Hi Hello World! OK, Bye"
+ assert_status 200
+ end
+
+ test "rendering with layout => false" do
+ get "/render_action_with_application_layout/basic/hello_world_with_layout_false"
+
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering with layout => :nil" do
+ get "/render_action_with_application_layout/basic/hello_world_with_layout_nil"
+
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering with layout => 'greetings'" do
+ get "/render_action_with_application_layout/basic/hello_world_with_custom_layout"
+
+ assert_body "Greetings Hello World! Bye"
+ assert_status 200
+ end
+ end
+
+ class TestLayout < Rack::TestCase
+ testing BasicController
+
+ test "builder works with layouts" do
+ get :with_builder_and_layout
+ assert_response "<html>\n<p>Hello</p>\n</html>\n"
+ end
+ end
+
+end
+
+module RenderActionWithControllerLayout
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_action_with_controller_layout/basic/hello_world.html.erb" => "Hello World!",
+ "layouts/render_action_with_controller_layout/basic.html.erb" => "With Controller Layout! <%= yield %> Bye"
+ )]
+
+ def hello_world
+ render :action => "hello_world"
+ end
+
+ def hello_world_with_layout
+ render :action => "hello_world", :layout => true
+ end
+
+ def hello_world_with_layout_false
+ render :action => "hello_world", :layout => false
+ end
+
+ def hello_world_with_layout_nil
+ render :action => "hello_world", :layout => nil
+ end
+
+ def hello_world_with_custom_layout
+ render :action => "hello_world", :layout => "greetings"
+ end
+ end
+
+ class ControllerLayoutTest < Rack::TestCase
+ test "render hello_world and implicitly use <controller_path>.html.erb as a layout." do
+ get "/render_action_with_controller_layout/basic/hello_world"
+
+ assert_body "With Controller Layout! Hello World! Bye"
+ assert_status 200
+ end
+
+ test "rendering with layout => true" do
+ get "/render_action_with_controller_layout/basic/hello_world_with_layout"
+
+ assert_body "With Controller Layout! Hello World! Bye"
+ assert_status 200
+ end
+
+ test "rendering with layout => false" do
+ get "/render_action_with_controller_layout/basic/hello_world_with_layout_false"
+
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering with layout => :nil" do
+ get "/render_action_with_controller_layout/basic/hello_world_with_layout_nil"
+
+ assert_body "Hello World!"
+ assert_status 200
+ end
+ end
+end
+
+module RenderActionWithBothLayouts
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new({
+ "render_action_with_both_layouts/basic/hello_world.html.erb" => "Hello World!",
+ "layouts/application.html.erb" => "Oh Hi <%= yield %> Bye",
+ "layouts/render_action_with_both_layouts/basic.html.erb" => "With Controller Layout! <%= yield %> Bye"
+ })]
+
+ def hello_world
+ render :action => "hello_world"
+ end
+
+ def hello_world_with_layout
+ render :action => "hello_world", :layout => true
+ end
+
+ def hello_world_with_layout_false
+ render :action => "hello_world", :layout => false
+ end
+
+ def hello_world_with_layout_nil
+ render :action => "hello_world", :layout => nil
+ end
+ end
+
+ class ControllerLayoutTest < Rack::TestCase
+ test "rendering implicitly use <controller_path>.html.erb over application.html.erb as a layout" do
+ get "/render_action_with_both_layouts/basic/hello_world"
+
+ assert_body "With Controller Layout! Hello World! Bye"
+ assert_status 200
+ end
+
+ test "rendering with layout => true" do
+ get "/render_action_with_both_layouts/basic/hello_world_with_layout"
+
+ assert_body "With Controller Layout! Hello World! Bye"
+ assert_status 200
+ end
+
+ test "rendering with layout => false" do
+ get "/render_action_with_both_layouts/basic/hello_world_with_layout_false"
+
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering with layout => :nil" do
+ get "/render_action_with_both_layouts/basic/hello_world_with_layout_nil"
+
+ assert_body "Hello World!"
+ assert_status 200
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
new file mode 100644
index 0000000000..f4a3db8b41
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -0,0 +1,170 @@
+require 'abstract_unit'
+
+module RenderBody
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render body: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render body: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render body: "hello david"
+ end
+
+ def custom_code
+ render body: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render body: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render body: nil
+ end
+
+ def with_nil_and_status
+ render body: nil, status: 403
+ end
+
+ def with_false
+ render body: false
+ end
+
+ def with_layout_true
+ render body: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render body: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render body: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render body: "hello world", layout: "greetings"
+ end
+
+ def with_custom_content_type
+ response.headers['Content-Type'] = 'application/json'
+ render body: '["troll","face"]'
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render body: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderBodyTest < Rack::TestCase
+ test "rendering body from a minimal controller" do
+ get "/render_body/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering body from an action with default options renders the body with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body from an action with default options renders the body without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body, while also providing a custom status code" do
+ get "/render_body/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering body with nil returns an empty body" do
+ get "/render_body/with_layout/with_nil"
+
+ assert_body ""
+ assert_status 200
+ end
+
+ test "Rendering body with nil and custom status code returns an empty body and the status" do
+ get "/render_body/with_layout/with_nil_and_status"
+
+ assert_body ""
+ assert_status 403
+ end
+
+ test "rendering body with false returns the string 'false'" do
+ get "/render_body/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering body with layout: true" do
+ get "/render_body/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering body with layout: 'greetings'" do
+ get "/render_body/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "specified content type should not be removed" do
+ get "/render_body/with_layout/with_custom_content_type"
+
+ assert_equal %w{ troll face }, JSON.parse(response.body)
+ assert_equal 'application/json', response.headers['Content-Type']
+ end
+
+ test "rendering body with layout: false" do
+ get "/render_body/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering body with layout: nil" do
+ get "/render_body/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb
new file mode 100644
index 0000000000..177a1c088d
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_context_test.rb
@@ -0,0 +1,54 @@
+require 'abstract_unit'
+
+# This is testing the decoupling of view renderer and view context
+# by allowing the controller to be used as view context. This is
+# similar to the way sinatra renders templates.
+module RenderContext
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>",
+ "layouts/basic.html.erb" => "?<%= yield %>?"
+ )]
+
+ # 1) Include ActionView::Context to bring the required dependencies
+ include ActionView::Context
+
+ # 2) Call _prepare_context that will do the required initialization
+ before_action :_prepare_context
+
+ def hello_world
+ @value = "Hello"
+ render :action => "hello_world", :layout => false
+ end
+
+ def with_layout
+ @value = "Hello"
+ render :action => "hello_world", :layout => "basic"
+ end
+
+ protected
+
+ # 3) Set view_context to self
+ def view_context
+ self
+ end
+
+ def __controller_method__
+ "controller context!"
+ end
+ end
+
+ class RenderContextTest < Rack::TestCase
+ test "rendering using the controller as context" do
+ get "/render_context/basic/hello_world"
+ assert_body "Hello from controller context!"
+ assert_status 200
+ end
+
+ test "rendering using the controller as context with layout" do
+ get "/render_context/basic/with_layout"
+ assert_body "?Hello from controller context!?"
+ assert_status 200
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_file_test.rb b/actionpack/test/controller/new_base/render_file_test.rb
new file mode 100644
index 0000000000..a961cbf849
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_file_test.rb
@@ -0,0 +1,99 @@
+require 'abstract_unit'
+
+module RenderFile
+ class BasicController < ActionController::Base
+ self.view_paths = File.dirname(__FILE__)
+
+ def index
+ render :file => File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
+ end
+
+ def with_instance_variables
+ @secret = 'in the sauce'
+ render :file => File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')
+ end
+
+ def without_file_key
+ render File.join(File.dirname(__FILE__), *%w[.. .. fixtures test hello_world])
+ end
+
+ def without_file_key_with_instance_variable
+ @secret = 'in the sauce'
+ render File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')
+ end
+
+ def relative_path
+ @secret = 'in the sauce'
+ render :file => '../../fixtures/test/render_file_with_ivar'
+ end
+
+ def relative_path_with_dot
+ @secret = 'in the sauce'
+ render :file => '../../fixtures/test/dot.directory/render_file_with_ivar'
+ end
+
+ def pathname
+ @secret = 'in the sauce'
+ render :file => Pathname.new(File.dirname(__FILE__)).join(*%w[.. .. fixtures test dot.directory render_file_with_ivar])
+ end
+
+ def with_locals
+ path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')
+ render :file => path, :locals => {:secret => 'in the sauce'}
+ end
+
+ def without_file_key_with_locals
+ path = FIXTURES.join('test/render_file_with_locals').to_s
+ render path, :locals => {:secret => 'in the sauce'}
+ end
+ end
+
+ class TestBasic < Rack::TestCase
+ testing RenderFile::BasicController
+
+ test "rendering simple template" do
+ get :index
+ assert_response "Hello world!"
+ end
+
+ test "rendering template with ivar" do
+ get :with_instance_variables
+ assert_response "The secret is in the sauce\n"
+ end
+
+ test "rendering path without specifying the :file key" do
+ get :without_file_key
+ assert_response "Hello world!"
+ end
+
+ test "rendering path without specifying the :file key with ivar" do
+ get :without_file_key_with_instance_variable
+ assert_response "The secret is in the sauce\n"
+ end
+
+ test "rendering a relative path" do
+ get :relative_path
+ assert_response "The secret is in the sauce\n"
+ end
+
+ test "rendering a relative path with dot" do
+ get :relative_path_with_dot
+ assert_response "The secret is in the sauce\n"
+ end
+
+ test "rendering a Pathname" do
+ get :pathname
+ assert_response "The secret is in the sauce\n"
+ end
+
+ test "rendering file with locals" do
+ get :with_locals
+ assert_response "The secret is in the sauce\n"
+ end
+
+ test "rendering path without specifying the :file key with locals" do
+ get :without_file_key_with_locals
+ assert_response "The secret is in the sauce\n"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
new file mode 100644
index 0000000000..fe11501eeb
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -0,0 +1,190 @@
+require 'abstract_unit'
+
+module RenderHtml
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render html: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render html: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render html: "hello david"
+ end
+
+ def custom_code
+ render html: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render html: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render html: nil
+ end
+
+ def with_nil_and_status
+ render html: nil, status: 403
+ end
+
+ def with_false
+ render html: false
+ end
+
+ def with_layout_true
+ render html: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render html: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render html: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render html: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render html: "hello world", layout: "ivar"
+ end
+
+ def with_unsafe_html_tag
+ render html: "<p>hello world</p>", layout: nil
+ end
+
+ def with_safe_html_tag
+ render html: "<p>hello world</p>".html_safe, layout: nil
+ end
+ end
+
+ class RenderHtmlTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_html/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_html/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body" do
+ get "/render_html/with_layout/with_nil"
+
+ assert_body ""
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body and the status" do
+ get "/render_html/with_layout/with_nil_and_status"
+
+ assert_body ""
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_html/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_html/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_html/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_html/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_html/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering html should escape the string if it is not html safe" do
+ get "/render_html/with_layout/with_unsafe_html_tag"
+
+ assert_body "&lt;p&gt;hello world&lt;/p&gt;"
+ assert_status 200
+ end
+
+ test "rendering html should not escape the string if it is html safe" do
+ get "/render_html/with_layout/with_safe_html_tag"
+
+ assert_body "<p>hello world</p>"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/html content type" do
+ get "/render_html/minimal/index"
+ assert_content_type "text/html"
+ end
+
+ test "rendering from normal controller returns response with text/html content type" do
+ get "/render_html/simple/index"
+ assert_content_type "text/html; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
new file mode 100644
index 0000000000..5b4885f7e0
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -0,0 +1,57 @@
+require 'abstract_unit'
+
+module RenderImplicitAction
+ class SimpleController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_implicit_action/simple/hello_world.html.erb" => "Hello world!",
+ "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!",
+ "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
+ ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', __FILE__))]
+
+ def hello_world() end
+ end
+
+ class RenderImplicitActionTest < Rack::TestCase
+ test "render a simple action with new explicit call to render" do
+ get "/render_implicit_action/simple/hello_world"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+
+ test "render an action with a missing method and has special characters" do
+ get "/render_implicit_action/simple/hyphen-ated"
+
+ assert_body "Hello hyphen-ated!"
+ assert_status 200
+ end
+
+ test "render an action called not_implemented" do
+ get "/render_implicit_action/simple/not_implemented"
+
+ assert_body "Not Implemented"
+ assert_status 200
+ end
+
+ test "render does not traverse the file system" do
+ assert_raises(AbstractController::ActionNotFound) do
+ action_name = %w(.. .. fixtures shared).join(File::SEPARATOR)
+ SimpleController.action(action_name).call(Rack::MockRequest.env_for("/"))
+ end
+ end
+
+ test "available_action? returns true for implicit actions" do
+ assert SimpleController.new.available_action?(:hello_world)
+ assert SimpleController.new.available_action?(:"hyphen-ated")
+ assert SimpleController.new.available_action?(:not_implemented)
+ end
+
+ test "available_action? does not allow File::SEPARATOR on the name" do
+ action_name = %w(evil .. .. path).join(File::SEPARATOR)
+ assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
+
+ action_name = %w(evil path).join(File::SEPARATOR)
+ assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb
new file mode 100644
index 0000000000..4ac40ca405
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_layout_test.rb
@@ -0,0 +1,127 @@
+require 'abstract_unit'
+
+module ControllerLayouts
+ class ImplicitController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "Main <%= yield %> Layout",
+ "layouts/override.html.erb" => "Override! <%= yield %>",
+ "basic.html.erb" => "Hello world!",
+ "controller_layouts/implicit/layout_false.html.erb" => "hi(layout_false.html.erb)"
+ )]
+
+ def index
+ render :template => "basic"
+ end
+
+ def override
+ render :template => "basic", :layout => "override"
+ end
+
+ def layout_false
+ render :layout => false
+ end
+
+ def builder_override
+ end
+ end
+
+ class ImplicitNameController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/controller_layouts/implicit_name.html.erb" => "Implicit <%= yield %> Layout",
+ "basic.html.erb" => "Hello world!"
+ )]
+
+ def index
+ render :template => "basic"
+ end
+ end
+
+ class RenderLayoutTest < Rack::TestCase
+ test "rendering a normal template, but using the implicit layout" do
+ get "/controller_layouts/implicit/index"
+
+ assert_body "Main Hello world! Layout"
+ assert_status 200
+ end
+
+ test "rendering a normal template, but using an implicit NAMED layout" do
+ get "/controller_layouts/implicit_name/index"
+
+ assert_body "Implicit Hello world! Layout"
+ assert_status 200
+ end
+
+ test "overriding an implicit layout with render :layout option" do
+ get "/controller_layouts/implicit/override"
+ assert_body "Override! Hello world!"
+ end
+
+ end
+
+ class LayoutOptionsTest < Rack::TestCase
+ testing ControllerLayouts::ImplicitController
+
+ test "rendering with :layout => false leaves out the implicit layout" do
+ get :layout_false
+ assert_response "hi(layout_false.html.erb)"
+ end
+ end
+
+ class MismatchFormatController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "<html><%= yield %></html>",
+ "controller_layouts/mismatch_format/index.xml.builder" => "xml.instruct!",
+ "controller_layouts/mismatch_format/implicit.builder" => "xml.instruct!",
+ "controller_layouts/mismatch_format/explicit.js.erb" => "alert('foo');"
+ )]
+
+ def explicit
+ render :layout => "application"
+ end
+ end
+
+ class MismatchFormatTest < Rack::TestCase
+ testing ControllerLayouts::MismatchFormatController
+
+ XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
+
+ test "if XML is selected, an HTML template is not also selected" do
+ get :index, :format => "xml"
+ assert_response XML_INSTRUCT
+ end
+
+ test "if XML is implicitly selected, an HTML template is not also selected" do
+ get :implicit
+ assert_response XML_INSTRUCT
+ end
+
+ test "a layout for JS is ignored even if explicitly provided for HTML" do
+ get :explicit, { :format => "js" }
+ assert_response "alert('foo');"
+ end
+ end
+
+ class FalseLayoutMethodController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "controller_layouts/false_layout_method/index.js.erb" => "alert('foo');"
+ )]
+
+ layout :which_layout?
+
+ def which_layout?
+ false
+ end
+
+ def index
+ end
+ end
+
+ class FalseLayoutMethodTest < Rack::TestCase
+ testing ControllerLayouts::FalseLayoutMethodController
+
+ test "access false layout returned by a method/proc" do
+ get :index, :format => "js"
+ assert_response "alert('foo');"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_partial_test.rb b/actionpack/test/controller/new_base/render_partial_test.rb
new file mode 100644
index 0000000000..9e5022c9f4
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_partial_test.rb
@@ -0,0 +1,63 @@
+require 'abstract_unit'
+
+module RenderPartial
+
+ class BasicController < ActionController::Base
+
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_partial/basic/_basic.html.erb" => "BasicPartial!",
+ "render_partial/basic/basic.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'basic' %><%= @test_unchanged %>",
+ "render_partial/basic/with_json.html.erb" => "<%= render :partial => 'with_json', :formats => [:json] %>",
+ "render_partial/basic/_with_json.json.erb" => "<%= render :partial => 'final', :formats => [:json] %>",
+ "render_partial/basic/_final.json.erb" => "{ final: json }",
+ "render_partial/basic/overridden.html.erb" => "<%= @test_unchanged = 'goodbye' %><%= render :partial => 'overridden' %><%= @test_unchanged %>",
+ "render_partial/basic/_overridden.html.erb" => "ParentPartial!",
+ "render_partial/child/_overridden.html.erb" => "OverriddenPartial!"
+ )]
+
+ def html_with_json_inside_json
+ render :action => "with_json"
+ end
+
+ def changing
+ @test_unchanged = 'hello'
+ render :action => "basic"
+ end
+
+ def overridden
+ @test_unchanged = 'hello'
+ end
+ end
+
+ class ChildController < BasicController; end
+
+ class TestPartial < Rack::TestCase
+ testing BasicController
+
+ test "rendering a partial in ActionView doesn't pull the ivars again from the controller" do
+ get :changing
+ assert_response("goodbyeBasicPartial!goodbye")
+ end
+
+ test "rendering a template with renders another partial with other format that renders other partial in the same format" do
+ get :html_with_json_inside_json
+ assert_content_type "text/html; charset=utf-8"
+ assert_response "{ final: json }"
+ end
+ end
+
+ class TestInheritedPartial < Rack::TestCase
+ testing ChildController
+
+ test "partial from parent controller gets picked if missing in child one" do
+ get :changing
+ assert_response("goodbyeBasicPartial!goodbye")
+ end
+
+ test "partial from child controller gets picked" do
+ get :overridden
+ assert_response("goodbyeOverriddenPartial!goodbye")
+ end
+ end
+
+end
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
new file mode 100644
index 0000000000..0e36d36b50
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -0,0 +1,168 @@
+require 'abstract_unit'
+
+module RenderPlain
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render plain: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render plain: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.text.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.text.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.text.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render plain: "hello david"
+ end
+
+ def custom_code
+ render plain: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render plain: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render plain: nil
+ end
+
+ def with_nil_and_status
+ render plain: nil, status: 403
+ end
+
+ def with_false
+ render plain: false
+ end
+
+ def with_layout_true
+ render plain: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render plain: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render plain: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render plain: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render plain: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderPlainTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_plain/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_plain/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body" do
+ get "/render_plain/with_layout/with_nil"
+
+ assert_body ""
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body and the status" do
+ get "/render_plain/with_layout/with_nil_and_status"
+
+ assert_body ""
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_plain/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_plain/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_plain/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_plain/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_plain/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/plain content type" do
+ get "/render_plain/minimal/index"
+ assert_content_type "text/plain"
+ end
+
+ test "rendering from normal controller returns response with text/plain content type" do
+ get "/render_plain/simple/index"
+ assert_content_type "text/plain; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
new file mode 100644
index 0000000000..4c9126ca8c
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -0,0 +1,114 @@
+require 'abstract_unit'
+
+module RenderStreaming
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_streaming/basic/hello_world.html.erb" => "Hello world",
+ "render_streaming/basic/boom.html.erb" => "<%= raise 'Ruby was here!' %>",
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/boom.html.erb" => "<body class=\"<%= nil.invalid! %>\"<%= yield %></body>"
+ )]
+
+ layout "application"
+
+ def hello_world
+ render :stream => true
+ end
+
+ def layout_exception
+ render :action => "hello_world", :stream => true, :layout => "boom"
+ end
+
+ def template_exception
+ render :action => "boom", :stream => true
+ end
+
+ def skip
+ render :action => "hello_world", :stream => false
+ end
+
+ def explicit
+ render :action => "hello_world", :stream => true
+ end
+
+ def no_layout
+ render :action => "hello_world", :stream => true, :layout => false
+ end
+
+ def explicit_cache
+ headers["Cache-Control"] = "private"
+ render :action => "hello_world", :stream => true
+ end
+ end
+
+ class StreamingTest < Rack::TestCase
+ test "rendering with streaming enabled at the class level" do
+ get "/render_streaming/basic/hello_world"
+ assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with streaming given to render" do
+ get "/render_streaming/basic/explicit"
+ assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with streaming do not override explicit cache control given to render" do
+ get "/render_streaming/basic/explicit_cache"
+ assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n"
+ assert_streaming! "private"
+ end
+
+ test "rendering with streaming no layout" do
+ get "/render_streaming/basic/no_layout"
+ assert_body "b\r\nHello world\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "skip rendering with streaming at render level" do
+ get "/render_streaming/basic/skip"
+ assert_body "Hello world, I'm here!"
+ end
+
+ test "rendering with layout exception" do
+ get "/render_streaming/basic/layout_exception"
+ assert_body "d\r\n<body class=\"\r\n37\r\n\"><script>window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with template exception" do
+ get "/render_streaming/basic/template_exception"
+ assert_body "37\r\n\"><script>window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
+ assert_streaming!
+ end
+
+ test "rendering with template exception logs the exception" do
+ io = StringIO.new
+ _old, ActionView::Base.logger = ActionView::Base.logger, ActiveSupport::Logger.new(io)
+
+ begin
+ get "/render_streaming/basic/template_exception"
+ io.rewind
+ assert_match "Ruby was here!", io.read
+ ensure
+ ActionView::Base.logger = _old
+ end
+ end
+
+ test "do not stream on HTTP/1.0" do
+ get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0"
+ assert_body "Hello world, I'm here!"
+ assert_status 200
+ assert_equal "22", headers["Content-Length"]
+ assert_equal nil, headers["Transfer-Encoding"]
+ end
+
+ def assert_streaming!(cache="no-cache")
+ assert_status 200
+ assert_equal nil, headers["Content-Length"]
+ assert_equal "chunked", headers["Transfer-Encoding"]
+ assert_equal cache, headers["Cache-Control"]
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
new file mode 100644
index 0000000000..e87811776a
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -0,0 +1,230 @@
+require 'abstract_unit'
+
+module RenderTemplate
+ class WithoutLayoutController < ActionController::Base
+
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "test/basic.html.erb" => "Hello from basic.html.erb",
+ "shared.html.erb" => "Elastica",
+ "locals.html.erb" => "The secret is <%= secret %>",
+ "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend",
+ "with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>",
+ "with_implicit_raw.html.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in an html template",
+ "with_implicit_raw.text.erb" => "Hello <%== '<strong>this is also raw</strong>' %> in a text template",
+ "test/with_json.html.erb" => "<%= render :template => 'test/with_json', :formats => [:json] %>",
+ "test/with_json.json.erb" => "<%= render :template => 'test/final', :formats => [:json] %>",
+ "test/final.json.erb" => "{ final: json }",
+ "test/with_error.html.erb" => "<%= raise 'i do not exist' %>"
+ )]
+
+ def index
+ render :template => "test/basic"
+ end
+
+ def html_with_json_inside_json
+ render :template => "test/with_json"
+ end
+
+ def index_without_key
+ render "test/basic"
+ end
+
+ def in_top_directory
+ render :template => 'shared'
+ end
+
+ def in_top_directory_with_slash
+ render :template => '/shared'
+ end
+
+ def in_top_directory_with_slash_without_key
+ render '/shared'
+ end
+
+ def with_locals
+ render :template => "locals", :locals => { :secret => 'area51' }
+ end
+
+ def builder_template
+ render :template => "xml_template"
+ end
+
+ def with_raw
+ render :template => "with_raw"
+ end
+
+ def with_implicit_raw
+ render :template => "with_implicit_raw"
+ end
+
+ def with_error
+ render :template => "test/with_error"
+ end
+
+ private
+
+ def show_detailed_exceptions?
+ request.local?
+ end
+ end
+
+ class TestWithoutLayout < Rack::TestCase
+ testing RenderTemplate::WithoutLayoutController
+
+ test "rendering a normal template with full path without layout" do
+ get :index
+ assert_response "Hello from basic.html.erb"
+ end
+
+ test "rendering a normal template with full path without layout without key" do
+ get :index_without_key
+ assert_response "Hello from basic.html.erb"
+ end
+
+ test "rendering a template not in a subdirectory" do
+ get :in_top_directory
+ assert_response "Elastica"
+ end
+
+ test "rendering a template not in a subdirectory with a leading slash" do
+ get :in_top_directory_with_slash
+ assert_response "Elastica"
+ end
+
+ test "rendering a template not in a subdirectory with a leading slash without key" do
+ get :in_top_directory_with_slash_without_key
+ assert_response "Elastica"
+ end
+
+ test "rendering a template with local variables" do
+ get :with_locals
+ assert_response "The secret is area51"
+ end
+
+ test "rendering a builder template" do
+ get :builder_template, "format" => "xml"
+ assert_response "<html>\n <p>Hello</p>\n</html>\n"
+ end
+
+ test "rendering a template with <%=raw stuff %>" do
+ get :with_raw
+
+ assert_body "Hello <strong>this is raw</strong>"
+ assert_status 200
+
+ get :with_implicit_raw
+
+ assert_body "Hello <strong>this is also raw</strong> in an html template"
+ assert_status 200
+
+ get :with_implicit_raw, format: 'text'
+
+ assert_body "Hello <strong>this is also raw</strong> in a text template"
+ assert_status 200
+ end
+
+ test "rendering a template with renders another template with other format that renders other template in the same format" do
+ get :html_with_json_inside_json
+ assert_content_type "text/html; charset=utf-8"
+ assert_response "{ final: json }"
+ end
+
+ test "rendering a template with error properly excerts the code" do
+ get :with_error
+ assert_status 500
+ assert_match "i do not exist", response.body
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "test/basic.html.erb" => "Hello from basic.html.erb",
+ "shared.html.erb" => "Elastica",
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well."
+ )]
+
+ def index
+ render :template => "test/basic"
+ end
+
+ def with_layout
+ render :template => "test/basic", :layout => true
+ end
+
+ def with_layout_false
+ render :template => "test/basic", :layout => false
+ end
+
+ def with_layout_nil
+ render :template => "test/basic", :layout => nil
+ end
+
+ def with_custom_layout
+ render :template => "test/basic", :layout => "greetings"
+ end
+ end
+
+ class TestWithLayout < Rack::TestCase
+ test "rendering with implicit layout" do
+ with_routing do |set|
+ set.draw { get ':controller', :action => :index }
+
+ get "/render_template/with_layout"
+
+ assert_body "Hello from basic.html.erb, I'm here!"
+ assert_status 200
+ end
+ end
+
+ test "rendering with layout => :true" do
+ get "/render_template/with_layout/with_layout"
+
+ assert_body "Hello from basic.html.erb, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering with layout => :false" do
+ get "/render_template/with_layout/with_layout_false"
+
+ assert_body "Hello from basic.html.erb"
+ assert_status 200
+ end
+
+ test "rendering with layout => :nil" do
+ get "/render_template/with_layout/with_layout_nil"
+
+ assert_body "Hello from basic.html.erb"
+ assert_status 200
+ end
+
+ test "rendering layout => 'greetings'" do
+ get "/render_template/with_layout/with_custom_layout"
+
+ assert_body "Hello from basic.html.erb, I wish thee well."
+ assert_status 200
+ end
+ end
+
+ module Compatibility
+ class WithoutLayoutController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "test/basic.html.erb" => "Hello from basic.html.erb",
+ "shared.html.erb" => "Elastica"
+ )]
+
+ def with_forward_slash
+ render :template => "/test/basic"
+ end
+ end
+
+ class TestTemplateRenderWithForwardSlash < Rack::TestCase
+ test "rendering a normal template with full path starting with a leading slash" do
+ get "/render_template/compatibility/without_layout/with_forward_slash"
+
+ assert_body "Hello from basic.html.erb"
+ assert_status 200
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
new file mode 100644
index 0000000000..5635e16234
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -0,0 +1,136 @@
+require 'abstract_unit'
+
+module Render
+ class BlankRenderController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render/blank_render/index.html.erb" => "Hello world!",
+ "render/blank_render/access_request.html.erb" => "The request: <%= request.method.to_s.upcase %>",
+ "render/blank_render/access_action_name.html.erb" => "Action Name: <%= action_name %>",
+ "render/blank_render/access_controller_name.html.erb" => "Controller Name: <%= controller_name %>",
+ "render/blank_render/overridden_with_own_view_paths_appended.html.erb" => "parent content",
+ "render/blank_render/overridden_with_own_view_paths_prepended.html.erb" => "parent content",
+ "render/blank_render/overridden.html.erb" => "parent content",
+ "render/child_render/overridden.html.erb" => "child content"
+ )]
+
+ def index
+ render
+ end
+
+ def access_request
+ render :action => "access_request"
+ end
+
+ def render_action_name
+ render :action => "access_action_name"
+ end
+
+ def overridden_with_own_view_paths_appended
+ end
+
+ def overridden_with_own_view_paths_prepended
+ end
+
+ def overridden
+ end
+
+ private
+
+ def secretz
+ render :text => "FAIL WHALE!"
+ end
+ end
+
+ class DoubleRenderController < ActionController::Base
+ def index
+ render :text => "hello"
+ render :text => "world"
+ end
+ end
+
+ class ChildRenderController < BlankRenderController
+ append_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_appended.html.erb" => "child content")
+ prepend_view_path ActionView::FixtureResolver.new("render/child_render/overridden_with_own_view_paths_prepended.html.erb" => "child content")
+ end
+
+ class RenderTest < Rack::TestCase
+ test "render with blank" do
+ with_routing do |set|
+ set.draw do
+ get ":controller", :action => 'index'
+ end
+
+ get "/render/blank_render"
+
+ assert_body "Hello world!"
+ assert_status 200
+ end
+ end
+
+ test "rendering more than once raises an exception" do
+ with_routing do |set|
+ set.draw do
+ get ":controller", :action => 'index'
+ end
+
+ assert_raises(AbstractController::DoubleRenderError) do
+ get "/render/double_render", {}, "action_dispatch.show_exceptions" => false
+ end
+ end
+ end
+ end
+
+ class TestOnlyRenderPublicActions < Rack::TestCase
+ # Only public methods on actual controllers are callable actions
+ test "raises an exception when a method of Object is called" do
+ assert_raises(AbstractController::ActionNotFound) do
+ get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false
+ end
+ end
+
+ test "raises an exception when a private method is called" do
+ assert_raises(AbstractController::ActionNotFound) do
+ get "/render/blank_render/secretz", {}, "action_dispatch.show_exceptions" => false
+ end
+ end
+ end
+
+ class TestVariousObjectsAvailableInView < Rack::TestCase
+ test "The request object is accessible in the view" do
+ get "/render/blank_render/access_request"
+ assert_body "The request: GET"
+ end
+
+ test "The action_name is accessible in the view" do
+ get "/render/blank_render/render_action_name"
+ assert_body "Action Name: render_action_name"
+ end
+
+ test "The controller_name is accessible in the view" do
+ get "/render/blank_render/access_controller_name"
+ assert_body "Controller Name: blank_render"
+ end
+ end
+
+ class TestViewInheritance < Rack::TestCase
+ test "Template from child controller gets picked over parent one" do
+ get "/render/child_render/overridden"
+ assert_body "child content"
+ end
+
+ test "Template from child controller with custom view_paths prepended gets picked over parent one" do
+ get "/render/child_render/overridden_with_own_view_paths_prepended"
+ assert_body "child content"
+ end
+
+ test "Template from child controller with custom view_paths appended gets picked over parent one" do
+ get "/render/child_render/overridden_with_own_view_paths_appended"
+ assert_body "child content"
+ end
+
+ test "Template from parent controller gets picked if missing in child controller" do
+ get "/render/child_render/index"
+ assert_body "Hello world!"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
new file mode 100644
index 0000000000..10bad57cd6
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -0,0 +1,158 @@
+require 'abstract_unit'
+
+module RenderText
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render text: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render text: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render text: "hello david"
+ end
+
+ def custom_code
+ render text: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render text: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render text: nil
+ end
+
+ def with_nil_and_status
+ render text: nil, status: 403
+ end
+
+ def with_false
+ render text: false
+ end
+
+ def with_layout_true
+ render text: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render text: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render text: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render text: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render text: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderTextTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_text/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_text/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_text/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_text/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body" do
+ get "/render_text/with_layout/with_nil"
+
+ assert_body ""
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body and the status" do
+ get "/render_text/with_layout/with_nil_and_status"
+
+ assert_body ""
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_text/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_text/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_text/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_text/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_text/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_xml_test.rb b/actionpack/test/controller/new_base/render_xml_test.rb
new file mode 100644
index 0000000000..b8527a943d
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_xml_test.rb
@@ -0,0 +1,11 @@
+require 'abstract_unit'
+
+module RenderXml
+
+ # This has no layout and it works
+ class BasicController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "render_xml/basic/with_render_erb" => "Hello world!"
+ )]
+ end
+end
diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb
new file mode 100644
index 0000000000..c3c549fbfc
--- /dev/null
+++ b/actionpack/test/controller/output_escaping_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+class OutputEscapingTest < ActiveSupport::TestCase
+
+ test "escape_html shouldn't die when passed nil" do
+ assert ERB::Util.h(nil).blank?
+ end
+
+ test "escapeHTML should escape strings" do
+ assert_equal "&lt;&gt;&quot;", ERB::Util.h("<>\"")
+ end
+
+ test "escapeHTML shouldn't touch explicitly safe strings" do
+ assert_equal "<", ERB::Util.h("<".html_safe)
+ end
+
+end
diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
new file mode 100644
index 0000000000..059f310d49
--- /dev/null
+++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb
@@ -0,0 +1,29 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class AlwaysPermittedParametersTest < ActiveSupport::TestCase
+ def setup
+ ActionController::Parameters.action_on_unpermitted_parameters = :raise
+ ActionController::Parameters.always_permitted_parameters = %w( controller action format )
+ end
+
+ def teardown
+ ActionController::Parameters.action_on_unpermitted_parameters = false
+ ActionController::Parameters.always_permitted_parameters = %w( controller action )
+ end
+
+ test "shows deprecations warning on NEVER_UNPERMITTED_PARAMS" do
+ assert_deprecated do
+ ActionController::Parameters::NEVER_UNPERMITTED_PARAMS
+ end
+ end
+
+ test "permits parameters that are whitelisted" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65 },
+ format: "json"
+ })
+ permitted = params.permit book: [:pages]
+ assert permitted.permitted?
+ end
+end
diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
new file mode 100644
index 0000000000..9ce04b9aeb
--- /dev/null
+++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb
@@ -0,0 +1,72 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class LogOnUnpermittedParamsTest < ActiveSupport::TestCase
+ def setup
+ ActionController::Parameters.action_on_unpermitted_parameters = :log
+ end
+
+ def teardown
+ ActionController::Parameters.action_on_unpermitted_parameters = false
+ end
+
+ test "logs on unexpected param" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65 },
+ fishing: "Turnips"
+ })
+
+ assert_logged("Unpermitted parameter: fishing") do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "logs on unexpected params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65 },
+ fishing: "Turnips",
+ car: "Mersedes"
+ })
+
+ assert_logged("Unpermitted parameters: fishing, car") do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "logs on unexpected nested param" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65, title: "Green Cats and where to find then." }
+ })
+
+ assert_logged("Unpermitted parameter: title") do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "logs on unexpected nested params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" }
+ })
+
+ assert_logged("Unpermitted parameters: title, author") do
+ params.permit(book: [:pages])
+ end
+ end
+
+ private
+
+ def assert_logged(message)
+ old_logger = ActionController::Base.logger
+ log = StringIO.new
+ ActionController::Base.logger = Logger.new(log)
+
+ begin
+ yield
+
+ log.rewind
+ assert_match message, log.read
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+end
diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb
new file mode 100644
index 0000000000..15338059bc
--- /dev/null
+++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb
@@ -0,0 +1,38 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class MultiParameterAttributesTest < ActiveSupport::TestCase
+ test "permitted multi-parameter attribute keys" do
+ params = ActionController::Parameters.new({
+ book: {
+ "shipped_at(1i)" => "2012",
+ "shipped_at(2i)" => "3",
+ "shipped_at(3i)" => "25",
+ "shipped_at(4i)" => "10",
+ "shipped_at(5i)" => "15",
+ "published_at(1i)" => "1999",
+ "published_at(2i)" => "2",
+ "published_at(3i)" => "5",
+ "price(1)" => "R$",
+ "price(2f)" => "2.02"
+ }
+ })
+
+ permitted = params.permit book: [ :shipped_at, :price ]
+
+ assert permitted.permitted?
+
+ assert_equal "2012", permitted[:book]["shipped_at(1i)"]
+ assert_equal "3", permitted[:book]["shipped_at(2i)"]
+ assert_equal "25", permitted[:book]["shipped_at(3i)"]
+ assert_equal "10", permitted[:book]["shipped_at(4i)"]
+ assert_equal "15", permitted[:book]["shipped_at(5i)"]
+
+ assert_equal "R$", permitted[:book]["price(1)"]
+ assert_equal "2.02", permitted[:book]["price(2f)"]
+
+ assert_nil permitted[:book]["published_at(1i)"]
+ assert_nil permitted[:book]["published_at(2i)"]
+ assert_nil permitted[:book]["published_at(3i)"]
+ end
+end
diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb
new file mode 100644
index 0000000000..3b1257e8d5
--- /dev/null
+++ b/actionpack/test/controller/parameters/nested_parameters_test.rb
@@ -0,0 +1,187 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class NestedParametersTest < ActiveSupport::TestCase
+ def assert_filtered_out(params, key)
+ assert !params.has_key?(key), "key #{key.inspect} has not been filtered out"
+ end
+
+ test "permitted nested parameters" do
+ params = ActionController::Parameters.new({
+ book: {
+ title: "Romeo and Juliet",
+ authors: [{
+ name: "William Shakespeare",
+ born: "1564-04-26"
+ }, {
+ name: "Christopher Marlowe"
+ }, {
+ name: %w(malicious injected names)
+ }],
+ details: {
+ pages: 200,
+ genre: "Tragedy"
+ },
+ id: {
+ isbn: 'x'
+ }
+ },
+ magazine: "Mjallo!"
+ })
+
+ permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages }, :id ]
+
+ assert permitted.permitted?
+ assert_equal "Romeo and Juliet", permitted[:book][:title]
+ assert_equal "William Shakespeare", permitted[:book][:authors][0][:name]
+ assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name]
+ assert_equal 200, permitted[:book][:details][:pages]
+
+ assert_filtered_out permitted, :magazine
+ assert_filtered_out permitted[:book], :id
+ assert_filtered_out permitted[:book][:details], :genre
+ assert_filtered_out permitted[:book][:authors][0], :born
+ assert_filtered_out permitted[:book][:authors][2], :name
+ end
+
+ test "permitted nested parameters with a string or a symbol as a key" do
+ params = ActionController::Parameters.new({
+ book: {
+ 'authors' => [
+ { name: 'William Shakespeare', born: '1564-04-26' },
+ { name: 'Christopher Marlowe' }
+ ]
+ }
+ })
+
+ permitted = params.permit book: [ { 'authors' => [ :name ] } ]
+
+ assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name]
+ assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name]
+
+ permitted = params.permit book: [ { authors: [ :name ] } ]
+
+ assert_equal 'William Shakespeare', permitted[:book]['authors'][0][:name]
+ assert_equal 'William Shakespeare', permitted[:book][:authors][0][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book]['authors'][1][:name]
+ assert_equal 'Christopher Marlowe', permitted[:book][:authors][1][:name]
+ end
+
+ test "nested arrays with strings" do
+ params = ActionController::Parameters.new({
+ book: {
+ genres: ["Tragedy"]
+ }
+ })
+
+ permitted = params.permit book: {genres: []}
+ assert_equal ["Tragedy"], permitted[:book][:genres]
+ end
+
+ test "permit may specify symbols or strings" do
+ params = ActionController::Parameters.new({
+ book: {
+ title: "Romeo and Juliet",
+ author: "William Shakespeare"
+ },
+ magazine: "Shakespeare Today"
+ })
+
+ permitted = params.permit({book: ["title", :author]}, "magazine")
+ assert_equal "Romeo and Juliet", permitted[:book][:title]
+ assert_equal "William Shakespeare", permitted[:book][:author]
+ assert_equal "Shakespeare Today", permitted[:magazine]
+ end
+
+ test "nested array with strings that should be hashes" do
+ params = ActionController::Parameters.new({
+ book: {
+ genres: ["Tragedy"]
+ }
+ })
+
+ permitted = params.permit book: { genres: :type }
+ assert_empty permitted[:book][:genres]
+ end
+
+ test "nested array with strings that should be hashes and additional values" do
+ params = ActionController::Parameters.new({
+ book: {
+ title: "Romeo and Juliet",
+ genres: ["Tragedy"]
+ }
+ })
+
+ permitted = params.permit book: [ :title, { genres: :type } ]
+ assert_equal "Romeo and Juliet", permitted[:book][:title]
+ assert_empty permitted[:book][:genres]
+ end
+
+ test "nested string that should be a hash" do
+ params = ActionController::Parameters.new({
+ book: {
+ genre: "Tragedy"
+ }
+ })
+
+ permitted = params.permit book: { genre: :type }
+ assert_nil permitted[:book][:genre]
+ end
+
+ test "fields_for-style nested params" do
+ params = ActionController::Parameters.new({
+ book: {
+ authors_attributes: {
+ :'0' => { name: 'William Shakespeare', age_of_death: '52' },
+ :'1' => { name: 'Unattributed Assistant' },
+ :'2' => { name: %w(injected names)}
+ }
+ }
+ })
+ permitted = params.permit book: { authors_attributes: [ :name ] }
+
+ assert_not_nil permitted[:book][:authors_attributes]['0']
+ assert_not_nil permitted[:book][:authors_attributes]['1']
+ assert_empty permitted[:book][:authors_attributes]['2']
+ assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name]
+ assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['1'][:name]
+
+ assert_filtered_out permitted[:book][:authors_attributes]['0'], :age_of_death
+ end
+
+ test "fields_for-style nested params with negative numbers" do
+ params = ActionController::Parameters.new({
+ book: {
+ authors_attributes: {
+ :'-1' => { name: 'William Shakespeare', age_of_death: '52' },
+ :'-2' => { name: 'Unattributed Assistant' }
+ }
+ }
+ })
+ permitted = params.permit book: { authors_attributes: [:name] }
+
+ assert_not_nil permitted[:book][:authors_attributes]['-1']
+ assert_not_nil permitted[:book][:authors_attributes]['-2']
+ assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['-1'][:name]
+ assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-2'][:name]
+
+ assert_filtered_out permitted[:book][:authors_attributes]['-1'], :age_of_death
+ end
+
+ test "nested number as key" do
+ params = ActionController::Parameters.new({
+ product: {
+ properties: {
+ '0' => "prop0",
+ '1' => "prop1"
+ }
+ }
+ })
+ params = params.require(:product).permit(:properties => ["0"])
+ assert_not_nil params[:properties]["0"]
+ assert_nil params[:properties]["1"]
+ assert_equal "prop0", params[:properties]["0"]
+ end
+end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
new file mode 100644
index 0000000000..aa894ffa17
--- /dev/null
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -0,0 +1,280 @@
+require 'abstract_unit'
+require 'action_dispatch/http/upload'
+require 'action_controller/metal/strong_parameters'
+
+class ParametersPermitTest < ActiveSupport::TestCase
+ def assert_filtered_out(params, key)
+ assert !params.has_key?(key), "key #{key.inspect} has not been filtered out"
+ end
+
+ setup do
+ @params = ActionController::Parameters.new(
+ person: {
+ age: '32',
+ name: {
+ first: 'David',
+ last: 'Heinemeier Hansson'
+ },
+ addresses: [{city: 'Chicago', state: 'Illinois'}]
+ }
+ )
+
+ @struct_fields = []
+ %w(0 1 12).each do |number|
+ ['', 'i', 'f'].each do |suffix|
+ @struct_fields << "sf(#{number}#{suffix})"
+ end
+ end
+ end
+
+ test 'if nothing is permitted, the hash becomes empty' do
+ params = ActionController::Parameters.new(id: '1234')
+ permitted = params.permit
+ assert permitted.permitted?
+ assert permitted.empty?
+ end
+
+ test 'key: permitted scalar values' do
+ values = ['a', :a, nil]
+ values += [0, 1.0, 2**128, BigDecimal.new(1)]
+ values += [true, false]
+ values += [Date.today, Time.now, DateTime.now]
+ values += [STDOUT, StringIO.new, ActionDispatch::Http::UploadedFile.new(tempfile: __FILE__),
+ Rack::Test::UploadedFile.new(__FILE__)]
+
+ values.each do |value|
+ params = ActionController::Parameters.new(id: value)
+ permitted = params.permit(:id)
+ assert_equal value, permitted[:id]
+
+ @struct_fields.each do |sf|
+ params = ActionController::Parameters.new(sf => value)
+ permitted = params.permit(:sf)
+ assert_equal value, permitted[sf]
+ end
+ end
+ end
+
+ test 'key: unknown keys are filtered out' do
+ params = ActionController::Parameters.new(id: '1234', injected: 'injected')
+ permitted = params.permit(:id)
+ assert_equal '1234', permitted[:id]
+ assert_filtered_out permitted, :injected
+ end
+
+ test 'key: arrays are filtered out' do
+ [[], [1], ['1']].each do |array|
+ params = ActionController::Parameters.new(id: array)
+ permitted = params.permit(:id)
+ assert_filtered_out permitted, :id
+
+ @struct_fields.each do |sf|
+ params = ActionController::Parameters.new(sf => array)
+ permitted = params.permit(:sf)
+ assert_filtered_out permitted, sf
+ end
+ end
+ end
+
+ test 'key: hashes are filtered out' do
+ [{}, {foo: 1}, {foo: 'bar'}].each do |hash|
+ params = ActionController::Parameters.new(id: hash)
+ permitted = params.permit(:id)
+ assert_filtered_out permitted, :id
+
+ @struct_fields.each do |sf|
+ params = ActionController::Parameters.new(sf => hash)
+ permitted = params.permit(:sf)
+ assert_filtered_out permitted, sf
+ end
+ end
+ end
+
+ test 'key: non-permitted scalar values are filtered out' do
+ params = ActionController::Parameters.new(id: Object.new)
+ permitted = params.permit(:id)
+ assert_filtered_out permitted, :id
+
+ @struct_fields.each do |sf|
+ params = ActionController::Parameters.new(sf => Object.new)
+ permitted = params.permit(:sf)
+ assert_filtered_out permitted, sf
+ end
+ end
+
+ test 'key: it is not assigned if not present in params' do
+ params = ActionController::Parameters.new(name: 'Joe')
+ permitted = params.permit(:id)
+ assert !permitted.has_key?(:id)
+ end
+
+ test 'key to empty array: empty arrays pass' do
+ params = ActionController::Parameters.new(id: [])
+ permitted = params.permit(id: [])
+ assert_equal [], permitted[:id]
+ end
+
+ test 'do not break params filtering on nil values' do
+ params = ActionController::Parameters.new(a: 1, b: [1, 2, 3], c: nil)
+
+ permitted = params.permit(:a, c: [], b: [])
+ assert_equal 1, permitted[:a]
+ assert_equal [1, 2, 3], permitted[:b]
+ assert_equal nil, permitted[:c]
+ end
+
+ test 'key to empty array: arrays of permitted scalars pass' do
+ [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array|
+ params = ActionController::Parameters.new(id: array)
+ permitted = params.permit(id: [])
+ assert_equal array, permitted[:id]
+ end
+ end
+
+ test 'key to empty array: permitted scalar values do not pass' do
+ ['foo', 1].each do |permitted_scalar|
+ params = ActionController::Parameters.new(id: permitted_scalar)
+ permitted = params.permit(id: [])
+ assert_filtered_out permitted, :id
+ end
+ end
+
+ test 'key to empty array: arrays of non-permitted scalar do not pass' do
+ [[Object.new], [[]], [[1]], [{}], [{id: '1'}]].each do |non_permitted_scalar|
+ params = ActionController::Parameters.new(id: non_permitted_scalar)
+ permitted = params.permit(id: [])
+ assert_filtered_out permitted, :id
+ end
+ end
+
+ test "fetch raises ParameterMissing exception" do
+ e = assert_raises(ActionController::ParameterMissing) do
+ @params.fetch :foo
+ end
+ assert_equal :foo, e.param
+ end
+
+ test "fetch with a default value of a hash does not mutate the object" do
+ params = ActionController::Parameters.new({})
+ params.fetch :foo, {}
+ assert_equal nil, params[:foo]
+ end
+
+ test 'hashes in array values get wrapped' do
+ params = ActionController::Parameters.new(foo: [{}, {}])
+ params[:foo].each do |hash|
+ assert !hash.permitted?
+ end
+ end
+
+ # Strong params has an optimization to avoid looping every time you read
+ # a key whose value is an array and building a new object. We check that
+ # optimization here.
+ test 'arrays are converted at most once' do
+ params = ActionController::Parameters.new(foo: [{}])
+ assert_same params[:foo], params[:foo]
+ end
+
+ # Strong params has an internal cache to avoid duplicated loops in the most
+ # common usage pattern. See the docs of the method `converted_arrays`.
+ #
+ # This test checks that if we push a hash to an array (in-place modification)
+ # the cache does not get fooled, the hash is still wrapped as strong params,
+ # and not permitted.
+ test 'mutated arrays are detected' do
+ params = ActionController::Parameters.new(users: [{id: 1}])
+
+ permitted = params.permit(users: [:id])
+ permitted[:users] << {injected: 1}
+ assert_not permitted[:users].last.permitted?
+ end
+
+ test "fetch doesnt raise ParameterMissing exception if there is a default" do
+ assert_equal "monkey", @params.fetch(:foo, "monkey")
+ assert_equal "monkey", @params.fetch(:foo) { "monkey" }
+ end
+
+ test "not permitted is sticky on accessors" do
+ assert !@params.slice(:person).permitted?
+ assert !@params[:person][:name].permitted?
+ assert !@params[:person].except(:name).permitted?
+
+ @params.each { |key, value| assert(!value.permitted?) if key == "person" }
+
+ assert !@params.fetch(:person).permitted?
+
+ assert !@params.values_at(:person).first.permitted?
+ end
+
+ test "permitted is sticky on accessors" do
+ @params.permit!
+ assert @params.slice(:person).permitted?
+ assert @params[:person][:name].permitted?
+ assert @params[:person].except(:name).permitted?
+
+ @params.each { |key, value| assert(value.permitted?) if key == "person" }
+
+ assert @params.fetch(:person).permitted?
+
+ assert @params.values_at(:person).first.permitted?
+ end
+
+ test "not permitted is sticky on mutators" do
+ assert !@params.delete_if { |k| k == "person" }.permitted?
+ assert !@params.keep_if { |k,v| k == "person" }.permitted?
+ end
+
+ test "permitted is sticky on mutators" do
+ @params.permit!
+ assert @params.delete_if { |k| k == "person" }.permitted?
+ assert @params.keep_if { |k,v| k == "person" }.permitted?
+ end
+
+ test "not permitted is sticky beyond merges" do
+ assert !@params.merge(a: "b").permitted?
+ end
+
+ test "permitted is sticky beyond merges" do
+ @params.permit!
+ assert @params.merge(a: "b").permitted?
+ end
+
+ test "modifying the parameters" do
+ @params[:person][:hometown] = "Chicago"
+ @params[:person][:family] = { brother: "Jonas" }
+
+ assert_equal "Chicago", @params[:person][:hometown]
+ assert_equal "Jonas", @params[:person][:family][:brother]
+ end
+
+ test "permit state is kept on a dup" do
+ @params.permit!
+ assert_equal @params.permitted?, @params.dup.permitted?
+ end
+
+ test "permit is recursive" do
+ @params.permit!
+ assert @params.permitted?
+ assert @params[:person].permitted?
+ assert @params[:person][:name].permitted?
+ assert @params[:person][:addresses][0].permitted?
+ end
+
+ test "permitted takes a default value when Parameters.permit_all_parameters is set" do
+ begin
+ ActionController::Parameters.permit_all_parameters = true
+ params = ActionController::Parameters.new({ person: {
+ age: "32", name: { first: "David", last: "Heinemeier Hansson" }
+ }})
+
+ assert params.slice(:person).permitted?
+ assert params[:person][:name].permitted?
+ ensure
+ ActionController::Parameters.permit_all_parameters = false
+ end
+ end
+
+ test "permitting parameters as an array" do
+ assert_equal "32", @params[:person].permit([ :age ])[:age]
+ end
+end
diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb
new file mode 100644
index 0000000000..f9cc9f96f1
--- /dev/null
+++ b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb
@@ -0,0 +1,33 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase
+ def setup
+ ActionController::Parameters.action_on_unpermitted_parameters = :raise
+ end
+
+ def teardown
+ ActionController::Parameters.action_on_unpermitted_parameters = false
+ end
+
+ test "raises on unexpected params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65 },
+ fishing: "Turnips"
+ })
+
+ assert_raises(ActionController::UnpermittedParameters) do
+ params.permit(book: [:pages])
+ end
+ end
+
+ test "raises on unexpected nested params" do
+ params = ActionController::Parameters.new({
+ book: { pages: 65, title: "Green Cats and where to find then." }
+ })
+
+ assert_raises(ActionController::UnpermittedParameters) do
+ params.permit(book: [:pages])
+ end
+ end
+end
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
new file mode 100644
index 0000000000..645ecae220
--- /dev/null
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -0,0 +1,362 @@
+require 'abstract_unit'
+
+module Admin; class User; end; end
+
+module ParamsWrapperTestHelp
+ def with_default_wrapper_options(&block)
+ @controller.class._set_wrapper_options({:format => [:json]})
+ @controller.class.inherited(@controller.class)
+ yield
+ end
+
+ def assert_parameters(expected)
+ assert_equal expected, self.class.controller_class.last_parameters
+ end
+end
+
+class ParamsWrapperTest < ActionController::TestCase
+ include ParamsWrapperTestHelp
+
+ class UsersController < ActionController::Base
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end
+
+ class User; end
+ class Person; end
+
+ tests UsersController
+
+ def teardown
+ UsersController.last_parameters = nil
+ end
+
+ def test_filtered_parameters
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_equal @request.filtered_parameters, { 'controller' => 'params_wrapper_test/users', 'action' => 'parse', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' } }
+ end
+ end
+
+ def test_derived_name_from_controller
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_wrapper_name
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_wrapper_model
+ with_default_wrapper_options do
+ UsersController.wrap_parameters Person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_include_option
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :include => :username
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_exclude_option
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :exclude => :title
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_specify_both_wrapper_name_and_include_option
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :person, :include => :username
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_not_enabled_format
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/xml'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
+ end
+ end
+
+ def test_wrap_parameters_false
+ with_default_wrapper_options do
+ UsersController.wrap_parameters false
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' })
+ end
+ end
+
+ def test_specify_format
+ with_default_wrapper_options do
+ UsersController.wrap_parameters :format => :xml
+
+ @request.env['CONTENT_TYPE'] = 'application/xml'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
+ end
+ end
+
+ def test_not_wrap_reserved_parameters
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu' }
+ assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '&#9731;', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_no_double_wrap_if_key_exists
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'user' => { 'username' => 'sikachu' }}
+ assert_parameters({ 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_nested_params
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'person' => { 'username' => 'sikachu' }}
+ assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}})
+ end
+ end
+
+ def test_derived_wrapped_keys_from_matching_model
+ User.expects(:respond_to?).with(:attribute_names).returns(true)
+ User.expects(:attribute_names).twice.returns(["username"])
+
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_derived_wrapped_keys_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:attribute_names).returns(true)
+ Person.expects(:attribute_names).twice.returns(["username"])
+
+ UsersController.wrap_parameters Person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_not_wrapping_abstract_model
+ User.expects(:respond_to?).with(:attribute_names).returns(true)
+ User.expects(:attribute_names).returns([])
+
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }})
+ end
+ end
+
+ def test_preserves_query_string_params
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ get :parse, { 'user' => { 'username' => 'nixon' } }
+ assert_parameters(
+ {'user' => { 'username' => 'nixon' } }
+ )
+ end
+ end
+
+ def test_empty_parameter_set
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, {}
+ assert_parameters(
+ {'user' => { } }
+ )
+ end
+ end
+end
+
+class NamespacedParamsWrapperTest < ActionController::TestCase
+ include ParamsWrapperTestHelp
+
+ module Admin
+ module Users
+ class UsersController < ActionController::Base;
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end
+ end
+ end
+
+ class SampleOne
+ def self.attribute_names
+ ["username"]
+ end
+ end
+
+ class SampleTwo
+ def self.attribute_names
+ ["title"]
+ end
+ end
+
+ tests Admin::Users::UsersController
+
+ def teardown
+ Admin::Users::UsersController.last_parameters = nil
+ end
+
+ def test_derived_name_from_controller
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_namespace_lookup_from_model
+ Admin.const_set(:User, Class.new(SampleOne))
+ begin
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ ensure
+ Admin.send :remove_const, :User
+ end
+ end
+
+ def test_hierarchy_namespace_lookup_from_model
+ Object.const_set(:User, Class.new(SampleTwo))
+ begin
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'title' => 'Developer' }})
+ end
+ ensure
+ Object.send :remove_const, :User
+ end
+ end
+
+end
+
+class AnonymousControllerParamsWrapperTest < ActionController::TestCase
+ include ParamsWrapperTestHelp
+
+ tests(Class.new(ActionController::Base) do
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end)
+
+ def test_does_not_implicitly_wrap_params
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu' })
+ end
+ end
+
+ def test_does_wrap_params_if_name_provided
+ with_default_wrapper_options do
+ @controller.class.wrap_parameters(:name => "guest")
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu' }
+ assert_parameters({ 'username' => 'sikachu', 'guest' => { 'username' => 'sikachu' }})
+ end
+ end
+end
+
+class IrregularInflectionParamsWrapperTest < ActionController::TestCase
+ include ParamsWrapperTestHelp
+
+ class ParamswrappernewsItem
+ def self.attribute_names
+ ['test_attr']
+ end
+ end
+
+ class ParamswrappernewsController < ActionController::Base
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end
+
+ tests ParamswrappernewsController
+
+ def test_uses_model_attribute_names_with_irregular_inflection
+ with_dup do
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'paramswrappernews_item', 'paramswrappernews'
+ end
+
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' }
+ assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }})
+ end
+ end
+ end
+
+ private
+
+ def with_dup
+ original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
+ yield
+ ensure
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
+ end
+end
diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb
new file mode 100644
index 0000000000..f46249d712
--- /dev/null
+++ b/actionpack/test/controller/permitted_params_test.rb
@@ -0,0 +1,25 @@
+require 'abstract_unit'
+
+class PeopleController < ActionController::Base
+ def create
+ render text: params[:person].permitted? ? "permitted" : "forbidden"
+ end
+
+ def create_with_permit
+ render text: params[:person].permit(:name).permitted? ? "permitted" : "forbidden"
+ end
+end
+
+class ActionControllerPermittedParamsTest < ActionController::TestCase
+ tests PeopleController
+
+ test "parameters are forbidden" do
+ post :create, { person: { name: "Mjallo!" } }
+ assert_equal "forbidden", response.body
+ end
+
+ test "parameters can be permitted and are then not forbidden" do
+ post :create_with_permit, { person: { name: "Mjallo!" } }
+ assert_equal "permitted", response.body
+ end
+end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
new file mode 100644
index 0000000000..103ca9c776
--- /dev/null
+++ b/actionpack/test/controller/redirect_test.rb
@@ -0,0 +1,354 @@
+require 'abstract_unit'
+
+class WorkshopsController < ActionController::Base
+end
+
+class RedirectController < ActionController::Base
+ # empty method not used anywhere to ensure methods like
+ # `status` and `location` aren't called on `redirect_to` calls
+ def status; render :text => 'called status'; end
+ def location; render :text => 'called location'; end
+
+ def simple_redirect
+ redirect_to :action => "hello_world"
+ end
+
+ def redirect_with_status
+ redirect_to({:action => "hello_world", :status => 301})
+ end
+
+ def redirect_with_status_hash
+ redirect_to({:action => "hello_world"}, {:status => 301})
+ end
+
+ def redirect_with_protocol
+ redirect_to :action => "hello_world", :protocol => "https"
+ end
+
+ def url_redirect_with_status
+ redirect_to("http://www.example.com", :status => :moved_permanently)
+ end
+
+ def url_redirect_with_status_hash
+ redirect_to("http://www.example.com", {:status => 301})
+ end
+
+ def relative_url_redirect_with_status
+ redirect_to("/things/stuff", :status => :found)
+ end
+
+ def relative_url_redirect_with_status_hash
+ redirect_to("/things/stuff", {:status => 301})
+ end
+
+ def redirect_to_back_with_status
+ redirect_to :back, :status => 307
+ end
+
+ def host_redirect
+ redirect_to :action => "other_host", :only_path => false, :host => 'other.test.host'
+ end
+
+ def module_redirect
+ redirect_to :controller => 'module_test/module_redirect', :action => "hello_world"
+ end
+
+ def redirect_with_assigns
+ @hello = "world"
+ redirect_to :action => "hello_world"
+ end
+
+ def redirect_to_url
+ redirect_to "http://www.rubyonrails.org/"
+ end
+
+ def redirect_to_url_with_unescaped_query_string
+ redirect_to "http://dev.rubyonrails.org/query?status=new"
+ end
+
+ def redirect_to_url_with_complex_scheme
+ redirect_to "x-test+scheme.complex:redirect"
+ end
+
+ def redirect_to_url_with_network_path_reference
+ redirect_to "//www.rubyonrails.org/"
+ end
+
+ def redirect_to_back
+ redirect_to :back
+ end
+
+ def redirect_to_existing_record
+ redirect_to Workshop.new(5)
+ end
+
+ def redirect_to_new_record
+ redirect_to Workshop.new(nil)
+ end
+
+ def redirect_to_nil
+ redirect_to nil
+ end
+
+ def redirect_to_params
+ redirect_to ActionController::Parameters.new(status: 200, protocol: 'javascript', f: '%0Aeval(name)')
+ end
+
+ def redirect_to_with_block
+ redirect_to proc { "http://www.rubyonrails.org/" }
+ end
+
+ def redirect_to_with_block_and_assigns
+ @url = "http://www.rubyonrails.org/"
+ redirect_to proc { @url }
+ end
+
+ def redirect_to_with_block_and_options
+ redirect_to proc { {:action => "hello_world"} }
+ end
+
+ def redirect_with_header_break
+ redirect_to "/lol\r\nwat"
+ end
+
+ def redirect_with_null_bytes
+ redirect_to "\000/lol\r\nwat"
+ end
+
+ def rescue_errors(e) raise e end
+
+ protected
+ def dashbord_url(id, message)
+ url_for :action => "dashboard", :params => { "id" => id, "message" => message }
+ end
+end
+
+class RedirectTest < ActionController::TestCase
+ tests RedirectController
+
+ def test_simple_redirect
+ get :simple_redirect
+ assert_response :redirect
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_header_break
+ get :redirect_with_header_break
+ assert_response :redirect
+ assert_equal "http://test.host/lolwat", redirect_to_url
+ end
+
+ def test_redirect_with_null_bytes
+ get :redirect_with_null_bytes
+ assert_response :redirect
+ assert_equal "http://test.host/lolwat", redirect_to_url
+ end
+
+ def test_redirect_with_no_status
+ get :simple_redirect
+ assert_response 302
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_status
+ get :redirect_with_status
+ assert_response 301
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_status_hash
+ get :redirect_with_status_hash
+ assert_response 301
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_redirect_with_protocol
+ get :redirect_with_protocol
+ assert_response 302
+ assert_equal "https://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_url_redirect_with_status
+ get :url_redirect_with_status
+ assert_response 301
+ assert_equal "http://www.example.com", redirect_to_url
+ end
+
+ def test_url_redirect_with_status_hash
+ get :url_redirect_with_status_hash
+ assert_response 301
+ assert_equal "http://www.example.com", redirect_to_url
+ end
+
+
+ def test_relative_url_redirect_with_status
+ get :relative_url_redirect_with_status
+ assert_response 302
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_relative_url_redirect_with_status_hash
+ get :relative_url_redirect_with_status_hash
+ assert_response 301
+ assert_equal "http://test.host/things/stuff", redirect_to_url
+ end
+
+ def test_redirect_to_back_with_status
+ @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
+ get :redirect_to_back_with_status
+ assert_response 307
+ assert_equal "http://www.example.com/coming/from", redirect_to_url
+ end
+
+ def test_simple_redirect_using_options
+ get :host_redirect
+ assert_response :redirect
+ assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
+ end
+
+ def test_module_redirect
+ get :module_redirect
+ assert_response :redirect
+ assert_redirected_to "http://test.host/module_test/module_redirect/hello_world"
+ end
+
+ def test_module_redirect_using_options
+ get :module_redirect
+ assert_response :redirect
+ assert_redirected_to :controller => 'module_test/module_redirect', :action => 'hello_world'
+ end
+
+ def test_redirect_with_assigns
+ get :redirect_with_assigns
+ assert_response :redirect
+ assert_equal "world", assigns["hello"]
+ end
+
+ def test_redirect_to_url
+ get :redirect_to_url
+ assert_response :redirect
+ assert_redirected_to "http://www.rubyonrails.org/"
+ end
+
+ def test_redirect_to_url_with_unescaped_query_string
+ get :redirect_to_url_with_unescaped_query_string
+ assert_response :redirect
+ assert_redirected_to "http://dev.rubyonrails.org/query?status=new"
+ end
+
+ def test_redirect_to_url_with_complex_scheme
+ get :redirect_to_url_with_complex_scheme
+ assert_response :redirect
+ assert_equal "x-test+scheme.complex:redirect", redirect_to_url
+ end
+
+ def test_redirect_to_url_with_network_path_reference
+ get :redirect_to_url_with_network_path_reference
+ assert_response :redirect
+ assert_equal "//www.rubyonrails.org/", redirect_to_url
+ end
+
+ def test_redirect_to_back
+ @request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
+ get :redirect_to_back
+ assert_response :redirect
+ assert_equal "http://www.example.com/coming/from", redirect_to_url
+ end
+
+ def test_redirect_to_back_with_no_referer
+ assert_raise(ActionController::RedirectBackError) {
+ @request.env["HTTP_REFERER"] = nil
+ get :redirect_to_back
+ }
+ end
+
+ def test_redirect_to_record
+ with_routing do |set|
+ set.draw do
+ resources :workshops
+ get ':controller/:action'
+ end
+
+ get :redirect_to_existing_record
+ assert_equal "http://test.host/workshops/5", redirect_to_url
+ assert_redirected_to Workshop.new(5)
+
+ get :redirect_to_new_record
+ assert_equal "http://test.host/workshops", redirect_to_url
+ assert_redirected_to Workshop.new(nil)
+ end
+ end
+
+ def test_redirect_to_nil
+ assert_raise(ActionController::ActionControllerError) do
+ get :redirect_to_nil
+ end
+ end
+
+ def test_redirect_to_params
+ assert_raise(ActionController::ActionControllerError) do
+ get :redirect_to_params
+ end
+ end
+
+ def test_redirect_to_with_block
+ get :redirect_to_with_block
+ assert_response :redirect
+ assert_redirected_to "http://www.rubyonrails.org/"
+ end
+
+ def test_redirect_to_with_block_and_assigns
+ get :redirect_to_with_block_and_assigns
+ assert_response :redirect
+ assert_redirected_to "http://www.rubyonrails.org/"
+ end
+
+ def test_redirect_to_with_block_and_accepted_options
+ with_routing do |set|
+ set.draw do
+ get ':controller/:action'
+ end
+
+ get :redirect_to_with_block_and_options
+
+ assert_response :redirect
+ assert_redirected_to "http://test.host/redirect/hello_world"
+ end
+ end
+end
+
+module ModuleTest
+ class ModuleRedirectController < ::RedirectController
+ def module_redirect
+ redirect_to :controller => '/redirect', :action => "hello_world"
+ end
+ end
+
+ class ModuleRedirectTest < ActionController::TestCase
+ tests ModuleRedirectController
+
+ def test_simple_redirect
+ get :simple_redirect
+ assert_response :redirect
+ assert_equal "http://test.host/module_test/module_redirect/hello_world", redirect_to_url
+ end
+
+ def test_simple_redirect_using_options
+ get :host_redirect
+ assert_response :redirect
+ assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
+ end
+
+ def test_module_redirect
+ get :module_redirect
+ assert_response :redirect
+ assert_equal "http://test.host/redirect/hello_world", redirect_to_url
+ end
+
+ def test_module_redirect_using_options
+ get :module_redirect
+ assert_response :redirect
+ assert_redirected_to :controller => '/redirect', :action => "hello_world"
+ end
+ end
+end
diff --git a/actionpack/test/controller/render_js_test.rb b/actionpack/test/controller/render_js_test.rb
new file mode 100644
index 0000000000..d550422a2f
--- /dev/null
+++ b/actionpack/test/controller/render_js_test.rb
@@ -0,0 +1,34 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+require 'pathname'
+
+class RenderJSTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ protect_from_forgery
+
+ def self.controller_path
+ 'test'
+ end
+
+ def render_vanilla_js_hello
+ render :js => "alert('hello')"
+ end
+
+ def show_partial
+ render :partial => 'partial'
+ end
+ end
+
+ tests TestController
+
+ def test_render_vanilla_js
+ xhr :get, :render_vanilla_js_hello
+ assert_equal "alert('hello')", @response.body
+ assert_equal "text/javascript", @response.content_type
+ end
+
+ def test_should_render_js_partial
+ xhr :get, :show_partial, :format => 'js'
+ assert_equal 'partial js', @response.body
+ end
+end
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
new file mode 100644
index 0000000000..ada978aa11
--- /dev/null
+++ b/actionpack/test/controller/render_json_test.rb
@@ -0,0 +1,136 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+require 'active_support/logger'
+require 'pathname'
+
+class RenderJsonTest < ActionController::TestCase
+ class JsonRenderable
+ def as_json(options={})
+ hash = { :a => :b, :c => :d, :e => :f }
+ hash.except!(*options[:except]) if options[:except]
+ hash
+ end
+
+ def to_json(options = {})
+ super :except => [:c, :e]
+ end
+ end
+
+ class TestController < ActionController::Base
+ protect_from_forgery
+
+ def self.controller_path
+ 'test'
+ end
+
+ def render_json_nil
+ render :json => nil
+ end
+
+ def render_json_render_to_string
+ render :text => render_to_string(:json => '[]')
+ end
+
+ def render_json_hello_world
+ render :json => ActiveSupport::JSON.encode(:hello => 'world')
+ end
+
+ def render_json_hello_world_with_status
+ render :json => ActiveSupport::JSON.encode(:hello => 'world'), :status => 401
+ end
+
+ def render_json_hello_world_with_callback
+ render :json => ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert'
+ end
+
+ def render_json_with_custom_content_type
+ render :json => ActiveSupport::JSON.encode(:hello => 'world'), :content_type => 'text/javascript'
+ end
+
+ def render_symbol_json
+ render :json => ActiveSupport::JSON.encode(:hello => 'world')
+ end
+
+ def render_json_with_render_to_string
+ render :json => {:hello => render_to_string(:partial => 'partial')}
+ end
+
+ def render_json_with_extra_options
+ render :json => JsonRenderable.new, :except => [:c, :e]
+ end
+
+ def render_json_without_options
+ render :json => JsonRenderable.new
+ end
+ end
+
+ tests TestController
+
+ def setup
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ super
+ @controller.logger = ActiveSupport::Logger.new(nil)
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_render_json_nil
+ get :render_json_nil
+ assert_equal 'null', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_render_to_string
+ get :render_json_render_to_string
+ assert_equal '[]', @response.body
+ end
+
+
+ def test_render_json
+ get :render_json_hello_world
+ assert_equal '{"hello":"world"}', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_with_status
+ get :render_json_hello_world_with_status
+ assert_equal '{"hello":"world"}', @response.body
+ assert_equal 401, @response.status
+ end
+
+ def test_render_json_with_callback
+ xhr :get, :render_json_hello_world_with_callback
+ assert_equal '/**/alert({"hello":"world"})', @response.body
+ assert_equal 'text/javascript', @response.content_type
+ end
+
+ def test_render_json_with_custom_content_type
+ xhr :get, :render_json_with_custom_content_type
+ assert_equal '{"hello":"world"}', @response.body
+ assert_equal 'text/javascript', @response.content_type
+ end
+
+ def test_render_symbol_json
+ get :render_symbol_json
+ assert_equal '{"hello":"world"}', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_with_render_to_string
+ get :render_json_with_render_to_string
+ assert_equal '{"hello":"partial html"}', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_forwards_extra_options
+ get :render_json_with_extra_options
+ assert_equal '{"a":"b"}', @response.body
+ assert_equal 'application/json', @response.content_type
+ end
+
+ def test_render_json_calls_to_json_from_object
+ get :render_json_without_options
+ assert_equal '{"a":"b"}', @response.body
+ end
+end
diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb
new file mode 100644
index 0000000000..af50e11261
--- /dev/null
+++ b/actionpack/test/controller/render_other_test.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+
+
+class RenderOtherTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def render_simon_says
+ render :simon => "foo"
+ end
+ end
+
+ tests TestController
+
+ def test_using_custom_render_option
+ ActionController.add_renderer :simon do |says, options|
+ self.content_type = Mime::TEXT
+ self.response_body = "Simon says: #{says}"
+ end
+
+ get :render_simon_says
+ assert_equal "Simon says: foo", @response.body
+ ensure
+ ActionController.remove_renderer :simon
+ end
+end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
new file mode 100644
index 0000000000..9926130c02
--- /dev/null
+++ b/actionpack/test/controller/render_test.rb
@@ -0,0 +1,530 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+require 'pathname'
+
+class TestControllerWithExtraEtags < ActionController::Base
+ etag { nil }
+ etag { 'ab' }
+ etag { :cde }
+ etag { [:f] }
+ etag { nil }
+
+ def fresh
+ render text: "stale" if stale?(etag: '123')
+ end
+
+ def array
+ render text: "stale" if stale?(etag: %w(1 2 3))
+ end
+end
+
+class TestController < ActionController::Base
+ protect_from_forgery
+
+ before_action :set_variable_for_layout
+
+ class LabellingFormBuilder < ActionView::Helpers::FormBuilder
+ end
+
+ layout :determine_layout
+
+ def name
+ nil
+ end
+
+ private :name
+ helper_method :name
+
+ def hello_world
+ end
+
+ def conditional_hello
+ if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123])
+ render :action => 'hello_world'
+ end
+ end
+
+ def conditional_hello_with_record
+ record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
+
+ if stale?(record)
+ render :action => 'hello_world'
+ end
+ end
+
+ def conditional_hello_with_public_header
+ if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
+ render :action => 'hello_world'
+ end
+ end
+
+ def conditional_hello_with_public_header_with_record
+ record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
+
+ if stale?(record, :public => true)
+ render :action => 'hello_world'
+ end
+ end
+
+ def conditional_hello_with_public_header_and_expires_at
+ expires_in 1.minute
+ if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
+ render :action => 'hello_world'
+ end
+ end
+
+ def conditional_hello_with_expires_in
+ expires_in 60.1.seconds
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public
+ expires_in 1.minute, :public => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_must_revalidate
+ expires_in 1.minute, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_and_must_revalidate
+ expires_in 1.minute, :public => true, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_with_more_keys
+ expires_in 1.minute, :public => true, 's-maxage' => 5.hours
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
+ expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_now
+ expires_now
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_cache_control_headers
+ response.headers['Cache-Control'] = 'no-transform'
+ expires_now
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_bangs
+ render :action => 'hello_world'
+ end
+ before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs
+
+ def handle_last_modified_and_etags
+ fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
+ end
+
+ def heading
+ head :ok
+ end
+
+ # :ported:
+ def double_render
+ render :text => "hello"
+ render :text => "world"
+ end
+
+ def double_redirect
+ redirect_to :action => "double_render"
+ redirect_to :action => "double_render"
+ end
+
+ def render_and_redirect
+ render :text => "hello"
+ redirect_to :action => "double_render"
+ end
+
+ def render_to_string_and_render
+ @stuff = render_to_string :text => "here is some cached stuff"
+ render :text => "Hi web users! #{@stuff}"
+ end
+
+ def render_to_string_with_inline_and_render
+ render_to_string :inline => "<%= 'dlrow olleh'.reverse %>"
+ render :template => "test/hello_world"
+ end
+
+ def rendering_with_conflicting_local_vars
+ @name = "David"
+ render :action => "potential_conflicts"
+ end
+
+ def hello_world_from_rxml_using_action
+ render :action => "hello_world_from_rxml", :handlers => [:builder]
+ end
+
+ # :deprecated:
+ def hello_world_from_rxml_using_template
+ render :template => "test/hello_world_from_rxml", :handlers => [:builder]
+ end
+
+ def head_created
+ head :created
+ end
+
+ def head_created_with_application_json_content_type
+ head :created, :content_type => "application/json"
+ end
+
+ def head_ok_with_image_png_content_type
+ head :ok, :content_type => "image/png"
+ end
+
+ def head_with_location_header
+ head :location => "/foo"
+ end
+
+ def head_with_location_object
+ head :location => Customer.new("david", 1)
+ end
+
+ def head_with_symbolic_status
+ head :status => params[:status].intern
+ end
+
+ def head_with_integer_status
+ head :status => params[:status].to_i
+ end
+
+ def head_with_string_status
+ head :status => params[:status]
+ end
+
+ def head_with_custom_header
+ head :x_custom_header => "something"
+ end
+
+ def head_with_www_authenticate_header
+ head 'WWW-Authenticate' => 'something'
+ end
+
+ def head_with_status_code_first
+ head :forbidden, :x_custom_header => "something"
+ end
+
+ private
+
+ def set_variable_for_layout
+ @variable_for_layout = nil
+ end
+
+ def determine_layout
+ case action_name
+ when "hello_world", "layout_test", "rendering_without_layout",
+ "rendering_nothing_on_layout", "render_text_hello_world",
+ "render_text_hello_world_with_layout",
+ "hello_world_with_layout_false",
+ "partial_only", "accessing_params_in_template",
+ "accessing_params_in_template_with_layout",
+ "render_with_explicit_template",
+ "render_with_explicit_string_template",
+ "update_page", "update_page_with_instance_variables"
+
+ "layouts/standard"
+ when "action_talk_to_layout", "layout_overriding_layout"
+ "layouts/talk_from_action"
+ when "render_implicit_html_template_from_xhr_request"
+ (request.xhr? ? 'layouts/xhr' : 'layouts/standard')
+ end
+ end
+end
+
+class MetalTestController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionView::Rendering
+ include ActionController::Rendering
+ include ActionController::RackDelegation
+
+
+ def accessing_logger_in_template
+ render :inline => "<%= logger.class %>"
+ end
+end
+
+class ExpiresInRenderTest < ActionController::TestCase
+ tests TestController
+
+ def test_expires_in_header
+ get :conditional_hello_with_expires_in
+ assert_equal "max-age=60, private", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_public
+ get :conditional_hello_with_expires_in_with_public
+ assert_equal "max-age=60, public", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_must_revalidate
+ get :conditional_hello_with_expires_in_with_must_revalidate
+ assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_public_and_must_revalidate
+ get :conditional_hello_with_expires_in_with_public_and_must_revalidate
+ assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_additional_headers
+ get :conditional_hello_with_expires_in_with_public_with_more_keys
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_old_syntax
+ get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_now
+ get :conditional_hello_with_expires_now
+ assert_equal "no-cache", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_now_with_cache_control_headers
+ get :conditional_hello_with_cache_control_headers
+ assert_match(/no-cache/, @response.headers["Cache-Control"])
+ assert_match(/no-transform/, @response.headers["Cache-Control"])
+ end
+
+ def test_date_header_when_expires_in
+ time = Time.mktime(2011,10,30)
+ Time.stubs(:now).returns(time)
+ get :conditional_hello_with_expires_in
+ assert_equal Time.now.httpdate, @response.headers["Date"]
+ end
+end
+
+class LastModifiedRenderTest < ActionController::TestCase
+ tests TestController
+
+ def setup
+ super
+ @last_modified = Time.now.utc.beginning_of_day.httpdate
+ end
+
+ def test_responds_with_last_modified
+ get :conditional_hello
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified
+ @request.if_modified_since = @last_modified
+ get :conditional_hello
+ assert_equal 304, @response.status.to_i
+ assert @response.body.blank?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_but_etag_differs
+ @request.if_modified_since = @last_modified
+ @request.if_none_match = "234"
+ get :conditional_hello
+ assert_response :success
+ end
+
+ def test_request_modified
+ @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
+ get :conditional_hello
+ assert_equal 200, @response.status.to_i
+ assert @response.body.present?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+
+ def test_responds_with_last_modified_with_record
+ get :conditional_hello_with_record
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_with_record
+ @request.if_modified_since = @last_modified
+ get :conditional_hello_with_record
+ assert_equal 304, @response.status.to_i
+ assert @response.body.blank?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_but_etag_differs_with_record
+ @request.if_modified_since = @last_modified
+ @request.if_none_match = "234"
+ get :conditional_hello_with_record
+ assert_response :success
+ end
+
+ def test_request_modified_with_record
+ @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
+ get :conditional_hello_with_record
+ assert_equal 200, @response.status.to_i
+ assert @response.body.present?
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_with_bang_gets_last_modified
+ get :conditional_hello_with_bangs
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ assert_response :success
+ end
+
+ def test_request_with_bang_obeys_last_modified
+ @request.if_modified_since = @last_modified
+ get :conditional_hello_with_bangs
+ assert_response :not_modified
+ end
+
+ def test_last_modified_works_with_less_than_too
+ @request.if_modified_since = 5.years.ago.httpdate
+ get :conditional_hello_with_bangs
+ assert_response :success
+ end
+end
+
+class EtagRenderTest < ActionController::TestCase
+ tests TestControllerWithExtraEtags
+
+ def test_multiple_etags
+ @request.if_none_match = etag(["123", 'ab', :cde, [:f]])
+ get :fresh
+ assert_response :not_modified
+
+ @request.if_none_match = %("nomatch")
+ get :fresh
+ assert_response :success
+ end
+
+ def test_array
+ @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]])
+ get :array
+ assert_response :not_modified
+
+ @request.if_none_match = %("nomatch")
+ get :array
+ assert_response :success
+ end
+
+ def etag(record)
+ Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record)).inspect
+ end
+end
+
+class MetalRenderTest < ActionController::TestCase
+ tests MetalTestController
+
+ def test_access_to_logger_in_view
+ get :accessing_logger_in_template
+ assert_equal "NilClass", @response.body
+ end
+end
+
+class HeadRenderTest < ActionController::TestCase
+ tests TestController
+
+ def setup
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_head_created
+ post :head_created
+ assert @response.body.blank?
+ assert_response :created
+ end
+
+ def test_head_created_with_application_json_content_type
+ post :head_created_with_application_json_content_type
+ assert @response.body.blank?
+ assert_equal "application/json", @response.header["Content-Type"]
+ assert_response :created
+ end
+
+ def test_head_ok_with_image_png_content_type
+ post :head_ok_with_image_png_content_type
+ assert @response.body.blank?
+ assert_equal "image/png", @response.header["Content-Type"]
+ assert_response :ok
+ end
+
+ def test_head_with_location_header
+ get :head_with_location_header
+ assert @response.body.blank?
+ assert_equal "/foo", @response.headers["Location"]
+ assert_response :ok
+ end
+
+ def test_head_with_location_object
+ with_routing do |set|
+ set.draw do
+ resources :customers
+ get ':controller/:action'
+ end
+
+ get :head_with_location_object
+ assert @response.body.blank?
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ assert_response :ok
+ end
+ end
+
+ def test_head_with_custom_header
+ get :head_with_custom_header
+ assert @response.body.blank?
+ assert_equal "something", @response.headers["X-Custom-Header"]
+ assert_response :ok
+ end
+
+ def test_head_with_www_authenticate_header
+ get :head_with_www_authenticate_header
+ assert @response.body.blank?
+ assert_equal "something", @response.headers["WWW-Authenticate"]
+ assert_response :ok
+ end
+
+ def test_head_with_symbolic_status
+ get :head_with_symbolic_status, :status => "ok"
+ assert_equal 200, @response.status
+ assert_response :ok
+
+ get :head_with_symbolic_status, :status => "not_found"
+ assert_equal 404, @response.status
+ assert_response :not_found
+
+ get :head_with_symbolic_status, :status => "no_content"
+ assert_equal 204, @response.status
+ assert !@response.headers.include?('Content-Length')
+ assert_response :no_content
+
+ Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code|
+ get :head_with_symbolic_status, :status => status.to_s
+ assert_equal code, @response.response_code
+ assert_response status
+ end
+ end
+
+ def test_head_with_integer_status
+ Rack::Utils::HTTP_STATUS_CODES.each do |code, message|
+ get :head_with_integer_status, :status => code.to_s
+ assert_equal message, @response.message
+ end
+ end
+
+ def test_head_with_string_status
+ get :head_with_string_status, :status => "404 Eat Dirt"
+ assert_equal 404, @response.response_code
+ assert_equal "Not Found", @response.message
+ assert_response :not_found
+ end
+
+ def test_head_with_status_code_first
+ get :head_with_status_code_first
+ assert_equal 403, @response.response_code
+ assert_equal "Forbidden", @response.message
+ assert_equal "something", @response.headers["X-Custom-Header"]
+ assert_response :forbidden
+ end
+end
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
new file mode 100644
index 0000000000..4f280c4bec
--- /dev/null
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -0,0 +1,97 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+require 'pathname'
+
+class RenderXmlTest < ActionController::TestCase
+ class XmlRenderable
+ def to_xml(options)
+ options[:root] ||= "i-am-xml"
+ "<#{options[:root]}/>"
+ end
+ end
+
+ class TestController < ActionController::Base
+ protect_from_forgery
+
+ def self.controller_path
+ 'test'
+ end
+
+ def render_with_location
+ render :xml => "<hello/>", :location => "http://example.com", :status => 201
+ end
+
+ def render_with_object_location
+ customer = Customer.new("Some guy", 1)
+ render :xml => "<customer/>", :location => customer, :status => :created
+ end
+
+ def render_with_to_xml
+ render :xml => XmlRenderable.new
+ end
+
+ def formatted_xml_erb
+ end
+
+ def render_xml_with_custom_content_type
+ render :xml => "<blah/>", :content_type => "application/atomsvc+xml"
+ end
+
+ def render_xml_with_custom_options
+ render :xml => XmlRenderable.new, :root => "i-am-THE-xml"
+ end
+ end
+
+ tests TestController
+
+ def setup
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ super
+ @controller.logger = ActiveSupport::Logger.new(nil)
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_rendering_with_location_should_set_header
+ get :render_with_location
+ assert_equal "http://example.com", @response.headers["Location"]
+ end
+
+ def test_rendering_xml_should_call_to_xml_if_possible
+ get :render_with_to_xml
+ assert_equal "<i-am-xml/>", @response.body
+ end
+
+ def test_rendering_xml_should_call_to_xml_with_extra_options
+ get :render_xml_with_custom_options
+ assert_equal "<i-am-THE-xml/>", @response.body
+ end
+
+ def test_rendering_with_object_location_should_set_header_with_url_for
+ with_routing do |set|
+ set.draw do
+ resources :customers
+ get ':controller/:action'
+ end
+
+ get :render_with_object_location
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ end
+ end
+
+ def test_should_render_formatted_xml_erb_template
+ get :formatted_xml_erb, :format => :xml
+ assert_equal '<test>passed formatted xml erb</test>', @response.body
+ end
+
+ def test_should_render_xml_but_keep_custom_content_type
+ get :render_xml_with_custom_content_type
+ assert_equal "application/atomsvc+xml", @response.content_type
+ end
+
+ def test_should_use_implicit_content_type
+ get :implicit_content_type, :format => 'atom'
+ assert_equal Mime::ATOM, @response.content_type
+ end
+end
diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb
new file mode 100644
index 0000000000..e624f11773
--- /dev/null
+++ b/actionpack/test/controller/request/test_request_test.rb
@@ -0,0 +1,35 @@
+require 'abstract_unit'
+require 'stringio'
+
+class ActionController::TestRequestTest < ActiveSupport::TestCase
+
+ def setup
+ @request = ActionController::TestRequest.new
+ end
+
+ def test_test_request_has_session_options_initialized
+ assert @request.session_options
+ end
+
+ ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_key do |option|
+ test "rack default session options #{option} exists in session options and is default" do
+ assert_equal(ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[option],
+ @request.session_options[option],
+ "Missing rack session default option #{option} in request.session_options")
+ end
+
+ test "rack default session options #{option} exists in session options" do
+ assert(@request.session_options.has_key?(option),
+ "Missing rack session option #{option} in request.session_options")
+ end
+ end
+
+ def test_session_id_exists_by_default
+ assert_not_nil(@request.session_options[:id])
+ end
+
+ def test_session_id_different_on_each_call
+ assert_not_equal(@request.session_options[:id], ActionController::TestRequest.new.session_options[:id])
+ end
+
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
new file mode 100644
index 0000000000..2a5aad9c0e
--- /dev/null
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -0,0 +1,501 @@
+require 'abstract_unit'
+require 'digest/sha1'
+require "active_support/log_subscriber/test_helper"
+
+# common controller actions
+module RequestForgeryProtectionActions
+ def index
+ render :inline => "<%= form_tag('/') {} %>"
+ end
+
+ def show_button
+ render :inline => "<%= button_to('New', '/') %>"
+ end
+
+ def external_form
+ render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>"
+ end
+
+ def external_form_without_protection
+ render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>"
+ end
+
+ def unsafe
+ render :text => 'pwn'
+ end
+
+ def meta
+ render :inline => "<%= csrf_meta_tags %>"
+ end
+
+ def external_form_for
+ render :inline => "<%= form_for(:some_resource, :authenticity_token => 'external_token') {} %>"
+ end
+
+ def form_for_without_protection
+ render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
+ end
+
+ def form_for_remote
+ render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>"
+ end
+
+ def form_for_remote_with_token
+ render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>"
+ end
+
+ def form_for_with_token
+ render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>"
+ end
+
+ def form_for_remote_with_external_token
+ render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
+ end
+
+ def same_origin_js
+ render js: 'foo();'
+ end
+
+ def negotiate_same_origin
+ respond_to do |format|
+ format.js { same_origin_js }
+ end
+ end
+
+ def cross_origin_js
+ same_origin_js
+ end
+
+ def negotiate_cross_origin
+ negotiate_same_origin
+ end
+
+ def rescue_action(e) raise e end
+end
+
+# sample controllers
+class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base
+ include RequestForgeryProtectionActions
+ protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :reset_session
+end
+
+class RequestForgeryProtectionControllerUsingException < ActionController::Base
+ include RequestForgeryProtectionActions
+ protect_from_forgery :only => %w(index meta same_origin_js negotiate_same_origin), :with => :exception
+end
+
+class RequestForgeryProtectionControllerUsingNullSession < ActionController::Base
+ protect_from_forgery :with => :null_session
+
+ def signed
+ cookies.signed[:foo] = 'bar'
+ render :nothing => true
+ end
+
+ def encrypted
+ cookies.encrypted[:foo] = 'bar'
+ render :nothing => true
+ end
+
+ def try_to_reset_session
+ reset_session
+ render :nothing => true
+ end
+end
+
+class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
+ self.allow_forgery_protection = false
+
+ def index
+ render :inline => "<%= form_tag('/') {} %>"
+ end
+
+ def show_button
+ render :inline => "<%= button_to('New', '/') %>"
+ end
+end
+
+class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession
+ def form_authenticity_param
+ 'foobar'
+ end
+end
+
+# common test methods
+module RequestForgeryProtectionTests
+ def setup
+ @token = "cf50faa3fe97702ca1ae"
+
+ SecureRandom.stubs(:base64).returns(@token)
+ @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
+ ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
+ end
+
+ def teardown
+ ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
+ end
+
+ def test_should_render_form_with_token_tag
+ assert_not_blocked do
+ get :index
+ end
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
+ end
+
+ def test_should_render_button_to_with_token_tag
+ assert_not_blocked do
+ get :show_button
+ end
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
+ end
+
+ def test_should_render_form_without_token_tag_if_remote
+ assert_not_blocked do
+ get :form_for_remote
+ end
+ assert_no_match(/authenticity_token/, response.body)
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_embedding_token_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+ assert_not_blocked do
+ get :form_for_remote
+ end
+ assert_match(/authenticity_token/, response.body)
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+ assert_not_blocked do
+ get :form_for_remote_with_external_token
+ end
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested
+ assert_not_blocked do
+ get :form_for_remote_with_external_token
+ end
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested
+ assert_not_blocked do
+ get :form_for_remote_with_token
+ end
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
+ end
+
+ def test_should_render_form_with_token_tag_with_authenticity_token_requested
+ assert_not_blocked do
+ get :form_for_with_token
+ end
+ assert_select 'form>input[name=?][value=?]', 'custom_authenticity_token', @token
+ end
+
+ def test_should_allow_get
+ assert_not_blocked { get :index }
+ end
+
+ def test_should_allow_head
+ assert_not_blocked { head :index }
+ end
+
+ def test_should_allow_post_without_token_on_unsafe_action
+ assert_not_blocked { post :unsafe }
+ end
+
+ def test_should_not_allow_post_without_token
+ assert_blocked { post :index }
+ end
+
+ def test_should_not_allow_post_without_token_irrespective_of_format
+ assert_blocked { post :index, format: 'xml' }
+ end
+
+ def test_should_not_allow_patch_without_token
+ assert_blocked { patch :index }
+ end
+
+ def test_should_not_allow_put_without_token
+ assert_blocked { put :index }
+ end
+
+ def test_should_not_allow_delete_without_token
+ assert_blocked { delete :index }
+ end
+
+ def test_should_not_allow_xhr_post_without_token
+ assert_blocked { xhr :post, :index }
+ end
+
+ def test_should_allow_post_with_token
+ assert_not_blocked { post :index, :custom_authenticity_token => @token }
+ end
+
+ def test_should_allow_patch_with_token
+ assert_not_blocked { patch :index, :custom_authenticity_token => @token }
+ end
+
+ def test_should_allow_put_with_token
+ assert_not_blocked { put :index, :custom_authenticity_token => @token }
+ end
+
+ def test_should_allow_delete_with_token
+ assert_not_blocked { delete :index, :custom_authenticity_token => @token }
+ end
+
+ def test_should_allow_post_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { post :index }
+ end
+
+ def test_should_allow_delete_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { delete :index }
+ end
+
+ def test_should_allow_patch_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { patch :index }
+ end
+
+ def test_should_allow_put_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { put :index }
+ end
+
+ def test_should_warn_on_missing_csrf_token
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+
+ begin
+ assert_blocked { post :index }
+
+ assert_equal 1, logger.logged(:warn).size
+ assert_match(/CSRF token authenticity/, logger.logged(:warn).last)
+ ensure
+ ActionController::Base.logger = old_logger
+ end
+ end
+
+ def test_should_not_warn_if_csrf_logging_disabled
+ old_logger = ActionController::Base.logger
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActionController::Base.logger = logger
+ ActionController::Base.log_warning_on_csrf_failure = false
+
+ begin
+ assert_blocked { post :index }
+
+ assert_equal 0, logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = old_logger
+ ActionController::Base.log_warning_on_csrf_failure = true
+ end
+ end
+
+ def test_should_only_allow_same_origin_js_get_with_xhr_header
+ assert_cross_origin_blocked { get :same_origin_js }
+ assert_cross_origin_blocked { get :same_origin_js, format: 'js' }
+ assert_cross_origin_blocked do
+ @request.accept = 'text/javascript'
+ get :negotiate_same_origin
+ end
+
+ assert_cross_origin_not_blocked { xhr :get, :same_origin_js }
+ assert_cross_origin_not_blocked { xhr :get, :same_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ xhr :get, :negotiate_same_origin
+ end
+ end
+
+ # Allow non-GET requests since GET is all a remote <script> tag can muster.
+ def test_should_allow_non_get_js_without_xhr_header
+ assert_cross_origin_not_blocked { post :same_origin_js, custom_authenticity_token: @token }
+ assert_cross_origin_not_blocked { post :same_origin_js, format: 'js', custom_authenticity_token: @token }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ post :negotiate_same_origin, custom_authenticity_token: @token
+ end
+ end
+
+ def test_should_only_allow_cross_origin_js_get_without_xhr_header_if_protection_disabled
+ assert_cross_origin_not_blocked { get :cross_origin_js }
+ assert_cross_origin_not_blocked { get :cross_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ get :negotiate_cross_origin
+ end
+
+ assert_cross_origin_not_blocked { xhr :get, :cross_origin_js }
+ assert_cross_origin_not_blocked { xhr :get, :cross_origin_js, format: 'js' }
+ assert_cross_origin_not_blocked do
+ @request.accept = 'text/javascript'
+ xhr :get, :negotiate_cross_origin
+ end
+ end
+
+ def assert_blocked
+ session[:something_like_user_id] = 1
+ yield
+ assert_nil session[:something_like_user_id], "session values are still present"
+ assert_response :success
+ end
+
+ def assert_not_blocked
+ assert_nothing_raised { yield }
+ assert_response :success
+ end
+
+ def assert_cross_origin_blocked
+ assert_raises(ActionController::InvalidCrossOriginRequest) do
+ yield
+ end
+ end
+
+ def assert_cross_origin_not_blocked
+ assert_not_blocked { yield }
+ end
+end
+
+# OK let's get our test on
+
+class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase
+ include RequestForgeryProtectionTests
+
+ setup do
+ @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
+ ActionController::Base.request_forgery_protection_token = :custom_authenticity_token
+ end
+
+ teardown do
+ ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
+ end
+
+ test 'should emit a csrf-param meta tag and a csrf-token meta tag' do
+ SecureRandom.stubs(:base64).returns(@token + '<=?')
+ get :meta
+ assert_select 'meta[name=?][content=?]', 'csrf-param', 'custom_authenticity_token'
+ assert_select 'meta[name=?][content=?]', 'csrf-token', 'cf50faa3fe97702ca1ae&lt;=?'
+ end
+end
+
+class RequestForgeryProtectionControllerUsingNullSessionTest < ActionController::TestCase
+ class NullSessionDummyKeyGenerator
+ def generate_key(secret)
+ '03312270731a2ed0d11ed091c2338a06'
+ end
+ end
+
+ def setup
+ @request.env[ActionDispatch::Cookies::GENERATOR_KEY] = NullSessionDummyKeyGenerator.new
+ end
+
+ test 'should allow to set signed cookies' do
+ post :signed
+ assert_response :ok
+ end
+
+ test 'should allow to set encrypted cookies' do
+ post :encrypted
+ assert_response :ok
+ end
+
+ test 'should allow reset_session' do
+ post :try_to_reset_session
+ assert_response :ok
+ end
+end
+
+class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
+ include RequestForgeryProtectionTests
+ def assert_blocked
+ assert_raises(ActionController::InvalidAuthenticityToken) do
+ yield
+ end
+ end
+end
+
+class FreeCookieControllerTest < ActionController::TestCase
+ def setup
+ @controller = FreeCookieController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @token = "cf50faa3fe97702ca1ae"
+
+ SecureRandom.stubs(:base64).returns(@token)
+ end
+
+ def test_should_not_render_form_with_token_tag
+ get :index
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
+ end
+
+ def test_should_not_render_button_to_with_token_tag
+ get :show_button
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
+ end
+
+ def test_should_allow_all_methods_without_token
+ [:post, :patch, :put, :delete].each do |method|
+ assert_nothing_raised { send(method, :index)}
+ end
+ end
+
+ test 'should not emit a csrf-token meta tag' do
+ get :meta
+ assert @response.body.blank?
+ end
+end
+
+class CustomAuthenticityParamControllerTest < ActionController::TestCase
+ def setup
+ super
+ @old_logger = ActionController::Base.logger
+ @logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ @token = "foobar"
+ @old_request_forgery_protection_token = ActionController::Base.request_forgery_protection_token
+ ActionController::Base.request_forgery_protection_token = @token
+ end
+
+ def teardown
+ ActionController::Base.request_forgery_protection_token = @old_request_forgery_protection_token
+ super
+ end
+
+ def test_should_not_warn_if_form_authenticity_param_matches_form_authenticity_token
+ ActionController::Base.logger = @logger
+ SecureRandom.stubs(:base64).returns(@token)
+
+ begin
+ post :index, :custom_token_name => 'foobar'
+ assert_equal 0, @logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = @old_logger
+ end
+ end
+
+ def test_should_warn_if_form_authenticity_param_does_not_match_form_authenticity_token
+ ActionController::Base.logger = @logger
+
+ begin
+ post :index, :custom_token_name => 'bazqux'
+ assert_equal 1, @logger.logged(:warn).size
+ ensure
+ ActionController::Base.logger = @old_logger
+ end
+ end
+end
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
new file mode 100644
index 0000000000..6803dbbb62
--- /dev/null
+++ b/actionpack/test/controller/required_params_test.rb
@@ -0,0 +1,51 @@
+require 'abstract_unit'
+
+class BooksController < ActionController::Base
+ def create
+ params.require(:book).require(:name)
+ head :ok
+ end
+end
+
+class ActionControllerRequiredParamsTest < ActionController::TestCase
+ tests BooksController
+
+ test "missing required parameters will raise exception" do
+ assert_raise ActionController::ParameterMissing do
+ post :create, { magazine: { name: "Mjallo!" } }
+ end
+
+ assert_raise ActionController::ParameterMissing do
+ post :create, { book: { title: "Mjallo!" } }
+ end
+ end
+
+ test "required parameters that are present will not raise" do
+ post :create, { book: { name: "Mjallo!" } }
+ assert_response :ok
+ end
+
+ test "required parameters with false value will not raise" do
+ post :create, { book: { name: false } }
+ assert_response :ok
+ end
+end
+
+class ParametersRequireTest < ActiveSupport::TestCase
+
+ test "required parameters should accept and return false value" do
+ assert_equal(false, ActionController::Parameters.new(person: false).require(:person))
+ end
+
+ test "required parameters must not be nil" do
+ assert_raises(ActionController::ParameterMissing) do
+ ActionController::Parameters.new(person: nil).require(:person)
+ end
+ end
+
+ test "required parameters must not be empty" do
+ assert_raises(ActionController::ParameterMissing) do
+ ActionController::Parameters.new(person: {}).require(:person)
+ end
+ end
+end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
new file mode 100644
index 0000000000..4898b0c57f
--- /dev/null
+++ b/actionpack/test/controller/rescue_test.rb
@@ -0,0 +1,348 @@
+require 'abstract_unit'
+
+class RescueController < ActionController::Base
+ class NotAuthorized < StandardError
+ end
+ class NotAuthorizedToRescueAsString < StandardError
+ end
+
+ class RecordInvalid < StandardError
+ end
+ class RecordInvalidToRescueAsString < StandardError
+ end
+
+ class NotAllowed < StandardError
+ end
+ class NotAllowedToRescueAsString < StandardError
+ end
+
+ class InvalidRequest < StandardError
+ end
+ class InvalidRequestToRescueAsString < StandardError
+ end
+
+ class BadGateway < StandardError
+ end
+ class BadGatewayToRescueAsString < StandardError
+ end
+
+ class ResourceUnavailable < StandardError
+ end
+ class ResourceUnavailableToRescueAsString < StandardError
+ end
+
+ # We use a fully-qualified name in some strings, and a relative constant
+ # name in some other to test correct handling of both cases.
+
+ rescue_from NotAuthorized, :with => :deny_access
+ rescue_from 'RescueController::NotAuthorizedToRescueAsString', :with => :deny_access
+
+ rescue_from RecordInvalid, :with => :show_errors
+ rescue_from 'RescueController::RecordInvalidToRescueAsString', :with => :show_errors
+
+ rescue_from NotAllowed, :with => proc { head :forbidden }
+ rescue_from 'RescueController::NotAllowedToRescueAsString', :with => proc { head :forbidden }
+
+ rescue_from InvalidRequest, :with => proc { |exception| render :text => exception.message }
+ rescue_from 'InvalidRequestToRescueAsString', :with => proc { |exception| render :text => exception.message }
+
+ rescue_from BadGateway do
+ head :status => 502
+ end
+ rescue_from 'BadGatewayToRescueAsString' do
+ head :status => 502
+ end
+
+ rescue_from ResourceUnavailable do |exception|
+ render :text => exception.message
+ end
+ rescue_from 'ResourceUnavailableToRescueAsString' do |exception|
+ render :text => exception.message
+ end
+
+ rescue_from ActionView::TemplateError do
+ render :text => 'action_view templater error'
+ end
+
+ rescue_from IOError do
+ render :text => 'io error'
+ end
+
+ before_action(only: :before_action_raises) { raise 'umm nice' }
+
+ def before_action_raises
+ end
+
+ def raises
+ render :text => 'already rendered'
+ raise "don't panic!"
+ end
+
+ def method_not_allowed
+ raise ActionController::MethodNotAllowed.new(:get, :head, :put)
+ end
+
+ def not_implemented
+ raise ActionController::NotImplemented.new(:get, :put)
+ end
+
+ def not_authorized
+ raise NotAuthorized
+ end
+ def not_authorized_raise_as_string
+ raise NotAuthorizedToRescueAsString
+ end
+
+ def not_allowed
+ raise NotAllowed
+ end
+ def not_allowed_raise_as_string
+ raise NotAllowedToRescueAsString
+ end
+
+ def invalid_request
+ raise InvalidRequest
+ end
+ def invalid_request_raise_as_string
+ raise InvalidRequestToRescueAsString
+ end
+
+ def record_invalid
+ raise RecordInvalid
+ end
+ def record_invalid_raise_as_string
+ raise RecordInvalidToRescueAsString
+ end
+
+ def bad_gateway
+ raise BadGateway
+ end
+ def bad_gateway_raise_as_string
+ raise BadGatewayToRescueAsString
+ end
+
+ def resource_unavailable
+ raise ResourceUnavailable
+ end
+ def resource_unavailable_raise_as_string
+ raise ResourceUnavailableToRescueAsString
+ end
+
+ def missing_template
+ end
+
+ def io_error_in_view
+ raise ActionView::TemplateError.new(nil, IOError.new('this is io error'))
+ end
+
+ def zero_division_error_in_view
+ raise ActionView::TemplateError.new(nil, ZeroDivisionError.new('this is zero division error'))
+ end
+
+ protected
+ def deny_access
+ head :forbidden
+ end
+
+ def show_errors(exception)
+ head :unprocessable_entity
+ end
+end
+
+class ExceptionInheritanceRescueController < ActionController::Base
+
+ class ParentException < StandardError
+ end
+
+ class ChildException < ParentException
+ end
+
+ class GrandchildException < ChildException
+ end
+
+ rescue_from ChildException, :with => lambda { head :ok }
+ rescue_from ParentException, :with => lambda { head :created }
+ rescue_from GrandchildException, :with => lambda { head :no_content }
+
+ def raise_parent_exception
+ raise ParentException
+ end
+
+ def raise_child_exception
+ raise ChildException
+ end
+
+ def raise_grandchild_exception
+ raise GrandchildException
+ end
+end
+
+class ExceptionInheritanceRescueControllerTest < ActionController::TestCase
+ def test_bottom_first
+ get :raise_grandchild_exception
+ assert_response :no_content
+ end
+
+ def test_inheritance_works
+ get :raise_child_exception
+ assert_response :created
+ end
+end
+
+class ControllerInheritanceRescueController < ExceptionInheritanceRescueController
+ class FirstExceptionInChildController < StandardError
+ end
+
+ class SecondExceptionInChildController < StandardError
+ end
+
+ rescue_from FirstExceptionInChildController, 'SecondExceptionInChildController', :with => lambda { head :gone }
+
+ def raise_first_exception_in_child_controller
+ raise FirstExceptionInChildController
+ end
+
+ def raise_second_exception_in_child_controller
+ raise SecondExceptionInChildController
+ end
+end
+
+class ControllerInheritanceRescueControllerTest < ActionController::TestCase
+ def test_first_exception_in_child_controller
+ get :raise_first_exception_in_child_controller
+ assert_response :gone
+ end
+
+ def test_second_exception_in_child_controller
+ get :raise_second_exception_in_child_controller
+ assert_response :gone
+ end
+
+ def test_exception_in_parent_controller
+ get :raise_parent_exception
+ assert_response :created
+ end
+end
+
+class RescueControllerTest < ActionController::TestCase
+
+ def test_io_error_in_view
+ get :io_error_in_view
+ assert_equal 'io error', @response.body
+ end
+
+ def test_zero_division_error_in_view
+ get :zero_division_error_in_view
+ assert_equal 'action_view templater error', @response.body
+ end
+
+ def test_rescue_handler
+ get :not_authorized
+ assert_response :forbidden
+ end
+ def test_rescue_handler_string
+ get :not_authorized_raise_as_string
+ assert_response :forbidden
+ end
+
+ def test_rescue_handler_with_argument
+ @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
+ get :record_invalid
+ end
+ def test_rescue_handler_with_argument_as_string
+ @controller.expects(:show_errors).once.with { |e| e.is_a?(Exception) }
+ get :record_invalid_raise_as_string
+ end
+
+ def test_proc_rescue_handler
+ get :not_allowed
+ assert_response :forbidden
+ end
+ def test_proc_rescue_handler_as_string
+ get :not_allowed_raise_as_string
+ assert_response :forbidden
+ end
+
+ def test_proc_rescue_handle_with_argument
+ get :invalid_request
+ assert_equal "RescueController::InvalidRequest", @response.body
+ end
+ def test_proc_rescue_handle_with_argument_as_string
+ get :invalid_request_raise_as_string
+ assert_equal "RescueController::InvalidRequestToRescueAsString", @response.body
+ end
+
+ def test_block_rescue_handler
+ get :bad_gateway
+ assert_response 502
+ end
+ def test_block_rescue_handler_as_string
+ get :bad_gateway_raise_as_string
+ assert_response 502
+ end
+
+ def test_block_rescue_handler_with_argument
+ get :resource_unavailable
+ assert_equal "RescueController::ResourceUnavailable", @response.body
+ end
+
+ def test_block_rescue_handler_with_argument_as_string
+ get :resource_unavailable_raise_as_string
+ assert_equal "RescueController::ResourceUnavailableToRescueAsString", @response.body
+ end
+end
+
+class RescueTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ class RecordInvalid < StandardError
+ def message
+ 'invalid'
+ end
+ end
+ rescue_from RecordInvalid, :with => :show_errors
+
+ def foo
+ render :text => "foo"
+ end
+
+ def invalid
+ raise RecordInvalid
+ end
+
+ def b00m
+ raise 'b00m'
+ end
+
+ protected
+ def show_errors(exception)
+ render :text => exception.message
+ end
+ end
+
+ test 'normal request' do
+ with_test_routing do
+ get '/foo'
+ assert_equal 'foo', response.body
+ end
+ end
+
+ test 'rescue exceptions inside controller' do
+ with_test_routing do
+ get '/invalid'
+ assert_equal 'invalid', response.body
+ end
+ end
+
+ private
+
+ def with_test_routing
+ with_routing do |set|
+ set.draw do
+ get 'foo', :to => ::RescueTest::TestController.action(:foo)
+ get 'invalid', :to => ::RescueTest::TestController.action(:invalid)
+ get 'b00m', :to => ::RescueTest::TestController.action(:b00m)
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
new file mode 100644
index 0000000000..a5f43c4b6b
--- /dev/null
+++ b/actionpack/test/controller/resources_test.rb
@@ -0,0 +1,1334 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/object/with_options'
+require 'active_support/core_ext/array/extract_options'
+
+class ResourcesTest < ActionController::TestCase
+
+ def test_default_restful_routes
+ with_restful_routing :messages do
+ assert_simply_restful_for :messages
+ end
+ end
+
+ def test_override_paths_for_member_and_collection_methods
+ collection_methods = { :rss => :get, :reorder => :post, :csv => :post }
+ member_methods = { :rss => :get, :atom => :get, :upload => :post, :fix => :post }
+ path_names = {:new => 'nuevo', :rss => 'canal', :fix => 'corrigir' }
+
+ with_restful_routing :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do
+
+ assert_restful_routes_for :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do |options|
+ member_methods.each do |action, method|
+ assert_recognizes(options.merge(:action => action.to_s, :id => '1'),
+ :path => "/messages/1/#{path_names[action] || action}",
+ :method => method)
+ end
+
+ collection_methods.each do |action, method|
+ assert_recognizes(options.merge(:action => action.to_s),
+ :path => "/messages/#{path_names[action] || action}",
+ :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages,
+ :collection => collection_methods,
+ :member => member_methods,
+ :path_names => path_names do |options|
+
+ collection_methods.keys.each do |action|
+ assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action
+ end
+
+ member_methods.keys.each do |action|
+ assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1"
+ end
+
+ end
+ end
+ end
+
+ def test_multiple_default_restful_routes
+ with_restful_routing :messages, :comments do
+ assert_simply_restful_for :messages
+ assert_simply_restful_for :comments
+ end
+ end
+
+ def test_multiple_resources_with_options
+ expected_options = {:controller => 'threads', :action => 'index'}
+
+ with_restful_routing :messages, :comments, expected_options.slice(:controller) do
+ assert_recognizes(expected_options, :path => 'comments')
+ assert_recognizes(expected_options, :path => 'messages')
+ end
+ end
+
+ def test_with_custom_conditions
+ with_restful_routing :messages, :conditions => { :subdomain => 'app' } do
+ assert @routes.recognize_path("/messages", :method => :get, :subdomain => 'app')
+ end
+ end
+
+ def test_irregular_id_with_no_constraints_should_raise_error
+ expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'}
+
+ with_restful_routing :messages do
+ assert_raise(Assertion) do
+ assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get)
+ end
+ end
+ end
+
+ def test_irregular_id_with_constraints_should_pass
+ expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'}
+
+ with_restful_routing(:messages, :constraints => {:id => /[0-9]\.[0-9]\.[0-9]/}) do
+ assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get)
+ end
+ end
+
+ def test_with_path_prefix_constraints
+ expected_options = {:controller => 'messages', :action => 'show', :thread_id => '1.1.1', :id => '1'}
+ with_restful_routing :messages, :path_prefix => '/thread/:thread_id', :constraints => {:thread_id => /[0-9]\.[0-9]\.[0-9]/} do
+ assert_recognizes(expected_options, :path => 'thread/1.1.1/messages/1', :method => :get)
+ end
+ end
+
+ def test_irregular_id_constraints_should_get_passed_to_member_actions
+ expected_options = {:controller => 'messages', :action => 'custom', :id => '1.1.1'}
+
+ with_restful_routing(:messages, :member => {:custom => :get}, :constraints => {:id => /[0-9]\.[0-9]\.[0-9]/}) do
+ assert_recognizes(expected_options, :path => 'messages/1.1.1/custom', :method => :get)
+ end
+ end
+
+ def test_with_path_prefix
+ with_restful_routing :messages, :path_prefix => '/thread/:thread_id' do
+ assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
+ end
+ end
+
+ def test_multiple_with_path_prefix
+ with_restful_routing :messages, :comments, :path_prefix => '/thread/:thread_id' do
+ assert_simply_restful_for :messages, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
+ assert_simply_restful_for :comments, :path_prefix => 'thread/5/', :options => { :thread_id => '5' }
+ end
+ end
+
+ def test_with_name_prefix
+ with_restful_routing :messages, :as => 'post_messages' do
+ assert_simply_restful_for :messages, :name_prefix => 'post_'
+ end
+ end
+
+ def test_with_collection_actions
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
+
+ with_routing do |set|
+ set.draw do
+ resources :messages do
+ get :a, :on => :collection
+ put :b, :on => :collection
+ post :c, :on => :collection
+ delete :d, :on => :collection
+ patch :e, :on => :collection
+ end
+ end
+
+ assert_restful_routes_for :messages do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action), :path => "/messages/#{action}", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ actions.keys.each do |action|
+ assert_named_route "/messages/#{action}", "#{action}_messages_path", :action => action
+ end
+ end
+ end
+ end
+
+ def test_with_collection_actions_and_name_prefix
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
+
+ with_routing do |set|
+ set.draw do
+ scope '/threads/:thread_id' do
+ resources :messages, :as => 'thread_messages' do
+ get :a, :on => :collection
+ put :b, :on => :collection
+ post :c, :on => :collection
+ delete :d, :on => :collection
+ patch :e, :on => :collection
+ end
+ end
+ end
+
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.keys.each do |action|
+ assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action
+ end
+ end
+ end
+ end
+
+ def test_with_collection_actions_and_name_prefix_and_member_action_with_same_name
+ actions = { 'a' => :get }
+
+ with_routing do |set|
+ set.draw do
+ scope '/threads/:thread_id' do
+ resources :messages, :as => 'thread_messages' do
+ get :a, :on => :collection
+ get :a, :on => :member
+ end
+ end
+ end
+
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.keys.each do |action|
+ assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action
+ end
+ end
+ end
+ end
+
+ def test_with_collection_action_and_name_prefix_and_formatted
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
+
+ with_routing do |set|
+ set.draw do
+ scope '/threads/:thread_id' do
+ resources :messages, :as => 'thread_messages' do
+ get :a, :on => :collection
+ put :b, :on => :collection
+ post :c, :on => :collection
+ delete :d, :on => :collection
+ patch :e, :on => :collection
+ end
+ end
+ end
+
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.each do |action, method|
+ assert_recognizes(options.merge(:action => action, :format => 'xml'), :path => "/threads/1/messages/#{action}.xml", :method => method)
+ end
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ actions.keys.each do |action|
+ assert_named_route "/threads/1/messages/#{action}.xml", "#{action}_thread_messages_path", :action => action, :format => 'xml'
+ end
+ end
+ end
+ end
+
+ def test_with_member_action
+ [:patch, :put, :post].each do |method|
+ with_restful_routing :messages, :member => { :mark => method } do
+ mark_options = {:action => 'mark', :id => '1'}
+ mark_path = "/messages/1/mark"
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method)
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ assert_named_route mark_path, :mark_message_path, mark_options
+ end
+ end
+ end
+ end
+
+ def test_with_member_action_and_requirement
+ expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'}
+
+ with_restful_routing(:messages, :constraints => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do
+ assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get)
+ end
+ end
+
+ def test_member_when_override_paths_for_default_restful_actions_with
+ [:patch, :put, :post].each do |method|
+ with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
+ mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
+ mark_path = "/messages/1/mark"
+
+ assert_restful_routes_for :messages, :path_names => {:new => 'nuevo'} do |options|
+ assert_recognizes(options.merge(mark_options), :path => mark_path, :method => method)
+ end
+
+ assert_restful_named_routes_for :messages, :path_names => {:new => 'nuevo'} do |options|
+ assert_named_route mark_path, :mark_message_path, mark_options
+ end
+ end
+ end
+ end
+
+ def test_with_two_member_actions_with_same_method
+ [:patch, :put, :post].each do |method|
+ with_routing do |set|
+ set.draw do
+ resources :messages do
+ member do
+ match :mark , :via => method
+ match :unmark, :via => method
+ end
+ end
+ end
+
+ %w(mark unmark).each do |action|
+ action_options = {:action => action, :id => '1'}
+ action_path = "/messages/1/#{action}"
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ assert_named_route action_path, "#{action}_message_path".to_sym, action_options
+ end
+ end
+ end
+ end
+ end
+
+ def test_array_as_collection_or_member_method_value
+ with_routing do |set|
+ set.draw do
+ resources :messages do
+ collection do
+ match :search, :via => [:post, :get]
+ end
+
+ member do
+ match :toggle, :via => [:post, :get]
+ end
+ end
+ end
+
+ assert_restful_routes_for :messages do |options|
+ [:get, :post].each do |method|
+ assert_recognizes(options.merge(:action => 'search'), :path => "/messages/search", :method => method)
+ end
+ [:get, :post].each do |method|
+ assert_recognizes(options.merge(:action => 'toggle', :id => '1'), :path => '/messages/1/toggle', :method => method)
+ end
+ end
+ end
+ end
+
+ def test_with_new_action
+ with_routing do |set|
+ set.draw do
+ resources :messages do
+ post :preview, :on => :new
+ end
+ end
+
+ preview_options = {:action => 'preview'}
+ preview_path = "/messages/new/preview"
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
+ end
+
+ assert_restful_named_routes_for :messages do |options|
+ assert_named_route preview_path, :preview_new_message_path, preview_options
+ end
+ end
+ end
+
+ def test_with_new_action_with_name_prefix
+ with_routing do |set|
+ set.draw do
+ scope('/threads/:thread_id') do
+ resources :messages, :as => "thread_messages" do
+ post :preview, :on => :new
+ end
+ end
+ end
+
+ preview_options = {:action => 'preview', :thread_id => '1'}
+ preview_path = "/threads/1/messages/new/preview"
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_named_route preview_path, :preview_new_thread_message_path, preview_options
+ end
+ end
+ end
+
+ def test_with_formatted_new_action_with_name_prefix
+ with_routing do |set|
+ set.draw do
+ scope('/threads/:thread_id') do
+ resources :messages, :as => "thread_messages" do
+ post :preview, :on => :new
+ end
+ end
+ end
+
+ preview_options = {:action => 'preview', :thread_id => '1', :format => 'xml'}
+ preview_path = "/threads/1/messages/new/preview.xml"
+ assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_recognizes(options.merge(preview_options), :path => preview_path, :method => :post)
+ end
+
+ assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
+ assert_named_route preview_path, :preview_new_thread_message_path, preview_options
+ end
+ end
+ end
+
+ def test_override_new_method
+ with_restful_routing :messages do
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get)
+ assert_raise(ActionController::RoutingError) do
+ @routes.recognize_path("/messages/new", :method => :post)
+ end
+ end
+ end
+
+ with_routing do |set|
+ set.draw do
+ resources :messages do
+ match :new, :via => [:post, :get], :on => :new
+ end
+ end
+
+ assert_restful_routes_for :messages do |options|
+ assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :post)
+ assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get)
+ end
+ end
+ end
+
+ def test_nested_restful_routes
+ with_routing do |set|
+ set.draw do
+ resources :threads do
+ resources :messages do
+ resources :comments
+ end
+ end
+ end
+
+ assert_simply_restful_for :threads
+ assert_simply_restful_for :messages,
+ :name_prefix => 'thread_',
+ :path_prefix => 'threads/1/',
+ :options => { :thread_id => '1' }
+ assert_simply_restful_for :comments,
+ :name_prefix => 'thread_message_',
+ :path_prefix => 'threads/1/messages/2/',
+ :options => { :thread_id => '1', :message_id => '2' }
+ end
+ end
+
+ def test_shallow_nested_restful_routes
+ with_routing do |set|
+ set.draw do
+ resources :threads, :shallow => true do
+ resources :messages do
+ resources :comments
+ end
+ end
+ end
+
+ assert_simply_restful_for :threads,
+ :shallow => true
+ assert_simply_restful_for :messages,
+ :name_prefix => 'thread_',
+ :path_prefix => 'threads/1/',
+ :shallow => true,
+ :options => { :thread_id => '1' }
+ assert_simply_restful_for :comments,
+ :name_prefix => 'message_',
+ :path_prefix => 'messages/2/',
+ :shallow => true,
+ :options => { :message_id => '2' }
+ end
+ end
+
+ def test_shallow_nested_restful_routes_with_namespaces
+ with_routing do |set|
+ set.draw do
+ namespace :backoffice do
+ namespace :admin do
+ resources :products, :shallow => true do
+ resources :images
+ end
+ end
+ end
+ end
+
+ assert_simply_restful_for :products,
+ :controller => 'backoffice/admin/products',
+ :namespace => 'backoffice/admin/',
+ :name_prefix => 'backoffice_admin_',
+ :path_prefix => 'backoffice/admin/',
+ :shallow => true
+ assert_simply_restful_for :images,
+ :controller => 'backoffice/admin/images',
+ :namespace => 'backoffice/admin/',
+ :name_prefix => 'backoffice_admin_product_',
+ :path_prefix => 'backoffice/admin/products/1/',
+ :shallow => true,
+ :options => { :product_id => '1' }
+ end
+ end
+
+ def test_restful_routes_dont_generate_duplicates
+ with_restful_routing :messages do
+ routes = @routes.routes
+ routes.each do |route|
+ routes.each do |r|
+ next if route === r # skip the comparison instance
+ assert_not_equal [route.conditions, route.path.spec.to_s], [r.conditions, r.path.spec.to_s]
+ end
+ end
+ end
+ end
+
+ def test_should_create_singleton_resource_routes
+ with_singleton_resources :account do
+ assert_singleton_restful_for :account
+ end
+ end
+
+ def test_should_create_multiple_singleton_resource_routes
+ with_singleton_resources :account, :logo do
+ assert_singleton_restful_for :account
+ assert_singleton_restful_for :logo
+ end
+ end
+
+ def test_should_create_nested_singleton_resource_routes
+ with_routing do |set|
+ set.draw do
+ resource :admin, :controller => 'admin' do
+ resource :account
+ end
+ end
+
+ assert_singleton_restful_for :admin, :controller => 'admin'
+ assert_singleton_restful_for :account, :name_prefix => "admin_", :path_prefix => 'admin/'
+ end
+ end
+
+ def test_singleton_resource_with_member_action
+ [:patch, :put, :post].each do |method|
+ with_routing do |set|
+ set.draw do
+ resource :account do
+ match :reset, :on => :member, :via => method
+ end
+ end
+
+ reset_options = {:action => 'reset'}
+ reset_path = "/account/reset"
+ assert_singleton_routes_for :account do |options|
+ assert_recognizes(options.merge(reset_options), :path => reset_path, :method => method)
+ end
+
+ assert_singleton_named_routes_for :account do |options|
+ assert_named_route reset_path, :reset_account_path, reset_options
+ end
+ end
+ end
+ end
+
+ def test_singleton_resource_with_two_member_actions_with_same_method
+ [:patch, :put, :post].each do |method|
+ with_routing do |set|
+ set.draw do
+ resource :account do
+ match :reset, :on => :member, :via => method
+ match :disable, :on => :member, :via => method
+ end
+ end
+
+ %w(reset disable).each do |action|
+ action_options = {:action => action}
+ action_path = "/account/#{action}"
+ assert_singleton_routes_for :account do |options|
+ assert_recognizes(options.merge(action_options), :path => action_path, :method => method)
+ end
+
+ assert_singleton_named_routes_for :account do |options|
+ assert_named_route action_path, "#{action}_account_path".to_sym, action_options
+ end
+ end
+ end
+ end
+ end
+
+ def test_should_nest_resources_in_singleton_resource
+ with_routing do |set|
+ set.draw do
+ resource :account do
+ resources :messages
+ end
+ end
+
+ assert_singleton_restful_for :account
+ assert_simply_restful_for :messages, :name_prefix => "account_", :path_prefix => 'account/'
+ end
+ end
+
+ def test_should_nest_resources_in_singleton_resource_with_path_scope
+ with_routing do |set|
+ set.draw do
+ scope ':site_id' do
+ resource(:account) do
+ resources :messages
+ end
+ end
+ end
+
+ assert_singleton_restful_for :account, :path_prefix => '7/', :options => { :site_id => '7' }
+ assert_simply_restful_for :messages, :name_prefix => "account_", :path_prefix => '7/account/', :options => { :site_id => '7' }
+ end
+ end
+
+ def test_should_nest_singleton_resource_in_resources
+ with_routing do |set|
+ set.draw do
+ resources :threads do
+ resource :admin, :controller => 'admin'
+ end
+ end
+
+ assert_simply_restful_for :threads
+ assert_singleton_restful_for :admin, :controller => 'admin', :name_prefix => 'thread_', :path_prefix => 'threads/5/', :options => { :thread_id => '5' }
+ end
+ end
+
+ def test_should_not_allow_delete_or_patch_or_put_on_collection_path
+ controller_name = :messages
+ with_restful_routing controller_name do
+ options = { :controller => controller_name.to_s }
+ collection_path = "/#{controller_name}"
+
+ assert_raise(Assertion) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
+ end
+
+ assert_raise(Assertion) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
+ end
+
+ assert_raise(Assertion) do
+ assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete)
+ end
+ end
+ end
+
+ def test_new_style_named_routes_for_resource
+ with_routing do |set|
+ set.draw do
+ scope '/threads/:thread_id' do
+ resources :messages, :as => 'thread_messages' do
+ get :search, :on => :collection
+ get :preview, :on => :new
+ end
+ end
+ end
+
+ assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
+ assert_named_route "/threads/1/messages/search", "search_thread_messages_path", {}
+ assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
+ assert_named_route "/threads/1/messages/new/preview", "preview_new_thread_message_path", {}
+ end
+ end
+
+ def test_new_style_named_routes_for_singleton_resource
+ with_routing do |set|
+ set.draw do
+ scope '/admin' do
+ resource :account, :as => :admin_account do
+ get :login, :on => :member
+ get :preview, :on => :new
+ end
+ end
+ end
+ assert_singleton_restful_for :account, :name_prefix => 'admin_', :path_prefix => 'admin/'
+ assert_named_route "/admin/account/login", "login_admin_account_path", {}
+ assert_named_route "/admin/account/new", "new_admin_account_path", {}
+ assert_named_route "/admin/account/new/preview", "preview_new_admin_account_path", {}
+ end
+ end
+
+ def test_resources_in_namespace
+ with_routing do |set|
+ set.draw do
+ namespace :backoffice do
+ resources :products
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/products", :name_prefix => 'backoffice_', :path_prefix => 'backoffice/'
+ end
+ end
+
+ def test_resources_in_nested_namespace
+ with_routing do |set|
+ set.draw do
+ namespace :backoffice do
+ namespace :admin do
+ resources :products
+ end
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/admin/products", :name_prefix => 'backoffice_admin_', :path_prefix => 'backoffice/admin/'
+ end
+ end
+
+ def test_resources_using_namespace
+ with_routing do |set|
+ set.draw do
+ namespace :backoffice, :path => nil, :as => nil do
+ resources :products
+ end
+ end
+
+ assert_simply_restful_for :products, :controller => "backoffice/products"
+ end
+ end
+
+ def test_nested_resources_using_namespace
+ with_routing do |set|
+ set.draw do
+ namespace :backoffice do
+ resources :products do
+ resources :images
+ end
+ end
+ end
+
+ assert_simply_restful_for :images, :controller => "backoffice/images", :name_prefix => 'backoffice_product_', :path_prefix => 'backoffice/products/1/', :options => {:product_id => '1'}
+ end
+ end
+
+ def test_nested_resources_in_nested_namespace
+ with_routing do |set|
+ set.draw do
+ namespace :backoffice do
+ namespace :admin do
+ resources :products do
+ resources :images
+ end
+ end
+ end
+ end
+
+ assert_simply_restful_for :images, :controller => "backoffice/admin/images", :name_prefix => 'backoffice_admin_product_', :path_prefix => 'backoffice/admin/products/1/', :options => {:product_id => '1'}
+ end
+ end
+
+ def test_with_path_segment
+ with_restful_routing :messages do
+ assert_simply_restful_for :messages
+ assert_recognizes({:controller => "messages", :action => "index"}, "/messages")
+ assert_recognizes({:controller => "messages", :action => "index"}, "/messages/")
+ end
+
+ with_routing do |set|
+ set.draw do
+ resources :messages, :path => 'reviews'
+ end
+ assert_simply_restful_for :messages, :as => 'reviews'
+ assert_recognizes({:controller => "messages", :action => "index"}, "/reviews")
+ assert_recognizes({:controller => "messages", :action => "index"}, "/reviews/")
+ end
+ end
+
+ def test_multiple_with_path_segment_and_controller
+ with_routing do |set|
+ set.draw do
+ resources :products do
+ resources :product_reviews, :path => 'reviews', :controller => 'messages'
+ end
+ resources :tutors do
+ resources :tutor_reviews, :path => 'reviews', :controller => 'comments'
+ end
+ end
+
+ assert_simply_restful_for :product_reviews, :controller=>'messages', :as => 'reviews', :name_prefix => 'product_', :path_prefix => 'products/1/', :options => {:product_id => '1'}
+ assert_simply_restful_for :tutor_reviews,:controller=>'comments', :as => 'reviews', :name_prefix => 'tutor_', :path_prefix => 'tutors/1/', :options => {:tutor_id => '1'}
+ end
+ end
+
+ def test_with_path_segment_path_prefix_constraints
+ expected_options = {:controller => 'messages', :action => 'show', :thread_id => '1.1.1', :id => '1'}
+ with_routing do |set|
+ set.draw do
+ scope '/thread/:thread_id', :constraints => { :thread_id => /[0-9]\.[0-9]\.[0-9]/ } do
+ resources :messages, :path => 'comments'
+ end
+ end
+ assert_recognizes(expected_options, :path => 'thread/1.1.1/comments/1', :method => :get)
+ end
+ end
+
+ def test_resource_has_only_show_action
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => :show
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy])
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :show, [:index, :new, :create, :edit, :update, :destroy])
+ end
+ end
+
+ def test_singleton_resource_has_only_show_action
+ with_routing do |set|
+ set.draw do
+ resource :account, :only => :show
+ end
+
+ assert_singleton_resource_allowed_routes('accounts', {}, :show, [:index, :new, :create, :edit, :update, :destroy])
+ assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :show, [:index, :new, :create, :edit, :update, :destroy])
+ end
+ end
+
+ def test_resource_does_not_have_destroy_action
+ with_routing do |set|
+ set.draw do
+ resources :products, :except => :destroy
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy)
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [:index, :new, :create, :show, :edit, :update], :destroy)
+ end
+ end
+
+ def test_singleton_resource_does_not_have_destroy_action
+ with_routing do |set|
+ set.draw do
+ resource :account, :except => :destroy
+ end
+
+ assert_singleton_resource_allowed_routes('accounts', {}, [:new, :create, :show, :edit, :update], :destroy)
+ assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [:new, :create, :show, :edit, :update], :destroy)
+ end
+ end
+
+ def test_resource_has_only_create_action_and_named_route
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => :create
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy])
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :create, [:index, :new, :show, :edit, :update, :destroy])
+
+ assert_not_nil set.named_routes[:products]
+ end
+ end
+
+ def test_resource_has_only_update_action_and_named_route
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => :update
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy])
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :update, [:index, :new, :create, :show, :edit, :destroy])
+
+ assert_not_nil set.named_routes[:product]
+ end
+ end
+
+ def test_resource_has_only_destroy_action_and_named_route
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => :destroy
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update])
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, :destroy, [:index, :new, :create, :show, :edit, :update])
+
+ assert_not_nil set.named_routes[:product]
+ end
+ end
+
+ def test_singleton_resource_has_only_create_action_and_named_route
+ with_routing do |set|
+ set.draw do
+ resource :account, :only => :create
+ end
+
+ assert_singleton_resource_allowed_routes('accounts', {}, :create, [:new, :show, :edit, :update, :destroy])
+ assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :create, [:new, :show, :edit, :update, :destroy])
+
+ assert_not_nil set.named_routes[:account]
+ end
+ end
+
+ def test_singleton_resource_has_only_update_action_and_named_route
+ with_routing do |set|
+ set.draw do
+ resource :account, :only => :update
+ end
+
+ assert_singleton_resource_allowed_routes('accounts', {}, :update, [:new, :create, :show, :edit, :destroy])
+ assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :update, [:new, :create, :show, :edit, :destroy])
+
+ assert_not_nil set.named_routes[:account]
+ end
+ end
+
+ def test_singleton_resource_has_only_destroy_action_and_named_route
+ with_routing do |set|
+ set.draw do
+ resource :account, :only => :destroy
+ end
+
+ assert_singleton_resource_allowed_routes('accounts', {}, :destroy, [:new, :create, :show, :edit, :update])
+ assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, :destroy, [:new, :create, :show, :edit, :update])
+
+ assert_not_nil set.named_routes[:account]
+ end
+ end
+
+ def test_resource_has_only_collection_action
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => [] do
+ get :sale, :on => :collection
+ end
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
+
+ assert_recognizes({ :controller => 'products', :action => 'sale' }, :path => 'products/sale', :method => :get)
+ assert_recognizes({ :controller => 'products', :action => 'sale', :format => 'xml' }, :path => 'products/sale.xml', :method => :get)
+ end
+ end
+
+ def test_resource_has_only_member_action
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => [] do
+ get :preview, :on => :member
+ end
+ end
+
+ assert_resource_allowed_routes('products', {}, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
+ assert_resource_allowed_routes('products', { :format => 'xml' }, { :id => '1' }, [], [:index, :new, :create, :show, :edit, :update, :destroy])
+
+ assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1' }, :path => 'products/1/preview', :method => :get)
+ assert_recognizes({ :controller => 'products', :action => 'preview', :id => '1', :format => 'xml' }, :path => 'products/1/preview.xml', :method => :get)
+ end
+ end
+
+ def test_singleton_resource_has_only_member_action
+ with_routing do |set|
+ set.draw do
+ resource :account, :only => [] do
+ member do
+ get :signup
+ end
+ end
+ end
+
+ assert_singleton_resource_allowed_routes('accounts', {}, [], [:new, :create, :show, :edit, :update, :destroy])
+ assert_singleton_resource_allowed_routes('accounts', { :format => 'xml' }, [], [:new, :create, :show, :edit, :update, :destroy])
+
+ assert_recognizes({ :controller => 'accounts', :action => 'signup' }, :path => 'account/signup', :method => :get)
+ assert_recognizes({ :controller => 'accounts', :action => 'signup', :format => 'xml' }, :path => 'account/signup.xml', :method => :get)
+ end
+ end
+
+ def test_nested_resource_has_only_show_and_member_action
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => [:index, :show] do
+ resources :images, :only => :show do
+ get :thumbnail, :on => :member
+ end
+ end
+ end
+
+ assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images')
+ assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :show, [:index, :new, :create, :edit, :update, :destroy], 'products/1/images')
+
+ assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2' }, :path => 'products/1/images/2/thumbnail', :method => :get)
+ assert_recognizes({ :controller => 'images', :action => 'thumbnail', :product_id => '1', :id => '2', :format => 'jpg' }, :path => 'products/1/images/2/thumbnail.jpg', :method => :get)
+ end
+ end
+
+ def test_nested_resource_does_not_inherit_only_option
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => :show do
+ resources :images, :except => :destroy
+ end
+ end
+
+ assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images')
+ assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update], :destroy, 'products/1/images')
+ end
+ end
+
+ def test_nested_resource_does_not_inherit_only_option_by_default
+ with_routing do |set|
+ set.draw do
+ resources :products, :only => :show do
+ resources :images
+ end
+ end
+
+ assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images')
+ assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images')
+ end
+ end
+
+ def test_nested_resource_does_not_inherit_except_option
+ with_routing do |set|
+ set.draw do
+ resources :products, :except => :show do
+ resources :images, :only => :destroy
+ end
+ end
+
+ assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images')
+ assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, :destroy, [:index, :new, :create, :show, :edit, :update], 'products/1/images')
+ end
+ end
+
+ def test_nested_resource_does_not_inherit_except_option_by_default
+ with_routing do |set|
+ set.draw do
+ resources :products, :except => :show do
+ resources :images
+ end
+ end
+
+ assert_resource_allowed_routes('images', { :product_id => '1' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images')
+ assert_resource_allowed_routes('images', { :product_id => '1', :format => 'xml' }, { :id => '2' }, [:index, :new, :create, :show, :edit, :update, :destroy], [], 'products/1/images')
+ end
+ end
+
+ def test_default_singleton_restful_route_uses_get
+ with_routing do |set|
+ set.draw do
+ resource :product
+ end
+
+ assert_routing '/product', :controller => 'products', :action => 'show'
+ assert set.recognize_path("/product", :method => :get)
+ end
+ end
+
+ def test_singleton_resource_name_is_not_singularized
+ with_singleton_resources(:preferences) do
+ assert_singleton_restful_for :preferences
+ end
+ end
+
+ protected
+ def with_restful_routing(*args)
+ options = args.extract_options!
+ collection_methods = options.delete(:collection)
+ member_methods = options.delete(:member)
+ path_prefix = options.delete(:path_prefix)
+ args.push(options)
+
+ with_routing do |set|
+ set.draw do
+ scope(path_prefix || '') do
+ resources(*args) do
+ if collection_methods
+ collection do
+ collection_methods.each do |name, method|
+ send(method, name)
+ end
+ end
+ end
+
+ if member_methods
+ member do
+ member_methods.each do |name, method|
+ send(method, name)
+ end
+ end
+ end
+ end
+ end
+ end
+ yield
+ end
+ end
+
+ def with_singleton_resources(*args)
+ with_routing do |set|
+ set.draw {resource(*args) }
+ yield
+ end
+ end
+
+ # runs assert_restful_routes_for and assert_restful_named_routes for on the controller_name and options, without passing a block.
+ def assert_simply_restful_for(controller_name, options = {})
+ assert_restful_routes_for controller_name, options
+ assert_restful_named_routes_for controller_name, nil, options
+ end
+
+ def assert_singleton_restful_for(singleton_name, options = {})
+ assert_singleton_routes_for singleton_name, options
+ assert_singleton_named_routes_for singleton_name, options
+ end
+
+ def assert_restful_routes_for(controller_name, options = {})
+ options[:options] ||= {}
+ options[:options][:controller] = options[:controller] || controller_name.to_s
+
+ if options[:shallow]
+ options[:shallow_options] ||= {}
+ options[:shallow_options][:controller] = options[:options][:controller]
+ else
+ options[:shallow_options] = options[:options]
+ end
+
+ new_action = @routes.resources_path_names[:new] || "new"
+ edit_action = @routes.resources_path_names[:edit] || "edit"
+
+ if options[:path_names]
+ new_action = options[:path_names][:new] if options[:path_names][:new]
+ edit_action = options[:path_names][:edit] if options[:path_names][:edit]
+ end
+
+ path = "#{options[:as] || controller_name}"
+ collection_path = "/#{options[:path_prefix]}#{path}"
+ shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}"
+ member_path = "#{shallow_path}/1"
+ new_path = "#{collection_path}/#{new_action}"
+ edit_member_path = "#{member_path}/#{edit_action}"
+ formatted_edit_member_path = "#{member_path}/#{edit_action}.xml"
+
+ with_options(options[:options]) do |controller|
+ controller.assert_routing collection_path, :action => 'index'
+ controller.assert_routing new_path, :action => 'new'
+ controller.assert_routing "#{collection_path}.xml", :action => 'index', :format => 'xml'
+ controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
+ end
+
+ with_options(options[:shallow_options]) do |controller|
+ controller.assert_routing member_path, :action => 'show', :id => '1'
+ controller.assert_routing edit_member_path, :action => 'edit', :id => '1'
+ controller.assert_routing "#{member_path}.xml", :action => 'show', :id => '1', :format => 'xml'
+ controller.assert_routing formatted_edit_member_path, :action => 'edit', :id => '1', :format => 'xml'
+ end
+
+ assert_recognizes(options[:options].merge(:action => 'index'), :path => collection_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create'), :path => collection_path, :method => :post)
+ assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1'), :path => member_path, :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1'), :path => edit_member_path, :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1'), :path => member_path, :method => :put)
+ assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1'), :path => member_path, :method => :delete)
+
+ assert_recognizes(options[:options].merge(:action => 'index', :format => 'xml'), :path => "#{collection_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{collection_path}.xml", :method => :post)
+ assert_recognizes(options[:shallow_options].merge(:action => 'show', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'edit', :id => '1', :format => 'xml'), :path => formatted_edit_member_path, :method => :get)
+ assert_recognizes(options[:shallow_options].merge(:action => 'update', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :put)
+ assert_recognizes(options[:shallow_options].merge(:action => 'destroy', :id => '1', :format => 'xml'), :path => "#{member_path}.xml", :method => :delete)
+
+ yield options[:options] if block_given?
+ end
+
+ # test named routes like foo_path and foos_path map to the correct options.
+ def assert_restful_named_routes_for(controller_name, singular_name = nil, options = {})
+ if singular_name.is_a?(Hash)
+ options = singular_name
+ singular_name = nil
+ end
+ singular_name ||= controller_name.to_s.singularize
+
+ options[:options] ||= {}
+ options[:options][:controller] = options[:controller] || controller_name.to_s
+
+ if options[:shallow]
+ options[:shallow_options] ||= {}
+ options[:shallow_options][:controller] = options[:options][:controller]
+ else
+ options[:shallow_options] = options[:options]
+ end
+
+ @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ @controller.singleton_class.send(:include, @routes.url_helpers)
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ get :index, options[:options]
+ options[:options].delete :action
+
+ path = "#{options[:as] || controller_name}"
+ shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}"
+ full_path = "/#{options[:path_prefix]}#{path}"
+ name_prefix = options[:name_prefix]
+ shallow_prefix = options[:shallow] ? options[:namespace].try(:gsub, /\//, '_') : options[:name_prefix]
+
+ new_action = "new"
+ edit_action = "edit"
+ if options[:path_names]
+ new_action = options[:path_names][:new] || "new"
+ edit_action = options[:path_names][:edit] || "edit"
+ end
+
+ assert_named_route "#{full_path}", "#{name_prefix}#{controller_name}_path", options[:options]
+ assert_named_route "#{full_path}.xml", "#{name_prefix}#{controller_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{shallow_path}/1", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
+ assert_named_route "#{shallow_path}/1.xml", "#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
+
+ assert_named_route "#{full_path}/#{new_action}", "new_#{name_prefix}#{singular_name}_path", options[:options]
+ assert_named_route "#{full_path}/#{new_action}.xml", "new_#{name_prefix}#{singular_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{shallow_path}/1/#{edit_action}", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1')
+ assert_named_route "#{shallow_path}/1/#{edit_action}.xml", "edit_#{shallow_prefix}#{singular_name}_path", options[:shallow_options].merge(:id => '1', :format => 'xml')
+
+ yield options[:options] if block_given?
+ end
+
+ def assert_singleton_routes_for(singleton_name, options = {})
+ options[:options] ||= {}
+ options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
+
+ full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
+ new_path = "#{full_path}/new"
+ edit_path = "#{full_path}/edit"
+ formatted_edit_path = "#{full_path}/edit.xml"
+
+ with_options options[:options] do |controller|
+ controller.assert_routing full_path, :action => 'show'
+ controller.assert_routing new_path, :action => 'new'
+ controller.assert_routing edit_path, :action => 'edit'
+ controller.assert_routing "#{full_path}.xml", :action => 'show', :format => 'xml'
+ controller.assert_routing "#{new_path}.xml", :action => 'new', :format => 'xml'
+ controller.assert_routing formatted_edit_path, :action => 'edit', :format => 'xml'
+ end
+
+ assert_recognizes(options[:options].merge(:action => 'show'), :path => full_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new'), :path => new_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'edit'), :path => edit_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create'), :path => full_path, :method => :post)
+ assert_recognizes(options[:options].merge(:action => 'update'), :path => full_path, :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'destroy'), :path => full_path, :method => :delete)
+
+ assert_recognizes(options[:options].merge(:action => 'show', :format => 'xml'), :path => "#{full_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'new', :format => 'xml'), :path => "#{new_path}.xml", :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'edit', :format => 'xml'), :path => formatted_edit_path, :method => :get)
+ assert_recognizes(options[:options].merge(:action => 'create', :format => 'xml'), :path => "#{full_path}.xml", :method => :post)
+ assert_recognizes(options[:options].merge(:action => 'update', :format => 'xml'), :path => "#{full_path}.xml", :method => :put)
+ assert_recognizes(options[:options].merge(:action => 'destroy', :format => 'xml'), :path => "#{full_path}.xml", :method => :delete)
+
+ yield options[:options] if block_given?
+ end
+
+ def assert_singleton_named_routes_for(singleton_name, options = {})
+ (options[:options] ||= {})[:controller] ||= singleton_name.to_s.pluralize
+ @controller = "#{options[:options][:controller].camelize}Controller".constantize.new
+ @controller.singleton_class.send(:include, @routes.url_helpers)
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ get :show, options[:options]
+ options[:options].delete :action
+
+ full_path = "/#{options[:path_prefix]}#{options[:as] || singleton_name}"
+ name_prefix = options[:name_prefix]
+
+ assert_named_route "#{full_path}", "#{name_prefix}#{singleton_name}_path", options[:options]
+ assert_named_route "#{full_path}.xml", "#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+
+ assert_named_route "#{full_path}/new", "new_#{name_prefix}#{singleton_name}_path", options[:options]
+ assert_named_route "#{full_path}/new.xml", "new_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ assert_named_route "#{full_path}/edit", "edit_#{name_prefix}#{singleton_name}_path", options[:options]
+ assert_named_route "#{full_path}/edit.xml", "edit_#{name_prefix}#{singleton_name}_path", options[:options].merge(:format => 'xml')
+ end
+
+ def assert_named_route(expected, route, options)
+ actual = @controller.send(route, options) rescue $!.class.name
+ assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
+ end
+
+ def assert_resource_methods(expected, resource, action_method, method)
+ assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
+ expected.each do |action|
+ assert resource.send("#{action_method}_methods")[method].include?(action)
+ "#{method} not in #{action_method} methods: #{resource.send("#{action_method}_methods")[method].inspect}"
+ end
+ end
+
+ def assert_resource_allowed_routes(controller, options, shallow_options, allowed, not_allowed, path = controller)
+ shallow_path = "#{path}/#{shallow_options[:id]}"
+ format = options[:format] && ".#{options[:format]}"
+ options.merge!(:controller => controller)
+ shallow_options.merge!(options)
+
+ assert_whether_allowed(allowed, not_allowed, options, 'index', "#{path}#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post)
+ assert_whether_allowed(allowed, not_allowed, shallow_options, 'show', "#{shallow_path}#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, shallow_options, 'edit', "#{shallow_path}/edit#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, shallow_options, 'update', "#{shallow_path}#{format}", :put)
+ assert_whether_allowed(allowed, not_allowed, shallow_options, 'destroy', "#{shallow_path}#{format}", :delete)
+ end
+
+ def assert_singleton_resource_allowed_routes(controller, options, allowed, not_allowed, path = controller.singularize)
+ format = options[:format] && ".#{options[:format]}"
+ options.merge!(:controller => controller)
+
+ assert_whether_allowed(allowed, not_allowed, options, 'new', "#{path}/new#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, options, 'create', "#{path}#{format}", :post)
+ assert_whether_allowed(allowed, not_allowed, options, 'show', "#{path}#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, options, 'edit', "#{path}/edit#{format}", :get)
+ assert_whether_allowed(allowed, not_allowed, options, 'update', "#{path}#{format}", :put)
+ assert_whether_allowed(allowed, not_allowed, options, 'destroy', "#{path}#{format}", :delete)
+ end
+
+ def assert_whether_allowed(allowed, not_allowed, options, action, path, method)
+ action = action.to_sym
+ options = options.merge(:action => action.to_s)
+ path_options = { :path => path, :method => method }
+
+ if Array(allowed).include?(action)
+ assert_recognizes options, path_options
+ elsif Array(not_allowed).include?(action)
+ assert_not_recognizes options, path_options
+ else
+ raise Assertion, 'Invalid Action has passed'
+ end
+ end
+
+ def assert_not_recognizes(expected_options, path)
+ assert_raise Assertion do
+ assert_recognizes(expected_options, path)
+ end
+ end
+end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
new file mode 100644
index 0000000000..c18914cc8e
--- /dev/null
+++ b/actionpack/test/controller/routing_test.rb
@@ -0,0 +1,2023 @@
+# encoding: utf-8
+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
+
+ROUTING = ActionDispatch::Routing
+
+# 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
+ get ':controller/:action/:variable/*additional'
+ end
+
+ safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
+ hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase }
+
+ @segment = "#{safe.join}#{unsafe.join}".freeze
+ @escaped = "#{safe.join}#{hex.join}".freeze
+ 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_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_precidence
+ 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? and 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? and 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_draw_with_block_arity_one_raises
+ assert_raise(RuntimeError) do
+ rs.draw { |map| map.match '/:controller(/:action(/:id))' }
+ end
+ 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 { 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_ignores_leading_slash
+ rs.clear!
+ rs.draw { get '/:controller(/:action(/:id))'}
+ test_default_setup
+ end
+
+ def test_route_with_colon_first
+ rs.draw do
+ get '/:controller/:action/:id', :action => 'index', :id => nil
+ get ':url', :controller => 'tiny_url', :action => 'translate'
+ end
+ end
+
+ def test_route_with_regexp_for_controller
+ rs.draw do
+ get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
+ get '/:controller(/:action(/:id))'
+ 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
+ get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ 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
+ get ':controller/:action/:file',
+ :controller => /admin|user/,
+ :action => /upload|download/,
+ :defaults => {:file => nil},
+ :constraints => {:file => %r{[^/]+(\.[^/]+)?}}
+ 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
+ get ':controller/:action/:id', :as => 'normal'
+ 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+/
+ get ':controller/:action/:id'
+ 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 { 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'
+ get ':controller/:action/:id'
+ 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
+ get ':controller/:action/:id'
+ 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
+ get 'page/:id(/:action)' => 'pages#show'
+ get ':controller(/:action(/:id))'
+ 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_fixnum_default
+ rs.draw do
+ get 'page(/:id)' => 'content#show_page', :id => 1
+ get ':controller/:action/:id'
+ 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
+ get ':controller/:action/:id'
+ 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 { 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}/ }
+ )
+ get ':controller/:action/:id'
+ 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
+ get ':controller/:action/:id'
+ 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'
+ get ':controller(/:action(/:id))'
+ 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
+ get ':controller/:action/:id'
+ 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
+ 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
+
+ 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
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ 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'
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ 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'
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ 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 = ROUTING::RouteSet.new
+ set.draw do
+ get '/:controller(/:action(/:id))'
+ end
+ set
+ end
+ end
+
+ def test_generate_extras
+ set.draw { 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 { |e| e.to_s }.sort
+ end
+
+ def test_extra_keys
+ set.draw { 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 { |e| e.to_s }.sort
+ end
+
+ def test_generate_extras_not_first
+ set.draw do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ 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 { |e| e.to_s }.sort
+ end
+
+ def test_generate_not_first
+ set.draw do
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ 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
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
+ end
+ extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal %w(that this), extras.map { |e| e.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')
+ 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
+ get '/:controller/:action/:id'
+ 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+/
+ get '/:controller(/:action(/:id))'
+ 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 !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
+ set.draw do
+ get '/profile' => 'profile#index'
+ end
+
+ request_path_params("/profile") rescue nil
+
+ assert !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
+ get ':controller/:id/:action'
+ 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"
+ get ':controller/:action/:id'
+ 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 { 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
+ get ':controller(/:action(/:id))'
+ 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
+ get ':controller(/:action(/:id))'
+ 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
+ get "/connection/manage(/:action)" => 'connection/manage#index'
+ get "/connection/connection" => "connection/connection#index"
+ get '/connection' => 'connection#index', :as => 'family_connection'
+ 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
+ get ':controller(/:action(/:id))'
+ 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'
+ get ':controller(/:action(/:id))'
+ 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 { 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
+ get '/cars/:action/:person/:car/', :controller => 'cars'
+ 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
+ get '/books/:action.rss', :controller => 'books'
+ 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
+ get '/books(/:action(.:format))', :controller => 'books'
+ 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 { 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 }
+
+ get ':controller/:action/:id'
+ end
+
+ assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 })
+ end
+
+ include ActionDispatch::RoutingVerbs
+
+ class TestSet < ROUTING::RouteSet
+ def initialize(block)
+ @block = block
+ super()
+ end
+
+ class Dispatcher < ROUTING::RouteSet::Dispatcher
+ def initialize(defaults, set, block)
+ super(defaults)
+ @block = block
+ @set = set
+ end
+
+ def controller_reference(controller_param)
+ block = @block
+ set = @set
+ Class.new(ActionController::Base) {
+ include set.url_helpers
+ define_method(:process) { |name| block.call(self) }
+ def to_a; [200, {}, []]; end
+ }
+ end
+ end
+
+ def dispatcher defaults
+ TestSet::Dispatcher.new defaults, self, @block
+ end
+ end
+
+ alias :routes :set
+
+ def test_generate_with_optional_params_recalls_last_request
+ controller = nil
+ @set = TestSet.new ->(c) { controller = c }
+
+ 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+/
+ get "blog/:controller/:action(/:id)"
+ 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"
+
+ 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'
+ get 'こんにちは/世界', :controller => 'news', :action => 'index'
+ match ':controller(/:action(/:id))(.:format)', :via => :all
+ 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'}, params)
+
+ params = {:controller => 'people', :foo => 'bar'}
+ assert_equal [:foo], @routes.extra_keys(params)
+ assert_equal({:controller => 'people', :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
diff --git a/actionpack/test/controller/runner_test.rb b/actionpack/test/controller/runner_test.rb
new file mode 100644
index 0000000000..3e9383abb2
--- /dev/null
+++ b/actionpack/test/controller/runner_test.rb
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+require 'action_dispatch/testing/integration'
+
+module ActionDispatch
+ class RunnerTest < ActiveSupport::TestCase
+ class MyRunner
+ include Integration::Runner
+
+ def initialize(session)
+ @integration_session = session
+ end
+
+ def hi; end
+ end
+
+ def test_respond_to?
+ runner = MyRunner.new(Class.new { def x; end }.new)
+ assert runner.respond_to?(:hi)
+ assert runner.respond_to?(:x)
+ end
+ end
+end
diff --git a/actionpack/test/controller/selector_test.rb b/actionpack/test/controller/selector_test.rb
new file mode 100644
index 0000000000..1e80c8601c
--- /dev/null
+++ b/actionpack/test/controller/selector_test.rb
@@ -0,0 +1,629 @@
+#--
+# Copyright (c) 2006 Assaf Arkin (http://labnotes.org)
+# Under MIT and/or CC By license.
+#++
+
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'action_view/vendor/html-scanner'
+
+class SelectorTest < ActiveSupport::TestCase
+ #
+ # Basic selector: element, id, class, attributes.
+ #
+
+ def test_element
+ parse(%Q{<div id="1"></div><p></p><div id="2"></div>})
+ # Match element by name.
+ select("div")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Not case sensitive.
+ select("DIV")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Universal match (all elements).
+ select("*")
+ assert_equal 3, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal nil, @matches[1].attributes["id"]
+ assert_equal "2", @matches[2].attributes["id"]
+ end
+
+
+ def test_identifier
+ parse(%Q{<div id="1"></div><p></p><div id="2"></div>})
+ # Match element by ID.
+ select("div#1")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match element by ID, substitute value.
+ select("div#?", 2)
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Element name does not match ID.
+ select("p#?", 2)
+ assert_equal 0, @matches.size
+ # Use regular expression.
+ select("#?", /\d/)
+ assert_equal 2, @matches.size
+ end
+
+
+ def test_class_name
+ parse(%Q{<div id="1" class=" foo "></div><p id="2" class=" foo bar "></p><div id="3" class="bar"></div>})
+ # Match element with specified class.
+ select("div.foo")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match any element with specified class.
+ select("*.foo")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Match elements with other class.
+ select("*.bar")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Match only element with both class names.
+ select("*.bar.foo")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ end
+
+
+ def test_attribute
+ parse(%Q{<div id="1"></div><p id="2" title="" bar="foo"></p><div id="3" title="foo"></div>})
+ # Match element with attribute.
+ select("div[title]")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Match any element with attribute.
+ select("*[title]")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Match element with attribute value.
+ select("*[title=foo]")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Match element with attribute and attribute value.
+ select("[bar=foo][title]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Not case sensitive.
+ select("[BAR=foo][TiTle]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ end
+
+
+ def test_attribute_quoted
+ parse(%Q{<div id="1" title="foo"></div><div id="2" title="bar"></div><div id="3" title=" bar "></div>})
+ # Match without quotes.
+ select("[title = bar]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match with single quotes.
+ select("[title = 'bar' ]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match with double quotes.
+ select("[title = \"bar\" ]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match with spaces.
+ select("[title = \" bar \" ]")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ end
+
+
+ def test_attribute_equality
+ parse(%Q{<div id="1" title="foo bar"></div><div id="2" title="barbaz"></div>})
+ # Match (fail) complete value.
+ select("[title=bar]")
+ assert_equal 0, @matches.size
+ # Match space-separate word.
+ select("[title~=foo]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("[title~=bar]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match beginning of value.
+ select("[title^=ba]")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Match end of value.
+ select("[title$=ar]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Match text in value.
+ select("[title*=bar]")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # Match first space separated word.
+ select("[title|=foo]")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("[title|=bar]")
+ assert_equal 0, @matches.size
+ end
+
+
+ #
+ # Selector composition: groups, sibling, children
+ #
+
+
+ def test_selector_group
+ parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>})
+ # Simple group selector.
+ select("h1,h3")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("h1 , h3")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Complex group selector.
+ parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>})
+ select("h1 a, h3 a")
+ assert_equal 2, @matches.size
+ assert_equal "foo", @matches[0].attributes["href"]
+ assert_equal "baz", @matches[1].attributes["href"]
+ # And now for the three selector challenge.
+ parse(%Q{<h1 id="1"><a href="foo"></a></h1><h2 id="2"><a href="bar"></a></h2><h3 id="2"><a href="baz"></a></h3>})
+ select("h1 a, h2 a, h3 a")
+ assert_equal 3, @matches.size
+ assert_equal "foo", @matches[0].attributes["href"]
+ assert_equal "bar", @matches[1].attributes["href"]
+ assert_equal "baz", @matches[2].attributes["href"]
+ end
+
+
+ def test_sibling_selector
+ parse(%Q{<h1 id="1"></h1><h2 id="2"></h2><h3 id="3"></h3>})
+ # Test next sibling.
+ select("h1+*")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select("h1+h2")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select("h1+h3")
+ assert_equal 0, @matches.size
+ select("*+h3")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Test any sibling.
+ select("h1~*")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("h2~*")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ end
+
+
+ def test_children_selector
+ parse(%Q{<div><p id="1"><span id="2"></span></p></div><div><p id="3"><span id="4" class="foo"></span></p></div>})
+ # Test child selector.
+ select("div>p")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("div>span")
+ assert_equal 0, @matches.size
+ select("div>p#3")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ select("div>p>span")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ # Test descendant selector.
+ select("div p")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("div span")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ select("div *#3")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ select("div *#4")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ # This is here because it failed before when whitespaces
+ # were not properly stripped.
+ select("div .foo")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ end
+
+
+ #
+ # Pseudo selectors: root, nth-child, empty, content, etc
+ #
+
+
+ def test_root_selector
+ parse(%Q{<div id="1"><div id="2"></div></div>})
+ # Can only find element if it's root.
+ select(":root")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("#1:root")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("#2:root")
+ assert_equal 0, @matches.size
+ # Opposite for nth-child.
+ select("#1:nth-child(1)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_odd_even
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Test odd nth children.
+ select("tr:nth-child(odd)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Test even nth children.
+ select("tr:nth-child(even)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ end
+
+
+ def test_nth_child_a_is_zero
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Test the third child.
+ select("tr:nth-child(0n+3)")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Same but an can be omitted when zero.
+ select("tr:nth-child(3)")
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ # Second element (but not every second element).
+ select("tr:nth-child(0n+2)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Before first and past last returns nothing.:
+ assert_raise(ArgumentError) { select("tr:nth-child(-1)") }
+ select("tr:nth-child(0)")
+ assert_equal 0, @matches.size
+ select("tr:nth-child(5)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_a_is_one
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # a is group of one, pick every element in group.
+ select("tr:nth-child(1n+0)")
+ assert_equal 4, @matches.size
+ # Same but a can be omitted when one.
+ select("tr:nth-child(n+0)")
+ assert_equal 4, @matches.size
+ # Same but b can be omitted when zero.
+ select("tr:nth-child(n)")
+ assert_equal 4, @matches.size
+ end
+
+
+ def test_nth_child_b_is_zero
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # If b is zero, pick the n-th element (here each one).
+ select("tr:nth-child(n+0)")
+ assert_equal 4, @matches.size
+ # If b is zero, pick the n-th element (here every second).
+ select("tr:nth-child(2n+0)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # If a and b are both zero, no element selected.
+ select("tr:nth-child(0n+0)")
+ assert_equal 0, @matches.size
+ select("tr:nth-child(0)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_a_is_negative
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Since a is -1, picks the first three elements.
+ select("tr:nth-child(-n+3)")
+ assert_equal 3, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ assert_equal "3", @matches[2].attributes["id"]
+ # Since a is -2, picks the first in every second of first four elements.
+ select("tr:nth-child(-2n+3)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ # Since a is -2, picks the first in every second of first three elements.
+ select("tr:nth-child(-2n+2)")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ end
+
+
+ def test_nth_child_b_is_negative
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Select last of four.
+ select("tr:nth-child(4n-1)")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ # Select first of four.
+ select("tr:nth-child(4n-4)")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ # Select last of every second.
+ select("tr:nth-child(2n-1)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ # Select nothing since an+b always < 0
+ select("tr:nth-child(-1n-1)")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_child_substitution_values
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Test with ?n?.
+ select("tr:nth-child(?n?)", 2, 1)
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "3", @matches[1].attributes["id"]
+ select("tr:nth-child(?n?)", 2, 2)
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ select("tr:nth-child(?n?)", 4, 2)
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ # Test with ? (b only).
+ select("tr:nth-child(?)", 3)
+ assert_equal 1, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ select("tr:nth-child(?)", 5)
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_nth_last_child
+ parse(%Q{<table><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # Last two elements.
+ select("tr:nth-last-child(-n+2)")
+ assert_equal 2, @matches.size
+ assert_equal "3", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ # All old elements counting from last one.
+ select("tr:nth-last-child(odd)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ end
+
+
+ def test_nth_of_type
+ parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # First two elements.
+ select("tr:nth-of-type(-n+2)")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ # All old elements counting from last one.
+ select("tr:nth-last-of-type(odd)")
+ assert_equal 2, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ assert_equal "4", @matches[1].attributes["id"]
+ end
+
+
+ def test_first_and_last
+ parse(%Q{<table><thead></thead><tr id="1"></tr><tr id="2"></tr><tr id="3"></tr><tr id="4"></tr></table>})
+ # First child.
+ select("tr:first-child")
+ assert_equal 0, @matches.size
+ select(":first-child")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ # First of type.
+ select("tr:first-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ select("thead:first-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ select("div:first-of-type")
+ assert_equal 0, @matches.size
+ # Last child.
+ select("tr:last-child")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ # Last of type.
+ select("tr:last-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "4", @matches[0].attributes["id"]
+ select("thead:last-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ select("div:last-of-type")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_only_child_and_only_type_first_and_last
+ # Only child.
+ parse(%Q{<table><tr></tr></table>})
+ select("table:only-child")
+ assert_equal 0, @matches.size
+ select("tr:only-child")
+ assert_equal 1, @matches.size
+ assert_equal "tr", @matches[0].name
+ parse(%Q{<table><tr></tr><tr></tr></table>})
+ select("tr:only-child")
+ assert_equal 0, @matches.size
+ # Only of type.
+ parse(%Q{<table><thead></thead><tr></tr><tr></tr></table>})
+ select("thead:only-of-type")
+ assert_equal 1, @matches.size
+ assert_equal "thead", @matches[0].name
+ select("td:only-of-type")
+ assert_equal 0, @matches.size
+ end
+
+
+ def test_empty
+ parse(%Q{<table><tr></tr></table>})
+ select("table:empty")
+ assert_equal 0, @matches.size
+ select("tr:empty")
+ assert_equal 1, @matches.size
+ parse(%Q{<div> </div>})
+ select("div:empty")
+ assert_equal 1, @matches.size
+ end
+
+
+ def test_content
+ parse(%Q{<div> </div>})
+ select("div:content()")
+ assert_equal 1, @matches.size
+ parse(%Q{<div>something </div>})
+ select("div:content()")
+ assert_equal 0, @matches.size
+ select("div:content(something)")
+ assert_equal 1, @matches.size
+ select("div:content( 'something' )")
+ assert_equal 1, @matches.size
+ select("div:content( \"something\" )")
+ assert_equal 1, @matches.size
+ select("div:content(?)", "something")
+ assert_equal 1, @matches.size
+ select("div:content(?)", /something/)
+ assert_equal 1, @matches.size
+ end
+
+
+ #
+ # Test negation.
+ #
+
+
+ def test_element_negation
+ parse(%Q{<p></p><div></div>})
+ select("*")
+ assert_equal 2, @matches.size
+ select("*:not(p)")
+ assert_equal 1, @matches.size
+ assert_equal "div", @matches[0].name
+ select("*:not(div)")
+ assert_equal 1, @matches.size
+ assert_equal "p", @matches[0].name
+ select("*:not(span)")
+ assert_equal 2, @matches.size
+ end
+
+
+ def test_id_negation
+ parse(%Q{<p id="1"></p><p id="2"></p>})
+ select("p")
+ assert_equal 2, @matches.size
+ select(":not(#1)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select(":not(#2)")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ end
+
+
+ def test_class_name_negation
+ parse(%Q{<p class="foo"></p><p class="bar"></p>})
+ select("p")
+ assert_equal 2, @matches.size
+ select(":not(.foo)")
+ assert_equal 1, @matches.size
+ assert_equal "bar", @matches[0].attributes["class"]
+ select(":not(.bar)")
+ assert_equal 1, @matches.size
+ assert_equal "foo", @matches[0].attributes["class"]
+ end
+
+
+ def test_attribute_negation
+ parse(%Q{<p title="foo"></p><p title="bar"></p>})
+ select("p")
+ assert_equal 2, @matches.size
+ select(":not([title=foo])")
+ assert_equal 1, @matches.size
+ assert_equal "bar", @matches[0].attributes["title"]
+ select(":not([title=bar])")
+ assert_equal 1, @matches.size
+ assert_equal "foo", @matches[0].attributes["title"]
+ end
+
+
+ def test_pseudo_class_negation
+ parse(%Q{<div><p id="1"></p><p id="2"></p></div>})
+ select("p")
+ assert_equal 2, @matches.size
+ select("p:not(:first-child)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ select("p:not(:nth-child(2))")
+ assert_equal 1, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ end
+
+
+ def test_negation_details
+ parse(%Q{<p id="1"></p><p id="2"></p><p id="3"></p>})
+ assert_raise(ArgumentError) { select(":not(") }
+ assert_raise(ArgumentError) { select(":not(:not())") }
+ select("p:not(#1):not(#3)")
+ assert_equal 1, @matches.size
+ assert_equal "2", @matches[0].attributes["id"]
+ end
+
+
+ def test_select_from_element
+ parse(%Q{<div><p id="1"></p><p id="2"></p></div>})
+ select("div")
+ @matches = @matches[0].select("p")
+ assert_equal 2, @matches.size
+ assert_equal "1", @matches[0].attributes["id"]
+ assert_equal "2", @matches[1].attributes["id"]
+ end
+
+
+protected
+
+ def parse(html)
+ @html = HTML::Document.new(html).root
+ end
+
+ def select(*selector)
+ @matches = HTML.selector(*selector).select(@html)
+ end
+
+end
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
new file mode 100644
index 0000000000..c002cf4d8f
--- /dev/null
+++ b/actionpack/test/controller/send_file_test.rb
@@ -0,0 +1,209 @@
+# encoding: utf-8
+require 'abstract_unit'
+
+module TestFileUtils
+ def file_name() File.basename(__FILE__) end
+ def file_path() File.expand_path(__FILE__) end
+ def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
+end
+
+class SendFileController < ActionController::Base
+ include TestFileUtils
+ include ActionController::Testing
+ layout "layouts/standard" # to make sure layouts don't interfere
+
+ attr_writer :options
+ def options
+ @options ||= {}
+ end
+
+ def file
+ send_file(file_path, options)
+ end
+
+ def data
+ send_data(file_data, options)
+ end
+end
+
+class SendFileWithActionControllerLive < SendFileController
+ include ActionController::Live
+end
+
+class SendFileTest < ActionController::TestCase
+ include TestFileUtils
+
+ def setup
+ @controller = SendFileController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_file_nostream
+ @controller.options = { :stream => false }
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ body = response.body
+ assert_kind_of String, body
+ assert_equal file_data, body
+ end
+
+ def test_file_stream
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_respond_to response.stream, :each
+ assert_respond_to response.stream, :to_path
+
+ require 'stringio'
+ output = StringIO.new
+ output.binmode
+ output.string.force_encoding(file_data.encoding)
+ response.body_parts.each { |part| output << part.to_s }
+ assert_equal file_data, output.string
+ end
+
+ def test_file_url_based_filename
+ @controller.options = { :url_based_filename => true }
+ response = nil
+ assert_nothing_raised { response = process('file') }
+ assert_not_nil response
+ assert_equal "attachment", response.headers["Content-Disposition"]
+ end
+
+ def test_data
+ response = nil
+ assert_nothing_raised { response = process('data') }
+ assert_not_nil response
+
+ assert_kind_of String, response.body
+ assert_equal file_data, response.body
+ end
+
+ def test_headers_after_send_shouldnt_include_charset
+ response = process('data')
+ assert_equal "application/octet-stream", response.headers["Content-Type"]
+
+ response = process('file')
+ assert_equal "application/octet-stream", response.headers["Content-Type"]
+ end
+
+ # Test that send_file_headers! is setting the correct HTTP headers.
+ def test_send_file_headers_bang
+ options = {
+ :type => Mime::PNG,
+ :disposition => 'disposition',
+ :filename => 'filename'
+ }
+
+ # Do it a few times: the resulting headers should be identical
+ # no matter how many times you send with the same options.
+ # Test resolving Ticket #458.
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ @controller.send(:send_file_headers!, options)
+ @controller.send(:send_file_headers!, options)
+
+ h = @controller.headers
+ assert_equal 'image/png', @controller.content_type
+ assert_equal 'disposition; filename="filename"', h['Content-Disposition']
+ assert_equal 'binary', h['Content-Transfer-Encoding']
+
+ # test overriding Cache-Control: no-cache header to fix IE open/save dialog
+ @controller.send(:send_file_headers!, options)
+ @controller.response.prepare!
+ assert_equal 'private', h['Cache-Control']
+ end
+
+ def test_send_file_headers_with_disposition_as_a_symbol
+ options = {
+ :type => Mime::PNG,
+ :disposition => :disposition,
+ :filename => 'filename'
+ }
+
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ assert_equal 'disposition; filename="filename"', @controller.headers['Content-Disposition']
+ end
+
+ def test_send_file_headers_with_mime_lookup_with_symbol
+ options = {
+ :type => :png
+ }
+
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+
+ assert_equal 'image/png', @controller.content_type
+ end
+
+
+ def test_send_file_headers_with_bad_symbol
+ options = {
+ :type => :this_type_is_not_registered
+ }
+
+ @controller.headers = {}
+ assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) }
+ end
+
+ def test_send_file_headers_guess_type_from_extension
+ {
+ 'image.png' => 'image/png',
+ 'image.jpeg' => 'image/jpeg',
+ 'image.jpg' => 'image/jpeg',
+ 'image.tif' => 'image/tiff',
+ 'image.gif' => 'image/gif',
+ 'movie.mpg' => 'video/mpeg',
+ 'file.zip' => 'application/zip',
+ 'file.unk' => 'application/octet-stream',
+ 'zip' => 'application/octet-stream'
+ }.each do |filename,expected_type|
+ options = { :filename => filename }
+ @controller.headers = {}
+ @controller.send(:send_file_headers!, options)
+ assert_equal expected_type, @controller.content_type
+ end
+ end
+
+ def test_send_file_with_default_content_disposition_header
+ process('data')
+ assert_equal 'attachment', @controller.headers['Content-Disposition']
+ end
+
+ def test_send_file_without_content_disposition_header
+ @controller.options = {:disposition => nil}
+ process('data')
+ assert_nil @controller.headers['Content-Disposition']
+ end
+
+ %w(file data).each do |method|
+ define_method "test_send_#{method}_status" do
+ @controller.options = { :stream => false, :status => 500 }
+ assert_nothing_raised { assert_not_nil process(method) }
+ assert_equal 500, @response.status
+ end
+
+ define_method "test_send_#{method}_content_type" do
+ @controller.options = { :stream => false, :content_type => "application/x-ruby" }
+ assert_nothing_raised { assert_not_nil process(method) }
+ assert_equal "application/x-ruby", @response.content_type
+ end
+
+ define_method "test_default_send_#{method}_status" do
+ @controller.options = { :stream => false }
+ assert_nothing_raised { assert_not_nil process(method) }
+ assert_equal 200, @response.status
+ end
+ end
+
+ def test_send_file_with_action_controller_live
+ @controller = SendFileWithActionControllerLive.new
+ @controller.options = { :content_type => "application/x-ruby" }
+
+ response = process('file')
+ assert_equal 200, response.status
+ end
+end
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
new file mode 100644
index 0000000000..f7eba1ef43
--- /dev/null
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -0,0 +1,112 @@
+require 'abstract_unit'
+
+module ShowExceptions
+ class ShowExceptionsController < ActionController::Base
+ use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ use ActionDispatch::DebugExceptions
+
+ before_action only: :another_boom do
+ request.env["action_dispatch.show_detailed_exceptions"] = true
+ end
+
+ def boom
+ raise 'boom!'
+ end
+
+ def another_boom
+ raise 'boom!'
+ end
+
+ def show_detailed_exceptions?
+ request.local?
+ end
+ end
+
+ class ShowExceptionsTest < ActionDispatch::IntegrationTest
+ test 'show error page from a remote ip' do
+ @app = ShowExceptionsController.action(:boom)
+ self.remote_addr = '208.77.188.166'
+ get '/'
+ assert_equal "500 error fixture\n", body
+ end
+
+ test 'show diagnostics from a local ip if show_detailed_exceptions? is set to request.local?' do
+ @app = ShowExceptionsController.action(:boom)
+ ['127.0.0.1', '127.0.0.127', '127.12.1.1', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
+ self.remote_addr = ip_address
+ get '/'
+ assert_match(/boom/, body)
+ end
+ end
+
+ test 'show diagnostics from a remote ip when env is already set' do
+ @app = ShowExceptionsController.action(:another_boom)
+ self.remote_addr = '208.77.188.166'
+ get '/'
+ assert_match(/boom/, body)
+ end
+ end
+
+ class ShowExceptionsOverriddenController < ShowExceptionsController
+ private
+
+ def show_detailed_exceptions?
+ params['detailed'] == '1'
+ end
+ end
+
+ class ShowExceptionsOverriddenTest < ActionDispatch::IntegrationTest
+ test 'show error page' do
+ @app = ShowExceptionsOverriddenController.action(:boom)
+ get '/', {'detailed' => '0'}
+ assert_equal "500 error fixture\n", body
+ end
+
+ test 'show diagnostics message' do
+ @app = ShowExceptionsOverriddenController.action(:boom)
+ get '/', {'detailed' => '1'}
+ assert_match(/boom/, body)
+ end
+ end
+
+ class ShowExceptionsFormatsTest < ActionDispatch::IntegrationTest
+ def test_render_json_exception
+ @app = ShowExceptionsOverriddenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'application/json'
+ assert_response :internal_server_error
+ assert_equal 'application/json', response.content_type.to_s
+ assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_json, response.body)
+ end
+
+ def test_render_xml_exception
+ @app = ShowExceptionsOverriddenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'application/xml'
+ assert_response :internal_server_error
+ assert_equal 'application/xml', response.content_type.to_s
+ assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_xml, response.body)
+ end
+
+ def test_render_fallback_exception
+ @app = ShowExceptionsOverriddenController.action(:boom)
+ get "/", {}, 'HTTP_ACCEPT' => 'text/csv'
+ assert_response :internal_server_error
+ assert_equal 'text/html', response.content_type.to_s
+ end
+ end
+
+ class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest
+ def test_render_failsafe_exception
+ @app = ShowExceptionsOverriddenController.action(:boom)
+ @exceptions_app = @app.instance_variable_get(:@exceptions_app)
+ @app.instance_variable_set(:@exceptions_app, nil)
+ $stderr = StringIO.new
+
+ get '/', {}, 'HTTP_ACCEPT' => 'text/json'
+ assert_response :internal_server_error
+ assert_equal 'text/plain', response.content_type.to_s
+ ensure
+ @app.instance_variable_set(:@exceptions_app, @exceptions_app)
+ $stderr = STDERR
+ end
+ end
+end
diff --git a/actionpack/test/controller/streaming_test.rb b/actionpack/test/controller/streaming_test.rb
new file mode 100644
index 0000000000..6ee6444065
--- /dev/null
+++ b/actionpack/test/controller/streaming_test.rb
@@ -0,0 +1,26 @@
+require 'abstract_unit'
+
+module ActionController
+ class StreamingResponseTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def self.controller_path
+ 'test'
+ end
+
+ def basic_stream
+ %w{ hello world }.each do |word|
+ response.stream.write word
+ response.stream.write "\n"
+ end
+ response.stream.close
+ end
+ end
+
+ tests TestController
+
+ def test_write_to_stream
+ get :basic_stream
+ assert_equal "hello\nworld\n", @response.body
+ end
+ end
+end
diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb
new file mode 100644
index 0000000000..060c940100
--- /dev/null
+++ b/actionpack/test/controller/test_case_test.rb
@@ -0,0 +1,1007 @@
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'active_support/json/decoding'
+
+class TestCaseTest < ActionController::TestCase
+ class TestController < ActionController::Base
+ def no_op
+ render :text => 'dummy'
+ end
+
+ def set_flash
+ flash["test"] = ">#{flash["test"]}<"
+ render :text => 'ignore me'
+ end
+
+ def set_flash_now
+ flash.now["test_now"] = ">#{flash["test_now"]}<"
+ render :text => 'ignore me'
+ end
+
+ def set_session
+ session['string'] = 'A wonder'
+ session[:symbol] = 'it works'
+ render :text => 'Success'
+ end
+
+ def reset_the_session
+ reset_session
+ render :text => 'ignore me'
+ end
+
+ def render_raw_post
+ raise ActiveSupport::TestCase::Assertion, "#raw_post is blank" if request.raw_post.blank?
+ render :text => request.raw_post
+ end
+
+ def render_body
+ render :text => request.body.read
+ end
+
+ def test_params
+ render :text => params.inspect
+ end
+
+ def test_uri
+ render :text => request.fullpath
+ end
+
+ def test_format
+ render :text => request.format
+ end
+
+ def test_query_string
+ render :text => request.query_string
+ end
+
+ def test_protocol
+ render :text => request.protocol
+ end
+
+ def test_headers
+ render text: request.headers.env.to_json
+ end
+
+ def test_html_output
+ render :text => <<HTML
+<html>
+ <body>
+ <a href="/"><img src="/images/button.png" /></a>
+ <div id="foo">
+ <ul>
+ <li class="item">hello</li>
+ <li class="item">goodbye</li>
+ </ul>
+ </div>
+ <div id="bar">
+ <form action="/somewhere">
+ Name: <input type="text" name="person[name]" id="person_name" />
+ </form>
+ </div>
+ </body>
+</html>
+HTML
+ end
+
+ def test_xml_output
+ response.content_type = "application/xml"
+ render :text => <<XML
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <area>area is an empty tag in HTML, raising an error if not in xml mode</area>
+</root>
+XML
+ end
+
+ def test_only_one_param
+ render :text => (params[:left] && params[:right]) ? "EEP, Both here!" : "OK"
+ end
+
+ def test_remote_addr
+ render :text => (request.remote_addr || "not specified")
+ end
+
+ def test_file_upload
+ render :text => params[:file].size
+ end
+
+ def test_send_file
+ send_file(File.expand_path(__FILE__))
+ end
+
+ def redirect_to_same_controller
+ redirect_to :controller => 'test', :action => 'test_uri', :id => 5
+ end
+
+ def redirect_to_different_controller
+ redirect_to :controller => 'fail', :id => 5
+ end
+
+ def create
+ head :created, :location => 'created resource'
+ end
+
+ def delete_cookie
+ cookies.delete("foo")
+ render :nothing => true
+ end
+
+ def test_assigns
+ @foo = "foo"
+ @foo_hash = {:foo => :bar}
+ render :nothing => true
+ end
+
+ private
+
+ def generate_url(opts)
+ url_for(opts.merge(:action => "test_uri"))
+ end
+ end
+
+ def setup
+ super
+ @controller = TestController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.env['PATH_INFO'] = nil
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get ':controller(/:action(/:id))'
+ end
+ end
+ end
+
+ class ViewAssignsController < ActionController::Base
+ def test_assigns
+ @foo = "foo"
+ render :nothing => true
+ end
+
+ def view_assigns
+ { "bar" => "bar" }
+ end
+ end
+
+ class DefaultUrlOptionsCachingController < ActionController::Base
+ before_action { @dynamic_opt = 'opt' }
+
+ def test_url_options_reset
+ render text: url_for(params)
+ end
+
+ def default_url_options
+ if defined?(@dynamic_opt)
+ super.merge dynamic_opt: @dynamic_opt
+ else
+ super
+ end
+ end
+ end
+
+ def test_url_options_reset
+ @controller = DefaultUrlOptionsCachingController.new
+ get :test_url_options_reset
+ assert_nil @request.params['dynamic_opt']
+ assert_match(/dynamic_opt=opt/, @response.body)
+ end
+
+ def test_raw_post_handling
+ params = Hash[:page, {:name => 'page name'}, 'some key', 123]
+ post :render_raw_post, params.dup
+
+ assert_equal params.to_query, @response.body
+ end
+
+ def test_body_stream
+ params = Hash[:page, { :name => 'page name' }, 'some key', 123]
+
+ post :render_body, params.dup
+
+ assert_equal params.to_query, @response.body
+ end
+
+ def test_document_body_and_params_with_post
+ post :test_params, :id => 1
+ assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_case_test/test\", \"action\"=>\"test_params\"}", @response.body)
+ end
+
+ def test_document_body_with_post
+ post :render_body, "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_document_body_with_put
+ put :render_body, "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_head
+ head :test_params
+ assert_equal 200, @response.status
+ end
+
+ def test_head_params_as_string
+ assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
+ end
+
+ def test_process_without_flash
+ process :set_flash
+ assert_equal '><', flash['test']
+ end
+
+ def test_process_with_flash
+ process :set_flash, "GET", nil, nil, { "test" => "value" }
+ assert_equal '>value<', flash['test']
+ end
+
+ def test_process_with_flash_now
+ process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" }
+ assert_equal '>value_now<', flash['test_now']
+ end
+
+ def test_process_with_session
+ process :set_session
+ assert_equal 'A wonder', session['string'], "A value stored in the session should be available by string key"
+ assert_equal 'A wonder', session[:string], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session['symbol'], "Test session hash should allow indifferent access"
+ assert_equal 'it works', session[:symbol], "Test session hash should allow indifferent access"
+ end
+
+ def test_process_with_session_arg
+ process :no_op, "GET", nil, { 'string' => 'value1', :symbol => 'value2' }
+ assert_equal 'value1', session['string']
+ assert_equal 'value1', session[:string]
+ assert_equal 'value2', session['symbol']
+ assert_equal 'value2', session[:symbol]
+ end
+
+ def test_process_merges_session_arg
+ session[:foo] = 'bar'
+ get :no_op, nil, { :bar => 'baz' }
+ assert_equal 'bar', session[:foo]
+ assert_equal 'baz', session[:bar]
+ end
+
+ def test_merged_session_arg_is_retained_across_requests
+ get :no_op, nil, { :foo => 'bar' }
+ assert_equal 'bar', session[:foo]
+ get :no_op
+ assert_equal 'bar', session[:foo]
+ end
+
+ def test_process_overwrites_existing_session_arg
+ session[:foo] = 'bar'
+ get :no_op, nil, { :foo => 'baz' }
+ assert_equal 'baz', session[:foo]
+ end
+
+ def test_session_is_cleared_from_controller_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @controller.session.to_hash
+ end
+
+ def test_session_is_cleared_from_request_after_reset_session
+ process :set_session
+ process :reset_the_session
+ assert_equal Hash.new, @request.session.to_hash
+ end
+
+ def test_response_and_request_have_nice_accessors
+ process :no_op
+ assert_equal @response, response
+ assert_equal @request, request
+ end
+
+ def test_process_with_request_uri_with_no_params
+ process :test_uri
+ assert_equal "/test_case_test/test/test_uri", @response.body
+ end
+
+ def test_process_with_request_uri_with_params
+ process :test_uri, "GET", :id => 7
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
+ end
+
+ def test_process_with_request_uri_with_params_with_explicit_uri
+ @request.env['PATH_INFO'] = "/explicit/uri"
+ process :test_uri, "GET", :id => 7
+ assert_equal "/explicit/uri", @response.body
+ end
+
+ def test_process_with_query_string
+ process :test_query_string, "GET", :q => 'test'
+ assert_equal "q=test", @response.body
+ end
+
+ def test_process_with_query_string_with_explicit_uri
+ @request.env['PATH_INFO'] = '/explicit/uri'
+ @request.env['QUERY_STRING'] = 'q=test?extra=question'
+ process :test_query_string
+ assert_equal "q=test?extra=question", @response.body
+ end
+
+ def test_multiple_calls
+ process :test_only_one_param, "GET", :left => true
+ assert_equal "OK", @response.body
+ process :test_only_one_param, "GET", :right => true
+ assert_equal "OK", @response.body
+ end
+
+ def test_assigns
+ process :test_assigns
+ # assigns can be accessed using assigns(key)
+ # or assigns[key], where key is a string or
+ # a symbol
+ assert_equal "foo", assigns(:foo)
+ assert_equal "foo", assigns("foo")
+ assert_equal "foo", assigns[:foo]
+ assert_equal "foo", assigns["foo"]
+
+ # but the assigned variable should not have its own keys stringified
+ expected_hash = { :foo => :bar }
+ assert_equal expected_hash, assigns(:foo_hash)
+ end
+
+ def test_view_assigns
+ @controller = ViewAssignsController.new
+ process :test_assigns
+ assert_equal nil, assigns(:foo)
+ assert_equal nil, assigns[:foo]
+ assert_equal "bar", assigns(:bar)
+ assert_equal "bar", assigns[:bar]
+ end
+
+ def test_assert_tag_tag
+ process :test_html_output
+
+ # there is a 'form' tag
+ assert_tag :tag => 'form'
+ # there is not an 'hr' tag
+ assert_no_tag :tag => 'hr'
+ end
+
+ def test_assert_tag_attributes
+ process :test_html_output
+
+ # there is a tag with an 'id' of 'bar'
+ assert_tag :attributes => { :id => "bar" }
+ # there is no tag with a 'name' of 'baz'
+ assert_no_tag :attributes => { :name => "baz" }
+ end
+
+ def test_assert_tag_parent
+ process :test_html_output
+
+ # there is a tag with a parent 'form' tag
+ assert_tag :parent => { :tag => "form" }
+ # there is no tag with a parent of 'input'
+ assert_no_tag :parent => { :tag => "input" }
+ end
+
+ def test_assert_tag_child
+ process :test_html_output
+
+ # there is a tag with a child 'input' tag
+ assert_tag :child => { :tag => "input" }
+ # there is no tag with a child 'strong' tag
+ assert_no_tag :child => { :tag => "strong" }
+ end
+
+ def test_assert_tag_ancestor
+ process :test_html_output
+
+ # there is a 'li' tag with an ancestor having an id of 'foo'
+ assert_tag :ancestor => { :attributes => { :id => "foo" } }, :tag => "li"
+ # there is no tag of any kind with an ancestor having an href matching 'foo'
+ assert_no_tag :ancestor => { :attributes => { :href => /foo/ } }
+ end
+
+ def test_assert_tag_descendant
+ process :test_html_output
+
+ # there is a tag with a descendant 'li' tag
+ assert_tag :descendant => { :tag => "li" }
+ # there is no tag with a descendant 'html' tag
+ assert_no_tag :descendant => { :tag => "html" }
+ end
+
+ def test_assert_tag_sibling
+ process :test_html_output
+
+ # there is a tag with a sibling of class 'item'
+ assert_tag :sibling => { :attributes => { :class => "item" } }
+ # there is no tag with a sibling 'ul' tag
+ assert_no_tag :sibling => { :tag => "ul" }
+ end
+
+ def test_assert_tag_after
+ process :test_html_output
+
+ # there is a tag following a sibling 'div' tag
+ assert_tag :after => { :tag => "div" }
+ # there is no tag following a sibling tag with id 'bar'
+ assert_no_tag :after => { :attributes => { :id => "bar" } }
+ end
+
+ def test_assert_tag_before
+ process :test_html_output
+
+ # there is a tag preceding a tag with id 'bar'
+ assert_tag :before => { :attributes => { :id => "bar" } }
+ # there is no tag preceding a 'form' tag
+ assert_no_tag :before => { :tag => "form" }
+ end
+
+ def test_assert_tag_children_count
+ process :test_html_output
+
+ # there is a tag with 2 children
+ assert_tag :children => { :count => 2 }
+ # in particular, there is a <ul> tag with two children (a nameless pair of <li>s)
+ assert_tag :tag => 'ul', :children => { :count => 2 }
+ # there is no tag with 4 children
+ assert_no_tag :children => { :count => 4 }
+ end
+
+ def test_assert_tag_children_less_than
+ process :test_html_output
+
+ # there is a tag with less than 5 children
+ assert_tag :children => { :less_than => 5 }
+ # there is no 'ul' tag with less than 2 children
+ assert_no_tag :children => { :less_than => 2 }, :tag => "ul"
+ end
+
+ def test_assert_tag_children_greater_than
+ process :test_html_output
+
+ # there is a 'body' tag with more than 1 children
+ assert_tag :children => { :greater_than => 1 }, :tag => "body"
+ # there is no tag with more than 10 children
+ assert_no_tag :children => { :greater_than => 10 }
+ end
+
+ def test_assert_tag_children_only
+ process :test_html_output
+
+ # there is a tag containing only one child with an id of 'foo'
+ assert_tag :children => { :count => 1,
+ :only => { :attributes => { :id => "foo" } } }
+ # there is no tag containing only one 'li' child
+ assert_no_tag :children => { :count => 1, :only => { :tag => "li" } }
+ end
+
+ def test_assert_tag_content
+ process :test_html_output
+
+ # the output contains the string "Name"
+ assert_tag :content => /Name/
+ # the output does not contain the string "test"
+ assert_no_tag :content => /test/
+ end
+
+ def test_assert_tag_multiple
+ process :test_html_output
+
+ # there is a 'div', id='bar', with an immediate child whose 'action'
+ # attribute matches the regexp /somewhere/.
+ assert_tag :tag => "div", :attributes => { :id => "bar" },
+ :child => { :attributes => { :action => /somewhere/ } }
+
+ # there is no 'div', id='foo', with a 'ul' child with more than
+ # 2 "li" children.
+ assert_no_tag :tag => "div", :attributes => { :id => "foo" },
+ :child => {
+ :tag => "ul",
+ :children => { :greater_than => 2,
+ :only => { :tag => "li" } } }
+ end
+
+ def test_assert_tag_children_without_content
+ process :test_html_output
+
+ # there is a form tag with an 'input' child which is a self closing tag
+ assert_tag :tag => "form",
+ :children => { :count => 1,
+ :only => { :tag => "input" } }
+
+ # the body tag has an 'a' child which in turn has an 'img' child
+ assert_tag :tag => "body",
+ :children => { :count => 1,
+ :only => { :tag => "a",
+ :children => { :count => 1,
+ :only => { :tag => "img" } } } }
+ end
+
+ def test_should_not_impose_childless_html_tags_in_xml
+ process :test_xml_output
+
+ begin
+ $stderr = StringIO.new
+ assert_select 'area' #This will cause a warning if content is processed as HTML
+ $stderr.rewind && err = $stderr.read
+ ensure
+ $stderr = STDERR
+ end
+
+ assert err.empty?
+ end
+
+ def test_assert_tag_attribute_matching
+ @response.body = '<input type="text" name="my_name">'
+ assert_tag :tag => 'input',
+ :attributes => { :name => /my/, :type => 'text' }
+ assert_no_tag :tag => 'input',
+ :attributes => { :name => 'my', :type => 'text' }
+ assert_no_tag :tag => 'input',
+ :attributes => { :name => /^my$/, :type => 'text' }
+ end
+
+ def test_assert_tag_content_matching
+ @response.body = "<p>hello world</p>"
+ assert_tag :tag => "p", :content => "hello world"
+ assert_tag :tag => "p", :content => /hello/
+ assert_no_tag :tag => "p", :content => "hello"
+ end
+
+ def test_assert_generates
+ assert_generates 'controller/action/5', :controller => 'controller', :action => 'action', :id => '5'
+ assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action"}
+ assert_generates 'controller/action/5', {:controller => "controller", :action => "action", :id => "5", :name => "bob"}, {}, {:name => "bob"}
+ assert_generates 'controller/action/7', {:id => "7", :name => "bob"}, {:controller => "controller", :action => "action"}, {:name => "bob"}
+ assert_generates 'controller/action/7', {:id => "7"}, {:controller => "controller", :action => "action", :name => "bob"}, {}
+ end
+
+ def test_assert_routing
+ assert_routing 'content', :controller => 'content', :action => 'index'
+ end
+
+ def test_assert_routing_with_method
+ with_routing do |set|
+ set.draw { resources(:content) }
+ assert_routing({ :method => 'post', :path => 'content' }, { :controller => 'content', :action => 'create' })
+ end
+ end
+
+ def test_assert_routing_in_module
+ with_routing do |set|
+ set.draw do
+ namespace :admin do
+ get 'user' => 'user#index'
+ end
+ end
+
+ assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
+ end
+ end
+
+ def test_assert_routing_with_glob
+ with_routing do |set|
+ set.draw { get('*path' => "pages#show") }
+ assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' })
+ end
+ end
+
+ def test_params_passing
+ get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
+ parsed_params
+ )
+ end
+
+ def test_params_passing_with_fixnums
+ get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
+ parsed_params
+ )
+ end
+
+ def test_params_passing_with_fixnums_when_not_html_request
+ get :test_params, :format => 'json', :count => 999
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'count' => 999 },
+ parsed_params
+ )
+ end
+
+ def test_params_passing_path_parameter_is_string_when_not_html_request
+ get :test_params, :format => 'json', :id => 1
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'id' => '1' },
+ parsed_params
+ )
+ end
+
+ def test_params_passing_with_frozen_values
+ assert_nothing_raised do
+ get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze
+ end
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }},
+ parsed_params
+ )
+ end
+
+ def test_params_passing_doesnt_modify_in_place
+ page = {:name => "Page name", :month => 4, :year => 2004, :day => 6}
+ get :test_params, :page => page
+ assert_equal 2004, page[:year]
+ end
+
+ test "set additional HTTP headers" do
+ @request.headers['Referer'] = "http://nohost.com/home"
+ @request.headers['Content-Type'] = "application/rss+xml"
+ get :test_headers
+ parsed_env = ActiveSupport::JSON.decode(@response.body)
+ assert_equal "http://nohost.com/home", parsed_env["HTTP_REFERER"]
+ assert_equal "application/rss+xml", parsed_env["CONTENT_TYPE"]
+ end
+
+ test "set additional env variables" do
+ @request.headers['HTTP_REFERER'] = "http://example.com/about"
+ @request.headers['CONTENT_TYPE'] = "application/json"
+ get :test_headers
+ parsed_env = ActiveSupport::JSON.decode(@response.body)
+ assert_equal "http://example.com/about", parsed_env["HTTP_REFERER"]
+ assert_equal "application/json", parsed_env["CONTENT_TYPE"]
+ end
+
+ def test_id_converted_to_string
+ get :test_params, :id => 20, :foo => Object.new
+ assert_kind_of String, @request.path_parameters[:id]
+ end
+
+ def test_array_path_parameter_handled_properly
+ with_routing do |set|
+ set.draw do
+ get 'file/*path', :to => 'test_case_test/test#test_params'
+ get ':controller/:action'
+ end
+
+ get :test_params, :path => ['hello', 'world']
+ assert_equal ['hello', 'world'], @request.path_parameters[:path]
+ assert_equal 'hello/world', @request.path_parameters[:path].to_param
+ end
+ end
+
+ def test_assert_realistic_path_parameters
+ get :test_params, :id => 20, :foo => Object.new
+
+ # All elements of path_parameters should use Symbol keys
+ @request.path_parameters.keys.each do |key|
+ assert_kind_of Symbol, key
+ end
+ end
+
+ def test_with_routing_places_routes_back
+ assert @routes
+ routes_id = @routes.object_id
+
+ begin
+ with_routing { raise 'fail' }
+ fail 'Should not be here.'
+ rescue RuntimeError
+ end
+
+ assert @routes
+ assert_equal routes_id, @routes.object_id
+ end
+
+ def test_remote_addr
+ get :test_remote_addr
+ assert_equal "0.0.0.0", @response.body
+
+ @request.remote_addr = "192.0.0.1"
+ get :test_remote_addr
+ assert_equal "192.0.0.1", @response.body
+ end
+
+ def test_header_properly_reset_after_remote_http_request
+ xhr :get, :test_params
+ assert_nil @request.env['HTTP_X_REQUESTED_WITH']
+ assert_nil @request.env['HTTP_ACCEPT']
+ end
+
+ def test_header_properly_reset_after_get_request
+ get :test_params
+ @request.recycle!
+ assert_nil @request.instance_variable_get("@request_method")
+ end
+
+ def test_params_reset_between_post_requests
+ post :no_op, :foo => "bar"
+ assert_equal "bar", @request.params[:foo]
+
+ post :no_op
+ assert @request.params[:foo].blank?
+ end
+
+ def test_filtered_parameters_reset_between_requests
+ get :no_op, :foo => "bar"
+ assert_equal "bar", @request.filtered_parameters[:foo]
+
+ get :no_op, :foo => "baz"
+ assert_equal "baz", @request.filtered_parameters[:foo]
+ end
+
+ def test_path_params_reset_between_request
+ get :test_params, :id => "foo"
+ assert_equal "foo", @request.path_parameters[:id]
+
+ get :test_params
+ assert_nil @request.path_parameters[:id]
+ end
+
+ def test_request_protocol_is_reset_after_request
+ get :test_protocol
+ assert_equal "http://", @response.body
+
+ @request.env["HTTPS"] = "on"
+ get :test_protocol
+ assert_equal "https://", @response.body
+
+ @request.env.delete("HTTPS")
+ get :test_protocol
+ assert_equal "http://", @response.body
+ end
+
+ def test_request_format
+ get :test_format, :format => 'html'
+ assert_equal 'text/html', @response.body
+
+ get :test_format, :format => 'json'
+ assert_equal 'application/json', @response.body
+
+ get :test_format, :format => 'xml'
+ assert_equal 'application/xml', @response.body
+
+ get :test_format
+ assert_equal 'text/html', @response.body
+ end
+
+ def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
+ cookies['foo'] = 'bar'
+ get :no_op
+ assert_equal 'bar', cookies['foo']
+ end
+
+ def test_should_detect_if_cookie_is_deleted
+ cookies['foo'] = 'bar'
+ get :delete_cookie
+ assert_nil cookies['foo']
+ end
+
+ %w(controller response request).each do |variable|
+ %w(get post put delete head process).each do |method|
+ define_method("test_#{variable}_missing_for_#{method}_raises_error") do
+ remove_instance_variable "@#{variable}"
+ begin
+ send(method, :test_remote_addr)
+ assert false, "expected RuntimeError, got nothing"
+ rescue RuntimeError => error
+ assert_match(%r{@#{variable} is nil}, error.message)
+ rescue => error
+ assert false, "expected RuntimeError, got #{error.class}"
+ end
+ end
+ end
+ end
+
+ FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart'
+
+ READ_BINARY = 'rb:binary'
+ READ_PLAIN = 'r:binary'
+
+ def test_test_uploaded_file
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ content_type = 'image/png'
+ expected = File.read(path)
+ expected.force_encoding(Encoding::BINARY)
+
+ file = Rack::Test::UploadedFile.new(path, content_type)
+ assert_equal filename, file.original_filename
+ assert_equal content_type, file.content_type
+ assert_equal file.path, file.local_path
+ assert_equal expected, file.read
+
+ new_content_type = "new content_type"
+ file.content_type = new_content_type
+ assert_equal new_content_type, file.content_type
+
+ end
+
+ def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case
+ TestCaseTest.stubs(:fixture_path).returns(FILES_DIR)
+
+ uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png')
+ assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ end
+
+ def test_test_uploaded_file_with_binary
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ content_type = 'image/png'
+
+ binary_uploaded_file = Rack::Test::UploadedFile.new(path, content_type, :binary)
+ assert_equal File.open(path, READ_BINARY).read, binary_uploaded_file.read
+
+ plain_uploaded_file = Rack::Test::UploadedFile.new(path, content_type)
+ assert_equal File.open(path, READ_PLAIN).read, plain_uploaded_file.read
+ end
+
+ def test_fixture_file_upload_with_binary
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ content_type = 'image/jpg'
+
+ binary_file_upload = fixture_file_upload(path, content_type, :binary)
+ assert_equal File.open(path, READ_BINARY).read, binary_file_upload.read
+
+ plain_file_upload = fixture_file_upload(path, content_type)
+ assert_equal File.open(path, READ_PLAIN).read, plain_file_upload.read
+ end
+
+ def test_fixture_file_upload
+ post :test_file_upload, :file => fixture_file_upload(FILES_DIR + "/mona_lisa.jpg", "image/jpg")
+ assert_equal '159528', @response.body
+ end
+
+ def test_fixture_file_upload_relative_to_fixture_path
+ TestCaseTest.stubs(:fixture_path).returns(FILES_DIR)
+ uploaded_file = fixture_file_upload("mona_lisa.jpg", "image/jpg")
+ assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ end
+
+ def test_fixture_file_upload_ignores_nil_fixture_path
+ TestCaseTest.stubs(:fixture_path).returns(nil)
+ uploaded_file = fixture_file_upload("#{FILES_DIR}/mona_lisa.jpg", "image/jpg")
+ assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
+ end
+
+ def test_action_dispatch_uploaded_file_upload
+ filename = 'mona_lisa.jpg'
+ path = "#{FILES_DIR}/#{filename}"
+ post :test_file_upload, :file => ActionDispatch::Http::UploadedFile.new(:filename => path, :type => "image/jpg", :tempfile => File.open(path))
+ assert_equal '159528', @response.body
+ end
+
+ def test_test_uploaded_file_exception_when_file_doesnt_exist
+ assert_raise(RuntimeError) { Rack::Test::UploadedFile.new('non_existent_file') }
+ end
+
+ def test_redirect_url_only_cares_about_location_header
+ get :create
+ assert_response :created
+
+ # Redirect url doesn't care that it wasn't a :redirect response.
+ assert_equal 'created resource', @response.redirect_url
+ assert_equal @response.redirect_url, redirect_to_url
+
+ # Must be a :redirect response.
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to 'created resource'
+ end
+ end
+end
+
+class InferringClassNameTest < ActionController::TestCase
+ def test_determine_controller_class
+ assert_equal ContentController, determine_class("ContentControllerTest")
+ end
+
+ def test_determine_controller_class_with_nonsense_name
+ assert_nil determine_class("HelloGoodBye")
+ end
+
+ def test_determine_controller_class_with_sensible_name_where_no_controller_exists
+ assert_nil determine_class("NoControllerWithThisNameTest")
+ end
+
+ private
+ def determine_class(name)
+ ActionController::TestCase.determine_default_controller_class(name)
+ end
+end
+
+class CrazyNameTest < ActionController::TestCase
+ tests ContentController
+
+ def test_controller_class_can_be_set_manually_not_just_inferred
+ assert_equal ContentController, self.class.controller_class
+ end
+end
+
+class CrazySymbolNameTest < ActionController::TestCase
+ tests :content
+
+ def test_set_controller_class_using_symbol
+ assert_equal ContentController, self.class.controller_class
+ end
+end
+
+class CrazyStringNameTest < ActionController::TestCase
+ tests 'content'
+
+ def test_set_controller_class_using_string
+ assert_equal ContentController, self.class.controller_class
+ end
+end
+
+class NamedRoutesControllerTest < ActionController::TestCase
+ tests ContentController
+
+ def test_should_be_able_to_use_named_routes_before_a_request_is_done
+ with_routing do |set|
+ set.draw { resources :contents }
+ assert_equal 'http://test.host/contents/new', new_content_url
+ assert_equal 'http://test.host/contents/1', content_url(:id => 1)
+ end
+ end
+end
+
+class AnonymousControllerTest < ActionController::TestCase
+ def setup
+ @controller = Class.new(ActionController::Base) do
+ def index
+ render :text => params[:controller]
+ end
+ end.new
+
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get ':controller(/:action(/:id))'
+ end
+ end
+ end
+
+ def test_controller_name
+ get :index
+ assert_equal 'anonymous', @response.body
+ end
+end
+
+class RoutingDefaultsTest < ActionController::TestCase
+ def setup
+ @controller = Class.new(ActionController::Base) do
+ def post
+ render :text => request.fullpath
+ end
+
+ def project
+ render :text => request.fullpath
+ end
+ end.new
+
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get '/posts/:id', :to => 'anonymous#post', :bucket_type => 'post'
+ get '/projects/:id', :to => 'anonymous#project', :defaults => { :bucket_type => 'project' }
+ end
+ end
+ end
+
+ def test_route_option_can_be_passed_via_process
+ get :post, :id => 1, :bucket_type => 'post'
+ assert_equal '/posts/1', @response.body
+ end
+
+ def test_route_default_is_not_required_for_building_request_uri
+ get :project, :id => 2
+ assert_equal '/projects/2', @response.body
+ end
+end
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
new file mode 100644
index 0000000000..24a09222b1
--- /dev/null
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -0,0 +1,188 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'controller/fake_controllers'
+require 'active_support/core_ext/object/with_options'
+
+module ActionPack
+ class URLForIntegrationTest < ActiveSupport::TestCase
+ include RoutingTestHelpers
+ include ActionDispatch::RoutingVerbs
+
+ 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
+ #match '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"
+
+ 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'
+ get ':controller(/:action(/:id))(.:format)'
+ root :to => "news#index"
+ }
+
+ attr_reader :routes
+ attr_accessor :controller
+
+ def setup
+ @routes = make_set false
+ @routes.draw(&Mapping)
+ end
+
+ [
+ ['/admin/users',[ { :use_route => 'admin_users' }]],
+ ['/admin/users',[ { :controller => 'admin/users' }]],
+ ['/admin/users',[ { :controller => 'admin/users', :action => 'index' }]],
+ ['/admin/users',[ { :action => 'index' }, { :controller => 'admin/users', :action => 'index' }, '/admin/users']],
+ ['/admin/users',[ { :controller => 'users', :action => 'index' }, { :controller => 'admin/accounts', :action => 'show', :id => '1' }, '/admin/accounts/show/1']],
+ ['/people',[ { :controller => '/people', :action => 'index' }, {:controller=>"admin/accounts", :action=>"foo", :id=>"bar"}, '/admin/accounts/foo/bar']],
+
+ ['/admin/posts',[ { :controller => 'admin/posts' }]],
+ ['/admin/posts/new',[ { :controller => 'admin/posts', :action => 'new' }]],
+
+ ['/blog/2009',[ { :controller => 'posts', :action => 'show_date', :year => 2009 }]],
+ ['/blog/2009/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1 }]],
+ ['/blog/2009/1/1',[ { :controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1 }]],
+
+ ['/archive/2010',[ { :controller => 'archive', :action => 'index', :year => '2010' }]],
+ ['/archive',[ { :controller => 'archive', :action => 'index' }]],
+ ['/archive?year=january',[ { :controller => 'archive', :action => 'index', :year => 'january' }]],
+
+ ['/people',[ { :controller => 'people', :action => 'index' }]],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'index' }, '/people']],
+ ['/people',[ { :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people',[ {}, { :controller => 'people', :action => 'index' }, '/people']],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people/new',[ { :use_route => 'new_person' }]],
+ ['/people/new',[ { :controller => 'people', :action => 'new' }]],
+ ['/people/1',[ { :use_route => 'person', :id => '1' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => '1' }]],
+ ['/people/1.xml',[ { :controller => 'people', :action => 'show', :id => '1', :format => 'xml' }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => 1 }]],
+ ['/people/1',[ { :controller => 'people', :action => 'show', :id => Model.new('1') }]],
+ ['/people/1',[ { :action => 'show', :id => '1' }, { :controller => 'people', :action => 'index' }, '/people']],
+ ['/people/1',[ { :action => 'show', :id => 1 }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people',[ { :controller => 'people', :action => 'index' }, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people/1',[ {}, { :controller => 'people', :action => 'show', :id => '1' }, '/people/show/1']],
+ ['/people/1',[ { :controller => 'people', :action => 'show' }, { :controller => 'people', :action => 'index', :id => '1' }, '/people/index/1']],
+ ['/people/1/edit',[ { :controller => 'people', :action => 'edit', :id => '1' }]],
+ ['/people/1/edit.xml',[ { :controller => 'people', :action => 'edit', :id => '1', :format => 'xml' }]],
+ ['/people/1/edit',[ { :use_route => 'edit_person', :id => '1' }]],
+ ['/people/1?legacy=true',[ { :controller => 'people', :action => 'show', :id => '1', :legacy => 'true' }]],
+ ['/people?legacy=true',[ { :controller => 'people', :action => 'index', :legacy => 'true' }]],
+
+ ['/id_default/2',[ { :controller => 'foo', :action => 'id_default', :id => '2' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => '1' }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default', :id => 1 }]],
+ ['/id_default',[ { :controller => 'foo', :action => 'id_default' }]],
+ ['/optional/bar',[ { :controller => 'posts', :action => 'index', :optional => 'bar' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+
+ ['/project',[ { :controller => 'project', :action => 'index' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :controller => 'project', :action => 'index'}, {:project_id => '1', :controller => 'project', :action => 'index' }, '/projects/1']],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1' }]],
+ ['/projects/1',[ { :use_route => 'project', :controller => 'project', :action => 'index' }, { :controller => 'project', :action => 'index', :project_id => '1' }, '/projects/1']],
+
+ ['/clients',[ { :controller => 'projects', :action => 'index' }]],
+ ['/clients?project_id=1',[ { :controller => 'projects', :action => 'index', :project_id => '1' }]],
+ ['/clients',[ { :controller => 'projects', :action => 'index' }, { :project_id => '1', :controller => 'project', :action => 'index' }, '/projects/1']],
+
+ ['/comment/20',[ { :id => 20 }, { :controller => 'comments', :action => 'show' }, '/comments/show']],
+ ['/comment/20',[ { :controller => 'comments', :id => 20, :action => 'show' }]],
+ ['/comments/boo',[ { :controller => 'comments', :action => 'boo' }]],
+
+ ['/ws/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1', :ws => true }]],
+ ['/ws/posts',[ { :controller => 'posts', :action => 'index', :ws => true }]],
+
+ ['/account',[ { :controller => 'account', :action => 'subscription' }]],
+ ['/account/billing',[ { :controller => 'account', :action => 'billing' }]],
+
+ ['/pages/1/notes/show/1',[ { :page_id => '1', :controller => 'notes', :action => 'show', :id => '1' }]],
+ ['/pages/1/notes/list',[ { :page_id => '1', :controller => 'notes', :action => 'list' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes', :action => 'index' }]],
+ ['/pages/1/notes',[ { :page_id => '1', :controller => 'notes' }]],
+ ['/notes',[ { :page_id => nil, :controller => 'notes' }]],
+ ['/notes',[ { :controller => 'notes' }]],
+ ['/notes/print',[ { :controller => 'notes', :action => 'print' }]],
+ ['/notes/print',[ {}, { :controller => 'notes', :action => 'print' }, '/notes/print']],
+
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :action => 'index', :id => '1' }, '/notes/index/1']],
+ ['/notes/index/1',[ { :controller => 'notes' }, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']],
+ ['/notes/index/1',[ { :action => 'index' }, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']],
+ ['/notes/index/1',[ {}, { :controller => 'notes', :id => '1', :action => 'index' }, '/notes/index/1']],
+ ['/notes/show/1',[ {}, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']],
+ ['/posts',[ { :controller => 'posts' }, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']],
+ ['/notes/list',[ { :action => 'list' }, { :controller => 'notes', :action => 'show', :id => '1' }, '/notes/show/1']],
+
+ ['/posts/ping',[ { :controller => 'posts', :action => 'ping' }]],
+ ['/posts/show/1',[ { :controller => 'posts', :action => 'show', :id => '1' }]],
+ ['/posts',[ { :controller => 'posts' }]],
+ ['/posts',[ { :controller => 'posts', :action => 'index' }]],
+ ['/posts/create',[ { :action => 'create' }, {:day=>nil, :month=>nil, :controller=>"posts", :action=>"show_date"}, '/blog']],
+ ['/posts?foo=bar',[ { :controller => 'posts', :foo => 'bar' }]],
+ ['/posts?foo%5B%5D=bar&foo%5B%5D=baz', [{ :controller => 'posts', :foo => ['bar', 'baz'] }]],
+ ['/posts?page=2', [{ :controller => 'posts', :page => 2 }]],
+ ['/posts?q%5Bfoo%5D%5Ba%5D=b', [{ :controller => 'posts', :q => { :foo => { :a => 'b'}} }]],
+
+ ['/news.rss', [{ :controller => 'news', :action => 'index', :format => 'rss' }]],
+ ].each_with_index do |(url, params), i|
+ if params.length > 1
+ hash, path_params, route = *params
+ hash[:only_path] = true
+
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ get URI('http://test.host' + route.to_s)
+ assert_equal path_params, controller.request.path_parameters
+ assert_equal url, controller.url_for(hash), params.inspect
+ end
+ else
+ define_method("test_#{url.gsub(/\W/, '_')}_#{i}") do
+ assert_equal url, url_for(@routes, params.first), params.inspect
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
new file mode 100644
index 0000000000..9f086af664
--- /dev/null
+++ b/actionpack/test/controller/url_for_test.rb
@@ -0,0 +1,426 @@
+require 'abstract_unit'
+
+module AbstractController
+ module Testing
+ class UrlForTest < ActionController::TestCase
+ class W
+ include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers
+ end
+
+ def teardown
+ W.default_url_options.clear
+ end
+
+ def test_nested_optional
+ klass = Class.new {
+ include ActionDispatch::Routing::RouteSet.new.tap { |r|
+ r.draw {
+ get "/foo/(:bar/(:baz))/:zot", :as => 'fun',
+ :controller => :articles,
+ :action => :index
+ }
+ }.url_helpers
+ self.default_url_options[:host] = 'example.com'
+ }
+
+ path = klass.new.fun_path({:controller => :articles,
+ :baz => "baz",
+ :zot => "zot",
+ :only_path => true })
+ # :bar key isn't provided
+ assert_equal '/foo/zot', path
+ end
+
+ def add_host!
+ W.default_url_options[:host] = 'www.basecamphq.com'
+ end
+
+ def add_port!
+ W.default_url_options[:port] = 3000
+ end
+
+ def add_numeric_host!
+ W.default_url_options[:host] = '127.0.0.1'
+ end
+
+ def test_exception_is_thrown_without_host
+ assert_raise ArgumentError do
+ W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
+ end
+ end
+
+ def test_anchor
+ assert_equal('/c/a#anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => 'anchor')
+ )
+ end
+
+ def test_anchor_should_call_to_param
+ assert_equal('/c/a#anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anchor'))
+ )
+ end
+
+ def test_anchor_should_escape_unsafe_pchar
+ assert_equal('/c/a#%23anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('#anchor'))
+ )
+ end
+
+ def test_anchor_should_not_escape_safe_pchar
+ assert_equal('/c/a#name=user&email=user@domain.com',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('name=user&email=user@domain.com'))
+ )
+ end
+
+ def test_default_host
+ add_host!
+ assert_equal('http://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_host_may_be_overridden
+ add_host!
+ assert_equal('http://37signals.basecamphq.com/c/a/i',
+ W.new.url_for(:host => '37signals.basecamphq.com', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_subdomain_may_be_changed
+ add_host!
+ assert_equal('http://api.basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_subdomain_may_be_object
+ model = Class.new { def self.to_param; 'api'; end }
+ add_host!
+ assert_equal('http://api.basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => model, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_subdomain_may_be_removed
+ add_host!
+ assert_equal('http://basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => false, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_subdomain_may_be_removed_with_blank_string
+ W.default_url_options[:host] = 'api.basecamphq.com'
+ assert_equal('http://basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => '', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_multiple_subdomains_may_be_removed
+ W.default_url_options[:host] = 'mobile.www.api.basecamphq.com'
+ assert_equal('http://basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => false, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_subdomain_may_be_accepted_with_numeric_host
+ add_numeric_host!
+ assert_equal('http://127.0.0.1/c/a/i',
+ W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_domain_may_be_changed
+ add_host!
+ assert_equal('http://www.37signals.com/c/a/i',
+ W.new.url_for(:domain => '37signals.com', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_tld_length_may_be_changed
+ add_host!
+ assert_equal('http://mobile.www.basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => 'mobile', :tld_length => 2, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_port
+ add_host!
+ assert_equal('http://www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :port => 3000)
+ )
+ end
+
+ def test_default_port
+ add_host!
+ add_port!
+ assert_equal('http://www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_protocol
+ add_host!
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ end
+
+ def test_protocol_with_and_without_separators
+ add_host!
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https')
+ )
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https:')
+ )
+ assert_equal('https://www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https://')
+ )
+ end
+
+ def test_without_protocol
+ add_host!
+ assert_equal('//www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => '//')
+ )
+ assert_equal('//www.basecamphq.com/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => false)
+ )
+ end
+
+ def test_without_protocol_and_with_port
+ add_host!
+ add_port!
+
+ assert_equal('//www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => '//')
+ )
+ assert_equal('//www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => false)
+ )
+ end
+
+ def test_trailing_slash
+ add_host!
+ options = {:controller => 'foo', :trailing_slash => true, :action => 'bar', :id => '33'}
+ assert_equal('http://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
+ end
+
+ def test_trailing_slash_with_protocol
+ add_host!
+ options = { :trailing_slash => true,:protocol => 'https', :controller => 'foo', :action => 'bar', :id => '33'}
+ assert_equal('https://www.basecamphq.com/foo/bar/33/', W.new.url_for(options) )
+ assert_equal 'https://www.basecamphq.com/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string'}))
+ end
+
+ def test_trailing_slash_with_only_path
+ options = {:controller => 'foo', :trailing_slash => true}
+ assert_equal '/foo/', W.new.url_for(options.merge({:only_path => true}))
+ options.update({:action => 'bar', :id => '33'})
+ assert_equal '/foo/bar/33/', W.new.url_for(options.merge({:only_path => true}))
+ assert_equal '/foo/bar/33/?query=string', W.new.url_for(options.merge({:query => 'string',:only_path => true}))
+ end
+
+ def test_trailing_slash_with_anchor
+ options = {:trailing_slash => true, :controller => 'foo', :action => 'bar', :id => '33', :only_path => true, :anchor=> 'chapter7'}
+ assert_equal '/foo/bar/33/#chapter7', W.new.url_for(options)
+ assert_equal '/foo/bar/33/?query=string#chapter7', W.new.url_for(options.merge({:query => 'string'}))
+ end
+
+ def test_trailing_slash_with_params
+ url = W.new.url_for(:trailing_slash => true, :only_path => true, :controller => 'cont', :action => 'act', :p1 => 'cafe', :p2 => 'link')
+ params = extract_params(url)
+ assert_equal params[0], { :p1 => 'cafe' }.to_query
+ assert_equal params[1], { :p2 => 'link' }.to_query
+ end
+
+ def test_relative_url_root_is_respected
+ add_host!
+ assert_equal('https://www.basecamphq.com/subdir/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i', :protocol => 'https', :script_name => '/subdir')
+ )
+ end
+
+ def test_named_routes
+ with_routing do |set|
+ set.draw do
+ get 'this/is/verbose', :to => 'home#index', :as => :no_args
+ get 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include set.url_helpers }
+
+ controller = kls.new
+ assert controller.respond_to?(:home_url)
+ assert_equal 'http://www.basecamphq.com/home/sweet/home/again',
+ controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again')
+
+ assert_equal("/home/sweet/home/alabama", controller.send(:home_path, :user => 'alabama', :host => 'unused'))
+ assert_equal("http://www.basecamphq.com/home/sweet/home/alabama", controller.send(:home_url, :user => 'alabama', :host => 'www.basecamphq.com'))
+ assert_equal("http://www.basecamphq.com/this/is/verbose", controller.send(:no_args_url, :host=>'www.basecamphq.com'))
+ end
+ end
+
+ def test_relative_url_root_is_respected_for_named_routes
+ with_routing do |set|
+ set.draw do
+ get '/home/sweet/home/:user', :to => 'home#index', :as => :home
+ end
+
+ kls = Class.new { include set.url_helpers }
+ controller = kls.new
+
+ assert_equal 'http://www.basecamphq.com/subdir/home/sweet/home/again',
+ controller.send(:home_url, :host => 'www.basecamphq.com', :user => 'again', :script_name => "/subdir")
+ end
+ end
+
+ def test_only_path
+ with_routing do |set|
+ set.draw do
+ get 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ get ':controller/:action/:id'
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include set.url_helpers }
+ controller = kls.new
+ assert_respond_to controller, :home_url
+ assert_equal '/brave/new/world',
+ controller.url_for(:controller => 'brave', :action => 'new', :id => 'world', :only_path => true)
+
+ assert_equal("/home/sweet/home/alabama", controller.home_path(:user => 'alabama', :host => 'unused', :only_path => true))
+ assert_equal("/home/sweet/home/alabama", controller.home_path('alabama'))
+ end
+ end
+
+ def test_one_parameter
+ assert_equal('/c/a?param=val',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :param => 'val')
+ )
+ end
+
+ def test_two_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :p1 => 'X1', :p2 => 'Y2')
+ params = extract_params(url)
+ assert_equal params[0], { :p1 => 'X1' }.to_query
+ assert_equal params[1], { :p2 => 'Y2' }.to_query
+ end
+
+ def test_hash_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:name => 'Bob', :category => 'prof'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[category]' => 'prof' }.to_query
+ assert_equal params[1], { 'query[name]' => 'Bob' }.to_query
+ end
+
+ def test_array_parameter
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => ['Bob', 'prof'])
+ params = extract_params(url)
+ assert_equal params[0], { 'query[]' => 'Bob' }.to_query
+ assert_equal params[1], { 'query[]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :query => {:person => {:name => 'Bob', :position => 'prof'}, :hobby => 'piercing'})
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position]' => 'prof' }.to_query
+ end
+
+ def test_hash_recursive_and_array_parameters
+ url = W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => 101, :query => {:person => {:name => 'Bob', :position => ['prof', 'art director']}, :hobby => 'piercing'})
+ assert_match(%r(^/c/a/101), url)
+ params = extract_params(url)
+ assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query
+ assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query
+ assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query
+ assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query
+ end
+
+ def test_path_generation_for_symbol_parameter_keys
+ assert_generates("/image", :controller=> :image)
+ end
+
+ def test_named_routes_with_nil_keys
+ with_routing do |set|
+ set.draw do
+ get 'posts.:format', :to => 'posts#index', :as => :posts
+ get '/', :to => 'posts#index', :as => :main
+ end
+
+ # We need to create a new class in order to install the new named route.
+ kls = Class.new { include set.url_helpers }
+ kls.default_url_options[:host] = 'www.basecamphq.com'
+
+ controller = kls.new
+ params = {:action => :index, :controller => :posts, :format => :xml}
+ assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params))
+ params[:format] = nil
+ assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params))
+ end
+ end
+
+ def test_multiple_includes_maintain_distinct_options
+ first_class = Class.new { include ActionController::UrlFor }
+ second_class = Class.new { include ActionController::UrlFor }
+
+ first_host, second_host = 'firsthost.com', 'secondhost.com'
+
+ first_class.default_url_options[:host] = first_host
+ second_class.default_url_options[:host] = second_host
+
+ assert_equal first_host, first_class.default_url_options[:host]
+ assert_equal second_host, second_class.default_url_options[:host]
+ end
+
+ def test_with_stringified_keys
+ assert_equal("/c", W.new.url_for('controller' => 'c', 'only_path' => true))
+ assert_equal("/c/a", W.new.url_for('controller' => 'c', 'action' => 'a', 'only_path' => true))
+ end
+
+ def test_with_hash_with_indifferent_access
+ W.default_url_options[:controller] = 'd'
+ W.default_url_options[:only_path] = false
+ assert_equal("/c", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'only_path' => true)))
+
+ W.default_url_options[:action] = 'b'
+ assert_equal("/c/a", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true)))
+ end
+
+ def test_url_params_with_nil_to_param_are_not_in_url
+ assert_equal("/c/a", W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :id => Struct.new(:to_param).new(nil)))
+ end
+
+ def test_false_url_params_are_included_in_query
+ assert_equal("/c/a?show=false", W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :show => false))
+ end
+
+ def test_url_generation_with_array_and_hash
+ with_routing do |set|
+ set.draw do
+ namespace :admin do
+ resources :posts
+ end
+ end
+
+ kls = Class.new { include set.url_helpers }
+ kls.default_url_options[:host] = 'www.basecamphq.com'
+
+ controller = kls.new
+ assert_equal("http://www.basecamphq.com/admin/posts/new?param=value",
+ controller.send(:url_for, [:new, :admin, :post, { param: 'value' }])
+ )
+ end
+ end
+
+ private
+ def extract_params(url)
+ url.split('?', 2).last.split('&').sort
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
new file mode 100644
index 0000000000..d9a1ae7d4f
--- /dev/null
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -0,0 +1,90 @@
+require 'abstract_unit'
+require 'controller/fake_controllers'
+
+class UrlRewriterTests < ActiveSupport::TestCase
+ class Rewriter
+ def initialize(request)
+ @options = {
+ :host => request.host_with_port,
+ :protocol => request.protocol
+ }
+ end
+
+ def rewrite(routes, options)
+ routes.url_for(@options.merge(options))
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @params = {}
+ @rewriter = Rewriter.new(@request) #.new(@request, @params)
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get ':controller(/:action(/:id))'
+ end
+ end
+ end
+
+ def test_port
+ assert_equal('http://test.host:1271/c/a/i',
+ @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :port => 1271)
+ )
+ end
+
+ def test_protocol_with_and_without_separator
+ assert_equal('https://test.host/c/a/i',
+ @rewriter.rewrite(@routes, :protocol => 'https', :controller => 'c', :action => 'a', :id => 'i')
+ )
+
+ assert_equal('https://test.host/c/a/i',
+ @rewriter.rewrite(@routes, :protocol => 'https://', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_user_name_and_password
+ assert_equal(
+ 'http://david:secret@test.host/c/a/i',
+ @rewriter.rewrite(@routes, :user => "david", :password => "secret", :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_user_name_and_password_with_escape_codes
+ assert_equal(
+ 'http://openid.aol.com%2Fnextangler:one+two%3F@test.host/c/a/i',
+ @rewriter.rewrite(@routes, :user => "openid.aol.com/nextangler", :password => "one two?", :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
+ def test_anchor
+ assert_equal(
+ 'http://test.host/c/a/i#anchor',
+ @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => 'anchor')
+ )
+ end
+
+ def test_anchor_should_call_to_param
+ assert_equal(
+ 'http://test.host/c/a/i#anchor',
+ @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anchor'))
+ )
+ end
+
+ def test_anchor_should_be_uri_escaped
+ assert_equal(
+ 'http://test.host/c/a/i#anc/hor',
+ @rewriter.rewrite(@routes, :controller => 'c', :action => 'a', :id => 'i', :anchor => Struct.new(:to_param).new('anc/hor'))
+ )
+ end
+
+ def test_trailing_slash
+ options = {:controller => 'foo', :action => 'bar', :id => '3', :only_path => true}
+ assert_equal '/foo/bar/3', @rewriter.rewrite(@routes, options)
+ assert_equal '/foo/bar/3?query=string', @rewriter.rewrite(@routes, options.merge({:query => 'string'}))
+ options.update({:trailing_slash => true})
+ assert_equal '/foo/bar/3/', @rewriter.rewrite(@routes, options)
+ options.update({:query => 'string'})
+ assert_equal '/foo/bar/3/?query=string', @rewriter.rewrite(@routes, options)
+ end
+end
+
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
new file mode 100644
index 0000000000..d80b0e2da0
--- /dev/null
+++ b/actionpack/test/controller/webservice_test.rb
@@ -0,0 +1,104 @@
+require 'abstract_unit'
+require 'active_support/json/decoding'
+
+class WebServiceTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def assign_parameters
+ if params[:full]
+ render :text => dump_params_keys
+ else
+ render :text => (params.keys - ['controller', 'action']).sort.join(", ")
+ end
+ end
+
+ def dump_params_keys(hash = params)
+ hash.keys.sort.inject("") do |s, k|
+ value = hash[k]
+ value = Hash === value ? "(#{dump_params_keys(value)})" : ""
+ s << ", " unless s.empty?
+ s << "#{k}#{value}"
+ end
+ end
+ end
+
+ def setup
+ @controller = TestController.new
+ @integration_session = nil
+ end
+
+ def test_check_parameters
+ with_test_route_set do
+ get "/"
+ assert_equal '', @controller.response.body
+ end
+ end
+
+ def test_post_json
+ with_test_route_set do
+ post "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
+
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'content...', @controller.params["entry"]['summary']
+ end
+ end
+
+ def test_put_json
+ with_test_route_set do
+ put "/", '{"entry":{"summary":"content..."}}', 'CONTENT_TYPE' => 'application/json'
+
+ assert_equal 'entry', @controller.response.body
+ assert @controller.params.has_key?(:entry)
+ assert_equal 'content...', @controller.params["entry"]['summary']
+ end
+ end
+
+ def test_register_and_use_json_simple
+ with_test_route_set do
+ with_params_parsers Mime::JSON => Proc.new { |data| ActiveSupport::JSON.decode(data)['request'].with_indifferent_access } do
+ post "/", '{"request":{"summary":"content...","title":"JSON"}}',
+ 'CONTENT_TYPE' => 'application/json'
+
+ assert_equal 'summary, title', @controller.response.body
+ assert @controller.params.has_key?(:summary)
+ assert @controller.params.has_key?(:title)
+ assert_equal 'content...', @controller.params["summary"]
+ assert_equal 'JSON', @controller.params["title"]
+ end
+ end
+ end
+
+ def test_use_json_with_empty_request
+ with_test_route_set do
+ assert_nothing_raised { post "/", "", 'CONTENT_TYPE' => 'application/json' }
+ assert_equal '', @controller.response.body
+ end
+ end
+
+ def test_dasherized_keys_as_json
+ with_test_route_set do
+ post "/?full=1", '{"first-key":{"sub-key":"..."}}', 'CONTENT_TYPE' => 'application/json'
+ assert_equal 'action, controller, first-key(sub-key), full', @controller.response.body
+ assert_equal "...", @controller.params['first-key']['sub-key']
+ end
+ end
+
+ private
+ def with_params_parsers(parsers = {})
+ old_session = @integration_session
+ @app = ActionDispatch::ParamsParser.new(app.routes, parsers)
+ reset!
+ yield
+ ensure
+ @integration_session = old_session
+ end
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match '/', :to => 'web_service_test/test#assign_parameters', :via => :all
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
new file mode 100644
index 0000000000..f767b07e75
--- /dev/null
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -0,0 +1,58 @@
+require 'abstract_unit'
+
+class DispatcherTest < ActiveSupport::TestCase
+ class Foo
+ cattr_accessor :a, :b
+ end
+
+ class DummyApp
+ def call(env)
+ [200, {}, 'response']
+ end
+ end
+
+ def setup
+ Foo.a, Foo.b = 0, 0
+ ActionDispatch::Callbacks.reset_callbacks(:call)
+ end
+
+ def test_before_and_after_callbacks
+ ActionDispatch::Callbacks.before { |*args| Foo.a += 1; Foo.b += 1 }
+ ActionDispatch::Callbacks.after { |*args| Foo.a += 1; Foo.b += 1 }
+
+ dispatch
+ assert_equal 2, Foo.a
+ assert_equal 2, Foo.b
+
+ dispatch
+ assert_equal 4, Foo.a
+ assert_equal 4, Foo.b
+
+ dispatch do |env|
+ raise "error"
+ end rescue nil
+ assert_equal 6, Foo.a
+ assert_equal 6, Foo.b
+ end
+
+ def test_to_prepare_and_cleanup_delegation
+ prepared = cleaned = false
+ ActionDispatch::Callbacks.to_prepare { prepared = true }
+ ActionDispatch::Callbacks.to_prepare { cleaned = true }
+
+ ActionDispatch::Reloader.prepare!
+ assert prepared
+
+ ActionDispatch::Reloader.cleanup!
+ assert cleaned
+ end
+
+ private
+
+ def dispatch(&block)
+ ActionDispatch::Callbacks.new(block || DummyApp.new).call(
+ {'rack.input' => StringIO.new('')}
+ )
+ end
+
+end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
new file mode 100644
index 0000000000..0f145666d1
--- /dev/null
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -0,0 +1,1070 @@
+require 'abstract_unit'
+
+begin
+ require 'openssl'
+ OpenSSL::PKCS5
+rescue LoadError, NameError
+ $stderr.puts "Skipping KeyGenerator test: broken OpenSSL install"
+else
+
+require 'active_support/key_generator'
+require 'active_support/message_verifier'
+
+class CookiesTest < ActionController::TestCase
+ class CustomSerializer
+ def self.load(value)
+ value.to_s + " and loaded"
+ end
+
+ def self.dump(value)
+ value.to_s + " was dumped"
+ end
+ end
+
+ class TestController < ActionController::Base
+ def authenticate
+ cookies["user_name"] = "david"
+ head :ok
+ end
+
+ def set_with_with_escapable_characters
+ cookies["that & guy"] = "foo & bar => baz"
+ head :ok
+ end
+
+ def authenticate_for_fourteen_days
+ cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
+ head :ok
+ end
+
+ def authenticate_for_fourteen_days_with_symbols
+ cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) }
+ head :ok
+ end
+
+ def set_multiple_cookies
+ cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
+ cookies["login"] = "XJ-122"
+ head :ok
+ end
+
+ def access_frozen_cookies
+ cookies["will"] = "work"
+ head :ok
+ end
+
+ def logout
+ cookies.delete("user_name")
+ head :ok
+ end
+
+ alias delete_cookie logout
+
+ def delete_cookie_with_path
+ cookies.delete("user_name", :path => '/beaten')
+ head :ok
+ end
+
+ def authenticate_with_http_only
+ cookies["user_name"] = { :value => "david", :httponly => true }
+ head :ok
+ end
+
+ def authenticate_with_secure
+ cookies["user_name"] = { :value => "david", :secure => true }
+ head :ok
+ end
+
+ def set_permanent_cookie
+ cookies.permanent[:user_name] = "Jamie"
+ head :ok
+ end
+
+ def set_signed_cookie
+ cookies.signed[:user_id] = 45
+ head :ok
+ end
+
+ def get_signed_cookie
+ cookies.signed[:user_id]
+ head :ok
+ end
+
+ def set_encrypted_cookie
+ cookies.encrypted[:foo] = 'bar'
+ head :ok
+ end
+
+ def get_encrypted_cookie
+ cookies.encrypted[:foo]
+ head :ok
+ end
+
+ def set_invalid_encrypted_cookie
+ cookies[:invalid_cookie] = 'invalid--9170e00a57cfc27083363b5c75b835e477bd90cf'
+ head :ok
+ end
+
+ def raise_data_overflow
+ cookies.signed[:foo] = 'bye!' * 1024
+ head :ok
+ end
+
+ def tampered_cookies
+ cookies[:tampered] = "BAh7BjoIZm9vIghiYXI%3D--123456780"
+ cookies.signed[:tampered]
+ head :ok
+ end
+
+ def set_permanent_signed_cookie
+ cookies.permanent.signed[:remember_me] = 100
+ head :ok
+ end
+
+ def delete_and_set_cookie
+ cookies.delete :user_name
+ cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) }
+ head :ok
+ end
+
+ def set_cookie_with_domain
+ cookies[:user_name] = {:value => "rizwanreza", :domain => :all}
+ head :ok
+ end
+
+ def delete_cookie_with_domain
+ cookies.delete(:user_name, :domain => :all)
+ head :ok
+ end
+
+ def set_cookie_with_domain_and_tld
+ cookies[:user_name] = {:value => "rizwanreza", :domain => :all, :tld_length => 2}
+ head :ok
+ end
+
+ def delete_cookie_with_domain_and_tld
+ cookies.delete(:user_name, :domain => :all, :tld_length => 2)
+ head :ok
+ end
+
+ def set_cookie_with_domains
+ cookies[:user_name] = {:value => "rizwanreza", :domain => %w(example1.com example2.com .example3.com)}
+ head :ok
+ end
+
+ def delete_cookie_with_domains
+ cookies.delete(:user_name, :domain => %w(example1.com example2.com .example3.com))
+ head :ok
+ end
+
+ def symbol_key
+ cookies[:user_name] = "david"
+ head :ok
+ end
+
+ def string_key
+ cookies['user_name'] = "dhh"
+ head :ok
+ end
+
+ def symbol_key_mock
+ cookies[:user_name] = "david" if cookies[:user_name] == "andrew"
+ head :ok
+ end
+
+ def string_key_mock
+ cookies['user_name'] = "david" if cookies['user_name'] == "andrew"
+ head :ok
+ end
+
+ def noop
+ head :ok
+ end
+ end
+
+ tests TestController
+
+ def setup
+ super
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33")
+ @request.env["action_dispatch.signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_fetch
+ x = Object.new
+ assert_not request.cookie_jar.key?('zzzzzz')
+ assert_equal x, request.cookie_jar.fetch('zzzzzz', x)
+ assert_not request.cookie_jar.key?('zzzzzz')
+ end
+
+ def test_fetch_exists
+ x = Object.new
+ request.cookie_jar['foo'] = 'bar'
+ assert_equal 'bar', request.cookie_jar.fetch('foo', x)
+ end
+
+ def test_fetch_block
+ x = Object.new
+ assert_not request.cookie_jar.key?('zzzzzz')
+ assert_equal x, request.cookie_jar.fetch('zzzzzz') { x }
+ end
+
+ def test_key_is_to_s
+ request.cookie_jar['foo'] = 'bar'
+ assert_equal 'bar', request.cookie_jar.fetch(:foo)
+ end
+
+ def test_fetch_type_error
+ assert_raises(KeyError) do
+ request.cookie_jar.fetch(:omglolwut)
+ end
+ end
+
+ def test_each
+ request.cookie_jar['foo'] = :bar
+ list = []
+ request.cookie_jar.each do |k,v|
+ list << [k, v]
+ end
+
+ assert_equal [['foo', :bar]], list
+ end
+
+ def test_enumerable
+ request.cookie_jar['foo'] = :bar
+ actual = request.cookie_jar.map { |k,v| [k.to_s, v.to_s] }
+ assert_equal [['foo', 'bar']], actual
+ end
+
+ def test_key_methods
+ assert !request.cookie_jar.key?(:foo)
+ assert !request.cookie_jar.has_key?("foo")
+
+ request.cookie_jar[:foo] = :bar
+ assert request.cookie_jar.key?(:foo)
+ assert request.cookie_jar.has_key?("foo")
+ end
+
+ def test_setting_cookie
+ get :authenticate
+ assert_cookie_header "user_name=david; path=/"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_setting_the_same_value_to_cookie
+ request.cookies[:user_name] = 'david'
+ get :authenticate
+ assert response.cookies.empty?
+ end
+
+ def test_setting_the_same_value_to_permanent_cookie
+ request.cookies[:user_name] = 'Jamie'
+ get :set_permanent_cookie
+ assert_equal response.cookies, 'user_name' => 'Jamie'
+ end
+
+ def test_setting_with_escapable_characters
+ get :set_with_with_escapable_characters
+ assert_cookie_header "that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/"
+ assert_equal({"that & guy" => "foo & bar => baz"}, @response.cookies)
+ end
+
+ def test_setting_cookie_for_fourteen_days
+ get :authenticate_for_fourteen_days
+ assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_setting_cookie_for_fourteen_days_with_symbols
+ get :authenticate_for_fourteen_days_with_symbols
+ assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_setting_cookie_with_http_only
+ get :authenticate_with_http_only
+ assert_cookie_header "user_name=david; path=/; HttpOnly"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_setting_cookie_with_secure
+ @request.env["HTTPS"] = "on"
+ get :authenticate_with_secure
+ assert_cookie_header "user_name=david; path=/; secure"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_setting_cookie_with_secure_when_always_write_cookie_is_true
+ ActionDispatch::Cookies::CookieJar.any_instance.stubs(:always_write_cookie).returns(true)
+ get :authenticate_with_secure
+ assert_cookie_header "user_name=david; path=/; secure"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_not_setting_cookie_with_secure
+ get :authenticate_with_secure
+ assert_not_cookie_header "user_name=david; path=/; secure"
+ assert_not_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_multiple_cookies
+ get :set_multiple_cookies
+ assert_equal 2, @response.cookies.size
+ assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000\nlogin=XJ-122; path=/"
+ assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies)
+ end
+
+ def test_setting_test_cookie
+ assert_nothing_raised { get :access_frozen_cookies }
+ end
+
+ def test_expiring_cookie
+ request.cookies[:user_name] = 'Joe'
+ get :logout
+ assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+ assert_equal({"user_name" => nil}, @response.cookies)
+ end
+
+ def test_delete_cookie_with_path
+ request.cookies[:user_name] = 'Joe'
+ get :delete_cookie_with_path
+ assert_cookie_header "user_name=; path=/beaten; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+ end
+
+ def test_delete_unexisting_cookie
+ request.cookies.clear
+ get :delete_cookie
+ assert @response.cookies.empty?
+ end
+
+ def test_deleted_cookie_predicate
+ cookies[:user_name] = 'Joe'
+ cookies.delete("user_name")
+ assert cookies.deleted?("user_name")
+ assert_equal false, cookies.deleted?("another")
+ end
+
+ def test_deleted_cookie_predicate_with_mismatching_options
+ cookies[:user_name] = 'Joe'
+ cookies.delete("user_name", :path => "/path")
+ assert_equal false, cookies.deleted?("user_name", :path => "/different")
+ end
+
+ def test_cookies_persist_throughout_request
+ response = get :authenticate
+ assert response.headers["Set-Cookie"] =~ /user_name=david/
+ end
+
+ def test_set_permanent_cookie
+ get :set_permanent_cookie
+ assert_match(/Jamie/, @response.headers["Set-Cookie"])
+ assert_match(%r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"])
+ end
+
+ def test_read_permanent_cookie
+ get :set_permanent_cookie
+ assert_equal 'Jamie', @controller.send(:cookies).permanent[:user_name]
+ end
+
+ def test_signed_cookie_using_default_serializer
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_signed_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_signed_cookie
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal '45 was dumped and loaded', cookies.signed[:user_id]
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageVerifier.new(secret, serializer: Marshal).generate(45)
+ @request.headers["Cookie"] = "user_id=#{marshal_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies['user_id'])
+ end
+
+ def test_signed_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ signed_cookie_salt = @request.env["action_dispatch.signed_cookie_salt"]
+ secret = key_generator.generate_key(signed_cookie_salt)
+ json_value = ActiveSupport::MessageVerifier.new(secret, serializer: JSON).generate(45)
+ @request.headers["Cookie"] = "user_id=#{json_value}"
+
+ get :get_signed_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal 45, cookies[:user_id]
+ assert_equal 45, cookies.signed[:user_id]
+
+ assert_nil @response.cookies["user_id"]
+ end
+
+ def test_accessing_nonexistant_signed_cookie_should_not_raise_an_invalid_signature
+ get :set_signed_cookie
+ assert_nil @controller.send(:cookies).signed[:non_existant_attribute]
+ end
+
+ def test_encrypted_cookie_using_default_serializer
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raise TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_marshal_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :marshal
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises TypeError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_json_serializer
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ get :set_encrypted_cookie
+ cookies = @controller.send :cookies
+ assert_not_equal 'bar', cookies[:foo]
+ assert_raises ::JSON::ParserError do
+ cookies.signed[:foo]
+ end
+ assert_equal 'bar', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_custom_serializer
+ @request.env["action_dispatch.cookies_serializer"] = CustomSerializer
+ get :set_encrypted_cookie
+ assert_not_equal 'bar', cookies.encrypted[:foo]
+ assert_equal 'bar was dumped and loaded', cookies.encrypted[:foo]
+ end
+
+ def test_encrypted_cookie_using_hybrid_serializer_can_migrate_marshal_dumped_value_to_json
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+
+ marshal_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: Marshal).encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{marshal_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_encrypted_cookie_using_hybrid_serializer_can_read_from_json_dumped_value
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ encrypted_cookie_salt = @request.env["action_dispatch.encrypted_cookie_salt"]
+ encrypted_signed_cookie_salt = @request.env["action_dispatch.encrypted_signed_cookie_salt"]
+ secret = key_generator.generate_key(encrypted_cookie_salt)
+ sign_secret = key_generator.generate_key(encrypted_signed_cookie_salt)
+ json_value = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON).encrypt_and_sign("bar")
+ @request.headers["Cookie"] = "foo=#{json_value}"
+
+ get :get_encrypted_cookie
+
+ cookies = @controller.send :cookies
+ assert_not_equal "bar", cookies[:foo]
+ assert_equal "bar", cookies.encrypted[:foo]
+
+ assert_nil @response.cookies["foo"]
+ end
+
+ def test_accessing_nonexistant_encrypted_cookie_should_not_raise_invalid_message
+ get :set_encrypted_cookie
+ assert_nil @controller.send(:cookies).encrypted[:non_existant_attribute]
+ end
+
+ def test_setting_invalid_encrypted_cookie_should_return_nil_when_accessing_it
+ get :set_invalid_encrypted_cookie
+ assert_nil @controller.send(:cookies).encrypted[:invalid_cookie]
+ end
+
+ def test_permanent_signed_cookie
+ get :set_permanent_signed_cookie
+ assert_match(%r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"])
+ assert_equal 100, @controller.send(:cookies).signed[:remember_me]
+ end
+
+ def test_delete_and_set_cookie
+ request.cookies[:user_name] = 'Joe'
+ get :delete_and_set_cookie
+ assert_cookie_header "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000"
+ assert_equal({"user_name" => "david"}, @response.cookies)
+ end
+
+ def test_raise_data_overflow
+ assert_raise(ActionDispatch::Cookies::CookieOverflow) do
+ get :raise_data_overflow
+ end
+ end
+
+ def test_tampered_cookies
+ assert_nothing_raised do
+ get :tampered_cookies
+ assert_response :success
+ end
+ end
+
+ def test_raises_argument_error_if_missing_secret
+ assert_raise(ArgumentError, nil.inspect) {
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil)
+ get :set_signed_cookie
+ }
+
+ assert_raise(ArgumentError, ''.inspect) {
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("")
+ get :set_signed_cookie
+ }
+ end
+
+ def test_raises_argument_error_if_secret_is_probably_insecure
+ assert_raise(ArgumentError, "password".inspect) {
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password")
+ get :set_signed_cookie
+ }
+
+ assert_raise(ArgumentError, "secret".inspect) {
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret")
+ get :set_signed_cookie
+ }
+
+ assert_raise(ArgumentError, "12345678901234567890123456789".inspect) {
+ @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789")
+ get :set_signed_cookie
+ }
+ end
+
+ def test_signed_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_signed_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed
+ end
+
+ def test_signed_uses_upgrade_legacy_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :set_signed_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacySignedCookieJar, cookies.signed
+ end
+
+ def test_signed_or_encrypted_uses_signed_cookie_jar_if_only_secret_token_is_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = nil
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::SignedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_signed_or_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.signed_or_encrypted
+ end
+
+ def test_encrypted_uses_encrypted_cookie_jar_if_only_secret_key_base_is_set
+ @request.env["action_dispatch.secret_token"] = nil
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::EncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_encrypted_uses_upgrade_legacy_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ get :get_encrypted_cookie
+ assert_kind_of ActionDispatch::Cookies::UpgradeLegacyEncryptedCookieJar, cookies.encrypted
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :json
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45)
+
+ @request.headers["Cookie"] = "user_id=#{legacy_value}"
+ get :get_signed_cookie
+
+ assert_equal 45, @controller.send(:cookies).signed[:user_id]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"])
+ verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON)
+ assert_equal 45, verifier.verify(@response.cookies["user_id"])
+ end
+
+ def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set
+ @request.env["action_dispatch.cookies_serializer"] = :hybrid
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+ @request.env["action_dispatch.encrypted_cookie_salt"] = "4433796b79d99a7735553e316522acee"
+ @request.env["action_dispatch.encrypted_signed_cookie_salt"] = "00646eb40062e1b1deff205a27cd30f9"
+
+ legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate('bar')
+
+ @request.headers["Cookie"] = "foo=#{legacy_value}"
+ get :get_encrypted_cookie
+
+ assert_equal 'bar', @controller.send(:cookies).encrypted[:foo]
+
+ key_generator = @request.env["action_dispatch.key_generator"]
+ secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_cookie_salt"])
+ sign_secret = key_generator.generate_key(@request.env["action_dispatch.encrypted_signed_cookie_salt"])
+ encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
+ assert_equal 'bar', encryptor.decrypt_and_verify(@response.cookies["foo"])
+ end
+
+ def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "user_id=45"
+ get :get_signed_cookie
+
+ assert_equal nil, @controller.send(:cookies).signed[:user_id]
+ assert_equal nil, @response.cookies["user_id"]
+ end
+
+ def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered
+ @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33"
+ @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff"
+
+ @request.headers["Cookie"] = "foo=baz"
+ get :get_encrypted_cookie
+
+ assert_equal nil, @controller.send(:cookies).encrypted[:foo]
+ assert_equal nil, @response.cookies["foo"]
+ end
+
+ def test_cookie_with_all_domain_option
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_a_non_standard_tld
+ @request.host = "two.subdomains.nextangle.local"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_australian_style_tld
+ @request.host = "nextangle.com.au"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com.au; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_uk_style_tld
+ @request.host = "nextangle.co.uk"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.co.uk; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_host_with_port
+ @request.host = "nextangle.local:3000"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_localhost
+ @request.host = "localhost"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_ipv4_address
+ @request.host = "192.168.1.1"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_ipv6_address
+ @request.host = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
+ get :set_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
+ def test_deleting_cookie_with_all_domain_option
+ request.cookies[:user_name] = 'Joe'
+ get :delete_cookie_with_domain
+ assert_response :success
+ assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+ end
+
+ def test_cookie_with_all_domain_option_and_tld_length
+ get :set_cookie_with_domain_and_tld
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_a_non_standard_tld_and_tld_length
+ @request.host = "two.subdomains.nextangle.local"
+ get :set_cookie_with_domain_and_tld
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
+ end
+
+ def test_cookie_with_all_domain_option_using_host_with_port_and_tld_length
+ @request.host = "nextangle.local:3000"
+ get :set_cookie_with_domain_and_tld
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/"
+ end
+
+ def test_deleting_cookie_with_all_domain_option_and_tld_length
+ request.cookies[:user_name] = 'Joe'
+ get :delete_cookie_with_domain_and_tld
+ assert_response :success
+ assert_cookie_header "user_name=; domain=.nextangle.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+ end
+
+ def test_cookie_with_several_preset_domains_using_one_of_these_domains
+ @request.host = "example1.com"
+ get :set_cookie_with_domains
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=example1.com; path=/"
+ end
+
+ def test_cookie_with_several_preset_domains_using_other_domain
+ @request.host = "other-domain.com"
+ get :set_cookie_with_domains
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; path=/"
+ end
+
+ def test_cookie_with_several_preset_domains_using_shared_domain
+ @request.host = "example3.com"
+ get :set_cookie_with_domains
+ assert_response :success
+ assert_cookie_header "user_name=rizwanreza; domain=.example3.com; path=/"
+ end
+
+ def test_deletings_cookie_with_several_preset_domains_using_one_of_these_domains
+ @request.host = "example2.com"
+ request.cookies[:user_name] = 'Joe'
+ get :delete_cookie_with_domains
+ assert_response :success
+ assert_cookie_header "user_name=; domain=example2.com; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+ end
+
+ def test_deletings_cookie_with_several_preset_domains_using_other_domain
+ @request.host = "other-domain.com"
+ request.cookies[:user_name] = 'Joe'
+ get :delete_cookie_with_domains
+ assert_response :success
+ assert_cookie_header "user_name=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"
+ end
+
+ def test_cookies_hash_is_indifferent_access
+ get :symbol_key
+ assert_equal "david", cookies[:user_name]
+ assert_equal "david", cookies['user_name']
+ get :string_key
+ assert_equal "dhh", cookies[:user_name]
+ assert_equal "dhh", cookies['user_name']
+ end
+
+ def test_setting_request_cookies_is_indifferent_access
+ cookies.clear
+ cookies[:user_name] = "andrew"
+ get :string_key_mock
+ assert_equal "david", cookies['user_name']
+
+ cookies.clear
+ cookies['user_name'] = "andrew"
+ get :symbol_key_mock
+ assert_equal "david", cookies[:user_name]
+ end
+
+ def test_cookies_retained_across_requests
+ get :symbol_key
+ assert_cookie_header "user_name=david; path=/"
+ assert_equal "david", cookies[:user_name]
+
+ get :noop
+ assert_nil @response.headers["Set-Cookie"]
+ assert_equal "david", cookies[:user_name]
+
+ get :noop
+ assert_nil @response.headers["Set-Cookie"]
+ assert_equal "david", cookies[:user_name]
+ end
+
+ def test_cookies_can_be_cleared
+ get :symbol_key
+ assert_equal "david", cookies[:user_name]
+
+ cookies.clear
+ get :noop
+ assert_nil cookies[:user_name]
+
+ get :symbol_key
+ assert_equal "david", cookies[:user_name]
+ end
+
+ def test_can_set_http_cookie_header
+ @request.env['HTTP_COOKIE'] = 'user_name=david'
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ @request.env['HTTP_COOKIE'] = 'user_name=andrew'
+ get :noop
+ assert_equal 'andrew', cookies['user_name']
+ assert_equal 'andrew', cookies[:user_name]
+ end
+
+ def test_can_set_request_cookies
+ @request.cookies['user_name'] = 'david'
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ @request.cookies[:user_name] = 'andrew'
+ get :noop
+ assert_equal 'andrew', cookies['user_name']
+ assert_equal 'andrew', cookies[:user_name]
+ end
+
+ def test_cookies_precedence_over_http_cookie
+ @request.env['HTTP_COOKIE'] = 'user_name=andrew'
+ get :authenticate
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+ end
+
+ def test_cookies_precedence_over_request_cookies
+ @request.cookies['user_name'] = 'andrew'
+ get :authenticate
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+
+ get :noop
+ assert_equal 'david', cookies['user_name']
+ assert_equal 'david', cookies[:user_name]
+ end
+
+ private
+ def assert_cookie_header(expected)
+ header = @response.headers["Set-Cookie"]
+ if header.respond_to?(:to_str)
+ assert_equal expected.split("\n").sort, header.split("\n").sort
+ else
+ assert_equal expected.split("\n"), header
+ end
+ end
+
+ def assert_not_cookie_header(expected)
+ header = @response.headers["Set-Cookie"]
+ if header.respond_to?(:to_str)
+ assert_not_equal expected.split("\n").sort, header.split("\n").sort
+ else
+ assert_not_equal expected.split("\n"), header
+ end
+ end
+end
+
+end
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
new file mode 100644
index 0000000000..24526fb00e
--- /dev/null
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -0,0 +1,283 @@
+require 'abstract_unit'
+
+class DebugExceptionsTest < ActionDispatch::IntegrationTest
+
+ class Boomer
+ attr_accessor :closed
+
+ def initialize(detailed = false)
+ @detailed = detailed
+ @closed = false
+ end
+
+ # We're obliged to implement this (even though it doesn't actually
+ # get called here) to properly comply with the Rack SPEC
+ def each
+ end
+
+ def close
+ @closed = true
+ end
+
+ def call(env)
+ env['action_dispatch.show_detailed_exceptions'] = @detailed
+ req = ActionDispatch::Request.new(env)
+ case req.path
+ when "/pass"
+ [404, { "X-Cascade" => "pass" }, self]
+ when "/not_found"
+ raise AbstractController::ActionNotFound
+ when "/runtime_error"
+ raise RuntimeError
+ when "/method_not_allowed"
+ raise ActionController::MethodNotAllowed
+ when "/unknown_http_method"
+ raise ActionController::UnknownHttpMethod
+ when "/not_implemented"
+ raise ActionController::NotImplemented
+ when "/unprocessable_entity"
+ raise ActionController::InvalidAuthenticityToken
+ when "/not_found_original_exception"
+ raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ when "/bad_request"
+ raise ActionController::BadRequest
+ when "/missing_keys"
+ raise ActionController::UrlGenerationError, "No route matches"
+ when "/parameter_missing"
+ raise ActionController::ParameterMissing, :missing_param_key
+ when "/original_syntax_error"
+ eval 'broke_syntax =' # `eval` need for raise native SyntaxError at runtime
+ when "/syntax_error_into_view"
+ begin
+ eval 'broke_syntax ='
+ rescue Exception => e
+ template = ActionView::Template.new(File.read(__FILE__),
+ __FILE__,
+ ActionView::Template::Handlers::Raw.new,
+ {})
+ raise ActionView::Template::Error.new(template, e)
+ end
+
+ else
+ raise "puke!"
+ end
+ end
+ end
+
+ def setup
+ app = ActiveSupport::OrderedOptions.new
+ app.config = ActiveSupport::OrderedOptions.new
+ app.config.assets = ActiveSupport::OrderedOptions.new
+ app.config.assets.prefix = '/sprockets'
+ Rails.stubs(:application).returns(app)
+ end
+
+ RoutesApp = Struct.new(:routes).new(SharedTestRoutes)
+ ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp)
+ DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp)
+
+ test 'skip diagnosis if not showing detailed exceptions' do
+ @app = ProductionApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ end
+
+ test 'skip diagnosis if not showing exceptions' do
+ @app = DevelopmentApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => false}
+ end
+ end
+
+ test 'raise an exception on cascade pass' do
+ @app = ProductionApp
+ assert_raise ActionController::RoutingError do
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ end
+
+ test 'closes the response body on cascade pass' do
+ boomer = Boomer.new(false)
+ @app = ActionDispatch::DebugExceptions.new(boomer)
+ assert_raise ActionController::RoutingError do
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ assert boomer.closed, "Expected to close the response body"
+ end
+
+ test 'displays routes in a table when a RoutingError occurs' do
+ @app = DevelopmentApp
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ routing_table = body[/route_table.*<.table>/m]
+ assert_match '/:controller(/:action)(.:format)', routing_table
+ assert_match ':controller#:action', routing_table
+ assert_no_match '&lt;|&gt;', routing_table, "there should not be escaped html in the output"
+ end
+
+ test "rescue with diagnostics message" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 500
+ assert_match(/puke/, body)
+
+ get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/#{AbstractController::ActionNotFound.name}/, body)
+
+ get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_match(/ActionController::MethodNotAllowed/, body)
+
+ get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_match(/ActionController::UnknownHttpMethod/, body)
+
+ get "/bad_request", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 400
+ assert_match(/ActionController::BadRequest/, body)
+
+ get "/parameter_missing", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 400
+ assert_match(/ActionController::ParameterMissing/, body)
+ end
+
+ test "rescue with text error for xhr request" do
+ @app = DevelopmentApp
+ xhr_request_env = {'action_dispatch.show_exceptions' => true, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'}
+
+ get "/", {}, xhr_request_env
+ assert_response 500
+ assert_no_match(/<header>/, body)
+ assert_no_match(/<body>/, body)
+ assert_equal response.content_type, "text/plain"
+ assert_match(/RuntimeError\npuke/, body)
+
+ get "/not_found", {}, xhr_request_env
+ assert_response 404
+ assert_no_match(/<body>/, body)
+ assert_equal response.content_type, "text/plain"
+ assert_match(/#{AbstractController::ActionNotFound.name}/, body)
+
+ get "/method_not_allowed", {}, xhr_request_env
+ assert_response 405
+ assert_no_match(/<body>/, body)
+ assert_equal response.content_type, "text/plain"
+ assert_match(/ActionController::MethodNotAllowed/, body)
+
+ get "/unknown_http_method", {}, xhr_request_env
+ assert_response 405
+ assert_no_match(/<body>/, body)
+ assert_equal response.content_type, "text/plain"
+ assert_match(/ActionController::UnknownHttpMethod/, body)
+
+ get "/bad_request", {}, xhr_request_env
+ assert_response 400
+ assert_no_match(/<body>/, body)
+ assert_equal response.content_type, "text/plain"
+ assert_match(/ActionController::BadRequest/, body)
+
+ get "/parameter_missing", {}, xhr_request_env
+ assert_response 400
+ assert_no_match(/<body>/, body)
+ assert_equal response.content_type, "text/plain"
+ assert_match(/ActionController::ParameterMissing/, body)
+ end
+
+ test "does not show filtered parameters" do
+ @app = DevelopmentApp
+
+ get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.parameter_filter' => [:foo]}
+ assert_response 500
+ assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
+ end
+
+ test "show registered original exception for wrapped exceptions" do
+ @app = DevelopmentApp
+
+ get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/AbstractController::ActionNotFound/, body)
+ end
+
+ test "named urls missing keys raise 500 level error" do
+ @app = DevelopmentApp
+
+ get "/missing_keys", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 500
+
+ assert_match(/ActionController::UrlGenerationError/, body)
+ end
+
+ test "show the controller name in the diagnostics template when controller name is present" do
+ @app = DevelopmentApp
+ get("/runtime_error", {}, {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'id' => 'unknown',
+ 'controller' => 'featured_tile'
+ }
+ })
+ assert_response 500
+ assert_match(/RuntimeError\n\s+in FeaturedTileController/, body)
+ end
+
+ test "sets the HTTP charset parameter" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
+ end
+
+ test 'uses logger from env' do
+ @app = DevelopmentApp
+ output = StringIO.new
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ assert_match(/puke/, output.rewind && output.read)
+ end
+
+ test 'uses backtrace cleaner from env' do
+ @app = DevelopmentApp
+ cleaner = stub(:clean => ['passed backtrace cleaner'])
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
+ assert_match(/passed backtrace cleaner/, body)
+ end
+
+ test 'logs exception backtrace when all lines silenced' do
+ output = StringIO.new
+ backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
+ backtrace_cleaner.add_silencer { true }
+
+ env = {'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.logger' => Logger.new(output),
+ 'action_dispatch.backtrace_cleaner' => backtrace_cleaner}
+
+ get "/", {}, env
+ assert_operator((output.rewind && output.read).lines.count, :>, 10)
+ end
+
+ test 'display backtrace when error type is SyntaxError' do
+ @app = DevelopmentApp
+
+ get '/original_syntax_error', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+
+ assert_response 500
+ assert_select '#Application-Trace' do
+ assert_select 'pre code', /\(eval\):1: syntax error, unexpected/
+ end
+ end
+
+ test 'display backtrace when error type is SyntaxError wrapped by ActionView::Template::Error' do
+ @app = DevelopmentApp
+
+ get '/syntax_error_into_view', {}, {'action_dispatch.backtrace_cleaner' => ActiveSupport::BacktraceCleaner.new}
+
+ assert_response 500
+ assert_select '#Application-Trace' do
+ assert_select 'pre code', /\(eval\):1: syntax error, unexpected/
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
new file mode 100644
index 0000000000..e2b38c23bc
--- /dev/null
+++ b/actionpack/test/dispatch/header_test.rb
@@ -0,0 +1,139 @@
+require "abstract_unit"
+
+class HeaderTest < ActiveSupport::TestCase
+ setup do
+ @headers = ActionDispatch::Http::Headers.new(
+ "CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"
+ )
+ end
+
+ test "#new does not normalize the data" do
+ headers = ActionDispatch::Http::Headers.new(
+ "Content-Type" => "application/json",
+ "HTTP_REFERER" => "/some/page",
+ "Host" => "http://test.com")
+
+ assert_equal({"Content-Type" => "application/json",
+ "HTTP_REFERER" => "/some/page",
+ "Host" => "http://test.com"}, headers.env)
+ end
+
+ test "#env returns the headers as env variables" do
+ assert_equal({"CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "#each iterates through the env variables" do
+ headers = []
+ @headers.each { |pair| headers << pair }
+ assert_equal [["CONTENT_TYPE", "text/plain"],
+ ["HTTP_REFERER", "/some/page"]], headers
+ end
+
+ test "set new headers" do
+ @headers["Host"] = "127.0.0.1"
+
+ assert_equal "127.0.0.1", @headers["Host"]
+ assert_equal "127.0.0.1", @headers["HTTP_HOST"]
+ end
+
+ test "headers can contain numbers" do
+ @headers["Content-MD5"] = "Q2hlY2sgSW50ZWdyaXR5IQ=="
+
+ assert_equal "Q2hlY2sgSW50ZWdyaXR5IQ==", @headers["Content-MD5"]
+ assert_equal "Q2hlY2sgSW50ZWdyaXR5IQ==", @headers["HTTP_CONTENT_MD5"]
+ end
+
+ test "set new env variables" do
+ @headers["HTTP_HOST"] = "127.0.0.1"
+
+ assert_equal "127.0.0.1", @headers["Host"]
+ assert_equal "127.0.0.1", @headers["HTTP_HOST"]
+ end
+
+ test "key?" do
+ assert @headers.key?("CONTENT_TYPE")
+ assert @headers.include?("CONTENT_TYPE")
+ assert @headers.key?("Content-Type")
+ assert @headers.include?("Content-Type")
+ end
+
+ test "fetch with block" do
+ assert_equal "omg", @headers.fetch("notthere") { "omg" }
+ end
+
+ test "accessing http header" do
+ assert_equal "/some/page", @headers["Referer"]
+ assert_equal "/some/page", @headers["referer"]
+ assert_equal "/some/page", @headers["HTTP_REFERER"]
+ end
+
+ test "accessing special header" do
+ assert_equal "text/plain", @headers["Content-Type"]
+ assert_equal "text/plain", @headers["content-type"]
+ assert_equal "text/plain", @headers["CONTENT_TYPE"]
+ end
+
+ test "fetch" do
+ assert_equal "text/plain", @headers.fetch("content-type", nil)
+ assert_equal "not found", @headers.fetch("not-found", "not found")
+ end
+
+ test "#merge! headers with mutation" do
+ @headers.merge!("Host" => "http://example.test",
+ "Content-Type" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://example.test",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "#merge! env with mutation" do
+ @headers.merge!("HTTP_HOST" => "http://first.com",
+ "CONTENT_TYPE" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://first.com",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "merge without mutation" do
+ combined = @headers.merge("HTTP_HOST" => "http://example.com",
+ "CONTENT_TYPE" => "text/html")
+ assert_equal({"HTTP_HOST" => "http://example.com",
+ "CONTENT_TYPE" => "text/html",
+ "HTTP_REFERER" => "/some/page"}, combined.env)
+
+ assert_equal({"CONTENT_TYPE" => "text/plain",
+ "HTTP_REFERER" => "/some/page"}, @headers.env)
+ end
+
+ test "env variables with . are not modified" do
+ headers = ActionDispatch::Http::Headers.new
+ headers.merge! "rack.input" => "",
+ "rack.request.cookie_hash" => "",
+ "action_dispatch.logger" => ""
+
+ assert_equal(["action_dispatch.logger",
+ "rack.input",
+ "rack.request.cookie_hash"], headers.env.keys.sort)
+ end
+
+ test "symbols are treated as strings" do
+ headers = ActionDispatch::Http::Headers.new
+ headers.merge!(:SERVER_NAME => "example.com",
+ "HTTP_REFERER" => "/",
+ :Host => "test.com")
+ assert_equal "example.com", headers["SERVER_NAME"]
+ assert_equal "/", headers[:HTTP_REFERER]
+ assert_equal "test.com", headers["HTTP_HOST"]
+ end
+
+ test "headers directly modifies the passed environment" do
+ env = {"HTTP_REFERER" => "/"}
+ headers = ActionDispatch::Http::Headers.new(env)
+ headers['Referer'] = "http://example.com/"
+ headers.merge! "CONTENT_TYPE" => "text/plain"
+ assert_equal({"HTTP_REFERER"=>"http://example.com/",
+ "CONTENT_TYPE"=>"text/plain"}, env)
+ end
+end
diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb
new file mode 100644
index 0000000000..512f3a8a7a
--- /dev/null
+++ b/actionpack/test/dispatch/live_response_test.rb
@@ -0,0 +1,94 @@
+require 'abstract_unit'
+require 'active_support/concurrency/latch'
+
+module ActionController
+ module Live
+ class ResponseTest < ActiveSupport::TestCase
+ def setup
+ @response = Live::Response.new
+ @response.request = ActionDispatch::Request.new({}) #yolo
+ end
+
+ def test_header_merge
+ header = @response.header.merge('Foo' => 'Bar')
+ assert_kind_of(ActionController::Live::Response::Header, header)
+ assert_not_equal header, @response.header
+ end
+
+ def test_initialize_with_default_headers
+ r = Class.new(Live::Response) do
+ def self.default_headers
+ { 'omg' => 'g' }
+ end
+ end
+
+ header = r.new.header
+ assert_kind_of(ActionController::Live::Response::Header, header)
+ end
+
+ def test_parallel
+ latch = ActiveSupport::Concurrency::Latch.new
+
+ t = Thread.new {
+ @response.stream.write 'foo'
+ latch.await
+ @response.stream.close
+ }
+
+ @response.await_commit
+ @response.each do |part|
+ assert_equal 'foo', part
+ latch.release
+ end
+ assert t.join
+ end
+
+ def test_setting_body_populates_buffer
+ @response.body = 'omg'
+ @response.close
+ assert_equal ['omg'], @response.body_parts
+ end
+
+ def test_cache_control_is_set
+ @response.stream.write 'omg'
+ assert_equal 'no-cache', @response.headers['Cache-Control']
+ end
+
+ def test_content_length_is_removed
+ @response.headers['Content-Length'] = "1234"
+ @response.stream.write 'omg'
+ assert_nil @response.headers['Content-Length']
+ end
+
+ def test_headers_cannot_be_written_after_webserver_reads
+ @response.stream.write 'omg'
+ latch = ActiveSupport::Concurrency::Latch.new
+
+ t = Thread.new {
+ @response.stream.each do |chunk|
+ latch.release
+ end
+ }
+
+ latch.await
+ assert @response.headers.frozen?
+ e = assert_raises(ActionDispatch::IllegalStateError) do
+ @response.headers['Content-Length'] = "zomg"
+ end
+
+ assert_equal 'header already sent', e.message
+ @response.stream.close
+ t.join
+ end
+
+ def test_headers_cannot_be_written_after_close
+ @response.stream.close
+
+ e = assert_raises(ActionDispatch::IllegalStateError) do
+ @response.headers['Content-Length'] = "zomg"
+ end
+ assert_equal 'header already sent', e.message
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
new file mode 100644
index 0000000000..3e554a9cf6
--- /dev/null
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -0,0 +1,112 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class MapperTest < ActiveSupport::TestCase
+ class FakeSet < ActionDispatch::Routing::RouteSet
+ attr_reader :routes
+ alias :set :routes
+
+ def initialize
+ @routes = []
+ end
+
+ def resources_path_names
+ {}
+ end
+
+ def request_class
+ ActionDispatch::Request
+ end
+
+ def add_route(*args)
+ routes << args
+ end
+
+ def conditions
+ routes.map { |x| x[1] }
+ end
+
+ def requirements
+ routes.map { |x| x[2] }
+ end
+ end
+
+ def test_initialize
+ Mapper.new FakeSet.new
+ end
+
+ def test_mapping_requirements
+ options = { :controller => 'foo', :action => 'bar', :via => :get }
+ m = Mapper::Mapping.build({}, FakeSet.new, '/store/:name(*rest)', options)
+ _, _, requirements, _ = m.to_route
+ assert_equal(/.+?/, requirements[:rest])
+ end
+
+ def test_map_slash
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/', :to => 'posts#index', :as => :main
+ assert_equal '/', fakeset.conditions.first[:path_info]
+ end
+
+ def test_map_more_slashes
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+
+ # FIXME: is this a desired behavior?
+ mapper.get '/one/two/', :to => 'posts#index', :as => :main
+ assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info]
+ end
+
+ def test_map_wildcard
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/*path', :to => 'pages#show'
+ assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal(/.+?/, fakeset.requirements.first[:path])
+ end
+
+ def test_map_wildcard_with_other_element
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/*path/foo/:bar', :to => 'pages#show'
+ assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal(/.+?/, fakeset.requirements.first[:path])
+ end
+
+ def test_map_wildcard_with_multiple_wildcard
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/*foo/*bar', :to => 'pages#show'
+ assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal(/.+?/, fakeset.requirements.first[:foo])
+ assert_equal(/.+?/, fakeset.requirements.first[:bar])
+ end
+
+ def test_map_wildcard_with_format_false
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/*path', :to => 'pages#show', :format => false
+ assert_equal '/*path', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:path]
+ end
+
+ def test_map_wildcard_with_format_true
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.get '/*path', :to => 'pages#show', :format => true
+ assert_equal '/*path.:format', fakeset.conditions.first[:path_info]
+ end
+
+ def test_raising_helpful_error_on_invalid_arguments
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ app = lambda { |env| [200, {}, [""]] }
+ assert_raises ArgumentError do
+ mapper.mount app
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/middleware_stack/middleware_test.rb b/actionpack/test/dispatch/middleware_stack/middleware_test.rb
new file mode 100644
index 0000000000..9607f026db
--- /dev/null
+++ b/actionpack/test/dispatch/middleware_stack/middleware_test.rb
@@ -0,0 +1,77 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/stack'
+
+module ActionDispatch
+ class MiddlewareStack
+ class MiddlewareTest < ActiveSupport::TestCase
+ class Omg; end
+
+ {
+ 'concrete' => Omg,
+ 'anonymous' => Class.new
+ }.each do |name, klass|
+
+ define_method("test_#{name}_klass") do
+ mw = Middleware.new klass
+ assert_equal klass, mw.klass
+ end
+
+ define_method("test_#{name}_==") do
+ mw1 = Middleware.new klass
+ mw2 = Middleware.new klass
+ assert_equal mw1, mw2
+ end
+
+ end
+
+ def test_string_class
+ mw = Middleware.new Omg.name
+ assert_equal Omg, mw.klass
+ end
+
+ def test_double_equal_works_with_classes
+ k = Class.new
+ mw = Middleware.new k
+ assert_operator mw, :==, k
+
+ result = mw != Class.new
+ assert result, 'middleware should not equal other anon class'
+ end
+
+ def test_double_equal_works_with_strings
+ mw = Middleware.new Omg
+ assert_operator mw, :==, Omg.name
+ end
+
+ def test_double_equal_normalizes_strings
+ mw = Middleware.new Omg
+ assert_operator mw, :==, "::#{Omg.name}"
+ end
+
+ def test_middleware_loads_classnames_from_cache
+ mw = Class.new(Middleware) {
+ attr_accessor :classcache
+ }.new(Omg.name)
+
+ fake_cache = { mw.name => Omg }
+ mw.classcache = fake_cache
+
+ assert_equal Omg, mw.klass
+
+ fake_cache[mw.name] = Middleware
+ assert_equal Middleware, mw.klass
+ end
+
+ def test_middleware_always_returns_class
+ mw = Class.new(Middleware) {
+ attr_accessor :classcache
+ }.new(Omg)
+
+ fake_cache = { mw.name => Middleware }
+ mw.classcache = fake_cache
+
+ assert_equal Omg, mw.klass
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
new file mode 100644
index 0000000000..948a690979
--- /dev/null
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -0,0 +1,117 @@
+require 'abstract_unit'
+
+class MiddlewareStackTest < ActiveSupport::TestCase
+ class FooMiddleware; end
+ class BarMiddleware; end
+ class BazMiddleware; end
+ class BlockMiddleware
+ attr_reader :block
+ def initialize(&block)
+ @block = block
+ end
+ end
+
+ def setup
+ @stack = ActionDispatch::MiddlewareStack.new
+ @stack.use FooMiddleware
+ @stack.use BarMiddleware
+ end
+
+ test "use should push middleware as class onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use BazMiddleware
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "use should push middleware as a string onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "use should push middleware as a symbol onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use :"MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "use should push middleware class with arguments onto the stack" do
+ assert_difference "@stack.size" do
+ @stack.use BazMiddleware, true, :foo => "bar"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ assert_equal([true, {:foo => "bar"}], @stack.last.args)
+ end
+
+ test "use should push middleware class with block arguments onto the stack" do
+ proc = Proc.new {}
+ assert_difference "@stack.size" do
+ @stack.use(BlockMiddleware, &proc)
+ end
+ assert_equal BlockMiddleware, @stack.last.klass
+ assert_equal proc, @stack.last.block
+ end
+
+ test "insert inserts middleware at the integer index" do
+ @stack.insert(1, BazMiddleware)
+ assert_equal BazMiddleware, @stack[1].klass
+ end
+
+ test "insert_after inserts middleware after the integer index" do
+ @stack.insert_after(1, BazMiddleware)
+ assert_equal BazMiddleware, @stack[2].klass
+ end
+
+ test "insert_before inserts middleware before another middleware class" do
+ @stack.insert_before(BarMiddleware, BazMiddleware)
+ assert_equal BazMiddleware, @stack[1].klass
+ end
+
+ test "insert_after inserts middleware after another middleware class" do
+ @stack.insert_after(BarMiddleware, BazMiddleware)
+ assert_equal BazMiddleware, @stack[2].klass
+ end
+
+ test "swaps one middleware out for another" do
+ assert_equal FooMiddleware, @stack[0].klass
+ @stack.swap(FooMiddleware, BazMiddleware)
+ assert_equal BazMiddleware, @stack[0].klass
+ end
+
+ test "swaps one middleware out for same middleware class" do
+ assert_equal FooMiddleware, @stack[0].klass
+ @stack.swap(FooMiddleware, FooMiddleware, Proc.new { |env| [500, {}, ['error!']] })
+ assert_equal FooMiddleware, @stack[0].klass
+ end
+
+ test "unshift adds a new middleware at the beginning of the stack" do
+ @stack.unshift :"MiddlewareStackTest::BazMiddleware"
+ assert_equal BazMiddleware, @stack.first.klass
+ end
+
+ test "raise an error on invalid index" do
+ assert_raise RuntimeError do
+ @stack.insert("HiyaMiddleware", BazMiddleware)
+ end
+
+ assert_raise RuntimeError do
+ @stack.insert_after("HiyaMiddleware", BazMiddleware)
+ end
+ end
+
+ test "lazy evaluates middleware class" do
+ assert_difference "@stack.size" do
+ @stack.use "MiddlewareStackTest::BazMiddleware"
+ end
+ assert_equal BazMiddleware, @stack.last.klass
+ end
+
+ test "lazy compares so unloaded constants are not loaded" do
+ @stack.use "UnknownMiddleware"
+ @stack.use :"MiddlewareStackTest::BazMiddleware"
+ assert @stack.include?("::MiddlewareStackTest::BazMiddleware")
+ end
+end
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
new file mode 100644
index 0000000000..d29cc8473e
--- /dev/null
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -0,0 +1,203 @@
+require 'abstract_unit'
+
+class MimeTypeTest < ActiveSupport::TestCase
+
+ test "parse single" do
+ Mime::LOOKUP.keys.each do |mime_type|
+ unless mime_type == 'image/*'
+ assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
+ end
+ end
+ end
+
+ test "unregister" do
+ begin
+ Mime::Type.register("text/x-mobile", :mobile)
+ assert defined?(Mime::MOBILE)
+ assert_equal Mime::MOBILE, Mime::LOOKUP['text/x-mobile']
+ assert_equal Mime::MOBILE, Mime::EXTENSION_LOOKUP['mobile']
+
+ Mime::Type.unregister(:mobile)
+ assert !defined?(Mime::MOBILE), "Mime::MOBILE should not be defined"
+ assert !Mime::LOOKUP.has_key?('text/x-mobile'), "Mime::LOOKUP should not have key ['text/x-mobile]"
+ assert !Mime::EXTENSION_LOOKUP.has_key?('mobile'), "Mime::EXTENSION_LOOKUP should not have key ['mobile]"
+ ensure
+ Mime.module_eval { remove_const :MOBILE if const_defined?(:MOBILE) }
+ Mime::LOOKUP.reject!{|key,_| key == 'text/x-mobile'}
+ end
+ end
+
+ test "parse text with trailing star at the beginning" do
+ accept = "text/*, text/html, application/json, multipart/form-data"
+ expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON, Mime::MULTIPART_FORM]
+ parsed = Mime::Type.parse(accept)
+ assert_equal expect, parsed
+ end
+
+ test "parse text with trailing star in the end" do
+ accept = "text/html, application/json, multipart/form-data, text/*"
+ expect = [Mime::HTML, Mime::JSON, Mime::MULTIPART_FORM, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML]
+ parsed = Mime::Type.parse(accept)
+ assert_equal expect, parsed
+ end
+
+ test "parse text with trailing star" do
+ accept = "text/*"
+ expect = [Mime::HTML, Mime::TEXT, Mime::JS, Mime::CSS, Mime::ICS, Mime::CSV, Mime::VCF, Mime::XML, Mime::YAML, Mime::JSON]
+ parsed = Mime::Type.parse(accept)
+ assert_equal expect, parsed
+ end
+
+ test "parse application with trailing star" do
+ accept = "application/*"
+ expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF, Mime::ZIP]
+ parsed = Mime::Type.parse(accept)
+ assert_equal expect, parsed
+ end
+
+ test "parse without q" do
+ accept = "text/xml,application/xhtml+xml,text/yaml,application/xml,text/html,image/png,text/plain,application/pdf,*/*"
+ expect = [Mime::HTML, Mime::XML, Mime::YAML, Mime::PNG, Mime::TEXT, Mime::PDF, Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ test "parse with q" do
+ accept = "text/xml,application/xhtml+xml,text/yaml; q=0.3,application/xml,text/html; q=0.8,image/png,text/plain; q=0.5,application/pdf,*/*; q=0.2"
+ expect = [Mime::HTML, Mime::XML, Mime::PNG, Mime::PDF, Mime::TEXT, Mime::YAML, Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ test "parse single media range with q" do
+ accept = "text/html;q=0.9"
+ expect = [Mime::HTML]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ test "parse arbitrary media type parameters" do
+ accept = 'multipart/form-data; boundary="simple boundary"'
+ expect = [Mime::MULTIPART_FORM]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ # Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP)
+ test "parse broken acceptlines" do
+ accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
+ expect = [Mime::HTML, Mime::XML, "image/*", Mime::TEXT, Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ end
+
+ # Accept header send with user HTTP_USER_AGENT: Mozilla/4.0
+ # (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; InfoPath.1)
+ test "parse other broken acceptlines" do
+ accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, , pronto/1.00.00, sslvpn/1.00.00.00, */*"
+ expect = ['image/gif', 'image/x-xbitmap', 'image/jpeg','image/pjpeg', 'application/x-shockwave-flash', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint', 'application/msword', 'pronto/1.00.00', 'sslvpn/1.00.00.00', Mime::ALL]
+ assert_equal expect, Mime::Type.parse(accept).collect { |c| c.to_s }
+ end
+
+ test "custom type" do
+ begin
+ Mime::Type.register("image/foo", :foo)
+ assert_nothing_raised do
+ assert_equal Mime::FOO, Mime::SET.last
+ end
+ ensure
+ Mime::Type.unregister(:FOO)
+ end
+ end
+
+ test "custom type with type aliases" do
+ begin
+ Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"]
+ %w[text/foobar text/foo text/bar].each do |type|
+ assert_equal Mime::FOOBAR, type
+ end
+ ensure
+ Mime::Type.unregister(:FOOBAR)
+ end
+ end
+
+ test "register callbacks" do
+ begin
+ registered_mimes = []
+ Mime::Type.register_callback do |mime|
+ registered_mimes << mime
+ end
+
+ Mime::Type.register("text/foo", :foo)
+ assert_equal registered_mimes, [Mime::FOO]
+ ensure
+ Mime::Type.unregister(:FOO)
+ end
+ end
+
+ test "custom type with extension aliases" do
+ begin
+ Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
+ %w[foobar foo bar].each do |extension|
+ assert_equal Mime::FOOBAR, Mime::EXTENSION_LOOKUP[extension]
+ end
+ ensure
+ Mime::Type.unregister(:FOOBAR)
+ end
+ end
+
+ test "register alias" do
+ begin
+ Mime::Type.register_alias "application/xhtml+xml", :foobar
+ assert_equal Mime::HTML, Mime::EXTENSION_LOOKUP['foobar']
+ ensure
+ Mime::Type.unregister(:FOOBAR)
+ end
+ end
+
+ test "type should be equal to symbol" do
+ assert_equal Mime::HTML, 'application/xhtml+xml'
+ assert_equal Mime::HTML, :html
+ end
+
+ test "type convenience methods" do
+ # Don't test Mime::ALL, since it Mime::ALL#html? == true
+ types = Mime::SET.symbols.uniq - [:all, :iphone]
+
+ # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
+ types.delete_if { |type| !Mime.const_defined?(type.upcase) }
+
+
+ types.each do |type|
+ mime = Mime.const_get(type.upcase)
+ assert mime.respond_to?("#{type}?"), "#{mime.inspect} does not respond to #{type}?"
+ assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?"
+ invalid_types = types - [type]
+ invalid_types.delete(:html) if Mime::Type.html_types.include?(type)
+ invalid_types.each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" }
+ end
+ end
+
+ test "mime all is html" do
+ assert Mime::ALL.all?, "Mime::ALL is not all?"
+ assert Mime::ALL.html?, "Mime::ALL is not html?"
+ end
+
+ test "verifiable mime types" do
+ all_types = Mime::SET.symbols
+ all_types.uniq!
+ # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE
+ all_types.delete_if { |type| !Mime.const_defined?(type.upcase) }
+ end
+
+ test "references gives preference to symbols before strings" do
+ assert_equal :html, Mime::HTML.ref
+ another = Mime::Type.lookup("foo/bar")
+ assert_nil another.to_sym
+ assert_equal "foo/bar", another.ref
+ end
+
+ test "regexp matcher" do
+ assert Mime::JS =~ "text/javascript"
+ assert Mime::JS =~ "application/javascript"
+ assert Mime::JS !~ "text/html"
+ assert !(Mime::JS !~ "text/javascript")
+ assert !(Mime::JS !~ "application/javascript")
+ assert Mime::HTML =~ 'application/xhtml+xml'
+ end
+end
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
new file mode 100644
index 0000000000..d5a4d8ee11
--- /dev/null
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -0,0 +1,93 @@
+require 'abstract_unit'
+require 'rails/engine'
+
+class TestRoutingMount < ActionDispatch::IntegrationTest
+ Router = ActionDispatch::Routing::RouteSet.new
+
+ class AppWithRoutes < Rails::Engine
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+ end
+
+ # Test for mounting apps that respond to routes, but aren't Rails-like apps.
+ class SinatraLikeApp
+ def self.routes; Object.new; end
+
+ def self.call(env)
+ [200, {"Content-Type" => "text/html"}, ["OK"]]
+ end
+ end
+
+ Router.draw do
+ SprocketsApp = lambda { |env|
+ [200, {"Content-Type" => "text/html"}, ["#{env["SCRIPT_NAME"]} -- #{env["PATH_INFO"]}"]]
+ }
+
+ mount SprocketsApp, :at => "/sprockets"
+ mount SprocketsApp => "/shorthand"
+
+ mount SinatraLikeApp, :at => "/fakeengine", :as => :fake
+ mount SinatraLikeApp, :at => "/getfake", :via => :get
+
+ scope "/its_a" do
+ mount SprocketsApp, :at => "/sprocket"
+ end
+
+ resources :users do
+ mount AppWithRoutes, :at => "/fakeengine", :as => :fake_mounted_at_resource
+ end
+
+ mount SprocketsApp, :at => "/", :via => :get
+ end
+
+ APP = RoutedRackApp.new Router
+ def app
+ APP
+ end
+
+ def test_app_name_is_properly_generated_when_engine_is_mounted_in_resources
+ assert Router.mounted_helpers.method_defined?(:user_fake_mounted_at_resource),
+ "A mounted helper should be defined with a parent's prefix"
+ assert Router.named_routes.routes[:user_fake_mounted_at_resource],
+ "A named route should be defined with a parent's prefix"
+ end
+
+ def test_mounting_at_root_path
+ get "/omg"
+ assert_equal " -- /omg", response.body
+ end
+
+ def test_mounting_sets_script_name
+ get "/sprockets/omg"
+ assert_equal "/sprockets -- /omg", response.body
+ end
+
+ def test_mounting_works_with_nested_script_name
+ get "/foo/sprockets/omg", {}, 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg'
+ assert_equal "/foo/sprockets -- /omg", response.body
+ end
+
+ def test_mounting_works_with_scope
+ get "/its_a/sprocket/omg"
+ assert_equal "/its_a/sprocket -- /omg", response.body
+ end
+
+ def test_mounting_with_shorthand
+ get "/shorthand/omg"
+ assert_equal "/shorthand -- /omg", response.body
+ end
+
+ def test_mounting_works_with_via
+ get "/getfake"
+ assert_equal "OK", response.body
+
+ post "/getfake"
+ assert_response :not_found
+ end
+
+ def test_with_fake_engine_does_not_call_invalid_method
+ get "/fakeengine"
+ assert_equal "OK", response.body
+ end
+end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
new file mode 100644
index 0000000000..f90d5499d7
--- /dev/null
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -0,0 +1,465 @@
+require 'abstract_unit'
+require 'rack/test'
+require 'rails/engine'
+
+module TestGenerationPrefix
+ class Post
+ extend ActiveModel::Naming
+
+ def to_param
+ "1"
+ end
+
+ def self.model_name
+ klass = "Post"
+ def klass.name; self end
+
+ ActiveModel::Name.new(klass)
+ end
+
+ def to_model; self; end
+ def persisted?; true; end
+ end
+
+ class WithMountedEngine < ActionDispatch::IntegrationTest
+ include Rack::Test::Methods
+
+ class BlogEngine < Rails::Engine
+ routes.draw do
+ get "/posts/:id", :to => "inside_engine_generating#show", :as => :post
+ get "/posts", :to => "inside_engine_generating#index", :as => :posts
+ get "/url_to_application", :to => "inside_engine_generating#url_to_application"
+ get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
+ get "/conflicting_url", :to => "inside_engine_generating#conflicting"
+ get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
+
+ get "/relative_path_root", :to => redirect("")
+ get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_option_root", :to => redirect(:path => "")
+ get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_root", :to => redirect { |params, request| "" }
+ get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
+
+ get "/absolute_path_root", :to => redirect("/")
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_root", :to => redirect(:path => "/")
+ get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_root", :to => redirect { |params, request| "/" }
+ get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
+ end
+ end
+
+ class RailsApplication < Rails::Engine
+ routes.draw do
+ scope "/:omg", :omg => "awesome" do
+ mount BlogEngine => "/blog", :as => "blog_engine"
+ end
+ get "/posts/:id", :to => "outside_engine_generating#post", :as => :post
+ get "/generate", :to => "outside_engine_generating#index"
+ get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
+ get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
+ get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
+ get "/conflicting_url", :to => "outside_engine_generating#conflicting"
+ get "/ivar_usage", :to => "outside_engine_generating#ivar_usage"
+ root :to => "outside_engine_generating#index"
+ end
+ end
+
+ # force draw
+ RailsApplication.routes.define_mounted_helper(:main_app)
+
+ class ::InsideEngineGeneratingController < ActionController::Base
+ include BlogEngine.routes.url_helpers
+ include RailsApplication.routes.mounted_helpers
+
+ def index
+ render :text => posts_path
+ end
+
+ def show
+ render :text => post_path(:id => params[:id])
+ end
+
+ def url_to_application
+ path = main_app.url_for(:controller => "outside_engine_generating",
+ :action => "index",
+ :only_path => true)
+ render :text => path
+ end
+
+ def polymorphic_path_for_engine
+ render :text => polymorphic_path(Post.new)
+ end
+
+ def conflicting
+ render :text => "engine"
+ end
+ end
+
+ class ::OutsideEngineGeneratingController < ActionController::Base
+ include BlogEngine.routes.mounted_helpers
+ include RailsApplication.routes.url_helpers
+
+ def index
+ render :text => blog_engine.post_path(:id => 1)
+ end
+
+ def polymorphic_path_for_engine
+ render :text => blog_engine.polymorphic_path(Post.new)
+ end
+
+ def polymorphic_path_for_app
+ render :text => polymorphic_path(Post.new)
+ end
+
+ def polymorphic_with_url_for
+ render :text => blog_engine.url_for(Post.new)
+ end
+
+ def conflicting
+ render :text => "application"
+ end
+
+ def ivar_usage
+ @blog_engine = "Not the engine route helper"
+ render :text => blog_engine.post_path(:id => 1)
+ end
+ end
+
+ class EngineObject
+ include ActionDispatch::Routing::UrlFor
+ include BlogEngine.routes.url_helpers
+ end
+
+ class AppObject
+ include ActionDispatch::Routing::UrlFor
+ include RailsApplication.routes.url_helpers
+ end
+
+ def app
+ RailsApplication.instance
+ end
+
+ attr_reader :engine_object, :app_object
+
+ def setup
+ RailsApplication.routes.default_url_options = {}
+ @engine_object = EngineObject.new
+ @app_object = AppObject.new
+ end
+
+ include BlogEngine.routes.mounted_helpers
+
+ # Inside Engine
+ test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do
+ get "/pure-awesomeness/blog/posts/1"
+ assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
+ end
+
+ test "[ENGINE] generating application's url never uses SCRIPT_NAME from request" do
+ get "/pure-awesomeness/blog/url_to_application"
+ assert_equal "/generate", last_response.body
+ end
+
+ test "[ENGINE] generating engine's url with polymorphic path" do
+ get "/pure-awesomeness/blog/polymorphic_path_for_engine"
+ assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
+ end
+
+ test "[ENGINE] url_helpers from engine have higher priotity than application's url_helpers" do
+ get "/awesome/blog/conflicting_url"
+ assert_equal "engine", last_response.body
+ end
+
+ test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_path_root"
+ verify_redirect "http://example.org/awesome/blog"
+ end
+
+ test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_path_redirect"
+ verify_redirect "http://example.org/awesome/blog/foo"
+ end
+
+ test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_option_root"
+ verify_redirect "http://example.org/awesome/blog"
+ end
+
+ test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_option_redirect"
+ verify_redirect "http://example.org/awesome/blog/foo"
+ end
+
+ test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_custom_root"
+ verify_redirect "http://example.org/awesome/blog"
+ end
+
+ test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
+ get "/awesome/blog/relative_custom_redirect"
+ verify_redirect "http://example.org/awesome/blog/foo"
+ end
+
+ test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_path_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_path_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_option_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_option_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_custom_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
+ get "/awesome/blog/absolute_custom_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ # Inside Application
+ test "[APP] generating engine's route includes prefix" do
+ get "/generate"
+ assert_equal "/awesome/blog/posts/1", last_response.body
+ end
+
+ test "[APP] generating engine's route includes default_url_options[:script_name]" do
+ RailsApplication.routes.default_url_options = {:script_name => "/something"}
+ get "/generate"
+ assert_equal "/something/awesome/blog/posts/1", last_response.body
+ end
+
+ test "[APP] generating engine's url with polymorphic path" do
+ get "/polymorphic_path_for_engine"
+ assert_equal "/awesome/blog/posts/1", last_response.body
+ end
+
+ test "polymorphic_path_for_app" do
+ get "/polymorphic_path_for_app"
+ assert_equal "/posts/1", last_response.body
+ end
+
+ test "[APP] generating engine's url with url_for(@post)" do
+ get "/polymorphic_with_url_for"
+ assert_equal "http://example.org/awesome/blog/posts/1", last_response.body
+ end
+
+ test "[APP] instance variable with same name as engine" do
+ get "/ivar_usage"
+ assert_equal "/awesome/blog/posts/1", last_response.body
+ end
+
+ # Inside any Object
+ test "[OBJECT] proxy route should override respond_to?() as expected" do
+ assert_respond_to blog_engine, :named_helper_that_should_be_invoked_only_in_respond_to_test_path
+ end
+
+ test "[OBJECT] generating engine's route includes prefix" do
+ assert_equal "/awesome/blog/posts/1", engine_object.post_path(:id => 1)
+ end
+
+ test "[OBJECT] generating engine's route includes dynamic prefix" do
+ assert_equal "/pure-awesomeness/blog/posts/3", engine_object.post_path(:id => 3, :omg => "pure-awesomeness")
+ end
+
+ test "[OBJECT] generating engine's route includes default_url_options[:script_name]" do
+ RailsApplication.routes.default_url_options = {:script_name => "/something"}
+ assert_equal "/something/pure-awesomeness/blog/posts/3", engine_object.post_path(:id => 3, :omg => "pure-awesomeness")
+ end
+
+ test "[OBJECT] generating application's route" do
+ assert_equal "/", app_object.root_path
+ end
+
+ test "[OBJECT] generating application's route includes default_url_options[:script_name]" do
+ RailsApplication.routes.default_url_options = {:script_name => "/something"}
+ assert_equal "/something/", app_object.root_path
+ end
+
+ test "[OBJECT] generating application's route includes default_url_options[:trailing_slash]" do
+ RailsApplication.routes.default_url_options[:trailing_slash] = true
+ assert_equal "/awesome/blog/posts", engine_object.posts_path
+ end
+
+ test "[OBJECT] generating engine's route with url_for" do
+ path = engine_object.url_for(:controller => "inside_engine_generating",
+ :action => "show",
+ :only_path => true,
+ :omg => "omg",
+ :id => 1)
+ assert_equal "/omg/blog/posts/1", path
+ end
+
+ test "[OBJECT] generating engine's route with named helpers" do
+ path = engine_object.posts_path
+ assert_equal "/awesome/blog/posts", path
+
+ path = engine_object.posts_url(:host => "example.com")
+ assert_equal "http://example.com/awesome/blog/posts", path
+ end
+
+ test "[OBJECT] generating engine's route with polymorphic_url" do
+ path = engine_object.polymorphic_path(Post.new)
+ assert_equal "/awesome/blog/posts/1", path
+
+ path = engine_object.polymorphic_url(Post.new, :host => "www.example.com")
+ assert_equal "http://www.example.com/awesome/blog/posts/1", path
+ end
+
+ private
+ def verify_redirect(url, status = 301)
+ assert_equal status, last_response.status
+ assert_equal url, last_response.headers["Location"]
+ assert_equal expected_redirect_body(url), last_response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
+ end
+ end
+
+ class EngineMountedAtRoot < ActionDispatch::IntegrationTest
+ include Rack::Test::Methods
+
+ class BlogEngine
+ def self.routes
+ @routes ||= begin
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.draw do
+ get "/posts/:id", :to => "posts#show", :as => :post
+
+ get "/relative_path_root", :to => redirect("")
+ get "/relative_path_redirect", :to => redirect("foo")
+ get "/relative_option_root", :to => redirect(:path => "")
+ get "/relative_option_redirect", :to => redirect(:path => "foo")
+ get "/relative_custom_root", :to => redirect { |params, request| "" }
+ get "/relative_custom_redirect", :to => redirect { |params, request| "foo" }
+
+ get "/absolute_path_root", :to => redirect("/")
+ get "/absolute_path_redirect", :to => redirect("/foo")
+ get "/absolute_option_root", :to => redirect(:path => "/")
+ get "/absolute_option_redirect", :to => redirect(:path => "/foo")
+ get "/absolute_custom_root", :to => redirect { |params, request| "/" }
+ get "/absolute_custom_redirect", :to => redirect { |params, request| "/foo" }
+ end
+
+ routes
+ end
+ end
+
+ def self.call(env)
+ env['action_dispatch.routes'] = routes
+ routes.call(env)
+ end
+ end
+
+ class RailsApplication < Rails::Engine
+ routes.draw do
+ mount BlogEngine => "/"
+ end
+ end
+
+ class ::PostsController < ActionController::Base
+ include BlogEngine.routes.url_helpers
+ include RailsApplication.routes.mounted_helpers
+
+ def show
+ render :text => post_path(:id => params[:id])
+ end
+ end
+
+ def app
+ RailsApplication.instance
+ end
+
+ test "generating path inside engine" do
+ get "/posts/1"
+ assert_equal "/posts/1", last_response.body
+ end
+
+ test "[ENGINE] relative path root uses SCRIPT_NAME from request" do
+ get "/relative_path_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do
+ get "/relative_path_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] relative option root uses SCRIPT_NAME from request" do
+ get "/relative_option_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do
+ get "/relative_option_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do
+ get "/relative_custom_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do
+ get "/relative_custom_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do
+ get "/absolute_path_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do
+ get "/absolute_path_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do
+ get "/absolute_option_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do
+ get "/absolute_option_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do
+ get "/absolute_custom_root"
+ verify_redirect "http://example.org/"
+ end
+
+ test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do
+ get "/absolute_custom_redirect"
+ verify_redirect "http://example.org/foo"
+ end
+
+ private
+ def verify_redirect(url, status = 301)
+ assert_equal status, last_response.status
+ assert_equal url, last_response.headers["Location"]
+ assert_equal expected_redirect_body(url), last_response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{url}">redirected</a>.</body></html>)
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/rack_cache_test.rb b/actionpack/test/dispatch/rack_cache_test.rb
new file mode 100644
index 0000000000..79d8a64d29
--- /dev/null
+++ b/actionpack/test/dispatch/rack_cache_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+require 'action_dispatch/http/rack_cache'
+
+class RackCacheMetaStoreTest < ActiveSupport::TestCase
+ class ReadWriteHash < ::Hash
+ alias :read :[]
+ alias :write :[]=
+ end
+
+ setup do
+ @store = ActionDispatch::RailsMetaStore.new(ReadWriteHash.new)
+ end
+
+ test "stuff is deep duped" do
+ @store.write(:foo, { :bar => :original })
+ hash = @store.read(:foo)
+ hash[:bar] = :changed
+ hash = @store.read(:foo)
+ assert_equal :original, hash[:bar]
+ end
+end
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
new file mode 100644
index 0000000000..62e8197e20
--- /dev/null
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -0,0 +1,176 @@
+require 'abstract_unit'
+
+class ReloaderTest < ActiveSupport::TestCase
+ Reloader = ActionDispatch::Reloader
+
+ teardown do
+ Reloader.reset_callbacks :prepare
+ Reloader.reset_callbacks :cleanup
+ end
+
+ def test_prepare_callbacks
+ a = b = c = nil
+ Reloader.to_prepare { |*args| a = b = c = 1 }
+ Reloader.to_prepare { |*args| b = c = 2 }
+ Reloader.to_prepare { |*args| c = 3 }
+
+ # Ensure to_prepare callbacks are not run when defined
+ assert_nil a || b || c
+
+ # Run callbacks
+ call_and_return_body
+
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+ end
+
+ class MyBody < Array
+ def initialize(&block)
+ @on_close = block
+ end
+
+ def foo
+ "foo"
+ end
+
+ def bar
+ "bar"
+ end
+
+ def close
+ @on_close.call if @on_close
+ end
+ end
+
+ def test_returned_body_object_always_responds_to_close
+ body = call_and_return_body
+ assert_respond_to body, :close
+ end
+
+ def test_returned_body_object_always_responds_to_close_even_if_called_twice
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+ end
+
+ def test_condition_specifies_when_to_reload
+ i, j = 0, 0, 0, 0
+ Reloader.to_prepare { |*args| i += 1 }
+ Reloader.to_cleanup { |*args| j += 1 }
+ app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 })
+ 5.times do
+ resp = app.call({})
+ resp[2].close
+ end
+ assert_equal 3, i
+ assert_equal 3, j
+ end
+
+ def test_returned_body_object_behaves_like_underlying_object
+ body = call_and_return_body do
+ b = MyBody.new
+ b << "hello"
+ b << "world"
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ assert_equal 2, body.size
+ assert_equal "hello", body[0]
+ assert_equal "world", body[1]
+ assert_equal "foo", body.foo
+ assert_equal "bar", body.bar
+ end
+
+ def test_it_calls_close_on_underlying_object_when_close_is_called_on_body
+ close_called = false
+ body = call_and_return_body do
+ b = MyBody.new do
+ close_called = true
+ end
+ [200, { "Content-Type" => "text/html" }, b]
+ end
+ body.close
+ assert close_called
+ end
+
+ def test_returned_body_object_responds_to_all_methods_supported_by_underlying_object
+ body = call_and_return_body do
+ [200, { "Content-Type" => "text/html" }, MyBody.new]
+ end
+ assert_respond_to body, :size
+ assert_respond_to body, :each
+ assert_respond_to body, :foo
+ assert_respond_to body, :bar
+ end
+
+ def test_cleanup_callbacks_are_called_when_body_is_closed
+ cleaned = false
+ Reloader.to_cleanup { cleaned = true }
+
+ body = call_and_return_body
+ assert !cleaned
+
+ body.close
+ assert cleaned
+ end
+
+ def test_prepare_callbacks_arent_called_when_body_is_closed
+ prepared = false
+ Reloader.to_prepare { prepared = true }
+
+ body = call_and_return_body
+ prepared = false
+
+ body.close
+ assert !prepared
+ end
+
+ def test_manual_reloading
+ prepared = cleaned = false
+ Reloader.to_prepare { prepared = true }
+ Reloader.to_cleanup { cleaned = true }
+
+ Reloader.prepare!
+ assert prepared
+ assert !cleaned
+
+ prepared = cleaned = false
+ Reloader.cleanup!
+ assert !prepared
+ assert cleaned
+ end
+
+ def test_prepend_prepare_callback
+ i = 10
+ Reloader.to_prepare { i += 1 }
+ Reloader.to_prepare(:prepend => true) { i = 0 }
+
+ Reloader.prepare!
+ assert_equal 1, i
+ end
+
+ def test_cleanup_callbacks_are_called_on_exceptions
+ cleaned = false
+ Reloader.to_cleanup { cleaned = true }
+
+ begin
+ call_and_return_body do
+ raise "error"
+ end
+ rescue
+ end
+
+ assert cleaned
+ end
+
+ private
+ def call_and_return_body(&block)
+ @response ||= 'response'
+ @reloader ||= Reloader.new(block || proc {[200, {}, @response]})
+ @reloader.call({'rack.input' => StringIO.new('')})[2]
+ end
+end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
new file mode 100644
index 0000000000..c609075e6b
--- /dev/null
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -0,0 +1,164 @@
+require 'abstract_unit'
+
+class JsonParamsParsingTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_request_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ head :ok
+ end
+ end
+
+ def teardown
+ TestController.last_request_parameters = nil
+ end
+
+ test "parses json params for application json" do
+ assert_parses(
+ {"person" => {"name" => "David"}},
+ "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ test "parses boolean and number json params for application json" do
+ assert_parses(
+ {"item" => {"enabled" => false, "count" => 10}},
+ "{\"item\": {\"enabled\": false, \"count\": 10}}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ test "parses json params for application jsonrequest" do
+ assert_parses(
+ {"person" => {"name" => "David"}},
+ "{\"person\": {\"name\": \"David\"}}", { 'CONTENT_TYPE' => 'application/jsonrequest' }
+ )
+ end
+
+ test "nils are stripped from collections" do
+ assert_parses(
+ {"person" => nil},
+ "{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ assert_parses(
+ {"person" => ['foo']},
+ "{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ assert_parses(
+ {"person" => nil},
+ "{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ test "logs error if parsing unsuccessful" do
+ with_test_routing do
+ output = StringIO.new
+ json = "[\"person]\": {\"name\": \"David\"}}"
+ post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)}
+ assert_response :bad_request
+ output.rewind && err = output.read
+ assert err =~ /Error occurred while parsing request parameters/
+ end
+ end
+
+ test "occurring a parse error if parsing unsuccessful" do
+ with_test_routing do
+ begin
+ $stderr = StringIO.new # suppress the log
+ json = "[\"person]\": {\"name\": \"David\"}}"
+ exception = assert_raise(ActionDispatch::ParamsParser::ParseError) { post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => false} }
+ assert_equal JSON::ParserError, exception.original_exception.class
+ assert_equal exception.original_exception.message, exception.message
+ ensure
+ $stderr = STDERR
+ end
+ end
+ end
+
+ test 'raw_post is not empty for JSON request' do
+ with_test_routing do
+ post '/parse', '{"posts": [{"title": "Post Title"}]}', 'CONTENT_TYPE' => 'application/json'
+ assert_equal '{"posts": [{"title": "Post Title"}]}', request.raw_post
+ end
+ end
+
+ private
+ def assert_parses(expected, actual, headers = {})
+ with_test_routing do
+ post "/parse", actual, headers
+ assert_response :ok
+ assert_equal(expected, TestController.last_request_parameters)
+ end
+ end
+
+ def with_test_routing
+ with_routing do |set|
+ set.draw do
+ post ':action', :to => ::JsonParamsParsingTest::TestController
+ end
+ yield
+ end
+ end
+end
+
+class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
+ class UsersController < ActionController::Base
+ wrap_parameters :format => :json
+
+ class << self
+ attr_accessor :last_request_parameters, :last_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ self.class.last_parameters = params
+ head :ok
+ end
+ end
+
+ def teardown
+ UsersController.last_request_parameters = nil
+ end
+
+ test "parses json params for application json" do
+ assert_parses(
+ {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ test "parses json params for application jsonrequest" do
+ assert_parses(
+ {"user" => {"username" => "sikachu"}, "username" => "sikachu"},
+ "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/jsonrequest' }
+ )
+ end
+
+ test "parses json with non-object JSON content" do
+ assert_parses(
+ {"user" => {"_json" => "string content" }, "_json" => "string content" },
+ "\"string content\"", { 'CONTENT_TYPE' => 'application/json' }
+ )
+ end
+
+ private
+ def assert_parses(expected, actual, headers = {})
+ with_test_routing(UsersController) do
+ post "/parse", actual, headers
+ assert_response :ok
+ assert_equal(expected, UsersController.last_request_parameters)
+ assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters)
+ end
+ end
+
+ def with_test_routing(controller)
+ with_routing do |set|
+ set.draw do
+ post ':action', :to => controller
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
new file mode 100644
index 0000000000..926472163e
--- /dev/null
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -0,0 +1,186 @@
+# encoding: utf-8
+require 'abstract_unit'
+
+class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_request_parameters, :last_parameters
+ end
+
+ def parse
+ self.class.last_request_parameters = begin
+ request.request_parameters
+ rescue EOFError
+ {}
+ end
+ self.class.last_parameters = request.parameters
+ head :ok
+ end
+
+ def read
+ render :text => "File: #{params[:uploaded_data].read}"
+ end
+ end
+
+ FIXTURE_PATH = File.dirname(__FILE__) + '/../../fixtures/multipart'
+
+ def teardown
+ TestController.last_request_parameters = nil
+ end
+
+ test "parses single parameter" do
+ assert_equal({ 'foo' => 'bar' }, parse_multipart('single_parameter'))
+ end
+
+ test "parses bracketed parameters" do
+ assert_equal({ 'foo' => { 'baz' => 'bar'}}, parse_multipart('bracketed_param'))
+ end
+
+ test "parse single utf8 parameter" do
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ parse_multipart('single_utf8_param'), "request.request_parameters")
+ assert_equal(
+ 'Iñtërnâtiônàlizætiøn_value',
+ TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters")
+ end
+
+ test "parse bracketed utf8 parameter" do
+ assert_equal({ 'Iñtërnâtiônàlizætiøn_name' => {
+ 'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'} },
+ parse_multipart('bracketed_utf8_param'), "request.request_parameters")
+ assert_equal(
+ {'Iñtërnâtiônàlizætiøn_nested_name' => 'Iñtërnâtiônàlizætiøn_value'},
+ TestController.last_parameters['Iñtërnâtiônàlizætiøn_name'], "request.parameters")
+ end
+
+ test "parses text file" do
+ params = parse_multipart('text_file')
+ assert_equal %w(file foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_equal 'file.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+ assert_equal 'contents', file.read
+ end
+
+ test "parses boundary problem file" do
+ params = parse_multipart('boundary_problem_file')
+ assert_equal %w(file foo), params.keys.sort
+
+ file = params['file']
+ foo = params['foo']
+
+ assert_equal 'file.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+
+ assert_equal 'bar', foo
+ end
+
+ test "parses large text file" do
+ params = parse_multipart('large_text_file')
+ assert_equal %w(file foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+
+ assert_equal 'file.txt', file.original_filename
+ assert_equal "text/plain", file.content_type
+ assert_equal(('a' * 20480), file.read)
+ end
+
+ test "parses binary file" do
+ params = parse_multipart('binary_file')
+ assert_equal %w(file flowers foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ file = params['file']
+ assert_equal 'file.csv', file.original_filename
+ assert_nil file.content_type
+ assert_equal 'contents', file.read
+
+ file = params['flowers']
+ assert_equal 'flowers.jpg', file.original_filename
+ assert_equal "image/jpeg", file.content_type
+ assert_equal 19512, file.size
+ end
+
+ test "parses mixed files" do
+ params = parse_multipart('mixed_files')
+ assert_equal %w(files foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ # Rack doesn't handle multipart/mixed for us.
+ files = params['files']
+ assert_equal 19756, files.bytesize
+ end
+
+ test "does not create tempfile if no file has been selected" do
+ params = parse_multipart('none')
+ assert_equal %w(submit-name), params.keys.sort
+ assert_equal 'Larry', params['submit-name']
+ assert_equal nil, params['files']
+ end
+
+ test "parses empty upload file" do
+ params = parse_multipart('empty')
+ assert_equal %w(files submit-name), params.keys.sort
+ assert_equal 'Larry', params['submit-name']
+ assert params['files']
+ assert_equal "", params['files'].read
+ end
+
+ test "uploads and reads binary file" do
+ with_test_routing do
+ fixture = FIXTURE_PATH + "/mona_lisa.jpg"
+ params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") }
+ post '/read', params
+ end
+ end
+
+ test "uploads and reads file" do
+ with_test_routing do
+ post '/read', :uploaded_data => fixture_file_upload(FIXTURE_PATH + "/hello.txt", "text/plain")
+ assert_equal "File: Hello", response.body
+ end
+ end
+
+ # This can happen in Internet Explorer when redirecting after multipart form submit.
+ test "does not raise EOFError on GET request with multipart content-type" do
+ with_routing do |set|
+ set.draw do
+ get ':action', controller: 'multipart_params_parsing_test/test'
+ end
+ headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" }
+ get "/parse", {}, headers
+ assert_response :ok
+ end
+ end
+
+ private
+ def fixture(name)
+ File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
+ { "rack.input" => file.read,
+ "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x",
+ "CONTENT_LENGTH" => file.stat.size.to_s }
+ end
+ end
+
+ def parse_multipart(name)
+ with_test_routing do
+ headers = fixture(name)
+ post "/parse", headers.delete("rack.input"), headers
+ assert_response :ok
+ TestController.last_request_parameters
+ end
+ end
+
+ def with_test_routing
+ with_routing do |set|
+ set.draw do
+ post ':action', :controller => 'multipart_params_parsing_test/test'
+ end
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
new file mode 100644
index 0000000000..4e99c26e03
--- /dev/null
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -0,0 +1,171 @@
+require 'abstract_unit'
+
+class QueryStringParsingTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_query_parameters
+ end
+
+ def parse
+ self.class.last_query_parameters = request.query_parameters
+ head :ok
+ end
+ end
+ class EarlyParse
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ # Trigger a Rack parse so that env caches the query params
+ Rack::Request.new(env).params
+ @app.call(env)
+ end
+ end
+
+ def teardown
+ TestController.last_query_parameters = nil
+ end
+
+ test "query string" do
+ assert_parses(
+ {"action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
+ "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
+ )
+ end
+
+ test "deep query string" do
+ assert_parses(
+ {'x' => {'y' => {'z' => '10'}}},
+ "x[y][z]=10"
+ )
+ end
+
+ test "deep query string with array" do
+ assert_parses({'x' => {'y' => {'z' => ['10']}}}, 'x[y][z][]=10')
+ assert_parses({'x' => {'y' => {'z' => ['10', '5']}}}, 'x[y][z][]=10&x[y][z][]=5')
+ end
+
+ test "deep query string with array of hash" do
+ assert_parses({'x' => {'y' => [{'z' => '10'}]}}, 'x[y][][z]=10')
+ assert_parses({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, 'x[y][][z]=10&x[y][][w]=10')
+ assert_parses({'x' => {'y' => [{'z' => '10', 'v' => {'w' => '10'}}]}}, 'x[y][][z]=10&x[y][][v][w]=10')
+ end
+
+ test "deep query string with array of hashes with one pair" do
+ assert_parses({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, 'x[y][][z]=10&x[y][][z]=20')
+ end
+
+ test "deep query string with array of hashes with multiple pairs" do
+ assert_parses(
+ {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
+ 'x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b'
+ )
+ end
+
+ test "query string with nil" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => ''},
+ "action=create_customer&full_name="
+ )
+ end
+
+ test "query string with array" do
+ assert_parses(
+ { "action" => "create_customer", "selected" => ["1", "2", "3"]},
+ "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
+ )
+ end
+
+ test "query string with amps" do
+ assert_parses(
+ { "action" => "create_customer", "name" => "Don't & Does"},
+ "action=create_customer&name=Don%27t+%26+Does"
+ )
+ end
+
+ test "query string with many equal" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => "abc=def=ghi"},
+ "action=create_customer&full_name=abc=def=ghi"
+ )
+ end
+
+ test "query string without equal" do
+ assert_parses({"action" => nil}, "action")
+ assert_parses({"action" => {"foo" => nil}}, "action[foo]")
+ assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]")
+ assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar][]")
+ assert_parses({"action" => {"foo" => nil }}, "action[foo][]")
+ assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]")
+ end
+
+ def test_array_parses_without_nil
+ assert_parses({"action" => ['1']}, "action[]=1&action[]")
+ end
+
+ test "perform_deep_munge" do
+ old_perform_deep_munge = ActionDispatch::Request::Utils.perform_deep_munge
+ ActionDispatch::Request::Utils.perform_deep_munge = false
+ begin
+ assert_parses({"action" => nil}, "action")
+ assert_parses({"action" => {"foo" => nil}}, "action[foo]")
+ assert_parses({"action" => {"foo" => {"bar" => nil}}}, "action[foo][bar]")
+ assert_parses({"action" => {"foo" => {"bar" => [nil]}}}, "action[foo][bar][]")
+ assert_parses({"action" => {"foo" => [nil]}}, "action[foo][]")
+ assert_parses({"action" => {"foo" => [{"bar" => nil}]}}, "action[foo][][bar]")
+ assert_parses({"action" => ['1',nil]}, "action[]=1&action[]")
+ ensure
+ ActionDispatch::Request::Utils.perform_deep_munge = old_perform_deep_munge
+ end
+ end
+
+ test "query string with empty key" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
+ "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
+ )
+ end
+
+ test "query string with many ampersands" do
+ assert_parses(
+ { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
+ "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
+ )
+ end
+
+ test "unbalanced query string with array" do
+ assert_parses(
+ {'location' => ["1", "2"], 'age_group' => ["2"]},
+ "location[]=1&location[]=2&age_group[]=2"
+ )
+ end
+
+ test "ambiguous query string returns a bad request" do
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => ::QueryStringParsingTest::TestController
+ end
+
+ get "/parse", nil, "QUERY_STRING" => "foo[]=bar&foo[4]=bar"
+ assert_response :bad_request
+ end
+ end
+
+ private
+ def assert_parses(expected, actual)
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => ::QueryStringParsingTest::TestController
+ end
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use(EarlyParse)
+ end
+
+
+ get "/parse", actual
+ assert_response :ok
+ assert_equal(expected, ::QueryStringParsingTest::TestController.last_query_parameters)
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
new file mode 100644
index 0000000000..10fb04e230
--- /dev/null
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -0,0 +1,116 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ class Request
+ class SessionTest < ActiveSupport::TestCase
+ def test_create_adds_itself_to_env
+ env = {}
+ s = Session.create(store, env, {})
+ assert_equal s, env[Rack::Session::Abstract::ENV_SESSION_KEY]
+ end
+
+ def test_to_hash
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+ assert_equal 'bar', s['foo']
+ assert_equal({'foo' => 'bar'}, s.to_hash)
+ end
+
+ def test_create_merges_old
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+
+ s1 = Session.create(store, env, {})
+ assert_not_equal s, s1
+ assert_equal 'bar', s1['foo']
+ end
+
+ def test_find
+ env = {}
+ assert_nil Session.find(env)
+
+ s = Session.create(store, env, {})
+ assert_equal s, Session.find(env)
+ end
+
+ def test_destroy
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+
+ s.destroy
+
+ assert_empty s
+ end
+
+ def test_keys
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+ s['adequate'] = 'awesome'
+ assert_equal %w[rails adequate], s.keys
+ end
+
+ def test_values
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+ s['adequate'] = 'awesome'
+ assert_equal %w[ftw awesome], s.values
+ end
+
+ def test_clear
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+ s['adequate'] = 'awesome'
+
+ s.clear
+ assert_empty(s.values)
+ end
+
+ def test_update
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+
+ s.update(:rails => 'awesome')
+
+ assert_equal(['rails'], s.keys)
+ assert_equal('awesome', s['rails'])
+ end
+
+ def test_delete
+ s = Session.create(store, {}, {})
+ s['rails'] = 'ftw'
+
+ s.delete('rails')
+
+ assert_empty(s.keys)
+ end
+
+ def test_fetch
+ session = Session.create(store, {}, {})
+
+ session['one'] = '1'
+ assert_equal '1', session.fetch(:one)
+
+ assert_equal '2', session.fetch(:two, '2')
+ assert_nil session.fetch(:two, nil)
+
+ assert_equal 'three', session.fetch(:three) {|el| el.to_s }
+
+ assert_raise KeyError do
+ session.fetch(:three)
+ end
+ end
+
+ private
+ def store
+ Class.new {
+ def load_session(env); [1, {}]; end
+ def session_exists?(env); true; end
+ def destroy_session(env, id, options); 123; end
+ }.new
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
new file mode 100644
index 0000000000..1de05cbf09
--- /dev/null
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -0,0 +1,177 @@
+require 'abstract_unit'
+
+class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ class << self
+ attr_accessor :last_request_parameters, :last_request_type
+ end
+
+ def parse
+ self.class.last_request_parameters = request.request_parameters
+ head :ok
+ end
+ end
+
+ def teardown
+ TestController.last_request_parameters = nil
+ end
+
+ test "parses unbalanced query string with array" do
+ query = "location[]=1&location[]=2&age_group[]=2"
+ expected = { 'location' => ["1", "2"], 'age_group' => ["2"] }
+ assert_parses expected, query
+ end
+
+ test "parses nested hash" do
+ query = [
+ "note[viewers][viewer][][type]=User",
+ "note[viewers][viewer][][id]=1",
+ "note[viewers][viewer][][type]=Group",
+ "note[viewers][viewer][][id]=2"
+ ].join("&")
+ expected = {
+ "note" => {
+ "viewers" => {
+ "viewer" => [
+ { "id" => "1", "type" => "User" },
+ { "type" => "Group", "id" => "2" }
+ ]
+ }
+ }
+ }
+ assert_parses expected, query
+ end
+
+ test "parses more complex nesting" do
+ query = [
+ "customers[boston][first][name]=David",
+ "customers[boston][first][url]=http://David",
+ "customers[boston][second][name]=Allan",
+ "customers[boston][second][url]=http://Allan",
+ "something_else=blah",
+ "something_nil=",
+ "something_empty=",
+ "products[first]=Apple Computer",
+ "products[second]=Pc",
+ "=Save"
+ ].join("&")
+ expected = {
+ "customers" => {
+ "boston" => {
+ "first" => {
+ "name" => "David",
+ "url" => "http://David"
+ },
+ "second" => {
+ "name" => "Allan",
+ "url" => "http://Allan"
+ }
+ }
+ },
+ "something_else" => "blah",
+ "something_empty" => "",
+ "something_nil" => "",
+ "products" => {
+ "first" => "Apple Computer",
+ "second" => "Pc"
+ }
+ }
+ assert_parses expected, query
+ end
+
+ test "parses params with array" do
+ query = "selected[]=1&selected[]=2&selected[]=3"
+ expected = { "selected" => ["1", "2", "3"] }
+ assert_parses expected, query
+ end
+
+ test "parses params with nil key" do
+ query = "=&test2=value1"
+ expected = { "test2" => "value1" }
+ assert_parses expected, query
+ end
+
+ test "parses params with array prefix and hashes" do
+ query = "a[][b][c]=d"
+ expected = { "a" => [{ "b" => { "c" => "d" } }] }
+ assert_parses expected, query
+ end
+
+ test "parses params with complex nesting" do
+ query = "a[][b][c][][d][]=e"
+ expected = { "a" => [{ "b" => { "c" => [{ "d" => ["e"] }] } }] }
+ assert_parses expected, query
+ end
+
+ test "parses params with file path" do
+ query = [
+ "customers[boston][first][name]=David",
+ "something_else=blah",
+ "logo=#{File.expand_path(__FILE__)}"
+ ].join("&")
+ expected = {
+ "customers" => {
+ "boston" => {
+ "first" => {
+ "name" => "David"
+ }
+ }
+ },
+ "something_else" => "blah",
+ "logo" => File.expand_path(__FILE__),
+ }
+ assert_parses expected, query
+ end
+
+ test "parses params with Safari 2 trailing null character" do
+ query = "selected[]=1&selected[]=2&selected[]=3\0"
+ expected = { "selected" => ["1", "2", "3"] }
+ assert_parses expected, query
+ end
+
+ test "ambiguous params returns a bad request" do
+ with_test_routing do
+ post "/parse", "foo[]=bar&foo[4]=bar"
+ assert_response :bad_request
+ end
+ end
+
+ private
+ def with_test_routing
+ with_routing do |set|
+ set.draw do
+ post ':action', to: ::UrlEncodedParamsParsingTest::TestController
+ end
+ yield
+ end
+ end
+
+ def assert_parses(expected, actual)
+ with_test_routing do
+ post "/parse", actual
+ assert_response :ok
+ assert_equal expected, TestController.last_request_parameters
+ assert_utf8 TestController.last_request_parameters
+ end
+ end
+
+ def assert_utf8(object)
+ correct_encoding = Encoding.default_internal
+
+ unless object.is_a?(Hash)
+ assert_equal correct_encoding, object.encoding, "#{object.inspect} should have been UTF-8"
+ return
+ end
+
+ object.each_value do |v|
+ case v
+ when Hash
+ assert_utf8 v
+ when Array
+ v.each { |el| assert_utf8 el }
+ else
+ assert_utf8 v
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
new file mode 100644
index 0000000000..a8050b4fab
--- /dev/null
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -0,0 +1,65 @@
+require 'abstract_unit'
+
+class RequestIdTest < ActiveSupport::TestCase
+ test "passing on the request id from the outside" do
+ assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid
+ end
+
+ test "ensure that only alphanumeric uurids are accepted" do
+ assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid
+ end
+
+ test "ensure that 255 char limit on the request id is being enforced" do
+ assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid
+ end
+
+ test "generating a request id when none is supplied" do
+ assert_match(/\w+-\w+-\w+-\w+-\w+/, stub_request.uuid)
+ end
+
+ private
+
+ def stub_request(env = {})
+ ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env)
+ ActionDispatch::Request.new(env)
+ end
+end
+
+class RequestIdResponseTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def index
+ head :ok
+ end
+ end
+
+ test "request id is passed all the way to the response" do
+ with_test_route_set do
+ get '/'
+ assert_match(/\w+/, @response.headers["X-Request-Id"])
+ end
+ end
+
+ test "request id given on request is passed all the way to the response" do
+ with_test_route_set do
+ get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500
+ assert_equal "X" * 255, @response.headers["X-Request-Id"]
+ end
+ end
+
+
+ private
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ get '/', :to => ::RequestIdResponseTest::TestController.action(:index)
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::RequestId
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
new file mode 100644
index 0000000000..6737609567
--- /dev/null
+++ b/actionpack/test/dispatch/request_test.rb
@@ -0,0 +1,1099 @@
+require 'abstract_unit'
+
+class BaseRequestTest < ActiveSupport::TestCase
+ def setup
+ @env = {
+ :ip_spoofing_check => true,
+ :tld_length => 1,
+ "rack.input" => "foo"
+ }
+ end
+
+ def url_for(options = {})
+ options = { host: 'www.example.com' }.merge!(options)
+ ActionDispatch::Http::URL.url_for(options)
+ end
+
+ protected
+ def stub_request(env = {})
+ ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true
+ @trusted_proxies ||= nil
+ ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies)
+ tld_length = env.key?(:tld_length) ? env.delete(:tld_length) : 1
+ ip_app.call(env)
+ ActionDispatch::Http::URL.tld_length = tld_length
+
+ env = @env.merge(env)
+ ActionDispatch::Request.new(env)
+ end
+end
+
+class RequestUrlFor < BaseRequestTest
+ test "url_for class method" do
+ e = assert_raise(ArgumentError) { url_for(:host => nil) }
+ assert_match(/Please provide the :host parameter/, e.message)
+
+ assert_equal '/books', url_for(:only_path => true, :path => '/books')
+
+ assert_equal 'http://www.example.com/books/?q=code', url_for(trailing_slash: true, path: '/books?q=code')
+ assert_equal 'http://www.example.com/books/?spareslashes=////', url_for(trailing_slash: true, path: '/books?spareslashes=////')
+
+ assert_equal 'http://www.example.com', url_for
+ assert_equal 'http://api.example.com', url_for(:subdomain => 'api')
+ assert_equal 'http://example.com', url_for(:subdomain => false)
+ assert_equal 'http://www.ror.com', url_for(:domain => 'ror.com')
+ assert_equal 'http://api.ror.co.uk', url_for(:host => 'www.ror.co.uk', :subdomain => 'api', :tld_length => 2)
+ assert_equal 'http://www.example.com:8080', url_for(:port => 8080)
+ assert_equal 'https://www.example.com', url_for(:protocol => 'https')
+ assert_equal 'http://www.example.com/docs', url_for(:path => '/docs')
+ assert_equal 'http://www.example.com#signup', url_for(:anchor => 'signup')
+ assert_equal 'http://www.example.com/', url_for(:trailing_slash => true)
+ assert_equal 'http://dhh:supersecret@www.example.com', url_for(:user => 'dhh', :password => 'supersecret')
+ assert_equal 'http://www.example.com?search=books', url_for(:params => { :search => 'books' })
+ assert_equal 'http://www.example.com?params=', url_for(:params => '')
+ assert_equal 'http://www.example.com?params=1', url_for(:params => 1)
+ end
+end
+
+class RequestIP < BaseRequestTest
+ test "remote ip" do
+ request = stub_request 'REMOTE_ADDR' => '1.2.3.4'
+ assert_equal '1.2.3.4', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '1.2.3.4',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '127.0.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,172.16.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,192.168.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,10.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 10.0.0.1, 10.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,127.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
+ assert_equal nil, request.remote_ip
+ end
+
+ test "remote ip spoof detection" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
+ 'HTTP_CLIENT_IP' => '2.2.2.2'
+ e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
+ request.remote_ip
+ }
+ assert_match(/IP spoofing attack/, e.message)
+ assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message)
+ assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message)
+ end
+
+ test "remote ip with spoof detection disabled" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
+ 'HTTP_CLIENT_IP' => '2.2.2.2',
+ :ip_spoofing_check => false
+ assert_equal '1.1.1.1', request.remote_ip
+ end
+
+ test "remote ip spoof protection ignores private addresses" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.17.19.51',
+ 'HTTP_CLIENT_IP' => '172.17.19.51',
+ 'REMOTE_ADDR' => '1.1.1.1',
+ 'HTTP_X_BLUECOAT_VIA' => 'de462e07a2db325e'
+ assert_equal '1.1.1.1', request.remote_ip
+ end
+
+ test "remote ip v6" do
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '::1',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::, fc01::, fdff'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'FE00::, FDFF::'
+ assert_equal 'FE00::', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
+ assert_equal nil, request.remote_ip
+ end
+
+ test "remote ip v6 spoof detection" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
+ 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
+ request.remote_ip
+ }
+ assert_match(/IP spoofing attack/, e.message)
+ assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message)
+ assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message)
+ end
+
+ test "remote ip v6 spoof detection disabled" do
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
+ 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ :ip_spoofing_check => false
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+ end
+
+ test "remote ip with user specified trusted proxies String" do
+ @trusted_proxies = "67.205.106.73"
+
+ request = stub_request 'REMOTE_ADDR' => '3.4.5.6',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
+ assert_equal '67.205.106.73', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73,unknown'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+ end
+
+ test "remote ip v6 with user specified trusted proxies String" do
+ @trusted_proxies = 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '::1', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip
+ end
+
+ test "remote ip with user specified trusted proxies Regexp" do
+ @trusted_proxies = /^67\.205\.106\.73$/i
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+ end
+
+ test "remote ip v6 with user specified trusted proxies Regexp" do
+ @trusted_proxies = /^fe80:0000:0000:0000:0202:b3ff:fe1e:8329$/i
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+ end
+
+ test "remote ip middleware not present still returns an IP" do
+ request = stub_request('REMOTE_ADDR' => '127.0.0.1')
+ assert_equal '127.0.0.1', request.remote_ip
+ end
+end
+
+class RequestDomain < BaseRequestTest
+ test "domains" do
+ request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org'
+ assert_equal "rubyonrails.org", request.domain
+
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk"
+ assert_equal "rubyonrails.co.uk", request.domain(2)
+
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk", :tld_length => 2
+ assert_equal "rubyonrails.co.uk", request.domain
+
+ request = stub_request 'HTTP_HOST' => "192.168.1.200"
+ assert_nil request.domain
+
+ request = stub_request 'HTTP_HOST' => "foo.192.168.1.200"
+ assert_nil request.domain
+
+ request = stub_request 'HTTP_HOST' => "192.168.1.200.com"
+ assert_equal "200.com", request.domain
+ end
+
+ test "subdomains" do
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.org"
+ assert_equal %w( www ), request.subdomains
+ assert_equal "www", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "www.rubyonrails.co.uk"
+ assert_equal %w( www ), request.subdomains(2)
+ assert_equal "www", request.subdomain(2)
+
+ request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk"
+ assert_equal %w( dev www ), request.subdomains(2)
+ assert_equal "dev.www", request.subdomain(2)
+
+ request = stub_request 'HTTP_HOST' => "dev.www.rubyonrails.co.uk", :tld_length => 2
+ assert_equal %w( dev www ), request.subdomains
+ assert_equal "dev.www", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "foobar.foobar.com"
+ assert_equal %w( foobar ), request.subdomains
+ assert_equal "foobar", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "192.168.1.200"
+ assert_equal [], request.subdomains
+ assert_equal "", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "foo.192.168.1.200"
+ assert_equal [], request.subdomains
+ assert_equal "", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => "192.168.1.200.com"
+ assert_equal %w( 192 168 1 ), request.subdomains
+ assert_equal "192.168.1", request.subdomain
+
+ request = stub_request 'HTTP_HOST' => nil
+ assert_equal [], request.subdomains
+ assert_equal "", request.subdomain
+ end
+end
+
+class RequestPort < BaseRequestTest
+ test "standard_port" do
+ request = stub_request
+ assert_equal 80, request.standard_port
+
+ request = stub_request 'HTTPS' => 'on'
+ assert_equal 443, request.standard_port
+ end
+
+ test "standard_port?" do
+ request = stub_request
+ assert !request.ssl?
+ assert request.standard_port?
+
+ request = stub_request 'HTTPS' => 'on'
+ assert request.ssl?
+ assert request.standard_port?
+
+ request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
+ assert !request.ssl?
+ assert !request.standard_port?
+
+ request = stub_request 'HTTP_HOST' => 'www.example.org:8443', 'HTTPS' => 'on'
+ assert request.ssl?
+ assert !request.standard_port?
+ end
+
+ test "optional port" do
+ request = stub_request 'HTTP_HOST' => 'www.example.org:80'
+ assert_equal nil, request.optional_port
+
+ request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
+ assert_equal 8080, request.optional_port
+ end
+
+ test "port string" do
+ request = stub_request 'HTTP_HOST' => 'www.example.org:80'
+ assert_equal '', request.port_string
+
+ request = stub_request 'HTTP_HOST' => 'www.example.org:8080'
+ assert_equal ':8080', request.port_string
+ end
+end
+
+class RequestPath < BaseRequestTest
+ test "full path" do
+ request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri', 'QUERY_STRING' => 'mapped=1'
+ assert_equal "/path/of/some/uri?mapped=1", request.fullpath
+ assert_equal "/path/of/some/uri", request.path_info
+
+ request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/path/of/some/uri'
+ assert_equal "/path/of/some/uri", request.fullpath
+ assert_equal "/path/of/some/uri", request.path_info
+
+ request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/'
+ assert_equal "/", request.fullpath
+ assert_equal "/", request.path_info
+
+ request = stub_request 'SCRIPT_NAME' => '', 'PATH_INFO' => '/', 'QUERY_STRING' => 'm=b'
+ assert_equal "/?m=b", request.fullpath
+ assert_equal "/", request.path_info
+
+ request = stub_request 'SCRIPT_NAME' => '/hieraki', 'PATH_INFO' => '/'
+ assert_equal "/hieraki/", request.fullpath
+ assert_equal "/", request.path_info
+
+ request = stub_request 'SCRIPT_NAME' => '/collaboration/hieraki', 'PATH_INFO' => '/books/edit/2'
+ assert_equal "/collaboration/hieraki/books/edit/2", request.fullpath
+ assert_equal "/books/edit/2", request.path_info
+
+ request = stub_request 'SCRIPT_NAME' => '/path', 'PATH_INFO' => '/of/some/uri', 'QUERY_STRING' => 'mapped=1'
+ assert_equal "/path/of/some/uri?mapped=1", request.fullpath
+ assert_equal "/of/some/uri", request.path_info
+ end
+
+ test "original_fullpath returns ORIGINAL_FULLPATH" do
+ request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar")
+
+ path = request.original_fullpath
+ assert_equal "/foo?bar", path
+ end
+
+ test "original_url returns url built using ORIGINAL_FULLPATH" do
+ request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar",
+ 'HTTP_HOST' => "example.org",
+ 'rack.url_scheme' => "http")
+
+ url = request.original_url
+ assert_equal "http://example.org/foo?bar", url
+ end
+
+ test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do
+ request = stub_request('PATH_INFO' => "/foo",
+ 'QUERY_STRING' => "bar")
+
+ path = request.original_fullpath
+ assert_equal "/foo?bar", path
+ end
+end
+
+class RequestHost < BaseRequestTest
+ test "host with default port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org:80'
+ assert_equal "rubyonrails.org", request.host_with_port
+ end
+
+ test "host with non default port" do
+ request = stub_request 'HTTP_HOST' => 'rubyonrails.org:81'
+ assert_equal "rubyonrails.org:81", request.host_with_port
+ end
+
+ test "proxy request" do
+ request = stub_request 'HTTP_HOST' => 'glu.ttono.us:80'
+ assert_equal "glu.ttono.us", request.host_with_port
+ end
+
+ test "http host" do
+ request = stub_request 'HTTP_HOST' => "rubyonrails.org:8080"
+ assert_equal "rubyonrails.org", request.host
+ assert_equal "rubyonrails.org:8080", request.host_with_port
+
+ request = stub_request 'HTTP_X_FORWARDED_HOST' => "www.firsthost.org, www.secondhost.org"
+ assert_equal "www.secondhost.org", request.host
+ end
+
+ test "http host with default port overrides server port" do
+ request = stub_request 'HTTP_HOST' => "rubyonrails.org"
+ assert_equal "rubyonrails.org", request.host_with_port
+ end
+
+ test "host with port if http standard port is specified" do
+ request = stub_request 'HTTP_X_FORWARDED_HOST' => "glu.ttono.us:80"
+ assert_equal "glu.ttono.us", request.host_with_port
+ end
+
+ test "host with port if https standard port is specified" do
+ request = stub_request(
+ 'HTTP_X_FORWARDED_PROTO' => "https",
+ 'HTTP_X_FORWARDED_HOST' => "glu.ttono.us:443"
+ )
+ assert_equal "glu.ttono.us", request.host_with_port
+ end
+
+ test "host if ipv6 reference" do
+ request = stub_request 'HTTP_HOST' => "[2001:1234:5678:9abc:def0::dead:beef]"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", request.host
+ end
+
+ test "host if ipv6 reference with port" do
+ request = stub_request 'HTTP_HOST' => "[2001:1234:5678:9abc:def0::dead:beef]:8008"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", request.host
+ end
+end
+
+class RequestCGI < BaseRequestTest
+ test "CGI environment variables" do
+ request = stub_request(
+ "AUTH_TYPE" => "Basic",
+ "GATEWAY_INTERFACE" => "CGI/1.1",
+ "HTTP_ACCEPT" => "*/*",
+ "HTTP_ACCEPT_CHARSET" => "UTF-8",
+ "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
+ "HTTP_ACCEPT_LANGUAGE" => "en",
+ "HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
+ "HTTP_FROM" => "googlebot",
+ "HTTP_HOST" => "glu.ttono.us:8007",
+ "HTTP_NEGOTIATE" => "trans",
+ "HTTP_PRAGMA" => "no-cache",
+ "HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
+ "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
+ "PATH_INFO" => "/homepage/",
+ "PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
+ "QUERY_STRING" => "",
+ "REMOTE_ADDR" => "207.7.108.53",
+ "REMOTE_HOST" => "google.com",
+ "REMOTE_IDENT" => "kevin",
+ "REMOTE_USER" => "kevin",
+ "REQUEST_METHOD" => "GET",
+ "SCRIPT_NAME" => "/dispatch.fcgi",
+ "SERVER_NAME" => "glu.ttono.us",
+ "SERVER_PORT" => "8007",
+ "SERVER_PROTOCOL" => "HTTP/1.1",
+ "SERVER_SOFTWARE" => "lighttpd/1.4.5",
+ )
+
+ assert_equal "Basic", request.auth_type
+ assert_equal 0, request.content_length
+ assert_equal nil, request.content_mime_type
+ assert_equal "CGI/1.1", request.gateway_interface
+ assert_equal "*/*", request.accept
+ assert_equal "UTF-8", request.accept_charset
+ assert_equal "gzip, deflate", request.accept_encoding
+ assert_equal "en", request.accept_language
+ assert_equal "no-cache, max-age=0", request.cache_control
+ assert_equal "googlebot", request.from
+ assert_equal "glu.ttono.us", request.host
+ assert_equal "trans", request.negotiate
+ assert_equal "no-cache", request.pragma
+ assert_equal "http://www.google.com/search?q=glu.ttono.us", request.referer
+ assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", request.user_agent
+ assert_equal "/homepage/", request.path_info
+ assert_equal "/home/kevinc/sites/typo/public/homepage/", request.path_translated
+ assert_equal "", request.query_string
+ assert_equal "207.7.108.53", request.remote_addr
+ assert_equal "google.com", request.remote_host
+ assert_equal "kevin", request.remote_ident
+ assert_equal "kevin", request.remote_user
+ assert_equal "GET", request.request_method
+ assert_equal "/dispatch.fcgi", request.script_name
+ assert_equal "glu.ttono.us", request.server_name
+ assert_equal 8007, request.server_port
+ assert_equal "HTTP/1.1", request.server_protocol
+ assert_equal "lighttpd", request.server_software
+ end
+end
+
+class LocalhostTest < BaseRequestTest
+ test "IPs that match localhost" do
+ request = stub_request("REMOTE_IP" => "127.1.1.1", "REMOTE_ADDR" => "127.1.1.1")
+ assert request.local?
+ end
+end
+
+class RequestCookie < BaseRequestTest
+ test "cookie syntax resilience" do
+ request = stub_request("HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes")
+ assert_equal "c84ace84796670c052c6ceb2451fb0f2", request.cookies["_session_id"], request.cookies.inspect
+ assert_equal "yes", request.cookies["is_admin"], request.cookies.inspect
+
+ # some Nokia phone browsers omit the space after the semicolon separator.
+ # some developers have grown accustomed to using comma in cookie values.
+ request = stub_request("HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes")
+ assert_equal "c84ace847", request.cookies["_session_id"], request.cookies.inspect
+ assert_equal "yes", request.cookies["is_admin"], request.cookies.inspect
+ end
+end
+
+class RequestParamsParsing < BaseRequestTest
+ test "doesnt break when content type has charset" do
+ request = stub_request(
+ 'REQUEST_METHOD' => 'POST',
+ 'CONTENT_LENGTH' => "flamenco=love".length,
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8',
+ 'rack.input' => StringIO.new("flamenco=love")
+ )
+
+ assert_equal({"flamenco"=> "love"}, request.request_parameters)
+ end
+
+ test "doesnt interpret request uri as query string when missing" do
+ request = stub_request('REQUEST_URI' => 'foo')
+ assert_equal({}, request.query_parameters)
+ end
+end
+
+class RequestRewind < BaseRequestTest
+ test "body should be rewound" do
+ data = 'rewind'
+ env = {
+ 'rack.input' => StringIO.new(data),
+ 'CONTENT_LENGTH' => data.length,
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded; charset=utf-8'
+ }
+
+ # Read the request body by parsing params.
+ request = stub_request(env)
+ request.request_parameters
+
+ # Should have rewound the body.
+ assert_equal 0, request.body.pos
+ end
+
+ test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do
+ request = stub_request(
+ 'rack.input' => StringIO.new("raw"),
+ 'CONTENT_LENGTH' => 3
+ )
+ assert_equal "raw", request.raw_post
+ assert_equal "raw", request.env['rack.input'].read
+ end
+end
+
+class RequestProtocol < BaseRequestTest
+ test "server software" do
+ assert_equal 'lighttpd', stub_request('SERVER_SOFTWARE' => 'lighttpd/1.4.5').server_software
+ assert_equal 'apache', stub_request('SERVER_SOFTWARE' => 'Apache3.422').server_software
+ end
+
+ test "xml http request" do
+ request = stub_request
+
+ assert !request.xml_http_request?
+ assert !request.xhr?
+
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => 'DefinitelyNotAjax1.0'
+ assert !request.xml_http_request?
+ assert !request.xhr?
+
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'
+ assert request.xml_http_request?
+ assert request.xhr?
+ end
+
+ test "reports ssl" do
+ assert !stub_request.ssl?
+ assert stub_request('HTTPS' => 'on').ssl?
+ end
+
+ test "reports ssl when proxied via lighttpd" do
+ assert stub_request('HTTP_X_FORWARDED_PROTO' => 'https').ssl?
+ end
+
+ test "scheme returns https when proxied" do
+ request = stub_request 'rack.url_scheme' => 'http'
+ assert !request.ssl?
+ assert_equal 'http', request.scheme
+
+ request = stub_request(
+ 'rack.url_scheme' => 'http',
+ 'HTTP_X_FORWARDED_PROTO' => 'https'
+ )
+ assert request.ssl?
+ assert_equal 'https', request.scheme
+ end
+end
+
+class RequestMethod < BaseRequestTest
+ test "method returns environment's request method when it has not been
+ overriden by middleware".squish do
+
+ ActionDispatch::Request::HTTP_METHODS.each do |method|
+ request = stub_request('REQUEST_METHOD' => method)
+
+ assert_equal method, request.method
+ assert_equal method.underscore.to_sym, request.method_symbol
+ end
+ end
+
+ test "invalid http method raises exception" do
+ assert_raise(ActionController::UnknownHttpMethod) do
+ stub_request('REQUEST_METHOD' => 'RANDOM_METHOD').request_method
+ end
+ end
+
+ test "method returns original value of environment request method on POST" do
+ request = stub_request('rack.methodoverride.original_method' => 'POST')
+ assert_equal 'POST', request.method
+ end
+
+ test "method raises exception on invalid HTTP method" do
+ assert_raise(ActionController::UnknownHttpMethod) do
+ stub_request('rack.methodoverride.original_method' => '_RANDOM_METHOD').method
+ end
+
+ assert_raise(ActionController::UnknownHttpMethod) do
+ stub_request('REQUEST_METHOD' => '_RANDOM_METHOD').method
+ end
+ end
+
+ test "post masquerading as patch" do
+ request = stub_request(
+ 'REQUEST_METHOD' => 'PATCH',
+ "rack.methodoverride.original_method" => "POST"
+ )
+
+ assert_equal "POST", request.method
+ assert_equal "PATCH", request.request_method
+ assert request.patch?
+ end
+
+ test "post masquerading as put" do
+ request = stub_request(
+ 'REQUEST_METHOD' => 'PUT',
+ "rack.methodoverride.original_method" => "POST"
+ )
+ assert_equal "POST", request.method
+ assert_equal "PUT", request.request_method
+ assert request.put?
+ end
+
+ test "post uneffected by local inflections" do
+ existing_acrnoyms = ActiveSupport::Inflector.inflections.acronyms.dup
+ existing_acrnoym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup
+ begin
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.acronym "POS"
+ end
+ assert_equal "pos_t", "POST".underscore
+ request = stub_request "REQUEST_METHOD" => "POST"
+ assert_equal :post, ActionDispatch::Request::HTTP_METHOD_LOOKUP["POST"]
+ assert_equal :post, request.method_symbol
+ assert request.post?
+ ensure
+ # Reset original acronym set
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.send(:instance_variable_set,"@acronyms",existing_acrnoyms)
+ inflect.send(:instance_variable_set,"@acronym_regex",existing_acrnoym_regex)
+ end
+ end
+ end
+end
+
+class RequestFormat < BaseRequestTest
+ test "xml format" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => 'xml' })
+ assert_equal Mime::XML, request.format
+ end
+
+ test "xhtml format" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => 'xhtml' })
+ assert_equal Mime::HTML, request.format
+ end
+
+ test "txt format" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => 'txt' })
+ assert_equal Mime::TEXT, request.format
+ end
+
+ test "XMLHttpRequest" do
+ request = stub_request(
+ 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
+ 'HTTP_ACCEPT' => [Mime::JS, Mime::HTML, Mime::XML, "text/xml", Mime::ALL].join(",")
+ )
+ request.expects(:parameters).at_least_once.returns({})
+ assert request.xhr?
+ assert_equal Mime::JS, request.format
+ end
+
+ test "can override format with parameter negative" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => :txt })
+ assert !request.format.xml?
+ end
+
+ test "can override format with parameter positive" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => :xml })
+ assert request.format.xml?
+ end
+
+ test "formats text/html with accept header" do
+ request = stub_request 'HTTP_ACCEPT' => 'text/html'
+ assert_equal [Mime::HTML], request.formats
+ end
+
+ test "formats blank with accept header" do
+ request = stub_request 'HTTP_ACCEPT' => ''
+ assert_equal [Mime::HTML], request.formats
+ end
+
+ test "formats XMLHttpRequest with accept header" do
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ assert_equal [Mime::JS], request.formats
+ end
+
+ test "formats application/xml with accept header" do
+ request = stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest")
+ assert_equal [Mime::XML], request.formats
+ end
+
+ test "formats format:text with accept header" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => :txt })
+ assert_equal [Mime::TEXT], request.formats
+ end
+
+ test "formats format:unknown with accept header" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ :format => :unknown })
+ assert_instance_of Mime::NullType, request.format
+ end
+
+ test "format is not nil with unknown format" do
+ request = stub_request
+ request.expects(:parameters).at_least_once.returns({ format: :hello })
+ assert request.format.nil?
+ assert_not request.format.html?
+ assert_not request.format.xml?
+ assert_not request.format.json?
+ end
+
+ test "format does not throw exceptions when malformed parameters" do
+ request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
+ assert request.formats
+ assert request.format.html?
+ end
+
+ test "formats with xhr request" do
+ request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [Mime::JS], request.formats
+ end
+
+ test "ignore_accept_header" do
+ old_ignore_accept_header = ActionDispatch::Request.ignore_accept_header
+ ActionDispatch::Request.ignore_accept_header = true
+
+ begin
+ request = stub_request 'HTTP_ACCEPT' => 'application/xml'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'application/jxw'
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::HTML ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'application/xml',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({})
+ assert_equal [ Mime::JS ], request.formats
+
+ request = stub_request 'HTTP_ACCEPT' => 'application/xml',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ request.expects(:parameters).at_least_once.returns({:format => :json})
+ assert_equal [ Mime::JSON ], request.formats
+ ensure
+ ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header
+ end
+ end
+end
+
+class RequestMimeType < BaseRequestTest
+ test "content type" do
+ assert_equal Mime::HTML, stub_request('CONTENT_TYPE' => 'text/html').content_mime_type
+ end
+
+ test "no content type" do
+ assert_equal nil, stub_request.content_mime_type
+ end
+
+ test "content type is XML" do
+ assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml').content_mime_type
+ end
+
+ test "content type with charset" do
+ assert_equal Mime::XML, stub_request('CONTENT_TYPE' => 'application/xml; charset=UTF-8').content_mime_type
+ end
+
+ test "user agent" do
+ assert_equal 'TestAgent', stub_request('HTTP_USER_AGENT' => 'TestAgent').user_agent
+ end
+
+ test "negotiate_mime" do
+ request = stub_request(
+ 'HTTP_ACCEPT' => 'text/html',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ )
+
+ assert_equal nil, request.negotiate_mime([Mime::XML, Mime::JSON])
+ assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::HTML])
+ assert_equal Mime::HTML, request.negotiate_mime([Mime::XML, Mime::ALL])
+ end
+
+ test "negotiate_mime with content_type" do
+ request = stub_request(
+ 'CONTENT_TYPE' => 'application/xml; charset=UTF-8',
+ 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest"
+ )
+
+ assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV])
+ end
+end
+
+class RequestParameters < BaseRequestTest
+ test "parameters" do
+ request = stub_request
+ request.expects(:request_parameters).at_least_once.returns({ "foo" => 1 })
+ request.expects(:query_parameters).at_least_once.returns({ "bar" => 2 })
+
+ assert_equal({"foo" => 1, "bar" => 2}, request.parameters)
+ assert_equal({"foo" => 1}, request.request_parameters)
+ assert_equal({"bar" => 2}, request.query_parameters)
+ end
+
+ test "parameters not accessible after rack parse error" do
+ request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
+
+ 2.times do
+ assert_raises(ActionController::BadRequest) do
+ # rack will raise a TypeError when parsing this query string
+ request.parameters
+ end
+ end
+ end
+
+ test "we have access to the original exception" do
+ request = stub_request("QUERY_STRING" => "x[y]=1&x[y][][w]=2")
+
+ e = assert_raises(ActionController::BadRequest) do
+ # rack will raise a TypeError when parsing this query string
+ request.parameters
+ end
+
+ assert e.original_exception
+ assert_equal e.original_exception.backtrace, e.backtrace
+ end
+end
+
+
+class RequestParameterFilter < BaseRequestTest
+ test "process parameter filter" do
+ test_hashes = [
+ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
+ [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
+ [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
+ [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
+ [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
+ [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
+ [{'baz'=>[{'foo'=>'baz'}, "1"]}, {'baz'=>[{'foo'=>'[FILTERED]'}, "1"]}, [/foo/]]]
+
+ test_hashes.each do |before_filter, after_filter, filter_words|
+ parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
+ assert_equal after_filter, parameter_filter.filter(before_filter)
+
+ filter_words << 'blah'
+ filter_words << lambda { |key, value|
+ value.reverse! if key =~ /bargain/
+ }
+
+ parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
+ before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
+ after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
+
+ assert_equal after_filter, parameter_filter.filter(before_filter)
+ end
+ end
+
+ test "filtered_parameters returns params filtered" do
+ request = stub_request(
+ 'action_dispatch.request.parameters' => {
+ 'lifo' => 'Pratik',
+ 'amount' => '420',
+ 'step' => '1'
+ },
+ 'action_dispatch.parameter_filter' => [:lifo, :amount]
+ )
+
+ params = request.filtered_parameters
+ assert_equal "[FILTERED]", params["lifo"]
+ assert_equal "[FILTERED]", params["amount"]
+ assert_equal "1", params["step"]
+ end
+
+ test "filtered_env filters env as a whole" do
+ request = stub_request(
+ 'action_dispatch.request.parameters' => {
+ 'amount' => '420',
+ 'step' => '1'
+ },
+ "RAW_POST_DATA" => "yada yada",
+ 'action_dispatch.parameter_filter' => [:lifo, :amount]
+ )
+ request = stub_request(request.filtered_env)
+
+ assert_equal "[FILTERED]", request.raw_post
+ assert_equal "[FILTERED]", request.params["amount"]
+ assert_equal "1", request.params["step"]
+ end
+
+ test "filtered_path returns path with filtered query string" do
+ %w(; &).each do |sep|
+ request = stub_request(
+ 'QUERY_STRING' => %w(username=sikachu secret=bd4f21f api_key=b1bc3b3cd352f68d79d7).join(sep),
+ 'PATH_INFO' => '/authenticate',
+ 'action_dispatch.parameter_filter' => [:secret, :api_key]
+ )
+
+ path = request.filtered_path
+ assert_equal %w(/authenticate?username=sikachu secret=[FILTERED] api_key=[FILTERED]).join(sep), path
+ end
+ end
+
+ test "filtered_path should not unescape a genuine '[FILTERED]' value" do
+ request = stub_request(
+ 'QUERY_STRING' => "secret=bd4f21f&genuine=%5BFILTERED%5D",
+ 'PATH_INFO' => '/authenticate',
+ 'action_dispatch.parameter_filter' => [:secret]
+ )
+
+ path = request.filtered_path
+ assert_equal request.script_name + "/authenticate?secret=[FILTERED]&genuine=%5BFILTERED%5D", path
+ end
+
+ test "filtered_path should preserve duplication of keys in query string" do
+ request = stub_request(
+ 'QUERY_STRING' => "username=sikachu&secret=bd4f21f&username=fxn",
+ 'PATH_INFO' => '/authenticate',
+ 'action_dispatch.parameter_filter' => [:secret]
+ )
+
+ path = request.filtered_path
+ assert_equal request.script_name + "/authenticate?username=sikachu&secret=[FILTERED]&username=fxn", path
+ end
+
+ test "filtered_path should ignore searchparts" do
+ request = stub_request(
+ 'QUERY_STRING' => "secret",
+ 'PATH_INFO' => '/authenticate',
+ 'action_dispatch.parameter_filter' => [:secret]
+ )
+
+ path = request.filtered_path
+ assert_equal request.script_name + "/authenticate?secret", path
+ end
+end
+
+class RequestEtag < BaseRequestTest
+ test "if_none_match_etags none" do
+ request = stub_request
+
+ assert_equal nil, request.if_none_match
+ assert_equal [], request.if_none_match_etags
+ assert !request.etag_matches?("foo")
+ assert !request.etag_matches?(nil)
+ end
+
+ test "if_none_match_etags single" do
+ header = 'the-etag'
+ request = stub_request('HTTP_IF_NONE_MATCH' => header)
+
+ assert_equal header, request.if_none_match
+ assert_equal [header], request.if_none_match_etags
+ assert request.etag_matches?("the-etag")
+ end
+
+ test "if_none_match_etags quoted single" do
+ header = '"the-etag"'
+ request = stub_request('HTTP_IF_NONE_MATCH' => header)
+
+ assert_equal header, request.if_none_match
+ assert_equal ['the-etag'], request.if_none_match_etags
+ assert request.etag_matches?("the-etag")
+ end
+
+ test "if_none_match_etags multiple" do
+ header = 'etag1, etag2, "third etag", "etag4"'
+ expected = ['etag1', 'etag2', 'third etag', 'etag4']
+ request = stub_request('HTTP_IF_NONE_MATCH' => header)
+
+ assert_equal header, request.if_none_match
+ assert_equal expected, request.if_none_match_etags
+ expected.each do |etag|
+ assert request.etag_matches?(etag), etag
+ end
+ end
+end
+
+class RequestVariant < BaseRequestTest
+ test "setting variant" do
+ request = stub_request
+
+ request.variant = :mobile
+ assert_equal [:mobile], request.variant
+
+ request.variant = [:phone, :tablet]
+ assert_equal [:phone, :tablet], request.variant
+
+ assert_raise ArgumentError do
+ request.variant = [:phone, "tablet"]
+ end
+
+ assert_raise ArgumentError do
+ request.variant = "yolo"
+ end
+ end
+
+ test "setting variant with non symbol value" do
+ request = stub_request
+ assert_raise ArgumentError do
+ request.variant = "mobile"
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
new file mode 100644
index 0000000000..187b9a2420
--- /dev/null
+++ b/actionpack/test/dispatch/response_test.rb
@@ -0,0 +1,316 @@
+require 'abstract_unit'
+
+class ResponseTest < ActiveSupport::TestCase
+ def setup
+ @response = ActionDispatch::Response.new
+ end
+
+ def test_can_wait_until_commit
+ t = Thread.new {
+ @response.await_commit
+ }
+ @response.commit!
+ assert @response.committed?
+ assert t.join(0.5)
+ end
+
+ def test_stream_close
+ @response.stream.close
+ assert @response.stream.closed?
+ end
+
+ def test_stream_write
+ @response.stream.write "foo"
+ @response.stream.close
+ assert_equal "foo", @response.body
+ end
+
+ def test_write_after_close
+ @response.stream.close
+
+ e = assert_raises(IOError) do
+ @response.stream.write "omg"
+ end
+ assert_equal "closed stream", e.message
+ end
+
+ def test_response_body_encoding
+ body = ["hello".encode(Encoding::UTF_8)]
+ response = ActionDispatch::Response.new 200, {}, body
+ assert_equal Encoding::UTF_8, response.body.encoding
+ end
+
+ test "simple output" do
+ @response.body = "Hello, World!"
+
+ status, headers, body = @response.to_a
+ assert_equal 200, status
+ assert_equal({
+ "Content-Type" => "text/html; charset=utf-8"
+ }, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["Hello, World!"], parts
+ end
+
+ test "status handled properly in initialize" do
+ assert_equal 200, ActionDispatch::Response.new('200 OK').status
+ end
+
+ test "utf8 output" do
+ @response.body = [1090, 1077, 1089, 1090].pack("U*")
+
+ status, headers, _ = @response.to_a
+ assert_equal 200, status
+ assert_equal({
+ "Content-Type" => "text/html; charset=utf-8"
+ }, headers)
+ end
+
+ test "content type" do
+ [204, 304].each do |c|
+ @response.status = c.to_s
+ _, headers, _ = @response.to_a
+ assert !headers.has_key?("Content-Type"), "#{c} should not have Content-Type header"
+ end
+
+ [200, 302, 404, 500].each do |c|
+ @response.status = c.to_s
+ _, headers, _ = @response.to_a
+ assert headers.has_key?("Content-Type"), "#{c} did not have Content-Type header"
+ end
+ end
+
+ test "does not include Status header" do
+ @response.status = "200 OK"
+ _, headers, _ = @response.to_a
+ assert !headers.has_key?('Status')
+ end
+
+ test "response code" do
+ @response.status = "200 OK"
+ assert_equal 200, @response.response_code
+
+ @response.status = "200"
+ assert_equal 200, @response.response_code
+
+ @response.status = 200
+ assert_equal 200, @response.response_code
+ end
+
+ test "code" do
+ @response.status = "200 OK"
+ assert_equal "200", @response.code
+
+ @response.status = "200"
+ assert_equal "200", @response.code
+
+ @response.status = 200
+ assert_equal "200", @response.code
+ end
+
+ test "message" do
+ @response.status = "200 OK"
+ assert_equal "OK", @response.message
+
+ @response.status = "200"
+ assert_equal "OK", @response.message
+
+ @response.status = 200
+ assert_equal "OK", @response.message
+ end
+
+ test "cookies" do
+ @response.set_cookie("user_name", :value => "david", :path => "/")
+ status, headers, body = @response.to_a
+ assert_equal "user_name=david; path=/", headers["Set-Cookie"]
+ assert_equal({"user_name" => "david"}, @response.cookies)
+
+ @response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
+ status, headers, body = @response.to_a
+ assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"]
+ assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies)
+
+ @response.delete_cookie("login")
+ status, headers, body = @response.to_a
+ assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
+ end
+
+ test "read cache control" do
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.cache_control[:public] = true
+ response.etag = '123'
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
+ assert_equal({:public => true}, resp.cache_control)
+
+ assert_equal('public', resp.headers['Cache-Control'])
+ assert_equal('"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
+ end
+
+ test "read charset and content type" do
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.charset = 'utf-16'
+ response.content_type = Mime::XML
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert_equal('utf-16', resp.charset)
+ assert_equal(Mime::XML, resp.content_type)
+
+ assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type'])
+ end
+
+ test "read content type without charset" do
+ original = ActionDispatch::Response.default_charset
+ begin
+ ActionDispatch::Response.default_charset = 'utf-16'
+ resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" })
+ assert_equal('utf-16', resp.charset)
+ ensure
+ ActionDispatch::Response.default_charset = original
+ end
+ end
+
+ test "read x_frame_options, x_content_type_options and x_xss_protection" do
+ original_default_headers = ActionDispatch::Response.default_headers
+ begin
+ ActionDispatch::Response.default_headers = {
+ 'X-Frame-Options' => 'DENY',
+ 'X-Content-Type-Options' => 'nosniff',
+ 'X-XSS-Protection' => '1;'
+ }
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert_equal('DENY', resp.headers['X-Frame-Options'])
+ assert_equal('nosniff', resp.headers['X-Content-Type-Options'])
+ assert_equal('1;', resp.headers['X-XSS-Protection'])
+ ensure
+ ActionDispatch::Response.default_headers = original_default_headers
+ end
+ end
+
+ test "read custom default_header" do
+ original_default_headers = ActionDispatch::Response.default_headers
+ begin
+ ActionDispatch::Response.default_headers = {
+ 'X-XX-XXXX' => 'Here is my phone number'
+ }
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.body = 'Hello'
+ }
+ resp.to_a
+
+ assert_equal('Here is my phone number', resp.headers['X-XX-XXXX'])
+ ensure
+ ActionDispatch::Response.default_headers = original_default_headers
+ end
+ end
+
+ test "respond_to? accepts include_private" do
+ assert_not @response.respond_to?(:method_missing)
+ assert @response.respond_to?(:method_missing, true)
+ end
+
+ test "can be destructured into status, headers and an enumerable body" do
+ response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
+ status, headers, body = response
+
+ assert_equal 404, status
+ assert_equal({ 'Content-Type' => 'text/plain' }, headers)
+ assert_equal ['Not Found'], body.each.to_a
+ end
+
+ test "[response].flatten does not recurse infinitely" do
+ Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
+ status, headers, body = [@response].flatten
+ assert_equal @response.status, status
+ assert_equal @response.headers, headers
+ assert_equal @response.body, body.each.to_a.join
+ end
+ end
+end
+
+class ResponseIntegrationTest < ActionDispatch::IntegrationTest
+ def app
+ @app
+ end
+
+ test "response cache control from railsish app" do
+ @app = lambda { |env|
+ ActionDispatch::Response.new.tap { |resp|
+ resp.cache_control[:public] = true
+ resp.etag = '123'
+ resp.body = 'Hello'
+ }.to_a
+ }
+
+ get '/'
+ assert_response :success
+
+ assert_equal('public', @response.headers['Cache-Control'])
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal({:public => true}, @response.cache_control)
+ end
+
+ test "response cache control from rackish app" do
+ @app = lambda { |env|
+ [200,
+ {'ETag' => '"202cb962ac59075b964b07152d234b70"',
+ 'Cache-Control' => 'public'}, ['Hello']]
+ }
+
+ get '/'
+ assert_response :success
+
+ assert_equal('public', @response.headers['Cache-Control'])
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+
+ assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal({:public => true}, @response.cache_control)
+ end
+
+ test "response charset and content type from railsish app" do
+ @app = lambda { |env|
+ ActionDispatch::Response.new.tap { |resp|
+ resp.charset = 'utf-16'
+ resp.content_type = Mime::XML
+ resp.body = 'Hello'
+ }.to_a
+ }
+
+ get '/'
+ assert_response :success
+
+ assert_equal('utf-16', @response.charset)
+ assert_equal(Mime::XML, @response.content_type)
+
+ assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
+ end
+
+ test "response charset and content type from rackish app" do
+ @app = lambda { |env|
+ [200,
+ {'Content-Type' => 'application/xml; charset=utf-16'},
+ ['Hello']]
+ }
+
+ get '/'
+ assert_response :success
+
+ assert_equal('utf-16', @response.charset)
+ assert_equal(Mime::XML, @response.content_type)
+
+ assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
+ end
+end
diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb
new file mode 100644
index 0000000000..7ef513b0c8
--- /dev/null
+++ b/actionpack/test/dispatch/routing/concerns_test.rb
@@ -0,0 +1,120 @@
+require 'abstract_unit'
+
+class RoutingConcernsTest < ActionDispatch::IntegrationTest
+ class Reviewable
+ def self.call(mapper, options = {})
+ mapper.resources :reviews, options
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ concern :commentable do |options|
+ resources :comments, options
+ end
+
+ concern :image_attachable do
+ resources :images, only: :index
+ end
+
+ concern :reviewable, Reviewable
+
+ resources :posts, concerns: [:commentable, :image_attachable, :reviewable] do
+ resource :video, concerns: :commentable do
+ concerns :reviewable, as: :video_reviews
+ end
+ end
+
+ resource :picture, concerns: :commentable do
+ resources :posts, concerns: :commentable
+ end
+
+ scope "/videos" do
+ concerns :commentable, except: :destroy
+ end
+ end
+ end
+
+ include Routes.url_helpers
+ APP = RoutedRackApp.new Routes
+ def app; APP end
+
+ def test_accessing_concern_from_resources
+ get "/posts/1/comments"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/comments", post_comments_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_resource
+ get "/picture/comments"
+ assert_equal "200", @response.code
+ assert_equal "/picture/comments", picture_comments_path
+ end
+
+ def test_accessing_concern_from_nested_resource
+ get "/posts/1/video/comments"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/video/comments", post_video_comments_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_nested_resources
+ get "/picture/posts/1/comments"
+ assert_equal "200", @response.code
+ assert_equal "/picture/posts/1/comments", picture_post_comments_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_resources_with_more_than_one_concern
+ get "/posts/1/images"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/images", post_images_path(post_id: 1)
+ end
+
+ def test_accessing_concern_from_resources_using_only_option
+ get "/posts/1/image/1"
+ assert_equal "404", @response.code
+ end
+
+ def test_accessing_callable_concern_
+ get "/posts/1/reviews/1"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/reviews/1", post_review_path(post_id: 1, id: 1)
+ end
+
+ def test_callable_concerns_accept_options
+ get "/posts/1/video/reviews/1"
+ assert_equal "200", @response.code
+ assert_equal "/posts/1/video/reviews/1", post_video_video_review_path(post_id: 1, id: 1)
+ end
+
+ def test_accessing_concern_from_a_scope
+ get "/videos/comments"
+ assert_equal "200", @response.code
+ end
+
+ def test_concerns_accept_options
+ delete "/videos/comments/1"
+ assert_equal "404", @response.code
+ end
+
+ def test_with_an_invalid_concern_name
+ e = assert_raise ArgumentError do
+ ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ resources :posts, concerns: :foo
+ end
+ end
+ end
+
+ assert_equal "No concern named foo was found!", e.message
+ end
+
+ def test_concerns_executes_block_in_context_of_current_mapper
+ mapper = ActionDispatch::Routing::Mapper.new(ActionDispatch::Routing::RouteSet.new)
+ mapper.concern :test_concern do
+ resources :things
+ return self
+ end
+
+ assert_equal mapper, mapper.concerns(:test_concern)
+ end
+end
diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb
new file mode 100644
index 0000000000..ff33dd5652
--- /dev/null
+++ b/actionpack/test/dispatch/routing/inspector_test.rb
@@ -0,0 +1,304 @@
+require 'abstract_unit'
+require 'rails/engine'
+require 'action_dispatch/routing/inspector'
+
+module ActionDispatch
+ module Routing
+ class RoutesInspectorTest < ActiveSupport::TestCase
+ def setup
+ @set = ActionDispatch::Routing::RouteSet.new
+ app = ActiveSupport::OrderedOptions.new
+ app.config = ActiveSupport::OrderedOptions.new
+ app.config.assets = ActiveSupport::OrderedOptions.new
+ app.config.assets.prefix = '/sprockets'
+ Rails.stubs(:application).returns(app)
+ Rails.stubs(:env).returns("development")
+ end
+
+ def draw(options = {}, &block)
+ @set.draw(&block)
+ inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes)
+ inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n")
+ end
+
+ def test_json_regexp_converter
+ @set.draw do
+ get '/cart', :to => 'cart#show'
+ end
+ route = ActionDispatch::Routing::RouteWrapper.new(@set.routes.first)
+ assert_equal "^\\/cart(?:\\.([^\\/.?]+))?$", route.json_regexp
+ end
+
+ def test_displaying_routes_for_engines
+ engine = Class.new(Rails::Engine) do
+ def self.inspect
+ "Blog::Engine"
+ end
+ end
+ engine.routes.draw do
+ get '/cart', :to => 'cart#show'
+ end
+
+ output = draw do
+ get '/custom/assets', :to => 'custom_assets#show'
+ mount engine => "/blog", :as => "blog"
+ end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ "custom_assets GET /custom/assets(.:format) custom_assets#show",
+ " blog /blog Blog::Engine",
+ "",
+ "Routes for Blog::Engine:",
+ " cart GET /cart(.:format) cart#show"
+ ], output
+ end
+
+ def test_displaying_routes_for_engines_without_routes
+ engine = Class.new(Rails::Engine) do
+ def self.inspect
+ "Blog::Engine"
+ end
+ end
+ engine.routes.draw do
+ end
+
+ output = draw do
+ mount engine => "/blog", as: "blog"
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " blog /blog Blog::Engine",
+ "",
+ "Routes for Blog::Engine:"
+ ], output
+ end
+
+ def test_cart_inspect
+ output = draw do
+ get '/cart', :to => 'cart#show'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " cart GET /cart(.:format) cart#show"
+ ], output
+ end
+
+ def test_inspect_shows_custom_assets
+ output = draw do
+ get '/custom/assets', :to => 'custom_assets#show'
+ end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ "custom_assets GET /custom/assets(.:format) custom_assets#show"
+ ], output
+ end
+
+ def test_inspect_routes_shows_resources_route
+ output = draw do
+ resources :articles
+ end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ " articles GET /articles(.:format) articles#index",
+ " POST /articles(.:format) articles#create",
+ " new_article GET /articles/new(.:format) articles#new",
+ "edit_article GET /articles/:id/edit(.:format) articles#edit",
+ " article GET /articles/:id(.:format) articles#show",
+ " PATCH /articles/:id(.:format) articles#update",
+ " PUT /articles/:id(.:format) articles#update",
+ " DELETE /articles/:id(.:format) articles#destroy"
+ ], output
+ end
+
+ def test_inspect_routes_shows_root_route
+ output = draw do
+ root :to => 'pages#main'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " root GET / pages#main"
+ ], output
+ end
+
+ def test_inspect_routes_shows_dynamic_action_route
+ output = draw do
+ get 'api/:action' => 'api'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /api/:action(.:format) api#:action"
+ ], output
+ end
+
+ def test_inspect_routes_shows_controller_and_action_only_route
+ output = draw do
+ get ':controller/:action'
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /:controller/:action(.:format) :controller#:action"
+ ], output
+ end
+
+ def test_inspect_routes_shows_controller_and_action_route_with_constraints
+ output = draw do
+ get ':controller(/:action(/:id))', :id => /\d+/
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"
+ ], output
+ end
+
+ def test_rake_routes_shows_route_with_defaults
+ output = draw do
+ get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ %Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]
+ ], output
+ end
+
+ def test_rake_routes_shows_route_with_constraints
+ output = draw do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"
+ ], output
+ end
+
+ def test_rake_routes_shows_routes_with_dashes
+ output = draw do
+ get 'about-us' => 'pages#about_us'
+ get 'our-work/latest'
+
+ resources :photos, only: [:show] do
+ get 'user-favorites', on: :collection
+ get 'preview-photo', on: :member
+ get 'summary-text'
+ end
+ end
+
+ assert_equal [
+ " Prefix Verb URI Pattern Controller#Action",
+ " about_us GET /about-us(.:format) pages#about_us",
+ " our_work_latest GET /our-work/latest(.:format) our_work#latest",
+ "user_favorites_photos GET /photos/user-favorites(.:format) photos#user_favorites",
+ " preview_photo_photo GET /photos/:id/preview-photo(.:format) photos#preview_photo",
+ " photo_summary_text GET /photos/:photo_id/summary-text(.:format) photos#summary_text",
+ " photo GET /photos/:id(.:format) photos#show"
+ ], output
+ end
+
+ class RackApp
+ def self.call(env)
+ end
+ end
+
+ def test_rake_routes_shows_route_with_rack_app
+ output = draw do
+ get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"
+ ], output
+ end
+
+ def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints
+ constraint = Class.new do
+ def inspect
+ "( my custom constraint )"
+ end
+ end
+
+ output = draw do
+ scope :constraint => constraint.new do
+ mount RackApp => '/foo'
+ end
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " /foo #{RackApp.name} {:constraint=>( my custom constraint )}"
+ ], output
+ end
+
+ def test_rake_routes_dont_show_app_mounted_in_assets_prefix
+ output = draw do
+ get '/sprockets' => RackApp
+ end
+ assert_no_match(/RackApp/, output.first)
+ assert_no_match(/\/sprockets/, output.first)
+ end
+
+ def test_rake_routes_shows_route_defined_in_under_assets_prefix
+ output = draw do
+ scope '/sprockets' do
+ get '/foo' => 'foo#bar'
+ end
+ end
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " foo GET /sprockets/foo(.:format) foo#bar"
+ ], output
+ end
+
+ def test_redirect
+ output = draw do
+ get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
+ get "/bar" => redirect(path: "/foo/bar", status: 307)
+ get "/foobar" => redirect{ "/foo/bar" }
+ end
+
+ assert_equal [
+ "Prefix Verb URI Pattern Controller#Action",
+ " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}",
+ " bar GET /bar(.:format) redirect(307, path: /foo/bar)",
+ "foobar GET /foobar(.:format) redirect(301)"
+ ], output
+ end
+
+ def test_routes_can_be_filtered
+ output = draw(filter: 'posts') do
+ resources :articles
+ resources :posts
+ end
+
+ assert_equal [" Prefix Verb URI Pattern Controller#Action",
+ " posts GET /posts(.:format) posts#index",
+ " POST /posts(.:format) posts#create",
+ " new_post GET /posts/new(.:format) posts#new",
+ "edit_post GET /posts/:id/edit(.:format) posts#edit",
+ " post GET /posts/:id(.:format) posts#show",
+ " PATCH /posts/:id(.:format) posts#update",
+ " PUT /posts/:id(.:format) posts#update",
+ " DELETE /posts/:id(.:format) posts#destroy"], output
+ end
+
+ def test_regression_route_with_controller_regexp
+ output = draw do
+ get ':controller(/:action)', controller: /api\/[^\/]+/, format: false
+ end
+
+ assert_equal ["Prefix Verb URI Pattern Controller#Action",
+ " GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb
new file mode 100644
index 0000000000..c465d56bde
--- /dev/null
+++ b/actionpack/test/dispatch/routing/route_set_test.rb
@@ -0,0 +1,93 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class RouteSetTest < ActiveSupport::TestCase
+ class SimpleApp
+ def initialize(response)
+ @response = response
+ end
+
+ def call(env)
+ [ 200, { 'Content-Type' => 'text/plain' }, [response] ]
+ end
+ end
+
+ setup do
+ @set = RouteSet.new
+ end
+
+ test "url helpers are added when route is added" do
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_raises NoMethodError do
+ assert_equal '/bar', url_helpers.bar_path
+ end
+
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ get 'bar', to: SimpleApp.new('bar#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_equal '/bar', url_helpers.bar_path
+ end
+
+ test "url helpers are updated when route is updated" do
+ draw do
+ get 'bar', to: SimpleApp.new('bar#index'), as: :bar
+ end
+
+ assert_equal '/bar', url_helpers.bar_path
+
+ draw do
+ get 'baz', to: SimpleApp.new('baz#index'), as: :bar
+ end
+
+ assert_equal '/baz', url_helpers.bar_path
+ end
+
+ test "url helpers are removed when route is removed" do
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ get 'bar', to: SimpleApp.new('bar#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_equal '/bar', url_helpers.bar_path
+
+ draw do
+ get 'foo', to: SimpleApp.new('foo#index')
+ end
+
+ assert_equal '/foo', url_helpers.foo_path
+ assert_raises NoMethodError do
+ assert_equal '/bar', url_helpers.bar_path
+ end
+ end
+
+ test "explicit keys win over implicit keys" do
+ draw do
+ resources :foo do
+ resources :bar, to: SimpleApp.new('foo#show')
+ end
+ end
+
+ assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(1, 2)
+ assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1)
+ end
+
+ private
+ def draw(&block)
+ @set.draw(&block)
+ end
+
+ def url_helpers
+ @set.url_helpers
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb
new file mode 100644
index 0000000000..aea4489852
--- /dev/null
+++ b/actionpack/test/dispatch/routing_assertions_test.rb
@@ -0,0 +1,115 @@
+require 'abstract_unit'
+require 'controller/fake_controllers'
+
+class SecureArticlesController < ArticlesController; end
+class BlockArticlesController < ArticlesController; end
+class QueryArticlesController < ArticlesController; end
+
+class RoutingAssertionsTest < ActionController::TestCase
+
+ def setup
+ @routes = ActionDispatch::Routing::RouteSet.new
+ @routes.draw do
+ resources :articles
+
+ scope 'secure', :constraints => { :protocol => 'https://' } do
+ resources :articles, :controller => 'secure_articles'
+ end
+
+ scope 'block', :constraints => lambda { |r| r.ssl? } do
+ resources :articles, :controller => 'block_articles'
+ end
+
+ scope 'query', :constraints => lambda { |r| r.params[:use_query] == 'true' } do
+ resources :articles, :controller => 'query_articles'
+ end
+ end
+ end
+
+ def test_assert_generates
+ assert_generates('/articles', { :controller => 'articles', :action => 'index' })
+ assert_generates('/articles/1', { :controller => 'articles', :action => 'show', :id => '1' })
+ end
+
+ def test_assert_generates_with_defaults
+ assert_generates('/articles/1/edit', { :controller => 'articles', :action => 'edit' }, { :id => '1' })
+ end
+
+ def test_assert_generates_with_extras
+ assert_generates('/articles', { :controller => 'articles', :action => 'index', :page => '1' }, {}, { :page => '1' })
+ end
+
+ def test_assert_recognizes
+ assert_recognizes({ :controller => 'articles', :action => 'index' }, '/articles')
+ assert_recognizes({ :controller => 'articles', :action => 'show', :id => '1' }, '/articles/1')
+ end
+
+ def test_assert_recognizes_with_extras
+ assert_recognizes({ :controller => 'articles', :action => 'index', :page => '1' }, '/articles', { :page => '1' })
+ end
+
+ def test_assert_recognizes_with_method
+ assert_recognizes({ :controller => 'articles', :action => 'create' }, { :path => '/articles', :method => :post })
+ assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :put })
+ end
+
+ def test_assert_recognizes_with_hash_constraint
+ assert_raise(Assertion) do
+ assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles')
+ end
+ assert_recognizes({ :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }, 'https://test.host/secure/articles')
+ end
+
+ def test_assert_recognizes_with_block_constraint
+ assert_raise(Assertion) do
+ assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'http://test.host/block/articles')
+ end
+ assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'https://test.host/block/articles')
+ end
+
+ def test_assert_recognizes_with_query_constraint
+ assert_raise(Assertion) do
+ assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'false' }, '/query/articles', { :use_query => 'false' })
+ end
+ assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'true' }, '/query/articles', { :use_query => 'true' })
+ end
+
+ def test_assert_routing
+ assert_routing('/articles', :controller => 'articles', :action => 'index')
+ end
+
+ def test_assert_routing_with_defaults
+ assert_routing('/articles/1/edit', { :controller => 'articles', :action => 'edit', :id => '1' }, { :id => '1' })
+ end
+
+ def test_assert_routing_with_extras
+ assert_routing('/articles', { :controller => 'articles', :action => 'index', :page => '1' }, { }, { :page => '1' })
+ end
+
+ def test_assert_routing_with_hash_constraint
+ assert_raise(Assertion) do
+ assert_routing('http://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
+ end
+ assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index', :protocol => 'https://' })
+ end
+
+ def test_assert_routing_with_block_constraint
+ assert_raise(Assertion) do
+ assert_routing('http://test.host/block/articles', { :controller => 'block_articles', :action => 'index' })
+ end
+ assert_routing('https://test.host/block/articles', { :controller => 'block_articles', :action => 'index' })
+ end
+
+ def test_with_routing
+ with_routing do |routes|
+ routes.draw do
+ resources :articles, :path => 'artikel'
+ end
+
+ assert_routing('/artikel', :controller => 'articles', :action => 'index')
+ assert_raise(Assertion) do
+ assert_routing('/articles', { :controller => 'articles', :action => 'index' })
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
new file mode 100644
index 0000000000..b8e20c52a0
--- /dev/null
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -0,0 +1,4411 @@
+# encoding: UTF-8
+require 'erb'
+require 'abstract_unit'
+require 'controller/fake_controllers'
+
+class TestRoutingMapper < ActionDispatch::IntegrationTest
+ SprocketsApp = lambda { |env|
+ [200, {"Content-Type" => "text/html"}, ["javascripts"]]
+ }
+
+ class IpRestrictor
+ def self.matches?(request)
+ request.ip =~ /192\.168\.1\.1\d\d/
+ end
+ end
+
+ class YoutubeFavoritesRedirector
+ def self.call(params, request)
+ "http://www.youtube.com/watch?v=#{params[:youtube_id]}"
+ end
+ end
+
+ def test_logout
+ draw do
+ controller :sessions do
+ delete 'logout' => :destroy
+ end
+ end
+
+ delete '/logout'
+ assert_equal 'sessions#destroy', @response.body
+
+ assert_equal '/logout', logout_path
+ assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true)
+ end
+
+ def test_login
+ draw do
+ default_url_options :host => "rubyonrails.org"
+
+ controller :sessions do
+ get 'login' => :new
+ post 'login' => :create
+ end
+ end
+
+ get '/login'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/login', login_path
+
+ post '/login'
+ assert_equal 'sessions#create', @response.body
+
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
+
+ assert_equal 'http://rubyonrails.org/login', url_for(:controller => 'sessions', :action => 'create')
+ assert_equal 'http://rubyonrails.org/login', login_url
+ end
+
+ def test_login_redirect
+ draw do
+ get 'account/login', :to => redirect("/login")
+ end
+
+ get '/account/login'
+ verify_redirect 'http://www.example.com/login'
+ end
+
+ def test_logout_redirect_without_to
+ draw do
+ get 'account/logout' => redirect("/logout"), :as => :logout_redirect
+ end
+
+ assert_equal '/account/logout', logout_redirect_path
+ get '/account/logout'
+ verify_redirect 'http://www.example.com/logout'
+ end
+
+ def test_namespace_redirect
+ draw do
+ namespace :private do
+ root :to => redirect('/private/index')
+ get "index", :to => 'private#index'
+ end
+ end
+
+ get '/private'
+ verify_redirect 'http://www.example.com/private/index'
+ end
+
+ def test_namespace_with_controller_segment
+ assert_raise(ArgumentError) do
+ draw do
+ namespace :admin do
+ get '/:controller(/:action(/:id(.:format)))'
+ end
+ end
+ end
+ end
+
+ def test_namespace_without_controller_segment
+ draw do
+ namespace :admin do
+ get 'hello/:controllers/:action'
+ end
+ end
+ get '/admin/hello/foo/new'
+ assert_equal 'foo', @request.params["controllers"]
+ end
+
+ def test_session_singleton_resource
+ draw do
+ resource :session do
+ get :create
+ post :reset
+ end
+ end
+
+ get '/session'
+ assert_equal 'sessions#create', @response.body
+ assert_equal '/session', session_path
+
+ post '/session'
+ assert_equal 'sessions#create', @response.body
+
+ put '/session'
+ assert_equal 'sessions#update', @response.body
+
+ delete '/session'
+ assert_equal 'sessions#destroy', @response.body
+
+ get '/session/new'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/session/new', new_session_path
+
+ get '/session/edit'
+ assert_equal 'sessions#edit', @response.body
+ assert_equal '/session/edit', edit_session_path
+
+ post '/session/reset'
+ assert_equal 'sessions#reset', @response.body
+ assert_equal '/session/reset', reset_session_path
+ end
+
+ def test_session_info_nested_singleton_resource
+ draw do
+ resource :session do
+ resource :info
+ end
+ end
+
+ get '/session/info'
+ assert_equal 'infos#show', @response.body
+ assert_equal '/session/info', session_info_path
+ end
+
+ def test_member_on_resource
+ draw do
+ resource :session do
+ member do
+ get :crush
+ end
+ end
+ end
+
+ get '/session/crush'
+ assert_equal 'sessions#crush', @response.body
+ assert_equal '/session/crush', crush_session_path
+ end
+
+ def test_redirect_modulo
+ draw do
+ get 'account/modulo/:name', :to => redirect("/%{name}s")
+ end
+
+ get '/account/modulo/name'
+ verify_redirect 'http://www.example.com/names'
+ end
+
+ def test_redirect_proc
+ draw do
+ get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" }
+ end
+
+ get '/account/proc/person'
+ verify_redirect 'http://www.example.com/people'
+ end
+
+ def test_redirect_proc_with_request
+ draw do
+ get 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
+ end
+
+ get '/account/proc_req'
+ verify_redirect 'http://www.example.com/GET'
+ end
+
+ def test_redirect_hash_with_subdomain
+ draw do
+ get 'mobile', :to => redirect(:subdomain => 'mobile')
+ end
+
+ get '/mobile'
+ verify_redirect 'http://mobile.example.com/mobile'
+ end
+
+ def test_redirect_hash_with_domain_and_path
+ draw do
+ get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '')
+ end
+
+ get '/documentation'
+ verify_redirect 'http://www.example-documentation.com'
+ end
+
+ def test_redirect_hash_with_path
+ draw do
+ get 'new_documentation', :to => redirect(:path => '/documentation/new')
+ end
+
+ get '/new_documentation'
+ verify_redirect 'http://www.example.com/documentation/new'
+ end
+
+ def test_redirect_hash_with_host
+ draw do
+ get 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
+ end
+
+ get '/super_new_documentation?section=top'
+ verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
+ end
+
+ def test_redirect_hash_path_substitution
+ draw do
+ get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
+ end
+
+ get '/stores/iernest'
+ verify_redirect 'http://stores.example.com/iernest'
+ end
+
+ def test_redirect_hash_path_substitution_with_catch_all
+ draw do
+ get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}')
+ end
+
+ get '/stores/iernest/products'
+ verify_redirect 'http://stores.example.com/iernest/products'
+ end
+
+ def test_redirect_class
+ draw do
+ get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
+ end
+
+ get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
+ verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
+ end
+
+ def test_openid
+ draw do
+ match 'openid/login', :via => [:get, :post], :to => "openid#login"
+ end
+
+ get '/openid/login'
+ assert_equal 'openid#login', @response.body
+
+ post '/openid/login'
+ assert_equal 'openid#login', @response.body
+ end
+
+ def test_bookmarks
+ draw do
+ scope "bookmark", :controller => "bookmarks", :as => :bookmark do
+ get :new, :path => "build"
+ post :create, :path => "create", :as => ""
+ put :update
+ get :remove, :action => :destroy, :as => :remove
+ end
+ end
+
+ get '/bookmark/build'
+ assert_equal 'bookmarks#new', @response.body
+ assert_equal '/bookmark/build', bookmark_new_path
+
+ post '/bookmark/create'
+ assert_equal 'bookmarks#create', @response.body
+ assert_equal '/bookmark/create', bookmark_path
+
+ put '/bookmark/update'
+ assert_equal 'bookmarks#update', @response.body
+ assert_equal '/bookmark/update', bookmark_update_path
+
+ get '/bookmark/remove'
+ assert_equal 'bookmarks#destroy', @response.body
+ assert_equal '/bookmark/remove', bookmark_remove_path
+ end
+
+ def test_pagemarks
+ draw do
+ scope "pagemark", :controller => "pagemarks", :as => :pagemark do
+ get "new", :path => "build"
+ post "create", :as => ""
+ put "update"
+ get "remove", :action => :destroy, :as => :remove
+ end
+ end
+
+ get '/pagemark/build'
+ assert_equal 'pagemarks#new', @response.body
+ assert_equal '/pagemark/build', pagemark_new_path
+
+ post '/pagemark/create'
+ assert_equal 'pagemarks#create', @response.body
+ assert_equal '/pagemark/create', pagemark_path
+
+ put '/pagemark/update'
+ assert_equal 'pagemarks#update', @response.body
+ assert_equal '/pagemark/update', pagemark_update_path
+
+ get '/pagemark/remove'
+ assert_equal 'pagemarks#destroy', @response.body
+ assert_equal '/pagemark/remove', pagemark_remove_path
+ end
+
+ def test_admin
+ draw do
+ constraints(:ip => /192\.168\.1\.\d\d\d/) do
+ get 'admin' => "queenbee#index"
+ end
+
+ constraints ::TestRoutingMapper::IpRestrictor do
+ get 'admin/accounts' => "queenbee#accounts"
+ end
+
+ get 'admin/passwords' => "queenbee#passwords", :constraints => ::TestRoutingMapper::IpRestrictor
+ end
+
+ get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#index', @response.body
+
+ get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
+
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#accounts', @response.body
+
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
+
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#passwords', @response.body
+
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
+ end
+
+ def test_global
+ draw do
+ controller(:global) do
+ get 'global/hide_notice'
+ get 'global/export', :action => :export, :as => :export_request
+ get '/export/:id/:file', :action => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ get 'global/:action'
+ end
+ end
+
+ get '/global/dashboard'
+ assert_equal 'global#dashboard', @response.body
+
+ get '/global/export'
+ assert_equal 'global#export', @response.body
+
+ get '/global/hide_notice'
+ assert_equal 'global#hide_notice', @response.body
+
+ get '/export/123/foo.txt'
+ assert_equal 'global#export', @response.body
+
+ assert_equal '/global/export', export_request_path
+ assert_equal '/global/hide_notice', global_hide_notice_path
+ assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt')
+ end
+
+ def test_local
+ draw do
+ get "/local/:action", :controller => "local"
+ end
+
+ get '/local/dashboard'
+ assert_equal 'local#dashboard', @response.body
+ end
+
+ # tests the use of dup in url_for
+ def test_url_for_with_no_side_effects
+ draw do
+ get "/projects/status(.:format)"
+ end
+
+ # without dup, additional (and possibly unwanted) values will be present in the options (eg. :host)
+ original_options = {:controller => 'projects', :action => 'status'}
+ options = original_options.dup
+
+ url_for options
+
+ # verify that the options passed in have not changed from the original ones
+ assert_equal original_options, options
+ end
+
+ def test_url_for_does_not_modify_controller
+ draw do
+ get "/projects/status(.:format)"
+ end
+
+ controller = '/projects'
+ options = {:controller => controller, :action => 'status', :only_path => true}
+ url = url_for(options)
+
+ assert_equal '/projects/status', url
+ assert_equal '/projects', controller
+ end
+
+ # tests the arguments modification free version of define_hash_access
+ def test_named_route_with_no_side_effects
+ draw do
+ resources :customers do
+ get "profile", :on => :member
+ end
+ end
+
+ original_options = { :host => 'test.host' }
+ options = original_options.dup
+
+ profile_customer_url("customer_model", options)
+
+ # verify that the options passed in have not changed from the original ones
+ assert_equal original_options, options
+ end
+
+ def test_projects_status
+ draw do
+ get "/projects/status(.:format)"
+ end
+
+ assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
+ assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true)
+ end
+
+ def test_projects
+ draw do
+ resources :projects, :controller => :project
+ end
+
+ get '/projects'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects', projects_path
+
+ post '/projects'
+ assert_equal 'project#create', @response.body
+
+ get '/projects.xml'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects.xml', projects_path(:format => 'xml')
+
+ get '/projects/new'
+ assert_equal 'project#new', @response.body
+ assert_equal '/projects/new', new_project_path
+
+ get '/projects/new.xml'
+ assert_equal 'project#new', @response.body
+ assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
+
+ get '/projects/1'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1', project_path(:id => '1')
+
+ get '/projects/1.xml'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
+
+ get '/projects/1/edit'
+ assert_equal 'project#edit', @response.body
+ assert_equal '/projects/1/edit', edit_project_path(:id => '1')
+ end
+
+ def test_projects_with_post_action_and_new_path_on_collection
+ draw do
+ resources :projects, :controller => :project do
+ post 'new', :action => 'new', :on => :collection, :as => :new
+ end
+ end
+
+ post '/projects/new'
+ assert_equal "project#new", @response.body
+ assert_equal "/projects/new", new_projects_path
+ end
+
+ def test_projects_involvements
+ draw do
+ resources :projects, :controller => :project do
+ resources :involvements, :attachments
+ end
+ end
+
+ get '/projects/1/involvements'
+ assert_equal 'involvements#index', @response.body
+ assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1')
+
+ get '/projects/1/involvements/new'
+ assert_equal 'involvements#new', @response.body
+ assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1')
+
+ get '/projects/1/involvements/1'
+ assert_equal 'involvements#show', @response.body
+ assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1')
+
+ put '/projects/1/involvements/1'
+ assert_equal 'involvements#update', @response.body
+
+ delete '/projects/1/involvements/1'
+ assert_equal 'involvements#destroy', @response.body
+
+ get '/projects/1/involvements/1/edit'
+ assert_equal 'involvements#edit', @response.body
+ assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1')
+ end
+
+ def test_projects_attachments
+ draw do
+ resources :projects, :controller => :project do
+ resources :involvements, :attachments
+ end
+ end
+
+ get '/projects/1/attachments'
+ assert_equal 'attachments#index', @response.body
+ assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1')
+ end
+
+ def test_projects_participants
+ draw do
+ resources :projects, :controller => :project do
+ resources :participants do
+ put :update_all, :on => :collection
+ end
+ end
+ end
+
+ get '/projects/1/participants'
+ assert_equal 'participants#index', @response.body
+ assert_equal '/projects/1/participants', project_participants_path(:project_id => '1')
+
+ put '/projects/1/participants/update_all'
+ assert_equal 'participants#update_all', @response.body
+ assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1')
+ end
+
+ def test_projects_companies
+ draw do
+ resources :projects, :controller => :project do
+ resources :companies do
+ resources :people
+ resource :avatar, :controller => :avatar
+ end
+ end
+ end
+
+ get '/projects/1/companies'
+ assert_equal 'companies#index', @response.body
+ assert_equal '/projects/1/companies', project_companies_path(:project_id => '1')
+
+ get '/projects/1/companies/1/people'
+ assert_equal 'people#index', @response.body
+ assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
+
+ get '/projects/1/companies/1/avatar'
+ assert_equal 'avatar#show', @response.body
+ assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
+ end
+
+ def test_project_manager
+ draw do
+ resources :projects do
+ resource :manager, :as => :super_manager do
+ post :fire
+ end
+ end
+ end
+
+ get '/projects/1/manager'
+ assert_equal 'managers#show', @response.body
+ assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1')
+
+ get '/projects/1/manager/new'
+ assert_equal 'managers#new', @response.body
+ assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1')
+
+ post '/projects/1/manager/fire'
+ assert_equal 'managers#fire', @response.body
+ assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1')
+ end
+
+ def test_project_images
+ draw do
+ resources :projects do
+ resources :images, :as => :funny_images do
+ post :revise, :on => :member
+ end
+ end
+ end
+
+ get '/projects/1/images'
+ assert_equal 'images#index', @response.body
+ assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1')
+
+ get '/projects/1/images/new'
+ assert_equal 'images#new', @response.body
+ assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1')
+
+ post '/projects/1/images/1/revise'
+ assert_equal 'images#revise', @response.body
+ assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1')
+ end
+
+ def test_projects_people
+ draw do
+ resources :projects do
+ resources :people do
+ nested do
+ scope "/:access_token" do
+ resource :avatar
+ end
+ end
+
+ member do
+ put :accessible_projects
+ post :resend, :generate_new_password
+ end
+ end
+ end
+ end
+
+ get '/projects/1/people'
+ assert_equal 'people#index', @response.body
+ assert_equal '/projects/1/people', project_people_path(:project_id => '1')
+
+ get '/projects/1/people/1'
+ assert_equal 'people#show', @response.body
+ assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1')
+
+ get '/projects/1/people/1/7a2dec8/avatar'
+ assert_equal 'avatars#show', @response.body
+ assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8')
+
+ put '/projects/1/people/1/accessible_projects'
+ assert_equal 'people#accessible_projects', @response.body
+ assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1')
+
+ post '/projects/1/people/1/resend'
+ assert_equal 'people#resend', @response.body
+ assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1')
+
+ post '/projects/1/people/1/generate_new_password'
+ assert_equal 'people#generate_new_password', @response.body
+ assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1')
+ end
+
+ def test_projects_with_resources_path_names
+ draw do
+ resources_path_names :correlation_indexes => "info_about_correlation_indexes"
+
+ resources :projects do
+ get :correlation_indexes, :on => :collection
+ end
+ end
+
+ get '/projects/info_about_correlation_indexes'
+ assert_equal 'projects#correlation_indexes', @response.body
+ assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
+ end
+
+ def test_projects_posts
+ draw do
+ resources :projects do
+ resources :posts do
+ get :archive, :toggle_view, :on => :collection
+ post :preview, :on => :member
+
+ resource :subscription
+
+ resources :comments do
+ post :preview, :on => :collection
+ end
+ end
+ end
+ end
+
+ get '/projects/1/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/projects/1/posts', project_posts_path(:project_id => '1')
+
+ get '/projects/1/posts/archive'
+ assert_equal 'posts#archive', @response.body
+ assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1')
+
+ get '/projects/1/posts/toggle_view'
+ assert_equal 'posts#toggle_view', @response.body
+ assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1')
+
+ post '/projects/1/posts/1/preview'
+ assert_equal 'posts#preview', @response.body
+ assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1')
+
+ get '/projects/1/posts/1/subscription'
+ assert_equal 'subscriptions#show', @response.body
+ assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1')
+
+ get '/projects/1/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
+
+ post '/projects/1/posts/1/comments/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1')
+ end
+
+ def test_replies
+ draw do
+ resources :replies do
+ member do
+ put :answer, :action => :mark_as_answer
+ delete :answer, :action => :unmark_as_answer
+ end
+ end
+ end
+
+ put '/replies/1/answer'
+ assert_equal 'replies#mark_as_answer', @response.body
+
+ delete '/replies/1/answer'
+ assert_equal 'replies#unmark_as_answer', @response.body
+ end
+
+ def test_resource_routes_with_only_and_except
+ draw do
+ resources :posts, :only => [:index, :show] do
+ resources :comments, :except => :destroy
+ end
+ end
+
+ get '/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/posts', posts_path
+
+ get '/posts/1'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/1', post_path(:id => 1)
+
+ get '/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/posts/1/comments', post_comments_path(:post_id => 1)
+
+ post '/posts'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ put '/posts/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ delete '/posts/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ delete '/posts/1/comments'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ end
+
+ def test_resource_routes_only_create_update_destroy
+ draw do
+ resource :past, :only => :destroy
+ resource :present, :only => :update
+ resource :future, :only => :create
+ end
+
+ delete '/past'
+ assert_equal 'pasts#destroy', @response.body
+ assert_equal '/past', past_path
+
+ patch '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ put '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ post '/future'
+ assert_equal 'futures#create', @response.body
+ assert_equal '/future', future_path
+ end
+
+ def test_resources_routes_only_create_update_destroy
+ draw do
+ resources :relationships, :only => [:create, :destroy]
+ resources :friendships, :only => [:update]
+ end
+
+ post '/relationships'
+ assert_equal 'relationships#create', @response.body
+ assert_equal '/relationships', relationships_path
+
+ delete '/relationships/1'
+ assert_equal 'relationships#destroy', @response.body
+ assert_equal '/relationships/1', relationship_path(1)
+
+ patch '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
+
+ put '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
+ end
+
+ def test_resource_with_slugs_in_ids
+ draw do
+ resources :posts
+ end
+
+ get '/posts/rails-rocks'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
+ end
+
+ def test_resources_for_uncountable_names
+ draw do
+ resources :sheep do
+ get "_it", :on => :member
+ end
+ end
+
+ assert_equal '/sheep', sheep_index_path
+ assert_equal '/sheep/1', sheep_path(1)
+ assert_equal '/sheep/new', new_sheep_path
+ assert_equal '/sheep/1/edit', edit_sheep_path(1)
+ assert_equal '/sheep/1/_it', _it_sheep_path(1)
+ end
+
+ def test_resource_does_not_modify_passed_options
+ options = {:id => /.+?/, :format => /json|xml/}
+ draw { resource :user, options }
+ assert_equal({:id => /.+?/, :format => /json|xml/}, options)
+ end
+
+ def test_resources_does_not_modify_passed_options
+ options = {:id => /.+?/, :format => /json|xml/}
+ draw { resources :users, options }
+ assert_equal({:id => /.+?/, :format => /json|xml/}, options)
+ end
+
+ def test_path_names
+ draw do
+ scope 'pt', :as => 'pt' do
+ resources :projects, :path_names => { :edit => 'editar', :new => 'novo' }, :path => 'projetos'
+ resource :admin, :path_names => { :new => 'novo', :activate => 'ativar' }, :path => 'administrador' do
+ put :activate, :on => :member
+ end
+ end
+ end
+
+ get '/pt/projetos'
+ assert_equal 'projects#index', @response.body
+ assert_equal '/pt/projetos', pt_projects_path
+
+ get '/pt/projetos/1/editar'
+ assert_equal 'projects#edit', @response.body
+ assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1)
+
+ get '/pt/administrador'
+ assert_equal 'admins#show', @response.body
+ assert_equal '/pt/administrador', pt_admin_path
+
+ get '/pt/administrador/novo'
+ assert_equal 'admins#new', @response.body
+ assert_equal '/pt/administrador/novo', new_pt_admin_path
+
+ put '/pt/administrador/ativar'
+ assert_equal 'admins#activate', @response.body
+ assert_equal '/pt/administrador/ativar', activate_pt_admin_path
+ end
+
+ def test_path_option_override
+ draw do
+ scope 'pt', :as => 'pt' do
+ resources :projects, :path_names => { :new => 'novo' }, :path => 'projetos' do
+ put :close, :on => :member, :path => 'fechar'
+ get :open, :on => :new, :path => 'abrir'
+ end
+ end
+ end
+
+ get '/pt/projetos/novo/abrir'
+ assert_equal 'projects#open', @response.body
+ assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
+
+ put '/pt/projetos/1/fechar'
+ assert_equal 'projects#close', @response.body
+ assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1)
+ end
+
+ def test_sprockets
+ draw do
+ get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
+ end
+
+ get '/sprockets.js'
+ assert_equal 'javascripts', @response.body
+ end
+
+ def test_update_person_route
+ draw do
+ get 'people/:id/update', :to => 'people#update', :as => :update_person
+ end
+
+ get '/people/1/update'
+ assert_equal 'people#update', @response.body
+
+ assert_equal '/people/1/update', update_person_path(:id => 1)
+ end
+
+ def test_update_project_person
+ draw do
+ get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
+ end
+
+ get '/projects/1/people/2/update'
+ assert_equal 'people#update', @response.body
+
+ assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2)
+ end
+
+ def test_forum_products
+ draw do
+ namespace :forum do
+ resources :products, :path => '' do
+ resources :questions
+ end
+ end
+ end
+
+ get '/forum'
+ assert_equal 'forum/products#index', @response.body
+ assert_equal '/forum', forum_products_path
+
+ get '/forum/basecamp'
+ assert_equal 'forum/products#show', @response.body
+ assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
+
+ get '/forum/basecamp/questions'
+ assert_equal 'forum/questions#index', @response.body
+ assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
+
+ get '/forum/basecamp/questions/1'
+ assert_equal 'forum/questions#show', @response.body
+ assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
+ end
+
+ def test_articles_perma
+ draw do
+ get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
+ end
+
+ get '/articles/2009/08/18/rails-3'
+ assert_equal 'articles#show', @response.body
+
+ assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
+ end
+
+ def test_account_namespace
+ draw do
+ namespace :account do
+ resource :subscription, :credit, :credit_card
+ end
+ end
+
+ get '/account/subscription'
+ assert_equal 'account/subscriptions#show', @response.body
+ assert_equal '/account/subscription', account_subscription_path
+
+ get '/account/credit'
+ assert_equal 'account/credits#show', @response.body
+ assert_equal '/account/credit', account_credit_path
+
+ get '/account/credit_card'
+ assert_equal 'account/credit_cards#show', @response.body
+ assert_equal '/account/credit_card', account_credit_card_path
+ end
+
+ def test_nested_namespace
+ draw do
+ namespace :account do
+ namespace :admin do
+ resource :subscription
+ end
+ end
+ end
+
+ get '/account/admin/subscription'
+ assert_equal 'account/admin/subscriptions#show', @response.body
+ assert_equal '/account/admin/subscription', account_admin_subscription_path
+ end
+
+ def test_namespace_nested_in_resources
+ draw do
+ resources :clients do
+ namespace :google do
+ resource :account do
+ namespace :secret do
+ resource :info
+ end
+ end
+ end
+ end
+ end
+
+ get '/clients/1/google/account'
+ assert_equal '/clients/1/google/account', client_google_account_path(1)
+ assert_equal 'google/accounts#show', @response.body
+
+ get '/clients/1/google/account/secret/info'
+ assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1)
+ assert_equal 'google/secret/infos#show', @response.body
+ end
+
+ def test_namespace_with_options
+ draw do
+ namespace :users, :path => 'usuarios' do
+ root :to => 'home#index'
+ end
+ end
+
+ get '/usuarios'
+ assert_equal '/usuarios', users_root_path
+ assert_equal 'users/home#index', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_module_option
+ draw do
+ namespace :foo, module: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', foo_posts_path
+ assert_equal 'bar/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', foo_post_path('1')
+ assert_equal 'bar/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'bar/comments#index', @response.body
+
+ get '/foo/comments/2'
+ assert_equal '/foo/comments/2', foo_comment_path('2')
+ assert_equal 'bar/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_path_option
+ draw do
+ namespace :foo, path: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/bar/posts'
+ assert_equal '/bar/posts', foo_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/bar/posts/1'
+ assert_equal '/bar/posts/1', foo_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/bar/posts/1/comments'
+ assert_equal '/bar/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/bar/comments/2'
+ assert_equal '/bar/comments/2', foo_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_as_option
+ draw do
+ namespace :foo, as: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', bar_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', bar_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', bar_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/foo/comments/2'
+ assert_equal '/foo/comments/2', bar_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_shallow_path_option
+ draw do
+ namespace :foo, shallow_path: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', foo_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', foo_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/bar/comments/2'
+ assert_equal '/bar/comments/2', foo_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespaced_shallow_routes_with_shallow_prefix_option
+ draw do
+ namespace :foo, shallow_prefix: 'bar' do
+ resources :posts, only: [:index, :show] do
+ resources :comments, only: [:index, :show], shallow: true
+ end
+ end
+ end
+
+ get '/foo/posts'
+ assert_equal '/foo/posts', foo_posts_path
+ assert_equal 'foo/posts#index', @response.body
+
+ get '/foo/posts/1'
+ assert_equal '/foo/posts/1', foo_post_path('1')
+ assert_equal 'foo/posts#show', @response.body
+
+ get '/foo/posts/1/comments'
+ assert_equal '/foo/posts/1/comments', foo_post_comments_path('1')
+ assert_equal 'foo/comments#index', @response.body
+
+ get '/foo/comments/2'
+ assert_equal '/foo/comments/2', bar_comment_path('2')
+ assert_equal 'foo/comments#show', @response.body
+ end
+
+ def test_namespace_containing_numbers
+ draw do
+ namespace :v2 do
+ resources :subscriptions
+ end
+ end
+
+ get '/v2/subscriptions'
+ assert_equal 'v2/subscriptions#index', @response.body
+ assert_equal '/v2/subscriptions', v2_subscriptions_path
+ end
+
+ def test_articles_with_id
+ draw do
+ controller :articles do
+ scope '/articles', :as => 'article' do
+ scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
+ get '/:id', :action => :with_id, :as => ""
+ end
+ end
+ end
+ end
+
+ get '/articles/rails/1'
+ assert_equal 'articles#with_id', @response.body
+
+ get '/articles/123/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+
+ assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1)
+ end
+
+ def test_access_token_rooms
+ draw do
+ scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do
+ resources :rooms
+ end
+ end
+
+ get '/12345/rooms'
+ assert_equal 'rooms#index', @response.body
+
+ get '/12345/rooms/1'
+ assert_equal 'rooms#show', @response.body
+
+ get '/12345/rooms/1/edit'
+ assert_equal 'rooms#edit', @response.body
+ end
+
+ def test_root
+ draw do
+ root :to => 'projects#index'
+ end
+
+ assert_equal '/', root_path
+ get '/'
+ assert_equal 'projects#index', @response.body
+ end
+
+ def test_scoped_root
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ root :to => 'projects#index'
+ end
+ end
+
+ assert_equal '/en', root_path(:locale => 'en')
+ get '/en'
+ assert_equal 'projects#index', @response.body
+ end
+
+ def test_scoped_root_as_name
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ root :to => 'projects#index', :as => 'projects'
+ end
+ end
+
+ assert_equal '/en', projects_path(:locale => 'en')
+ assert_equal '/', projects_path
+ get '/en'
+ assert_equal 'projects#index', @response.body
+ end
+
+ def test_scope_with_format_option
+ draw do
+ get "direct/index", as: :no_format_direct, format: false
+
+ scope format: false do
+ get "scoped/index", as: :no_format_scoped
+ end
+ end
+
+ assert_equal "/direct/index", no_format_direct_path
+ assert_equal "/direct/index?format=html", no_format_direct_path(format: "html")
+
+ assert_equal "/scoped/index", no_format_scoped_path
+ assert_equal "/scoped/index?format=html", no_format_scoped_path(format: "html")
+
+ get '/scoped/index'
+ assert_equal "scoped#index", @response.body
+
+ get '/scoped/index.html'
+ assert_equal "Not Found", @response.body
+ end
+
+ def test_resources_with_format_false_from_scope
+ draw do
+ scope format: false do
+ resources :posts
+ resource :user
+ end
+ end
+
+ get "/posts"
+ assert_response :success
+ assert_equal "posts#index", @response.body
+ assert_equal "/posts", posts_path
+
+ get "/posts.html"
+ assert_response :not_found
+ assert_equal "Not Found", @response.body
+ assert_equal "/posts?format=html", posts_path(format: "html")
+
+ get "/user"
+ assert_response :success
+ assert_equal "users#show", @response.body
+ assert_equal "/user", user_path
+
+ get "/user.html"
+ assert_response :not_found
+ assert_equal "Not Found", @response.body
+ assert_equal "/user?format=html", user_path(format: "html")
+ end
+
+ def test_index
+ draw do
+ get '/info' => 'projects#info', :as => 'info'
+ end
+
+ assert_equal '/info', info_path
+ get '/info'
+ assert_equal 'projects#info', @response.body
+ end
+
+ def test_match_with_many_paths_containing_a_slash
+ draw do
+ get 'get/first', 'get/second', 'get/third', :to => 'get#show'
+ end
+
+ get '/get/first'
+ assert_equal 'get#show', @response.body
+
+ get '/get/second'
+ assert_equal 'get#show', @response.body
+
+ get '/get/third'
+ assert_equal 'get#show', @response.body
+ end
+
+ def test_match_shorthand_with_no_scope
+ draw do
+ get 'account/overview'
+ end
+
+ assert_equal '/account/overview', account_overview_path
+ get '/account/overview'
+ assert_equal 'account#overview', @response.body
+ end
+
+ def test_match_shorthand_inside_namespace
+ draw do
+ namespace :account do
+ get 'shorthand'
+ end
+ end
+
+ assert_equal '/account/shorthand', account_shorthand_path
+ get '/account/shorthand'
+ assert_equal 'account#shorthand', @response.body
+ end
+
+ def test_match_shorthand_with_multiple_paths_inside_namespace
+ draw do
+ namespace :proposals do
+ put 'activate', 'inactivate'
+ end
+ end
+
+ put '/proposals/activate'
+ assert_equal 'proposals#activate', @response.body
+
+ put '/proposals/inactivate'
+ assert_equal 'proposals#inactivate', @response.body
+ end
+
+ def test_match_shorthand_inside_namespace_with_controller
+ draw do
+ namespace :api do
+ get "products/list"
+ end
+ end
+
+ assert_equal '/api/products/list', api_products_list_path
+ get '/api/products/list'
+ assert_equal 'api/products#list', @response.body
+ end
+
+ def test_match_shorthand_inside_scope_with_variables_with_controller
+ draw do
+ scope ':locale' do
+ match 'questions/new', via: [:get]
+ end
+ end
+
+ get '/de/questions/new'
+ assert_equal 'questions#new', @response.body
+ assert_equal 'de', @request.params[:locale]
+ end
+
+ def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller
+ draw do
+ namespace :api do
+ namespace :v3 do
+ scope ':locale' do
+ get "products/list"
+ end
+ end
+ end
+ end
+
+ get '/api/v3/en/products/list'
+ assert_equal 'api/v3/products#list', @response.body
+ end
+
+ def test_controller_option_with_nesting_and_leading_slash
+ draw do
+ scope '/job', controller: 'job' do
+ scope ':id', action: 'manage_applicant' do
+ get "/active"
+ end
+ end
+ end
+
+ get '/job/5/active'
+ assert_equal 'job#manage_applicant', @response.body
+ end
+
+ def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
+ draw do
+ resources :replies do
+ collection do
+ get 'page/:page' => 'replies#index', :page => %r{\d+}
+ get ':page' => 'replies#index', :page => %r{\d+}
+ end
+ end
+ end
+
+ assert_equal '/replies', replies_path
+ end
+
+ def test_scoped_controller_with_namespace_and_action
+ draw do
+ namespace :account do
+ get ':action/callback', :action => /twitter|github/, :controller => "callbacks", :as => :callback
+ end
+ end
+
+ assert_equal '/account/twitter/callback', account_callback_path("twitter")
+ get '/account/twitter/callback'
+ assert_equal 'account/callbacks#twitter', @response.body
+
+ get '/account/whatever/callback'
+ assert_equal 'Not Found', @response.body
+ end
+
+ def test_convention_match_nested_and_with_leading_slash
+ draw do
+ get '/account/nested/overview'
+ end
+
+ assert_equal '/account/nested/overview', account_nested_overview_path
+ get '/account/nested/overview'
+ assert_equal 'account/nested#overview', @response.body
+ end
+
+ def test_convention_with_explicit_end
+ draw do
+ get 'sign_in' => "sessions#new"
+ end
+
+ get '/sign_in'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/sign_in', sign_in_path
+ end
+
+ def test_redirect_with_complete_url_and_status
+ draw do
+ get 'account/google' => redirect('http://www.google.com/', :status => 302)
+ end
+
+ get '/account/google'
+ verify_redirect 'http://www.google.com/', 302
+ end
+
+ def test_redirect_with_port
+ draw do
+ get 'account/login', :to => redirect("/login")
+ end
+
+ previous_host, self.host = self.host, 'www.example.com:3000'
+
+ get '/account/login'
+ verify_redirect 'http://www.example.com:3000/login'
+ ensure
+ self.host = previous_host
+ end
+
+ def test_normalize_namespaced_matches
+ draw do
+ namespace :account do
+ get 'description', :action => :description, :as => "description"
+ end
+ end
+
+ assert_equal '/account/description', account_description_path
+
+ get '/account/description'
+ assert_equal 'account#description', @response.body
+ end
+
+ def test_namespaced_roots
+ draw do
+ namespace :account do
+ root :to => "account#index"
+ end
+ end
+
+ assert_equal '/account', account_root_path
+ get '/account'
+ assert_equal 'account/account#index', @response.body
+ end
+
+ def test_optional_scoped_root
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ root :to => 'projects#index'
+ end
+ end
+
+ assert_equal '/en', root_path("en")
+ get '/en'
+ assert_equal 'projects#index', @response.body
+ end
+
+ def test_optional_scoped_path
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ resources :descriptions
+ end
+ end
+
+ assert_equal '/en/descriptions', descriptions_path("en")
+ assert_equal '/descriptions', descriptions_path(nil)
+ assert_equal '/en/descriptions/1', description_path("en", 1)
+ assert_equal '/descriptions/1', description_path(nil, 1)
+
+ get '/en/descriptions'
+ assert_equal 'descriptions#index', @response.body
+
+ get '/descriptions'
+ assert_equal 'descriptions#index', @response.body
+
+ get '/en/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
+
+ get '/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
+ end
+
+ def test_nested_optional_scoped_path
+ draw do
+ namespace :admin do
+ scope '(:locale)', :locale => /en|pl/ do
+ resources :descriptions
+ end
+ end
+ end
+
+ assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
+ assert_equal '/admin/descriptions', admin_descriptions_path(nil)
+ assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
+ assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
+
+ get '/admin/en/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
+
+ get '/admin/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
+
+ get '/admin/en/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
+
+ get '/admin/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
+ end
+
+ def test_nested_optional_path_shorthand
+ draw do
+ scope '(:locale)', :locale => /en|pl/ do
+ get "registrations/new"
+ end
+ end
+
+ get '/registrations/new'
+ assert_nil @request.params[:locale]
+
+ get '/en/registrations/new'
+ assert_equal 'en', @request.params[:locale]
+ end
+
+ def test_default_string_params
+ draw do
+ get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
+ get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
+
+ defaults :id => 'home' do
+ get 'scoped_pages/(:id)', :to => 'pages#show'
+ end
+ end
+
+ get '/inline_pages'
+ assert_equal 'home', @request.params[:id]
+
+ get '/default_pages'
+ assert_equal 'home', @request.params[:id]
+
+ get '/scoped_pages'
+ assert_equal 'home', @request.params[:id]
+ end
+
+ def test_default_integer_params
+ draw do
+ get 'inline_pages/(:page)', to: 'pages#show', page: 1
+ get 'default_pages/(:page)', to: 'pages#show', defaults: { page: 1 }
+
+ defaults page: 1 do
+ get 'scoped_pages/(:page)', to: 'pages#show'
+ end
+ end
+
+ get '/inline_pages'
+ assert_equal 1, @request.params[:page]
+
+ get '/default_pages'
+ assert_equal 1, @request.params[:page]
+
+ get '/scoped_pages'
+ assert_equal 1, @request.params[:page]
+ end
+
+ def test_resource_constraints
+ draw do
+ resources :products, :constraints => { :id => /\d{4}/ } do
+ root :to => "products#root"
+ get :favorite, :on => :collection
+ resources :images
+ end
+
+ resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
+ end
+
+ get '/products/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/products'
+ assert_equal 'products#root', @response.body
+ get '/products/favorite'
+ assert_equal 'products#favorite', @response.body
+ get '/products/0001'
+ assert_equal 'products#show', @response.body
+
+ get '/products/1/images'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/products/0001/images'
+ assert_equal 'images#index', @response.body
+ get '/products/0001/images/0001'
+ assert_equal 'images#show', @response.body
+
+ get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'dashboards#show', @response.body
+ end
+
+ def test_root_works_in_the_resources_scope
+ draw do
+ resources :products do
+ root :to => "products#root"
+ end
+ end
+
+ get '/products'
+ assert_equal 'products#root', @response.body
+ assert_equal '/products', products_root_path
+ end
+
+ def test_module_scope
+ draw do
+ resource :token, :module => :api
+ end
+
+ get '/token'
+ assert_equal 'api/tokens#show', @response.body
+ assert_equal '/token', token_path
+ end
+
+ def test_path_scope
+ draw do
+ scope :path => 'api' do
+ resource :me
+ get '/' => 'mes#index'
+ end
+ end
+
+ get '/api/me'
+ assert_equal 'mes#show', @response.body
+ assert_equal '/api/me', me_path
+
+ get '/api'
+ assert_equal 'mes#index', @response.body
+ end
+
+ def test_symbol_scope
+ draw do
+ scope :path => 'api' do
+ scope :v2 do
+ resource :me, as: 'v2_me'
+ get '/' => 'mes#index'
+ end
+
+ scope :v3, :admin do
+ resource :me, as: 'v3_me'
+ end
+ end
+ end
+
+ get '/api/v2/me'
+ assert_equal 'mes#show', @response.body
+ assert_equal '/api/v2/me', v2_me_path
+
+ get '/api/v2'
+ assert_equal 'mes#index', @response.body
+
+ get '/api/v3/admin/me'
+ assert_equal 'mes#show', @response.body
+ end
+
+ def test_url_generator_for_generic_route
+ draw do
+ get "whatever/:controller(/:action(/:id))"
+ end
+
+ get '/whatever/foo/bar'
+ assert_equal 'foo#bar', @response.body
+
+ assert_equal 'http://www.example.com/whatever/foo/bar/1',
+ url_for(:controller => "foo", :action => "bar", :id => 1)
+ end
+
+ def test_url_generator_for_namespaced_generic_route
+ draw do
+ get "whatever/:controller(/:action(/:id))", :id => /\d+/
+ end
+
+ get '/whatever/foo/bar/show'
+ assert_equal 'foo/bar#show', @response.body
+
+ get '/whatever/foo/bar/show/1'
+ assert_equal 'foo/bar#show', @response.body
+
+ assert_equal 'http://www.example.com/whatever/foo/bar/show',
+ url_for(:controller => "foo/bar", :action => "show")
+
+ assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
+ url_for(:controller => "foo/bar", :action => "show", :id => '1')
+ end
+
+ def test_resource_new_actions
+ draw do
+ resources :replies do
+ new do
+ post :preview
+ end
+ end
+
+ scope 'pt', :as => 'pt' do
+ resources :projects, :path_names => { :new => 'novo' }, :path => 'projetos' do
+ post :preview, :on => :new
+ end
+
+ resource :admin, :path_names => { :new => 'novo' }, :path => 'administrador' do
+ post :preview, :on => :new
+ end
+
+ resources :products, :path_names => { :new => 'novo' } do
+ new do
+ post :preview
+ end
+ end
+ end
+
+ resource :profile do
+ new do
+ post :preview
+ end
+ end
+ end
+
+ assert_equal '/replies/new/preview', preview_new_reply_path
+ assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
+ assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
+ assert_equal '/pt/products/novo/preview', preview_new_pt_product_path
+ assert_equal '/profile/new/preview', preview_new_profile_path
+
+ post '/replies/new/preview'
+ assert_equal 'replies#preview', @response.body
+
+ post '/pt/projetos/novo/preview'
+ assert_equal 'projects#preview', @response.body
+
+ post '/pt/administrador/novo/preview'
+ assert_equal 'admins#preview', @response.body
+
+ post '/pt/products/novo/preview'
+ assert_equal 'products#preview', @response.body
+
+ post '/profile/new/preview'
+ assert_equal 'profiles#preview', @response.body
+ end
+
+ def test_resource_merges_options_from_scope
+ draw do
+ scope :only => :show do
+ resource :account
+ end
+ end
+
+ assert_raise(NoMethodError) { new_account_path }
+
+ get '/account/new'
+ assert_equal 404, status
+ end
+
+ def test_resources_merges_options_from_scope
+ draw do
+ scope :only => [:index, :show] do
+ resources :products do
+ resources :images
+ end
+ end
+ end
+
+ assert_raise(NoMethodError) { edit_product_path('1') }
+
+ get '/products/1/edit'
+ assert_equal 404, status
+
+ assert_raise(NoMethodError) { edit_product_image_path('1', '2') }
+
+ post '/products/1/images/2/edit'
+ assert_equal 404, status
+ end
+
+ def test_shallow_nested_resources
+ draw do
+ shallow do
+ namespace :api do
+ resources :teams do
+ resources :players
+ resource :captain
+ end
+ end
+ end
+
+ resources :threads, :shallow => true do
+ resource :owner
+ resources :messages do
+ resources :comments do
+ member do
+ post :preview
+ end
+ end
+ end
+ end
+ end
+
+ get '/api/teams'
+ assert_equal 'api/teams#index', @response.body
+ assert_equal '/api/teams', api_teams_path
+
+ get '/api/teams/new'
+ assert_equal 'api/teams#new', @response.body
+ assert_equal '/api/teams/new', new_api_team_path
+
+ get '/api/teams/1'
+ assert_equal 'api/teams#show', @response.body
+ assert_equal '/api/teams/1', api_team_path(:id => '1')
+
+ get '/api/teams/1/edit'
+ assert_equal 'api/teams#edit', @response.body
+ assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1')
+
+ get '/api/teams/1/players'
+ assert_equal 'api/players#index', @response.body
+ assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1')
+
+ get '/api/teams/1/players/new'
+ assert_equal 'api/players#new', @response.body
+ assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1')
+
+ get '/api/players/2'
+ assert_equal 'api/players#show', @response.body
+ assert_equal '/api/players/2', api_player_path(:id => '2')
+
+ get '/api/players/2/edit'
+ assert_equal 'api/players#edit', @response.body
+ assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2')
+
+ get '/api/teams/1/captain'
+ assert_equal 'api/captains#show', @response.body
+ assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1')
+
+ get '/api/teams/1/captain/new'
+ assert_equal 'api/captains#new', @response.body
+ assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1')
+
+ get '/api/teams/1/captain/edit'
+ assert_equal 'api/captains#edit', @response.body
+ assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1')
+
+ get '/threads'
+ assert_equal 'threads#index', @response.body
+ assert_equal '/threads', threads_path
+
+ get '/threads/new'
+ assert_equal 'threads#new', @response.body
+ assert_equal '/threads/new', new_thread_path
+
+ get '/threads/1'
+ assert_equal 'threads#show', @response.body
+ assert_equal '/threads/1', thread_path(:id => '1')
+
+ get '/threads/1/edit'
+ assert_equal 'threads#edit', @response.body
+ assert_equal '/threads/1/edit', edit_thread_path(:id => '1')
+
+ get '/threads/1/owner'
+ assert_equal 'owners#show', @response.body
+ assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1')
+
+ get '/threads/1/messages'
+ assert_equal 'messages#index', @response.body
+ assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1')
+
+ get '/threads/1/messages/new'
+ assert_equal 'messages#new', @response.body
+ assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1')
+
+ get '/messages/2'
+ assert_equal 'messages#show', @response.body
+ assert_equal '/messages/2', message_path(:id => '2')
+
+ get '/messages/2/edit'
+ assert_equal 'messages#edit', @response.body
+ assert_equal '/messages/2/edit', edit_message_path(:id => '2')
+
+ get '/messages/2/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/messages/2/comments', message_comments_path(:message_id => '2')
+
+ get '/messages/2/comments/new'
+ assert_equal 'comments#new', @response.body
+ assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2')
+
+ get '/comments/3'
+ assert_equal 'comments#show', @response.body
+ assert_equal '/comments/3', comment_path(:id => '3')
+
+ get '/comments/3/edit'
+ assert_equal 'comments#edit', @response.body
+ assert_equal '/comments/3/edit', edit_comment_path(:id => '3')
+
+ post '/comments/3/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
+ end
+
+ def test_shallow_nested_resources_inside_resource
+ draw do
+ resource :membership, shallow: true do
+ resources :cards
+ end
+ end
+
+ get '/membership/cards'
+ assert_equal 'cards#index', @response.body
+ assert_equal '/membership/cards', membership_cards_path
+
+ get '/membership/cards/new'
+ assert_equal 'cards#new', @response.body
+ assert_equal '/membership/cards/new', new_membership_card_path
+
+ post '/membership/cards'
+ assert_equal 'cards#create', @response.body
+
+ get '/cards/1'
+ assert_equal 'cards#show', @response.body
+ assert_equal '/cards/1', card_path('1')
+
+ get '/cards/1/edit'
+ assert_equal 'cards#edit', @response.body
+ assert_equal '/cards/1/edit', edit_card_path('1')
+
+ put '/cards/1'
+ assert_equal 'cards#update', @response.body
+
+ patch '/cards/1'
+ assert_equal 'cards#update', @response.body
+
+ delete '/cards/1'
+ assert_equal 'cards#destroy', @response.body
+ end
+
+ def test_shallow_deeply_nested_resources
+ draw do
+ resources :blogs do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ get '/comments/1'
+ assert_equal 'comments#show', @response.body
+
+ assert_equal '/comments/1', comment_path('1')
+ assert_equal '/blogs/new', new_blog_path
+ assert_equal '/blogs/1/posts/new', new_blog_post_path(:blog_id => 1)
+ assert_equal '/blogs/1/posts/2/comments/new', new_blog_post_comment_path(:blog_id => 1, :post_id => 2)
+ end
+
+ def test_direct_children_of_shallow_resources
+ draw do
+ resources :blogs do
+ resources :posts, shallow: true do
+ resources :comments
+ end
+ end
+ end
+
+ post '/posts/1/comments'
+ assert_equal 'comments#create', @response.body
+ assert_equal '/posts/1/comments', post_comments_path('1')
+
+ get '/posts/2/comments/new'
+ assert_equal 'comments#new', @response.body
+ assert_equal '/posts/2/comments/new', new_post_comment_path('2')
+
+ get '/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/posts/1/comments', post_comments_path('1')
+ end
+
+ def test_shallow_nested_resources_within_scope
+ draw do
+ scope '/hello' do
+ shallow do
+ resources :notes do
+ resources :trackbacks
+ end
+ end
+ end
+ end
+
+ get '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#index', @response.body
+ assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
+
+ get '/hello/notes/1/edit'
+ assert_equal 'notes#edit', @response.body
+ assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
+
+ get '/hello/notes/1/trackbacks/new'
+ assert_equal 'trackbacks#new', @response.body
+ assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
+
+ get '/hello/trackbacks/1'
+ assert_equal 'trackbacks#show', @response.body
+ assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
+
+ get '/hello/trackbacks/1/edit'
+ assert_equal 'trackbacks#edit', @response.body
+ assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
+
+ put '/hello/trackbacks/1'
+ assert_equal 'trackbacks#update', @response.body
+
+ post '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#create', @response.body
+
+ delete '/hello/trackbacks/1'
+ assert_equal 'trackbacks#destroy', @response.body
+
+ get '/hello/notes'
+ assert_equal 'notes#index', @response.body
+
+ post '/hello/notes'
+ assert_equal 'notes#create', @response.body
+
+ get '/hello/notes/new'
+ assert_equal 'notes#new', @response.body
+ assert_equal '/hello/notes/new', new_note_path
+
+ get '/hello/notes/1'
+ assert_equal 'notes#show', @response.body
+ assert_equal '/hello/notes/1', note_path(:id => 1)
+
+ put '/hello/notes/1'
+ assert_equal 'notes#update', @response.body
+
+ delete '/hello/notes/1'
+ assert_equal 'notes#destroy', @response.body
+ end
+
+ def test_shallow_option_nested_resources_within_scope
+ draw do
+ scope '/hello' do
+ resources :notes, :shallow => true do
+ resources :trackbacks
+ end
+ end
+ end
+
+ get '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#index', @response.body
+ assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
+
+ get '/hello/notes/1/edit'
+ assert_equal 'notes#edit', @response.body
+ assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
+
+ get '/hello/notes/1/trackbacks/new'
+ assert_equal 'trackbacks#new', @response.body
+ assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
+
+ get '/hello/trackbacks/1'
+ assert_equal 'trackbacks#show', @response.body
+ assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
+
+ get '/hello/trackbacks/1/edit'
+ assert_equal 'trackbacks#edit', @response.body
+ assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
+
+ put '/hello/trackbacks/1'
+ assert_equal 'trackbacks#update', @response.body
+
+ post '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#create', @response.body
+
+ delete '/hello/trackbacks/1'
+ assert_equal 'trackbacks#destroy', @response.body
+
+ get '/hello/notes'
+ assert_equal 'notes#index', @response.body
+
+ post '/hello/notes'
+ assert_equal 'notes#create', @response.body
+
+ get '/hello/notes/new'
+ assert_equal 'notes#new', @response.body
+ assert_equal '/hello/notes/new', new_note_path
+
+ get '/hello/notes/1'
+ assert_equal 'notes#show', @response.body
+ assert_equal '/hello/notes/1', note_path(:id => 1)
+
+ put '/hello/notes/1'
+ assert_equal 'notes#update', @response.body
+
+ delete '/hello/notes/1'
+ assert_equal 'notes#destroy', @response.body
+ end
+
+ def test_custom_resource_routes_are_scoped
+ draw do
+ resources :customers do
+ get :recent, :on => :collection
+ get "profile", :on => :member
+ get "secret/profile" => "customers#secret", :on => :member
+ post "preview" => "customers#preview", :as => :another_preview, :on => :new
+ resource :avatar do
+ get "thumbnail" => "avatars#thumbnail", :as => :thumbnail, :on => :member
+ end
+ resources :invoices do
+ get "outstanding" => "invoices#outstanding", :on => :collection
+ get "overdue", :action => :overdue, :on => :collection
+ get "print" => "invoices#print", :as => :print, :on => :member
+ post "preview" => "invoices#preview", :as => :preview, :on => :new
+ end
+ resources :notes, :shallow => true do
+ get "preview" => "notes#preview", :as => :preview, :on => :new
+ get "print" => "notes#print", :as => :print, :on => :member
+ end
+ end
+
+ namespace :api do
+ resources :customers do
+ get "recent" => "customers#recent", :as => :recent, :on => :collection
+ get "profile" => "customers#profile", :as => :profile, :on => :member
+ post "preview" => "customers#preview", :as => :preview, :on => :new
+ end
+ end
+ end
+
+ assert_equal '/customers/recent', recent_customers_path
+ assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
+ assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1')
+ assert_equal '/customers/new/preview', another_preview_new_customer_path
+ assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
+ assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
+ assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
+ assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
+ assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
+ assert_equal '/notes/1/print', print_note_path(:id => '1')
+ assert_equal '/api/customers/recent', recent_api_customers_path
+ assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
+ assert_equal '/api/customers/new/preview', preview_new_api_customer_path
+
+ get '/customers/1/invoices/overdue'
+ assert_equal 'invoices#overdue', @response.body
+
+ get '/customers/1/secret/profile'
+ assert_equal 'customers#secret', @response.body
+ end
+
+ def test_shallow_nested_routes_ignore_module
+ draw do
+ scope :module => :api do
+ resources :errors, :shallow => true do
+ resources :notices
+ end
+ end
+ end
+
+ get '/errors/1/notices'
+ assert_equal 'api/notices#index', @response.body
+ assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
+
+ get '/notices/1'
+ assert_equal 'api/notices#show', @response.body
+ assert_equal '/notices/1', notice_path(:id => '1')
+ end
+
+ def test_non_greedy_regexp
+ draw do
+ namespace :api do
+ scope(':version', :version => /.+/) do
+ resources :users, :id => /.+?/, :format => /json|xml/
+ end
+ end
+ end
+
+ get '/api/1.0/users'
+ assert_equal 'api/users#index', @response.body
+ assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
+
+ get '/api/1.0/users.json'
+ assert_equal 'api/users#index', @response.body
+ assert_equal true, @request.format.json?
+ assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json)
+
+ get '/api/1.0/users/first.last'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last')
+
+ get '/api/1.0/users/first.last.xml'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal true, @request.format.xml?
+ assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
+ end
+
+ def test_match_without_via
+ assert_raises(ArgumentError) do
+ draw do
+ match '/foo/bar', :to => 'files#show'
+ end
+ end
+ end
+
+ def test_match_with_empty_via
+ assert_raises(ArgumentError) do
+ draw do
+ match '/foo/bar', :to => 'files#show', :via => []
+ end
+ end
+ end
+
+ def test_glob_parameter_accepts_regexp
+ draw do
+ get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
+ end
+
+ get '/en/path/to/existing/file.html'
+ assert_equal 200, @response.status
+ end
+
+ def test_resources_controller_name_is_not_pluralized
+ draw do
+ resources :content
+ end
+
+ get '/content'
+ assert_equal 'content#index', @response.body
+ end
+
+ def test_url_generator_for_optional_prefix_dynamic_segment
+ draw do
+ get "(/:username)/followers" => "followers#index"
+ end
+
+ get '/bob/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/bob/followers',
+ url_for(:controller => "followers", :action => "index", :username => "bob")
+
+ get '/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/followers',
+ url_for(:controller => "followers", :action => "index", :username => nil)
+ end
+
+ def test_url_generator_for_optional_suffix_static_and_dynamic_segment
+ draw do
+ get "/groups(/user/:username)" => "groups#index"
+ end
+
+ get '/groups/user/bob'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups/user/bob',
+ url_for(:controller => "groups", :action => "index", :username => "bob")
+
+ get '/groups'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups',
+ url_for(:controller => "groups", :action => "index", :username => nil)
+ end
+
+ def test_url_generator_for_optional_prefix_static_and_dynamic_segment
+ draw do
+ get "(/user/:username)/photos" => "photos#index"
+ end
+
+ get '/user/bob/photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/user/bob/photos',
+ url_for(:controller => "photos", :action => "index", :username => "bob")
+
+ get '/photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/photos',
+ url_for(:controller => "photos", :action => "index", :username => nil)
+ end
+
+ def test_url_recognition_for_optional_static_segments
+ draw do
+ scope '(groups)' do
+ scope '(discussions)' do
+ resources :messages
+ end
+ end
+ end
+
+ get '/groups/discussions/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/groups/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
+
+ get '/groups/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/groups/messages/1'
+ assert_equal 'messages#show', @response.body
+
+ get '/discussions/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
+
+ get '/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/messages/1'
+ assert_equal 'messages#show', @response.body
+ end
+
+ def test_router_removes_invalid_conditions
+ draw do
+ scope :constraints => { :id => /\d+/ } do
+ get '/tickets', :to => 'tickets#index', :as => :tickets
+ end
+ end
+
+ get '/tickets'
+ assert_equal 'tickets#index', @response.body
+ assert_equal '/tickets', tickets_path
+ end
+
+ def test_constraints_are_merged_from_scope
+ draw do
+ scope :constraints => { :id => /\d{4}/ } do
+ resources :movies do
+ resources :reviews
+ resource :trailer
+ end
+ end
+ end
+
+ get '/movies/0001'
+ assert_equal 'movies#show', @response.body
+ assert_equal '/movies/0001', movie_path(:id => '0001')
+
+ get '/movies/00001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::UrlGenerationError){ movie_path(:id => '00001') }
+
+ get '/movies/0001/reviews'
+ assert_equal 'reviews#index', @response.body
+ assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001')
+
+ get '/movies/00001/reviews'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::UrlGenerationError){ movie_reviews_path(:movie_id => '00001') }
+
+ get '/movies/0001/reviews/0001'
+ assert_equal 'reviews#show', @response.body
+ assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001')
+
+ get '/movies/00001/reviews/0001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::UrlGenerationError){ movie_path(:movie_id => '00001', :id => '00001') }
+
+ get '/movies/0001/trailer'
+ assert_equal 'trailers#show', @response.body
+ assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001')
+
+ get '/movies/00001/trailer'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::UrlGenerationError){ movie_trailer_path(:movie_id => '00001') }
+ end
+
+ def test_only_should_be_read_from_scope
+ draw do
+ scope :only => [:index, :show] do
+ namespace :only do
+ resources :clubs do
+ resources :players
+ resource :chairman
+ end
+ end
+ end
+ end
+
+ get '/only/clubs'
+ assert_equal 'only/clubs#index', @response.body
+ assert_equal '/only/clubs', only_clubs_path
+
+ get '/only/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_path(:id => '1') }
+
+ get '/only/clubs/1/players'
+ assert_equal 'only/players#index', @response.body
+ assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1')
+
+ get '/only/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/only/clubs/1/chairman'
+ assert_equal 'only/chairmen#show', @response.body
+ assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1')
+
+ get '/only/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') }
+ end
+
+ def test_except_should_be_read_from_scope
+ draw do
+ scope :except => [:new, :create, :edit, :update, :destroy] do
+ namespace :except do
+ resources :clubs do
+ resources :players
+ resource :chairman
+ end
+ end
+ end
+ end
+
+ get '/except/clubs'
+ assert_equal 'except/clubs#index', @response.body
+ assert_equal '/except/clubs', except_clubs_path
+
+ get '/except/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_path(:id => '1') }
+
+ get '/except/clubs/1/players'
+ assert_equal 'except/players#index', @response.body
+ assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1')
+
+ get '/except/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/except/clubs/1/chairman'
+ assert_equal 'except/chairmen#show', @response.body
+ assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1')
+
+ get '/except/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') }
+ end
+
+ def test_only_option_should_override_scope
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index
+ end
+ end
+ end
+
+ get '/only/sectors'
+ assert_equal 'only/sectors#index', @response.body
+ assert_equal '/only/sectors', only_sectors_path
+
+ get '/only/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_path(:id => '1') }
+ end
+
+ def test_only_option_should_not_inherit
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies
+ resource :leader
+ end
+ end
+ end
+ end
+
+ get '/only/sectors/1/companies/2'
+ assert_equal 'only/companies#show', @response.body
+ assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/only/sectors/1/leader'
+ assert_equal 'only/leaders#show', @response.body
+ assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1')
+ end
+
+ def test_except_option_should_override_scope
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy]
+ end
+ end
+ end
+
+ get '/except/sectors'
+ assert_equal 'except/sectors#index', @response.body
+ assert_equal '/except/sectors', except_sectors_path
+
+ get '/except/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_path(:id => '1') }
+ end
+
+ def test_except_option_should_not_inherit
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies
+ resource :leader
+ end
+ end
+ end
+ end
+
+ get '/except/sectors/1/companies/2'
+ assert_equal 'except/companies#show', @response.body
+ assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/except/sectors/1/leader'
+ assert_equal 'except/leaders#show', @response.body
+ assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1')
+ end
+
+ def test_except_option_should_override_scoped_only
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :managers, :except => [:show, :update, :destroy]
+ end
+ end
+ end
+ end
+
+ get '/only/sectors/1/managers'
+ assert_equal 'only/managers#index', @response.body
+ assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
+
+ get '/only/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') }
+ end
+
+ def test_only_option_should_override_scoped_except
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :managers, :only => :index
+ end
+ end
+ end
+ end
+
+ get '/except/sectors/1/managers'
+ assert_equal 'except/managers#index', @response.body
+ assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
+
+ get '/except/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') }
+ end
+
+ def test_only_scope_should_override_parent_scope
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies do
+ scope :only => :index do
+ resources :divisions
+ end
+ end
+ end
+ end
+ end
+ end
+
+ get '/only/sectors/1/companies/2/divisions'
+ assert_equal 'only/divisions#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+
+ def test_except_scope_should_override_parent_scope
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies do
+ scope :except => [:show, :update, :destroy] do
+ resources :divisions
+ end
+ end
+ end
+ end
+ end
+ end
+
+ get '/except/sectors/1/companies/2/divisions'
+ assert_equal 'except/divisions#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+
+ def test_except_scope_should_override_parent_only_scope
+ draw do
+ scope :only => :show do
+ namespace :only do
+ resources :sectors, :only => :index do
+ resources :companies do
+ scope :except => [:show, :update, :destroy] do
+ resources :departments
+ end
+ end
+ end
+ end
+ end
+ end
+
+ get '/only/sectors/1/companies/2/departments'
+ assert_equal 'only/departments#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+
+ def test_only_scope_should_override_parent_except_scope
+ draw do
+ scope :except => :index do
+ namespace :except do
+ resources :sectors, :except => [:show, :update, :destroy] do
+ resources :companies do
+ scope :only => :index do
+ resources :departments
+ end
+ end
+ end
+ end
+ end
+ end
+
+ get '/except/sectors/1/companies/2/departments'
+ assert_equal 'except/departments#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
+ end
+
+ def test_resources_are_not_pluralized
+ draw do
+ namespace :transport do
+ resources :taxis
+ end
+ end
+
+ get '/transport/taxis'
+ assert_equal 'transport/taxis#index', @response.body
+ assert_equal '/transport/taxis', transport_taxis_path
+
+ get '/transport/taxis/new'
+ assert_equal 'transport/taxis#new', @response.body
+ assert_equal '/transport/taxis/new', new_transport_taxi_path
+
+ post '/transport/taxis'
+ assert_equal 'transport/taxis#create', @response.body
+
+ get '/transport/taxis/1'
+ assert_equal 'transport/taxis#show', @response.body
+ assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1')
+
+ get '/transport/taxis/1/edit'
+ assert_equal 'transport/taxis#edit', @response.body
+ assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1')
+
+ put '/transport/taxis/1'
+ assert_equal 'transport/taxis#update', @response.body
+
+ delete '/transport/taxis/1'
+ assert_equal 'transport/taxis#destroy', @response.body
+ end
+
+ def test_singleton_resources_are_not_singularized
+ draw do
+ namespace :medical do
+ resource :taxis
+ end
+ end
+
+ get '/medical/taxis/new'
+ assert_equal 'medical/taxis#new', @response.body
+ assert_equal '/medical/taxis/new', new_medical_taxis_path
+
+ post '/medical/taxis'
+ assert_equal 'medical/taxis#create', @response.body
+
+ get '/medical/taxis'
+ assert_equal 'medical/taxis#show', @response.body
+ assert_equal '/medical/taxis', medical_taxis_path
+
+ get '/medical/taxis/edit'
+ assert_equal 'medical/taxis#edit', @response.body
+ assert_equal '/medical/taxis/edit', edit_medical_taxis_path
+
+ put '/medical/taxis'
+ assert_equal 'medical/taxis#update', @response.body
+
+ delete '/medical/taxis'
+ assert_equal 'medical/taxis#destroy', @response.body
+ end
+
+ def test_greedy_resource_id_regexp_doesnt_match_edit_and_custom_action
+ draw do
+ resources :sections, :id => /.+/ do
+ get :preview, :on => :member
+ end
+ end
+
+ get '/sections/1/edit'
+ assert_equal 'sections#edit', @response.body
+ assert_equal '/sections/1/edit', edit_section_path(:id => '1')
+
+ get '/sections/1/preview'
+ assert_equal 'sections#preview', @response.body
+ assert_equal '/sections/1/preview', preview_section_path(:id => '1')
+ end
+
+ def test_resource_constraints_are_pushed_to_scope
+ draw do
+ namespace :wiki do
+ resources :articles, :id => /[^\/]+/ do
+ resources :comments, :only => [:create, :new]
+ end
+ end
+ end
+
+ get '/wiki/articles/Ruby_on_Rails_3.0'
+ assert_equal 'wiki/articles#show', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0')
+
+ get '/wiki/articles/Ruby_on_Rails_3.0/comments/new'
+ assert_equal 'wiki/comments#new', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0')
+
+ post '/wiki/articles/Ruby_on_Rails_3.0/comments'
+ assert_equal 'wiki/comments#create', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0')
+ end
+
+ def test_resources_path_can_be_a_symbol
+ draw do
+ resources :wiki_pages, :path => :pages
+ resource :wiki_account, :path => :my_account
+ end
+
+ get '/pages'
+ assert_equal 'wiki_pages#index', @response.body
+ assert_equal '/pages', wiki_pages_path
+
+ get '/pages/Ruby_on_Rails'
+ assert_equal 'wiki_pages#show', @response.body
+ assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails')
+
+ get '/my_account'
+ assert_equal 'wiki_accounts#show', @response.body
+ assert_equal '/my_account', wiki_account_path
+ end
+
+ def test_redirect_https
+ draw do
+ get 'secure', :to => redirect("/secure/login")
+ end
+
+ with_https do
+ get '/secure'
+ verify_redirect 'https://www.example.com/secure/login'
+ end
+ end
+
+ def test_path_parameters_is_not_stale
+ draw do
+ scope '/countries/:country', :constraints => lambda { |params, req| %w(all France).include?(params[:country]) } do
+ get '/', :to => 'countries#index'
+ get '/cities', :to => 'countries#cities'
+ end
+
+ get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
+ end
+
+ get '/countries/France'
+ assert_equal 'countries#index', @response.body
+
+ get '/countries/France/cities'
+ assert_equal 'countries#cities', @response.body
+
+ get '/countries/UK'
+ verify_redirect 'http://www.example.com/countries/all'
+
+ get '/countries/UK/cities'
+ verify_redirect 'http://www.example.com/countries/all/cities'
+ end
+
+ def test_constraints_block_not_carried_to_following_routes
+ draw do
+ scope '/italians' do
+ get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
+ get '/sculptors', :to => 'italians#sculptors'
+ get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
+ end
+ end
+
+ get '/italians/writers'
+ assert_equal 'Not Found', @response.body
+
+ get '/italians/sculptors'
+ assert_equal 'italians#sculptors', @response.body
+
+ get '/italians/painters/botticelli'
+ assert_equal 'Not Found', @response.body
+
+ get '/italians/painters/michelangelo'
+ assert_equal 'italians#painters', @response.body
+ end
+
+ def test_custom_resource_actions_defined_using_string
+ draw do
+ resources :customers do
+ resources :invoices do
+ get "aged/:months", :on => :collection, :action => :aged, :as => :aged
+ end
+
+ get "inactive", :on => :collection
+ post "deactivate", :on => :member
+ get "old", :on => :collection, :as => :stale
+ end
+ end
+
+ get '/customers/inactive'
+ assert_equal 'customers#inactive', @response.body
+ assert_equal '/customers/inactive', inactive_customers_path
+
+ post '/customers/1/deactivate'
+ assert_equal 'customers#deactivate', @response.body
+ assert_equal '/customers/1/deactivate', deactivate_customer_path(:id => '1')
+
+ get '/customers/old'
+ assert_equal 'customers#old', @response.body
+ assert_equal '/customers/old', stale_customers_path
+
+ get '/customers/1/invoices/aged/3'
+ assert_equal 'invoices#aged', @response.body
+ assert_equal '/customers/1/invoices/aged/3', aged_customer_invoices_path(:customer_id => '1', :months => '3')
+ end
+
+ def test_route_defined_in_resources_scope_level
+ draw do
+ resources :customers do
+ get "export"
+ end
+ end
+
+ get '/customers/1/export'
+ assert_equal 'customers#export', @response.body
+ assert_equal '/customers/1/export', customer_export_path(:customer_id => '1')
+ end
+
+ def test_named_character_classes_in_regexp_constraints
+ draw do
+ get '/purchases/:token/:filename',
+ :to => 'purchases#fetch',
+ :token => /[[:alnum:]]{10}/,
+ :filename => /(.+)/,
+ :as => :purchase
+ end
+
+ get '/purchases/315004be7e/Ruby_on_Rails_3.pdf'
+ assert_equal 'purchases#fetch', @response.body
+ assert_equal '/purchases/315004be7e/Ruby_on_Rails_3.pdf', purchase_path(:token => '315004be7e', :filename => 'Ruby_on_Rails_3.pdf')
+ end
+
+ def test_nested_resource_constraints
+ draw do
+ resources :lists, :id => /([A-Za-z0-9]{25})|default/ do
+ resources :todos, :id => /\d+/
+ end
+ end
+
+ get '/lists/01234012340123401234fffff'
+ assert_equal 'lists#show', @response.body
+ assert_equal '/lists/01234012340123401234fffff', list_path(:id => '01234012340123401234fffff')
+
+ get '/lists/01234012340123401234fffff/todos/1'
+ assert_equal 'todos#show', @response.body
+ assert_equal '/lists/01234012340123401234fffff/todos/1', list_todo_path(:list_id => '01234012340123401234fffff', :id => '1')
+
+ get '/lists/2/todos/1'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::UrlGenerationError){ list_todo_path(:list_id => '2', :id => '1') }
+ end
+
+ def test_redirect_argument_error
+ routes = Class.new { include ActionDispatch::Routing::Redirection }.new
+ assert_raises(ArgumentError) { routes.redirect Object.new }
+ end
+
+ def test_named_route_check
+ before, after = nil
+
+ draw do
+ before = has_named_route?(:hello)
+ get "/hello", as: :hello, to: "hello#world"
+ after = has_named_route?(:hello)
+ end
+
+ assert !before, "expected to not have named route :hello before route definition"
+ assert after, "expected to have named route :hello after route definition"
+ end
+
+ def test_explicitly_avoiding_the_named_route
+ draw do
+ scope :as => "routes" do
+ get "/c/:id", :as => :collision, :to => "collision#show"
+ get "/collision", :to => "collision#show"
+ get "/no_collision", :to => "collision#show", :as => nil
+ end
+ end
+
+ assert !respond_to?(:routes_no_collision_path)
+ end
+
+ def test_controller_name_with_leading_slash_raise_error
+ assert_raise(ArgumentError) do
+ draw { get '/feeds/:service', :to => '/feeds#show' }
+ end
+
+ assert_raise(ArgumentError) do
+ draw { get '/feeds/:service', :controller => '/feeds', :action => 'show' }
+ end
+
+ assert_raise(ArgumentError) do
+ draw { get '/api/feeds/:service', :to => '/api/feeds#show' }
+ end
+
+ assert_raise(ArgumentError) do
+ assert_deprecated do
+ draw { controller("/feeds") { get '/feeds/:service', :to => :show } }
+ end
+ end
+
+ assert_raise(ArgumentError) do
+ draw { resources :feeds, :controller => '/feeds' }
+ end
+ end
+
+ def test_invalid_route_name_raises_error
+ assert_raise(ArgumentError) do
+ draw { get '/products', :to => 'products#index', :as => 'products ' }
+ end
+
+ assert_raise(ArgumentError) do
+ draw { get '/products', :to => 'products#index', :as => ' products' }
+ end
+
+ assert_raise(ArgumentError) do
+ draw { get '/products', :to => 'products#index', :as => 'products!' }
+ end
+
+ assert_raise(ArgumentError) do
+ draw { get '/products', :to => 'products#index', :as => 'products index' }
+ end
+
+ assert_raise(ArgumentError) do
+ draw { get '/products', :to => 'products#index', :as => '1products' }
+ end
+ end
+
+ def test_duplicate_route_name_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ get '/duplicate', :to => 'duplicate#show', :as => 'collision'
+ end
+ end
+ end
+
+ def test_duplicate_route_name_via_resources_raises_error
+ assert_raise(ArgumentError) do
+ draw do
+ resources :collisions
+ get '/collision', :to => 'collision#show', :as => 'collision'
+ end
+ end
+ end
+
+ def test_nested_route_in_nested_resource
+ draw do
+ resources :posts, :only => [:index, :show] do
+ resources :comments, :except => :destroy do
+ get "views" => "comments#views", :as => :views
+ end
+ end
+ end
+
+ get "/posts/1/comments/2/views"
+ assert_equal "comments#views", @response.body
+ assert_equal "/posts/1/comments/2/views", post_comment_views_path(:post_id => '1', :comment_id => '2')
+ end
+
+ def test_root_in_deeply_nested_scope
+ draw do
+ resources :posts, :only => [:index, :show] do
+ namespace :admin do
+ root :to => "index#index"
+ end
+ end
+ end
+
+ get "/posts/1/admin"
+ assert_equal "admin/index#index", @response.body
+ assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1')
+ end
+
+ def test_custom_param
+ draw do
+ resources :profiles, :param => :username do
+ get :details, :on => :member
+ resources :messages
+ end
+ end
+
+ get '/profiles/bob'
+ assert_equal 'profiles#show', @response.body
+ assert_equal 'bob', @request.params[:username]
+
+ get '/profiles/bob/details'
+ assert_equal 'bob', @request.params[:username]
+
+ get '/profiles/bob/messages/34'
+ assert_equal 'bob', @request.params[:profile_username]
+ assert_equal '34', @request.params[:id]
+ end
+
+ def test_custom_param_constraint
+ draw do
+ resources :profiles, :param => :username, :username => /[a-z]+/ do
+ get :details, :on => :member
+ resources :messages
+ end
+ end
+
+ get '/profiles/bob1'
+ assert_equal 404, @response.status
+
+ get '/profiles/bob1/details'
+ assert_equal 404, @response.status
+
+ get '/profiles/bob1/messages/34'
+ assert_equal 404, @response.status
+ end
+
+ def test_shallow_custom_param
+ draw do
+ resources :orders do
+ constraints :download => /[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/ do
+ resources :downloads, :param => :download, :shallow => true
+ end
+ end
+ end
+
+ get '/downloads/0c0c0b68-d24b-11e1-a861-001ff3fffe6f.zip'
+ assert_equal 'downloads#show', @response.body
+ assert_equal '0c0c0b68-d24b-11e1-a861-001ff3fffe6f', @request.params[:download]
+ end
+
+ def test_action_from_path_is_not_frozen
+ draw do
+ get 'search' => 'search'
+ end
+
+ get '/search'
+ assert !@request.params[:action].frozen?
+ end
+
+ def test_multiple_positional_args_with_the_same_name
+ draw do
+ get '/downloads/:id/:id.tar' => 'downloads#show', as: :download, format: false
+ end
+
+ expected_params = {
+ controller: 'downloads',
+ action: 'show',
+ id: '1'
+ }
+
+ get '/downloads/1/1.tar'
+ assert_equal 'downloads#show', @response.body
+ assert_equal expected_params, @request.path_parameters
+ assert_equal '/downloads/1/1.tar', download_path('1')
+ assert_equal '/downloads/1/1.tar', download_path('1', '1')
+ end
+
+ def test_absolute_controller_namespace
+ draw do
+ namespace :foo do
+ get '/', to: '/bar#index', as: 'root'
+ end
+ end
+
+ get '/foo'
+ assert_equal 'bar#index', @response.body
+ assert_equal '/foo', foo_root_path
+ end
+
+ def test_namespace_as_controller
+ draw do
+ namespace :foo do
+ get '/', to: '/bar#index', as: 'root'
+ end
+ end
+
+ get '/foo'
+ assert_equal 'bar#index', @response.body
+ assert_equal '/foo', foo_root_path
+ end
+
+ def test_trailing_slash
+ draw do
+ resources :streams
+ end
+
+ get '/streams'
+ assert @response.ok?, 'route without trailing slash should work'
+
+ get '/streams/'
+ assert @response.ok?, 'route with trailing slash should work'
+
+ get '/streams?foobar'
+ assert @response.ok?, 'route without trailing slash and with QUERY_STRING should work'
+
+ get '/streams/?foobar'
+ assert @response.ok?, 'route with trailing slash and with QUERY_STRING should work'
+ end
+
+ def test_route_with_dashes_in_path
+ draw do
+ get '/contact-us', to: 'pages#contact_us'
+ end
+
+ get '/contact-us'
+ assert_equal 'pages#contact_us', @response.body
+ assert_equal '/contact-us', contact_us_path
+ end
+
+ def test_shorthand_route_with_dashes_in_path
+ draw do
+ get '/about-us/index'
+ end
+
+ get '/about-us/index'
+ assert_equal 'about_us#index', @response.body
+ assert_equal '/about-us/index', about_us_index_path
+ end
+
+ def test_resource_routes_with_dashes_in_path
+ draw do
+ resources :photos, only: [:show] do
+ get 'user-favorites', on: :collection
+ get 'preview-photo', on: :member
+ get 'summary-text'
+ end
+ end
+
+ get '/photos/user-favorites'
+ assert_equal 'photos#user_favorites', @response.body
+ assert_equal '/photos/user-favorites', user_favorites_photos_path
+
+ get '/photos/1/preview-photo'
+ assert_equal 'photos#preview_photo', @response.body
+ assert_equal '/photos/1/preview-photo', preview_photo_photo_path('1')
+
+ get '/photos/1/summary-text'
+ assert_equal 'photos#summary_text', @response.body
+ assert_equal '/photos/1/summary-text', photo_summary_text_path('1')
+
+ get '/photos/1'
+ assert_equal 'photos#show', @response.body
+ assert_equal '/photos/1', photo_path('1')
+ end
+
+ def test_shallow_path_inside_namespace_is_not_added_twice
+ draw do
+ namespace :admin do
+ shallow do
+ resources :posts do
+ resources :comments
+ end
+ end
+ end
+ end
+
+ get '/admin/posts/1/comments'
+ assert_equal 'admin/comments#index', @response.body
+ assert_equal '/admin/posts/1/comments', admin_post_comments_path('1')
+ end
+
+ def test_mix_string_to_controller_action
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_string_to_controller
+ draw do
+ get '/projects', controller: 'project_files',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_string_to_action
+ draw do
+ get '/projects', action: 'index',
+ to: 'comments#index'
+ end
+ get '/projects'
+ assert_equal 'comments#index', @response.body
+ end
+
+ def test_mix_symbol_to_controller_action
+ assert_deprecated do
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: :show
+ end
+ end
+ get '/projects'
+ assert_equal 'project_files#show', @response.body
+ end
+
+ def test_mix_string_to_controller_action_no_hash
+ assert_deprecated do
+ draw do
+ get '/projects', controller: 'project_files',
+ action: 'index',
+ to: 'show'
+ end
+ end
+ get '/projects'
+ assert_equal 'show#index', @response.body
+ end
+
+ def test_shallow_path_and_prefix_are_not_added_to_non_shallow_routes
+ draw do
+ scope shallow_path: 'projects', shallow_prefix: 'project' do
+ resources :projects do
+ resources :files, controller: 'project_files', shallow: true
+ end
+ end
+ end
+
+ get '/projects'
+ assert_equal 'projects#index', @response.body
+ assert_equal '/projects', projects_path
+
+ get '/projects/new'
+ assert_equal 'projects#new', @response.body
+ assert_equal '/projects/new', new_project_path
+
+ post '/projects'
+ assert_equal 'projects#create', @response.body
+
+ get '/projects/1'
+ assert_equal 'projects#show', @response.body
+ assert_equal '/projects/1', project_path('1')
+
+ get '/projects/1/edit'
+ assert_equal 'projects#edit', @response.body
+ assert_equal '/projects/1/edit', edit_project_path('1')
+
+ patch '/projects/1'
+ assert_equal 'projects#update', @response.body
+
+ delete '/projects/1'
+ assert_equal 'projects#destroy', @response.body
+
+ get '/projects/1/files'
+ assert_equal 'project_files#index', @response.body
+ assert_equal '/projects/1/files', project_files_path('1')
+
+ get '/projects/1/files/new'
+ assert_equal 'project_files#new', @response.body
+ assert_equal '/projects/1/files/new', new_project_file_path('1')
+
+ post '/projects/1/files'
+ assert_equal 'project_files#create', @response.body
+
+ get '/projects/files/2'
+ assert_equal 'project_files#show', @response.body
+ assert_equal '/projects/files/2', project_file_path('2')
+
+ get '/projects/files/2/edit'
+ assert_equal 'project_files#edit', @response.body
+ assert_equal '/projects/files/2/edit', edit_project_file_path('2')
+
+ patch '/projects/files/2'
+ assert_equal 'project_files#update', @response.body
+
+ delete '/projects/files/2'
+ assert_equal 'project_files#destroy', @response.body
+ end
+
+ def test_scope_path_is_copied_to_shallow_path
+ draw do
+ scope path: 'foo' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/foo/comments/1', comment_path('1')
+ end
+
+ def test_scope_as_is_copied_to_shallow_prefix
+ draw do
+ scope as: 'foo' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/comments/1', foo_comment_path('1')
+ end
+
+ def test_scope_shallow_prefix_is_not_overwritten_by_as
+ draw do
+ scope as: 'foo', shallow_prefix: 'bar' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/comments/1', bar_comment_path('1')
+ end
+
+ def test_scope_shallow_path_is_not_overwritten_by_path
+ draw do
+ scope path: 'foo', shallow_path: 'bar' do
+ resources :posts do
+ resources :comments, shallow: true
+ end
+ end
+ end
+
+ assert_equal '/bar/comments/1', comment_path('1')
+ end
+
+private
+
+ def draw(&block)
+ self.class.stub_controllers do |routes|
+ routes.default_url_options = { host: 'www.example.com' }
+ routes.draw(&block)
+ @app = RoutedRackApp.new routes
+ end
+ end
+
+ def url_for(options = {})
+ @app.routes.url_helpers.url_for(options)
+ end
+
+ def method_missing(method, *args, &block)
+ if method.to_s =~ /_(path|url)$/
+ @app.routes.url_helpers.send(method, *args, &block)
+ else
+ super
+ end
+ end
+
+ def with_https
+ old_https = https?
+ https!
+ yield
+ ensure
+ https!(old_https)
+ end
+
+ def verify_redirect(url, status=301)
+ assert_equal status, @response.status
+ assert_equal url, @response.headers['Location']
+ assert_equal expected_redirect_body(url), @response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
+ end
+end
+
+class TestAltApp < ActionDispatch::IntegrationTest
+ class AltRequest
+ attr_accessor :path_parameters, :path_info, :script_name
+ attr_reader :env
+
+ def initialize(env)
+ @path_parameters = {}
+ @env = env
+ @path_info = "/"
+ @script_name = ""
+ end
+
+ def request_method
+ "GET"
+ end
+
+ def ip
+ "127.0.0.1"
+ end
+
+ def x_header
+ @env["HTTP_X_HEADER"] || ""
+ end
+ end
+
+ class XHeader
+ def call(env)
+ [200, {"Content-Type" => "text/html"}, ["XHeader"]]
+ end
+ end
+
+ class AltApp
+ def call(env)
+ [200, {"Content-Type" => "text/html"}, ["Alternative App"]]
+ end
+ end
+
+ AltRoutes = ActionDispatch::Routing::RouteSet.new(AltRequest)
+ AltRoutes.draw do
+ get "/" => TestAltApp::XHeader.new, :constraints => {:x_header => /HEADER/}
+ get "/" => TestAltApp::AltApp.new
+ end
+
+ APP = build_app AltRoutes
+
+ def app
+ APP
+ end
+
+ def test_alt_request_without_header
+ get "/"
+ assert_equal "Alternative App", @response.body
+ end
+
+ def test_alt_request_with_matched_header
+ get "/", {}, "HTTP_X_HEADER" => "HEADER"
+ assert_equal "XHeader", @response.body
+ end
+
+ def test_alt_request_with_unmatched_header
+ get "/", {}, "HTTP_X_HEADER" => "NON_MATCH"
+ assert_equal "Alternative App", @response.body
+ end
+end
+
+class TestAppendingRoutes < ActionDispatch::IntegrationTest
+ def simple_app(resp)
+ lambda { |e| [ 200, { 'Content-Type' => 'text/plain' }, [resp] ] }
+ end
+
+ def setup
+ super
+ s = self
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.append do
+ get '/hello' => s.simple_app('fail')
+ get '/goodbye' => s.simple_app('goodbye')
+ end
+
+ routes.draw do
+ get '/hello' => s.simple_app('hello')
+ end
+ @app = self.class.build_app routes
+ end
+
+ def test_goodbye_should_be_available
+ get '/goodbye'
+ assert_equal 'goodbye', @response.body
+ end
+
+ def test_hello_should_not_be_overwritten
+ get '/hello'
+ assert_equal 'hello', @response.body
+ end
+
+ def test_missing_routes_are_still_missing
+ get '/random'
+ assert_equal 404, @response.status
+ end
+end
+
+class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
+ module ::Admin
+ class StorageFilesController < ActionController::Base
+ def index
+ render :text => "admin/storage_files#index"
+ end
+ end
+ end
+
+ def draw(&block)
+ routes = ActionDispatch::Routing::RouteSet.new
+ routes.draw(&block)
+ @app = self.class.build_app routes
+ end
+
+ def test_missing_controller
+ ex = assert_raises(ArgumentError) {
+ draw do
+ get '/foo/bar', :action => :index
+ end
+ }
+ assert_match(/Missing :controller/, ex.message)
+ end
+
+ def test_missing_action
+ ex = assert_raises(ArgumentError) {
+ assert_deprecated do
+ draw do
+ get '/foo/bar', :to => 'foo'
+ end
+ end
+ }
+ assert_match(/Missing :action/, ex.message)
+ end
+
+ def test_missing_action_on_hash
+ ex = assert_raises(ArgumentError) {
+ draw do
+ get '/foo/bar', :to => 'foo#'
+ end
+ }
+ assert_match(/Missing :action/, ex.message)
+ end
+
+ def test_valid_controller_options_inside_namespace
+ draw do
+ namespace :admin do
+ resources :storage_files, :controller => "storage_files"
+ end
+ end
+
+ get '/admin/storage_files'
+ assert_equal "admin/storage_files#index", @response.body
+ end
+
+ def test_resources_with_valid_namespaced_controller_option
+ draw do
+ resources :storage_files, :controller => 'admin/storage_files'
+ end
+
+ get '/storage_files'
+ assert_equal "admin/storage_files#index", @response.body
+ end
+
+ def test_warn_with_ruby_constant_syntax_controller_option
+ e = assert_raise(ArgumentError) do
+ draw do
+ namespace :admin do
+ resources :storage_files, :controller => "StorageFiles"
+ end
+ end
+ end
+
+ assert_match "'admin/StorageFiles' is not a supported controller name", e.message
+ end
+
+ def test_warn_with_ruby_constant_syntax_namespaced_controller_option
+ e = assert_raise(ArgumentError) do
+ draw do
+ resources :storage_files, :controller => 'Admin::StorageFiles'
+ end
+ end
+
+ assert_match "'Admin::StorageFiles' is not a supported controller name", e.message
+ end
+
+ def test_warn_with_ruby_constant_syntax_no_colons
+ e = assert_raise(ArgumentError) do
+ draw do
+ resources :storage_files, :controller => 'Admin'
+ end
+ end
+
+ assert_match "'Admin' is not a supported controller name", e.message
+ end
+end
+
+class TestDefaultScope < ActionDispatch::IntegrationTest
+ module ::Blog
+ class PostsController < ActionController::Base
+ def index
+ render :text => "blog/posts#index"
+ end
+ end
+ end
+
+ DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new
+ DefaultScopeRoutes.default_scope = {:module => :blog}
+ DefaultScopeRoutes.draw do
+ resources :posts
+ end
+
+ APP = build_app DefaultScopeRoutes
+
+ def app
+ APP
+ end
+
+ include DefaultScopeRoutes.url_helpers
+
+ def test_default_scope
+ get '/posts'
+ assert_equal "blog/posts#index", @response.body
+ end
+end
+
+class TestHttpMethods < ActionDispatch::IntegrationTest
+ RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT)
+ RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK)
+ RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY)
+ RFC3648 = %w(ORDERPATCH)
+ RFC3744 = %w(ACL)
+ RFC5323 = %w(SEARCH)
+ RFC4791 = %w(MKCALENDAR)
+ RFC5789 = %w(PATCH)
+
+ def simple_app(response)
+ lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [response] ] }
+ end
+
+ attr_reader :app
+
+ def setup
+ s = self
+ routes = ActionDispatch::Routing::RouteSet.new
+ @app = RoutedRackApp.new routes
+
+ routes.draw do
+ (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method|
+ match '/' => s.simple_app(method), :via => method.underscore.to_sym
+ end
+ end
+ end
+
+ (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789).each do |method|
+ test "request method #{method.underscore} can be matched" do
+ get '/', nil, 'REQUEST_METHOD' => method
+ assert_equal method, @response.body
+ end
+ end
+end
+
+class TestUriPathEscaping < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ get '/:segment' => lambda { |env|
+ path_params = env['action_dispatch.request.path_parameters']
+ [200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]]
+ }, :as => :segment
+
+ get '/*splat' => lambda { |env|
+ path_params = env['action_dispatch.request.path_parameters']
+ [200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]]
+ }, :as => :splat
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ test 'escapes slash in generated path segment' do
+ assert_equal '/a%20b%2Fc+d', segment_path(:segment => 'a b/c+d')
+ end
+
+ test 'unescapes recognized path segment' do
+ get '/a%20b%2Fc+d'
+ assert_equal 'a b/c+d', @response.body
+ end
+
+ test 'does not escape slash in generated path splat' do
+ assert_equal '/a%20b/c+d', splat_path(:splat => 'a b/c+d')
+ end
+
+ test 'unescapes recognized path splat' do
+ get '/a%20b/c+d'
+ assert_equal 'a b/c+d', @response.body
+ end
+end
+
+class TestUnicodePaths < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ get "/ほげ" => lambda { |env|
+ [200, { 'Content-Type' => 'text/plain' }, []]
+ }, :as => :unicode_path
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ test 'recognizes unicode path' do
+ get "/#{Rack::Utils.escape("ほげ")}"
+ assert_equal "200", @response.code
+ end
+end
+
+class TestMultipleNestedController < ActionDispatch::IntegrationTest
+ module ::Foo
+ module Bar
+ class BazController < ActionController::Base
+ def index
+ render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
+ end
+ end
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ namespace :foo do
+ namespace :bar do
+ get "baz" => "baz#index"
+ end
+ end
+ get "pooh" => "pooh#index"
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ test "controller option which starts with '/' from multiple nested controller" do
+ get "/foo/bar/baz"
+ assert_equal "/pooh", @response.body
+ end
+end
+
+class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/~user" => ok
+ get "/young-and-fine" => ok
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ test 'recognizes tilde path' do
+ get "/~user"
+ assert_equal "200", @response.code
+ end
+
+ test 'recognizes minus path' do
+ get "/young-and-fine"
+ assert_equal "200", @response.code
+ end
+
+end
+
+class TestRedirectInterpolation < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/foo/:id" => redirect("/foo/bar/%{id}")
+ get "/bar/:id" => redirect(:path => "/foo/bar/%{id}")
+ get "/baz/:id" => redirect("/baz?id=%{id}&foo=?&bar=1#id-%{id}")
+ get "/foo/bar/:id" => ok
+ get "/baz" => ok
+ end
+ end
+
+ APP = build_app Routes
+ def app; APP end
+
+ test "redirect escapes interpolated parameters with redirect proc" do
+ get "/foo/1%3E"
+ verify_redirect "http://www.example.com/foo/bar/1%3E"
+ end
+
+ test "redirect escapes interpolated parameters with option proc" do
+ get "/bar/1%3E"
+ verify_redirect "http://www.example.com/foo/bar/1%3E"
+ end
+
+ test "path redirect escapes interpolated parameters correctly" do
+ get "/foo/1%201"
+ verify_redirect "http://www.example.com/foo/bar/1%201"
+
+ get "/baz/1%201"
+ verify_redirect "http://www.example.com/baz?id=1+1&foo=?&bar=1#id-1%201"
+ end
+
+private
+ def verify_redirect(url, status=301)
+ assert_equal status, @response.status
+ assert_equal url, @response.headers['Location']
+ assert_equal expected_redirect_body(url), @response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
+ end
+end
+
+class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/:foo" => ok, :constraints => lambda { |r| r.params[:foo] == 'foo' }
+ get "/:bar" => ok
+ end
+ end
+
+ APP = build_app Routes
+ def app; APP end
+
+ test "parameters are reset between constraint checks" do
+ get "/bar"
+ assert_equal nil, @request.params[:foo]
+ assert_equal "bar", @request.params[:bar]
+ end
+end
+
+class TestGlobRoutingMapper < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/*id" => redirect("/not_cars"), :constraints => {id: /dummy/}
+ get "/cars" => ok
+ end
+ end
+
+ #include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ def test_glob_constraint
+ get "/dummy"
+ assert_equal "301", @response.code
+ assert_equal "/not_cars", @response.header['Location'].match('/[^/]+$')[0]
+ end
+
+ def test_glob_constraint_skip_route
+ get "/cars"
+ assert_equal "200", @response.code
+ end
+ def test_glob_constraint_skip_all
+ get "/missing"
+ assert_equal "404", @response.code
+ end
+end
+
+class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/foo' => ok, as: :foo
+ get '/post(/:action(/:id))' => ok, as: :posts
+ get '/:foo/:foo_type/bars/:id' => ok, as: :bar
+ get '/projects/:id.:format' => ok, as: :project
+ get '/pages/:id' => ok, as: :page
+ get '/wiki/*page' => ok, as: :wiki
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ test 'enabled when not mounted and default_url_options is empty' do
+ assert Routes.url_helpers.optimize_routes_generation?
+ end
+
+ test 'named route called as singleton method' do
+ assert_equal '/foo', Routes.url_helpers.foo_path
+ end
+
+ test 'named route called on included module' do
+ assert_equal '/foo', foo_path
+ end
+
+ test 'nested optional segments are removed' do
+ assert_equal '/post', Routes.url_helpers.posts_path
+ assert_equal '/post', posts_path
+ end
+
+ test 'segments with same prefix are replaced correctly' do
+ assert_equal '/foo/baz/bars/1', Routes.url_helpers.bar_path('foo', 'baz', '1')
+ assert_equal '/foo/baz/bars/1', bar_path('foo', 'baz', '1')
+ end
+
+ test 'segments separated with a period are replaced correctly' do
+ assert_equal '/projects/1.json', Routes.url_helpers.project_path(1, :json)
+ assert_equal '/projects/1.json', project_path(1, :json)
+ end
+
+ test 'segments with question marks are escaped' do
+ assert_equal '/pages/foo%3Fbar', Routes.url_helpers.page_path('foo?bar')
+ assert_equal '/pages/foo%3Fbar', page_path('foo?bar')
+ end
+
+ test 'segments with slashes are escaped' do
+ assert_equal '/pages/foo%2Fbar', Routes.url_helpers.page_path('foo/bar')
+ assert_equal '/pages/foo%2Fbar', page_path('foo/bar')
+ end
+
+ test 'glob segments with question marks are escaped' do
+ assert_equal '/wiki/foo%3Fbar', Routes.url_helpers.wiki_path('foo?bar')
+ assert_equal '/wiki/foo%3Fbar', wiki_path('foo?bar')
+ end
+
+ test 'glob segments with slashes are not escaped' do
+ assert_equal '/wiki/foo/bar', Routes.url_helpers.wiki_path('foo/bar')
+ assert_equal '/wiki/foo/bar', wiki_path('foo/bar')
+ end
+end
+
+class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
+ class CategoriesController < ActionController::Base
+ def show
+ render :text => "categories#show"
+ end
+ end
+
+ class ProductsController < ActionController::Base
+ def show
+ render :text => "products#show"
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ scope :module => "test_named_route_url_helpers" do
+ get "/categories/:id" => 'categories#show', :as => :category
+ get "/products/:id" => 'products#show', :as => :product
+ end
+ end
+ end
+
+ APP = build_app Routes
+ def app; APP end
+
+ include Routes.url_helpers
+
+ test "url helpers do not ignore nil parameters when using non-optimized routes" do
+ Routes.stubs(:optimize_routes_generation?).returns(false)
+
+ get "/categories/1"
+ assert_response :success
+ assert_raises(ActionController::UrlGenerationError) { product_path(nil) }
+ end
+end
+
+class TestUrlConstraints < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ constraints :subdomain => 'admin' do
+ get '/' => ok, :as => :admin_root
+ end
+
+ scope :constraints => { :protocol => 'https://' } do
+ get '/' => ok, :as => :secure_root
+ end
+
+ get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 }
+
+ get '/search' => ok, :constraints => { :subdomain => false }
+
+ get '/logs' => ok, :constraints => { :subdomain => true }
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ test "constraints are copied to defaults when using constraints method" do
+ assert_equal 'http://admin.example.com/', admin_root_url
+
+ get 'http://admin.example.com/'
+ assert_response :success
+ end
+
+ test "constraints are copied to defaults when using scope constraints hash" do
+ assert_equal 'https://www.example.com/', secure_root_url
+
+ get 'https://www.example.com/'
+ assert_response :success
+ end
+
+ test "constraints are copied to defaults when using route constraints hash" do
+ assert_equal 'http://www.example.com:8080/', alternate_root_url
+
+ get 'http://www.example.com:8080/'
+ assert_response :success
+ end
+
+ test "false constraint expressions check for absence of values" do
+ get 'http://example.com/search'
+ assert_response :success
+ assert_equal 'http://example.com/search', search_url
+
+ get 'http://api.example.com/search'
+ assert_response :not_found
+ end
+
+ test "true constraint expressions check for presence of values" do
+ get 'http://api.example.com/logs'
+ assert_response :success
+ assert_equal 'http://api.example.com/logs', logs_url
+
+ get 'http://example.com/logs'
+ assert_response :not_found
+ end
+end
+
+class TestInvalidUrls < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def show
+ render :text => "foo#show"
+ end
+ end
+
+ test "invalid UTF-8 encoding returns a 400 Bad Request" do
+ with_routing do |set|
+ set.draw do
+ get "/bar/:id", :to => redirect("/foo/show/%{id}")
+ get "/foo/show(/:id)", :to => "test_invalid_urls/foo#show"
+ get "/foo(/:action(/:id))", :controller => "test_invalid_urls/foo"
+ get "/:controller(/:action(/:id))"
+ end
+
+ get "/%E2%EF%BF%BD%A6"
+ assert_response :bad_request
+
+ get "/foo/%E2%EF%BF%BD%A6"
+ assert_response :bad_request
+
+ get "/foo/show/%E2%EF%BF%BD%A6"
+ assert_response :bad_request
+
+ get "/bar/%E2%EF%BF%BD%A6"
+ assert_response :bad_request
+ end
+ end
+end
+
+class TestOptionalRootSegments < ActionDispatch::IntegrationTest
+ stub_controllers do |routes|
+ Routes = routes
+ Routes.draw do
+ get '/(page/:page)', :to => 'pages#index', :as => :root
+ end
+ end
+
+ APP = build_app Routes
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_optional_root_segments
+ get '/'
+ assert_equal 'pages#index', @response.body
+ assert_equal '/', root_path
+
+ get '/page/1'
+ assert_equal 'pages#index', @response.body
+ assert_equal '1', @request.params[:page]
+ assert_equal '/page/1', root_path('1')
+ assert_equal '/page/1', root_path(:page => '1')
+ end
+end
+
+class TestPortConstraints < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get '/integer', to: ok, constraints: { :port => 8080 }
+ get '/string', to: ok, constraints: { :port => '8080' }
+ get '/array', to: ok, constraints: { :port => [8080] }
+ get '/regexp', to: ok, constraints: { :port => /8080/ }
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ def test_integer_port_constraints
+ get 'http://www.example.com/integer'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/integer'
+ assert_response :success
+ end
+
+ def test_string_port_constraints
+ get 'http://www.example.com/string'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/string'
+ assert_response :success
+ end
+
+ def test_array_port_constraints
+ get 'http://www.example.com/array'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/array'
+ assert_response :success
+ end
+
+ def test_regexp_port_constraints
+ get 'http://www.example.com/regexp'
+ assert_response :not_found
+
+ get 'http://www.example.com:8080/regexp'
+ assert_response :success
+ end
+end
+
+class TestFormatConstraints < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get '/string', to: ok, constraints: { format: 'json' }
+ get '/regexp', to: ok, constraints: { format: /json/ }
+ get '/json_only', to: ok, format: true, constraints: { format: /json/ }
+ get '/xml_only', to: ok, format: 'xml'
+ end
+ end
+
+ include Routes.url_helpers
+ APP = build_app Routes
+ def app; APP end
+
+ def test_string_format_constraints
+ get 'http://www.example.com/string'
+ assert_response :success
+
+ get 'http://www.example.com/string.json'
+ assert_response :success
+
+ get 'http://www.example.com/string.html'
+ assert_response :not_found
+ end
+
+ def test_regexp_format_constraints
+ get 'http://www.example.com/regexp'
+ assert_response :success
+
+ get 'http://www.example.com/regexp.json'
+ assert_response :success
+
+ get 'http://www.example.com/regexp.html'
+ assert_response :not_found
+ end
+
+ def test_enforce_with_format_true_with_constraint
+ get 'http://www.example.com/json_only.json'
+ assert_response :success
+
+ get 'http://www.example.com/json_only.html'
+ assert_response :not_found
+
+ get 'http://www.example.com/json_only'
+ assert_response :not_found
+ end
+
+ def test_enforce_with_string
+ get 'http://www.example.com/xml_only.xml'
+ assert_response :success
+
+ get 'http://www.example.com/xml_only'
+ assert_response :success
+
+ get 'http://www.example.com/xml_only.json'
+ assert_response :not_found
+ end
+end
+
+class TestCallableConstraintValidation < ActionDispatch::IntegrationTest
+ def test_constraint_with_object_not_callable
+ assert_raises(ArgumentError) do
+ ActionDispatch::Routing::RouteSet.new.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/test', to: ok, constraints: Object.new
+ end
+ end
+ end
+end
+
+class TestRouteDefaults < ActionDispatch::IntegrationTest
+ stub_controllers do |routes|
+ Routes = routes
+ Routes.draw do
+ resources :posts, bucket_type: 'post'
+ resources :projects, defaults: { bucket_type: 'project' }
+ end
+ end
+
+ APP = build_app Routes
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_route_options_are_required_for_url_for
+ assert_raises(ActionController::UrlGenerationError) do
+ assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, only_path: true)
+ end
+
+ assert_equal '/posts/1', url_for(controller: 'posts', action: 'show', id: 1, bucket_type: 'post', only_path: true)
+ end
+
+ def test_route_defaults_are_not_required_for_url_for
+ assert_equal '/projects/1', url_for(controller: 'projects', action: 'show', id: 1, only_path: true)
+ end
+end
+
+class TestRackAppRouteGeneration < ActionDispatch::IntegrationTest
+ stub_controllers do |routes|
+ Routes = routes
+ Routes.draw do
+ rack_app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ mount rack_app, at: '/account', as: 'account'
+ mount rack_app, at: '/:locale/account', as: 'localized_account'
+ end
+ end
+
+ APP = build_app Routes
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_mounted_application_doesnt_match_unnamed_route
+ assert_raise(ActionController::UrlGenerationError) do
+ assert_equal '/account?controller=products', url_for(controller: 'products', action: 'index', only_path: true)
+ end
+
+ assert_raise(ActionController::UrlGenerationError) do
+ assert_equal '/de/account?controller=products', url_for(controller: 'products', action: 'index', :locale => 'de', only_path: true)
+ end
+ end
+end
+
+class TestRedirectRouteGeneration < ActionDispatch::IntegrationTest
+ stub_controllers do |routes|
+ Routes = routes
+ Routes.draw do
+ get '/account', to: redirect('/myaccount'), as: 'account'
+ get '/:locale/account', to: redirect('/%{locale}/myaccount'), as: 'localized_account'
+ end
+ end
+
+ APP = build_app Routes
+ def app
+ APP
+ end
+
+ include Routes.url_helpers
+
+ def test_redirect_doesnt_match_unnamed_route
+ assert_raise(ActionController::UrlGenerationError) do
+ assert_equal '/account?controller=products', url_for(controller: 'products', action: 'index', only_path: true)
+ end
+
+ assert_raise(ActionController::UrlGenerationError) do
+ assert_equal '/de/account?controller=products', url_for(controller: 'products', action: 'index', :locale => 'de', only_path: true)
+ end
+ end
+end
+
+class TestUrlGenerationErrors < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ get "/products/:id" => 'products#show', :as => :product
+ end
+ end
+
+ APP = build_app Routes
+ def app; APP end
+
+ include Routes.url_helpers
+
+ test "url helpers raise a helpful error message whem generation fails" do
+ url, missing = { action: 'show', controller: 'products', id: nil }, [:id]
+ message = "No route matches #{url.inspect} missing required keys: #{missing.inspect}"
+
+ # Optimized url helper
+ error = assert_raises(ActionController::UrlGenerationError){ product_path(nil) }
+ assert_equal message, error.message
+
+ # Non-optimized url helper
+ error = assert_raises(ActionController::UrlGenerationError, message){ product_path(id: nil) }
+ assert_equal message, error.message
+ end
+end
diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb
new file mode 100644
index 0000000000..fe1a7b4f86
--- /dev/null
+++ b/actionpack/test/dispatch/session/abstract_store_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ module Session
+ class AbstractStoreTest < ActiveSupport::TestCase
+ class MemoryStore < AbstractStore
+ def initialize(app)
+ @sessions = {}
+ super
+ end
+
+ def get_session(env, sid)
+ sid ||= 1
+ session = @sessions[sid] ||= {}
+ [sid, session]
+ end
+
+ def set_session(env, sid, session, options)
+ @sessions[sid] = session
+ end
+ end
+
+ def test_session_is_set
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ assert Request::Session.find @env
+ end
+
+ def test_new_session_object_is_merged_with_old
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ session = Request::Session.find @env
+ session['foo'] = 'bar'
+
+ as.call(@env)
+ session1 = Request::Session.find @env
+
+ assert_not_equal session, session1
+ assert_equal session.to_hash, session1.to_hash
+ end
+
+ private
+ def app(&block)
+ @env = nil
+ lambda { |env| @env = env }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
new file mode 100644
index 0000000000..9f810cad01
--- /dev/null
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -0,0 +1,179 @@
+require 'abstract_unit'
+require 'fixtures/session_autoload_test/session_autoload_test/foo'
+
+class CacheStoreTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def no_session_access
+ head :ok
+ end
+
+ def set_session_value
+ session[:foo] = "bar"
+ head :ok
+ end
+
+ def set_serialized_session_value
+ session[:foo] = SessionAutoloadTest::Foo.new
+ head :ok
+ end
+
+ def get_session_value
+ render :text => "foo: #{session[:foo].inspect}"
+ end
+
+ def get_session_id
+ render :text => "#{request.session_options[:id]}"
+ end
+
+ def call_reset_session
+ session[:bar]
+ reset_session
+ session[:bar] = "baz"
+ head :ok
+ end
+ end
+
+ def test_setting_and_getting_session_value
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ end
+ end
+
+ def test_getting_nil_session_value
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache"
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ end
+
+ def test_setting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+
+ get '/get_session_id'
+ assert_response :success
+ assert_not_equal session_id, response.body
+ end
+ end
+
+ def test_getting_session_id
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/get_session_id'
+ assert_response :success
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_deserializes_unloaded_class
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ get '/set_serialized_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_id'
+ assert_response :success
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
+ end
+ end
+
+ def test_prevents_session_fixation
+ with_test_route_set do
+ assert_equal nil, @cache.read('_session_id:0xhax')
+
+ cookies['_session_id'] = '0xhax'
+ get '/set_session_value'
+
+ assert_response :success
+ assert_not_equal '0xhax', cookies['_session_id']
+ assert_equal nil, @cache.read('_session_id:0xhax')
+ assert_equal({'foo' => 'bar'}, @cache.read("_session_id:#{cookies['_session_id']}"))
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => ::CacheStoreTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ @cache = ActiveSupport::Cache::MemoryStore.new
+ middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => @cache
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
new file mode 100644
index 0000000000..e99ff46edf
--- /dev/null
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -0,0 +1,356 @@
+require 'abstract_unit'
+require 'stringio'
+require 'active_support/key_generator'
+
+class CookieStoreTest < ActionDispatch::IntegrationTest
+ SessionKey = '_myapp_session'
+ SessionSecret = 'b3c631c314c0bbca50c1b2843150fe33'
+ Generator = ActiveSupport::LegacyKeyGenerator.new(SessionSecret)
+
+ Verifier = ActiveSupport::MessageVerifier.new(SessionSecret, :digest => 'SHA1')
+ SignedBar = Verifier.generate(:foo => "bar", :session_id => SecureRandom.hex(16))
+
+ class TestController < ActionController::Base
+ def no_session_access
+ head :ok
+ end
+
+ def persistent_session_id
+ render :text => session[:session_id]
+ end
+
+ def set_session_value
+ session[:foo] = "bar"
+ render :text => Rack::Utils.escape(Verifier.generate(session.to_hash))
+ end
+
+ def get_session_value
+ render :text => "foo: #{session[:foo].inspect}"
+ end
+
+ def get_session_id
+ render :text => "id: #{request.session_options[:id]}"
+ end
+
+ def get_class_after_reset_session
+ reset_session
+ render :text => "class: #{session.class}"
+ end
+
+ def call_session_clear
+ session.clear
+ head :ok
+ end
+
+ def call_reset_session
+ reset_session
+ head :ok
+ end
+
+ def raise_data_overflow
+ session[:foo] = 'bye!' * 1024
+ head :ok
+ end
+
+ def change_session_id
+ request.session_options[:id] = nil
+ get_session_id
+ end
+
+ def renew_session_id
+ request.session_options[:renew] = true
+ head :ok
+ end
+ end
+
+ def test_setting_session_value
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
+ headers['Set-Cookie']
+ end
+ end
+
+ def test_getting_session_value
+ with_test_route_set do
+ cookies[SessionKey] = SignedBar
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ end
+ end
+
+ def test_getting_session_id
+ with_test_route_set do
+ cookies[SessionKey] = SignedBar
+ get '/persistent_session_id'
+ assert_response :success
+ assert_equal response.body.size, 32
+ session_id = response.body
+
+ get '/get_session_id'
+ assert_response :success
+ assert_equal "id: #{session_id}", response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_disregards_tampered_sessions
+ with_test_route_set do
+ cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--123456780"
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_does_not_set_secure_cookies_over_http
+ with_test_route_set(:secure => true) do
+ get '/set_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie']
+ end
+ end
+
+ def test_properly_renew_cookies
+ with_test_route_set do
+ get '/set_session_value'
+ get '/persistent_session_id'
+ session_id = response.body
+ get '/renew_session_id'
+ get '/persistent_session_id'
+ assert_not_equal response.body, session_id
+ end
+ end
+
+ def test_does_set_secure_cookies_over_https
+ with_test_route_set(:secure => true) do
+ get '/set_session_value', nil, 'HTTPS' => 'on'
+ assert_response :success
+ assert_equal "_myapp_session=#{response.body}; path=/; secure; HttpOnly",
+ headers['Set-Cookie']
+ end
+ end
+
+ # {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"}
+ SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c"
+
+ def test_deserializes_unloaded_classes_on_get_id
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ cookies[SessionKey] = SignedSerializedCookie
+ get '/get_session_id'
+ assert_response :success
+ assert_equal 'id: ce8b0752a6ab7c7af3cdb8a80e6b9e46', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_deserializes_unloaded_classes_on_get_value
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ cookies[SessionKey] = SignedSerializedCookie
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_close_raises_when_data_overflows
+ with_test_route_set do
+ assert_raise(ActionDispatch::Cookies::CookieOverflow) {
+ get '/raise_data_overflow'
+ }
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_is_not_accessed
+ with_test_route_set do
+ get '/no_session_access'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie']
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_is_unchanged
+ with_test_route_set do
+ cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" +
+ "fef868465920f415f2c0652d6910d3af288a0367"
+ get '/no_session_access'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie']
+ end
+ end
+
+ def test_setting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ session_payload = response.body
+ assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
+ headers['Set-Cookie']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+ assert_not_nil session_payload
+ assert_not_equal session_payload, cookies[SessionKey]
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_class_type_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
+ headers['Set-Cookie']
+
+ get '/get_class_after_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+ assert_equal 'class: ActionDispatch::Request::Session', response.body
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil headers['Set-Cookie'], "should only create session on write, not read"
+ end
+ end
+
+ def test_setting_session_value_after_session_clear
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert_equal "_myapp_session=#{response.body}; path=/; HttpOnly",
+ headers['Set-Cookie']
+
+ get '/call_session_clear'
+ assert_response :success
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_persistent_session_id
+ with_test_route_set do
+ cookies[SessionKey] = SignedBar
+ get '/persistent_session_id'
+ assert_response :success
+ assert_equal response.body.size, 32
+ session_id = response.body
+ get '/persistent_session_id'
+ assert_equal session_id, response.body
+ reset!
+ get '/persistent_session_id'
+ assert_not_equal session_id, response.body
+ end
+ end
+
+ def test_setting_session_id_to_nil_is_respected
+ with_test_route_set do
+ cookies[SessionKey] = SignedBar
+
+ get "/get_session_id"
+ sid = response.body
+ assert_equal sid.size, 36
+
+ get "/change_session_id"
+ assert_not_equal sid, response.body
+ end
+ end
+
+ def test_session_store_with_expire_after
+ with_test_route_set(:expire_after => 5.hours) do
+ # First request accesses the session
+ time = Time.local(2008, 4, 24)
+ Time.stubs(:now).returns(time)
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
+
+ cookies[SessionKey] = SignedBar
+
+ get '/set_session_value'
+ assert_response :success
+
+ cookie_body = response.body
+ assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly",
+ headers['Set-Cookie']
+
+ # Second request does not access the session
+ time = Time.local(2008, 4, 25)
+ Time.stubs(:now).returns(time)
+ expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d %b %Y %H:%M:%S -0000")
+
+ get '/no_session_access'
+ assert_response :success
+
+ assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly",
+ headers['Set-Cookie']
+ end
+ end
+
+ def test_session_store_with_explicit_domain
+ with_test_route_set(:domain => "example.es") do
+ get '/set_session_value'
+ assert_match(/domain=example\.es/, headers['Set-Cookie'])
+ headers['Set-Cookie']
+ end
+ end
+
+ def test_session_store_without_domain
+ with_test_route_set do
+ get '/set_session_value'
+ assert_no_match(/domain\=/, headers['Set-Cookie'])
+ end
+ end
+
+ def test_session_store_with_nil_domain
+ with_test_route_set(:domain => nil) do
+ get '/set_session_value'
+ assert_no_match(/domain\=/, headers['Set-Cookie'])
+ end
+ end
+
+ def test_session_store_with_all_domains
+ with_test_route_set(:domain => :all) do
+ get '/set_session_value'
+ assert_match(/domain=\.example\.com/, headers['Set-Cookie'])
+ end
+ end
+
+ private
+
+ # Overwrite get to send SessionSecret in env hash
+ def get(path, parameters = nil, env = {})
+ env["action_dispatch.key_generator"] ||= Generator
+ super
+ end
+
+ def with_test_route_set(options = {})
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => ::CookieStoreTest::TestController
+ end
+
+ options = { :key => SessionKey }.merge!(options)
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::Session::CookieStore, options
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+
+end
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
new file mode 100644
index 0000000000..f7a06cfed4
--- /dev/null
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -0,0 +1,201 @@
+require 'abstract_unit'
+require 'securerandom'
+
+# You need to start a memcached server inorder to run these tests
+class MemCacheStoreTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def no_session_access
+ head :ok
+ end
+
+ def set_session_value
+ session[:foo] = "bar"
+ head :ok
+ end
+
+ def set_serialized_session_value
+ session[:foo] = SessionAutoloadTest::Foo.new
+ head :ok
+ end
+
+ def get_session_value
+ render :text => "foo: #{session[:foo].inspect}"
+ end
+
+ def get_session_id
+ render :text => "#{request.session_options[:id]}"
+ end
+
+ def call_reset_session
+ session[:bar]
+ reset_session
+ session[:bar] = "baz"
+ head :ok
+ end
+ end
+
+ begin
+ require 'dalli'
+ ss = Dalli::Client.new('localhost:11211').stats
+ raise Dalli::DalliError unless ss['localhost:11211']
+
+ def test_setting_and_getting_session_value
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_getting_nil_session_value
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_setting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+
+ get '/get_session_id'
+ assert_response :success
+ assert_not_equal session_id, response.body
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_getting_session_id
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/get_session_id'
+ assert_response :success
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_deserializes_unloaded_class
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ get '/set_serialized_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_id'
+ assert_response :success
+ end
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+
+ def test_prevents_session_fixation
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ session_id = cookies['_session_id']
+
+ reset!
+
+ get '/set_session_value', :_session_id => session_id
+ assert_response :success
+ assert_not_equal session_id, cookies['_session_id']
+ end
+ rescue Dalli::RingError => ex
+ skip ex.message, ex.backtrace
+ end
+ rescue LoadError, RuntimeError, Dalli::DalliError
+ $stderr.puts "Skipping MemCacheStoreTest tests. Start memcached and try again."
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => ::MemCacheStoreTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id', :namespace => "mem_cache_store_test:#{SecureRandom.hex(10)}"
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb
new file mode 100644
index 0000000000..d30461a623
--- /dev/null
+++ b/actionpack/test/dispatch/session/test_session_test.rb
@@ -0,0 +1,43 @@
+require 'abstract_unit'
+require 'stringio'
+
+class ActionController::TestSessionTest < ActiveSupport::TestCase
+ def test_initialize_with_values
+ session = ActionController::TestSession.new(one: 'one', two: 'two')
+ assert_equal('one', session[:one])
+ assert_equal('two', session[:two])
+ end
+
+ def test_setting_session_item_sets_item
+ session = ActionController::TestSession.new
+ session[:key] = 'value'
+ assert_equal('value', session[:key])
+ end
+
+ def test_calling_delete_removes_item_and_returns_its_value
+ session = ActionController::TestSession.new
+ session[:key] = 'value'
+ assert_equal('value', session[:key])
+ assert_equal('value', session.delete(:key))
+ assert_nil(session[:key])
+ end
+
+ def test_calling_update_with_params_passes_to_attributes
+ session = ActionController::TestSession.new
+ session.update('key' => 'value')
+ assert_equal('value', session[:key])
+ end
+
+ def test_clear_empties_session
+ session = ActionController::TestSession.new(one: 'one', two: 'two')
+ session.clear
+ assert_nil(session[:one])
+ assert_nil(session[:two])
+ end
+
+ def test_keys_and_values
+ session = ActionController::TestSession.new(one: '1', two: '2')
+ assert_equal %w(one two), session.keys
+ assert_equal %w(1 2), session.values
+ end
+end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
new file mode 100644
index 0000000000..323fbc285e
--- /dev/null
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -0,0 +1,115 @@
+require 'abstract_unit'
+
+class ShowExceptionsTest < ActionDispatch::IntegrationTest
+
+ class Boomer
+ def call(env)
+ req = ActionDispatch::Request.new(env)
+ case req.path
+ when "/not_found"
+ raise AbstractController::ActionNotFound
+ when "/bad_params"
+ raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new)
+ when "/method_not_allowed"
+ raise ActionController::MethodNotAllowed
+ when "/unknown_http_method"
+ raise ActionController::UnknownHttpMethod
+ when "/not_found_original_exception"
+ raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ else
+ raise "puke!"
+ end
+ end
+ end
+
+ ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public"))
+
+ test "skip exceptions app if not showing exceptions" do
+ @app = ProductionApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => false}
+ end
+ end
+
+ test "rescue with error page" do
+ @app = ProductionApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 500
+ assert_equal "500 error fixture\n", body
+
+ get "/bad_params", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 400
+ assert_equal "400 error fixture\n", body
+
+ get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_equal "404 error fixture\n", body
+
+ get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_equal "", body
+
+ get "/unknown_http_method", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_equal "", body
+ end
+
+ test "localize rescue error page" do
+ old_locale, I18n.locale = I18n.locale, :da
+
+ begin
+ @app = ProductionApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 500
+ assert_equal "500 localized error fixture\n", body
+
+ get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_equal "404 error fixture\n", body
+ ensure
+ I18n.locale = old_locale
+ end
+ end
+
+ test "sets the HTTP charset parameter" do
+ @app = ProductionApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
+ end
+
+ test "show registered original exception for wrapped exceptions" do
+ @app = ProductionApp
+
+ get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/404 error/, body)
+ end
+
+ test "calls custom exceptions app" do
+ exceptions_app = lambda do |env|
+ assert_kind_of AbstractController::ActionNotFound, env["action_dispatch.exception"]
+ assert_equal "/404", env["PATH_INFO"]
+ assert_equal "/not_found_original_exception", env["action_dispatch.original_path"]
+ [404, { "Content-Type" => "text/plain" }, ["YOU FAILED BRO"]]
+ end
+
+ @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
+ get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_equal "YOU FAILED BRO", body
+ end
+
+ test "returns an empty response if custom exceptions app returns X-Cascade pass" do
+ exceptions_app = lambda do |env|
+ [404, { "X-Cascade" => "pass" }, []]
+ end
+
+ @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
+ get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_equal "", body
+ end
+end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
new file mode 100644
index 0000000000..c3598c5e8e
--- /dev/null
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -0,0 +1,219 @@
+require 'abstract_unit'
+
+class SSLTest < ActionDispatch::IntegrationTest
+ def default_app
+ lambda { |env|
+ headers = {'Content-Type' => "text/html"}
+ headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
+ [200, headers, ["OK"]]
+ }
+ end
+
+ def app
+ @app ||= ActionDispatch::SSL.new(default_app)
+ end
+ attr_writer :app
+
+ def test_allows_https_url
+ get "https://example.org/path?key=value"
+ assert_response :success
+ end
+
+ def test_allows_https_proxy_header_url
+ get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https"
+ assert_response :success
+ end
+
+ def test_redirects_http_to_https
+ get "http://example.org/path?key=value"
+ assert_response :redirect
+ assert_equal "https://example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_hsts_header_by_default
+ get "https://example.org/"
+ assert_equal "max-age=31536000",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_no_hsts_with_insecure_connection
+ get "http://example.org/"
+ assert_not response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_header
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => true)
+ get "https://example.org/"
+ assert_equal "max-age=31536000",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_disable_hsts_header
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => false)
+ get "https://example.org/"
+ assert_not response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_expires
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 500 })
+ get "https://example.org/"
+ assert_equal "max-age=500",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_expires_with_duration
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 1.year })
+ get "https://example.org/"
+ assert_equal "max-age=31557600",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_include_subdomains
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true })
+ get "https://example.org/"
+ assert_equal "max-age=31536000; includeSubDomains",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_flag_cookies_as_secure
+ get "https://example.org/"
+ assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" ],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_at_end_of_line
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; HttpOnly; secure"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_more_spaces_before
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; HttpOnly; secure"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_more_spaces_after
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; secure; HttpOnly"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; secure; HttpOnly"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+
+ def test_flag_cookies_as_secure_with_has_not_spaces_before
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/;secure; HttpOnly"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/;secure; HttpOnly"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_has_not_spaces_after
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; secure;HttpOnly"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; secure;HttpOnly"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_ignore_case
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; Secure; HttpOnly"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; Secure; HttpOnly"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_no_cookies
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ [200, {'Content-Type' => "text/html"}, ["OK"]]
+ })
+ get "https://example.org/"
+ assert !response.headers['Set-Cookie']
+ end
+
+ def test_redirect_to_host
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_port
+ self.app = ActionDispatch::SSL.new(default_app, :port => 8443)
+ get "http://example.org/path?key=value"
+ assert_equal "https://example.org:8443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_host_and_port
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org", :port => 8443)
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org:8443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_host_with_port
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org:443")
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org:443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_secure_host_when_on_subdomain
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
+ get "http://ssl.example.org/path?key=value"
+ assert_equal "https://ssl.example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_secure_subdomain_when_on_deep_subdomain
+ self.app = ActionDispatch::SSL.new(default_app, :host => "example.co.uk")
+ get "http://double.rainbow.what.does.it.mean.example.co.uk/path?key=value"
+ assert_equal "https://example.co.uk/path?key=value",
+ response.headers['Location']
+ end
+end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
new file mode 100644
index 0000000000..afdda70748
--- /dev/null
+++ b/actionpack/test/dispatch/static_test.rb
@@ -0,0 +1,175 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'rbconfig'
+
+module StaticTests
+ def test_serves_dynamic_content
+ assert_equal "Hello, World!", get("/nofile").body
+ end
+
+ def test_handles_urls_with_bad_encoding
+ assert_equal "Hello, World!", get("/doorkeeper%E3E4").body
+ end
+
+ def test_sets_cache_control
+ response = get("/index.html")
+ assert_html "/index.html", response
+ assert_equal "public, max-age=60", response.headers["Cache-Control"]
+ end
+
+ def test_serves_static_index_at_root
+ assert_html "/index.html", get("/index.html")
+ assert_html "/index.html", get("/index")
+ assert_html "/index.html", get("/")
+ assert_html "/index.html", get("")
+ end
+
+ def test_serves_static_file_in_directory
+ assert_html "/foo/bar.html", get("/foo/bar.html")
+ assert_html "/foo/bar.html", get("/foo/bar/")
+ assert_html "/foo/bar.html", get("/foo/bar")
+ end
+
+ def test_serves_static_index_file_in_directory
+ assert_html "/foo/index.html", get("/foo/index.html")
+ assert_html "/foo/index.html", get("/foo/")
+ assert_html "/foo/index.html", get("/foo")
+ end
+
+ def test_served_static_file_with_non_english_filename
+ jruby_skip "Stop skipping if following bug gets fixed: " \
+ "http://jira.codehaus.org/browse/JRUBY-7192"
+ assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
+ end
+
+
+ def test_serves_static_file_with_exclamation_mark_in_filename
+ with_static_file "/foo/foo!bar.html" do |file|
+ assert_html file, get("/foo/foo%21bar.html")
+ assert_html file, get("/foo/foo!bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_dollar_sign_in_filename
+ with_static_file "/foo/foo$bar.html" do |file|
+ assert_html file, get("/foo/foo%24bar.html")
+ assert_html file, get("/foo/foo$bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_ampersand_in_filename
+ with_static_file "/foo/foo&bar.html" do |file|
+ assert_html file, get("/foo/foo%26bar.html")
+ assert_html file, get("/foo/foo&bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_apostrophe_in_filename
+ with_static_file "/foo/foo'bar.html" do |file|
+ assert_html file, get("/foo/foo%27bar.html")
+ assert_html file, get("/foo/foo'bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_parentheses_in_filename
+ with_static_file "/foo/foo(bar).html" do |file|
+ assert_html file, get("/foo/foo%28bar%29.html")
+ assert_html file, get("/foo/foo(bar).html")
+ end
+ end
+
+ def test_serves_static_file_with_plus_sign_in_filename
+ with_static_file "/foo/foo+bar.html" do |file|
+ assert_html file, get("/foo/foo%2Bbar.html")
+ assert_html file, get("/foo/foo+bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_comma_in_filename
+ with_static_file "/foo/foo,bar.html" do |file|
+ assert_html file, get("/foo/foo%2Cbar.html")
+ assert_html file, get("/foo/foo,bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_semi_colon_in_filename
+ with_static_file "/foo/foo;bar.html" do |file|
+ assert_html file, get("/foo/foo%3Bbar.html")
+ assert_html file, get("/foo/foo;bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_at_symbol_in_filename
+ with_static_file "/foo/foo@bar.html" do |file|
+ assert_html file, get("/foo/foo%40bar.html")
+ assert_html file, get("/foo/foo@bar.html")
+ end
+ end
+
+ # Windows doesn't allow \ / : * ? " < > | in filenames
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ def test_serves_static_file_with_colon
+ with_static_file "/foo/foo:bar.html" do |file|
+ assert_html file, get("/foo/foo%3Abar.html")
+ assert_html file, get("/foo/foo:bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_asterisk
+ with_static_file "/foo/foo*bar.html" do |file|
+ assert_html file, get("/foo/foo%2Abar.html")
+ assert_html file, get("/foo/foo*bar.html")
+ end
+ end
+ end
+
+ private
+
+ def assert_html(body, response)
+ assert_equal body, response.body
+ assert_equal "text/html", response.headers["Content-Type"]
+ end
+
+ def get(path)
+ Rack::MockRequest.new(@app).request("GET", path)
+ end
+
+ def with_static_file(file)
+ path = "#{FIXTURE_LOAD_PATH}/#{public_path}" + file
+ begin
+ File.open(path, "wb+") { |f| f.write(file) }
+ rescue Errno::EPROTO
+ skip "Couldn't create a file #{path}"
+ end
+
+ yield file
+ ensure
+ File.delete(path) if File.exist? path
+ end
+end
+
+class StaticTest < ActiveSupport::TestCase
+ DummyApp = lambda { |env|
+ [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]]
+ }
+
+ def setup
+ @app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60")
+ end
+
+ def public_path
+ "public"
+ end
+
+ include StaticTests
+end
+
+class StaticEncodingTest < StaticTest
+ def setup
+ @app = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/公共", "public, max-age=60")
+ end
+
+ def public_path
+ "公共"
+ end
+end
diff --git a/actionpack/test/dispatch/template_assertions_test.rb b/actionpack/test/dispatch/template_assertions_test.rb
new file mode 100644
index 0000000000..3c393f937b
--- /dev/null
+++ b/actionpack/test/dispatch/template_assertions_test.rb
@@ -0,0 +1,98 @@
+require 'abstract_unit'
+
+class AssertTemplateController < ActionController::Base
+ def render_with_partial
+ render partial: 'test/partial'
+ end
+
+ def render_with_template
+ render 'test/hello_world'
+ end
+
+ def render_with_layout
+ @variable_for_layout = nil
+ render 'test/hello_world', layout: "layouts/standard"
+ end
+
+ def render_with_file
+ render file: 'README.rdoc'
+ end
+
+ def render_nothing
+ head :ok
+ end
+end
+
+class AssertTemplateControllerTest < ActionDispatch::IntegrationTest
+ def test_template_reset_between_requests
+ get '/assert_template/render_with_template'
+ assert_template 'test/hello_world'
+
+ get '/assert_template/render_nothing'
+ assert_template nil
+ end
+
+ def test_partial_reset_between_requests
+ get '/assert_template/render_with_partial'
+ assert_template partial: 'test/_partial'
+
+ get '/assert_template/render_nothing'
+ assert_template partial: nil
+ end
+
+ def test_layout_reset_between_requests
+ get '/assert_template/render_with_layout'
+ assert_template layout: 'layouts/standard'
+
+ get '/assert_template/render_nothing'
+ assert_template layout: nil
+ end
+
+ def test_file_reset_between_requests
+ get '/assert_template/render_with_file'
+ assert_template file: 'README.rdoc'
+
+ get '/assert_template/render_nothing'
+ assert_template file: nil
+ end
+
+ def test_template_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_template'
+ session.assert_template 'test/hello_world'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template nil
+ end
+ end
+
+ def test_partial_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_partial'
+ session.assert_template partial: 'test/_partial'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template partial: nil
+ end
+ end
+
+ def test_layout_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_layout'
+ session.assert_template layout: 'layouts/standard'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template layout: nil
+ end
+ end
+
+ def test_file_reset_between_requests_when_opening_a_session
+ open_session do |session|
+ session.get '/assert_template/render_with_file'
+ session.assert_template file: 'README.rdoc'
+
+ session.get '/assert_template/render_nothing'
+ session.assert_template file: nil
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb
new file mode 100644
index 0000000000..65ad8677f3
--- /dev/null
+++ b/actionpack/test/dispatch/test_request_test.rb
@@ -0,0 +1,99 @@
+require 'abstract_unit'
+
+class TestRequestTest < ActiveSupport::TestCase
+ test "sane defaults" do
+ env = ActionDispatch::TestRequest.new.env
+
+ assert_equal "GET", env.delete("REQUEST_METHOD")
+ assert_equal "off", env.delete("HTTPS")
+ assert_equal "http", env.delete("rack.url_scheme")
+ assert_equal "example.org", env.delete("SERVER_NAME")
+ assert_equal "80", env.delete("SERVER_PORT")
+ assert_equal "/", env.delete("PATH_INFO")
+ assert_equal "", env.delete("SCRIPT_NAME")
+ assert_equal "", env.delete("QUERY_STRING")
+ assert_equal "0", env.delete("CONTENT_LENGTH")
+
+ assert_equal "test.host", env.delete("HTTP_HOST")
+ assert_equal "0.0.0.0", env.delete("REMOTE_ADDR")
+ assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT")
+
+ assert_equal [1, 2], env.delete("rack.version")
+ assert_equal "", env.delete("rack.input").string
+ assert_kind_of StringIO, env.delete("rack.errors")
+ assert_equal true, env.delete("rack.multithread")
+ assert_equal true, env.delete("rack.multiprocess")
+ assert_equal false, env.delete("rack.run_once")
+
+ assert env.empty?, env.inspect
+ end
+
+ test "cookie jar" do
+ req = ActionDispatch::TestRequest.new
+
+ assert_equal({}, req.cookies)
+ assert_equal nil, req.env["HTTP_COOKIE"]
+
+ req.cookie_jar["user_name"] = "david"
+ assert_cookies({"user_name" => "david"}, req.cookie_jar)
+
+ req.cookie_jar["login"] = "XJ-122"
+ assert_cookies({"user_name" => "david", "login" => "XJ-122"}, req.cookie_jar)
+
+ assert_nothing_raised do
+ req.cookie_jar["login"] = nil
+ assert_cookies({"user_name" => "david", "login" => nil}, req.cookie_jar)
+ end
+
+ req.cookie_jar.delete(:login)
+ assert_cookies({"user_name" => "david"}, req.cookie_jar)
+
+ req.cookie_jar.clear
+ assert_cookies({}, req.cookie_jar)
+
+ req.cookie_jar.update(:user_name => "david")
+ assert_cookies({"user_name" => "david"}, req.cookie_jar)
+ end
+
+ test "does not complain when Rails.application is nil" do
+ Rails.stubs(:application).returns(nil)
+ req = ActionDispatch::TestRequest.new
+
+ assert_equal false, req.env.empty?
+ end
+
+ test "default remote address is 0.0.0.0" do
+ req = ActionDispatch::TestRequest.new
+ assert_equal '0.0.0.0', req.remote_addr
+ end
+
+ test "allows remote address to be overridden" do
+ req = ActionDispatch::TestRequest.new('REMOTE_ADDR' => '127.0.0.1')
+ assert_equal '127.0.0.1', req.remote_addr
+ end
+
+ test "default host is test.host" do
+ req = ActionDispatch::TestRequest.new
+ assert_equal 'test.host', req.host
+ end
+
+ test "allows host to be overridden" do
+ req = ActionDispatch::TestRequest.new('HTTP_HOST' => 'www.example.com')
+ assert_equal 'www.example.com', req.host
+ end
+
+ test "default user agent is 'Rails Testing'" do
+ req = ActionDispatch::TestRequest.new
+ assert_equal 'Rails Testing', req.user_agent
+ end
+
+ test "allows user agent to be overridden" do
+ req = ActionDispatch::TestRequest.new('HTTP_USER_AGENT' => 'GoogleBot')
+ assert_equal 'GoogleBot', req.user_agent
+ end
+
+ private
+ def assert_cookies(expected, cookie_jar)
+ assert_equal(expected, cookie_jar.instance_variable_get("@cookies"))
+ end
+end
diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb
new file mode 100644
index 0000000000..dc17668def
--- /dev/null
+++ b/actionpack/test/dispatch/test_response_test.rb
@@ -0,0 +1,21 @@
+require 'abstract_unit'
+
+class TestResponseTest < ActiveSupport::TestCase
+ def assert_response_code_range(range, predicate)
+ response = ActionDispatch::TestResponse.new
+ (0..599).each do |status|
+ response.status = status
+ assert_equal range.include?(status), response.send(predicate),
+ "ActionDispatch::TestResponse.new(#{status}).#{predicate}"
+ end
+ end
+
+ test "helpers" do
+ assert_response_code_range 200..299, :success?
+ assert_response_code_range [404], :missing?
+ assert_response_code_range 300..399, :redirect?
+ assert_response_code_range 500..599, :error?
+ assert_response_code_range 500..599, :server_error?
+ assert_response_code_range 400..499, :client_error?
+ end
+end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
new file mode 100644
index 0000000000..55ebbd5143
--- /dev/null
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -0,0 +1,105 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ class UploadedFileTest < ActiveSupport::TestCase
+ def test_constructor_with_argument_error
+ assert_raises(ArgumentError) do
+ Http::UploadedFile.new({})
+ end
+ end
+
+ def test_original_filename
+ uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
+ assert_equal 'foo', uf.original_filename
+ end
+
+ def test_filename_should_be_in_utf_8
+ uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
+ assert_equal "UTF-8", uf.original_filename.encoding.to_s
+ end
+
+ def test_filename_should_always_be_in_utf_8
+ uf = Http::UploadedFile.new(:filename => 'foo'.encode(Encoding::SHIFT_JIS),
+ :tempfile => Object.new)
+ assert_equal "UTF-8", uf.original_filename.encoding.to_s
+ end
+
+ def test_content_type
+ uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new)
+ assert_equal 'foo', uf.content_type
+ end
+
+ def test_headers
+ uf = Http::UploadedFile.new(:head => 'foo', :tempfile => Object.new)
+ assert_equal 'foo', uf.headers
+ end
+
+ def test_tempfile
+ uf = Http::UploadedFile.new(:tempfile => 'foo')
+ assert_equal 'foo', uf.tempfile
+ end
+
+ def test_to_io_returns_the_tempfile
+ tf = Object.new
+ uf = Http::UploadedFile.new(:tempfile => tf)
+ assert_equal tf, uf.to_io
+ end
+
+ def test_delegates_path_to_tempfile
+ tf = Class.new { def path; 'thunderhorse' end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse', uf.path
+ end
+
+ def test_delegates_open_to_tempfile
+ tf = Class.new { def open; 'thunderhorse' end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse', uf.open
+ end
+
+ def test_delegates_close_to_tempfile
+ tf = Class.new { def close(unlink_now=false); 'thunderhorse' end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse', uf.close
+ end
+
+ def test_close_accepts_parameter
+ tf = Class.new { def close(unlink_now=false); "thunderhorse: #{unlink_now}" end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse: true', uf.close(true)
+ end
+
+ def test_delegates_read_to_tempfile
+ tf = Class.new { def read(length=nil, buffer=nil); 'thunderhorse' end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal 'thunderhorse', uf.read
+ end
+
+ def test_delegates_read_to_tempfile_with_params
+ tf = Class.new { def read(length=nil, buffer=nil); [length, buffer] end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse })
+ end
+
+ def test_delegate_respects_respond_to?
+ tf = Class.new { def read; yield end; private :read }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert_raises(NoMethodError) do
+ uf.read
+ end
+ end
+
+ def test_delegate_eof_to_tempfile
+ tf = Class.new { def eof?; true end; }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert uf.eof?
+ end
+
+ def test_respond_to?
+ tf = Class.new { def read; yield end }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert uf.respond_to?(:headers), 'responds to headers'
+ assert uf.respond_to?(:read), 'responds to read'
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
new file mode 100644
index 0000000000..8f79e7bf9a
--- /dev/null
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -0,0 +1,134 @@
+require 'abstract_unit'
+
+module TestUrlGeneration
+ class WithMountPoint < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new
+ include Routes.url_helpers
+
+ class ::MyRouteGeneratingController < ActionController::Base
+ include Routes.url_helpers
+ def index
+ render :text => foo_path
+ end
+ end
+
+ Routes.draw do
+ get "/foo", :to => "my_route_generating#index", :as => :foo
+
+ resources :bars
+
+ mount MyRouteGeneratingController.action(:index), at: '/bar'
+ end
+
+ APP = build_app Routes
+
+ def _routes
+ Routes
+ end
+
+ def app
+ APP
+ end
+
+ test "generating URLS normally" do
+ assert_equal "/foo", foo_path
+ end
+
+ test "accepting a :script_name option" do
+ assert_equal "/bar/foo", foo_path(:script_name => "/bar")
+ end
+
+ test "the request's SCRIPT_NAME takes precedence over the route" do
+ get "/foo", {}, 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes
+ assert_equal "/new/foo", response.body
+ end
+
+ test "the request's SCRIPT_NAME wraps the mounted app's" do
+ get '/new/bar/foo', {}, 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes
+ assert_equal "/new/bar/foo", response.body
+ end
+
+ test "handling http protocol with https set" do
+ https!
+ assert_equal "http://www.example.com/foo", foo_url(:protocol => "http")
+ end
+
+ test "extracting protocol from host when protocol not present" do
+ assert_equal "httpz://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: nil)
+ end
+
+ test "formatting host when protocol is present" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "httpz://www.example.com", protocol: "http://")
+ end
+
+ test "default ports are removed from the host" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:80", protocol: "http://")
+ assert_equal "https://www.example.com/foo", foo_url(host: "www.example.com:443", protocol: "https://")
+ end
+
+ test "port is extracted from the host" do
+ assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "http://")
+ assert_equal "//www.example.com:8080/foo", foo_url(host: "www.example.com:8080", protocol: "//")
+ assert_equal "//www.example.com:80/foo", foo_url(host: "www.example.com:80", protocol: "//")
+ end
+
+ test "port option is used" do
+ assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com", protocol: "http://", port: 8080)
+ assert_equal "//www.example.com:8080/foo", foo_url(host: "www.example.com", protocol: "//", port: 8080)
+ assert_equal "//www.example.com:80/foo", foo_url(host: "www.example.com", protocol: "//", port: 80)
+ end
+
+ test "port option overrides the host" do
+ assert_equal "http://www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: 8080)
+ assert_equal "//www.example.com:8080/foo", foo_url(host: "www.example.com:8443", protocol: "//", port: 8080)
+ assert_equal "//www.example.com:80/foo", foo_url(host: "www.example.com:443", protocol: "//", port: 80)
+ end
+
+ test "port option disables the host when set to nil" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: nil)
+ assert_equal "//www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "//", port: nil)
+ end
+
+ test "port option disables the host when set to false" do
+ assert_equal "http://www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "http://", port: false)
+ assert_equal "//www.example.com/foo", foo_url(host: "www.example.com:8443", protocol: "//", port: false)
+ end
+
+ test "keep subdomain when key is true" do
+ assert_equal "http://www.example.com/foo", foo_url(subdomain: true)
+ end
+
+ test "keep subdomain when key is missing" do
+ assert_equal "http://www.example.com/foo", foo_url
+ end
+
+ test "omit subdomain when key is nil" do
+ assert_equal "http://example.com/foo", foo_url(subdomain: nil)
+ end
+
+ test "omit subdomain when key is false" do
+ assert_equal "http://example.com/foo", foo_url(subdomain: false)
+ end
+
+ test "omit subdomain when key is blank" do
+ assert_equal "http://example.com/foo", foo_url(subdomain: "")
+ end
+
+ test "generating URLs with trailing slashes" do
+ assert_equal "/bars.json", bars_path(
+ trailing_slash: true,
+ format: 'json'
+ )
+ end
+
+ test "generating URLS with querystring and trailing slashes" do
+ assert_equal "/bars.json?a=b", bars_path(
+ trailing_slash: true,
+ a: 'b',
+ format: 'json'
+ )
+ end
+
+ end
+end
+
diff --git a/actionpack/test/fixtures/_top_level_partial_only.erb b/actionpack/test/fixtures/_top_level_partial_only.erb
new file mode 100644
index 0000000000..44f25b61d0
--- /dev/null
+++ b/actionpack/test/fixtures/_top_level_partial_only.erb
@@ -0,0 +1 @@
+top level partial \ No newline at end of file
diff --git a/actionpack/test/fixtures/alternate_helpers/foo_helper.rb b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb
new file mode 100644
index 0000000000..2528584473
--- /dev/null
+++ b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb
@@ -0,0 +1,3 @@
+module FooHelper
+ redefine_method(:baz) {}
+end
diff --git a/actionpack/test/fixtures/bad_customers/_bad_customer.html.erb b/actionpack/test/fixtures/bad_customers/_bad_customer.html.erb
new file mode 100644
index 0000000000..d22af431ec
--- /dev/null
+++ b/actionpack/test/fixtures/bad_customers/_bad_customer.html.erb
@@ -0,0 +1 @@
+<%= greeting %> bad customer: <%= bad_customer.name %><%= bad_customer_counter %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/company.rb b/actionpack/test/fixtures/company.rb
new file mode 100644
index 0000000000..f3ac3642fa
--- /dev/null
+++ b/actionpack/test/fixtures/company.rb
@@ -0,0 +1,9 @@
+class Company < ActiveRecord::Base
+ has_one :mascot
+ self.sequence_name = :companies_nonstd_seq
+
+ validates_presence_of :name
+ def validate
+ errors.add('rating', 'rating should not be 2') if rating == 2
+ end
+end
diff --git a/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb b/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb
new file mode 100644
index 0000000000..8491ab9f80
--- /dev/null
+++ b/actionpack/test/fixtures/filter_test/implicit_actions/edit.html.erb
@@ -0,0 +1 @@
+edit \ No newline at end of file
diff --git a/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb b/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb
new file mode 100644
index 0000000000..0a89cecf05
--- /dev/null
+++ b/actionpack/test/fixtures/filter_test/implicit_actions/show.html.erb
@@ -0,0 +1 @@
+show \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/_partial.erb b/actionpack/test/fixtures/functional_caching/_partial.erb
new file mode 100644
index 0000000000..ec0da7cf50
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/_partial.erb
@@ -0,0 +1,3 @@
+<% cache do %>
+Old fragment caching in a partial
+<% end %>
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
new file mode 100644
index 0000000000..9b88fa1f5a
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.html.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache do %><p>ERB</p><% end %>
+</body>
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
new file mode 100644
index 0000000000..efdcc28e0f
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached.xml.builder
@@ -0,0 +1,5 @@
+xml.body do
+ cache do
+ xml.p "Builder"
+ end
+end
diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
new file mode 100644
index 0000000000..e523b74ae3
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache do %><p>PHONE</p><% end %>
+</body>
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
new file mode 100644
index 0000000000..fa5e6bd318
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached.html.erb
@@ -0,0 +1,3 @@
+Hello
+<%= cache do %>This bit's fragment cached<% end %>
+<%= 'Ciao' %>
diff --git a/actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb b/actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
new file mode 100644
index 0000000000..3125583a28
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/fragment_cached_without_digest.html.erb
@@ -0,0 +1,3 @@
+<body>
+<%= cache 'nodigest', skip_digest: true do %><p>ERB</p><% end %>
+</body>
diff --git a/actionpack/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb b/actionpack/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb
new file mode 100644
index 0000000000..a9462d3499
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb
@@ -0,0 +1 @@
+<%= render :partial => 'partial' %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb
new file mode 100644
index 0000000000..41647f1404
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb
@@ -0,0 +1,2 @@
+<%= render :inline => 'Some inline content' %>
+<%= cache do %>Some cached content<% end %>
diff --git a/actionpack/test/fixtures/helpers/abc_helper.rb b/actionpack/test/fixtures/helpers/abc_helper.rb
new file mode 100644
index 0000000000..cf2774bb5f
--- /dev/null
+++ b/actionpack/test/fixtures/helpers/abc_helper.rb
@@ -0,0 +1,3 @@
+module AbcHelper
+ def bare_a() end
+end
diff --git a/actionpack/test/fixtures/helpers/fun/games_helper.rb b/actionpack/test/fixtures/helpers/fun/games_helper.rb
new file mode 100644
index 0000000000..3b7adce086
--- /dev/null
+++ b/actionpack/test/fixtures/helpers/fun/games_helper.rb
@@ -0,0 +1,5 @@
+module Fun
+ module GamesHelper
+ def stratego() "Iz guuut!" end
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/helpers/fun/pdf_helper.rb b/actionpack/test/fixtures/helpers/fun/pdf_helper.rb
new file mode 100644
index 0000000000..0171be8500
--- /dev/null
+++ b/actionpack/test/fixtures/helpers/fun/pdf_helper.rb
@@ -0,0 +1,5 @@
+module Fun
+ module PdfHelper
+ def foobar() 'baz' end
+ end
+end
diff --git a/actionpack/test/fixtures/helpers/just_me_helper.rb b/actionpack/test/fixtures/helpers/just_me_helper.rb
new file mode 100644
index 0000000000..b140a7b9b4
--- /dev/null
+++ b/actionpack/test/fixtures/helpers/just_me_helper.rb
@@ -0,0 +1,3 @@
+module JustMeHelper
+ def me() "mine!" end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/helpers/me_too_helper.rb b/actionpack/test/fixtures/helpers/me_too_helper.rb
new file mode 100644
index 0000000000..ce56042143
--- /dev/null
+++ b/actionpack/test/fixtures/helpers/me_too_helper.rb
@@ -0,0 +1,3 @@
+module MeTooHelper
+ def me() "me too!" end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
new file mode 100644
index 0000000000..9faa427736
--- /dev/null
+++ b/actionpack/test/fixtures/helpers1_pack/pack1_helper.rb
@@ -0,0 +1,5 @@
+module Pack1Helper
+ def conflicting_helper
+ "pack1"
+ end
+end
diff --git a/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
new file mode 100644
index 0000000000..cf56697dfb
--- /dev/null
+++ b/actionpack/test/fixtures/helpers2_pack/pack2_helper.rb
@@ -0,0 +1,5 @@
+module Pack2Helper
+ def conflicting_helper
+ "pack2"
+ end
+end
diff --git a/actionpack/test/fixtures/layouts/_customers.erb b/actionpack/test/fixtures/layouts/_customers.erb
new file mode 100644
index 0000000000..ae63f13cd3
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/_customers.erb
@@ -0,0 +1 @@
+<title><%= yield Struct.new(:name).new("David") %></title> \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/block_with_layout.erb b/actionpack/test/fixtures/layouts/block_with_layout.erb
new file mode 100644
index 0000000000..73ac833e52
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/block_with_layout.erb
@@ -0,0 +1,3 @@
+<%= render(:layout => "layout_for_partial", :locals => { :name => "Anthony" }) do %>Inside from first block in layout<% "Return value should be discarded" %><% end %>
+<%= yield %>
+<%= render(:layout => "layout_for_partial", :locals => { :name => "Ramm" }) do %>Inside from second block in layout<% end %>
diff --git a/actionpack/test/fixtures/layouts/builder.builder b/actionpack/test/fixtures/layouts/builder.builder
new file mode 100644
index 0000000000..7c7d4b2dd1
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/builder.builder
@@ -0,0 +1,3 @@
+xml.wrapper do
+ xml << yield
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/partial_with_layout.erb b/actionpack/test/fixtures/layouts/partial_with_layout.erb
new file mode 100644
index 0000000000..a0349d731e
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/partial_with_layout.erb
@@ -0,0 +1,3 @@
+<%= render( :layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => {:name => 'Anthony' } ) %>
+<%= yield %>
+<%= render( :layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => {:name => 'Ramm' } ) %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/standard.html.erb b/actionpack/test/fixtures/layouts/standard.html.erb
new file mode 100644
index 0000000000..5e6c24fe39
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/standard.html.erb
@@ -0,0 +1 @@
+<html><%= yield %><%= @variable_for_layout %></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/talk_from_action.erb b/actionpack/test/fixtures/layouts/talk_from_action.erb
new file mode 100644
index 0000000000..bf53fdb785
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/talk_from_action.erb
@@ -0,0 +1,2 @@
+<title><%= @title || yield(:title) %></title>
+<%= yield -%> \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/with_html_partial.html.erb b/actionpack/test/fixtures/layouts/with_html_partial.html.erb
new file mode 100644
index 0000000000..fd2896aeaa
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/with_html_partial.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "partial_only_html" %><%= yield %>
diff --git a/actionpack/test/fixtures/layouts/xhr.html.erb b/actionpack/test/fixtures/layouts/xhr.html.erb
new file mode 100644
index 0000000000..85285324ec
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/xhr.html.erb
@@ -0,0 +1,2 @@
+XHR!
+<%= yield %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/layouts/yield.erb b/actionpack/test/fixtures/layouts/yield.erb
new file mode 100644
index 0000000000..482dc9022e
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/yield.erb
@@ -0,0 +1,2 @@
+<title><%= yield :title %></title>
+<%= yield %>
diff --git a/actionpack/test/fixtures/localized/hello_world.de.html b/actionpack/test/fixtures/localized/hello_world.de.html
new file mode 100644
index 0000000000..4727d7a7e0
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.de.html
@@ -0,0 +1 @@
+Gutten Tag \ No newline at end of file
diff --git a/actionpack/test/fixtures/localized/hello_world.en.html b/actionpack/test/fixtures/localized/hello_world.en.html
new file mode 100644
index 0000000000..5e1c309dae
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.en.html
@@ -0,0 +1 @@
+Hello World \ No newline at end of file
diff --git a/actionpack/test/fixtures/localized/hello_world.it.erb b/actionpack/test/fixtures/localized/hello_world.it.erb
new file mode 100644
index 0000000000..9191fdc187
--- /dev/null
+++ b/actionpack/test/fixtures/localized/hello_world.it.erb
@@ -0,0 +1 @@
+Ciao Mondo \ No newline at end of file
diff --git a/actionpack/test/fixtures/multipart/binary_file b/actionpack/test/fixtures/multipart/binary_file
new file mode 100644
index 0000000000..556187ac1f
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/binary_file
Binary files differ
diff --git a/actionpack/test/fixtures/multipart/boundary_problem_file b/actionpack/test/fixtures/multipart/boundary_problem_file
new file mode 100644
index 0000000000..889c4aabe3
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/boundary_problem_file
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="file.txt"
+Content-Type: text/plain
+
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/bracketed_param b/actionpack/test/fixtures/multipart/bracketed_param
new file mode 100644
index 0000000000..096bd8a192
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/bracketed_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="foo[baz]"
+
+bar
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/bracketed_utf8_param b/actionpack/test/fixtures/multipart/bracketed_utf8_param
new file mode 100644
index 0000000000..976ca44a45
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/bracketed_utf8_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name[Iñtërnâtiônàlizætiøn_nested_name]"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/empty b/actionpack/test/fixtures/multipart/empty
new file mode 100644
index 0000000000..f0f79835c9
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/empty
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="submit-name"
+
+Larry
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="file1.txt"
+Content-Type: text/plain
+
+
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/hello.txt b/actionpack/test/fixtures/multipart/hello.txt
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/hello.txt
@@ -0,0 +1 @@
+Hello \ No newline at end of file
diff --git a/actionpack/test/fixtures/multipart/large_text_file b/actionpack/test/fixtures/multipart/large_text_file
new file mode 100644
index 0000000000..7f97fb1d79
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/large_text_file
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="file.txt"
+Content-Type: text/plain
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/mixed_files b/actionpack/test/fixtures/multipart/mixed_files
new file mode 100644
index 0000000000..5eba7a6b48
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/mixed_files
Binary files differ
diff --git a/actionpack/test/fixtures/multipart/mona_lisa.jpg b/actionpack/test/fixtures/multipart/mona_lisa.jpg
new file mode 100644
index 0000000000..5cf3bef3d0
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/mona_lisa.jpg
Binary files differ
diff --git a/actionpack/test/fixtures/multipart/none b/actionpack/test/fixtures/multipart/none
new file mode 100644
index 0000000000..d66f4730f1
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/none
@@ -0,0 +1,9 @@
+--AaB03x
+Content-Disposition: form-data; name="submit-name"
+
+Larry
+--AaB03x
+Content-Disposition: form-data; name="files"; filename=""
+
+
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/single_parameter b/actionpack/test/fixtures/multipart/single_parameter
new file mode 100644
index 0000000000..8962c35430
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/single_parameter
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/single_utf8_param b/actionpack/test/fixtures/multipart/single_utf8_param
new file mode 100644
index 0000000000..b86f62d1e1
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/single_utf8_param
@@ -0,0 +1,5 @@
+--AaB03x
+Content-Disposition: form-data; name="Iñtërnâtiônàlizætiøn_name"
+
+Iñtërnâtiônàlizætiøn_value
+--AaB03x--
diff --git a/actionpack/test/fixtures/multipart/text_file b/actionpack/test/fixtures/multipart/text_file
new file mode 100644
index 0000000000..e0367d68c0
--- /dev/null
+++ b/actionpack/test/fixtures/multipart/text_file
@@ -0,0 +1,10 @@
+--AaB03x
+Content-Disposition: form-data; name="foo"
+
+bar
+--AaB03x
+Content-Disposition: form-data; name="file"; filename="file.txt"
+Content-Type: text/plain
+
+contents
+--AaB03x--
diff --git a/actionpack/test/fixtures/old_content_type/render_default_content_types_for_respond_to.xml.erb b/actionpack/test/fixtures/old_content_type/render_default_content_types_for_respond_to.xml.erb
new file mode 100644
index 0000000000..25dc746886
--- /dev/null
+++ b/actionpack/test/fixtures/old_content_type/render_default_content_types_for_respond_to.xml.erb
@@ -0,0 +1 @@
+<hello>world</hello> \ No newline at end of file
diff --git a/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder b/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder
new file mode 100644
index 0000000000..598d62e2fc
--- /dev/null
+++ b/actionpack/test/fixtures/old_content_type/render_default_for_builder.builder
@@ -0,0 +1 @@
+xml.p "Hello world!" \ No newline at end of file
diff --git a/actionpack/test/fixtures/old_content_type/render_default_for_erb.erb b/actionpack/test/fixtures/old_content_type/render_default_for_erb.erb
new file mode 100644
index 0000000000..c7926d48bb
--- /dev/null
+++ b/actionpack/test/fixtures/old_content_type/render_default_for_erb.erb
@@ -0,0 +1 @@
+<%= 'hello world!' %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/post_test/layouts/post.html.erb b/actionpack/test/fixtures/post_test/layouts/post.html.erb
new file mode 100644
index 0000000000..c6c1a586dd
--- /dev/null
+++ b/actionpack/test/fixtures/post_test/layouts/post.html.erb
@@ -0,0 +1 @@
+<html><div id="html"><%= yield %></div></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb b/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb
new file mode 100644
index 0000000000..db0e43694d
--- /dev/null
+++ b/actionpack/test/fixtures/post_test/layouts/super_post.iphone.erb
@@ -0,0 +1 @@
+<html><div id="super_iphone"><%= yield %></div></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/post_test/post/index.html.erb b/actionpack/test/fixtures/post_test/post/index.html.erb
new file mode 100644
index 0000000000..b349b25618
--- /dev/null
+++ b/actionpack/test/fixtures/post_test/post/index.html.erb
@@ -0,0 +1 @@
+Hello Firefox \ No newline at end of file
diff --git a/actionpack/test/fixtures/post_test/post/index.iphone.erb b/actionpack/test/fixtures/post_test/post/index.iphone.erb
new file mode 100644
index 0000000000..d741e44351
--- /dev/null
+++ b/actionpack/test/fixtures/post_test/post/index.iphone.erb
@@ -0,0 +1 @@
+Hello iPhone \ No newline at end of file
diff --git a/actionpack/test/fixtures/post_test/super_post/index.html.erb b/actionpack/test/fixtures/post_test/super_post/index.html.erb
new file mode 100644
index 0000000000..7fc2eb190a
--- /dev/null
+++ b/actionpack/test/fixtures/post_test/super_post/index.html.erb
@@ -0,0 +1 @@
+Super Firefox \ No newline at end of file
diff --git a/actionpack/test/fixtures/post_test/super_post/index.iphone.erb b/actionpack/test/fixtures/post_test/super_post/index.iphone.erb
new file mode 100644
index 0000000000..99063a8d8c
--- /dev/null
+++ b/actionpack/test/fixtures/post_test/super_post/index.iphone.erb
@@ -0,0 +1 @@
+Super iPhone \ No newline at end of file
diff --git a/actionpack/test/fixtures/public/400.html b/actionpack/test/fixtures/public/400.html
new file mode 100644
index 0000000000..03be6bedaf
--- /dev/null
+++ b/actionpack/test/fixtures/public/400.html
@@ -0,0 +1 @@
+400 error fixture
diff --git a/actionpack/test/fixtures/public/404.html b/actionpack/test/fixtures/public/404.html
new file mode 100644
index 0000000000..497397ccea
--- /dev/null
+++ b/actionpack/test/fixtures/public/404.html
@@ -0,0 +1 @@
+404 error fixture
diff --git a/actionpack/test/fixtures/public/500.da.html b/actionpack/test/fixtures/public/500.da.html
new file mode 100644
index 0000000000..a497c13656
--- /dev/null
+++ b/actionpack/test/fixtures/public/500.da.html
@@ -0,0 +1 @@
+500 localized error fixture
diff --git a/actionpack/test/fixtures/public/500.html b/actionpack/test/fixtures/public/500.html
new file mode 100644
index 0000000000..7c66c7a943
--- /dev/null
+++ b/actionpack/test/fixtures/public/500.html
@@ -0,0 +1 @@
+500 error fixture
diff --git a/actionpack/test/fixtures/public/foo/bar.html b/actionpack/test/fixtures/public/foo/bar.html
new file mode 100644
index 0000000000..9a35646205
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/bar.html
@@ -0,0 +1 @@
+/foo/bar.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/public/foo/baz.css b/actionpack/test/fixtures/public/foo/baz.css
new file mode 100644
index 0000000000..b5173fbef2
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/baz.css
@@ -0,0 +1,3 @@
+body {
+background: #000;
+}
diff --git a/actionpack/test/fixtures/public/foo/index.html b/actionpack/test/fixtures/public/foo/index.html
new file mode 100644
index 0000000000..497a2e898f
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/index.html
@@ -0,0 +1 @@
+/foo/index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/public/foo/こんにちは.html b/actionpack/test/fixtures/public/foo/こんにちは.html
new file mode 100644
index 0000000000..1df9166522
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/こんにちは.html
@@ -0,0 +1 @@
+means hello in Japanese
diff --git a/actionpack/test/fixtures/public/index.html b/actionpack/test/fixtures/public/index.html
new file mode 100644
index 0000000000..525950ba6b
--- /dev/null
+++ b/actionpack/test/fixtures/public/index.html
@@ -0,0 +1 @@
+/index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb b/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb
new file mode 100644
index 0000000000..84a84049f8
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/all_types_with_layout.html.erb
@@ -0,0 +1 @@
+HTML for all_types_with_layout \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb b/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb
new file mode 100644
index 0000000000..0cdfa41494
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/custom_constant_handling_without_block.mobile.erb
@@ -0,0 +1 @@
+Mobile \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb b/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb
new file mode 100644
index 0000000000..1f3f1c6516
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.html.erb
@@ -0,0 +1 @@
+Hello future from <%= @type -%>! \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb b/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb
new file mode 100644
index 0000000000..17888ac303
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/iphone_with_html_response_type.iphone.erb
@@ -0,0 +1 @@
+Hello iPhone future from <%= @type -%>! \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/layouts/missing.html.erb b/actionpack/test/fixtures/respond_to/layouts/missing.html.erb
new file mode 100644
index 0000000000..d6f92a3120
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/layouts/missing.html.erb
@@ -0,0 +1 @@
+<html><div id="html_missing"><%= yield %></div></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/layouts/standard.html.erb b/actionpack/test/fixtures/respond_to/layouts/standard.html.erb
new file mode 100644
index 0000000000..c6c1a586dd
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/layouts/standard.html.erb
@@ -0,0 +1 @@
+<html><div id="html"><%= yield %></div></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb b/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb
new file mode 100644
index 0000000000..84444517f0
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/layouts/standard.iphone.erb
@@ -0,0 +1 @@
+<html><div id="iphone"><%= yield %></div></html> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults.html.erb b/actionpack/test/fixtures/respond_to/using_defaults.html.erb
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/using_defaults.html.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults.xml.builder b/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
new file mode 100644
index 0000000000..598d62e2fc
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/using_defaults.xml.builder
@@ -0,0 +1 @@
+xml.p "Hello world!" \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb
new file mode 100644
index 0000000000..9f1f855269
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/using_defaults_with_all.html.erb
@@ -0,0 +1 @@
+HTML!
diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.html.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
new file mode 100644
index 0000000000..598d62e2fc
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/using_defaults_with_type_list.xml.builder
@@ -0,0 +1 @@
+xml.p "Hello world!" \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb
new file mode 100644
index 0000000000..e905d051bf
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+phablet.erb
@@ -0,0 +1 @@
+phablet \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb
new file mode 100644
index 0000000000..65526af8cf
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_any_implicit_render.html+tablet.erb
@@ -0,0 +1 @@
+tablet \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb b/actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb
new file mode 100644
index 0000000000..cd222a4a49
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_inline_syntax_without_block.html+phone.erb
@@ -0,0 +1 @@
+phone \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb b/actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb
new file mode 100644
index 0000000000..c86c3f3551
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_plus_none_for_format.html.erb
@@ -0,0 +1 @@
+none \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb b/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
new file mode 100644
index 0000000000..317801ad30
--- /dev/null
+++ b/actionpack/test/fixtures/respond_to/variant_with_implicit_rendering.html+mobile.erb
@@ -0,0 +1 @@
+mobile \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/edit.html.erb b/actionpack/test/fixtures/respond_with/edit.html.erb
new file mode 100644
index 0000000000..ae82dfa4fc
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/edit.html.erb
@@ -0,0 +1 @@
+Edit world!
diff --git a/actionpack/test/fixtures/respond_with/new.html.erb b/actionpack/test/fixtures/respond_with/new.html.erb
new file mode 100644
index 0000000000..96c8f1b88b
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/new.html.erb
@@ -0,0 +1 @@
+New world!
diff --git a/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/respond_with_additional_params.html.erb
diff --git a/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb b/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb
new file mode 100644
index 0000000000..bf5869ed22
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_invalid_resource_with_template.xml.erb
@@ -0,0 +1 @@
+<content>I should not be displayed</content> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb b/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb
new file mode 100644
index 0000000000..b313017913
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_options_with_template.xml.erb
@@ -0,0 +1 @@
+<customer-name><%= @customer.name %></customer-name> \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.erb b/actionpack/test/fixtures/respond_with/using_resource.js.erb
new file mode 100644
index 0000000000..4417680bce
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_resource.js.erb
@@ -0,0 +1 @@
+alert("Hi"); \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_resource_with_block.html.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/ruby_template.ruby b/actionpack/test/fixtures/ruby_template.ruby
new file mode 100644
index 0000000000..5097bce47c
--- /dev/null
+++ b/actionpack/test/fixtures/ruby_template.ruby
@@ -0,0 +1,2 @@
+body = ""
+body << ["Hello", "from", "Ruby", "code"].join(" ")
diff --git a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
new file mode 100644
index 0000000000..4ee7a24561
--- /dev/null
+++ b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
@@ -0,0 +1,10 @@
+module SessionAutoloadTest
+ class Foo
+ def initialize(bar='baz')
+ @bar = bar
+ end
+ def inspect
+ "#<#{self.class} bar:#{@bar.inspect}>"
+ end
+ end
+end
diff --git a/actionpack/test/fixtures/shared.html.erb b/actionpack/test/fixtures/shared.html.erb
new file mode 100644
index 0000000000..af262fc9f8
--- /dev/null
+++ b/actionpack/test/fixtures/shared.html.erb
@@ -0,0 +1 @@
+Elastica \ No newline at end of file
diff --git a/actionpack/test/fixtures/star_star_mime/index.js.erb b/actionpack/test/fixtures/star_star_mime/index.js.erb
new file mode 100644
index 0000000000..4da4181f56
--- /dev/null
+++ b/actionpack/test/fixtures/star_star_mime/index.js.erb
@@ -0,0 +1 @@
+function addition(a,b){ return a+b; }
diff --git a/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb b/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb
new file mode 100644
index 0000000000..bda57d0fae
--- /dev/null
+++ b/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb
@@ -0,0 +1,5 @@
+This is my layout
+
+<%= yield %>
+
+End.
diff --git a/actionpack/test/fixtures/test/_partial.erb b/actionpack/test/fixtures/test/_partial.erb
new file mode 100644
index 0000000000..e466dcbd8e
--- /dev/null
+++ b/actionpack/test/fixtures/test/_partial.erb
@@ -0,0 +1 @@
+invalid \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_partial.html.erb b/actionpack/test/fixtures/test/_partial.html.erb
new file mode 100644
index 0000000000..e39f6c9827
--- /dev/null
+++ b/actionpack/test/fixtures/test/_partial.html.erb
@@ -0,0 +1 @@
+partial html \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_partial.js.erb b/actionpack/test/fixtures/test/_partial.js.erb
new file mode 100644
index 0000000000..b350cdd7ef
--- /dev/null
+++ b/actionpack/test/fixtures/test/_partial.js.erb
@@ -0,0 +1 @@
+partial js \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb b/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb
new file mode 100644
index 0000000000..8b8a449236
--- /dev/null
+++ b/actionpack/test/fixtures/test/dot.directory/render_file_with_ivar.erb
@@ -0,0 +1 @@
+The secret is <%= @secret %>
diff --git a/actionpack/test/fixtures/test/formatted_xml_erb.builder b/actionpack/test/fixtures/test/formatted_xml_erb.builder
new file mode 100644
index 0000000000..14fd3549fb
--- /dev/null
+++ b/actionpack/test/fixtures/test/formatted_xml_erb.builder
@@ -0,0 +1 @@
+xml.test 'failed' \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/formatted_xml_erb.html.erb b/actionpack/test/fixtures/test/formatted_xml_erb.html.erb
new file mode 100644
index 0000000000..0c855a604b
--- /dev/null
+++ b/actionpack/test/fixtures/test/formatted_xml_erb.html.erb
@@ -0,0 +1 @@
+<test>passed formatted html erb</test> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb b/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb
new file mode 100644
index 0000000000..6ca09d5304
--- /dev/null
+++ b/actionpack/test/fixtures/test/formatted_xml_erb.xml.erb
@@ -0,0 +1 @@
+<test>passed formatted xml erb</test> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello/hello.erb b/actionpack/test/fixtures/test/hello/hello.erb
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello/hello.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world.erb b/actionpack/test/fixtures/test/hello_world.erb
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world_with_partial.html.erb b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
new file mode 100644
index 0000000000..ec31545356
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
@@ -0,0 +1,2 @@
+Hello world!
+<%= render '/test/partial' %>
diff --git a/actionpack/test/fixtures/test/hello_xml_world.builder b/actionpack/test/fixtures/test/hello_xml_world.builder
new file mode 100644
index 0000000000..e7081b89fe
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_xml_world.builder
@@ -0,0 +1,11 @@
+xml.html do
+ xml.head do
+ xml.title "Hello World"
+ end
+
+ xml.body do
+ xml.p "abes"
+ xml.p "monks"
+ xml.p "wiseguys"
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/implicit_content_type.atom.builder b/actionpack/test/fixtures/test/implicit_content_type.atom.builder
new file mode 100644
index 0000000000..2fcb32d247
--- /dev/null
+++ b/actionpack/test/fixtures/test/implicit_content_type.atom.builder
@@ -0,0 +1,2 @@
+xml.atom do
+end
diff --git a/actionpack/test/fixtures/test/render_file_with_ivar.erb b/actionpack/test/fixtures/test/render_file_with_ivar.erb
new file mode 100644
index 0000000000..8b8a449236
--- /dev/null
+++ b/actionpack/test/fixtures/test/render_file_with_ivar.erb
@@ -0,0 +1 @@
+The secret is <%= @secret %>
diff --git a/actionpack/test/fixtures/test/render_file_with_locals.erb b/actionpack/test/fixtures/test/render_file_with_locals.erb
new file mode 100644
index 0000000000..ebe09faee6
--- /dev/null
+++ b/actionpack/test/fixtures/test/render_file_with_locals.erb
@@ -0,0 +1 @@
+The secret is <%= secret %>
diff --git a/actionpack/test/fixtures/公共/foo/bar.html b/actionpack/test/fixtures/公共/foo/bar.html
new file mode 100644
index 0000000000..9a35646205
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/bar.html
@@ -0,0 +1 @@
+/foo/bar.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/foo/baz.css b/actionpack/test/fixtures/公共/foo/baz.css
new file mode 100644
index 0000000000..b5173fbef2
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/baz.css
@@ -0,0 +1,3 @@
+body {
+background: #000;
+}
diff --git a/actionpack/test/fixtures/公共/foo/index.html b/actionpack/test/fixtures/公共/foo/index.html
new file mode 100644
index 0000000000..497a2e898f
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/index.html
@@ -0,0 +1 @@
+/foo/index.html \ No newline at end of file
diff --git a/actionpack/test/fixtures/公共/foo/こんにちは.html b/actionpack/test/fixtures/公共/foo/こんにちは.html
new file mode 100644
index 0000000000..1df9166522
--- /dev/null
+++ b/actionpack/test/fixtures/公共/foo/こんにちは.html
@@ -0,0 +1 @@
+means hello in Japanese
diff --git a/actionpack/test/fixtures/公共/index.html b/actionpack/test/fixtures/公共/index.html
new file mode 100644
index 0000000000..525950ba6b
--- /dev/null
+++ b/actionpack/test/fixtures/公共/index.html
@@ -0,0 +1 @@
+/index.html \ No newline at end of file
diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb
new file mode 100644
index 0000000000..c1da374007
--- /dev/null
+++ b/actionpack/test/journey/gtg/builder_test.rb
@@ -0,0 +1,79 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module GTG
+ class TestBuilder < ActiveSupport::TestCase
+ def test_following_states_multi
+ table = tt ['a|a']
+ assert_equal 1, table.move([0], 'a').length
+ end
+
+ def test_following_states_multi_regexp
+ table = tt [':a|b']
+ assert_equal 1, table.move([0], 'fooo').length
+ assert_equal 2, table.move([0], 'b').length
+ end
+
+ def test_multi_path
+ table = tt ['/:a/d', '/b/c']
+
+ [
+ [1, '/'],
+ [2, 'b'],
+ [2, '/'],
+ [1, 'c'],
+ ].inject([0]) { |state, (exp, sym)|
+ new = table.move(state, sym)
+ assert_equal exp, new.length
+ new
+ }
+ end
+
+ def test_match_data_ambiguous
+ table = tt %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+
+ sim = NFA::Simulator.new table
+
+ match = sim.match '/articles/new'
+ assert_equal 2, match.memos.length
+ end
+
+ ##
+ # Identical Routes may have different restrictions.
+ def test_match_same_paths
+ table = tt %w{
+ /articles/new(.:format)
+ /articles/new(.:format)
+ }
+
+ sim = NFA::Simulator.new table
+
+ match = sim.match '/articles/new'
+ assert_equal 2, match.memos.length
+ end
+
+ private
+ def ast strings
+ parser = Journey::Parser.new
+ asts = strings.map { |string|
+ memo = Object.new
+ ast = parser.parse string
+ ast.each { |n| n.memo = memo }
+ ast
+ }
+ Nodes::Or.new asts
+ end
+
+ def tt strings
+ Builder.new(ast(strings)).transition_table
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb
new file mode 100644
index 0000000000..b968780d8d
--- /dev/null
+++ b/actionpack/test/journey/gtg/transition_table_test.rb
@@ -0,0 +1,115 @@
+require 'abstract_unit'
+require 'active_support/json/decoding'
+
+module ActionDispatch
+ module Journey
+ module GTG
+ class TestGeneralizedTable < ActiveSupport::TestCase
+ def test_to_json
+ table = tt %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+
+ json = ActiveSupport::JSON.decode table.to_json
+ assert json['regexp_states']
+ assert json['string_states']
+ assert json['accepting']
+ end
+
+ if system("dot -V 2>/dev/null")
+ def test_to_svg
+ table = tt %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+ svg = table.to_svg
+ assert svg
+ assert_no_match(/DOCTYPE/, svg)
+ end
+ end
+
+ def test_simulate_gt
+ sim = simulator_for ['/foo', '/bar']
+ assert_match sim, '/foo'
+ end
+
+ def test_simulate_gt_regexp
+ sim = simulator_for [':foo']
+ assert_match sim, 'foo'
+ end
+
+ def test_simulate_gt_regexp_mix
+ sim = simulator_for ['/get', '/:method/foo']
+ assert_match sim, '/get'
+ assert_match sim, '/get/foo'
+ end
+
+ def test_simulate_optional
+ sim = simulator_for ['/foo(/bar)']
+ assert_match sim, '/foo'
+ assert_match sim, '/foo/bar'
+ assert_no_match sim, '/foo/'
+ end
+
+ def test_match_data
+ path_asts = asts %w{ /get /:method/foo }
+ paths = path_asts.dup
+
+ builder = GTG::Builder.new Nodes::Or.new path_asts
+ tt = builder.transition_table
+
+ sim = GTG::Simulator.new tt
+
+ match = sim.match '/get'
+ assert_equal [paths.first], match.memos
+
+ match = sim.match '/get/foo'
+ assert_equal [paths.last], match.memos
+ end
+
+ def test_match_data_ambiguous
+ path_asts = asts %w{
+ /articles(.:format)
+ /articles/new(.:format)
+ /articles/:id/edit(.:format)
+ /articles/:id(.:format)
+ }
+
+ paths = path_asts.dup
+ ast = Nodes::Or.new path_asts
+
+ builder = GTG::Builder.new ast
+ sim = GTG::Simulator.new builder.transition_table
+
+ match = sim.match '/articles/new'
+ assert_equal [paths[1], paths[3]], match.memos
+ end
+
+ private
+ def asts paths
+ parser = Journey::Parser.new
+ paths.map { |x|
+ ast = parser.parse x
+ ast.each { |n| n.memo = ast}
+ ast
+ }
+ end
+
+ def tt paths
+ x = asts paths
+ builder = GTG::Builder.new Nodes::Or.new x
+ builder.transition_table
+ end
+
+ def simulator_for paths
+ GTG::Simulator.new tt(paths)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb
new file mode 100644
index 0000000000..673a491fe5
--- /dev/null
+++ b/actionpack/test/journey/nfa/simulator_test.rb
@@ -0,0 +1,98 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module NFA
+ class TestSimulator < ActiveSupport::TestCase
+ def test_simulate_simple
+ sim = simulator_for ['/foo']
+ assert_match sim, '/foo'
+ end
+
+ def test_simulate_simple_no_match
+ sim = simulator_for ['/foo']
+ assert_no_match sim, 'foo'
+ end
+
+ def test_simulate_simple_no_match_too_long
+ sim = simulator_for ['/foo']
+ assert_no_match sim, '/foo/bar'
+ end
+
+ def test_simulate_simple_no_match_wrong_string
+ sim = simulator_for ['/foo']
+ assert_no_match sim, '/bar'
+ end
+
+ def test_simulate_regex
+ sim = simulator_for ['/:foo/bar']
+ assert_match sim, '/bar/bar'
+ assert_match sim, '/foo/bar'
+ end
+
+ def test_simulate_or
+ sim = simulator_for ['/foo', '/bar']
+ assert_match sim, '/bar'
+ assert_match sim, '/foo'
+ assert_no_match sim, '/baz'
+ end
+
+ def test_simulate_optional
+ sim = simulator_for ['/foo(/bar)']
+ assert_match sim, '/foo'
+ assert_match sim, '/foo/bar'
+ assert_no_match sim, '/foo/'
+ end
+
+ def test_matchdata_has_memos
+ paths = %w{ /foo /bar }
+ parser = Journey::Parser.new
+ asts = paths.map { |x|
+ ast = parser.parse x
+ ast.each { |n| n.memo = ast}
+ ast
+ }
+
+ expected = asts.first
+
+ builder = Builder.new Nodes::Or.new asts
+
+ sim = Simulator.new builder.transition_table
+
+ md = sim.match '/foo'
+ assert_equal [expected], md.memos
+ end
+
+ def test_matchdata_memos_on_merge
+ parser = Journey::Parser.new
+ routes = [
+ '/articles(.:format)',
+ '/articles/new(.:format)',
+ '/articles/:id/edit(.:format)',
+ '/articles/:id(.:format)',
+ ].map { |path|
+ ast = parser.parse path
+ ast.each { |n| n.memo = ast }
+ ast
+ }
+
+ asts = routes.dup
+
+ ast = Nodes::Or.new routes
+
+ nfa = Journey::NFA::Builder.new ast
+ sim = Simulator.new nfa.transition_table
+ md = sim.match '/articles'
+ assert_equal [asts.first], md.memos
+ end
+
+ def simulator_for paths
+ parser = Journey::Parser.new
+ asts = paths.map { |x| parser.parse x }
+ builder = Builder.new Nodes::Or.new asts
+ Simulator.new builder.transition_table
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb
new file mode 100644
index 0000000000..1248082c03
--- /dev/null
+++ b/actionpack/test/journey/nfa/transition_table_test.rb
@@ -0,0 +1,72 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module NFA
+ class TestTransitionTable < ActiveSupport::TestCase
+ def setup
+ @parser = Journey::Parser.new
+ end
+
+ def test_eclosure
+ table = tt '/'
+ assert_equal [0], table.eclosure(0)
+
+ table = tt ':a|:b'
+ assert_equal 3, table.eclosure(0).length
+
+ table = tt '(:a|:b)'
+ assert_equal 5, table.eclosure(0).length
+ assert_equal 5, table.eclosure([0]).length
+ end
+
+ def test_following_states_one
+ table = tt '/'
+
+ assert_equal [1], table.following_states(0, '/')
+ assert_equal [1], table.following_states([0], '/')
+ end
+
+ def test_following_states_group
+ table = tt 'a|b'
+ states = table.eclosure 0
+
+ assert_equal 1, table.following_states(states, 'a').length
+ assert_equal 1, table.following_states(states, 'b').length
+ end
+
+ def test_following_states_multi
+ table = tt 'a|a'
+ states = table.eclosure 0
+
+ assert_equal 2, table.following_states(states, 'a').length
+ assert_equal 0, table.following_states(states, 'b').length
+ end
+
+ def test_following_states_regexp
+ table = tt 'a|:a'
+ states = table.eclosure 0
+
+ assert_equal 1, table.following_states(states, 'a').length
+ assert_equal 1, table.following_states(states, /[^\.\/\?]+/).length
+ assert_equal 0, table.following_states(states, 'b').length
+ end
+
+ def test_alphabet
+ table = tt 'a|:a'
+ assert_equal [/[^\.\/\?]+/, 'a'], table.alphabet
+
+ table = tt 'a|a'
+ assert_equal ['a'], table.alphabet
+ end
+
+ private
+ def tt string
+ ast = @parser.parse string
+ builder = Builder.new ast
+ builder.transition_table
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb
new file mode 100644
index 0000000000..d411a5018a
--- /dev/null
+++ b/actionpack/test/journey/nodes/symbol_test.rb
@@ -0,0 +1,17 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Nodes
+ class TestSymbol < ActiveSupport::TestCase
+ def test_default_regexp?
+ sym = Symbol.new nil
+ assert sym.default_regexp?
+
+ sym.regexp = nil
+ assert_not sym.default_regexp?
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb
new file mode 100644
index 0000000000..9dfdfc23ed
--- /dev/null
+++ b/actionpack/test/journey/path/pattern_test.rb
@@ -0,0 +1,284 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Path
+ class TestPattern < ActiveSupport::TestCase
+ x = /.+/
+ {
+ '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?\Z},
+ '/:controller/foo' => %r{\A/(#{x})/foo\Z},
+ '/:controller/:action' => %r{\A/(#{x})/([^/.?]+)\Z},
+ '/:controller' => %r{\A/(#{x})\Z},
+ '/:controller(/:action(/:id))' => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z},
+ '/:controller/:action.xml' => %r{\A/(#{x})/([^/.?]+)\.xml\Z},
+ '/:controller.:format' => %r{\A/(#{x})\.([^/.?]+)\Z},
+ '/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?\Z},
+ '/:controller/*foo' => %r{\A/(#{x})/(.+)\Z},
+ '/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar\Z},
+ }.each do |path, expected|
+ define_method(:"test_to_regexp_#{path}") do
+ strexp = Router::Strexp.build(
+ path,
+ { :controller => /.+/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_equal(expected, path.to_regexp)
+ end
+ end
+
+ {
+ '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?},
+ '/:controller/foo' => %r{\A/(#{x})/foo},
+ '/:controller/:action' => %r{\A/(#{x})/([^/.?]+)},
+ '/:controller' => %r{\A/(#{x})},
+ '/:controller(/:action(/:id))' => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?},
+ '/:controller/:action.xml' => %r{\A/(#{x})/([^/.?]+)\.xml},
+ '/:controller.:format' => %r{\A/(#{x})\.([^/.?]+)},
+ '/:controller(.:format)' => %r{\A/(#{x})(?:\.([^/.?]+))?},
+ '/:controller/*foo' => %r{\A/(#{x})/(.+)},
+ '/:controller/*foo/bar' => %r{\A/(#{x})/(.+)/bar},
+ }.each do |path, expected|
+ define_method(:"test_to_non_anchored_regexp_#{path}") do
+ strexp = Router::Strexp.build(
+ path,
+ { :controller => /.+/ },
+ ["/", ".", "?"],
+ false
+ )
+ path = Pattern.new strexp
+ assert_equal(expected, path.to_regexp)
+ end
+ end
+
+ {
+ '/:controller(/:action)' => %w{ controller action },
+ '/:controller/foo' => %w{ controller },
+ '/:controller/:action' => %w{ controller action },
+ '/:controller' => %w{ controller },
+ '/:controller(/:action(/:id))' => %w{ controller action id },
+ '/:controller/:action.xml' => %w{ controller action },
+ '/:controller.:format' => %w{ controller format },
+ '/:controller(.:format)' => %w{ controller format },
+ '/:controller/*foo' => %w{ controller foo },
+ '/:controller/*foo/bar' => %w{ controller foo },
+ }.each do |path, expected|
+ define_method(:"test_names_#{path}") do
+ strexp = Router::Strexp.build(
+ path,
+ { :controller => /.+/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_equal(expected, path.names)
+ end
+ end
+
+ def test_to_regexp_with_extended_group
+ strexp = Router::Strexp.build(
+ '/page/:name',
+ { :name => /
+ #ROFL
+ (tender|love
+ #MAO
+ )/x },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/page/tender')
+ assert_match(path, '/page/love')
+ assert_no_match(path, '/page/loving')
+ end
+
+ def test_optional_names
+ [
+ ['/:foo(/:bar(/:baz))', %w{ bar baz }],
+ ['/:foo(/:bar)', %w{ bar }],
+ ['/:foo(/:bar)/:lol(/:baz)', %w{ bar baz }],
+ ].each do |pattern, list|
+ path = Pattern.from_string pattern
+ assert_equal list.sort, path.optional_names.sort
+ end
+ end
+
+ def test_to_regexp_match_non_optional
+ strexp = Router::Strexp.build(
+ '/:name',
+ { :name => /\d+/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/123')
+ assert_no_match(path, '/')
+ end
+
+ def test_to_regexp_with_group
+ strexp = Router::Strexp.build(
+ '/page/:name',
+ { :name => /(tender|love)/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/page/tender')
+ assert_match(path, '/page/love')
+ assert_no_match(path, '/page/loving')
+ end
+
+ def test_ast_sets_regular_expressions
+ requirements = { :name => /(tender|love)/, :value => /./ }
+ strexp = Router::Strexp.build(
+ '/page/:name/:value',
+ requirements,
+ ["/", ".", "?"]
+ )
+
+ assert_equal requirements, strexp.requirements
+
+ path = Pattern.new strexp
+ nodes = path.ast.grep(Nodes::Symbol)
+ assert_equal 2, nodes.length
+ nodes.each do |node|
+ assert_equal requirements[node.to_sym], node.regexp
+ end
+ end
+
+ def test_match_data_with_group
+ strexp = Router::Strexp.build(
+ '/page/:name',
+ { :name => /(tender|love)/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ match = path.match '/page/tender'
+ assert_equal 'tender', match[1]
+ assert_equal 2, match.length
+ end
+
+ def test_match_data_with_multi_group
+ strexp = Router::Strexp.build(
+ '/page/:name/:id',
+ { :name => /t(((ender|love)))()/ },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ match = path.match '/page/tender/10'
+ assert_equal 'tender', match[1]
+ assert_equal '10', match[2]
+ assert_equal 3, match.length
+ assert_equal %w{ tender 10 }, match.captures
+ end
+
+ def test_star_with_custom_re
+ z = /\d+/
+ strexp = Router::Strexp.build(
+ '/page/*foo',
+ { :foo => z },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_equal(%r{\A/page/(#{z})\Z}, path.to_regexp)
+ end
+
+ def test_insensitive_regexp_with_group
+ strexp = Router::Strexp.build(
+ '/page/:name/aaron',
+ { :name => /(tender|love)/i },
+ ["/", ".", "?"]
+ )
+ path = Pattern.new strexp
+ assert_match(path, '/page/TENDER/aaron')
+ assert_match(path, '/page/loVE/aaron')
+ assert_no_match(path, '/page/loVE/AAron')
+ end
+
+ def test_to_regexp_with_strexp
+ strexp = Router::Strexp.build('/:controller', { }, ["/", ".", "?"])
+ path = Pattern.new strexp
+ x = %r{\A/([^/.?]+)\Z}
+
+ assert_equal(x.source, path.source)
+ end
+
+ def test_to_regexp_defaults
+ path = Pattern.from_string '/:controller(/:action(/:id))'
+ expected = %r{\A/([^/.?]+)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}
+ assert_equal expected, path.to_regexp
+ end
+
+ def test_failed_match
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ uri = 'content'
+
+ assert_not path =~ uri
+ end
+
+ def test_match_controller
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ uri = '/content'
+
+ match = path =~ uri
+ assert_equal %w{ controller action id format }, match.names
+ assert_equal 'content', match[1]
+ assert_nil match[2]
+ assert_nil match[3]
+ assert_nil match[4]
+ end
+
+ def test_match_controller_action
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ uri = '/content/list'
+
+ match = path =~ uri
+ assert_equal %w{ controller action id format }, match.names
+ assert_equal 'content', match[1]
+ assert_equal 'list', match[2]
+ assert_nil match[3]
+ assert_nil match[4]
+ end
+
+ def test_match_controller_action_id
+ path = Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ uri = '/content/list/10'
+
+ match = path =~ uri
+ assert_equal %w{ controller action id format }, match.names
+ assert_equal 'content', match[1]
+ assert_equal 'list', match[2]
+ assert_equal '10', match[3]
+ assert_nil match[4]
+ end
+
+ def test_match_literal
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+
+ uri = '/books'
+ match = path =~ uri
+ assert_equal %w{ action format }, match.names
+ assert_nil match[1]
+ assert_nil match[2]
+ end
+
+ def test_match_literal_with_action
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+
+ uri = '/books/list'
+ match = path =~ uri
+ assert_equal %w{ action format }, match.names
+ assert_equal 'list', match[1]
+ assert_nil match[2]
+ end
+
+ def test_match_literal_with_action_and_format
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+
+ uri = '/books/list.rss'
+ match = path =~ uri
+ assert_equal %w{ action format }, match.names
+ assert_equal 'list', match[1]
+ assert_equal 'rss', match[2]
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/route/definition/parser_test.rb b/actionpack/test/journey/route/definition/parser_test.rb
new file mode 100644
index 0000000000..d7d7172a40
--- /dev/null
+++ b/actionpack/test/journey/route/definition/parser_test.rb
@@ -0,0 +1,110 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Definition
+ class TestParser < ActiveSupport::TestCase
+ def setup
+ @parser = Parser.new
+ end
+
+ def test_slash
+ assert_equal :SLASH, @parser.parse('/').type
+ assert_round_trip '/'
+ end
+
+ def test_segment
+ assert_round_trip '/foo'
+ end
+
+ def test_segments
+ assert_round_trip '/foo/bar'
+ end
+
+ def test_segment_symbol
+ assert_round_trip '/foo/:id'
+ end
+
+ def test_symbol
+ assert_round_trip '/:foo'
+ end
+
+ def test_group
+ assert_round_trip '(/:foo)'
+ end
+
+ def test_groups
+ assert_round_trip '(/:foo)(/:bar)'
+ end
+
+ def test_nested_groups
+ assert_round_trip '(/:foo(/:bar))'
+ end
+
+ def test_dot_symbol
+ assert_round_trip('.:format')
+ end
+
+ def test_dot_literal
+ assert_round_trip('.xml')
+ end
+
+ def test_segment_dot
+ assert_round_trip('/foo.:bar')
+ end
+
+ def test_segment_group_dot
+ assert_round_trip('/foo(.:bar)')
+ end
+
+ def test_segment_group
+ assert_round_trip('/foo(/:action)')
+ end
+
+ def test_segment_groups
+ assert_round_trip('/foo(/:action)(/:bar)')
+ end
+
+ def test_segment_nested_groups
+ assert_round_trip('/foo(/:action(/:bar))')
+ end
+
+ def test_group_followed_by_path
+ assert_round_trip('/foo(/:action)/:bar')
+ end
+
+ def test_star
+ assert_round_trip('*foo')
+ assert_round_trip('/*foo')
+ assert_round_trip('/bar/*foo')
+ assert_round_trip('/bar/(*foo)')
+ end
+
+ def test_or
+ assert_round_trip('a|b')
+ assert_round_trip('a|b|c')
+ assert_round_trip('(a|b)|c')
+ assert_round_trip('a|(b|c)')
+ assert_round_trip('*a|(b|c)')
+ assert_round_trip('*a|:b|c')
+ end
+
+ def test_arbitrary
+ assert_round_trip('/bar/*foo#')
+ end
+
+ def test_literal_dot_paren
+ assert_round_trip "/sprockets.js(.:format)"
+ end
+
+ def test_groups_with_dot
+ assert_round_trip "/(:locale)(.:format)"
+ end
+
+ def assert_round_trip str
+ assert_equal str, @parser.parse(str).to_s
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb
new file mode 100644
index 0000000000..624e6df51a
--- /dev/null
+++ b/actionpack/test/journey/route/definition/scanner_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ module Definition
+ class TestScanner < ActiveSupport::TestCase
+ def setup
+ @scanner = Scanner.new
+ end
+
+ # /page/:id(/:action)(.:format)
+ def test_tokens
+ [
+ ['/', [[:SLASH, '/']]],
+ ['*omg', [[:STAR, '*omg']]],
+ ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]],
+ ['/~page', [[:SLASH, '/'], [:LITERAL, '~page']]],
+ ['/pa-ge', [[:SLASH, '/'], [:LITERAL, 'pa-ge']]],
+ ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]],
+ ['/(:page)', [
+ [:SLASH, '/'],
+ [:LPAREN, '('],
+ [:SYMBOL, ':page'],
+ [:RPAREN, ')'],
+ ]],
+ ['(/:action)', [
+ [:LPAREN, '('],
+ [:SLASH, '/'],
+ [:SYMBOL, ':action'],
+ [:RPAREN, ')'],
+ ]],
+ ['(())', [[:LPAREN, '('],
+ [:LPAREN, '('], [:RPAREN, ')'], [:RPAREN, ')']]],
+ ['(.:format)', [
+ [:LPAREN, '('],
+ [:DOT, '.'],
+ [:SYMBOL, ':format'],
+ [:RPAREN, ')'],
+ ]],
+ ].each do |str, expected|
+ @scanner.scan_setup str
+ assert_tokens expected, @scanner
+ end
+ end
+
+ def assert_tokens tokens, scanner
+ toks = []
+ while tok = scanner.next_token
+ toks << tok
+ end
+ assert_equal tokens, toks
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb
new file mode 100644
index 0000000000..21d867aca0
--- /dev/null
+++ b/actionpack/test/journey/route_test.rb
@@ -0,0 +1,106 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class TestRoute < ActiveSupport::TestCase
+ def test_initialize
+ app = Object.new
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ defaults = {}
+ route = Route.new("name", app, path, {}, defaults)
+
+ assert_equal app, route.app
+ assert_equal path, route.path
+ assert_same defaults, route.defaults
+ end
+
+ def test_route_adds_itself_as_memo
+ app = Object.new
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ defaults = {}
+ route = Route.new("name", app, path, {}, defaults)
+
+ route.ast.grep(Nodes::Terminal).each do |node|
+ assert_equal route, node.memo
+ end
+ end
+
+ def test_ip_address
+ path = Path::Pattern.from_string '/messages/:id(.:format)'
+ route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
+ { :controller => 'foo', :action => 'bar' })
+ assert_equal '192.168.1.1', route.ip
+ end
+
+ def test_default_ip
+ path = Path::Pattern.from_string '/messages/:id(.:format)'
+ route = Route.new("name", nil, path, {},
+ { :controller => 'foo', :action => 'bar' })
+ assert_equal(//, route.ip)
+ end
+
+ def test_format_with_star
+ path = Path::Pattern.from_string '/:controller/*extra'
+ route = Route.new("name", nil, path, {},
+ { :controller => 'foo', :action => 'bar' })
+ assert_equal '/foo/himom', route.format({
+ :controller => 'foo',
+ :extra => 'himom',
+ })
+ end
+
+ def test_connects_all_match
+ path = Path::Pattern.from_string '/:controller(/:action(/:id(.:format)))'
+ route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
+
+ assert_equal '/foo/bar/10', route.format({
+ :controller => 'foo',
+ :action => 'bar',
+ :id => 10
+ })
+ end
+
+ def test_extras_are_not_included_if_optional
+ path = Path::Pattern.from_string '/page/:id(/:action)'
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
+
+ assert_equal '/page/10', route.format({ :id => 10 })
+ end
+
+ def test_extras_are_not_included_if_optional_with_parameter
+ path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
+
+ assert_equal '/pages/10', route.format({:id => 10})
+ end
+
+ def test_extras_are_not_included_if_optional_parameter_is_nil
+ path = Path::Pattern.from_string '(/sections/:section)/pages/:id'
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
+
+ assert_equal '/pages/10', route.format({:id => 10, :section => nil})
+ end
+
+ def test_score
+ constraints = {:required_defaults => [:controller, :action]}
+ defaults = {:controller=>"pages", :action=>"show"}
+
+ path = Path::Pattern.from_string "/page/:id(/:action)(.:format)"
+ specific = Route.new "name", nil, path, constraints, defaults
+
+ path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"
+ generic = Route.new "name", nil, path, constraints
+
+ knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
+
+ routes = [specific, generic]
+
+ assert_not_equal specific.score(knowledge), generic.score(knowledge)
+
+ found = routes.sort_by { |r| r.score(knowledge) }.last
+
+ assert_equal specific, found
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb
new file mode 100644
index 0000000000..9b2b85ec73
--- /dev/null
+++ b/actionpack/test/journey/router/utils_test.rb
@@ -0,0 +1,38 @@
+# coding: utf-8
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class Router
+ class TestUtils < ActiveSupport::TestCase
+ def test_path_escape
+ assert_equal "a/b%20c+d%25", Utils.escape_path("a/b c+d%")
+ end
+
+ def test_segment_escape
+ assert_equal "a%2Fb%20c+d%25", Utils.escape_segment("a/b c+d%")
+ end
+
+ def test_fragment_escape
+ assert_equal "a/b%20c+d%25?e", Utils.escape_fragment("a/b c+d%?e")
+ end
+
+ def test_uri_unescape
+ assert_equal "a/b c+d", Utils.unescape_uri("a%2Fb%20c+d")
+ end
+
+ def test_uri_unescape_with_utf8_string
+ assert_equal "Šašinková", Utils.unescape_uri("%C5%A0a%C5%A1inkov%C3%A1".force_encoding(Encoding::US_ASCII))
+ end
+
+ def test_normalize_path_not_greedy
+ assert_equal "/foo%20bar%20baz", Utils.normalize_path("/foo%20bar%20baz")
+ end
+
+ def test_normalize_path_uppercase
+ assert_equal "/foo%AAbar%AAbaz", Utils.normalize_path("/foo%aabar%aabaz")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb
new file mode 100644
index 0000000000..2e7e8e1bea
--- /dev/null
+++ b/actionpack/test/journey/router_test.rb
@@ -0,0 +1,588 @@
+# encoding: UTF-8
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class TestRouter < ActiveSupport::TestCase
+ attr_reader :routes
+
+ def setup
+ @app = Routing::RouteSet::Dispatcher.new({})
+ @routes = Routes.new
+ @router = Router.new(@routes)
+ @formatter = Formatter.new(@routes)
+ end
+
+ class FakeRequestFeeler < Struct.new(:env, :called)
+ def new env
+ self.env = env
+ self
+ end
+
+ def hello
+ self.called = true
+ 'world'
+ end
+
+ def path_info; env['PATH_INFO']; end
+ def request_method; env['REQUEST_METHOD']; end
+ def ip; env['REMOTE_ADDR']; end
+ end
+
+ def test_dashes
+ router = Router.new(routes)
+
+ exp = Router::Strexp.build '/foo-bar-baz', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, {}, {:id => nil}, {}
+
+ 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
+ router = Router.new(routes)
+
+ #match the escaped version of /ほげ
+ exp = Router::Strexp.build '/%E3%81%BB%E3%81%92', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, {}, {:id => nil}, {}
+
+ 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_request_class_and_requirements_success
+ klass = FakeRequestFeeler.new nil
+ router = Router.new(routes)
+
+ requirements = { :hello => /world/ }
+
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, requirements, {:id => nil}, {}
+
+ env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
+ router.recognize(env) do |r, params|
+ assert_equal({:id => '10'}, params)
+ end
+
+ assert klass.called, 'hello should have been called'
+ assert_equal env.env, klass.env
+ end
+
+ def test_request_class_and_requirements_fail
+ klass = FakeRequestFeeler.new nil
+ router = Router.new(routes)
+
+ requirements = { :hello => /mom/ }
+
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ router.routes.add_route nil, path, requirements, {:id => nil}, {}
+
+ env = rails_env({'PATH_INFO' => '/foo/10'}, klass)
+ router.recognize(env) do |r, params|
+ flunk 'route should not be found'
+ end
+
+ assert klass.called, 'hello should have been called'
+ assert_equal env.env, klass.env
+ end
+
+ class CustomPathRequest < ActionDispatch::Request
+ def path_info
+ env['custom.path_info']
+ end
+
+ def path_info=(x)
+ env['custom.path_info'] = x
+ end
+ end
+
+ def test_request_class_overrides_path_info
+ router = Router.new(routes)
+
+ exp = Router::Strexp.build '/bar', {}, ['/.?']
+ path = Path::Pattern.new exp
+
+ routes.add_route nil, path, {}, {}, {}
+
+ env = rails_env({'PATH_INFO' => '/foo',
+ 'custom.path_info' => '/bar'}, CustomPathRequest)
+
+ recognized = false
+ router.recognize(env) do |r, params|
+ recognized = true
+ end
+
+ assert recognized, "route should have been recognized"
+ end
+
+ def test_regexp_first_precedence
+ add_routes @router, [
+ Router::Strexp.build("/whois/:domain", {:domain => /\w+\.[\w\.]+/}, ['/', '.', '?']),
+ Router::Strexp.build("/whois/:id(.:format)", {}, ['/', '.', '?'])
+ ]
+
+ 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', r.path.spec.to_s
+ end
+
+ def test_required_parts_verified_are_anchored
+ add_routes @router, [
+ Router::Strexp.build("/foo/:id", { :id => /\d/ }, ['/', '.', '?'], false)
+ ]
+
+ assert_raises(ActionController::UrlGenerationError) do
+ @formatter.generate(nil, { :id => '10' }, { })
+ end
+ end
+
+ def test_required_parts_are_verified_when_building
+ add_routes @router, [
+ Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ ]
+
+ path, _ = @formatter.generate(nil, { :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
+ add_routes @router, [
+ Router::Strexp.build("/foo(/:id)", {:id => /\d/}, ['/', '.', '?'], false)
+ ]
+
+ path, _ = @formatter.generate(nil, { :id => '10' }, { })
+ assert_equal '/foo/10', path
+
+ path, _ = @formatter.generate(nil, { }, { })
+ assert_equal '/foo', path
+
+ path, _ = @formatter.generate(nil, { :id => 'aa' }, { })
+ assert_equal '/foo/aa', path
+ end
+
+ def test_knows_what_parts_are_missing_from_named_route
+ route_name = "gorby_thunderhorse"
+ pattern = Router::Strexp.build("/foo/:id", { :id => /\d+/ }, ['/', '.', '?'], false)
+ path = Path::Pattern.new pattern
+ @router.routes.add_route nil, path, {}, {}, route_name
+
+ error = assert_raises(ActionController::UrlGenerationError) do
+ @formatter.generate(route_name, { }, { })
+ end
+
+ assert_match(/missing required keys: \[:id\]/, error.message)
+ end
+
+ def test_X_Cascade
+ add_routes @router, [ "/messages(.:format)" ]
+ 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
+ path = Path::Pattern.from_string '/foo(/:id)'
+ @router.routes.add_route nil, path, {}, {:id => nil}, {}
+
+ env = rails_env 'PATH_INFO' => '/foo/10'
+ @router.recognize(env) do |r, params|
+ assert_equal({:id => '10'}, params)
+ end
+
+ env = rails_env 'PATH_INFO' => '/foo'
+ @router.recognize(env) do |r, params|
+ assert_equal({:id => nil}, params)
+ end
+ end
+
+ def test_recognize_with_unbound_regexp
+ add_routes @router, [
+ Router::Strexp.build("/foo", { }, ['/', '.', '?'], false)
+ ]
+
+ 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
+ add_routes @router, [
+ Router::Strexp.build("/foo", { }, ['/', '.', '?'], true)
+ ]
+
+ 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
+ add_routes @router, [
+ "/messages(.:format)",
+ "/messages/new(.:format)",
+ "/messages/:id/edit(.:format)",
+ "/messages/:id(.:format)"
+ ]
+ 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
+ add_routes @router, [ "/messages/:a/:b" ]
+
+ path, _ = @formatter.generate(nil, { :a => 'a' }, { :b => 'b' })
+ assert_equal "/messages/a/b", path
+ end
+
+ def test_splat_in_recall
+ add_routes @router, [ "/*path" ]
+
+ path, _ = @formatter.generate(nil, { }, { :path => 'b' })
+ assert_equal "/b", path
+ end
+
+ def test_recall_should_be_used_when_scoring
+ add_routes @router, [
+ "/messages/:action(/:id(.:format))",
+ "/messages/:id(.:format)"
+ ]
+
+ path, _ = @formatter.generate(nil, { :id => 10 }, { :action => 'index' })
+ assert_equal "/messages/index/10", path
+ end
+
+ def test_nil_path_parts_are_ignored
+ path = Path::Pattern.from_string "/:controller(/:action(.:format))"
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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"] ]
+ str = Router::Strexp.build("/", Hash[params], ['/', '.', '?'], true)
+ path = Path::Pattern.new str
+
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ path, _ = @formatter.generate(nil, Hash[params], {})
+ assert_equal '/', path
+ end
+
+ def test_generate_calls_param_proc
+ path = Path::Pattern.from_string '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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
+ path = Path::Pattern.from_string '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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
+ path = Path::Pattern.from_string '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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
+ path = Path::Pattern.from_string '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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
+ path = Path::Pattern.from_string '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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_uses_recall_if_needed
+ path = Path::Pattern.from_string '/:controller(/:action(/:id))'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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
+ path = Path::Pattern.from_string '/:controller(/:action)'
+ @router.routes.add_route @app, path, {}, {}, {}
+
+ 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
+ path = Path::Pattern.from_string "/:controller(/:action(/:id))"
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {}, {})
+
+ env = rails_env 'PATH_INFO' => request_path
+ called = false
+
+ @router.recognize(env) do |r, params|
+ assert_equal route, r
+ assert_equal(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
+ path = Path::Pattern.from_string '/:segment/*splat'
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {}, {})
+
+ env = rails_env 'PATH_INFO' => request_path
+ called = false
+
+ @router.recognize(env) do |r, params|
+ assert_equal route, r
+ assert_equal(expected, params)
+ called = true
+ end
+
+ assert called
+ end
+ end
+
+ def test_namespaced_controller
+ strexp = Router::Strexp.build(
+ "/:controller(/:action(/:id))",
+ { :controller => /.+?/ },
+ ["/", ".", "?"]
+ )
+ path = Path::Pattern.new strexp
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {}, {})
+
+ 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
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+ app = Object.new
+ route = @router.routes.add_route(app, path, {}, {:controller => 'books'})
+
+ 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_request_as_get_route
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+ app = Object.new
+ conditions = {
+ :request_method => 'GET'
+ }
+ @router.routes.add_route(app, path, conditions, {})
+
+ 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_verbs
+ path = Path::Pattern.from_string "/books(/:action(.:format))"
+ app = Object.new
+ conditions = {
+ :request_method => 'GET'
+ }
+ @router.routes.add_route(app, path, conditions, {})
+
+ conditions = conditions.dup
+ conditions[:request_method] = 'POST'
+
+ post = @router.routes.add_route(app, path, conditions, {})
+
+ env = rails_env 'PATH_INFO' => '/books/list.rss',
+ "REQUEST_METHOD" => "POST"
+
+ called = false
+ @router.recognize(env) do |r, params|
+ assert_equal post, r
+ called = true
+ end
+
+ assert called
+ end
+
+ private
+
+ def add_routes router, paths
+ paths.each do |path|
+ if String === path
+ path = Path::Pattern.from_string path
+ else
+ path = Path::Pattern.new path
+ end
+ router.routes.add_route @app, path, {}, {}, {}
+ end
+ end
+
+ def rails_env env, klass = ActionDispatch::Request
+ klass.new 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
diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb
new file mode 100644
index 0000000000..a4efc82b8c
--- /dev/null
+++ b/actionpack/test/journey/routes_test.rb
@@ -0,0 +1,53 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Journey
+ class TestRoutes < ActiveSupport::TestCase
+ def test_clear
+ routes = Routes.new
+ exp = Router::Strexp.build '/foo(/:id)', {}, ['/.?']
+ path = Path::Pattern.new exp
+ requirements = { :hello => /world/ }
+
+ routes.add_route nil, path, requirements, {:id => nil}, {}
+ assert_equal 1, routes.length
+
+ routes.clear
+ assert_equal 0, routes.length
+ end
+
+ def test_ast
+ routes = Routes.new
+ path = Path::Pattern.from_string '/hello'
+
+ routes.add_route nil, path, {}, {}, {}
+ ast = routes.ast
+ routes.add_route nil, path, {}, {}, {}
+ assert_not_equal ast, routes.ast
+ end
+
+ def test_simulator_changes
+ routes = Routes.new
+ path = Path::Pattern.from_string '/hello'
+
+ routes.add_route nil, path, {}, {}, {}
+ sim = routes.simulator
+ routes.add_route nil, path, {}, {}, {}
+ assert_not_equal sim, routes.simulator
+ end
+
+ def test_first_name_wins
+ #def add_route app, path, conditions, defaults, name = nil
+ routes = Routes.new
+
+ one = Path::Pattern.from_string '/hello'
+ two = Path::Pattern.from_string '/aaron'
+
+ routes.add_route nil, one, {}, {}, 'aaron'
+ routes.add_route nil, two, {}, {}, 'aaron'
+
+ assert_equal '/hello', routes.named_routes['aaron'].path.spec.to_s
+ end
+ end
+ end
+end
diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb
new file mode 100644
index 0000000000..1a2863b689
--- /dev/null
+++ b/actionpack/test/lib/controller/fake_controllers.rb
@@ -0,0 +1,35 @@
+class ContentController < ActionController::Base; end
+
+module Admin
+ class AccountsController < ActionController::Base; end
+ class PostsController < ActionController::Base; end
+ class StuffController < ActionController::Base; end
+ class UserController < ActionController::Base; end
+ class UsersController < ActionController::Base; end
+end
+
+module Api
+ class UsersController < ActionController::Base; end
+ class ProductsController < ActionController::Base; end
+end
+
+class AccountController < ActionController::Base; end
+class ArchiveController < ActionController::Base; end
+class ArticlesController < ActionController::Base; end
+class BarController < ActionController::Base; end
+class BlogController < ActionController::Base; end
+class BooksController < ActionController::Base; end
+class CarsController < ActionController::Base; end
+class CcController < ActionController::Base; end
+class CController < ActionController::Base; end
+class FooController < ActionController::Base; end
+class GeocodeController < ActionController::Base; end
+class NewsController < ActionController::Base; end
+class NotesController < ActionController::Base; end
+class PagesController < ActionController::Base; end
+class PeopleController < ActionController::Base; end
+class PostsController < ActionController::Base; end
+class SubpathBooksController < ActionController::Base; end
+class SymbolsController < ActionController::Base; end
+class UserController < ActionController::Base; end
+class UsersController < ActionController::Base; end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
new file mode 100644
index 0000000000..b8b51d86c2
--- /dev/null
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -0,0 +1,118 @@
+require "active_model"
+
+class Customer < Struct.new(:name, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ undef_method :to_json
+
+ def to_xml(options={})
+ if options[:builder]
+ options[:builder].name name
+ else
+ "<name>#{name}</name>"
+ end
+ end
+
+ def to_js(options={})
+ "name: #{name.inspect}"
+ end
+ alias :to_text :to_js
+
+ def errors
+ []
+ end
+
+ def persisted?
+ id.present?
+ end
+end
+
+class ValidatedCustomer < Customer
+ def errors
+ if name =~ /Sikachu/i
+ []
+ else
+ [{:name => "is invalid"}]
+ end
+ end
+end
+
+module Quiz
+ class Question < Struct.new(:name, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def persisted?
+ id.present?
+ end
+ end
+
+ class Store < Question
+ end
+end
+
+class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+ extend ActiveModel::Translation
+
+ alias_method :secret?, :secret
+ alias_method :persisted?, :persisted
+
+ def initialize(*args)
+ super
+ @persisted = false
+ end
+
+ attr_accessor :author
+ def author_attributes=(attributes); end
+
+ attr_accessor :comments, :comment_ids
+ def comments_attributes=(attributes); end
+
+ attr_accessor :tags
+ def tags_attributes=(attributes); end
+end
+
+class Comment
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ attr_reader :id
+ attr_reader :post_id
+ def initialize(id = nil, post_id = nil); @id, @post_id = id, post_id end
+ def to_key; id ? [id] : nil end
+ def save; @id = 1; @post_id = 1 end
+ def persisted?; @id.present? end
+ def to_param; @id.to_s; end
+ def name
+ @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"
+ end
+
+ attr_accessor :relevances
+ def relevances_attributes=(attributes); end
+
+ attr_accessor :body
+end
+
+module Blog
+ def self.use_relative_model_naming?
+ true
+ end
+
+ class Post < Struct.new(:title, :id)
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ def persisted?
+ id.present?
+ end
+ end
+end
+
+class RenderJsonTestException < Exception
+ def as_json(options = nil)
+ { :error => self.class.name, :message => self.to_s }
+ end
+end
diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb
new file mode 100644
index 0000000000..09ca7ff73b
--- /dev/null
+++ b/actionpack/test/routing/helper_test.rb
@@ -0,0 +1,45 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class HelperTest < ActiveSupport::TestCase
+ class Duck
+ def to_param
+ nil
+ end
+ end
+
+ def test_exception
+ rs = ::ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ resources :ducks do
+ member do
+ get :pond
+ end
+ end
+ end
+
+ x = Class.new {
+ include rs.url_helpers
+ }
+ assert_raises ActionController::UrlGenerationError do
+ x.new.pond_duck_path Duck.new
+ end
+ end
+
+ def test_path_deprecation
+ rs = ::ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ resources :ducks
+ end
+
+ x = Class.new {
+ include rs.url_helpers(false)
+ }
+ assert_deprecated do
+ assert_equal '/ducks', x.new.ducks_path
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/tmp/.gitignore b/actionpack/test/tmp/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/tmp/.gitignore