mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
779bc9987d
When building the default middleware stack, the ActionDispatch::ActionableExceptions middleware should only be used if the configuration has :consider_all_requests_local set to true. There is an existing check #actionable_request? in ActionableExceptions that will prevent actionable exceptions from being dispatched, which relies on the "action_dispatch.show_detailed_exceptions" header being set to false. This check should remain in place as a second line of defense in case the ActionableExceptions middleware is added back to the application. However, we can go ahead and remove the middleware from the default stack when the environment is not local.
356 lines
12 KiB
Ruby
356 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "isolation/abstract_unit"
|
|
|
|
module ApplicationTests
|
|
class MiddlewareTest < ActiveSupport::TestCase
|
|
include ActiveSupport::Testing::Isolation
|
|
|
|
def setup
|
|
build_app
|
|
FileUtils.rm_rf "#{app_path}/config/environments"
|
|
end
|
|
|
|
def teardown
|
|
teardown_app
|
|
end
|
|
|
|
def app
|
|
@app ||= Rails.application
|
|
end
|
|
|
|
test "default middleware stack" do
|
|
add_to_config "config.active_record.migration_error = :page_load"
|
|
|
|
boot!
|
|
|
|
assert_equal [
|
|
"Webpacker::DevServerProxy",
|
|
"ActionDispatch::HostAuthorization",
|
|
"Rack::Sendfile",
|
|
"ActionDispatch::Static",
|
|
"ActionDispatch::Executor",
|
|
"ActiveSupport::Cache::Strategy::LocalCache",
|
|
"Rack::Runtime",
|
|
"Rack::MethodOverride",
|
|
"ActionDispatch::RequestId",
|
|
"ActionDispatch::RemoteIp",
|
|
"Rails::Rack::Logger",
|
|
"ActionDispatch::ShowExceptions",
|
|
"ActionDispatch::DebugExceptions",
|
|
"ActionDispatch::Reloader",
|
|
"ActionDispatch::Callbacks",
|
|
"ActiveRecord::Migration::CheckPending",
|
|
"ActionDispatch::Cookies",
|
|
"ActionDispatch::Session::CookieStore",
|
|
"ActionDispatch::Flash",
|
|
"ActionDispatch::ContentSecurityPolicy::Middleware",
|
|
"ActionDispatch::PermissionsPolicy::Middleware",
|
|
"Rack::Head",
|
|
"Rack::ConditionalGet",
|
|
"Rack::ETag",
|
|
"Rack::TempfileReaper"
|
|
], middleware
|
|
end
|
|
|
|
test "default middleware stack when requests are local" do
|
|
add_to_config "config.consider_all_requests_local = true"
|
|
add_to_config "config.active_record.migration_error = :page_load"
|
|
|
|
boot!
|
|
|
|
assert_equal [
|
|
"Webpacker::DevServerProxy",
|
|
"ActionDispatch::HostAuthorization",
|
|
"Rack::Sendfile",
|
|
"ActionDispatch::Static",
|
|
"ActionDispatch::Executor",
|
|
"ActiveSupport::Cache::Strategy::LocalCache",
|
|
"Rack::Runtime",
|
|
"Rack::MethodOverride",
|
|
"ActionDispatch::RequestId",
|
|
"ActionDispatch::RemoteIp",
|
|
"Rails::Rack::Logger",
|
|
"ActionDispatch::ShowExceptions",
|
|
"ActionDispatch::DebugExceptions",
|
|
"ActionDispatch::ActionableExceptions",
|
|
"ActionDispatch::Reloader",
|
|
"ActionDispatch::Callbacks",
|
|
"ActiveRecord::Migration::CheckPending",
|
|
"ActionDispatch::Cookies",
|
|
"ActionDispatch::Session::CookieStore",
|
|
"ActionDispatch::Flash",
|
|
"ActionDispatch::ContentSecurityPolicy::Middleware",
|
|
"ActionDispatch::PermissionsPolicy::Middleware",
|
|
"Rack::Head",
|
|
"Rack::ConditionalGet",
|
|
"Rack::ETag",
|
|
"Rack::TempfileReaper"
|
|
], middleware
|
|
end
|
|
|
|
test "api middleware stack" do
|
|
add_to_config "config.api_only = true"
|
|
|
|
boot!
|
|
|
|
assert_equal [
|
|
"Webpacker::DevServerProxy",
|
|
"ActionDispatch::HostAuthorization",
|
|
"Rack::Sendfile",
|
|
"ActionDispatch::Static",
|
|
"ActionDispatch::Executor",
|
|
"ActiveSupport::Cache::Strategy::LocalCache",
|
|
"Rack::Runtime",
|
|
"ActionDispatch::RequestId",
|
|
"ActionDispatch::RemoteIp",
|
|
"Rails::Rack::Logger",
|
|
"ActionDispatch::ShowExceptions",
|
|
"ActionDispatch::DebugExceptions",
|
|
"ActionDispatch::Reloader",
|
|
"ActionDispatch::Callbacks",
|
|
"Rack::Head",
|
|
"Rack::ConditionalGet",
|
|
"Rack::ETag"
|
|
], middleware
|
|
end
|
|
|
|
test "middleware dependencies" do
|
|
boot!
|
|
|
|
# The following array-of-arrays describes dependencies between
|
|
# middlewares: the first item in each list depends on the
|
|
# remaining items (and therefore must occur later in the
|
|
# middleware stack).
|
|
|
|
dependencies = [
|
|
# Logger needs a fully "corrected" request environment
|
|
%w(Rails::Rack::Logger Rack::MethodOverride ActionDispatch::RequestId ActionDispatch::RemoteIp),
|
|
|
|
# Serving public/ doesn't invoke user code, so it should skip
|
|
# locks etc
|
|
%w(ActionDispatch::Executor ActionDispatch::Static),
|
|
|
|
# Errors during reload must be reported
|
|
%w(ActionDispatch::Reloader ActionDispatch::ShowExceptions ActionDispatch::DebugExceptions),
|
|
|
|
# Outright dependencies
|
|
%w(ActionDispatch::Static Rack::Sendfile),
|
|
%w(ActionDispatch::Flash ActionDispatch::Session::CookieStore),
|
|
%w(ActionDispatch::Session::CookieStore ActionDispatch::Cookies),
|
|
]
|
|
|
|
require "tsort"
|
|
sorted = TSort.tsort((middleware | dependencies.flatten).method(:each),
|
|
lambda { |n, &b| dependencies.each { |m, *ds| ds.each(&b) if m == n } })
|
|
assert_equal sorted, middleware
|
|
end
|
|
|
|
test "Rack::Cache is not included by default" do
|
|
boot!
|
|
|
|
assert_not_includes middleware, "Rack::Cache", "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache"
|
|
end
|
|
|
|
test "Rack::Cache is present when action_dispatch.rack_cache is set" do
|
|
add_to_config "config.action_dispatch.rack_cache = true"
|
|
|
|
boot!
|
|
|
|
assert_includes middleware, "Rack::Cache"
|
|
end
|
|
|
|
test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do
|
|
add_to_config "config.active_record.migration_error = :page_load"
|
|
|
|
boot!
|
|
|
|
assert_includes middleware, "ActiveRecord::Migration::CheckPending"
|
|
end
|
|
|
|
test "ActionDispatch::SSL is present when force_ssl is set" do
|
|
add_to_config "config.force_ssl = true"
|
|
boot!
|
|
assert_includes middleware, "ActionDispatch::SSL"
|
|
end
|
|
|
|
test "ActionDispatch::SSL is configured with options when given" do
|
|
add_to_config "config.force_ssl = true"
|
|
add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }"
|
|
boot!
|
|
|
|
assert_equal [{ redirect: { host: "example.com" }, ssl_default_redirect_status: 308 }], Rails.application.middleware[2].args
|
|
end
|
|
|
|
test "removing Active Record omits its middleware" do
|
|
use_frameworks []
|
|
boot!
|
|
assert_not_includes middleware, "ActiveRecord::Migration::CheckPending"
|
|
end
|
|
|
|
test "includes executor" do
|
|
boot!
|
|
assert_includes middleware, "ActionDispatch::Executor"
|
|
end
|
|
|
|
test "does not include lock if cache_classes is set and so is eager_load" do
|
|
add_to_config "config.cache_classes = true"
|
|
add_to_config "config.eager_load = true"
|
|
boot!
|
|
assert_not_includes middleware, "Rack::Lock"
|
|
end
|
|
|
|
test "does not include lock if allow_concurrency is set to :unsafe" do
|
|
add_to_config "config.allow_concurrency = :unsafe"
|
|
boot!
|
|
assert_not_includes middleware, "Rack::Lock"
|
|
end
|
|
|
|
test "includes lock if allow_concurrency is disabled" do
|
|
add_to_config "config.allow_concurrency = false"
|
|
boot!
|
|
assert_includes middleware, "Rack::Lock"
|
|
end
|
|
|
|
test "removes static asset server if public_file_server.enabled is disabled" do
|
|
add_to_config "config.public_file_server.enabled = false"
|
|
boot!
|
|
assert_not_includes middleware, "ActionDispatch::Static"
|
|
end
|
|
|
|
test "can delete a middleware from the stack" do
|
|
add_to_config "config.middleware.delete ActionDispatch::Static"
|
|
boot!
|
|
assert_not_includes middleware, "ActionDispatch::Static"
|
|
end
|
|
|
|
test "can delete a middleware from the stack even if insert_before is added after delete" do
|
|
add_to_config "config.middleware.delete Rack::Runtime"
|
|
add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)"
|
|
boot!
|
|
assert_includes middleware, "Rack::Config"
|
|
assert_not middleware.include?("Rack::Runtime")
|
|
end
|
|
|
|
test "can delete a middleware from the stack even if insert_after is added after delete" do
|
|
add_to_config "config.middleware.delete Rack::Runtime"
|
|
add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)"
|
|
boot!
|
|
assert_includes middleware, "Rack::Config"
|
|
assert_not middleware.include?("Rack::Runtime")
|
|
end
|
|
|
|
test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
|
|
add_to_config "config.action_dispatch.show_exceptions = false"
|
|
boot!
|
|
assert_includes middleware, "ActionDispatch::ShowExceptions"
|
|
assert_includes middleware, "ActionDispatch::DebugExceptions"
|
|
end
|
|
|
|
test "removes ActionDispatch::Reloader if cache_classes is true" do
|
|
add_to_config "config.cache_classes = true"
|
|
boot!
|
|
assert_not_includes middleware, "ActionDispatch::Reloader"
|
|
end
|
|
|
|
test "use middleware" do
|
|
use_frameworks []
|
|
add_to_config "config.middleware.use Rack::Config"
|
|
boot!
|
|
assert_equal "Rack::Config", middleware.last
|
|
end
|
|
|
|
test "insert middleware after" do
|
|
add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config"
|
|
boot!
|
|
assert_equal "Rack::Config", middleware.fourth
|
|
end
|
|
|
|
test "unshift middleware" do
|
|
add_to_config "config.middleware.unshift Rack::Config"
|
|
boot!
|
|
assert_equal "Rack::Config", middleware.second
|
|
end
|
|
|
|
test "Rails.cache does not respond to middleware" do
|
|
add_to_config "config.cache_store = :memory_store, { timeout: 10 }"
|
|
boot!
|
|
assert_equal "Rack::Runtime", middleware[5]
|
|
assert_instance_of ActiveSupport::Cache::MemoryStore, Rails.cache
|
|
end
|
|
|
|
test "Rails.cache does respond to middleware" do
|
|
boot!
|
|
assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware[5]
|
|
assert_equal "Rack::Runtime", middleware[6]
|
|
end
|
|
|
|
test "insert middleware before" do
|
|
add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config"
|
|
boot!
|
|
assert_equal "Rack::Config", middleware.third
|
|
end
|
|
|
|
test "can't change middleware after it's built" do
|
|
boot!
|
|
assert_raise FrozenError do
|
|
app.config.middleware.use Rack::Config
|
|
end
|
|
end
|
|
|
|
# ConditionalGet + Etag
|
|
test "conditional get + etag middlewares handle http caching based on body" do
|
|
make_basic_app
|
|
|
|
class ::OmgController < ActionController::Base
|
|
def index
|
|
if params[:nothing]
|
|
render plain: ""
|
|
else
|
|
render plain: "OMG"
|
|
end
|
|
end
|
|
end
|
|
|
|
etag = "W/" + "c00862d1c6c1cf7c1b49388306e7b3c1".inspect
|
|
|
|
get "/"
|
|
assert_equal 200, last_response.status
|
|
assert_equal "OMG", last_response.body
|
|
assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"]
|
|
assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"]
|
|
assert_equal etag, last_response.headers["Etag"]
|
|
|
|
get "/", {}, { "HTTP_IF_NONE_MATCH" => etag }
|
|
assert_equal 304, last_response.status
|
|
assert_equal "", last_response.body
|
|
assert_nil last_response.headers["Content-Type"]
|
|
assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"]
|
|
assert_equal etag, last_response.headers["Etag"]
|
|
|
|
get "/?nothing=true"
|
|
assert_equal 200, last_response.status
|
|
assert_equal "", last_response.body
|
|
assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"]
|
|
assert_equal "no-cache", last_response.headers["Cache-Control"]
|
|
assert_nil last_response.headers["Etag"]
|
|
end
|
|
|
|
test "ORIGINAL_FULLPATH is passed to env" do
|
|
boot!
|
|
env = ::Rack::MockRequest.env_for("/foo/?something")
|
|
Rails.application.call(env)
|
|
|
|
assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"]
|
|
end
|
|
|
|
private
|
|
def boot!
|
|
require "#{app_path}/config/environment"
|
|
end
|
|
|
|
def middleware
|
|
Rails.application.middleware.map(&:klass).map(&:name)
|
|
end
|
|
end
|
|
end
|