diff options
Diffstat (limited to 'railties/test/application')
30 files changed, 1853 insertions, 456 deletions
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb index 3e0f31860b..7623e8e352 100644 --- a/railties/test/application/asset_debugging_test.rb +++ b/railties/test/application/asset_debugging_test.rb @@ -95,7 +95,7 @@ module ApplicationTests end end - test "public url methods are not over-written by the asset pipeline" do + test "public URL methods are not over-written by the asset pipeline" do contents = "doesnotexist" cases = { asset_url: %r{http://example.org/#{contents}}, diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 4ca6d02b85..a80581211b 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -68,20 +68,6 @@ module ApplicationTests assert_equal 'a = "/assets/rails.png";', last_response.body.strip end - test "assets do not require compressors until it is used" do - app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" - add_to_env_config "production", "config.assets.compile = true" - add_to_env_config "production", "config.assets.precompile = []" - - # Load app env - app "production" - - assert_not defined?(Uglifier) - get "/assets/demo.js" - assert_match "alert()", last_response.body - assert defined?(Uglifier) - end - test "precompile creates the file, gives it the original asset's content and run in production as default" do app_file "app/assets/config/manifest.js", "//= link_tree ../javascripts" app_file "app/assets/javascripts/application.js", "alert();" @@ -443,13 +429,13 @@ module ApplicationTests end test "digested assets are not mistakenly removed" do - app_file "app/assets/application.js", "alert();" + app_file "app/assets/application.css", "div { font-weight: bold }" add_to_config "config.assets.compile = true" precompile! - files = Dir["#{app_path}/public/assets/application-*.js"] - assert_equal 1, files.length, "Expected digested application.js asset to be generated, but none found" + files = Dir["#{app_path}/public/assets/application-*.css"] + assert_equal 1, files.length, "Expected digested application.css asset to be generated, but none found" end test "digested assets are removed from configured path" do @@ -464,7 +450,7 @@ module ApplicationTests assert_equal 0, files.length, "Expected application.js asset to be removed, but still exists" end - test "asset urls should use the request's protocol by default" do + test "asset URLs should use the request's protocol by default" do app_with_assets_in_view add_to_config "config.asset_host = 'example.com'" add_to_env_config "development", "config.assets.digest = false" @@ -480,7 +466,7 @@ module ApplicationTests assert_match('src="https://example.com/assets/application.self.js', last_response.body) end - test "asset urls should be protocol-relative if no request is in scope" do + test "asset URLs should be protocol-relative if no request is in scope" do app_file "app/assets/images/rails.png", "notreallyapng" app_file "app/assets/javascripts/image_loader.js.erb", "var src='<%= image_path('rails.png') %>';" add_to_config "config.assets.precompile = %w{rails.png image_loader.js}" diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index 54934dbe24..a952d2466b 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -43,18 +43,22 @@ module ApplicationTests # Ignore line that's only output by Bundler < 1.14 output.sub!(/^Resolving dependencies\.\.\.\n/, "") + # Suppress Bundler platform warnings from output + output.gsub!(/^The dependency .* will be unused .*\.\n/, "") + # Ignore warnings such as `Psych.safe_load is deprecated` + output.gsub!(/^warning:\s.*\n/, "") - assert_equal(<<-OUTPUT, output) -== Installing dependencies == -The Gemfile's dependencies are satisfied + assert_equal(<<~OUTPUT, output) + == Installing dependencies == + The Gemfile's dependencies are satisfied -== Preparing database == -Created database 'db/development.sqlite3' -Created database 'db/test.sqlite3' + == Preparing database == + Created database 'db/development.sqlite3' + Created database 'db/test.sqlite3' -== Removing old logs and tempfiles == + == Removing old logs and tempfiles == -== Restarting application server == + == Restarting application server == OUTPUT end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c2699006f6..b8e167b488 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -3,6 +3,7 @@ require "isolation/abstract_unit" require "rack/test" require "env_helpers" +require "set" class ::MyMailInterceptor def self.delivering_email(email); email; end @@ -123,6 +124,18 @@ module ApplicationTests assert_equal "MyLogger", Rails.application.config.logger.class.name end + test "raises an error if cache does not support recyclable cache keys" do + build_app(initializers: true) + add_to_env_config "production", "config.cache_store = Class.new {}.new" + add_to_env_config "production", "config.active_record.cache_versioning = true" + + error = assert_raise(RuntimeError) do + app "production" + end + + assert_match(/You're using a cache/, error.message) + end + test "a renders exception on pending migration" do add_to_config <<-RUBY config.active_record.migration_error = :page_load @@ -297,6 +310,115 @@ module ApplicationTests assert_equal %w(noop_email).to_set, PostsMailer.instance_variable_get(:@action_methods) end + test "does not eager load attribute methods in development" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + app "development" + + assert_not_includes Post.instance_methods, :title + end + + test "does not eager load attribute methods in production when the schema cache is empty" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app "production" + + assert_not_includes Post.instance_methods, :title + end + + test "eager loads attribute methods in production when the schema cache is populated" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app_file "config/initializers/schema_cache.rb", <<-RUBY + ActiveRecord::Base.connection.schema_cache.add("posts") + RUBY + + app "production" + + assert_includes Post.instance_methods, :title + end + + test "does not attempt to eager load attribute methods for models that aren't connected" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define(version: 1) do + create_table :posts do |t| + t.string :title + end + end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app_file "app/models/comment.rb", <<-RUBY + class Comment < ActiveRecord::Base + establish_connection(adapter: "mysql2", database: "does_not_exist") + end + RUBY + + assert_nothing_raised do + app "production" + end + end + test "initialize an eager loaded, cache classes app" do add_to_config <<-RUBY config.eager_load = true @@ -474,45 +596,30 @@ module ApplicationTests assert_equal "some_value", verifier.verify(message) end - test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" do + test "application will generate secret_key_base in tmp file if blank in development" do app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.credentials.secret_key_base = nil - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" RUBY - app "production" + # For test that works even if tmp dir does not exist. + Dir.chdir(app_path) { FileUtils.remove_dir("tmp") } - assert_kind_of ActiveSupport::LegacyKeyGenerator, Rails.application.key_generator - message = app.message_verifier(:sensitive_value).generate("some_value") - assert_equal "some_value", Rails.application.message_verifier(:sensitive_value).verify(message) + app "development" + + assert_not_nil app.secrets.secret_key_base + assert File.exist?(app_path("tmp/development_secret.txt")) end - test "config.secret_token is deprecated" do + test "application will not generate secret_key_base in tmp file if blank in production" do app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + Rails.application.credentials.secret_key_base = nil RUBY - app "production" - - assert_deprecated(/secret_token/) do - app.secrets - end - end - - test "secrets.secret_token is deprecated" do - app_file "config/secrets.yml", <<-YAML - production: - secret_token: "b3c631c314c0bbca50c1b2843150fe33" - YAML - - app "production" - - assert_deprecated(/secret_token/) do - app.secrets + assert_raises ArgumentError do + app "production" end end - test "raises when secret_key_base is blank" do app_file "config/initializers/secret_token.rb", <<-RUBY Rails.application.credentials.secret_key_base = nil @@ -534,23 +641,8 @@ module ApplicationTests end end - test "prefer secrets.secret_token over config.secret_token" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "" - RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_token: 3b7cd727ee24e8444053437c36cc66c3 - YAML - - app "development" - - assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_token - end - test "application verifier can build different verifiers" do make_basic_app do |application| - application.credentials.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" application.config.session_store :disabled end @@ -589,22 +681,6 @@ module ApplicationTests assert_equal "3b7cd727ee24e8444053437c36cc66c3", app.secrets.secret_key_base end - test "config.secret_token over-writes a blank secrets.secret_token" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" - RUBY - app_file "config/secrets.yml", <<-YAML - development: - secret_key_base: - secret_token: - YAML - - app "development" - - assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.secrets.secret_token - assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token - end - test "custom secrets saved in config/secrets.yml are loaded in app secrets" do app_file "config/secrets.yml", <<-YAML development: @@ -667,19 +743,6 @@ module ApplicationTests assert_equal "iaminallyoursecretkeybase", app.secrets.secret_key_base end - test "uses ActiveSupport::LegacyKeyGenerator as app.key_generator when secrets.secret_key_base is blank" do - app_file "config/initializers/secret_token.rb", <<-RUBY - Rails.application.credentials.secret_key_base = nil - Rails.application.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" - RUBY - - app "production" - - assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token - assert_nil app.credentials.secret_key_base - assert_kind_of ActiveSupport::LegacyKeyGenerator, app.key_generator - end - test "that nested keys are symbolized the same as parents for hashes more than one level deep" do app_file "config/secrets.yml", <<-YAML development: @@ -1117,6 +1180,38 @@ module ApplicationTests end end + test "autoloaders" do + app "development" + + config = Rails.application.config + assert Rails.autoloaders.zeitwerk_enabled? + assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main + assert_equal "rails.main", Rails.autoloaders.main.tag + assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once + assert_equal "rails.once", Rails.autoloaders.once.tag + assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.main.inflector + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.once.inflector + + config.autoloader = :classic + assert_not Rails.autoloaders.zeitwerk_enabled? + assert_nil Rails.autoloaders.main + assert_nil Rails.autoloaders.once + assert_equal 0, Rails.autoloaders.count + + config.autoloader = :zeitwerk + assert Rails.autoloaders.zeitwerk_enabled? + assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main + assert_equal "rails.main", Rails.autoloaders.main.tag + assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once + assert_equal "rails.once", Rails.autoloaders.once.tag + assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.main.inflector + assert_equal ActiveSupport::Dependencies::ZeitwerkIntegration::Inflector, Rails.autoloaders.once.inflector + + assert_raises(ArgumentError) { config.autoloader = :unknown } + end + test "config.action_view.cache_template_loading with cache_classes default" do add_to_config "config.cache_classes = true" @@ -1433,14 +1528,12 @@ module ApplicationTests end test "config.session_store with :active_record_store with activerecord-session_store gem" do - begin - make_basic_app do |application| - ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore) - application.config.session_store :active_record_store - end - ensure - ActionDispatch::Session.send :remove_const, :ActiveRecordStore + make_basic_app do |application| + ActionDispatch::Session::ActiveRecordStore = Class.new(ActionDispatch::Session::CookieStore) + application.config.session_store :active_record_store end + ensure + ActionDispatch::Session.send :remove_const, :ActiveRecordStore end test "config.session_store with :active_record_store without activerecord-session_store gem" do @@ -1603,6 +1696,14 @@ module ApplicationTests assert_kind_of Hash, Rails.application.config.database_configuration end + test "autoload paths do not include asset paths" do + app "development" + ActiveSupport::Dependencies.autoload_paths.each do |path| + assert_not_operator path, :ends_with?, "app/assets" + assert_not_operator path, :ends_with?, "app/javascript" + end + end + test "raises with proper error message if no database configuration found" do FileUtils.rm("#{app_path}/config/database.yml") err = assert_raises RuntimeError do @@ -1668,10 +1769,10 @@ module ApplicationTests assert_equal true, Rails.application.config.action_mailer.show_previews end - test "config_for loads custom configuration from yaml files" do + test "config_for loads custom configuration from yaml accessible as symbol or string" do app_file "config/custom.yml", <<-RUBY development: - key: 'custom key' + foo: 'bar' RUBY add_to_config <<-RUBY @@ -1680,7 +1781,171 @@ module ApplicationTests app "development" - assert_equal "custom key", Rails.application.config.my_custom_config["key"] + assert_equal "bar", Rails.application.config.my_custom_config[:foo] + assert_equal "bar", Rails.application.config.my_custom_config["foo"] + end + + test "config_for loads nested custom configuration from yaml as symbol keys" do + app_file "config/custom.yml", <<-RUBY + development: + foo: + bar: + baz: 1 + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "development" + + assert_equal 1, Rails.application.config.my_custom_config[:foo][:bar][:baz] + end + + test "config_for loads nested custom configuration from yaml with deprecated non-symbol access" do + app_file "config/custom.yml", <<-RUBY + development: + foo: + bar: + baz: 1 + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "development" + + assert_deprecated do + assert_equal 1, Rails.application.config.my_custom_config["foo"]["bar"]["baz"] + end + end + + test "config_for loads nested custom configuration inside array from yaml with deprecated non-symbol access" do + app_file "config/custom.yml", <<-RUBY + development: + foo: + bar: + - baz: 1 + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "development" + + config = Rails.application.config.my_custom_config + assert_instance_of Rails::Application::NonSymbolAccessDeprecatedHash, config[:foo][:bar].first + + assert_deprecated do + assert_equal 1, config[:foo][:bar].first["baz"] + end + end + + test "config_for makes all hash methods available" do + app_file "config/custom.yml", <<-RUBY + development: + foo: 0 + bar: + baz: 1 + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "development" + + actual = Rails.application.config.my_custom_config + + assert_equal({ foo: 0, bar: { baz: 1 } }, actual) + assert_equal([ :foo, :bar ], actual.keys) + assert_equal([ 0, baz: 1], actual.values) + assert_equal({ foo: 0, bar: { baz: 1 } }, actual.to_h) + assert_equal(0, actual[:foo]) + assert_equal({ baz: 1 }, actual[:bar]) + end + + test "config_for generates deprecation notice when nested hash methods are called with non-symbols" do + app_file "config/custom.yml", <<-RUBY + development: + foo: + bar: 1 + baz: 2 + qux: + boo: 3 + RUBY + + app "development" + + actual = Rails.application.config_for("custom")[:foo] + + # slice + assert_deprecated do + assert_equal({ bar: 1, baz: 2 }, actual.slice("bar", "baz")) + end + + # except + assert_deprecated do + assert_equal({ qux: { boo: 3 } }, actual.except("bar", "baz")) + end + + # dig + assert_deprecated do + assert_equal(3, actual.dig("qux", "boo")) + end + + # fetch - hit + assert_deprecated do + assert_equal(1, actual.fetch("bar", 0)) + end + + # fetch - miss + assert_deprecated do + assert_equal(0, actual.fetch("does-not-exist", 0)) + end + + # fetch_values + assert_deprecated do + assert_equal([1, 2], actual.fetch_values("bar", "baz")) + end + + # key? - hit + assert_deprecated do + assert(actual.key?("bar")) + end + + # key? - miss + assert_deprecated do + assert_not(actual.key?("does-not-exist")) + end + + # slice! + actual = Rails.application.config_for("custom")[:foo] + + assert_deprecated do + slice = actual.slice!("bar", "baz") + assert_equal({ bar: 1, baz: 2 }, actual) + assert_equal({ qux: { boo: 3 } }, slice) + end + + # extract! + actual = Rails.application.config_for("custom")[:foo] + + assert_deprecated do + extracted = actual.extract!("bar", "baz") + assert_equal({ bar: 1, baz: 2 }, extracted) + assert_equal({ qux: { boo: 3 } }, actual) + end + + # except! + actual = Rails.application.config_for("custom")[:foo] + + assert_deprecated do + actual.except!("bar", "baz") + assert_equal({ qux: { boo: 3 } }, actual) + end end test "config_for uses the Pathname object if it is provided" do @@ -1695,7 +1960,7 @@ module ApplicationTests app "development" - assert_equal "custom key", Rails.application.config.my_custom_config["key"] + assert_equal "custom key", Rails.application.config.my_custom_config[:key] end test "config_for raises an exception if the file does not exist" do @@ -1725,8 +1990,29 @@ module ApplicationTests assert_equal({}, Rails.application.config.my_custom_config) end - test "config_for with empty file returns an empty hash" do + test "config_for implements shared configuration as secrets case found" do app_file "config/custom.yml", <<-RUBY + shared: + foo: :bar + test: + foo: :baz + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app "test" + + assert_equal(:baz, Rails.application.config.my_custom_config[:foo]) + end + + test "config_for implements shared configuration as secrets case not found" do + app_file "config/custom.yml", <<-RUBY + shared: + foo: :bar + test: + foo: :baz RUBY add_to_config <<-RUBY @@ -1735,40 +2021,45 @@ module ApplicationTests app "development" - assert_equal({}, Rails.application.config.my_custom_config) + assert_equal(:bar, Rails.application.config.my_custom_config[:foo]) end - test "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do - remove_from_config '.*config\.load_defaults.*\n' + test "config_for with empty file returns an empty hash" do + app_file "config/custom.yml", <<-RUBY + RUBY - app_file "app/models/post.rb", <<-RUBY - class Post < ActiveRecord::Base - end + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') RUBY app "development" - force_lazy_load_hooks { Post } - assert_not ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + assert_equal({}, Rails.application.config.my_custom_config) end - test "default SQLite3Adapter.represent_boolean_as_integer for new installs is true" do + test "represent_boolean_as_integer is deprecated" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + RUBY + app_file "app/models/post.rb", <<-RUBY class Post < ActiveRecord::Base end RUBY app "development" - force_lazy_load_hooks { Post } - - assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + assert_deprecated do + force_lazy_load_hooks { Post } + end end - test "represent_boolean_as_integer should be able to set via config.active_record.sqlite3.represent_boolean_as_integer" do + test "represent_boolean_as_integer raises when the value is false" do remove_from_config '.*config\.load_defaults.*\n' app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY - Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = false RUBY app_file "app/models/post.rb", <<-RUBY @@ -1777,9 +2068,9 @@ module ApplicationTests RUBY app "development" - force_lazy_load_hooks { Post } - - assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + assert_raises(RuntimeError) do + force_lazy_load_hooks { Post } + end end test "config_for containing ERB tags should evaluate" do @@ -1794,7 +2085,7 @@ module ApplicationTests app "development" - assert_equal "custom key", Rails.application.config.my_custom_config["key"] + assert_equal "custom key", Rails.application.config.my_custom_config[:key] end test "config_for with syntax error show a more descriptive exception" do @@ -1827,7 +2118,7 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert_equal "unicorn", Rails.application.config.my_custom_config["key"] + assert_equal "unicorn", Rails.application.config.my_custom_config[:key] end test "api_only is false by default" do @@ -1981,7 +2272,9 @@ module ApplicationTests test "ActionView::Template.finalize_compiled_template_methods is true by default" do app "test" - assert_equal true, ActionView::Template.finalize_compiled_template_methods + assert_deprecated do + ActionView::Template.finalize_compiled_template_methods + end end test "ActionView::Template.finalize_compiled_template_methods can be configured via config.action_view.finalize_compiled_template_methods" do @@ -1993,7 +2286,210 @@ module ApplicationTests app "test" - assert_equal false, ActionView::Template.finalize_compiled_template_methods + assert_deprecated do + ActionView::Template.finalize_compiled_template_methods + end + end + + test "ActiveJob::Base.return_false_on_aborted_enqueue is true by default" do + app "development" + + assert_equal true, ActiveJob::Base.return_false_on_aborted_enqueue + end + + test "ActiveJob::Base.return_false_on_aborted_enqueue is false in the 5.x defaults" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_config 'config.load_defaults "5.2"' + + app "development" + + assert_equal false, ActiveJob::Base.return_false_on_aborted_enqueue + end + + test "ActiveJob::Base.return_false_on_aborted_enqueue can be configured in the new framework defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY + Rails.application.config.active_job.return_false_on_aborted_enqueue = true + RUBY + + app "development" + + assert_equal true, ActiveJob::Base.return_false_on_aborted_enqueue + end + + test "ActiveStorage.queues[:analysis] is :active_storage_analysis by default" do + app "development" + + assert_equal :active_storage_analysis, ActiveStorage.queues[:analysis] + end + + test "ActiveStorage.queues[:analysis] is nil without Rails 6 defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app "development" + + assert_nil ActiveStorage.queues[:analysis] + end + + test "ActiveStorage.queues[:purge] is :active_storage_purge by default" do + app "development" + + assert_equal :active_storage_purge, ActiveStorage.queues[:purge] + end + + test "ActiveStorage.queues[:purge] is nil without Rails 6 defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app "development" + + assert_nil ActiveStorage.queues[:purge] + end + + test "ActionMailbox.logger is Rails.logger by default" do + app "development" + + assert_equal Rails.logger, ActionMailbox.logger + end + + test "ActionMailbox.logger can be configured" do + app_file "lib/my_logger.rb", <<-RUBY + require "logger" + class MyLogger < ::Logger + end + RUBY + + add_to_config <<-RUBY + require "my_logger" + config.action_mailbox.logger = MyLogger.new(STDOUT) + RUBY + + app "development" + + assert_equal "MyLogger", ActionMailbox.logger.class.name + end + + test "ActionMailbox.incinerate_after is 30.days by default" do + app "development" + + assert_equal 30.days, ActionMailbox.incinerate_after + end + + test "ActionMailbox.incinerate_after can be configured" do + add_to_config <<-RUBY + config.action_mailbox.incinerate_after = 14.days + RUBY + + app "development" + + assert_equal 14.days, ActionMailbox.incinerate_after + end + + test "ActionMailbox.queues[:incineration] is :action_mailbox_incineration by default" do + app "development" + + assert_equal :action_mailbox_incineration, ActionMailbox.queues[:incineration] + end + + test "ActionMailbox.queues[:incineration] can be configured" do + add_to_config <<-RUBY + config.action_mailbox.queues.incineration = :another_queue + RUBY + + app "development" + + assert_equal :another_queue, ActionMailbox.queues[:incineration] + end + + test "ActionMailbox.queues[:routing] is :action_mailbox_routing by default" do + app "development" + + assert_equal :action_mailbox_routing, ActionMailbox.queues[:routing] + end + + test "ActionMailbox.queues[:routing] can be configured" do + add_to_config <<-RUBY + config.action_mailbox.queues.routing = :another_queue + RUBY + + app "development" + + assert_equal :another_queue, ActionMailbox.queues[:routing] + end + + test "ActionMailer::Base.delivery_job is ActionMailer::MailDeliveryJob by default" do + app "development" + + assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job + end + + test "ActionMailer::Base.delivery_job is ActionMailer::DeliveryJob in the 5.x defaults" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_config 'config.load_defaults "5.2"' + + app "development" + + assert_equal ActionMailer::DeliveryJob, ActionMailer::Base.delivery_job + end + + test "ActionMailer::Base.delivery_job can be configured in the new framework defaults" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_6_0.rb", <<-RUBY + Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" + RUBY + + app "development" + + assert_equal ActionMailer::MailDeliveryJob, ActionMailer::Base.delivery_job + end + + test "ActiveRecord::Base.filter_attributes should equal to filter_parameters" do + app_file "config/initializers/filter_parameters_logging.rb", <<-RUBY + Rails.application.config.filter_parameters += [ :password, :credit_card_number ] + RUBY + app "development" + assert_equal [ :password, :credit_card_number ], Rails.application.config.filter_parameters + assert_equal [ :password, :credit_card_number ], ActiveRecord::Base.filter_attributes + end + + test "ActiveStorage.routes_prefix can be configured via config.active_storage.routes_prefix" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.active_storage.routes_prefix = '/files' + end + RUBY + + output = rails("routes", "-g", "active_storage") + assert_equal <<~MESSAGE, output + Prefix Verb URI Pattern Controller#Action + rails_service_blob GET /files/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_representation GET /files/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show + rails_disk_service GET /files/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /files/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /files/direct_uploads(.:format) active_storage/direct_uploads#create + MESSAGE + end + + test "hosts include .localhost in development" do + app "development" + assert_includes Rails.application.config.hosts, ".localhost" + end + + test "disable_sandbox is false by default" do + app "development" + + assert_equal false, Rails.configuration.disable_sandbox + end + + test "disable_sandbox can be overridden" do + add_to_config <<-RUBY + config.disable_sandbox = true + RUBY + + app "development" + + assert Rails.configuration.disable_sandbox end private diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 4a14042cd3..db16f4cc56 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -25,7 +25,7 @@ class ConsoleTest < ActiveSupport::TestCase end def test_app_method_should_return_integration_session - TestHelpers::Rack.send :remove_method, :app + TestHelpers::Rack.remove_method :app load_environment console_session = irb_context.app assert_instance_of ActionDispatch::Integration::Session, console_session @@ -109,7 +109,7 @@ class FullStackConsoleTest < ActiveSupport::TestCase CODE system "#{app_path}/bin/rails runner 'Post.connection.create_table :posts'" - @master, @slave = PTY.open + @primary, @replica = PTY.open end def teardown @@ -117,19 +117,23 @@ class FullStackConsoleTest < ActiveSupport::TestCase end def write_prompt(command, expected_output = nil) - @master.puts command - assert_output command, @master - assert_output expected_output, @master if expected_output - assert_output "> ", @master + @primary.puts command + assert_output command, @primary + assert_output expected_output, @primary if expected_output + assert_output "> ", @primary end - def spawn_console(options) - Process.spawn( + def spawn_console(options, wait_for_prompt: true) + pid = Process.spawn( "#{app_path}/bin/rails console #{options}", - in: @slave, out: @slave, err: @slave + in: @replica, out: @replica, err: @replica ) - assert_output "> ", @master, 30 + if wait_for_prompt + assert_output "> ", @primary, 30 + end + + pid end def test_sandbox @@ -138,21 +142,32 @@ class FullStackConsoleTest < ActiveSupport::TestCase write_prompt "Post.count", "=> 0" write_prompt "Post.create" write_prompt "Post.count", "=> 1" - @master.puts "quit" + @primary.puts "quit" spawn_console("--sandbox") write_prompt "Post.count", "=> 0" write_prompt "Post.transaction { Post.create; raise }" write_prompt "Post.count", "=> 0" - @master.puts "quit" + @primary.puts "quit" + end + + def test_sandbox_when_sandbox_is_disabled + add_to_config <<-RUBY + config.disable_sandbox = true + RUBY + + output = `#{app_path}/bin/rails console --sandbox` + + assert_includes output, "sandbox mode is disabled" + assert_equal 1, $?.exitstatus end def test_environment_option_and_irb_option - spawn_console("test -- --verbose") + spawn_console("-e test -- --verbose") write_prompt "a = 1", "a = 1" write_prompt "puts Rails.env", "puts Rails.env\r\ntest" - @master.puts "quit" + @primary.puts "quit" end end diff --git a/railties/test/application/credentials_test.rb b/railties/test/application/credentials_test.rb new file mode 100644 index 0000000000..2f6b109b50 --- /dev/null +++ b/railties/test/application/credentials_test.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "env_helpers" + +class Rails::CredentialsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + setup :build_app + teardown :teardown_app + + test "reads credentials from environment specific path" do + write_credentials_override(:production) + + app("production") + + assert_equal "revealed", Rails.application.credentials.mystery + end + + test "reads credentials from customized path and key" do + write_credentials_override(:staging) + add_to_env_config("production", "config.credentials.content_path = config.root.join('config/credentials/staging.yml.enc')") + add_to_env_config("production", "config.credentials.key_path = config.root.join('config/credentials/staging.key')") + + app("production") + + assert_equal "revealed", Rails.application.credentials.mystery + end + + test "reads credentials using environment variable key" do + write_credentials_override(:production, with_key: false) + + switch_env("RAILS_MASTER_KEY", credentials_key) do + app("production") + + assert_equal "revealed", Rails.application.credentials.mystery + end + end + + private + def write_credentials_override(name, with_key: true) + Dir.chdir(app_path) do + Dir.mkdir "config/credentials" + File.write "config/credentials/#{name}.key", credentials_key if with_key + + # secret_key_base: secret + # mystery: revealed + File.write "config/credentials/#{name}.yml.enc", + "vgvKu4MBepIgZ5VHQMMPwnQNsLlWD9LKmJHu3UA/8yj6x+3fNhz3DwL9brX7UA==--qLdxHP6e34xeTAiI--nrcAsleXuo9NqiEuhntAhw==" + end + end + + def credentials_key + "2117e775dc2024d4f49ddf3aeb585919" + end +end diff --git a/railties/test/application/dbconsole_test.rb b/railties/test/application/dbconsole_test.rb index 8eb293c179..8c03fe4ac6 100644 --- a/railties/test/application/dbconsole_test.rb +++ b/railties/test/application/dbconsole_test.rb @@ -33,11 +33,11 @@ module ApplicationTests end RUBY - master, slave = PTY.open - spawn_dbconsole(slave) - assert_output("sqlite>", master) + primary, replica = PTY.open + spawn_dbconsole(replica) + assert_output("sqlite>", primary) ensure - master.puts ".exit" + primary.puts ".exit" end def test_respect_environment_option @@ -56,14 +56,14 @@ module ApplicationTests database: db/production.sqlite3 YAML - master, slave = PTY.open - spawn_dbconsole(slave, "-e production") - assert_output("sqlite>", master) + primary, replica = PTY.open + spawn_dbconsole(replica, "-e production") + assert_output("sqlite>", primary) - master.puts "pragma database_list;" - assert_output("production.sqlite3", master) + primary.puts "pragma database_list;" + assert_output("production.sqlite3", primary) ensure - master.puts ".exit" + primary.puts ".exit" end private diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 1530ea82d6..a35247fc43 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -39,7 +39,7 @@ module ApplicationTests assert_equal expanded_path, ActionMailer::Base.view_paths[0].to_s end - test "allows me to configure default url options for ActionMailer" do + test "allows me to configure default URL options for ActionMailer" do app_file "config/environments/development.rb", <<-RUBY Rails.application.configure do config.action_mailer.default_url_options = { :host => "test.rails" } @@ -61,7 +61,7 @@ module ApplicationTests assert_equal "https", ActionMailer::Base.default_url_options[:protocol] end - test "includes url helpers as action methods" do + test "includes URL helpers as action methods" do app_file "config/routes.rb", <<-RUBY Rails.application.routes.draw do get "/foo", :to => lambda { |env| [200, {}, []] }, :as => :foo @@ -230,35 +230,31 @@ module ApplicationTests end test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do - begin - require "#{app_path}/config/environment" - orig_database_url = ENV.delete("DATABASE_URL") - orig_rails_env, Rails.env = Rails.env, "development" - ActiveRecord::Base.establish_connection - assert ActiveRecord::Base.connection - assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) - ensure - ActiveRecord::Base.remove_connection - ENV["DATABASE_URL"] = orig_database_url if orig_database_url - Rails.env = orig_rails_env if orig_rails_env - end + require "#{app_path}/config/environment" + orig_database_url = ENV.delete("DATABASE_URL") + orig_rails_env, Rails.env = Rails.env, "development" + ActiveRecord::Base.establish_connection + assert ActiveRecord::Base.connection + assert_match(/#{ActiveRecord::Base.configurations[Rails.env]['database']}/, ActiveRecord::Base.connection_config[:database]) + ensure + ActiveRecord::Base.remove_connection + ENV["DATABASE_URL"] = orig_database_url if orig_database_url + Rails.env = orig_rails_env if orig_rails_env end test "active record establish_connection uses DATABASE_URL even if Rails.env is set" do - begin - require "#{app_path}/config/environment" - orig_database_url = ENV.delete("DATABASE_URL") - orig_rails_env, Rails.env = Rails.env, "development" - database_url_db_name = "db/database_url_db.sqlite3" - ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" - ActiveRecord::Base.establish_connection - assert ActiveRecord::Base.connection - assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) - ensure - ActiveRecord::Base.remove_connection - ENV["DATABASE_URL"] = orig_database_url if orig_database_url - Rails.env = orig_rails_env if orig_rails_env - end + require "#{app_path}/config/environment" + orig_database_url = ENV.delete("DATABASE_URL") + orig_rails_env, Rails.env = Rails.env, "development" + database_url_db_name = "db/database_url_db.sqlite3" + ENV["DATABASE_URL"] = "sqlite3:#{database_url_db_name}" + ActiveRecord::Base.establish_connection + assert ActiveRecord::Base.connection + assert_match(/#{database_url_db_name}/, ActiveRecord::Base.connection_config[:database]) + ensure + ActiveRecord::Base.remove_connection + ENV["DATABASE_URL"] = orig_database_url if orig_database_url + Rails.env = orig_rails_env if orig_rails_env end test "connections checked out during initialization are returned to the pool" do diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index 889ad16fb8..9c98489590 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -34,6 +34,22 @@ class LoadingTest < ActiveSupport::TestCase assert_equal "omg", p.title end + test "constants without a matching file raise NameError" do + app_file "app/models/post.rb", <<-RUBY + class Post + NON_EXISTING_CONSTANT + end + RUBY + + boot_app + + e = assert_raise(NameError) { User } + assert_equal "uninitialized constant #{self.class}::User", e.message + + e = assert_raise(NameError) { Post } + assert_equal "uninitialized constant Post::NON_EXISTING_CONSTANT", e.message + end + test "concerns in app are autoloaded" do app_file "app/controllers/concerns/trackable.rb", <<-CONCERN module Trackable @@ -371,6 +387,72 @@ class LoadingTest < ActiveSupport::TestCase end end + test "active record query cache hooks are installed before first request in production" do + app_file "app/controllers/omg_controller.rb", <<-RUBY + begin + class OmgController < ActionController::Metal + ActiveSupport.run_load_hooks(:action_controller, self) + def show + if ActiveRecord::Base.connection.query_cache_enabled + self.response_body = ["Query cache is enabled."] + else + self.response_body = ["Expected ActiveRecord::Base.connection.query_cache_enabled to be true"] + end + end + end + rescue => e + puts "Error loading metal: \#{e.class} \#{e.message}" + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/:controller(/:action)" + end + RUBY + + boot_app "production" + + require "rack/test" + extend Rack::Test::Methods + + get "/omg/show" + assert_equal "Query cache is enabled.", last_response.body + end + + test "active record query cache hooks are installed before first request in development" do + app_file "app/controllers/omg_controller.rb", <<-RUBY + begin + class OmgController < ActionController::Metal + ActiveSupport.run_load_hooks(:action_controller, self) + def show + if ActiveRecord::Base.connection.query_cache_enabled + self.response_body = ["Query cache is enabled."] + else + self.response_body = ["Expected ActiveRecord::Base.connection.query_cache_enabled to be true"] + end + end + end + rescue => e + puts "Error loading metal: \#{e.class} \#{e.message}" + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/:controller(/:action)" + end + RUBY + + boot_app "development" + + require "rack/test" + extend Rack::Test::Methods + + get "/omg/show" + assert_equal "Query cache is enabled.", last_response.body + end + private def setup_ar! @@ -382,4 +464,12 @@ class LoadingTest < ActiveSupport::TestCase end end end + + def boot_app(env = "development") + ENV["RAILS_ENV"] = env + + require "#{app_path}/config/environment" + ensure + ENV.delete "RAILS_ENV" + end end diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index ba186bda44..fb84276b8a 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -85,6 +85,7 @@ module ApplicationTests end test "mailer previews are loaded from a custom preview_path" do + app_dir "lib/mailer_previews" add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" mailer "notifier", <<-RUBY @@ -254,6 +255,7 @@ module ApplicationTests end test "mailer previews are reloaded from a custom preview_path" do + app_dir "lib/mailer_previews" add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" app("development") @@ -818,6 +820,7 @@ module ApplicationTests def build_app super app_file "config/routes.rb", "Rails.application.routes.draw do; end" + app_dir "test/mailers/previews" end def mailer(name, contents) diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb index ecb4ee3446..fe48ef3f03 100644 --- a/railties/test/application/middleware/cookies_test.rb +++ b/railties/test/application/middleware/cookies_test.rb @@ -110,14 +110,14 @@ module ApplicationTests assert_equal "signed cookie".inspect, last_response.body get "/foo/read_raw_cookie" - assert_equal "signed cookie", verifier_sha512.verify(last_response.body) + assert_equal "signed cookie", verifier_sha512.verify(last_response.body, purpose: "cookie.signed_cookie") get "/foo/write_raw_cookie_sha256" get "/foo/read_signed" assert_equal "signed cookie".inspect, last_response.body get "/foo/read_raw_cookie" - assert_equal "signed cookie", verifier_sha512.verify(last_response.body) + assert_equal "signed cookie", verifier_sha512.verify(last_response.body, purpose: "cookie.signed_cookie") end test "encrypted cookies rotating multiple encryption keys" do @@ -180,14 +180,14 @@ module ApplicationTests assert_equal "encrypted cookie".inspect, last_response.body get "/foo/read_raw_cookie" - assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body) + assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body, purpose: "cookie.encrypted_cookie") - get "/foo/write_raw_cookie_sha256" + get "/foo/write_raw_cookie_two" get "/foo/read_encrypted" assert_equal "encrypted cookie".inspect, last_response.body get "/foo/read_raw_cookie" - assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body) + assert_equal "encrypted cookie", encryptor.decrypt_and_verify(last_response.body, purpose: "cookie.encrypted_cookie") end end end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb index 2d659ade8d..17df78ed4e 100644 --- a/railties/test/application/middleware/exceptions_test.rb +++ b/railties/test/application/middleware/exceptions_test.rb @@ -60,7 +60,7 @@ module ApplicationTests assert_equal "YOU FAILED", last_response.body end - test "url generation error when action_dispatch.show_exceptions is set raises an exception" do + test "URL generation error when action_dispatch.show_exceptions is set raises an exception" do controller :foo, <<-RUBY class FooController < ActionController::Base def index diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb index 83cf8a27f7..515b32080e 100644 --- a/railties/test/application/middleware/remote_ip_test.rb +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -12,7 +12,9 @@ module ApplicationTests remote_ip = nil env = Rack::MockRequest.env_for("/").merge(env).merge!( "action_dispatch.show_exceptions" => false, - "action_dispatch.key_generator" => ActiveSupport::LegacyKeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33") + "action_dispatch.key_generator" => ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000) + ) ) endpoint = Proc.new do |e| diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb index 9182a63ab7..479615c133 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -183,7 +183,7 @@ module ApplicationTests encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) get "/foo/read_raw_cookie" - assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] + assert_equal 1, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] end test "session upgrading signature to encryption cookie store works the same way as encrypted cookie store" do @@ -215,8 +215,6 @@ module ApplicationTests RUBY add_to_config <<-RUBY - secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - # Enable AEAD cookies config.action_dispatch.use_authenticated_cookie_encryption = true RUBY @@ -235,69 +233,7 @@ module ApplicationTests encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) get "/foo/read_raw_cookie" - assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] - end - - test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do - app_file "config/routes.rb", <<-RUBY - Rails.application.routes.draw do - get ':controller(/:action)' - end - RUBY - - controller :foo, <<-RUBY - class FooController < ActionController::Base - def write_raw_session - # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} - cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" - head :ok - end - - def write_session - session[:foo] = session[:foo] + 1 - head :ok - end - - def read_session - render plain: session[:foo] - end - - def read_encrypted_cookie - render plain: cookies.encrypted[:_myapp_session]['foo'] - end - - def read_raw_cookie - render plain: cookies[:_myapp_session] - end - end - RUBY - - add_to_config <<-RUBY - secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - - # Enable AEAD cookies - config.action_dispatch.use_authenticated_cookie_encryption = true - RUBY - - require "#{app_path}/config/environment" - - get "/foo/write_raw_session" - get "/foo/read_session" - assert_equal "1", last_response.body - - get "/foo/write_session" - get "/foo/read_session" - assert_equal "2", last_response.body - - get "/foo/read_encrypted_cookie" - assert_equal "2", last_response.body - - cipher = "aes-256-gcm" - secret = app.key_generator.generate_key("authenticated encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) - - get "/foo/read_raw_cookie" - assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] + assert_equal 1, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] end test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" do @@ -364,71 +300,7 @@ module ApplicationTests encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) get "/foo/read_raw_cookie" - assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] - ensure - ENV["RAILS_ENV"] = old_rails_env - end - end - - test "session upgrading legacy signed cookies to new signed cookies" do - app_file "config/routes.rb", <<-RUBY - Rails.application.routes.draw do - get ':controller(/:action)' - end - RUBY - - controller :foo, <<-RUBY - class FooController < ActionController::Base - def write_raw_session - # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} - cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" - head :ok - end - - def write_session - session[:foo] = session[:foo] + 1 - head :ok - end - - def read_session - render plain: session[:foo] - end - - def read_signed_cookie - render plain: cookies.signed[:_myapp_session]['foo'] - end - - def read_raw_cookie - render plain: cookies[:_myapp_session] - end - end - RUBY - - add_to_config <<-RUBY - secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" - Rails.application.credentials.secret_key_base = nil - RUBY - - begin - old_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "production" - - require "#{app_path}/config/environment" - - get "/foo/write_raw_session" - get "/foo/read_session" - assert_equal "1", last_response.body - - get "/foo/write_session" - get "/foo/read_session" - assert_equal "2", last_response.body - - get "/foo/read_signed_cookie" - assert_equal "2", last_response.body - - verifier = ActiveSupport::MessageVerifier.new(app.secrets.secret_token) - - get "/foo/read_raw_cookie" - assert_equal 2, verifier.verify(last_response.body)["foo"] + assert_equal 2, encryptor.decrypt_and_verify(last_response.body, purpose: "cookie._myapp_session")["foo"] ensure ENV["RAILS_ENV"] = old_rails_env end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 5efaf841d4..4242daf39a 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -25,6 +25,8 @@ module ApplicationTests boot! assert_equal [ + "Webpacker::DevServerProxy", + "ActionDispatch::HostAuthorization", "Rack::Sendfile", "ActionDispatch::Static", "ActionDispatch::Executor", @@ -56,6 +58,8 @@ module ApplicationTests boot! assert_equal [ + "Webpacker::DevServerProxy", + "ActionDispatch::HostAuthorization", "Rack::Sendfile", "ActionDispatch::Static", "ActionDispatch::Executor", @@ -138,7 +142,7 @@ module ApplicationTests add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }" boot! - assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware.first.args + assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[2].args end test "removing Active Record omits its middleware" do @@ -222,35 +226,36 @@ module ApplicationTests test "insert middleware after" do add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" boot! - assert_equal "Rack::Config", middleware.second + 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.first + assert_equal "Rack::Config", middleware.second 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 + assert_equal "Rack::Runtime", middleware[5] end test "Rails.cache does respond to middleware" do boot! - assert_equal "Rack::Runtime", middleware.fifth + 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.first + assert_equal "Rack::Config", middleware.third end test "can't change middleware after it's built" do boot! - assert_raise frozen_error_class do + assert_raise FrozenError do app.config.middleware.use Rack::Config end end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb index d6c81c1fe2..f0f1112f6b 100644 --- a/railties/test/application/multiple_applications_test.rb +++ b/railties/test/application/multiple_applications_test.rb @@ -100,30 +100,6 @@ module ApplicationTests assert_nothing_raised { AppTemplate::Application.new } end - def test_initializers_run_on_different_applications_go_to_the_same_class - application1 = AppTemplate::Application.new - run_count = 0 - - AppTemplate::Application.initializer :init0 do - run_count += 1 - end - - application1.initializer :init1 do - run_count += 1 - end - - AppTemplate::Application.new.initializer :init2 do - run_count += 1 - end - - assert_equal 0, run_count, "Without loading the initializers, the count should be 0" - - # Set config.eager_load to false so that an eager_load warning doesn't pop up - AppTemplate::Application.create { config.eager_load = false }.initialize! - - assert_equal 3, run_count, "There should have been three initializers that incremented the count" - end - def test_consoles_run_on_different_applications_go_to_the_same_class run_count = 0 AppTemplate::Application.console { run_count += 1 } @@ -165,12 +141,12 @@ module ApplicationTests app.config.some_setting = "a_different_setting" assert_equal "a_different_setting", app.config.some_setting, "The configuration's some_setting should be set." - new_config = Rails::Application::Configuration.new("root_of_application") + new_config = Rails::Application::Configuration.new(Pathname.new("root_of_application")) new_config.some_setting = "some_setting_dude" app.config = new_config assert_equal "some_setting_dude", app.config.some_setting, "The configuration's some_setting should have changed." - assert_equal "root_of_application", app.config.root, "The root should have changed to the new config's root." + assert_equal "root_of_application", app.config.root.to_s, "The root should have changed to the new config's root." assert_equal new_config, app.config, "The application's config should have changed to the new config." end end diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb index d949a48366..ea425d5fa5 100644 --- a/railties/test/application/rack/logger_test.rb +++ b/railties/test/application/rack/logger_test.rb @@ -53,6 +53,12 @@ module ApplicationTests wait assert_match 'Started HEAD "/"', logs end + + test "logger logs correct remote IP address" do + get "/", {}, { "REMOTE_ADDR" => "127.0.0.1", "HTTP_X_FORWARDED_FOR" => "1.2.3.4" } + wait + assert_match 'Started GET "/" for 1.2.3.4', logs + end end end end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 0594236b1f..258066a7e6 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true require "isolation/abstract_unit" +require "env_helpers" module ApplicationTests module RakeTests class RakeDbsTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::Isolation, EnvHelpers def setup build_app @@ -31,6 +32,7 @@ module ApplicationTests output = rails("db:create") assert_match(/Created database/, output) assert File.exist?(expected_database) + yield if block_given? assert_equal expected_database, ActiveRecord::Base.connection_config[:database] if environment_loaded output = rails("db:drop") assert_match(/Dropped database/, output) @@ -38,12 +40,12 @@ module ApplicationTests end end - test "db:create and db:drop without database url" do + test "db:create and db:drop without database URL" do require "#{app_path}/config/environment" db_create_and_drop ActiveRecord::Base.configurations[Rails.env]["database"] end - test "db:create and db:drop with database url" do + test "db:create and db:drop with database URL" do require "#{app_path}/config/environment" set_database_url db_create_and_drop database_url_db_name @@ -51,6 +53,7 @@ module ApplicationTests test "db:create and db:drop respect environment setting" do app_file "config/database.yml", <<-YAML + <% 1 %> development: database: <%= Rails.application.config.database %> adapter: sqlite3 @@ -62,7 +65,61 @@ module ApplicationTests end RUBY - db_create_and_drop "db/development.sqlite3", environment_loaded: false + db_create_and_drop("db/development.sqlite3", environment_loaded: false) + end + + test "db:create and db:drop don't raise errors when loading YAML with multiline ERB" do + app_file "config/database.yml", <<-YAML + development: + database: <%= + Rails.application.config.database + %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + db_create_and_drop("db/development.sqlite3", environment_loaded: false) + end + + test "db:create and db:drop don't raise errors when loading YAML containing conditional statements in ERB" do + app_file "config/database.yml", <<-YAML + development: + <% if Rails.application.config.database %> + database: <%= Rails.application.config.database %> + <% else %> + database: db/default.sqlite3 + <% end %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + db_create_and_drop("db/development.sqlite3", environment_loaded: false) + end + + test "db:create and db:drop don't raise errors when loading YAML containing multiple ERB statements on the same line" do + app_file "config/database.yml", <<-YAML + development: + database: <% if Rails.application.config.database %><%= Rails.application.config.database %><% else %>db/default.sqlite3<% end %> + adapter: sqlite3 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + + db_create_and_drop("db/development.sqlite3", environment_loaded: false) end def with_database_existing @@ -83,6 +140,8 @@ module ApplicationTests def with_bad_permissions Dir.chdir(app_path) do + skip "Can't avoid permissions as root" if Process.uid.zero? + set_database_url FileUtils.chmod("-w", "db") yield @@ -93,7 +152,7 @@ module ApplicationTests test "db:create failure because bad permissions" do with_bad_permissions do output = rails("db:create", allow_failure: true) - assert_match(/Couldn't create database/, output) + assert_match("Couldn't create '#{database_url_db_name}' database. Please check your configuration.", output) assert_equal 1, $?.exitstatus end end @@ -127,6 +186,59 @@ module ApplicationTests end end + test "db:truncate_all truncates all non-internal tables" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + + rails "db:truncate_all" + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 0, Book.count + end + end + + test "db:truncate_all does not truncate any tables when environment is protected" do + with_rails_env "production" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"") + + output = rails("db:truncate_all", allow_failure: true) + assert_match(/ActiveRecord::ProtectedEnvironmentError/, output) + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 1, Book.count + assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\"")) + end + end + end + def db_migrate_and_status(expected_database) rails "generate", "model", "book", "title:string" rails "db:migrate" @@ -167,9 +279,10 @@ module ApplicationTests def db_fixtures_load(expected_database) Dir.chdir(app_path) do rails "generate", "model", "book", "title:string" + reload rails "db:migrate", "db:fixtures:load" + assert_match expected_database, ActiveRecord::Base.connection_config[:database] - require "#{app_path}/app/models/book" assert_equal 2, Book.count end end @@ -189,8 +302,9 @@ module ApplicationTests require "#{app_path}/config/environment" rails "generate", "model", "admin::book", "title:string" + reload rails "db:migrate", "db:fixtures:load" - require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count end @@ -311,13 +425,12 @@ module ApplicationTests end test "db:setup loads schema and seeds database" do - begin - @old_rails_env = ENV["RAILS_ENV"] - @old_rack_env = ENV["RACK_ENV"] - ENV.delete "RAILS_ENV" - ENV.delete "RACK_ENV" + @old_rails_env = ENV["RAILS_ENV"] + @old_rack_env = ENV["RACK_ENV"] + ENV.delete "RAILS_ENV" + ENV.delete "RACK_ENV" - app_file "db/schema.rb", <<-RUBY + app_file "db/schema.rb", <<-RUBY ActiveRecord::Schema.define(version: "1") do create_table :users do |t| t.string :name @@ -325,16 +438,15 @@ module ApplicationTests end RUBY - app_file "db/seeds.rb", <<-RUBY - puts ActiveRecord::Base.connection_config[:database] - RUBY + app_file "db/seeds.rb", <<-RUBY + puts ActiveRecord::Base.connection_config[:database] + RUBY - database_path = rails("db:setup") - assert_equal "development.sqlite3", File.basename(database_path.strip) - ensure - ENV["RAILS_ENV"] = @old_rails_env - ENV["RACK_ENV"] = @old_rack_env - end + database_path = rails("db:setup") + assert_equal "development.sqlite3", File.basename(database_path.strip) + ensure + ENV["RAILS_ENV"] = @old_rails_env + ENV["RACK_ENV"] = @old_rack_env end test "db:setup sets ar_internal_metadata" do @@ -375,6 +487,88 @@ module ApplicationTests assert_equal "test", test_environment.call end + + test "db:seed:replant truncates all non-internal tables and loads the seeds" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + + app_file "db/seeds.rb", <<-RUBY + Book.create!(title: "Rework") + Book.create!(title: "Ruby Under a Microscope") + RUBY + + rails "db:seed:replant" + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 2, Book.count + assert_not_predicate Book.where(title: "Remote"), :exists? + assert_predicate Book.where(title: "Rework"), :exists? + assert_predicate Book.where(title: "Ruby Under a Microscope"), :exists? + end + end + + test "db:seed:replant does not truncate any tables and does not load the seeds when environment is protected" do + with_rails_env "production" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + rails "db:migrate" + require "#{app_path}/config/environment" + Book.create!(title: "Remote") + assert_equal 1, Book.count + schema_migrations = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + internal_metadata = ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + books = ActiveRecord::Base.connection.execute("SELECT * from \"books\"") + + app_file "db/seeds.rb", <<-RUBY + Book.create!(title: "Rework") + RUBY + + output = rails("db:seed:replant", allow_failure: true) + assert_match(/ActiveRecord::ProtectedEnvironmentError/, output) + + assert_equal( + schema_migrations, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.schema_migrations_table_name}\"") + ) + assert_equal( + internal_metadata, + ActiveRecord::Base.connection.execute("SELECT * from \"#{ActiveRecord::Base.internal_metadata_table_name}\"") + ) + assert_equal 1, Book.count + assert_equal(books, ActiveRecord::Base.connection.execute("SELECT * from \"books\"")) + assert_not_predicate Book.where(title: "Rework"), :exists? + end + end + end + + test "db:prepare setup the database" do + Dir.chdir(app_path) do + rails "generate", "model", "book", "title:string" + output = rails("db:prepare") + assert_match(/CreateBooks: migrated/, output) + + output = rails("db:drop") + assert_match(/Dropped database/, output) + + rails "generate", "model", "recipe", "title:string" + output = rails("db:prepare") + assert_match(/CreateBooks: migrated/, output) + assert_match(/CreateRecipes: migrated/, output) + end + end end end end diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb index 66e1ac9d99..a87f453075 100644 --- a/railties/test/application/rake/dev_test.rb +++ b/railties/test/application/rake/dev_test.rb @@ -9,6 +9,7 @@ module ApplicationTests def setup build_app + add_to_env_config("development", "config.active_support.deprecation = :stderr") end def teardown @@ -17,33 +18,46 @@ module ApplicationTests test "dev:cache creates file and outputs message" do Dir.chdir(app_path) do - output = rails("dev:cache") - assert File.exist?("tmp/caching-dev.txt") - assert_match(/Development mode is now being cached/, output) + stderr = capture(:stderr) do + output = run_rake_dev_cache + assert File.exist?("tmp/caching-dev.txt") + assert_match(/Development mode is now being cached/, output) + end + assert_match(/DEPRECATION WARNING: Using `bin\/rake dev:cache` is deprecated and will be removed in Rails 6.1/, stderr) end end test "dev:cache deletes file and outputs message" do Dir.chdir(app_path) do - rails "dev:cache" # Create caching file. - output = rails("dev:cache") # Delete caching file. - assert_not File.exist?("tmp/caching-dev.txt") - assert_match(/Development mode is no longer being cached/, output) + stderr = capture(:stderr) do + run_rake_dev_cache # Create caching file. + output = run_rake_dev_cache # Delete caching file. + assert_not File.exist?("tmp/caching-dev.txt") + assert_match(/Development mode is no longer being cached/, output) + end + assert_match(/DEPRECATION WARNING: Using `bin\/rake dev:cache` is deprecated and will be removed in Rails 6.1/, stderr) end end test "dev:cache touches tmp/restart.txt" do Dir.chdir(app_path) do - rails "dev:cache" - assert File.exist?("tmp/restart.txt") - - prev_mtime = File.mtime("tmp/restart.txt") - sleep(1) - rails "dev:cache" - curr_mtime = File.mtime("tmp/restart.txt") - assert_not_equal prev_mtime, curr_mtime + stderr = capture(:stderr) do + run_rake_dev_cache + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + run_rake_dev_cache + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime + end + assert_match(/DEPRECATION WARNING: Using `bin\/rake dev:cache` is deprecated and will be removed in Rails 6.1/, stderr) end end + + private + def run_rake_dev_cache + `bin/rake dev:cache` + end end end end diff --git a/railties/test/application/rake/initializers_test.rb b/railties/test/application/rake/initializers_test.rb new file mode 100644 index 0000000000..8de4967021 --- /dev/null +++ b/railties/test/application/rake/initializers_test.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeInitializersTest < ActiveSupport::TestCase + setup :build_app + teardown :teardown_app + + test "`rake initializers` prints out defined initializers invoked by Rails" do + capture(:stderr) do + initial_output = run_rake_initializers + initial_output_length = initial_output.split("\n").length + + assert_operator initial_output_length, :>, 0 + assert_not initial_output.include?("set_added_test_module") + + add_to_config <<-RUBY + initializer(:set_added_test_module) { } + RUBY + + final_output = run_rake_initializers + final_output_length = final_output.split("\n").length + + assert_equal 1, (final_output_length - initial_output_length) + assert final_output.include?("set_added_test_module") + end + end + + test "`rake initializers` outputs a deprecation warning" do + add_to_env_config("development", "config.active_support.deprecation = :stderr") + + stderr = capture(:stderr) { run_rake_initializers } + assert_match(/DEPRECATION WARNING: Using `bin\/rake initializers` is deprecated and will be removed in Rails 6.1/, stderr) + end + + private + def run_rake_initializers + Dir.chdir(app_path) { `bin/rake initializers` } + end + end + end +end diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb index 07d96fcb56..147b8f94e1 100644 --- a/railties/test/application/rake/multi_dbs_test.rb +++ b/railties/test/application/rake/multi_dbs_test.rb @@ -16,21 +16,24 @@ module ApplicationTests teardown_app end - def db_create_and_drop(namespace, expected_database, environment_loaded: true) + def db_create_and_drop(namespace, expected_database) Dir.chdir(app_path) do output = rails("db:create") assert_match(/Created database/, output) assert_match_namespace(namespace, output) + assert_no_match(/already exists/, output) assert File.exist?(expected_database) + output = rails("db:drop") assert_match(/Dropped database/, output) assert_match_namespace(namespace, output) + assert_no_match(/does not exist/, output) assert_not File.exist?(expected_database) end end - def db_create_and_drop_namespace(namespace, expected_database, environment_loaded: true) + def db_create_and_drop_namespace(namespace, expected_database) Dir.chdir(app_path) do output = rails("db:create:#{namespace}") assert_match(/Created database/, output) @@ -52,11 +55,40 @@ module ApplicationTests end end - def db_migrate_and_schema_dump_and_load(namespace, expected_database, format) + def db_migrate_and_migrate_status + Dir.chdir(app_path) do + generate_models_for_animals + rails "db:migrate" + output = rails "db:migrate:status" + assert_match(/up \d+ Create books/, output) + assert_match(/up \d+ Create dogs/, output) + end + end + + def db_migrate_and_schema_cache_dump + Dir.chdir(app_path) do + generate_models_for_animals + rails "db:migrate" + rails "db:schema:cache:dump" + assert File.exist?("db/schema_cache.yml") + assert File.exist?("db/animals_schema_cache.yml") + end + end + + def db_migrate_and_schema_cache_dump_and_schema_cache_clear Dir.chdir(app_path) do - rails "generate", "model", "book", "title:string" - rails "generate", "model", "dog", "name:string" - write_models_for_animals + generate_models_for_animals + rails "db:migrate" + rails "db:schema:cache:dump" + rails "db:schema:cache:clear" + assert_not File.exist?("db/schema_cache.yml") + assert_not File.exist?("db/animals_schema_cache.yml") + end + end + + def db_migrate_and_schema_dump_and_load(format) + Dir.chdir(app_path) do + generate_models_for_animals rails "db:migrate", "db:#{format}:dump" if format == "schema" @@ -81,11 +113,9 @@ module ApplicationTests end end - def db_migrate_namespaced(namespace, expected_database) + def db_migrate_namespaced(namespace) Dir.chdir(app_path) do - rails "generate", "model", "book", "title:string" - rails "generate", "model", "dog", "name:string" - write_models_for_animals + generate_models_for_animals output = rails("db:migrate:#{namespace}") if namespace == "primary" assert_match(/CreateBooks: migrated/, output) @@ -95,6 +125,33 @@ module ApplicationTests end end + def db_migrate_status_namespaced(namespace) + Dir.chdir(app_path) do + generate_models_for_animals + output = rails("db:migrate:status:#{namespace}") + if namespace == "primary" + assert_match(/up \d+ Create books/, output) + else + assert_match(/up \d+ Create dogs/, output) + end + end + end + + def db_prepare + Dir.chdir(app_path) do + generate_models_for_animals + output = rails("db:prepare") + + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| + if db_config.spec_name == "primary" + assert_match(/CreateBooks: migrated/, output) + else + assert_match(/CreateDogs: migrated/, output) + end + end + end + end + def write_models_for_animals # make a directory for the animals migration FileUtils.mkdir_p("#{app_path}/db/animals_migrate") @@ -114,51 +171,81 @@ module ApplicationTests # create the base model for dog to inherit from File.open("#{app_path}/app/models/animals_base.rb", "w") do |file| - file.write(<<-EOS -class AnimalsBase < ActiveRecord::Base - self.abstract_class = true + file.write(<<~EOS) + class AnimalsBase < ActiveRecord::Base + self.abstract_class = true - establish_connection :animals -end -EOS -) + establish_connection :animals + end + EOS end end + def generate_models_for_animals + rails "generate", "model", "book", "title:string" + rails "generate", "model", "dog", "name:string" + write_models_for_animals + reload + end + test "db:create and db:drop works on all databases for env" do require "#{app_path}/config/environment" - ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| - db_create_and_drop namespace, config["database"] + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| + db_create_and_drop db_config.spec_name, db_config.config["database"] end end test "db:create:namespace and db:drop:namespace works on specified databases" do require "#{app_path}/config/environment" - ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| - db_create_and_drop_namespace namespace, config["database"] + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| + db_create_and_drop_namespace db_config.spec_name, db_config.config["database"] end end test "db:migrate and db:schema:dump and db:schema:load works on all databases" do require "#{app_path}/config/environment" - ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| - db_migrate_and_schema_dump_and_load namespace, config["database"], "schema" - end + db_migrate_and_schema_dump_and_load "schema" end test "db:migrate and db:structure:dump and db:structure:load works on all databases" do require "#{app_path}/config/environment" - ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| - db_migrate_and_schema_dump_and_load namespace, config["database"], "structure" - end + db_migrate_and_schema_dump_and_load "structure" end test "db:migrate:namespace works" do require "#{app_path}/config/environment" - ActiveRecord::Base.configurations[Rails.env].each do |namespace, config| - db_migrate_namespaced namespace, config["database"] + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| + db_migrate_namespaced db_config.spec_name end end + + test "db:migrate:status works on all databases" do + require "#{app_path}/config/environment" + db_migrate_and_migrate_status + end + + test "db:migrate:status:namespace works" do + require "#{app_path}/config/environment" + ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).each do |db_config| + db_migrate_namespaced db_config.spec_name + db_migrate_status_namespaced db_config.spec_name + end + end + + test "db:schema:cache:dump works on all databases" do + require "#{app_path}/config/environment" + db_migrate_and_schema_cache_dump + end + + test "db:schema:cache:clear works on all databases" do + require "#{app_path}/config/environment" + db_migrate_and_schema_cache_dump_and_schema_cache_clear + end + + test "db:prepare works on all databases" do + require "#{app_path}/config/environment" + db_prepare + end end end end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 9e22ba84b5..60802ef7c4 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -10,6 +10,7 @@ module ApplicationTests def setup build_app + add_to_env_config("development", "config.active_support.deprecation = :stderr") require "rails/all" super end diff --git a/railties/test/application/rake/routes_test.rb b/railties/test/application/rake/routes_test.rb new file mode 100644 index 0000000000..9879d1f047 --- /dev/null +++ b/railties/test/application/rake/routes_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeRoutesTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup :build_app + teardown :teardown_app + + test "`rake routes` outputs routes" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + end + RUBY + + assert_equal <<~MESSAGE, run_rake_routes + Prefix Verb URI Pattern Controller#Action + cart GET /cart(.:format) cart#show + rails_amazon_inbound_emails POST /rails/action_mailbox/amazon/inbound_emails(.:format) action_mailbox/ingresses/amazon/inbound_emails#create + rails_mandrill_inbound_emails POST /rails/action_mailbox/mandrill/inbound_emails(.:format) action_mailbox/ingresses/mandrill/inbound_emails#create + rails_postmark_inbound_emails POST /rails/action_mailbox/postmark/inbound_emails(.:format) action_mailbox/ingresses/postmark/inbound_emails#create + rails_relay_inbound_emails POST /rails/action_mailbox/relay/inbound_emails(.:format) action_mailbox/ingresses/relay/inbound_emails#create + rails_sendgrid_inbound_emails POST /rails/action_mailbox/sendgrid/inbound_emails(.:format) action_mailbox/ingresses/sendgrid/inbound_emails#create + rails_mailgun_inbound_emails POST /rails/action_mailbox/mailgun/inbound_emails/mime(.:format) action_mailbox/ingresses/mailgun/inbound_emails#create + rails_conductor_inbound_emails GET /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#index + POST /rails/conductor/action_mailbox/inbound_emails(.:format) rails/conductor/action_mailbox/inbound_emails#create + new_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/new(.:format) rails/conductor/action_mailbox/inbound_emails#new + edit_rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id/edit(.:format) rails/conductor/action_mailbox/inbound_emails#edit + rails_conductor_inbound_email GET /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#show + PATCH /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update + PUT /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#update + DELETE /rails/conductor/action_mailbox/inbound_emails/:id(.:format) rails/conductor/action_mailbox/inbound_emails#destroy + rails_conductor_inbound_email_reroute POST /rails/conductor/action_mailbox/:inbound_email_id/reroute(.:format) rails/conductor/action_mailbox/reroutes#create + rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show + rails_blob_representation GET /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show + rails_disk_service GET /rails/active_storage/disk/:encoded_key/*filename(.:format) active_storage/disk#show + update_rails_disk_service PUT /rails/active_storage/disk/:encoded_token(.:format) active_storage/disk#update + rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create + MESSAGE + end + + test "`rake routes` outputs a deprecation warning" do + add_to_env_config("development", "config.active_support.deprecation = :stderr") + + stderr = capture(:stderr) { run_rake_routes } + assert_match(/DEPRECATION WARNING: Using `bin\/rake routes` is deprecated and will be removed in Rails 6.1/, stderr) + end + + private + def run_rake_routes + Dir.chdir(app_path) { `bin/rake routes` } + end + end + end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 1522a2bbc5..fe56e3d076 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -118,7 +118,7 @@ module ApplicationTests end def test_code_statistics_sanity - assert_match "Code LOC: 25 Test LOC: 0 Code to Test Ratio: 1:0.0", + assert_match "Code LOC: 32 Test LOC: 0 Code to Test Ratio: 1:0.0", rails("stats") end @@ -145,8 +145,8 @@ module ApplicationTests # loading a specific fixture rails "db:fixtures:load", "FIXTURES=products" - assert_equal 2, ::AppTemplate::Application::Product.count - assert_equal 0, ::AppTemplate::Application::User.count + assert_equal 2, Product.count + assert_equal 0, User.count end def test_loading_only_yml_fixtures @@ -160,7 +160,10 @@ module ApplicationTests def test_scaffold_tests_pass_by_default rails "generate", "scaffold", "user", "username:string", "password:string" - with_rails_env("test") { rails("db:migrate") } + with_rails_env("test") do + rails("db:migrate") + rails("webpacker:compile") + end output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) @@ -189,7 +192,10 @@ module ApplicationTests rails "generate", "model", "Product" rails "generate", "model", "Cart" rails "generate", "scaffold", "LineItems", "product:references", "cart:belongs_to" - with_rails_env("test") { rails("db:migrate") } + with_rails_env("test") do + rails("db:migrate") + rails("webpacker:compile") + end output = rails("test") assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb index 8f5f48c281..dfb9540093 100644 --- a/railties/test/application/runner_test.rb +++ b/railties/test/application/runner_test.rb @@ -105,6 +105,14 @@ module ApplicationTests assert_match "development", rails("runner", "puts Rails.env") end + def test_environment_option + assert_match "production", rails("runner", "-e", "production", "puts Rails.env") + end + + def test_environment_option_is_properly_expanded + assert_match "production", rails("runner", "-e", "prod", "puts Rails.env") + end + def test_runner_detects_syntax_errors output = rails("runner", "puts 'hello world", allow_failure: true) assert_not_predicate $?, :success? diff --git a/railties/test/application/server_test.rb b/railties/test/application/server_test.rb index 92b991dd05..5fe1b4e6e7 100644 --- a/railties/test/application/server_test.rb +++ b/railties/test/application/server_test.rb @@ -18,20 +18,6 @@ module ApplicationTests teardown_app end - test "deprecate support of older `config.ru`" do - remove_file "config.ru" - app_file "config.ru", <<-RUBY - require_relative 'config/environment' - run AppTemplate::Application - RUBY - - server = Rails::Server.new(config: "#{app_path}/config.ru") - server.app - - log = File.read(Rails.application.config.paths["log"].first) - assert_match(/DEPRECATION WARNING: Using `Rails::Application` subclass to start the server is deprecated/, log) - end - test "restart rails server with custom pid file path" do skip "PTY unavailable" unless available_pty? @@ -40,17 +26,17 @@ module ApplicationTests f.puts "require 'bundler/setup'" end - master, slave = PTY.open + primary, replica = PTY.open pid = nil - begin - pid = Process.spawn("#{app_path}/bin/rails server -P tmp/dummy.pid", in: slave, out: slave, err: slave) - assert_output("Listening", master) + Bundler.with_original_env do + pid = Process.spawn("bin/rails server -b localhost -P tmp/dummy.pid", chdir: app_path, in: replica, out: replica, err: replica) + assert_output("Listening", primary) rails("restart") - assert_output("Restarting", master) - assert_output("Inherited", master) + assert_output("Restarting", primary) + assert_output("tcp://localhost:3000", primary) ensure kill(pid) if pid end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 8e5ccf94cc..0bdd2b314d 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -16,7 +16,7 @@ module ApplicationTests teardown_app end - def test_run_via_backwardscompatibility + def test_run_via_backwards_compatibility require "minitest/rails_plugin" assert_nothing_raised do @@ -98,6 +98,17 @@ module ApplicationTests end end + def test_run_channels + create_test_file :channels, "foo_channel" + create_test_file :channels, "bar_channel" + + rails("test:channels").tap do |output| + assert_match "FooChannelTest", output + assert_match "BarChannelTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + def test_run_controllers create_test_file :controllers, "foo_controller" create_test_file :controllers, "bar_controller" @@ -131,6 +142,18 @@ module ApplicationTests end end + def test_run_mailboxes + create_test_file :mailboxes, "foo_mailbox" + create_test_file :mailboxes, "bar_mailbox" + create_test_file :models, "foo" + + rails("test:mailboxes").tap do |output| + assert_match "FooMailboxTest", output + assert_match "BarMailboxTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + def test_run_functionals create_test_file :mailers, "foo_mailer" create_test_file :controllers, "bar_controller" @@ -155,11 +178,11 @@ module ApplicationTests end def test_run_all_suites - suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs] + suites = [:models, :helpers, :unit, :channels, :controllers, :mailers, :functional, :integration, :jobs, :mailboxes] suites.each { |suite| create_test_file suite, "foo_#{suite}" } run_test_command("") .tap do |output| suites.each { |suite| assert_match "Foo#{suite.to_s.camelize}Test", output } - assert_match "8 runs, 8 assertions, 0 failures", output + assert_match "10 runs, 10 assertions, 0 failures", output end end @@ -504,7 +527,7 @@ module ApplicationTests create_test_file :models, "post", pass: false, print: false output = run_test_command("test/models/post_test.rb") - expect = %r{Running:\n\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nbin/rails test test/models/post_test.rb:4\n\n\n\n} + expect = %r{Running:\n\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nrails test test/models/post_test.rb:4\n\n\n\n} assert_match expect, output end @@ -523,25 +546,84 @@ module ApplicationTests end def test_run_in_parallel_with_processes + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + file_name = create_parallel_processes_test_file + app_file "db/schema.rb", <<-RUBY + ActiveRecord::Schema.define(version: 1) do + create_table :users do |t| + t.string :name + end + end + RUBY + output = run_test_command(file_name) assert_match %r{Finished in.*\n2 runs, 2 assertions}, output + assert_no_match "create_table(:users)", output end def test_run_in_parallel_with_threads - app_path("/test/test_helper.rb") do |file_name| - file = File.read(file_name) - file.sub!(/parallelize\(([^\)]*)\)/, "parallelize(\\1, with: :threads)") - File.write(file_name, file) - end + exercise_parallelization_regardless_of_machine_core_count(with: :threads) file_name = create_parallel_threads_test_file + app_file "db/schema.rb", <<-RUBY + ActiveRecord::Schema.define(version: 1) do + create_table :users do |t| + t.string :name + end + end + RUBY + output = run_test_command(file_name) assert_match %r{Finished in.*\n2 runs, 2 assertions}, output + assert_no_match "create_table(:users)", output + end + + def test_run_in_parallel_with_unmarshable_exception + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + + file = app_file "test/fail_test.rb", <<-RUBY + require "test_helper" + class FailTest < ActiveSupport::TestCase + class BadError < StandardError + def initialize + super + @proc = ->{ } + end + end + + test "fail" do + raise BadError + assert true + end + end + RUBY + + output = run_test_command(file) + + assert_match "DRb::DRbRemoteError: FailTest::BadError", output + assert_match "1 runs, 0 assertions, 0 failures, 1 errors", output + end + + def test_run_in_parallel_with_unknown_object + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + + create_scaffold + + app_file "config/environments/test.rb", <<-RUBY + Rails.application.configure do + config.action_controller.allow_forgery_protection = true + config.action_dispatch.show_exceptions = false + end + RUBY + + output = run_test_command("-n test_should_create_user") + + assert_match "ActionController::InvalidAuthenticityToken", output end def test_raise_error_when_specified_file_does_not_exist @@ -553,7 +635,7 @@ module ApplicationTests create_test_file :models, "account" create_test_file :models, "post", pass: false # This specifically verifies TEST for backwards compatibility with rake test - # as bin/rails test already supports running tests from a single file more cleanly. + # as `rails test` already supports running tests from a single file more cleanly. output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` } assert_match "PostTest", output, "passing TEST= should run selected test" @@ -660,6 +742,7 @@ module ApplicationTests def test_reset_sessions_before_rollback_on_system_tests app_file "test/system/reset_session_before_rollback_test.rb", <<-RUBY require "application_system_test_case" + require "selenium/webdriver" class ResetSessionBeforeRollbackTest < ApplicationSystemTestCase def teardown_fixtures @@ -685,6 +768,32 @@ module ApplicationTests end end + def test_reset_sessions_on_failed_system_test_screenshot + app_file "test/system/reset_sessions_on_failed_system_test_screenshot_test.rb", <<~RUBY + require "application_system_test_case" + require "selenium/webdriver" + + class ResetSessionsOnFailedSystemTestScreenshotTest < ApplicationSystemTestCase + ActionDispatch::SystemTestCase.class_eval do + def take_failed_screenshot + raise Capybara::CapybaraError + end + end + + Capybara.instance_eval do + def reset_sessions! + puts "Capybara.reset_sessions! called" + end + end + + test "dummy" do + end + end + RUBY + output = run_test_command("test/system/reset_sessions_on_failed_system_test_screenshot_test.rb") + assert_match "Capybara.reset_sessions! called", output + end + def test_system_tests_are_not_run_with_the_default_test_command app_file "test/system/dummy_test.rb", <<-RUBY require "application_system_test_case" @@ -719,6 +828,7 @@ module ApplicationTests def test_system_tests_are_run_through_rake_test_when_given_in_TEST app_file "test/system/dummy_test.rb", <<-RUBY require "application_system_test_case" + require "selenium/webdriver" class DummyTest < ApplicationSystemTestCase test "something" do @@ -885,6 +995,14 @@ module ApplicationTests RUBY end + def exercise_parallelization_regardless_of_machine_core_count(with:) + app_path("test/test_helper.rb") do |file_name| + file = File.read(file_name) + file.sub!(/parallelize\(([^\)]*)\)/, "parallelize(workers: 2, with: :#{with})") + File.write(file_name, file) + end + end + def create_env_test app_file "test/unit/env_test.rb", <<-RUBY require 'test_helper' diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index fb43bebfbe..83e63718df 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -234,7 +234,7 @@ module ApplicationTests # TODO: would be nice if we could detect the schema change automatically. # For now, the user has to synchronize the schema manually. - # This test-case serves as a reminder for this use-case. + # This test case serves as a reminder for this use case. test "manually synchronize test schema after rollback" do output = rails("generate", "model", "user", "name:string") version = output.match(/(\d+)_create_users\.rb/)[1] diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index f22b9fda3d..dc737089f6 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -19,6 +19,8 @@ module ApplicationTests config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log config.eager_load = false + config.hosts << proc { true } + config.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" end Rails.application.initialize! diff --git a/railties/test/application/zeitwerk_integration_test.rb b/railties/test/application/zeitwerk_integration_test.rb new file mode 100644 index 0000000000..b248459e47 --- /dev/null +++ b/railties/test/application/zeitwerk_integration_test.rb @@ -0,0 +1,371 @@ +# frozen_string_literal: true + +require "isolation/abstract_unit" +require "active_support/dependencies/zeitwerk_integration" + +class ZeitwerkIntegrationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def boot(env = "development") + app(env) + end + + def teardown + teardown_app + end + + def deps + ActiveSupport::Dependencies + end + + def decorated? + deps.singleton_class < deps::ZeitwerkIntegration::Decorations + end + + test "ActiveSupport::Dependencies is decorated by default" do + boot + + assert decorated? + assert Rails.autoloaders.zeitwerk_enabled? + assert_instance_of Zeitwerk::Loader, Rails.autoloaders.main + assert_instance_of Zeitwerk::Loader, Rails.autoloaders.once + assert_equal [Rails.autoloaders.main, Rails.autoloaders.once], Rails.autoloaders.to_a + end + + test "ActiveSupport::Dependencies is not decorated in classic mode" do + add_to_config "config.autoloader = :classic" + boot + + assert_not decorated? + assert_not Rails.autoloaders.zeitwerk_enabled? + assert_nil Rails.autoloaders.main + assert_nil Rails.autoloaders.once + assert_equal 0, Rails.autoloaders.count + end + + test "autoloaders inflect with Active Support" do + app_file "config/initializers/inflections.rb", <<-RUBY + ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym 'RESTful' + end + RUBY + + app_file "app/controllers/restful_controller.rb", <<-RUBY + class RESTfulController < ApplicationController + end + RUBY + + boot + + basename = "restful_controller" + abspath = "#{Rails.root}/app/controllers/#{basename}.rb" + camelized = "RESTfulController" + + Rails.autoloaders.each do |autoloader| + assert_equal camelized, autoloader.inflector.camelize(basename, abspath) + end + + assert RESTfulController + end + + test "constantize returns the value stored in the constant" do + app_file "app/models/admin/user.rb", "class Admin::User; end" + boot + + assert_same Admin::User, deps.constantize("Admin::User") + end + + test "constantize raises if the constant is unknown" do + boot + + assert_raises(NameError) { deps.constantize("Admin") } + end + + test "safe_constantize returns the value stored in the constant" do + app_file "app/models/admin/user.rb", "class Admin::User; end" + boot + + assert_same Admin::User, deps.safe_constantize("Admin::User") + end + + test "safe_constantize returns nil for unknown constants" do + boot + + assert_nil deps.safe_constantize("Admin") + end + + test "to_unload? says if a constant would be unloaded (main)" do + app_file "app/models/user.rb", "class User; end" + app_file "app/models/post.rb", "class Post; end" + boot + + assert Post + assert deps.to_unload?("Post") + assert_not deps.to_unload?("User") + end + + test "to_unload? says if a constant would be unloaded (once)" do + add_to_config 'config.autoload_once_paths << "#{Rails.root}/extras"' + app_file "extras/foo.rb", "class Foo; end" + app_file "extras/bar.rb", "class Bar; end" + boot + + assert Foo + assert_not deps.to_unload?("Foo") + assert_not deps.to_unload?("Bar") + end + + test "to_unload? says if a constant would be unloaded (reloading disabled)" do + app_file "app/models/user.rb", "class User; end" + app_file "app/models/post.rb", "class Post; end" + boot("production") + + assert Post + assert_not deps.to_unload?("Post") + assert_not deps.to_unload?("User") + end + + test "eager loading loads the application code" do + $zeitwerk_integration_test_user = false + $zeitwerk_integration_test_post = false + + app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true" + app_file "app/models/post.rb", "class Post; end; $zeitwerk_integration_test_post = true" + + boot("production") + + assert $zeitwerk_integration_test_user + assert $zeitwerk_integration_test_post + end + + test "reloading is enabled if config.cache_classes is false" do + boot + + assert Rails.autoloaders.main.reloading_enabled? + assert_not Rails.autoloaders.once.reloading_enabled? + end + + test "reloading is disabled if config.cache_classes is true" do + boot("production") + + assert_not Rails.autoloaders.main.reloading_enabled? + assert_not Rails.autoloaders.once.reloading_enabled? + end + + test "eager loading loads code in engines" do + $test_blog_engine_eager_loaded = false + + engine("blog") do |bukkit| + bukkit.write("lib/blog.rb", "class BlogEngine < Rails::Engine; end") + bukkit.write("app/models/post.rb", "Post = $test_blog_engine_eager_loaded = true") + end + + boot("production") + + assert $test_blog_engine_eager_loaded + end + + test "eager loading loads anything managed by Zeitwerk" do + $zeitwerk_integration_test_user = false + app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true" + + $zeitwerk_integration_test_extras = false + app_dir "extras" + app_file "extras/webhook_hacks.rb", "WebhookHacks = 1; $zeitwerk_integration_test_extras = true" + + require "zeitwerk" + autoloader = Zeitwerk::Loader.new + autoloader.push_dir("#{app_path}/extras") + autoloader.setup + + boot("production") + + assert $zeitwerk_integration_test_user + assert $zeitwerk_integration_test_extras + end + + test "autoload directories not present in eager load paths are not eager loaded" do + $zeitwerk_integration_test_user = false + app_file "app/models/user.rb", "class User; end; $zeitwerk_integration_test_user = true" + + $zeitwerk_integration_test_lib = false + app_dir "lib" + app_file "lib/webhook_hacks.rb", "WebhookHacks = 1; $zeitwerk_integration_test_lib = true" + + $zeitwerk_integration_test_extras = false + app_dir "extras" + app_file "extras/websocket_hacks.rb", "WebsocketHacks = 1; $zeitwerk_integration_test_extras = true" + + add_to_config "config.autoload_paths << '#{app_path}/lib'" + add_to_config "config.autoload_once_paths << '#{app_path}/extras'" + + boot("production") + + assert $zeitwerk_integration_test_user + assert_not $zeitwerk_integration_test_lib + assert_not $zeitwerk_integration_test_extras + + assert WebhookHacks + assert WebsocketHacks + + assert $zeitwerk_integration_test_lib + assert $zeitwerk_integration_test_extras + end + + test "autoload_paths are set as root dirs of main, and in the same order" do + boot + + existing_autoload_paths = deps.autoload_paths.select { |dir| File.directory?(dir) } + assert_equal existing_autoload_paths, Rails.autoloaders.main.dirs + end + + test "autoload_once_paths go to the once autoloader, and in the same order" do + extras = %w(e1 e2 e3) + extras.each do |extra| + app_dir extra + add_to_config %(config.autoload_once_paths << "\#{Rails.root}/#{extra}") + end + + boot + + extras = extras.map { |extra| "#{app_path}/#{extra}" } + extras.each do |extra| + assert_not_includes Rails.autoloaders.main.dirs, extra + end + assert_equal extras, Rails.autoloaders.once.dirs + end + + test "clear reloads the main autoloader, and does not reload the once one" do + boot + + $zeitwerk_integration_reload_test = [] + + main_autoloader = Rails.autoloaders.main + def main_autoloader.reload + $zeitwerk_integration_reload_test << :main_autoloader + super + end + + once_autoloader = Rails.autoloaders.once + def once_autoloader.reload + $zeitwerk_integration_reload_test << :once_autoloader + super + end + + ActiveSupport::Dependencies.clear + + assert_equal %i(main_autoloader), $zeitwerk_integration_reload_test + end + + test "verbose = true sets the dependencies logger if present" do + boot + + logger = Logger.new(File::NULL) + ActiveSupport::Dependencies.logger = logger + ActiveSupport::Dependencies.verbose = true + + Rails.autoloaders.each do |autoloader| + assert_same logger, autoloader.logger + end + end + + test "verbose = true sets the Rails logger as fallback" do + boot + + ActiveSupport::Dependencies.verbose = true + + Rails.autoloaders.each do |autoloader| + assert_same Rails.logger, autoloader.logger + end + end + + test "verbose = false sets loggers to nil" do + boot + + ActiveSupport::Dependencies.verbose = true + Rails.autoloaders.each do |autoloader| + assert autoloader.logger + end + + ActiveSupport::Dependencies.verbose = false + Rails.autoloaders.each do |autoloader| + assert_nil autoloader.logger + end + end + + test "unhooks" do + boot + + assert_equal Module, Module.method(:const_missing).owner + assert_equal :no_op, deps.unhook! + end + + test "autoloaders.logger=" do + boot + + logger = ->(_msg) { } + Rails.autoloaders.logger = logger + + Rails.autoloaders.each do |autoloader| + assert_same logger, autoloader.logger + end + + Rails.autoloaders.logger = Rails.logger + + Rails.autoloaders.each do |autoloader| + assert_same Rails.logger, autoloader.logger + end + + Rails.autoloaders.logger = nil + + Rails.autoloaders.each do |autoloader| + assert_nil autoloader.logger + end + end + + # This is here because to guarantee classic mode works as always, Zeitwerk + # integration does not touch anything in classic. The descendants tracker is a + # very small one-liner exception. We leave its main test suite untouched, and + # add some minimal safety net here. + # + # When time passes, things are going to be reorganized (famous last words). + test "descendants tracker" do + class ::ZeitwerkDTIntegrationTestRoot + extend ActiveSupport::DescendantsTracker + end + class ::ZeitwerkDTIntegrationTestChild < ::ZeitwerkDTIntegrationTestRoot; end + class ::ZeitwerkDTIntegrationTestGrandchild < ::ZeitwerkDTIntegrationTestChild; end + + begin + app_file "app/models/user.rb", "class User < ZeitwerkDTIntegrationTestRoot; end" + app_file "app/models/post.rb", "class Post < ZeitwerkDTIntegrationTestRoot; end" + app_file "app/models/tutorial.rb", "class Tutorial < Post; end" + boot + + assert User + assert Tutorial + + direct_descendants = [ZeitwerkDTIntegrationTestChild, User, Post].to_set + assert_equal direct_descendants, ZeitwerkDTIntegrationTestRoot.direct_descendants.to_set + + descendants = direct_descendants.merge([ZeitwerkDTIntegrationTestGrandchild, Tutorial]) + assert_equal descendants, ZeitwerkDTIntegrationTestRoot.descendants.to_set + + ActiveSupport::DescendantsTracker.clear + + direct_descendants = [ZeitwerkDTIntegrationTestChild].to_set + assert_equal direct_descendants, ZeitwerkDTIntegrationTestRoot.direct_descendants.to_set + + descendants = direct_descendants.merge([ZeitwerkDTIntegrationTestGrandchild]) + assert_equal descendants, ZeitwerkDTIntegrationTestRoot.descendants.to_set + ensure + Object.send(:remove_const, :ZeitwerkDTIntegrationTestRoot) + Object.send(:remove_const, :ZeitwerkDTIntegrationTestChild) + Object.send(:remove_const, :ZeitwerkDTIntegrationTestGrandchild) + end + end +end |