diff options
Diffstat (limited to 'actionpack/test/controller')
75 files changed, 19683 insertions, 0 deletions
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 "<p>hello world</p>" + 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 "<>"", 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' => '☃', 'username' => 'sikachu' } + assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', '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<=?' + 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 |