diff options
Diffstat (limited to 'actionpack/test')
64 files changed, 1417 insertions, 778 deletions
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index a4770b66e1..6db045fcd7 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -30,7 +30,7 @@ module AbstractController end test "register mime types on method missing" do - AbstractController::Collector.send(:remove_method, :js) + AbstractController::Collector.remove_method :js begin collector = MyCollector.new assert_not_respond_to collector, :js diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index f4787ed27a..6460ca6f8d 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -13,13 +13,6 @@ silence_warnings do Encoding.default_external = Encoding::UTF_8 end -require "drb" -begin - require "drb/unix" -rescue LoadError - puts "'drb/unix' is not available" -end - if ENV["TRAVIS"] PROCESS_COUNT = 0 else @@ -80,7 +73,7 @@ end module ActiveSupport class TestCase if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 - parallelize_me! + parallelize(workers: PROCESS_COUNT) end end end @@ -232,6 +225,7 @@ module ActionController routes = ActionDispatch::Routing::RouteSet.new routes.draw(&block) include routes.url_helpers + routes end end @@ -358,86 +352,19 @@ class ImagesController < ResourcesController; end require "active_support/testing/method_call_assertions" -class ForkingExecutor - class Server - include DRb::DRbUndumped - - def initialize - @queue = Queue.new - end - - def record(reporter, result) - reporter.record result - end - - def <<(o) - o[2] = DRbObject.new(o[2]) if o - @queue << o - end - def pop; @queue.pop; end - end - - def initialize(size) - @size = size - @queue = Server.new - @pool = nil - @url = DRb.start_service("drbunix:", @queue).uri - end - - def <<(work); @queue << work; end - - def shutdown - pool = @size.times.map { - fork { - DRb.stop_service - queue = DRbObject.new_with_uri @url - while job = queue.pop - klass = job[0] - method = job[1] - reporter = job[2] - result = Minitest.run_one_method klass, method - if result.error? - translate_exceptions result - end - queue.record reporter, result - end - } - } - @size.times { @queue << nil } - pool.each { |pid| Process.waitpid pid } - end +class ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions private - def translate_exceptions(result) - result.failures.map! { |e| - begin - Marshal.dump e - e - rescue TypeError - ex = Exception.new e.message - ex.set_backtrace e.backtrace - Minitest::UnexpectedError.new ex - end - } + # Skips the current run on Rubinius using Minitest::Assertions#skip + def rubinius_skip(message = "") + skip message if RUBY_ENGINE == "rbx" end -end - -if RUBY_ENGINE == "ruby" && PROCESS_COUNT > 0 - # Use N processes (N defaults to 4) - Minitest.parallel_executor = ForkingExecutor.new(PROCESS_COUNT) -end - -class ActiveSupport::TestCase - include ActiveSupport::Testing::MethodCallAssertions - # Skips the current run on Rubinius using Minitest::Assertions#skip - private def rubinius_skip(message = "") - skip message if RUBY_ENGINE == "rbx" - end - # Skips the current run on JRuby using Minitest::Assertions#skip - private def jruby_skip(message = "") - skip message if defined?(JRUBY_VERSION) - end + # Skips the current run on JRuby using Minitest::Assertions#skip + def jruby_skip(message = "") + skip message if defined?(JRUBY_VERSION) + end end class DrivenByRackTest < ActionDispatch::SystemTestCase @@ -455,3 +382,5 @@ end class DrivenBySeleniumWithHeadlessFirefox < ActionDispatch::SystemTestCase driven_by :selenium, using: :headless_firefox end + +require_relative "../../tools/test_common" diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index 763df3a776..51286155b9 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -276,16 +276,14 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase 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 + 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 def test_template_objects_exist diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index a672ede1a9..d8cea10153 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -138,7 +138,7 @@ class ControllerInstanceTests < ActiveSupport::TestCase response_headers = SimpleController.action("hello").call( "REQUEST_METHOD" => "GET", - "rack.input" => -> {} + "rack.input" => -> { } )[1] assert response_headers.key?("X-Frame-Options") diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 6fe036dd15..f09e812147 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -60,14 +60,6 @@ class FragmentCachingTest < ActionController::TestCase @m2v2 = ModelWithKeyAndVersion.new("model/2", "2") end - def test_fragment_cache_key - assert_deprecated do - 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 - end - def test_combined_fragment_cache_key assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key") assert_equal [ :views, "test.host/fragment_caching_test/some_action" ], @@ -220,7 +212,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", - @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached")}/fragment") + @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached", "html")}/fragment") end def test_fragment_caching_in_partials @@ -229,7 +221,7 @@ CACHED assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", - @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial")}/test.host/functional_caching/html_fragment_cached_with_partial")) + @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial", "html")}/test.host/functional_caching/html_fragment_cached_with_partial")) end def test_skipping_fragment_cache_digesting @@ -259,7 +251,7 @@ CACHED assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", - @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached")}/test.host/functional_caching/inline_fragment_cached")) + @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached", "html")}/test.host/functional_caching/inline_fragment_cached")) end def test_fragment_cache_instrumentation @@ -279,36 +271,39 @@ CACHED end def test_html_formatted_fragment_caching - get :formatted_fragment_cached, format: "html" + format = "html" + get :formatted_fragment_cached, format: format 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/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment") end def test_xml_formatted_fragment_caching - get :formatted_fragment_cached, format: "xml" + format = "xml" + get :formatted_fragment_cached, format: format 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/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached")}/fragment") + @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment") end def test_fragment_caching_with_variant - get :formatted_fragment_cached_with_variant, format: "html", params: { v: :phone } + format = "html" + get :formatted_fragment_cached_with_variant, format: format, params: { v: :phone } 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/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}/fragment") + @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant", format)}/fragment") end def test_fragment_caching_with_html_partials_in_xml @@ -317,8 +312,8 @@ CACHED end private - def template_digest(name) - ActionView::Digestor.digest(name: name, finder: @controller.lookup_context) + def template_digest(name, format) + ActionView::Digestor.digest(name: name, format: format, finder: @controller.lookup_context) end end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 425a6e25cc..fcee812ee4 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -457,6 +457,7 @@ class FilterTest < ActionController::TestCase prepend_before_action :before_all prepend_after_action :after_all before_action :between_before_all_and_after_all + after_action :between_before_all_and_after_all def before_all @ran_filter ||= [] @@ -472,6 +473,7 @@ class FilterTest < ActionController::TestCase @ran_filter ||= [] @ran_filter << "between_before_all_and_after_all" end + def show render plain: "hello" end @@ -765,7 +767,7 @@ class FilterTest < ActionController::TestCase def test_running_prepended_before_and_after_action test_process(PrependingBeforeAndAfterController) - assert_equal %w( before_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter) + assert_equal %w( before_all between_before_all_and_after_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter) end def test_skipping_and_limiting_controller @@ -886,7 +888,7 @@ class ControllerWithSymbolAsFilter < PostsController yield # Do stuff... - wtf += 1 + wtf + 1 end end @@ -998,16 +1000,12 @@ class YieldingAroundFiltersTest < ActionController::TestCase def test_nested_actions controller = ControllerWithNestedFilters assert_nothing_raised do - begin - test_process(controller, "raises_both") - rescue Before, After - end + test_process(controller, "raises_both") + rescue Before, After end assert_raise Before do - begin - test_process(controller, "raises_both") - rescue After - end + test_process(controller, "raises_both") + rescue After end end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 34bc2c0caa..bf95c633e5 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -242,8 +242,11 @@ end class FlashIntegrationTest < ActionDispatch::IntegrationTest SessionKey = "_myapp_session" - Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") - Rotations = ActiveSupport::Messages::RotationConfiguration.new + Generator = ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000) + ) + Rotations = ActiveSupport::Messages::RotationConfiguration.new + SIGNED_COOKIE_SALT = "signed cookie" class TestController < ActionController::Base add_flash_types :bar @@ -342,6 +345,21 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end + def test_flash_usable_in_metal_without_helper + controller_class = nil + + assert_nothing_raised do + controller_class = Class.new(ActionController::Metal) do + include ActionController::Flash + end + end + + controller = controller_class.new + + assert_respond_to controller, :alert + assert_respond_to controller, :notice + end + private # Overwrite get to send SessionSecret in env hash @@ -350,6 +368,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest args[0][:env] ||= {} args[0][:env]["action_dispatch.key_generator"] ||= Generator args[0][:env]["action_dispatch.cookies_rotations"] = Rotations + args[0][:env]["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT super(path, *args) end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 3f211cd60d..dd4ff85d11 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -44,7 +44,10 @@ class HttpDigestAuthenticationTest < ActionController::TestCase 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) + @request.env["action_dispatch.key_generator"] = ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new(@secret) + ) + @request.env["action_dispatch.http_auth_salt"] = "http authentication" end teardown do @@ -272,7 +275,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase credentials.merge!(options) path_info = @request.env["PATH_INFO"].to_s uri = options[:uri] || path_info - credentials.merge!(uri: uri) + credentials[:uri] = uri @request.env["ORIGINAL_FULLPATH"] = path_info ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index 672aa1351c..103123f98c 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -150,7 +150,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase end test "token_and_options returns empty string with empty token" do - token = "".dup + token = +"" actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 39ede1442a..4dddd98f9f 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -152,7 +152,7 @@ class IntegrationTestTest < ActiveSupport::TestCase assert_equal "pass", @test.foo ensure # leave other tests as unaffected as possible - mixin.__send__(:remove_method, :method_missing) + mixin.remove_method :method_missing end end end @@ -808,17 +808,17 @@ class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest end end - test "session uses default url options from routes" do + 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 + 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 + test "controller can override default URL options from request" do get "/bar" assert_response :success assert_equal "http://bar.com/foo", foos_url diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 431fe90b23..d81c43b87d 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -304,7 +304,7 @@ module ActionController # Simulate InterlockHook ActiveSupport::Dependencies.interlock.start_running res = get :write_sleep_autoload - res.each {} + res.each { } ActiveSupport::Dependencies.interlock.done_running end diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index be455642de..0562c16284 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -82,9 +82,7 @@ module Another @last_payload = payload end - def last_payload - @last_payload - end + attr_reader :last_payload end end diff --git a/actionpack/test/controller/metal_test.rb b/actionpack/test/controller/metal_test.rb index 248ef36b7c..7b53092266 100644 --- a/actionpack/test/controller/metal_test.rb +++ b/actionpack/test/controller/metal_test.rb @@ -20,7 +20,7 @@ class MetalControllerInstanceTests < ActiveSupport::TestCase response_headers = SimpleController.action("hello").call( "REQUEST_METHOD" => "GET", - "rack.input" => -> {} + "rack.input" => -> { } )[1] assert_not response_headers.key?("X-Frame-Options") diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 771eccb29b..2f8f191828 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -78,7 +78,7 @@ class RespondToController < ActionController::Base def missing_templates respond_to do |type| # This test requires a block that is empty - type.json {} + type.json { } type.xml end end @@ -102,10 +102,30 @@ class RespondToController < ActionController::Base end end + def using_conflicting_nested_js_then_html + respond_to do |outer_type| + outer_type.js do + respond_to do |inner_type| + inner_type.html { render body: "HTML" } + end + end + end + end + + def using_non_conflicting_nested_js_then_js + respond_to do |outer_type| + outer_type.js do + respond_to do |inner_type| + inner_type.js { render body: "JS" } + end + end + end + end + def custom_type_handling respond_to do |type| type.html { render body: "HTML" } - type.custom("application/crazy-xml") { render body: "Crazy XML" } + type.custom("application/fancy-xml") { render body: "Fancy XML" } type.all { render body: "Nothing" } end end @@ -138,6 +158,12 @@ class RespondToController < ActionController::Base end end + def handle_any_with_template + respond_to do |type| + type.any { render "test/hello_world" } + end + end + def all_types_with_layout respond_to do |type| type.html @@ -294,12 +320,14 @@ class RespondToControllerTest < ActionController::TestCase @request.host = "www.example.com" Mime::Type.register_alias("text/html", :iphone) Mime::Type.register("text/x-mobile", :mobile) + Mime::Type.register("application/fancy-xml", :fancy_xml) end def teardown super Mime::Type.unregister(:iphone) Mime::Type.unregister(:mobile) + Mime::Type.unregister(:fancy_xml) end def test_html @@ -430,6 +458,20 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "<p>Hello world!</p>\n", @response.body end + def test_using_conflicting_nested_js_then_html + @request.accept = "*/*" + assert_raises(ActionController::RespondToMismatchError) do + get :using_conflicting_nested_js_then_html + end + end + + def test_using_non_conflicting_nested_js_then_js + @request.accept = "*/*" + get :using_non_conflicting_nested_js_then_js + assert_equal "text/javascript", @response.content_type + assert_equal "JS", @response.body + end + def test_with_atom_content_type @request.accept = "" @request.env["CONTENT_TYPE"] = "application/atom+xml" @@ -455,10 +497,10 @@ class RespondToControllerTest < ActionController::TestCase end def test_custom_types - @request.accept = "application/crazy-xml" + @request.accept = "application/fancy-xml" get :custom_type_handling - assert_equal "application/crazy-xml", @response.content_type - assert_equal "Crazy XML", @response.body + assert_equal "application/fancy-xml", @response.content_type + assert_equal "Fancy XML", @response.body @request.accept = "text/html" get :custom_type_handling @@ -536,6 +578,13 @@ class RespondToControllerTest < ActionController::TestCase assert_equal "HTML", @response.body end + def test_handle_any_with_template + @request.accept = "*/*" + + get :handle_any_with_template + assert_equal "Hello world!", @response.body + end + def test_html_type_with_layout @request.accept = "text/html" get :all_types_with_layout diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index b049022a06..7572d514fb 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -13,7 +13,7 @@ module BareMetalTest 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 = "".dup + string = +"" body.each do |part| assert part.is_a?(String), "Each part of the body must be a String" diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb index 7205e90176..548fa4300d 100644 --- a/actionpack/test/controller/new_base/content_negotiation_test.rb +++ b/actionpack/test/controller/new_base/content_negotiation_test.rb @@ -20,9 +20,19 @@ module ContentNegotiation assert_body "Hello world */*!" end - test "Not all mimes are converted to symbol" do + test "A js or */* Accept header will return HTML" do + get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "text/javascript, */*" } + assert_body "Hello world text/html!" + end + + test "A js or */* Accept header on xhr will return JavaScript" do + get "/content_negotiation/basic/hello", headers: { "HTTP_ACCEPT" => "text/javascript, */*" }, xhr: true + assert_body "Hello world text/javascript!" + end + + test "Unregistered mimes are ignored" do get "/content_negotiation/basic/all", headers: { "HTTP_ACCEPT" => "text/plain, mime/another" } - assert_body '[:text, "mime/another"]' + assert_body "[:text]" 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 deleted file mode 100644 index 07fbadae9f..0000000000 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -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 def __controller_method__ - "controller context!" - end - - # 3) Set view_context to self - private def view_context - self - 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 index de8af029e0..01d0223519 100644 --- a/actionpack/test/controller/new_base/render_file_test.rb +++ b/actionpack/test/controller/new_base/render_file_test.rb @@ -17,12 +17,12 @@ module RenderFile def relative_path @secret = "in the sauce" - render file: "../../fixtures/test/render_file_with_ivar" + render file: "../actionpack/test/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" + render file: "../actionpack/test/fixtures/test/dot.directory/render_file_with_ivar" end def pathname @@ -40,32 +40,44 @@ module RenderFile testing RenderFile::BasicController test "rendering simple template" do - get :index + assert_deprecated do + get :index + end assert_response "Hello world!" end test "rendering template with ivar" do - get :with_instance_variables + assert_deprecated do + get :with_instance_variables + end assert_response "The secret is in the sauce\n" end test "rendering a relative path" do - get :relative_path + assert_deprecated do + get :relative_path + end assert_response "The secret is in the sauce\n" end test "rendering a relative path with dot" do - get :relative_path_with_dot + assert_deprecated do + get :relative_path_with_dot + end assert_response "The secret is in the sauce\n" end test "rendering a Pathname" do - get :pathname + assert_deprecated do + get :pathname + end assert_response "The secret is in the sauce\n" end test "rendering file with locals" do - get :with_locals + assert_deprecated do + get :with_locals + end assert_response "The secret is in the sauce\n" end end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 68c7f2d9ea..7789e654d5 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -75,6 +75,28 @@ class ParametersAccessorsTest < ActiveSupport::TestCase end end + test "each_value carries permitted status" do + @params.permit! + @params.each_value do |value| + assert_predicate(value, :permitted?) + end + end + + test "each_value carries unpermitted status" do + @params.each_value do |value| + assert_not_predicate(value, :permitted?) + end + end + + test "each_key converts to hash for permitted" do + @params.permit! + @params.each_key { |key| assert_kind_of(String, key) if key == "person" } + end + + test "each_key converts to hash for unpermitted" do + @params.each_key { |key| assert_kind_of(String, key) if key == "person" } + end + test "empty? returns true when params contains no key/value pairs" do params = ActionController::Parameters.new assert_empty params diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb index fe0e5e368d..974612fb7b 100644 --- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb +++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb @@ -20,7 +20,7 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase end end - test "permits parameters that are whitelisted" do + test "allows both explicitly listed and always-permitted parameters" do params = ActionController::Parameters.new( book: { pages: 65 }, format: "json") diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index d2fa0aa16e..fbfe24059b 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -365,17 +365,15 @@ class ParametersPermitTest < ActiveSupport::TestCase 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_predicate params.slice(:person), :permitted? - assert_predicate params[:person][:name], :permitted? - ensure - ActionController::Parameters.permit_all_parameters = false - end + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new(person: { + age: "32", name: { first: "David", last: "Heinemeier Hansson" } + }) + + assert_predicate params.slice(:person), :permitted? + assert_predicate params[:person][:name], :permitted? + ensure + ActionController::Parameters.permit_all_parameters = false end test "permitting parameters as an array" do @@ -396,16 +394,14 @@ class ParametersPermitTest < ActiveSupport::TestCase end test "to_h returns converted hash when .permit_all_parameters is set" do - begin - ActionController::Parameters.permit_all_parameters = true - params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") - - assert_instance_of ActiveSupport::HashWithIndifferentAccess, params.to_h - assert_not_kind_of ActionController::Parameters, params.to_h - assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h) - ensure - ActionController::Parameters.permit_all_parameters = false - end + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") + + assert_instance_of ActiveSupport::HashWithIndifferentAccess, params.to_h + assert_not_kind_of ActionController::Parameters, params.to_h + assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_h) + ensure + ActionController::Parameters.permit_all_parameters = false end test "to_hash raises UnfilteredParameters on unfiltered params" do @@ -429,17 +425,15 @@ class ParametersPermitTest < ActiveSupport::TestCase end test "to_hash returns converted hash when .permit_all_parameters is set" do - begin - ActionController::Parameters.permit_all_parameters = true - params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") - - assert_instance_of Hash, params.to_hash - assert_not_kind_of ActionController::Parameters, params.to_hash - assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash) - assert_equal({ "crab" => "Senjougahara Hitagi" }, params) - ensure - ActionController::Parameters.permit_all_parameters = false - end + ActionController::Parameters.permit_all_parameters = true + params = ActionController::Parameters.new(crab: "Senjougahara Hitagi") + + assert_instance_of Hash, params.to_hash + assert_not_kind_of ActionController::Parameters, params.to_hash + assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash) + assert_equal({ "crab" => "Senjougahara Hitagi" }, params) + ensure + ActionController::Parameters.permit_all_parameters = false end test "to_unsafe_h returns unfiltered params" do diff --git a/actionpack/test/controller/params_parse_test.rb b/actionpack/test/controller/params_parse_test.rb new file mode 100644 index 0000000000..440ab06fd7 --- /dev/null +++ b/actionpack/test/controller/params_parse_test.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class ParamsParseTest < ActionController::TestCase + class UsersController < ActionController::Base + def create + head :ok + end + end + + tests UsersController + + def test_parse_error_logged_once + log_output = capture_log_output do + post :create, body: "{", as: :json + end + assert_equal <<~LOG, log_output + Error occurred while parsing request parameters. + Contents: + + { + LOG + end + + private + + def capture_log_output + output = StringIO.new + request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output) + yield + output.string + end +end diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 2959dc3e4d..7f1c41787a 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -5,6 +5,12 @@ require "abstract_unit" class Workshop extend ActiveModel::Naming include ActiveModel::Conversion + + OUT_OF_SCOPE_BLOCK = proc do + raise "Not executed in controller's context" unless RedirectController === self + request.original_url + end + attr_accessor :id def initialize(id) @@ -62,10 +68,18 @@ class RedirectController < ActionController::Base redirect_back(fallback_location: "/things/stuff", status: 307) end + def redirect_back_with_status_and_fallback_location_to_another_host + redirect_back(fallback_location: "http://www.rubyonrails.org/", status: 307) + end + def safe_redirect_back_with_status redirect_back(fallback_location: "/things/stuff", status: 307, allow_other_host: false) end + def safe_redirect_back_with_status_and_fallback_location_to_another_host + redirect_back(fallback_location: "http://www.rubyonrails.org/", status: 307, allow_other_host: false) + end + def host_redirect redirect_to action: "other_host", only_path: false, host: "other.test.host" end @@ -119,6 +133,10 @@ class RedirectController < ActionController::Base redirect_to proc { { action: "hello_world" } } end + def redirect_to_out_of_scope_block + redirect_to Workshop::OUT_OF_SCOPE_BLOCK + end + def redirect_with_header_break redirect_to "/lol\r\nwat" end @@ -204,6 +222,13 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/things/stuff", redirect_to_url end + def test_relative_url_redirect_host_with_port + request.host = "test.host:1234" + get :relative_url_redirect_with_status + assert_response 302 + assert_equal "http://test.host:1234/things/stuff", redirect_to_url + end + def test_simple_redirect_using_options get :host_redirect assert_response :redirect @@ -263,6 +288,13 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/things/stuff", redirect_to_url end + def test_redirect_back_with_no_referer_redirects_to_another_host + get :redirect_back_with_status_and_fallback_location_to_another_host + + assert_response 307 + assert_equal "http://www.rubyonrails.org/", redirect_to_url + end + def test_safe_redirect_back_from_other_host @request.env["HTTP_REFERER"] = "http://another.host/coming/from" get :safe_redirect_back_with_status @@ -280,6 +312,20 @@ class RedirectTest < ActionController::TestCase assert_equal referer, redirect_to_url end + def test_safe_redirect_back_with_no_referer + get :safe_redirect_back_with_status + + assert_response 307 + assert_equal "http://test.host/things/stuff", redirect_to_url + end + + def test_safe_redirect_back_with_no_referer_redirects_to_another_host + get :safe_redirect_back_with_status_and_fallback_location_to_another_host + + assert_response 307 + assert_equal "http://www.rubyonrails.org/", redirect_to_url + end + def test_redirect_to_record with_routing do |set| set.draw do @@ -326,6 +372,12 @@ class RedirectTest < ActionController::TestCase assert_redirected_to "http://www.rubyonrails.org/" end + def test_redirect_to_out_of_scope_block + get :redirect_to_out_of_scope_block + assert_response :redirect + assert_redirected_to "http://test.host/redirect/redirect_to_out_of_scope_block" + end + def test_redirect_to_with_block_and_accepted_options with_routing do |set| set.draw do diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 6e3bd0596b..8bb6617eaa 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -183,6 +183,11 @@ class TestController < ActionController::Base render action: "hello_world" end + def conditional_hello_without_expires_and_public_header + response.headers["Cache-Control"] = "public, no-cache" + render action: "hello_world" + end + def conditional_hello_with_bangs render action: "hello_world" end @@ -250,6 +255,15 @@ class TestController < ActionController::Base head 204 end + def head_default_content_type + # simulating path like "/1.foobar" + request.formats = [] + + respond_to do |format| + format.any { head 200 } + end + end + private def set_variable_for_layout @@ -309,11 +323,12 @@ class ExpiresInRenderTest < ActionController::TestCase end def test_dynamic_render_with_file - # This is extremely bad, but should be possible to do. assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) - response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } - assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), - response.body + assert_deprecated do + assert_raises ActionView::MissingTemplate do + get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' } + end + end end def test_dynamic_render_with_absolute_path @@ -337,9 +352,11 @@ class ExpiresInRenderTest < ActionController::TestCase def test_permitted_dynamic_render_file_hash assert File.exist?(File.expand_path("../../test/abstract_unit.rb", __dir__)) - response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } - assert_equal File.read(File.expand_path("../../test/abstract_unit.rb", __dir__)), - response.body + assert_deprecated do + assert_raises ActionView::MissingTemplate do + get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } } + end + end end def test_dynamic_render_file_hash @@ -409,6 +426,11 @@ class ExpiresInRenderTest < ActionController::TestCase assert_equal "no-cache", @response.headers["Cache-Control"] end + def test_no_expires_now_with_public + get :conditional_hello_without_expires_and_public_header + assert_equal "public, no-cache", @response.headers["Cache-Control"] + end + def test_date_header_when_expires_in time = Time.mktime(2011, 10, 30) Time.stub :now, time do @@ -814,6 +836,11 @@ class HeadRenderTest < ActionController::TestCase get :head_and_return end end + + def test_head_default_content_type + post :head_default_content_type + assert_equal "text/html", @response.header["Content-Type"] + end end class HttpCacheForeverTest < ActionController::TestCase diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb index ae8330e029..ea79f4de85 100644 --- a/actionpack/test/controller/renderer_test.rb +++ b/actionpack/test/controller/renderer_test.rb @@ -40,7 +40,7 @@ class RendererTest < ActiveSupport::TestCase test "rendering with an instance renderer" do renderer = ApplicationController.renderer.new - content = renderer.render file: "test/hello_world" + content = assert_deprecated { renderer.render file: "test/hello_world" } assert_equal "Hello world!", content end @@ -115,14 +115,14 @@ class RendererTest < ActiveSupport::TestCase assert_equal "true", content end - test "return valid asset url with defaults" do + test "return valid asset URL with defaults" do renderer = ApplicationController.renderer content = renderer.render inline: "<%= asset_url 'asset.jpg' %>" assert_equal "http://example.org/asset.jpg", content end - test "return valid asset url when https is true" do + test "return valid asset URL when https is true" do renderer = ApplicationController.renderer.new https: true content = renderer.render inline: "<%= asset_url 'asset.jpg' %>" diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 4ed79073e5..089b0b94d4 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -62,12 +62,8 @@ class RescueController < ActionController::Base render plain: exception.message end - rescue_from ActionView::TemplateError do - render plain: "action_view templater error" - end - - rescue_from IOError do - render plain: "io error" + rescue_from ActionDispatch::Http::Parameters::ParseError do + render plain: "parse error", status: :bad_request end before_action(only: :before_action_raises) { raise "umm nice" } @@ -75,19 +71,6 @@ class RescueController < ActionController::Base def before_action_raises end - def raises - render plain: "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 @@ -130,6 +113,11 @@ class RescueController < ActionController::Base raise ResourceUnavailableToRescueAsString end + def arbitrary_action + params + render plain: "arbitrary action" + end + def missing_template end @@ -306,6 +294,23 @@ class RescueControllerTest < ActionController::TestCase get :exception_with_no_handler_for_wrapper assert_response :unprocessable_entity end + + test "can rescue a ParseError" do + capture_log_output do + post :arbitrary_action, body: "{", as: :json + end + assert_response :bad_request + assert_equal "parse error", response.body + end + + private + + def capture_log_output + output = StringIO.new + request.set_header "action_dispatch.logger", ActiveSupport::Logger.new(output) + yield + output.string + end end class RescueTest < ActionDispatch::IntegrationTest @@ -325,10 +330,6 @@ class RescueTest < ActionDispatch::IntegrationTest raise RecordInvalid end - def b00m - raise "b00m" - end - private def show_errors(exception) render plain: exception.message @@ -356,7 +357,6 @@ class RescueTest < ActionDispatch::IntegrationTest 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 diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 3688fdbeee..d2146f12a5 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -853,6 +853,28 @@ class ResourcesTest < ActionController::TestCase end end + def test_resource_has_show_action_but_does_not_have_destroy_action + with_routing do |set| + set.draw do + resources :products, only: [:show, :destroy], except: :destroy + 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_show_action_but_does_not_have_destroy_action + with_routing do |set| + set.draw do + resource :account, only: [:show, :destroy], except: :destroy + end + + assert_singleton_resource_allowed_routes("accounts", {}, :show, [:new, :create, :edit, :update, :destroy]) + assert_singleton_resource_allowed_routes("accounts", { format: "xml" }, :show, [:new, :create, :edit, :update, :destroy]) + end + end + def test_resource_has_only_create_action_and_named_route with_routing do |set| set.draw do @@ -1322,7 +1344,7 @@ class ResourcesTest < ActionController::TestCase 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) + options[:controller] = controller shallow_options.merge!(options) assert_whether_allowed(allowed, not_allowed, options, "index", "#{path}#{format}", :get) @@ -1336,7 +1358,7 @@ class ResourcesTest < ActionController::TestCase def assert_singleton_resource_allowed_routes(controller, options, allowed, not_allowed, path = controller.singularize) format = options[:format] && ".#{options[:format]}" - options.merge!(controller: controller) + options[: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) diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index a7033b2d30..b378bb80b8 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -25,8 +25,8 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ]) hex = unsafe.map { |char| "%" + char.unpack1("H2").upcase } - @segment = "#{safe.join}#{unsafe.join}".freeze - @escaped = "#{safe.join}#{hex.join}".freeze + @segment = "#{safe.join}#{unsafe.join}" + @escaped = "#{safe.join}#{hex.join}" end def test_route_generation_escapes_unsafe_path_characters @@ -309,7 +309,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase def test_specific_controller_action_failure rs.draw do - mount lambda {} => "/foo" + mount lambda { } => "/foo" end assert_raises(ActionController::UrlGenerationError) do @@ -355,10 +355,10 @@ class LegacyRouteSetTests < ActiveSupport::TestCase rs.draw { ActiveSupport::Deprecation.silence { get "/:controller/:action", action: /auth[-|_].+/ } } assert_equal({ action: "auth_google", controller: "content" }, rs.recognize_path("/content/auth_google")) - assert_equal({ action: "auth-facebook", controller: "content" }, rs.recognize_path("/content/auth-facebook")) + assert_equal({ action: "auth-twitter", controller: "content" }, rs.recognize_path("/content/auth-twitter")) assert_equal "/content/auth_google", url_for(rs, controller: "content", action: "auth_google") - assert_equal "/content/auth-facebook", url_for(rs, controller: "content", action: "auth-facebook") + assert_equal "/content/auth-twitter", url_for(rs, controller: "content", action: "auth-twitter") end def test_route_with_regexp_for_controller @@ -674,7 +674,7 @@ class LegacyRouteSetTests < ActiveSupport::TestCase 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".dup # 'text' in Russian + token = +"\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian token.force_encoding(Encoding::BINARY) escaped_token = CGI.escape(token) diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 7b1a52b277..c917cdf761 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -144,7 +144,7 @@ class SendFileTest < ActionController::TestCase get :test_send_file_headers_bang assert_equal "image/png", response.content_type - assert_equal 'disposition; filename="filename"', response.get_header("Content-Disposition") + assert_equal %(disposition; filename="filename"; filename*=UTF-8''filename), response.get_header("Content-Disposition") assert_equal "binary", response.get_header("Content-Transfer-Encoding") assert_equal "private", response.get_header("Cache-Control") end @@ -153,7 +153,7 @@ class SendFileTest < ActionController::TestCase def test_send_file_headers_with_disposition_as_a_symbol get :test_send_file_headers_with_disposition_as_a_symbol - assert_equal 'disposition; filename="filename"', response.get_header("Content-Disposition") + assert_equal %(disposition; filename="filename"; filename*=UTF-8''filename), response.get_header("Content-Disposition") end def test_send_file_headers_with_mime_lookup_with_symbol diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 2094aa1aed..8724f9bcdb 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -99,15 +99,16 @@ module ShowExceptions 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) + middleware = @app.instance_variable_get(:@middleware) + @exceptions_app = middleware.instance_variable_get(:@exceptions_app) + middleware.instance_variable_set(:@exceptions_app, nil) $stderr = StringIO.new get "/", headers: { "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) + middleware.instance_variable_set(:@exceptions_app, @exceptions_app) $stderr = STDERR end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index dda2686a9b..998a495d0d 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -156,6 +156,10 @@ XML render html: '<body class="foo"></body>'.html_safe end + def render_json + render json: request.raw_post + end + def boom raise "boom!" end @@ -474,6 +478,18 @@ XML ) end + def test_nil_params + get :test_params, params: nil + parsed_params = JSON.parse(@response.body) + assert_equal( + { + "action" => "test_params", + "controller" => "test_case_test/test" + }, + parsed_params + ) + end + def test_query_param_named_action get :test_query_parameters, params: { action: "foobar" } parsed_params = JSON.parse(@response.body) @@ -542,7 +558,7 @@ XML def test_params_passing_with_frozen_values assert_nothing_raised do get :test_params, params: { - frozen: "icy".freeze, frozens: ["icy".freeze].freeze, deepfreeze: { frozen: "icy".freeze }.freeze + frozen: -"icy", frozens: [-"icy"].freeze, deepfreeze: { frozen: -"icy" }.freeze } end parsed_params = ::JSON.parse(@response.body) @@ -936,7 +952,7 @@ XML get :create assert_response :created - # Redirect url doesn't care that it wasn't a :redirect response. + # Redirect URL doesn't care that it wasn't a :redirect response. assert_equal "/resource", @response.redirect_url assert_equal @response.redirect_url, redirect_to_url @@ -965,6 +981,16 @@ XML assert_equal "q=test2", @response.body end + + def test_parsed_body_without_as_option + post :render_json, body: { foo: "heyo" } + assert_equal({ "foo" => "heyo" }, response.parsed_body) + end + + def test_parsed_body_with_as_option + post :render_json, body: { foo: "heyo" }.to_json, as: :json + assert_equal({ "foo" => "heyo" }, response.parsed_body) + end end class ResponseDefaultHeadersTest < ActionController::TestCase diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index e381abee36..9222250b9c 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -354,6 +354,14 @@ module AbstractController assert_equal({ p2: "Y2" }.to_query, params[1]) end + def test_params_option + url = W.new.url_for(only_path: true, controller: "c", action: "a", params: { domain: "foo", id: "1" }) + params = extract_params(url) + assert_equal("/c/a?domain=foo&id=1", url) + assert_equal({ domain: "foo" }.to_query, params[0]) + assert_equal({ id: "1" }.to_query, params[1]) + 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) diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 4a10637b54..23a46df5cd 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -14,7 +14,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest end def dump_params_keys(hash = params) - hash.keys.sort.inject("") do |s, k| + hash.keys.sort.each_with_object(+"") do |k, s| value = hash[k] if value.is_a?(Hash) || value.is_a?(ActionController::Parameters) @@ -23,8 +23,8 @@ class WebServiceTest < ActionDispatch::IntegrationTest value = "" end - s += ", " unless s.empty? - s += "#{k}#{value}" + s << ", " unless s.empty? + s << "#{k}#{value}" end end end diff --git a/actionpack/test/dispatch/content_disposition_test.rb b/actionpack/test/dispatch/content_disposition_test.rb new file mode 100644 index 0000000000..3f5959da6e --- /dev/null +++ b/actionpack/test/dispatch/content_disposition_test.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "abstract_unit" + +module ActionDispatch + class ContentDispositionTest < ActiveSupport::TestCase + test "encoding a Latin filename" do + disposition = Http::ContentDisposition.new(disposition: :inline, filename: "racecar.jpg") + + assert_equal %(filename="racecar.jpg"), disposition.ascii_filename + assert_equal "filename*=UTF-8''racecar.jpg", disposition.utf8_filename + assert_equal "inline; #{disposition.ascii_filename}; #{disposition.utf8_filename}", disposition.to_s + end + + test "encoding a Latin filename with accented characters" do + disposition = Http::ContentDisposition.new(disposition: :inline, filename: "råcëçâr.jpg") + + assert_equal %(filename="racecar.jpg"), disposition.ascii_filename + assert_equal "filename*=UTF-8''r%C3%A5c%C3%AB%C3%A7%C3%A2r.jpg", disposition.utf8_filename + assert_equal "inline; #{disposition.ascii_filename}; #{disposition.utf8_filename}", disposition.to_s + end + + test "encoding a non-Latin filename" do + disposition = Http::ContentDisposition.new(disposition: :inline, filename: "автомобиль.jpg") + + assert_equal %(filename="%3F%3F%3F%3F%3F%3F%3F%3F%3F%3F.jpg"), disposition.ascii_filename + assert_equal "filename*=UTF-8''%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%BE%D0%B1%D0%B8%D0%BB%D1%8C.jpg", disposition.utf8_filename + assert_equal "inline; #{disposition.ascii_filename}; #{disposition.utf8_filename}", disposition.to_s + end + + test "without filename" do + disposition = Http::ContentDisposition.new(disposition: :inline, filename: nil) + + assert_equal "inline", disposition.to_s + end + end +end diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb index 4f9a4ff2bd..c8c885f35c 100644 --- a/actionpack/test/dispatch/content_security_policy_test.rb +++ b/actionpack/test/dispatch/content_security_policy_test.rb @@ -260,12 +260,13 @@ class DefaultContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationT ROUTES.draw do scope module: "default_content_security_policy_integration_test" do get "/", to: "policy#index" + get "/redirect", to: redirect("/") end end POLICY = ActionDispatch::ContentSecurityPolicy.new do |p| - p.default_src :self - p.script_src :https + p.default_src -> { :self } + p.script_src -> { :https } end class PolicyConfigMiddleware @@ -295,14 +296,19 @@ class DefaultContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationT def test_adds_nonce_to_script_src_content_security_policy_only_once get "/" get "/" + assert_response :success + assert_policy "default-src 'self'; script-src https: 'nonce-iyhD0Yc0W+c='" + end + + def test_redirect_works_with_dynamic_sources + get "/redirect" + assert_response :redirect assert_policy "default-src 'self'; script-src https: 'nonce-iyhD0Yc0W+c='" end private def assert_policy(expected, report_only: false) - assert_response :success - if report_only expected_header = "Content-Security-Policy-Report-Only" unexpected_header = "Content-Security-Policy" @@ -339,6 +345,11 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest p.script_src :self end + content_security_policy only: :style_src do |p| + p.default_src false + p.style_src :self + end + content_security_policy(false, only: :no_policy) content_security_policy_report_only only: :report_only @@ -363,6 +374,10 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest head :ok end + def style_src + head :ok + end + def no_policy head :ok end @@ -381,6 +396,7 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest get "/conditional", to: "policy#conditional" get "/report-only", to: "policy#report_only" get "/script-src", to: "policy#script_src" + get "/style-src", to: "policy#style_src" get "/no-policy", to: "policy#no_policy" end end @@ -441,6 +457,11 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest assert_policy "script-src 'self' 'nonce-iyhD0Yc0W+c='" end + def test_adds_nonce_to_style_src_content_security_policy + get "/style-src" + assert_policy "style-src 'self' 'nonce-iyhD0Yc0W+c='" + end + def test_generates_no_content_security_policy get "/no-policy" diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index aba778fad6..d129fa717d 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -123,6 +123,11 @@ class CookiesTest < ActionController::TestCase head :ok end + def set_cookie_if_not_present + cookies["user_name"] = "alice" unless cookies["user_name"].present? + head :ok + end + def logout cookies.delete("user_name") head :ok @@ -289,6 +294,46 @@ class CookiesTest < ActionController::TestCase cookies[:user_name] = { value: "assain", expires: 2.hours } head :ok end + + def encrypted_discount_and_user_id_cookie + cookies.encrypted[:user_id] = { value: 50, expires: 1.hour } + cookies.encrypted[:discount_percentage] = 10 + + head :ok + end + + def signed_discount_and_user_id_cookie + cookies.signed[:user_id] = { value: 50, expires: 1.hour } + cookies.signed[:discount_percentage] = 10 + + head :ok + end + + def rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_on + # cookies.encrypted[:favorite] = { value: "5-2-Stable Chocolate Cookies", expires: 1000.years } + cookies[:favorite] = "KvH5lIHvX5vPQkLIK63r/NuIMwzWky8M0Zwk8SZ6DwUv8+srf36geR4nWq5KmhsZIYXA8NRdCZYIfxMKJsOFlz77Gf+Fq8vBBCWJTp95rx39A28TCUTJEyMhCNJO5eie7Skef76Qt5Jo/SCnIADAhzyGQkGBopKRcA==--qXZZFWGbCy6N8AGy--WswoH+xHrNh9MzSXDpB2fA==" + + head :ok + end + + def rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off + cookies[:favorite] = "rTG4zs5UufEFAr+ppKwh+MDMymKyAUMOSaWyYa3uUVmD8sMQqyiyQBxgYeAncDHVZIlo4y+kDVSzp66u1/7BNYpnmFe8ES/YT2m8ckNA23jBDmnRZ9CTNfMIRXjFtfxO9YxEOzzhn0ZiA0/zFtr5wkluXtxplOz959Q7MgLOyvTze2h9p8A=--QHOS3rAEGq/HCxXs--xQNra8dk24Idc2qBtpMLpg==" + + head :ok + end + + def rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_on + # cookies.signed[:favorite] = { value: "5-2-Stable Choco Chip Cookie", expires: 1000.years } + cookies[:favorite] = "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaUUxTFRJdFUzUmhZbXhsSUVOb2IyTnZJRU5vYVhBZ1EyOXZhMmxsQmpvR1JWUT0iLCJleHAiOiIzMDE4LTA3LTExVDE2OjExOjI2Ljc1M1oiLCJwdXIiOm51bGx9fQ==--7df5d885b78b70a501d6e82140ae91b24060ac00" + + head :ok + end + + def rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_off + cookies[:favorite] = "BAhJIiE1LTItU3RhYmxlIENob2NvIENoaXAgQ29va2llBjoGRVQ=--50bbdbf8d64f5a3ec3e54878f54d4f55b6cb3aff" + + head :ok + end end tests TestController @@ -296,7 +341,7 @@ class CookiesTest < ActionController::TestCase SECRET_KEY_BASE = "b3c631c314c0bbca50c1b2843150fe33" SIGNED_COOKIE_SALT = "signed cookie" ENCRYPTED_COOKIE_SALT = "encrypted cookie" - ENCRYPTED_SIGNED_COOKIE_SALT = "sigend encrypted cookie" + ENCRYPTED_SIGNED_COOKIE_SALT = "signed encrypted cookie" AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "authenticated encrypted cookie" def setup @@ -485,21 +530,6 @@ class CookiesTest < ActionController::TestCase assert_equal 45, verifier.verify(@response.cookies["user_id"]) end - def test_signed_cookie_with_legacy_secret_scheme - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - old_message = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", digest: "SHA1", serializer: Marshal).generate(45) - - @request.headers["Cookie"] = "user_id=#{old_message}" - get :get_signed_cookie - assert_equal 45, @controller.send(:cookies).signed[:user_id] - - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key("signed cookie") - verifier = ActiveSupport::MessageVerifier.new(secret, digest: "SHA1", serializer: Marshal) - assert_equal 45, verifier.verify(@response.cookies["user_id"]) - end - def test_tampered_with_signed_cookie key_generator = @request.env["action_dispatch.key_generator"] secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) @@ -719,175 +749,7 @@ class CookiesTest < ActionController::TestCase assert_equal ["user_name", "user_id"], @request.cookie_jar.instance_variable_get(:@cookies).keys end - def test_raises_argument_error_if_missing_secret - assert_raise(ArgumentError, nil.inspect) { - @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new(nil) - get :set_signed_cookie - } - - assert_raise(ArgumentError, "".inspect) { - @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("") - get :set_signed_cookie - } - end - - def test_raises_argument_error_if_secret_is_probably_insecure - assert_raise(ArgumentError, "password".inspect) { - @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("password") - get :set_signed_cookie - } - - assert_raise(ArgumentError, "secret".inspect) { - @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("secret") - get :set_signed_cookie - } - - assert_raise(ArgumentError, "12345678901234567890123456789".inspect) { - @request.env["action_dispatch.key_generator"] = ActiveSupport::LegacyKeyGenerator.new("12345678901234567890123456789") - get :set_signed_cookie - } - end - - def test_legacy_signed_cookie_is_read_and_transparently_upgraded_by_signed_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45) - - @request.headers["Cookie"] = "user_id=#{legacy_value}" - get :get_signed_cookie - - assert_equal 45, @controller.send(:cookies).signed[:user_id] - - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) - verifier = ActiveSupport::MessageVerifier.new(secret) - assert_equal 45, verifier.verify(@response.cookies["user_id"]) - end - - def test_legacy_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") - - @request.headers["Cookie"] = "foo=#{legacy_value}" - get :get_encrypted_cookie - - assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - - secret = @request.env["action_dispatch.key_generator"].generate_key(@request.env["action_dispatch.authenticated_encrypted_cookie_salt"], 32) - encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: Marshal) - assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) - end - - def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.cookies_serializer"] = :json - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45) - - @request.headers["Cookie"] = "user_id=#{legacy_value}" - get :get_signed_cookie - - assert_equal 45, @controller.send(:cookies).signed[:user_id] - - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) - verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON) - assert_equal 45, verifier.verify(@response.cookies["user_id"]) - end - - def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_json_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.cookies_serializer"] = :json - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") - - @request.headers["Cookie"] = "foo=#{legacy_value}" - get :get_encrypted_cookie - - assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - - cipher = "aes-256-gcm" - salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] - secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len(cipher)] - encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: cipher, serializer: JSON) - assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) - end - - def test_legacy_json_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.cookies_serializer"] = :hybrid - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate(45) - - @request.headers["Cookie"] = "user_id=#{legacy_value}" - get :get_signed_cookie - - assert_equal 45, @controller.send(:cookies).signed[:user_id] - - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) - verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON) - assert_equal 45, verifier.verify(@response.cookies["user_id"]) - end - - def test_legacy_json_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.cookies_serializer"] = :hybrid - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33", serializer: JSON).generate("bar") - - @request.headers["Cookie"] = "foo=#{legacy_value}" - get :get_encrypted_cookie - - assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - - salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] - secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")] - encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON) - assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) - end - - def test_legacy_marshal_signed_cookie_is_read_and_transparently_upgraded_by_signed_json_hybrid_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.cookies_serializer"] = :hybrid - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate(45) - - @request.headers["Cookie"] = "user_id=#{legacy_value}" - get :get_signed_cookie - - assert_equal 45, @controller.send(:cookies).signed[:user_id] - - key_generator = @request.env["action_dispatch.key_generator"] - secret = key_generator.generate_key(@request.env["action_dispatch.signed_cookie_salt"]) - verifier = ActiveSupport::MessageVerifier.new(secret, serializer: JSON) - assert_equal 45, verifier.verify(@response.cookies["user_id"]) - end - - def test_legacy_marshal_signed_cookie_is_read_and_transparently_encrypted_by_encrypted_hybrid_cookie_jar_if_both_secret_token_and_secret_key_base_are_set - @request.env["action_dispatch.cookies_serializer"] = :hybrid - - @request.env["action_dispatch.use_authenticated_cookie_encryption"] = true - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.env["action_dispatch.secret_key_base"] = "c3b95688f35581fad38df788add315ff" - - legacy_value = ActiveSupport::MessageVerifier.new("b3c631c314c0bbca50c1b2843150fe33").generate("bar") - - @request.headers["Cookie"] = "foo=#{legacy_value}" - get :get_encrypted_cookie - - assert_equal "bar", @controller.send(:cookies).encrypted[:foo] - - salt = @request.env["action_dispatch.authenticated_encrypted_cookie_salt"] - secret = @request.env["action_dispatch.key_generator"].generate_key(salt)[0, ActiveSupport::MessageEncryptor.key_len("aes-256-gcm")] - encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm", serializer: JSON) - assert_equal "bar", encryptor.decrypt_and_verify(@response.cookies["foo"]) - end - def test_legacy_signed_cookie_is_treated_as_nil_by_signed_cookie_jar_if_tampered - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.headers["Cookie"] = "user_id=45" get :get_signed_cookie @@ -896,8 +758,6 @@ class CookiesTest < ActionController::TestCase end def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered - @request.env["action_dispatch.secret_token"] = "b3c631c314c0bbca50c1b2843150fe33" - @request.headers["Cookie"] = "foo=baz" get :get_encrypted_cookie @@ -1273,7 +1133,17 @@ class CookiesTest < ActionController::TestCase assert_equal "bar", @controller.encrypted_cookie end + def test_cookie_override + get :set_cookie_if_not_present + assert_equal "alice", cookies["user_name"] + cookies["user_name"] = "bob" + get :set_cookie_if_not_present + assert_equal "bob", cookies["user_name"] + end + def test_signed_cookie_with_expires_set_relatively + request.env["action_dispatch.use_cookies_with_metadata"] = true + cookies.signed[:user_name] = { value: "assain", expires: 2.hours } travel 1.hour @@ -1284,6 +1154,8 @@ class CookiesTest < ActionController::TestCase end def test_encrypted_cookie_with_expires_set_relatively + request.env["action_dispatch.use_cookies_with_metadata"] = true + cookies.encrypted[:user_name] = { value: "assain", expires: 2.hours } travel 1.hour @@ -1300,6 +1172,117 @@ class CookiesTest < ActionController::TestCase end end + def test_purpose_metadata_for_encrypted_cookies + get :encrypted_discount_and_user_id_cookie + + cookies[:discount_percentage] = cookies[:user_id] + assert_equal 50, cookies.encrypted[:discount_percentage] + + request.env["action_dispatch.use_cookies_with_metadata"] = true + + get :encrypted_discount_and_user_id_cookie + + cookies[:discount_percentage] = cookies[:user_id] + assert_nil cookies.encrypted[:discount_percentage] + end + + def test_purpose_metadata_for_signed_cookies + get :signed_discount_and_user_id_cookie + + cookies[:discount_percentage] = cookies[:user_id] + assert_equal 50, cookies.signed[:discount_percentage] + + request.env["action_dispatch.use_cookies_with_metadata"] = true + + get :signed_discount_and_user_id_cookie + + cookies[:discount_percentage] = cookies[:user_id] + assert_nil cookies.signed[:discount_percentage] + end + + def test_switch_off_metadata_for_encrypted_cookies_if_config_is_false + request.env["action_dispatch.use_cookies_with_metadata"] = false + + get :encrypted_discount_and_user_id_cookie + + travel 2.hours + assert_nil cookies.signed[:user_id] + end + + def test_switch_off_metadata_for_signed_cookies_if_config_is_false + request.env["action_dispatch.use_cookies_with_metadata"] = false + + get :signed_discount_and_user_id_cookie + + travel 2.hours + + assert_nil cookies.signed[:user_id] + end + + def test_read_rails_5_2_stable_encrypted_cookies_if_config_is_false + request.env["action_dispatch.use_cookies_with_metadata"] = false + + get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_on + + assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] + + travel 1001.years do + assert_nil cookies.encrypted[:favorite] + end + + get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off + + assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] + end + + def test_read_rails_5_2_stable_signed_cookies_if_config_is_false + request.env["action_dispatch.use_cookies_with_metadata"] = false + + get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_on + + assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] + + travel 1001.years do + assert_nil cookies.signed[:favorite] + end + + get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_off + + assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] + end + + def test_read_rails_5_2_stable_encrypted_cookies_if_use_metadata_config_is_true + request.env["action_dispatch.use_cookies_with_metadata"] = true + + get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_on + + assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] + + travel 1001.years do + assert_nil cookies.encrypted[:favorite] + end + + get :rails_5_2_stable_encrypted_cookie_with_authenticated_encryption_flag_off + + assert_equal "5-2-Stable Chocolate Cookies", cookies.encrypted[:favorite] + end + + def test_read_rails_5_2_stable_signed_cookies_if_use_metadata_config_is_true + request.env["action_dispatch.use_cookies_with_metadata"] = true + + get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_on + + assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] + + travel 1001.years do + assert_nil cookies.signed[:favorite] + end + + get :rails_5_2_stable_signed_cookie_with_authenticated_encryption_flag_off + + assert_equal "5-2-Stable Choco Chip Cookie", cookies.signed[:favorite] + end + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 44b79c0e5d..2812b1b614 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -8,7 +8,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest class Boomer attr_accessor :closed - def initialize(detailed = false) + def initialize(detailed = false) @detailed = detailed @closed = false end @@ -27,66 +27,70 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end def raise_nested_exceptions + raise "First error" + rescue begin - raise "First error" + raise "Second error" rescue - begin - raise "Second error" - rescue - raise "Third error" - end + raise "Third error" end end def call(env) env["action_dispatch.show_detailed_exceptions"] = @detailed req = ActionDispatch::Request.new(env) + template = ActionView::Template.new(File.read(__FILE__), __FILE__, ActionView::Template::Handlers::Raw.new, format: :html, locals: []) + case req.path - when %r{/pass} + when "/pass" [404, { "X-Cascade" => "pass" }, self] - when %r{/not_found} + when "/not_found" raise AbstractController::ActionNotFound - when %r{/runtime_error} + when "/runtime_error" raise RuntimeError - when %r{/method_not_allowed} + when "/method_not_allowed" raise ActionController::MethodNotAllowed - when %r{/intercepted_error} + when "/intercepted_error" raise InterceptedErrorInstance - when %r{/unknown_http_method} + when "/unknown_http_method" raise ActionController::UnknownHttpMethod - when %r{/not_implemented} + when "/not_implemented" raise ActionController::NotImplemented - when %r{/unprocessable_entity} + when "/unprocessable_entity" raise ActionController::InvalidAuthenticityToken - when %r{/not_found_original_exception} + when "/invalid_mimetype" + raise Mime::Type::InvalidMimeType + when "/not_found_original_exception" begin raise AbstractController::ActionNotFound.new rescue - raise ActionView::Template::Error.new("template") + raise ActionView::Template::Error.new(template) end - when %r{/missing_template} + when "/cause_mapped_to_rescue_responses" + begin + raise ActionController::ParameterMissing, :missing_param_key + rescue + raise NameError.new("uninitialized constant Userr") + end + when "/missing_template" raise ActionView::MissingTemplate.new(%w(foo), "foo/index", %w(foo), false, "mailer") - when %r{/bad_request} + when "/bad_request" raise ActionController::BadRequest - when %r{/missing_keys} + when "/missing_keys" raise ActionController::UrlGenerationError, "No route matches" - when %r{/parameter_missing} + when "/parameter_missing" raise ActionController::ParameterMissing, :missing_param_key - when %r{/original_syntax_error} + when "/original_syntax_error" eval "broke_syntax =" # `eval` need for raise native SyntaxError at runtime - when %r{/syntax_error_into_view} + when "/syntax_error_into_view" begin eval "broke_syntax =" rescue Exception - template = ActionView::Template.new(File.read(__FILE__), - __FILE__, - ActionView::Template::Handlers::Raw.new, - {}) raise ActionView::Template::Error.new(template) end - when %r{/framework_raises} + when "/framework_raises" method_that_raises - when %r{/nested_exceptions} + when "/nested_exceptions" raise_nested_exceptions else raise "puke!" @@ -176,6 +180,10 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true } assert_response 400 assert_match(/ActionController::ParameterMissing/, body) + + get "/invalid_mimetype", headers: { "Accept" => "text/html,*", "action_dispatch.show_exceptions" => true } + assert_response 406 + assert_match(/Mime::Type::InvalidMimeType/, body) end test "rescue with text error for xhr request" do @@ -290,22 +298,20 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end test "rescue with JSON format as fallback if API request format is not supported" do - begin - Mime::Type.register "text/wibble", :wibble + Mime::Type.register "text/wibble", :wibble - ActionDispatch::IntegrationTest.register_encoder(:wibble, - param_encoder: -> params { params }) + ActionDispatch::IntegrationTest.register_encoder(:wibble, + param_encoder: -> params { params }) - @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) + @app = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp, :api) - get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble - assert_response 500 - assert_equal "application/json", response.content_type - assert_match(/RuntimeError: puke/, body) + get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble + assert_response 500 + assert_equal "application/json", response.content_type + assert_match(/RuntimeError: puke/, body) - ensure - Mime::Type.unregister :wibble - end + ensure + Mime::Type.unregister :wibble end test "does not show filtered parameters" do @@ -317,15 +323,25 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_match(""foo"=>"[FILTERED]"", body) end - test "show registered original exception for wrapped exceptions" do + test "show registered original exception if the last exception is TemplateError" do @app = DevelopmentApp get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } assert_response 404 - assert_match(/AbstractController::ActionNotFound/, body) + assert_match %r{AbstractController::ActionNotFound}, body + assert_match %r{Showing <i>.*test/dispatch/debug_exceptions_test.rb</i>}, body + end + + test "show the last exception and cause even when the cause is mapped to resque_responses" do + @app = DevelopmentApp + + get "/cause_mapped_to_rescue_responses", headers: { "action_dispatch.show_exceptions" => true } + assert_response 500 + assert_match %r{ActionController::ParameterMissing}, body + assert_match %r{NameError}, body end - test "named urls missing keys raise 500 level error" do + test "named URLs missing keys raise 500 level error" do @app = DevelopmentApp get "/missing_keys", headers: { "action_dispatch.show_exceptions" => true } @@ -368,7 +384,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest }) assert_response 500 - assert_includes(body, CGI.escapeHTML(PP.pp(params, "".dup, 200))) + assert_includes(body, CGI.escapeHTML(PP.pp(params, +"", 200))) end test "sets the HTTP charset parameter" do @@ -484,6 +500,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_select "#Application-Trace-0" do assert_select "code", /syntax error, unexpected/ end + assert_match %r{Showing <i>.*test/dispatch/debug_exceptions_test.rb</i>}, body end test "debug exceptions app shows user code that caused the error in source view" do diff --git a/actionpack/test/dispatch/exception_wrapper_test.rb b/actionpack/test/dispatch/exception_wrapper_test.rb index 600280d6b3..668469a01d 100644 --- a/actionpack/test/dispatch/exception_wrapper_test.rb +++ b/actionpack/test/dispatch/exception_wrapper_test.rb @@ -20,6 +20,7 @@ module ActionDispatch setup do @cleaner = ActiveSupport::BacktraceCleaner.new + @cleaner.remove_filters! @cleaner.add_silencer { |line| line !~ /^lib/ } end diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb index 3a265a056b..bd2a5b35fb 100644 --- a/actionpack/test/dispatch/header_test.rb +++ b/actionpack/test/dispatch/header_test.rb @@ -156,7 +156,7 @@ class HeaderTest < ActiveSupport::TestCase env = { "HTTP_REFERER" => "/" } headers = make_headers(env) headers["Referer"] = "http://example.com/" - headers.merge! "CONTENT_TYPE" => "text/plain" + headers["CONTENT_TYPE"] = "text/plain" assert_equal({ "HTTP_REFERER" => "http://example.com/", "CONTENT_TYPE" => "text/plain" }, env) end diff --git a/actionpack/test/dispatch/host_authorization_test.rb b/actionpack/test/dispatch/host_authorization_test.rb new file mode 100644 index 0000000000..5263dd2597 --- /dev/null +++ b/actionpack/test/dispatch/host_authorization_test.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require "abstract_unit" +require "ipaddr" + +class HostAuthorizationTest < ActionDispatch::IntegrationTest + App = -> env { [200, {}, %w(Success)] } + + test "blocks requests to unallowed host" do + @app = ActionDispatch::HostAuthorization.new(App, %w(only.com)) + + get "/" + + assert_response :forbidden + assert_match "Blocked host: www.example.com", response.body + end + + test "allows all requests if hosts is empty" do + @app = ActionDispatch::HostAuthorization.new(App, nil) + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "hosts can be a single element array" do + @app = ActionDispatch::HostAuthorization.new(App, %w(www.example.com)) + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "hosts can be a string" do + @app = ActionDispatch::HostAuthorization.new(App, "www.example.com") + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "passes requests to allowed hosts with domain name notation" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "does not allow domain name notation in the HOST header itself" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/", env: { + "HOST" => ".example.com", + } + + assert_response :forbidden + assert_match "Blocked host: .example.com", response.body + end + + test "checks for requests with #=== to support wider range of host checks" do + @app = ActionDispatch::HostAuthorization.new(App, [-> input { input == "www.example.com" }]) + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "mark the host when authorized" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/" + + assert_equal "www.example.com", request.get_header("action_dispatch.authorized_host") + end + + test "sanitizes regular expressions to prevent accidental matches" do + @app = ActionDispatch::HostAuthorization.new(App, [/w.example.co/]) + + get "/" + + assert_response :forbidden + assert_match "Blocked host: www.example.com", response.body + end + + test "blocks requests to unallowed host supporting custom responses" do + @app = ActionDispatch::HostAuthorization.new(App, ["w.example.co"], -> env do + [401, {}, %w(Custom)] + end) + + get "/" + + assert_response :unauthorized + assert_equal "Custom", body + end + + test "blocks requests with spoofed X-FORWARDED-HOST" do + @app = ActionDispatch::HostAuthorization.new(App, [IPAddr.new("127.0.0.1")]) + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "127.0.0.1", + "HOST" => "www.example.com", + } + + assert_response :forbidden + assert_match "Blocked host: 127.0.0.1", response.body + end + + test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do + @app = ActionDispatch::HostAuthorization.new(App, nil) + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "127.0.0.1", + "HOST" => "www.example.com", + } + + assert_response :ok + assert_equal "Success", body + end + + test "detects localhost domain spoofing" do + @app = ActionDispatch::HostAuthorization.new(App, "localhost") + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "localhost", + "HOST" => "www.example.com", + } + + assert_response :forbidden + assert_match "Blocked host: localhost", response.body + end + + test "forwarded hosts should be permitted" do + @app = ActionDispatch::HostAuthorization.new(App, "domain.com") + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "sub.domain.com", + "HOST" => "domain.com", + } + + assert_response :forbidden + assert_match "Blocked host: sub.domain.com", response.body + end + + test "forwarded hosts are allowed when permitted" do + @app = ActionDispatch::HostAuthorization.new(App, ".domain.com") + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "sub.domain.com", + "HOST" => "domain.com", + } + + assert_response :ok + assert_equal "Success", body + end +end diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb index a9a56f205f..f2459112b2 100644 --- a/actionpack/test/dispatch/live_response_test.rb +++ b/actionpack/test/dispatch/live_response_test.rb @@ -51,18 +51,24 @@ module ActionController assert_equal ["omg"], @response.body_parts end - def test_cache_control_is_set + def test_cache_control_is_set_by_default @response.stream.write "omg" assert_equal "no-cache", @response.headers["Cache-Control"] end + def test_cache_control_is_set_manually + @response.set_header("Cache-Control", "public") + @response.stream.write "omg" + assert_equal "public", @response.headers["Cache-Control"] + end + def test_content_length_is_removed @response.headers["Content-Length"] = "1234" @response.stream.write "omg" assert_nil @response.headers["Content-Length"] end - def test_headers_cannot_be_written_after_webserver_reads + def test_headers_cannot_be_written_after_web_server_reads @response.stream.write "omg" latch = Concurrent::CountDownLatch.new diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb index e9f7ad41dd..90f2eccd19 100644 --- a/actionpack/test/dispatch/middleware_stack_test.rb +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -3,13 +3,24 @@ require "abstract_unit" class MiddlewareStackTest < ActiveSupport::TestCase - class FooMiddleware; end - class BarMiddleware; end - class BazMiddleware; end - class HiyaMiddleware; end - class BlockMiddleware + class Base + def initialize(app) + @app = app + end + + def call(env) + @app.call(env) + end + end + + class FooMiddleware < Base; end + class BarMiddleware < Base; end + class BazMiddleware < Base; end + class HiyaMiddleware < Base; end + class BlockMiddleware < Base attr_reader :block - def initialize(&block) + def initialize(app, &block) + super(app) @block = block end end @@ -42,7 +53,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase end test "use should push middleware class with block arguments onto the stack" do - proc = Proc.new {} + proc = Proc.new { } assert_difference "@stack.size" do @stack.use(BlockMiddleware, &proc) end @@ -109,6 +120,24 @@ class MiddlewareStackTest < ActiveSupport::TestCase assert_equal @stack.last, @stack.last end + test "instruments the execution of middlewares" do + app = @stack.build(proc { |env| [200, {}, []] }) + env = {} + + events = [] + + subscriber = proc do |*args| + events << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.subscribed(subscriber, "process_middleware.action_dispatch") do + app.call(env) + end + + assert_equal 2, events.count + assert_equal ["MiddlewareStackTest::BarMiddleware", "MiddlewareStackTest::FooMiddleware"], events.map { |e| e.payload[:middleware] } + end + test "includes a middleware" do assert_equal true, @stack.include?(ActionDispatch::MiddlewareStack::Middleware.new(BarMiddleware, nil, nil)) end diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index fa264417e1..50f6c06fee 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -96,57 +96,47 @@ class MimeTypeTest < ActiveSupport::TestCase end test "custom type" do - begin - type = Mime::Type.register("image/foo", :foo) - assert_equal type, Mime[:foo] - ensure - Mime::Type.unregister(:foo) - end + type = Mime::Type.register("image/foo", :foo) + assert_equal type, Mime[:foo] + ensure + Mime::Type.unregister(:foo) end test "custom type with type aliases" do - begin - Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"] - %w[text/foobar text/foo text/bar].each do |type| - assert_equal Mime[:foobar], type - end - ensure - Mime::Type.unregister(:foobar) + Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"] + %w[text/foobar text/foo text/bar].each do |type| + assert_equal Mime[:foobar], type end + ensure + Mime::Type.unregister(:foobar) end test "register callbacks" do - begin - registered_mimes = [] - Mime::Type.register_callback do |mime| - registered_mimes << mime - end - - mime = Mime::Type.register("text/foo", :foo) - assert_equal [mime], registered_mimes - ensure - Mime::Type.unregister(:foo) + registered_mimes = [] + Mime::Type.register_callback do |mime| + registered_mimes << mime end + + mime = Mime::Type.register("text/foo", :foo) + assert_equal [mime], registered_mimes + ensure + Mime::Type.unregister(:foo) end test "custom type with extension aliases" do - begin - Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"] - %w[foobar foo bar].each do |extension| - assert_equal Mime[:foobar], Mime::EXTENSION_LOOKUP[extension] - end - ensure - Mime::Type.unregister(:foobar) + Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"] + %w[foobar foo bar].each do |extension| + assert_equal Mime[:foobar], Mime::EXTENSION_LOOKUP[extension] end + ensure + Mime::Type.unregister(:foobar) end test "register alias" do - begin - Mime::Type.register_alias "application/xhtml+xml", :foobar - assert_equal Mime[:html], Mime::EXTENSION_LOOKUP["foobar"] - ensure - Mime::Type.unregister(:foobar) - end + Mime::Type.register_alias "application/xhtml+xml", :foobar + assert_equal Mime[:html], Mime::EXTENSION_LOOKUP["foobar"] + ensure + Mime::Type.unregister(:foobar) end test "type should be equal to symbol" do @@ -184,4 +174,51 @@ class MimeTypeTest < ActiveSupport::TestCase assert_not (Mime[:js] !~ "application/javascript") assert Mime[:html] =~ "application/xhtml+xml" end + + test "can be initialized with wildcards" do + assert_equal "*/*", Mime::Type.new("*/*").to_s + assert_equal "text/*", Mime::Type.new("text/*").to_s + assert_equal "video/*", Mime::Type.new("video/*").to_s + end + + test "can be initialized with parameters" do + assert_equal "text/html; parameter", Mime::Type.new("text/html; parameter").to_s + assert_equal "text/html; parameter=abc", Mime::Type.new("text/html; parameter=abc").to_s + assert_equal 'text/html; parameter="abc"', Mime::Type.new('text/html; parameter="abc"').to_s + assert_equal 'text/html; parameter=abc; parameter2="xyz"', Mime::Type.new('text/html; parameter=abc; parameter2="xyz"').to_s + end + + test "invalid mime types raise error" do + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new("too/many/slash") + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new("missingslash") + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new("improper/semicolon;") + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new('improper/semicolon; parameter=abc; parameter2="xyz";') + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new("text/html, text/plain") + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new("*/html") + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new("") + end + + assert_raises Mime::Type::InvalidMimeType do + Mime::Type.new(nil) + end + end end diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb index f6cf653980..e42ea89f6f 100644 --- a/actionpack/test/dispatch/mount_test.rb +++ b/actionpack/test/dispatch/mount_test.rb @@ -80,6 +80,12 @@ class TestRoutingMount < ActionDispatch::IntegrationTest assert_equal "/shorthand -- /omg", response.body end + def test_mounting_does_not_match_similar_paths + get "/shorthandomg" + assert_not_equal "/shorthand -- /omg", response.body + assert_equal " -- /shorthandomg", response.body + end + def test_mounting_works_with_via get "/getfake" assert_equal "OK", response.body diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 85ea04356a..63c147cb1b 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -13,7 +13,7 @@ module TestGenerationPrefix end def self.model_name - klass = "Post".dup + klass = +"Post" def klass.name; self end ActiveModel::Name.new(klass) @@ -151,17 +151,17 @@ module TestGenerationPrefix include BlogEngine.routes.mounted_helpers # Inside Engine - test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do + test "[ENGINE] generating engine's URL use SCRIPT_NAME from request" do get "/pure-awesomeness/blog/posts/1" assert_equal "/pure-awesomeness/blog/posts/1", response.body end - test "[ENGINE] generating application's url never uses SCRIPT_NAME from request" do + test "[ENGINE] generating application's URL never uses SCRIPT_NAME from request" do get "/pure-awesomeness/blog/url_to_application" assert_equal "/generate", response.body end - test "[ENGINE] generating engine's url with polymorphic path" do + test "[ENGINE] generating engine's URL with polymorphic path" do get "/pure-awesomeness/blog/polymorphic_path_for_engine" assert_equal "/pure-awesomeness/blog/posts/1", response.body end @@ -243,7 +243,7 @@ module TestGenerationPrefix assert_equal "/something/awesome/blog/posts/1", response.body end - test "[APP] generating engine's url with polymorphic path" do + test "[APP] generating engine's URL with polymorphic path" do get "/polymorphic_path_for_engine" assert_equal "/awesome/blog/posts/1", response.body end @@ -253,7 +253,7 @@ module TestGenerationPrefix assert_equal "/posts/1", response.body end - test "[APP] generating engine's url with url_for(@post)" do + test "[APP] generating engine's URL with url_for(@post)" do get "/polymorphic_with_url_for" assert_equal "http://www.example.com/awesome/blog/posts/1", response.body end diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index beab8e78b5..2a48a12497 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -74,17 +74,15 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest test "occurring a parse error if parsing unsuccessful" do with_test_routing do - begin - $stderr = StringIO.new # suppress the log - json = "[\"person]\": {\"name\": \"David\"}}" - exception = assert_raise(ActionDispatch::Http::Parameters::ParseError) do - post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false } - end - assert_equal JSON::ParserError, exception.cause.class - assert_equal exception.cause.message, exception.message - ensure - $stderr = STDERR + $stderr = StringIO.new # suppress the log + json = "[\"person]\": {\"name\": \"David\"}}" + exception = assert_raise(ActionDispatch::Http::Parameters::ParseError) do + post "/parse", params: json, headers: { "CONTENT_TYPE" => "application/json", "action_dispatch.show_exceptions" => false } end + assert_equal JSON::ParserError, exception.cause.class + assert_equal exception.cause.message, exception.message + ensure + $stderr = STDERR end end @@ -157,31 +155,27 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest end test "parses json params after custom json mime type registered" do - begin - Mime::Type.unregister :json - Mime::Type.register "application/json", :json, %w(application/vnd.rails+json) - assert_parses( - { "user" => { "username" => "meinac" }, "username" => "meinac" }, - "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/json" - ) - ensure - Mime::Type.unregister :json - Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) - end + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w(application/vnd.rails+json) + assert_parses( + { "user" => { "username" => "meinac" }, "username" => "meinac" }, + "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/json" + ) + ensure + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) end test "parses json params after custom json mime type registered with synonym" do - begin - Mime::Type.unregister :json - Mime::Type.register "application/json", :json, %w(application/vnd.rails+json) - assert_parses( - { "user" => { "username" => "meinac" }, "username" => "meinac" }, - "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/vnd.rails+json" - ) - ensure - Mime::Type.unregister :json - Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) - end + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w(application/vnd.rails+json) + assert_parses( + { "user" => { "username" => "meinac" }, "username" => "meinac" }, + "{\"username\": \"meinac\"}", "CONTENT_TYPE" => "application/vnd.rails+json" + ) + ensure + Mime::Type.unregister :json + Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) end private diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 84a2d1f69e..eb49396145 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -24,7 +24,7 @@ class BaseRequestTest < ActiveSupport::TestCase def stub_request(env = {}) ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true @trusted_proxies ||= nil - ip_app = ActionDispatch::RemoteIp.new(Proc.new {}, ip_spoofing_check, @trusted_proxies) + ip_app = ActionDispatch::RemoteIp.new(Proc.new { }, ip_spoofing_check, @trusted_proxies) ActionDispatch::Http::URL.tld_length = env.delete(:tld_length) if env.key?(:tld_length) ip_app.call(env) @@ -411,7 +411,7 @@ class RequestPath < BaseRequestTest assert_equal "/foo?bar", path end - test "original_url returns url built using ORIGINAL_FULLPATH" do + test "original_url returns URL built using ORIGINAL_FULLPATH" do request = stub_request("ORIGINAL_FULLPATH" => "/foo?bar", "HTTP_HOST" => "example.org", "rack.url_scheme" => "http") @@ -763,7 +763,6 @@ class RequestMethod < BaseRequestTest test "post uneffected by local inflections" do existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup - assert_deprecated { ActiveSupport::Inflector.inflections.acronym_regex.dup } begin ActiveSupport::Inflector.inflections do |inflect| inflect.acronym "POS" @@ -1059,44 +1058,9 @@ class RequestParameters < BaseRequestTest end class RequestParameterFilter < BaseRequestTest - test "process parameter filter" do - test_hashes = [ - [{ "foo" => "bar" }, { "foo" => "bar" }, %w'food'], - [{ "foo" => "bar" }, { "foo" => "[FILTERED]" }, %w'foo'], - [{ "foo" => "bar", "bar" => "foo" }, { "foo" => "[FILTERED]", "bar" => "foo" }, %w'foo baz'], - [{ "foo" => "bar", "baz" => "foo" }, { "foo" => "[FILTERED]", "baz" => "[FILTERED]" }, %w'foo baz'], - [{ "bar" => { "foo" => "bar", "bar" => "foo" } }, { "bar" => { "foo" => "[FILTERED]", "bar" => "foo" } }, %w'fo'], - [{ "foo" => { "foo" => "bar", "bar" => "foo" } }, { "foo" => "[FILTERED]" }, %w'f banana'], - [{ "deep" => { "cc" => { "code" => "bar", "bar" => "foo" }, "ss" => { "code" => "bar" } } }, { "deep" => { "cc" => { "code" => "[FILTERED]", "bar" => "foo" }, "ss" => { "code" => "bar" } } }, %w'deep.cc.code'], - [{ "baz" => [{ "foo" => "baz" }, "1"] }, { "baz" => [{ "foo" => "[FILTERED]" }, "1"] }, [/foo/]]] - - test_hashes.each do |before_filter, after_filter, filter_words| - parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) - assert_equal after_filter, parameter_filter.filter(before_filter) - - filter_words << "blah" - filter_words << lambda { |key, value| - value.reverse! if key =~ /bargain/ - } - - parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) - before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo" } } } - after_filter["barg"] = { :bargain => "niag", "blah" => "[FILTERED]", "bar" => { "bargain" => { "blah" => "[FILTERED]" } } } - - assert_equal after_filter, parameter_filter.filter(before_filter) - end - end - - test "parameter filter should maintain hash with indifferent access" do - test_hashes = [ - [{ "foo" => "bar" }.with_indifferent_access, ["blah"]], - [{ "foo" => "bar" }.with_indifferent_access, []] - ] - - test_hashes.each do |before_filter, filter_words| - parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words) - assert_instance_of ActiveSupport::HashWithIndifferentAccess, - parameter_filter.filter(before_filter) + test "parameter filter is deprecated" do + assert_deprecated do + ActionDispatch::Http::ParameterFilter.new(["blah"]) end end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 0f37d074af..7758b0406a 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -42,7 +42,7 @@ class ResponseTest < ActiveSupport::TestCase def test_each_isnt_called_if_str_body_is_written # Controller writes and reads response body each_counter = 0 - @response.body = Object.new.tap { |o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call "foo" } } + @response.body = Object.new.tap { |o| o.singleton_class.define_method(:each) { |&block| each_counter += 1; block.call "foo" } } @response["X-Foo"] = @response.body assert_equal 1, each_counter, "#each was not called once" @@ -539,4 +539,38 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers["ETag"]) assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag) end + + test "response Content-Type with optional parameters" do + @app = lambda { |env| + [ + 200, + { "Content-Type" => "text/csv; charset=utf-16; header=present" }, + ["Hello"] + ] + } + + get "/" + assert_response :success + + assert_equal("text/csv; charset=utf-16; header=present", @response.headers["Content-Type"]) + assert_equal("text/csv", @response.content_type) + assert_equal("utf-16", @response.charset) + end + + test "response Content-Type with quoted-string" do + @app = lambda { |env| + [ + 200, + { "Content-Type" => 'text/csv; header=present; charset="utf-16"' }, + ["Hello"] + ] + } + + get "/" + assert_response :success + + assert_equal('text/csv; header=present; charset="utf-16"', @response.headers["Content-Type"]) + assert_equal("text/csv", @response.content_type) + assert_equal("utf-16", @response.charset) + end end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 9150d5010b..fe1f1995d8 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -368,19 +368,19 @@ module ActionDispatch assert_equal [ "No routes were found for this grep pattern.", - "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html." ], output end def test_not_routes_when_expanded - output = draw(grep: "rails/dummy", formatter: ActionDispatch::Routing::ConsoleFormatter::Expanded.new) {} + output = draw(grep: "rails/dummy", formatter: ActionDispatch::Routing::ConsoleFormatter::Expanded.new) { } assert_equal [ "You don't have any routes defined!", "", "Please add some routes in config/routes.rb.", "", - "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html." ], output end @@ -434,7 +434,7 @@ module ActionDispatch assert_equal [ "No routes were found for this controller.", - "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html." ], output end @@ -445,19 +445,19 @@ module ActionDispatch assert_equal [ "No routes were found for this grep pattern.", - "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html." ], output end def test_no_routes_were_defined - output = draw(grep: "Rails::DummyController") {} + output = draw(grep: "Rails::DummyController") { } assert_equal [ "You don't have any routes defined!", "", "Please add some routes in config/routes.rb.", "", - "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html." ], output end diff --git a/actionpack/test/dispatch/routing/non_dispatch_routed_app_test.rb b/actionpack/test/dispatch/routing/non_dispatch_routed_app_test.rb new file mode 100644 index 0000000000..676a8c38d4 --- /dev/null +++ b/actionpack/test/dispatch/routing/non_dispatch_routed_app_test.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "abstract_unit" + +module ActionDispatch + module Routing + class NonDispatchRoutedAppTest < ActionDispatch::IntegrationTest + # For example, Grape::API + class SimpleApp + def self.call(env) + [ 200, { "Content-Type" => "text/plain" }, [] ] + end + + def self.routes + [] + end + end + + setup { @app = SimpleApp } + + test "does not except" do + get "/foo" + assert_response :success + end + end + end +end diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index e61d47b160..e6a2c35798 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -29,7 +29,7 @@ module ActionDispatch assert_not empty? end - test "url helpers are added when route is added" do + test "URL helpers are added when route is added" do draw do get "foo", to: SimpleApp.new("foo#index") end @@ -48,7 +48,7 @@ module ActionDispatch assert_equal "/bar", url_helpers.bar_path end - test "url helpers are updated when route is updated" do + test "URL helpers are updated when route is updated" do draw do get "bar", to: SimpleApp.new("bar#index"), as: :bar end @@ -62,7 +62,7 @@ module ActionDispatch assert_equal "/baz", url_helpers.bar_path end - test "url helpers are removed when route is removed" do + test "URL helpers are removed when route is removed" do draw do get "foo", to: SimpleApp.new("foo#index") get "bar", to: SimpleApp.new("bar#index") diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5efbe5b553..362488d585 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -115,6 +115,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 301, status end + def test_accepts_a_constraint_object_responding_to_call + constraint = Class.new do + def call(*); true; end + def matches?(*); false; end + end + + draw do + get "/", to: "home#show", constraints: constraint.new + end + + assert_nothing_raised do + get "/" + end + end + def test_namespace_with_controller_segment assert_raise(ArgumentError) do draw do @@ -1367,6 +1382,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "projects#index", @response.body end + def test_optionally_scoped_root_unscoped_access + draw do + scope "(:locale)" do + scope "(:platform)" do + scope "(:browser)" do + root to: "projects#index" + end + end + end + end + + assert_equal "/", root_path + get "/" + assert_equal "projects#index", @response.body + end + def test_scope_with_format_option draw do get "direct/index", as: :no_format_direct, format: false @@ -2169,6 +2200,37 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "cards#destroy", @response.body end + def test_shallow_false_inside_nested_shallow_resource + draw do + resources :blogs, shallow: true do + resources :posts do + resources :comments, shallow: false + resources :tags + end + end + end + + get "/posts/1/comments" + assert_equal "comments#index", @response.body + assert_equal "/posts/1/comments", post_comments_path("1") + + get "/posts/1/comments/new" + assert_equal "comments#new", @response.body + assert_equal "/posts/1/comments/new", new_post_comment_path("1") + + get "/posts/1/comments/2" + assert_equal "comments#show", @response.body + assert_equal "/posts/1/comments/2", post_comment_path("1", "2") + + get "/posts/1/comments/2/edit" + assert_equal "comments#edit", @response.body + assert_equal "/posts/1/comments/2/edit", edit_post_comment_path("1", "2") + + get "/tags/3" + assert_equal "tags#show", @response.body + assert_equal "/tags/3", tag_path("3") + end + def test_shallow_deeply_nested_resources draw do resources :blogs do @@ -3307,13 +3369,23 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "0c0c0b68-d24b-11e1-a861-001ff3fffe6f", @request.params[:download] end - def test_action_from_path_is_not_frozen + def test_colon_containing_custom_param + ex = assert_raises(ArgumentError) { + draw do + resources :profiles, param: "username/:is_admin" + end + } + + assert_match(/:param option can't contain colon/, ex.message) + end + + def test_action_from_path_is_frozen draw do get "search" => "search" end get "/search" - assert_not_predicate @request.params[:action], :frozen? + assert_predicate @request.params[:action], :frozen? end def test_multiple_positional_args_with_the_same_name @@ -3667,15 +3739,25 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end end - def test_multiple_roots + def test_multiple_roots_raises_error + ex = assert_raises(ArgumentError) { + draw do + root "pages#index", constraints: { host: "www.example.com" } + root "admin/pages#index", constraints: { host: "admin.example.com" } + end + } + assert_match(/Invalid route name, already in use: 'root'/, ex.message) + end + + def test_multiple_named_roots draw do namespace :foo do root "pages#index", constraints: { host: "www.example.com" } - root "admin/pages#index", constraints: { host: "admin.example.com" } + root "admin/pages#index", constraints: { host: "admin.example.com" }, as: :admin_root end root "pages#index", constraints: { host: "www.example.com" } - root "admin/pages#index", constraints: { host: "admin.example.com" } + root "admin/pages#index", constraints: { host: "admin.example.com" }, as: :admin_root end get "http://www.example.com/foo" @@ -4341,7 +4423,7 @@ class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest include Routes.url_helpers - test "url helpers do not ignore nil parameters when using non-optimized routes" do + test "URL helpers do not ignore nil parameters when using non-optimized routes" do Routes.stub :optimize_routes_generation?, false do get "/categories/1" assert_response :success @@ -4713,7 +4795,7 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest include Routes.url_helpers - test "url helpers raise a 'missing keys' error for a nil param with optimized helpers" do + test "URL helpers raise a 'missing keys' error for a nil param with optimized helpers" do url, missing = { action: "show", controller: "products", id: nil }, [:id] message = "No route matches #{url.inspect}, missing required keys: #{missing.inspect}" @@ -4721,7 +4803,7 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest assert_equal message, error.message end - test "url helpers raise a 'constraint failure' error for a nil param with non-optimized helpers" do + test "URL helpers raise a 'constraint failure' error for a nil param with non-optimized helpers" do url, missing = { action: "show", controller: "products", id: nil }, [:id] message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}" @@ -4729,15 +4811,15 @@ class TestUrlGenerationErrors < ActionDispatch::IntegrationTest assert_equal message, error.message end - test "url helpers raise message with mixed parameters when generation fails" do + test "URL helpers raise message with mixed parameters when generation fails" do url, missing = { action: "show", controller: "products", id: nil, "id" => "url-tested" }, [:id] message = "No route matches #{url.inspect}, possible unmatched constraints: #{missing.inspect}" - # Optimized url helper + # Optimized URL helper error = assert_raises(ActionController::UrlGenerationError) { product_path(nil, "id" => "url-tested") } assert_equal message, error.message - # Non-optimized url helper + # Non-optimized URL helper error = assert_raises(ActionController::UrlGenerationError, message) { product_path(id: nil, "id" => "url-tested") } assert_equal message, error.message end @@ -4950,8 +5032,12 @@ end class FlashRedirectTest < ActionDispatch::IntegrationTest SessionKey = "_myapp_session" - Generator = ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") - Rotations = ActiveSupport::Messages::RotationConfiguration.new + Generator = ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000) + ) + Rotations = ActiveSupport::Messages::RotationConfiguration.new + SIGNED_COOKIE_SALT = "signed cookie" + ENCRYPTED_SIGNED_COOKIE_SALT = "signed encrypted cookie" class KeyGeneratorMiddleware def initialize(app) @@ -4961,6 +5047,8 @@ class FlashRedirectTest < ActionDispatch::IntegrationTest def call(env) env["action_dispatch.key_generator"] ||= Generator env["action_dispatch.cookies_rotations"] ||= Rotations + env["action_dispatch.signed_cookie_salt"] = SIGNED_COOKIE_SALT + env["action_dispatch.encrypted_signed_cookie_salt"] = ENCRYPTED_SIGNED_COOKIE_SALT @app.call(env) end diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index 9b51ee1cad..ac685a7dca 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -38,8 +38,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest begin require "dalli" - ss = Dalli::Client.new("localhost:11211").stats - raise Dalli::DalliError unless ss["localhost:11211"] + servers = ENV["MEMCACHE_SERVERS"] || "localhost:11211" + ss = Dalli::Client.new(servers).stats + raise Dalli::DalliError unless ss[servers] def test_setting_and_getting_session_value with_test_route_set do @@ -195,7 +196,9 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest end @app = self.class.build_app(set) do |middleware| - middleware.use ActionDispatch::Session::MemCacheStore, key: "_session_id", namespace: "mem_cache_store_test:#{SecureRandom.hex(10)}" + middleware.use ActionDispatch::Session::MemCacheStore, + key: "_session_id", namespace: "mem_cache_store_test:#{SecureRandom.hex(10)}", + memcache_server: ENV["MEMCACHE_SERVERS"] || "localhost:11211" middleware.delete ActionDispatch::ShowExceptions end diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index b69071b44b..6fafa4e426 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -9,6 +9,8 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest case req.path when "/not_found" raise AbstractController::ActionNotFound + when "/invalid_mimetype" + raise Mime::Type::InvalidMimeType when "/bad_params", "/bad_params.json" begin raise StandardError.new @@ -36,32 +38,36 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "skip exceptions app if not showing exceptions" do @app = ProductionApp assert_raise RuntimeError do - get "/", headers: { "action_dispatch.show_exceptions" => false } + get "/", env: { "action_dispatch.show_exceptions" => false } end end test "rescue with error page" do @app = ProductionApp - get "/", headers: { "action_dispatch.show_exceptions" => true } + get "/", env: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_equal "500 error fixture\n", body - get "/bad_params", headers: { "action_dispatch.show_exceptions" => true } + get "/bad_params", env: { "action_dispatch.show_exceptions" => true } assert_response 400 assert_equal "400 error fixture\n", body - get "/not_found", headers: { "action_dispatch.show_exceptions" => true } + get "/not_found", env: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_equal "404 error fixture\n", body - get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true } + get "/method_not_allowed", env: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_equal "", body - get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true } + get "/unknown_http_method", env: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_equal "", body + + get "/invalid_mimetype", headers: { "Accept" => "text/html,*", "action_dispatch.show_exceptions" => true } + assert_response 406 + assert_equal "", body end test "localize rescue error page" do @@ -70,11 +76,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest begin @app = ProductionApp - get "/", headers: { "action_dispatch.show_exceptions" => true } + get "/", env: { "action_dispatch.show_exceptions" => true } assert_response 500 assert_equal "500 localized error fixture\n", body - get "/not_found", headers: { "action_dispatch.show_exceptions" => true } + get "/not_found", env: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_equal "404 error fixture\n", body ensure @@ -85,14 +91,14 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "sets the HTTP charset parameter" do @app = ProductionApp - get "/", headers: { "action_dispatch.show_exceptions" => true } + get "/", env: { "action_dispatch.show_exceptions" => true } assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] end test "show registered original exception for wrapped exceptions" do @app = ProductionApp - get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } + get "/not_found_original_exception", env: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_match(/404 error/, body) end @@ -106,7 +112,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) - get "/not_found_original_exception", headers: { "action_dispatch.show_exceptions" => true } + get "/not_found_original_exception", env: { "action_dispatch.show_exceptions" => true } assert_response 404 assert_equal "YOU FAILED", body end @@ -117,7 +123,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app) - get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true } + get "/method_not_allowed", env: { "action_dispatch.show_exceptions" => true } assert_response 405 assert_equal "", body end @@ -125,12 +131,12 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest test "bad params exception is returned in the correct format" do @app = ProductionApp - get "/bad_params", headers: { "action_dispatch.show_exceptions" => true } + get "/bad_params", env: { "action_dispatch.show_exceptions" => true } assert_equal "text/html; charset=utf-8", response.headers["Content-Type"] assert_response 400 assert_match(/400 error/, body) - get "/bad_params.json", headers: { "action_dispatch.show_exceptions" => true } + get "/bad_params.json", env: { "action_dispatch.show_exceptions" => true } assert_equal "application/json; charset=utf-8", response.headers["Content-Type"] assert_response 400 assert_equal("{\"status\":400,\"error\":\"Bad Request\"}", body) diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 6b69cd9999..d44aa00122 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -31,7 +31,7 @@ module StaticTests end def test_handles_urls_with_ascii_8bit - assert_equal "Hello, World!", get("/doorkeeper%E3E4".dup.force_encoding("ASCII-8BIT")).body + assert_equal "Hello, World!", get((+"/doorkeeper%E3E4").force_encoding("ASCII-8BIT")).body end def test_handles_urls_with_ascii_8bit_on_win_31j @@ -39,7 +39,7 @@ module StaticTests Encoding.default_internal = "Windows-31J" Encoding.default_external = "Windows-31J" end - assert_equal "Hello, World!", get("/doorkeeper%E3E4".dup.force_encoding("ASCII-8BIT")).body + assert_equal "Hello, World!", get((+"/doorkeeper%E3E4").force_encoding("ASCII-8BIT")).body end def test_handles_urls_with_null_byte diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb index a824ee0c84..0d08f17af3 100644 --- a/actionpack/test/dispatch/system_testing/driver_test.rb +++ b/actionpack/test/dispatch/system_testing/driver_test.rb @@ -2,6 +2,7 @@ require "abstract_unit" require "action_dispatch/system_testing/driver" +require "selenium/webdriver" class DriverTest < ActiveSupport::TestCase test "initializing the driver" do @@ -22,6 +23,7 @@ class DriverTest < ActiveSupport::TestCase driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_chrome, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" }) assert_equal :selenium, driver.instance_variable_get(:@name) assert_equal :headless_chrome, driver.instance_variable_get(:@browser).name + assert_instance_of Selenium::WebDriver::Chrome::Options, driver.instance_variable_get(:@browser).options assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size) assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options) end @@ -30,6 +32,7 @@ class DriverTest < ActiveSupport::TestCase driver = ActionDispatch::SystemTesting::Driver.new(:selenium, using: :headless_firefox, screen_size: [1400, 1400], options: { url: "http://example.com/wd/hub" }) assert_equal :selenium, driver.instance_variable_get(:@name) assert_equal :headless_firefox, driver.instance_variable_get(:@browser).name + assert_instance_of Selenium::WebDriver::Firefox::Options, driver.instance_variable_get(:@browser).options assert_equal [1400, 1400], driver.instance_variable_get(:@screen_size) assert_equal ({ url: "http://example.com/wd/hub" }), driver.instance_variable_get(:@options) end @@ -51,4 +54,70 @@ class DriverTest < ActiveSupport::TestCase test "registerable? returns false if driver is rack_test" do assert_not ActionDispatch::SystemTesting::Driver.new(:rack_test).send(:registerable?) end + + test "define extra capabilities using chrome" do + driver_option = nil + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :chrome) do |option| + option.add_argument("start-maximized") + option.add_emulation(device_name: "iphone 6") + option.add_preference(:detach, true) + + driver_option = option + end + driver.use + + expected = { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } + assert_equal expected, driver_option.as_json + end + + test "define extra capabilities using headless_chrome" do + driver_option = nil + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :headless_chrome) do |option| + option.add_argument("start-maximized") + option.add_emulation(device_name: "iphone 6") + option.add_preference(:detach, true) + + driver_option = option + end + driver.use + + expected = { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } + assert_equal expected, driver_option.as_json + end + + test "define extra capabilities using firefox" do + driver_option = nil + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :firefox) do |option| + option.add_preference("browser.startup.homepage", "http://www.seleniumhq.com/") + option.add_argument("--host=127.0.0.1") + + driver_option = option + end + driver.use + + expected = { "moz:firefoxOptions" => { args: ["--host=127.0.0.1"], prefs: { "browser.startup.homepage" => "http://www.seleniumhq.com/" } } } + assert_equal expected, driver_option.as_json + end + + test "define extra capabilities using headless_firefox" do + driver_option = nil + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :headless_firefox) do |option| + option.add_preference("browser.startup.homepage", "http://www.seleniumhq.com/") + option.add_argument("--host=127.0.0.1") + + driver_option = option + end + driver.use + + expected = { "moz:firefoxOptions" => { args: ["--host=127.0.0.1"], prefs: { "browser.startup.homepage" => "http://www.seleniumhq.com/" } } } + assert_equal expected, driver_option.as_json + end + + test "does not define extra capabilities" do + driver = ActionDispatch::SystemTesting::Driver.new(:selenium, screen_size: [1400, 1400], using: :firefox) + + assert_nothing_raised do + driver.use + end + end end diff --git a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb index de79c05657..b756b91379 100644 --- a/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb +++ b/actionpack/test/dispatch/system_testing/screenshot_helper_test.rb @@ -3,6 +3,7 @@ require "abstract_unit" require "action_dispatch/system_testing/test_helpers/screenshot_helper" require "capybara/dsl" +require "selenium/webdriver" class ScreenshotHelperTest < ActiveSupport::TestCase test "image path is saved in tmp directory" do @@ -41,22 +42,20 @@ class ScreenshotHelperTest < ActiveSupport::TestCase end test "display_image return artifact format when specify RAILS_SYSTEM_TESTING_SCREENSHOT environment" do - begin - original_output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] - ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = "artifact" + original_output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] + ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = "artifact" - new_test = DrivenBySeleniumWithChrome.new("x") + new_test = DrivenBySeleniumWithChrome.new("x") - assert_equal "artifact", new_test.send(:output_type) + assert_equal "artifact", new_test.send(:output_type) - Rails.stub :root, Pathname.getwd do - new_test.stub :passed?, false do - assert_match %r|url=artifact://.+?tmp/screenshots/failures_x\.png|, new_test.send(:display_image) - end + Rails.stub :root, Pathname.getwd do + new_test.stub :passed?, false do + assert_match %r|url=artifact://.+?tmp/screenshots/failures_x\.png|, new_test.send(:display_image) end - ensure - ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = original_output_type end + ensure + ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] = original_output_type end test "image path returns the absolute path from root" do diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb index b078a5abc5..847b09dcfe 100644 --- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb +++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "abstract_unit" +require "selenium/webdriver" class SetDriverToRackTestTest < DrivenByRackTest test "uses rack_test" do diff --git a/actionpack/test/dispatch/test_response_test.rb b/actionpack/test/dispatch/test_response_test.rb index f0b8f7785d..2629a61057 100644 --- a/actionpack/test/dispatch/test_response_test.rb +++ b/actionpack/test/dispatch/test_response_test.rb @@ -27,11 +27,4 @@ class TestResponseTest < ActiveSupport::TestCase response = ActionDispatch::TestResponse.create(200, { "Content-Type" => "application/json" }, '{ "foo": "fighters" }') assert_equal({ "foo" => "fighters" }, response.parsed_body) end - - test "response status aliases deprecated" do - response = ActionDispatch::TestResponse.create - assert_deprecated { response.success? } - assert_deprecated { response.missing? } - assert_deprecated { response.error? } - end end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 21169fcb5c..03e5274541 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true require "abstract_unit" +require "tempfile" +require "stringio" module ActionDispatch class UploadedFileTest < ActiveSupport::TestCase @@ -11,109 +13,118 @@ module ActionDispatch end def test_original_filename - uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new) + uf = Http::UploadedFile.new(filename: "foo", tempfile: Tempfile.new) assert_equal "foo", uf.original_filename end def test_filename_is_different_object file_str = "foo" - uf = Http::UploadedFile.new(filename: file_str, tempfile: Object.new) + uf = Http::UploadedFile.new(filename: file_str, tempfile: Tempfile.new) assert_not_equal file_str.object_id, uf.original_filename.object_id end def test_filename_should_be_in_utf_8 - uf = Http::UploadedFile.new(filename: "foo", tempfile: Object.new) + uf = Http::UploadedFile.new(filename: "foo", tempfile: Tempfile.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s end def test_filename_should_always_be_in_utf_8 uf = Http::UploadedFile.new(filename: "foo".encode(Encoding::SHIFT_JIS), - tempfile: Object.new) + tempfile: Tempfile.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s end def test_content_type - uf = Http::UploadedFile.new(type: "foo", tempfile: Object.new) + uf = Http::UploadedFile.new(type: "foo", tempfile: Tempfile.new) assert_equal "foo", uf.content_type end def test_headers - uf = Http::UploadedFile.new(head: "foo", tempfile: Object.new) + uf = Http::UploadedFile.new(head: "foo", tempfile: Tempfile.new) assert_equal "foo", uf.headers end def test_tempfile - uf = Http::UploadedFile.new(tempfile: "foo") - assert_equal "foo", uf.tempfile + tf = Tempfile.new + uf = Http::UploadedFile.new(tempfile: tf) + assert_equal tf, uf.tempfile end - def test_to_io_returns_the_tempfile - tf = Object.new + def test_to_io_returns_file + tf = Tempfile.new uf = Http::UploadedFile.new(tempfile: tf) - assert_equal tf, uf.to_io + assert_equal tf.to_io, uf.to_io end def test_delegates_path_to_tempfile - tf = Class.new { def path; "thunderhorse" end } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_equal "thunderhorse", uf.path + tf = Tempfile.new + uf = Http::UploadedFile.new(tempfile: tf) + assert_equal tf.path, uf.path end def test_delegates_open_to_tempfile - tf = Class.new { def open; "thunderhorse" end } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_equal "thunderhorse", uf.open + tf = Tempfile.new + tf.close + uf = Http::UploadedFile.new(tempfile: tf) + assert_equal tf, uf.open + assert_not tf.closed? end def test_delegates_close_to_tempfile - tf = Class.new { def close(unlink_now = false); "thunderhorse" end } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_equal "thunderhorse", uf.close + tf = Tempfile.new + uf = Http::UploadedFile.new(tempfile: tf) + uf.close + assert tf.closed? end def test_close_accepts_parameter - tf = Class.new { def close(unlink_now = false); "thunderhorse: #{unlink_now}" end } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_equal "thunderhorse: true", uf.close(true) + tf = Tempfile.new + uf = Http::UploadedFile.new(tempfile: tf) + uf.close(true) + assert tf.closed? + assert_nil tf.path end def test_delegates_read_to_tempfile - tf = Class.new { def read(length = nil, buffer = nil); "thunderhorse" end } - uf = Http::UploadedFile.new(tempfile: tf.new) + tf = Tempfile.new + tf << "thunderhorse" + tf.rewind + uf = Http::UploadedFile.new(tempfile: tf) assert_equal "thunderhorse", uf.read end def test_delegates_read_to_tempfile_with_params - tf = Class.new { def read(length = nil, buffer = nil); [length, buffer] end } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_equal %w{ thunder horse }, uf.read(*%w{ thunder horse }) - end - - def test_delegate_respects_respond_to? - tf = Class.new { def read; yield end; private :read } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_raises(NoMethodError) do - uf.read - end + tf = Tempfile.new + tf << "thunderhorse" + tf.rewind + uf = Http::UploadedFile.new(tempfile: tf) + assert_equal "thunder", uf.read(7) + assert_equal "horse", uf.read(5, String.new) end def test_delegate_eof_to_tempfile - tf = Class.new { def eof?; true end; } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_predicate uf, :eof? + tf = Tempfile.new + tf << "thunderhorse" + uf = Http::UploadedFile.new(tempfile: tf) + assert_equal true, uf.eof? + tf.rewind + assert_equal false, uf.eof? end def test_delegate_to_path_to_tempfile - tf = Class.new { def to_path; "/any/file/path" end; } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_equal "/any/file/path", uf.to_path + tf = Tempfile.new + uf = Http::UploadedFile.new(tempfile: tf) + assert_equal tf.to_path, uf.to_path end - def test_respond_to? - tf = Class.new { def read; yield end } - uf = Http::UploadedFile.new(tempfile: tf.new) - assert_respond_to uf, :headers - assert_respond_to uf, :read + def test_io_copy_stream + tf = Tempfile.new + tf << "thunderhorse" + tf.rewind + uf = Http::UploadedFile.new(tempfile: tf) + result = StringIO.new + IO.copy_stream(uf, result) + assert_equal "thunderhorse", result.string end end end diff --git a/actionpack/test/fixtures/alternate_helpers/foo_helper.rb b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb index 3aadb6145e..c1a995af5f 100644 --- a/actionpack/test/fixtures/alternate_helpers/foo_helper.rb +++ b/actionpack/test/fixtures/alternate_helpers/foo_helper.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module FooHelper - redefine_method(:baz) {} + redefine_method(:baz) { } end diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index 3e7aea57f1..2f39abcb92 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -34,17 +34,17 @@ module ActionDispatch end { - "/:controller(/:action)" => %r{\A/(#{x})(?:/([^/.?]+))?}, - "/:controller/foo" => %r{\A/(#{x})/foo}, - "/:controller/:action" => %r{\A/(#{x})/([^/.?]+)}, - "/:controller" => %r{\A/(#{x})}, - "/:controller(/:action(/:id))" => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?}, - "/:controller/:action.xml" => %r{\A/(#{x})/([^/.?]+)\.xml}, - "/:controller.:format" => %r{\A/(#{x})\.([^/.?]+)}, - "/:controller(.:format)" => %r{\A/(#{x})(?:\.([^/.?]+))?}, - "/:controller/*foo" => %r{\A/(#{x})/(.+)}, - "/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar}, - "/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))}, + "/:controller(/:action)" => %r{\A/(#{x})(?:/([^/.?]+))?(?:\b|\Z)}, + "/:controller/foo" => %r{\A/(#{x})/foo(?:\b|\Z)}, + "/:controller/:action" => %r{\A/(#{x})/([^/.?]+)(?:\b|\Z)}, + "/:controller" => %r{\A/(#{x})(?:\b|\Z)}, + "/:controller(/:action(/:id))" => %r{\A/(#{x})(?:/([^/.?]+)(?:/([^/.?]+))?)?(?:\b|\Z)}, + "/:controller/:action.xml" => %r{\A/(#{x})/([^/.?]+)\.xml(?:\b|\Z)}, + "/:controller.:format" => %r{\A/(#{x})\.([^/.?]+)(?:\b|\Z)}, + "/:controller(.:format)" => %r{\A/(#{x})(?:\.([^/.?]+))?(?:\b|\Z)}, + "/:controller/*foo" => %r{\A/(#{x})/(.+)(?:\b|\Z)}, + "/:controller/*foo/bar" => %r{\A/(#{x})/(.+)/bar(?:\b|\Z)}, + "/:foo|*bar" => %r{\A/(?:([^/.?]+)|(.+))(?:\b|\Z)}, }.each do |path, expected| define_method(:"test_to_non_anchored_regexp_#{Regexp.escape(path)}") do path = Pattern.build( @@ -280,6 +280,15 @@ module ActionDispatch assert_equal "list", match[1] assert_equal "rss", match[2] end + + def test_named_captures + path = Path::Pattern.from_string "/books(/:action(.:format))" + + uri = "/books/list.rss" + match = path =~ uri + named_captures = { "action" => "list", "format" => "rss" } + assert_equal named_captures, match.named_captures + end end end end diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index 2d09098f11..472f1bf35e 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -23,7 +23,7 @@ module ActionDispatch end def test_uri_unescape_with_utf8_string - assert_equal "Šašinková", Utils.unescape_uri("%C5%A0a%C5%A1inkov%C3%A1".dup.force_encoding(Encoding::US_ASCII)) + assert_equal "Šašinková", Utils.unescape_uri((+"%C5%A0a%C5%A1inkov%C3%A1").force_encoding(Encoding::US_ASCII)) end def test_normalize_path_not_greedy diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index 1f4e14aef6..f8d89def6a 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -284,7 +284,7 @@ module ActionDispatch def test_generate_missing_keys_no_matches_different_format_keys get "/:controller/:action/:name", to: "foo#bar" - primarty_parameters = { + primary_parameters = { id: 1, controller: "tasks", action: "show", @@ -297,9 +297,9 @@ module ActionDispatch missing_parameters = { missing_key => "task_1" } - request_parameters = primarty_parameters.merge(redirection_parameters).merge(missing_parameters) + request_parameters = primary_parameters.merge(redirection_parameters).merge(missing_parameters) - message = "No route matches #{Hash[request_parameters.sort_by { |k, v|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}" + message = "No route matches #{Hash[request_parameters.sort_by { |k, _|k.to_s }].inspect}, missing required keys: #{[missing_key.to_sym].inspect}" error = assert_raises(ActionController::UrlGenerationError) do @formatter.generate( |