6b126ffdcd
We want the actual order to be very predictable, so it's rightly defined in code -- not with an on-the-fly tsort. But we can do the tsort here, and then verify that it matches the implemented ordering. This way we don't leave future readers guessing which parts of the ordering are deliberate and which are arbitrary.
311 lines
9.8 KiB
Ruby
311 lines
9.8 KiB
Ruby
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 [
|
|
"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",
|
|
"Rack::Head",
|
|
"Rack::ConditionalGet",
|
|
"Rack::ETag"
|
|
], middleware
|
|
end
|
|
|
|
test "api middleware stack" do
|
|
add_to_config "config.api_only = true"
|
|
|
|
boot!
|
|
|
|
assert_equal [
|
|
"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" } }], Rails.application.middleware.first.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.second
|
|
end
|
|
|
|
test "unshift middleware" do
|
|
add_to_config "config.middleware.unshift Rack::Config"
|
|
boot!
|
|
assert_equal "Rack::Config", middleware.first
|
|
end
|
|
|
|
test "Rails.cache does not respond to middleware" do
|
|
add_to_config "config.cache_store = :memory_store"
|
|
boot!
|
|
assert_equal "Rack::Runtime", middleware.fourth
|
|
end
|
|
|
|
test "Rails.cache does respond to middleware" do
|
|
boot!
|
|
assert_equal "Rack::Runtime", middleware.fifth
|
|
end
|
|
|
|
test "insert middleware before" do
|
|
add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config"
|
|
boot!
|
|
assert_equal "Rack::Config", middleware.first
|
|
end
|
|
|
|
test "can't change middleware after it's built" do
|
|
boot!
|
|
assert_raise RuntimeError 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
|