diff options
Diffstat (limited to 'railties/test/application')
39 files changed, 8505 insertions, 0 deletions
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb new file mode 100644 index 0000000000..8b83784ed6 --- /dev/null +++ b/railties/test/application/asset_debugging_test.rb @@ -0,0 +1,73 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class AssetDebuggingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + build_app(initializers: true) + end + + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" + app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/posts', to: "posts#index" + end + RUBY + + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + end + RUBY + + ENV["RAILS_ENV"] = "production" + + boot_rails + end + + def teardown + teardown_app + end + + # FIXME: shush Sass warning spam, not relevant to testing Railties + def get(*) + Kernel.silence_warnings { super } + end + + test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do + # config.assets.debug and config.assets.compile are false for production environment + ENV["RAILS_ENV"] = "production" + output = Dir.chdir(app_path){ `bin/rake assets:precompile --trace 2>&1` } + assert $?.success?, output + + # Load app env + app "production" + + class ::PostsController < ActionController::Base ; end + + # the debug_assets params isn't used if compile is off + get '/posts?debug_assets=true' + assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body) + assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) + end + + test "assets are served with sourcemaps when compile is true and debug_assets params is true" do + add_to_env_config "production", "config.assets.compile = true" + + # Load app env + app "production" + + class ::PostsController < ActionController::Base ; end + + get '/posts?debug_assets=true' + assert_match(/<script src="\/assets\/application(\.debug)?-([0-z]+)\.js"><\/script>/, last_response.body) + end + end +end diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb new file mode 100644 index 0000000000..18882e1855 --- /dev/null +++ b/railties/test/application/assets_test.rb @@ -0,0 +1,504 @@ +require 'isolation/abstract_unit' +require 'rack/test' +require 'active_support/json' + +module ApplicationTests + class AssetsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app(initializers: true) + boot_rails + end + + def teardown + teardown_app + end + + def precompile!(env = nil) + with_env env.to_h do + quietly do + precompile_task = "bin/rake assets:precompile --trace 2>&1" + output = Dir.chdir(app_path) { %x[ #{precompile_task} ] } + assert $?.success?, output + output + end + end + end + + def with_env(env) + env.each { |k, v| ENV[k.to_s] = v } + yield + ensure + env.each_key { |k| ENV.delete k.to_s } + end + + def clean_assets! + quietly do + assert Dir.chdir(app_path) { system('bin/rake assets:clobber') } + end + end + + def assert_file_exists(filename) + globbed = Dir[filename] + assert globbed.one?, "Found #{globbed.size} files matching #{filename}. All files in the directory: #{Dir.entries(File.dirname(filename)).inspect}" + end + + def assert_no_file_exists(filename) + assert !File.exist?(filename), "#{filename} does exist" + end + + test "assets routes have higher priority" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/javascripts/demo.js.erb", "a = <%= image_path('rails.png').inspect %>;" + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '*path', to: lambda { |env| [200, { "Content-Type" => "text/html" }, ["Not an asset"]] } + end + RUBY + + add_to_env_config "development", "config.assets.digest = false" + + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + require "#{app_path}/config/environment" + end + + get "/assets/demo.js" + 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 !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();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + + precompile! + + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();\n", File.read(file) + end + end + + def test_precompile_does_not_hit_the_database + app_file "app/assets/config/manifest.js", "//= link_tree ../javascripts" + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/javascripts/foo/application.js", "alert();" + app_file "app/controllers/users_controller.rb", <<-eoruby + class UsersController < ApplicationController; end + eoruby + app_file "app/models/user.rb", <<-eoruby + class User < ActiveRecord::Base; raise 'should not be reached'; end + eoruby + + precompile! \ + RAILS_ENV: 'production', + DATABASE_URL: 'postgresql://baduser:badpass@127.0.0.1/dbname' + + files = Dir["#{app_path}/public/assets/application-*.js"] + files << Dir["#{app_path}/public/assets/foo/application-*.js"].first + files.each do |file| + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();".strip, File.read(file).strip + end + end + + test "precompile application.js and application.css and all other non JS/CSS files" do + app_file "app/assets/javascripts/application.js", "alert();" + app_file "app/assets/stylesheets/application.css", "body{}" + + app_file "app/assets/javascripts/someapplication.js", "alert();" + app_file "app/assets/stylesheets/someapplication.css", "body{}" + + app_file "app/assets/javascripts/something.min.js", "alert();" + app_file "app/assets/stylesheets/something.min.css", "body{}" + + app_file "app/assets/javascripts/something.else.js.erb", "alert();" + app_file "app/assets/stylesheets/something.else.css.erb", "body{}" + + images_should_compile = ["a.png", "happyface.png", "happy_face.png", "happy.face.png", + "happy-face.png", "happy.happy_face.png", "happy_happy.face.png", + "happy.happy.face.png", "-happy.png", "-happy.face.png", + "_happy.face.png", "_happy.png"] + + images_should_compile.each do |filename| + app_file "app/assets/images/#{filename}", "happy" + end + + precompile! + + images_should_compile = ["a-*.png", "happyface-*.png", "happy_face-*.png", "happy.face-*.png", + "happy-face-*.png", "happy.happy_face-*.png", "happy_happy.face-*.png", + "happy.happy.face-*.png", "-happy-*.png", "-happy.face-*.png", + "_happy.face-*.png", "_happy-*.png"] + + images_should_compile.each do |filename| + assert_file_exists("#{app_path}/public/assets/#{filename}") + end + + assert_file_exists("#{app_path}/public/assets/application-*.js") + assert_file_exists("#{app_path}/public/assets/application-*.css") + + assert_no_file_exists("#{app_path}/public/assets/someapplication-*.js") + assert_no_file_exists("#{app_path}/public/assets/someapplication-*.css") + + assert_no_file_exists("#{app_path}/public/assets/something.min-*.js") + assert_no_file_exists("#{app_path}/public/assets/something.min-*.css") + + assert_no_file_exists("#{app_path}/public/assets/something.else-*.js") + assert_no_file_exists("#{app_path}/public/assets/something.else-*.css") + end + + test "precompile something.js for directory containing index file" do + add_to_config "config.assets.precompile = [ 'something.js' ]" + app_file "app/assets/javascripts/something/index.js.erb", "alert();" + + precompile! + + assert_file_exists("#{app_path}/public/assets/something-*.js") + end + + test 'precompile use assets defined in app env config' do + add_to_env_config 'production', 'config.assets.precompile = [ "something.js" ]' + app_file 'app/assets/javascripts/something.js.erb', 'alert();' + + precompile! RAILS_ENV: 'production' + + assert_file_exists("#{app_path}/public/assets/something-*.js") + end + + test 'precompile use assets defined in app config and reassigned in app env config' do + add_to_config 'config.assets.precompile = [ "something_manifest.js" ]' + add_to_env_config 'production', 'config.assets.precompile += [ "another_manifest.js" ]' + + app_file 'app/assets/config/something_manifest.js', '//= link something.js' + app_file 'app/assets/config/another_manifest.js', '//= link another.js' + + app_file 'app/assets/javascripts/something.js.erb', 'alert();' + app_file 'app/assets/javascripts/another.js.erb', 'alert();' + + precompile! RAILS_ENV: 'production' + + assert_file_exists("#{app_path}/public/assets/something_manifest-*.js") + assert_file_exists("#{app_path}/public/assets/something-*.js") + assert_file_exists("#{app_path}/public/assets/another_manifest-*.js") + assert_file_exists("#{app_path}/public/assets/another-*.js") + end + + test "asset pipeline should use a Sprockets::CachedEnvironment when config.assets.digest is true" do + add_to_config "config.action_controller.perform_caching = false" + add_to_env_config "production", "config.assets.compile = true" + + # Load app env + app "production" + + assert_equal Sprockets::CachedEnvironment, Rails.application.assets.class + end + + test "precompile creates a manifest file with all the assets listed" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "<%= asset_path('rails.png') %>" + app_file "app/assets/javascripts/application.js", "alert();" + + precompile! + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert_match(/application-([0-z]+)\.js/, assets["assets"]["application.js"]) + assert_match(/application-([0-z]+)\.css/, assets["assets"]["application.css"]) + end + + test "the manifest file should be saved by default in the same assets folder" do + app_file "app/assets/javascripts/application.js", "alert();" + add_to_config "config.assets.prefix = '/x'" + + precompile! + + manifest = Dir["#{app_path}/public/x/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert_match(/application-([0-z]+)\.js/, assets["assets"]["application.js"]) + end + + test "assets do not require any assets group gem when manifest file is present" do + app_file "app/assets/javascripts/application.js", "alert();" + add_to_env_config "production", "config.public_file_server.enabled = true" + + precompile! RAILS_ENV: 'production' + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + asset_path = assets["assets"]["application.js"] + + # Load app env + app "production" + + # Checking if Uglifier is defined we can know if Sprockets was reached or not + assert !defined?(Uglifier) + get "/assets/#{asset_path}" + assert_match "alert()", last_response.body + assert !defined?(Uglifier) + end + + test "precompile properly refers files referenced with asset_path" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" + + precompile! + + file = Dir["#{app_path}/public/assets/application-*.css"].first + assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file)) + end + + test "precompile shouldn't use the digests present in manifest.json" do + app_file "app/assets/images/rails.png", "notactuallyapng" + + app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" + + precompile! RAILS_ENV: 'production' + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + asset_path = assets["assets"]["application.css"] + + app_file "app/assets/images/rails.png", "p { url: change }" + + precompile! + + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert_not_equal asset_path, assets["assets"]["application.css"] + end + + test "precompile appends the md5 hash to files referenced with asset_path and run in production with digest true" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "p { background-image: url(<%= asset_path('rails.png') %>) }" + + precompile! RAILS_ENV: 'production' + + file = Dir["#{app_path}/public/assets/application-*.css"].first + assert_match(/\/assets\/rails-([0-z]+)\.png/, File.read(file)) + end + + test "precompile should handle utf8 filenames" do + filename = "レイルズ.png" + app_file "app/assets/images/#{filename}", "not an image really" + app_file "app/assets/config/manifest.js", "//= link_tree ../images" + add_to_config "config.assets.precompile = %w(manifest.js)" + + precompile! + + manifest = Dir["#{app_path}/public/assets/.sprockets-manifest-*.json"].first + assets = ActiveSupport::JSON.decode(File.read(manifest)) + assert asset_path = assets["assets"].find { |(k, _)| k && k =~ /.png/ }[1] + + # Load app env + app "development" + + get "/assets/#{URI.parser.escape(asset_path)}" + assert_match "not an image really", last_response.body + assert_file_exists("#{app_path}/public/assets/#{asset_path}") + end + + test "assets are cleaned up properly" do + app_file "public/assets/application.js", "alert();" + app_file "public/assets/application.css", "a { color: green; }" + app_file "public/assets/subdir/broken.png", "not really an image file" + + clean_assets! + + files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/development/*", + "#{app_path}/tmp/cache/assets/test/*", "#{app_path}/tmp/cache/assets/production/*"] + assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" + end + + test "assets routes are not drawn when compilation is disabled" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + add_to_config "config.assets.compile = false" + + # Load app env + app "production" + + get "/assets/demo.js" + assert_equal 404, last_response.status + end + + test "does not stream session cookies back" do + app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/omg', :to => "omg#index" + end + RUBY + + add_to_env_config "development", "config.assets.digest = false" + + # Load app env + app "development" + + class ::OmgController < ActionController::Base + def index + flash[:cool_story] = true + render text: "ok" + end + end + + get "/omg" + assert_equal 'ok', last_response.body + + get "/assets/demo.js" + assert_match "alert()", last_response.body + assert_equal nil, last_response.headers["Set-Cookie"] + end + + test "files in any assets/ directories are not added to Sprockets" do + %w[app lib vendor].each do |dir| + app_file "#{dir}/assets/#{dir}_test.erb", "testing" + end + + app_file "app/assets/javascripts/demo.js", "alert();" + + add_to_env_config "development", "config.assets.digest = false" + + # Load app env + app "development" + + get "/assets/demo.js" + assert_match "alert();", last_response.body + assert_equal 200, last_response.status + end + + test "assets are concatenated when debug is off and compile is off either if debug_assets param is provided" do + app_with_assets_in_view + + # config.assets.debug and config.assets.compile are false for production environment + precompile! RAILS_ENV: 'production' + + # Load app env + app "production" + + class ::PostsController < ActionController::Base ; end + + # the debug_assets params isn't used if compile is off + get '/posts?debug_assets=true' + assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body) + assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body) + end + + test "assets can access model information when precompiling" do + app_file "app/models/post.rb", "class Post; end" + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js.erb", "<%= Post.name %>" + + precompile! + + assert_equal "Post\n;\n", File.read(Dir["#{app_path}/public/assets/application-*.js"].first) + end + + test "initialization on the assets group should set assets_dir" do + require "#{app_path}/config/application" + Rails.application.initialize!(:assets) + assert_not_nil Rails.application.config.action_controller.assets_dir + end + + test "enhancements to assets:precompile should only run once" do + app_file "lib/tasks/enhance.rake", "Rake::Task['assets:precompile'].enhance { puts 'enhancement' }" + output = precompile! + assert_equal 1, output.scan("enhancement").size + end + + test "digested assets are not mistakenly removed" do + app_file "app/assets/application.js", "alert();" + 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" + end + + test "digested assets are removed from configured path" do + app_file "public/production_assets/application.js", "alert();" + add_to_env_config "production", "config.assets.prefix = 'production_assets'" + + ENV["RAILS_ENV"] = nil + + clean_assets! + + files = Dir["#{app_path}/public/production_assets/application-*.js"] + 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 + app_with_assets_in_view + add_to_config "config.asset_host = 'example.com'" + add_to_env_config "development", "config.assets.digest = false" + + # Load app env + app "development" + + class ::PostsController < ActionController::Base; end + + get '/posts', {}, {'HTTPS'=>'off'} + assert_match('src="http://example.com/assets/application.debug.js', last_response.body) + get '/posts', {}, {'HTTPS'=>'on'} + assert_match('src="https://example.com/assets/application.debug.js', last_response.body) + end + + 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}" + add_to_config "config.asset_host = 'example.com'" + add_to_env_config "development", "config.assets.digest = false" + + precompile! + + assert_match "src='//example.com/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/image_loader-*.js"].first) + end + + test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do + ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri" + app_file "app/assets/images/rails.png", "notreallyapng" + app_file "app/assets/javascripts/app.js.erb", "var src='<%= image_path('rails.png') %>';" + add_to_config "config.assets.precompile = %w{rails.png app.js}" + add_to_env_config "development", "config.assets.digest = false" + + precompile! + + assert_match "src='/sub/uri/assets/rails.png'", File.read(Dir["#{app_path}/public/assets/app-*.js"].first) + end + + private + + def app_with_assets_in_view + app_file "app/assets/javascripts/application.js", "//= require_tree ." + app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }" + app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>" + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/posts', :to => "posts#index" + end + RUBY + end + end +end diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb new file mode 100644 index 0000000000..1bdced02e9 --- /dev/null +++ b/railties/test/application/bin_setup_test.rb @@ -0,0 +1,54 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class BinSetupTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + def test_bin_setup + Dir.chdir(app_path) do + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:articles) {} + end + RUBY + + list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + File.write("log/my.log", "zomg!") + + assert_equal '[]', list_tables.call + assert_equal 5, File.size("log/my.log") + assert_not File.exist?("tmp/restart.txt") + `bin/setup 2>&1` + assert_equal 0, File.size("log/my.log") + assert_equal '["articles", "schema_migrations"]', list_tables.call + assert File.exist?("tmp/restart.txt") + end + end + + def test_bin_setup_output + Dir.chdir(app_path) do + app_file 'db/schema.rb', "" + + output = `bin/setup 2>&1` + assert_equal(<<-OUTPUT, output) +== Installing dependencies == +The Gemfile's dependencies are satisfied + +== Preparing database == + +== Removing old logs and tempfiles == + +== Restarting application server == + OUTPUT + end + end + end +end diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb new file mode 100644 index 0000000000..28b3b2f2d6 --- /dev/null +++ b/railties/test/application/configuration/custom_test.rb @@ -0,0 +1,54 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + module ConfigurationTests + class CustomTest < ActiveSupport::TestCase + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + test 'access custom configuration point' do + add_to_config <<-RUBY + config.x.payment_processing.schedule = :daily + config.x.payment_processing.retries = 3 + config.x.super_debugger = true + config.x.hyper_debugger = false + config.x.nil_debugger = nil + RUBY + require_environment + + x = Rails.configuration.x + assert_equal :daily, x.payment_processing.schedule + assert_equal 3, x.payment_processing.retries + assert_equal true, x.super_debugger + assert_equal false, x.hyper_debugger + assert_equal nil, x.nil_debugger + assert_nil x.i_do_not_exist.zomg + end + + private + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def copy_app + FileUtils.cp_r(app_path, new_app) + end + + def app + @app ||= Rails.application + end + + def require_environment + require "#{app_path}/config/environment" + end + end + end +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb new file mode 100644 index 0000000000..49f63d5d31 --- /dev/null +++ b/railties/test/application/configuration_test.rb @@ -0,0 +1,1437 @@ +require "isolation/abstract_unit" +require 'rack/test' +require 'env_helpers' + +class ::MyMailInterceptor + def self.delivering_email(email); email; end +end + +class ::MyOtherMailInterceptor < ::MyMailInterceptor; end + +class ::MyPreviewMailInterceptor + def self.previewing_email(email); email; end +end + +class ::MyOtherPreviewMailInterceptor < ::MyPreviewMailInterceptor; end + +class ::MyMailObserver + def self.delivered_email(email); email; end +end + +class ::MyOtherMailObserver < ::MyMailObserver; end + +module ApplicationTests + class ConfigurationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + include EnvHelpers + + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def copy_app + FileUtils.cp_r(app_path, new_app) + end + + def app(env = 'development') + @app ||= begin + ENV['RAILS_ENV'] = env + + # FIXME: shush Sass warning spam, not relevant to testing Railties + Kernel.silence_warnings do + require "#{app_path}/config/environment" + end + + Rails.application + ensure + ENV.delete 'RAILS_ENV' + end + end + + def setup + build_app + boot_rails + supress_default_config + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + def supress_default_config + FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__") + end + + def restore_default_config + FileUtils.rm_rf("#{app_path}/config/environments") + FileUtils.mv("#{app_path}/config/__environments__", "#{app_path}/config/environments") + end + + test "Rails.env does not set the RAILS_ENV environment variable which would leak out into rake tasks" do + require "rails" + + switch_env "RAILS_ENV", nil do + Rails.env = "development" + assert_equal "development", Rails.env + assert_nil ENV['RAILS_ENV'] + end + end + + test "lib dir is on LOAD_PATH during config" do + app_file 'lib/my_logger.rb', <<-RUBY + require "logger" + class MyLogger < ::Logger + end + RUBY + add_to_top_of_config <<-RUBY + require 'my_logger' + config.logger = MyLogger.new STDOUT + RUBY + + app 'development' + + assert_equal 'MyLogger', Rails.application.config.logger.class.name + end + + test "a renders exception on pending migration" do + add_to_config <<-RUBY + config.active_record.migration_error = :page_load + config.consider_all_requests_local = true + config.action_dispatch.show_exceptions = true + RUBY + + app_file 'db/migrate/20140708012246_create_user.rb', <<-RUBY + class CreateUser < ActiveRecord::Migration + def change + create_table :users + end + end + RUBY + + app 'development' + + ActiveRecord::Migrator.migrations_paths = ["#{app_path}/db/migrate"] + + begin + get "/foo" + assert_equal 500, last_response.status + assert_match "ActiveRecord::PendingMigrationError", last_response.body + ensure + ActiveRecord::Migrator.migrations_paths = nil + end + end + + test "Rails.groups returns available groups" do + require "rails" + + Rails.env = "development" + assert_equal [:default, "development"], Rails.groups + assert_equal [:default, "development", :assets], Rails.groups(assets: [:development]) + assert_equal [:default, "development", :another, :assets], Rails.groups(:another, assets: %w(development)) + + Rails.env = "test" + assert_equal [:default, "test"], Rails.groups(assets: [:development]) + + ENV["RAILS_GROUPS"] = "javascripts,stylesheets" + assert_equal [:default, "test", "javascripts", "stylesheets"], Rails.groups + end + + test "Rails.application is nil until app is initialized" do + require 'rails' + assert_nil Rails.application + app 'development' + assert_equal AppTemplate::Application.instance, Rails.application + end + + test "Rails.application responds to all instance methods" do + app 'development' + assert_respond_to Rails.application, :routes_reloader + assert_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader + end + + test "Rails::Application responds to paths" do + app 'development' + assert_respond_to AppTemplate::Application, :paths + assert_equal ["#{app_path}/app/views"], AppTemplate::Application.paths["app/views"].expanded + end + + test "the application root is set correctly" do + app 'development' + assert_equal Pathname.new(app_path), Rails.application.root + end + + test "the application root can be seen from the application singleton" do + app 'development' + assert_equal Pathname.new(app_path), AppTemplate::Application.root + end + + test "the application root can be set" do + copy_app + add_to_config <<-RUBY + config.root = '#{new_app}' + RUBY + + use_frameworks [] + + app 'development' + + assert_equal Pathname.new(new_app), Rails.application.root + end + + test "the application root is Dir.pwd if there is no config.ru" do + File.delete("#{app_path}/config.ru") + + use_frameworks [] + + Dir.chdir("#{app_path}") do + app 'development' + assert_equal Pathname.new("#{app_path}"), Rails.application.root + end + end + + test "Rails.root should be a Pathname" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + app 'development' + + assert_instance_of Pathname, Rails.root + end + + test "Rails.public_path should be a Pathname" do + add_to_config <<-RUBY + config.paths["public"] = "somewhere" + RUBY + + app 'development' + + assert_instance_of Pathname, Rails.public_path + end + + test "initialize an eager loaded, cache classes app" do + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + app 'development' + + assert_equal :require, ActiveSupport::Dependencies.mechanism + end + + test "application is always added to eager_load namespaces" do + app 'development' + assert_includes Rails.application.config.eager_load_namespaces, AppTemplate::Application + end + + test "the application can be eager loaded even when there are no frameworks" do + FileUtils.rm_rf("#{app_path}/config/environments") + add_to_config <<-RUBY + config.eager_load = true + config.cache_classes = true + RUBY + + use_frameworks [] + + assert_nothing_raised do + app 'development' + end + end + + test "filter_parameters should be able to set via config.filter_parameters" do + add_to_config <<-RUBY + config.filter_parameters += [ :foo, 'bar', lambda { |key, value| + value = value.reverse if key =~ /baz/ + }] + RUBY + + assert_nothing_raised do + app 'development' + end + end + + test "filter_parameters should be able to set via config.filter_parameters in an initializer" do + app_file 'config/initializers/filter_parameters_logging.rb', <<-RUBY + Rails.application.config.filter_parameters += [ :password, :foo, 'bar' ] + RUBY + + app 'development' + + assert_equal [:password, :foo, 'bar'], Rails.application.env_config['action_dispatch.parameter_filter'] + end + + test "config.to_prepare is forwarded to ActionDispatch" do + $prepared = false + + add_to_config <<-RUBY + config.to_prepare do + $prepared = true + end + RUBY + + assert !$prepared + + app 'development' + + get "/" + assert $prepared + end + + def assert_utf8 + assert_equal Encoding::UTF_8, Encoding.default_external + assert_equal Encoding::UTF_8, Encoding.default_internal + end + + test "skipping config.encoding still results in 'utf-8' as the default" do + app 'development' + assert_utf8 + end + + test "config.encoding sets the default encoding" do + add_to_config <<-RUBY + config.encoding = "utf-8" + RUBY + + app 'development' + assert_utf8 + end + + test "config.paths.public sets Rails.public_path" do + add_to_config <<-RUBY + config.paths["public"] = "somewhere" + RUBY + + app 'development' + assert_equal Pathname.new(app_path).join("somewhere"), Rails.public_path + end + + test "In production mode, config.public_file_server.enabled is off by default" do + restore_default_config + + with_rails_env "production" do + app 'production' + assert_not app.config.public_file_server.enabled + end + end + + test "In production mode, config.public_file_server.enabled is enabled when RAILS_SERVE_STATIC_FILES is set" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_SERVE_STATIC_FILES", "1" do + app 'production' + assert app.config.public_file_server.enabled + end + end + end + + test "In production mode, config.public_file_server.enabled is disabled when RAILS_SERVE_STATIC_FILES is blank" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_SERVE_STATIC_FILES", " " do + app 'production' + assert_not app.config.public_file_server.enabled + end + end + end + + test "config.serve_static_files is deprecated" do + make_basic_app do |application| + assert_deprecated do + application.config.serve_static_files = true + end + + assert application.config.public_file_server.enabled + end + end + + test "config.static_cache_control is deprecated" do + make_basic_app do |application| + assert_deprecated do + application.config.static_cache_control = "public, max-age=60" + end + + assert_equal application.config.static_cache_control, "public, max-age=60" + end + end + + test "Use key_generator when secret_key_base is set" do + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled + end + + class ::OmgController < ActionController::Base + def index + cookies.signed[:some_key] = "some_value" + render text: cookies[:some_key] + end + end + + get "/" + + secret = app.key_generator.generate_key('signed cookie') + verifier = ActiveSupport::MessageVerifier.new(secret) + assert_equal 'some_value', verifier.verify(last_response.body) + end + + test "application verifier can be used in the entire application" do + make_basic_app do |application| + application.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled + end + + message = app.message_verifier(:sensitive_value).generate("some_value") + + assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message) + + secret = app.key_generator.generate_key('sensitive_value') + verifier = ActiveSupport::MessageVerifier.new(secret) + assert_equal 'some_value', verifier.verify(message) + end + + test "application message verifier can be used when the key_generator is ActiveSupport::LegacyKeyGenerator" 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: + YAML + + app 'development' + + assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator + assert_equal app.env_config['action_dispatch.key_generator'].class, ActiveSupport::LegacyKeyGenerator + message = app.message_verifier(:sensitive_value).generate("some_value") + assert_equal 'some_value', Rails.application.message_verifier(:sensitive_value).verify(message) + end + + test "warns when secrets.secret_key_base is blank and config.secret_token is set" 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: + YAML + + app 'development' + + assert_deprecated(/You didn't set `secret_key_base`./) do + app.env_config + end + end + + test "raise when secrets.secret_key_base is not a type of string" do + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: 123 + YAML + + app 'development' + + assert_raise(ArgumentError) do + app.key_generator + 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.secrets.secret_key_base = 'b3c631c314c0bbca50c1b2843150fe33' + application.config.session_store :disabled + end + + default_verifier = app.message_verifier(:sensitive_value) + text_verifier = app.message_verifier(:text) + + message = text_verifier.generate('some_value') + + assert_equal 'some_value', text_verifier.verify(message) + assert_raises ActiveSupport::MessageVerifier::InvalidSignature do + default_verifier.verify(message) + end + + assert_equal default_verifier.object_id, app.message_verifier(:sensitive_value).object_id + assert_not_equal default_verifier.object_id, text_verifier.object_id + end + + test "secrets.secret_key_base is used when config/secrets.yml is present" do + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 + YAML + + app 'development' + assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base + end + + test "secret_key_base is copied from config to secrets when not set" do + remove_file "config/secrets.yml" + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c3" + RUBY + + app 'development' + 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: + secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 + aws_access_key_id: myamazonaccesskeyid + aws_secret_access_key: myamazonsecretaccesskey + YAML + + app 'development' + + assert_equal 'myamazonaccesskeyid', app.secrets.aws_access_key_id + assert_equal 'myamazonsecretaccesskey', app.secrets.aws_secret_access_key + end + + test "blank config/secrets.yml does not crash the loading process" do + app_file 'config/secrets.yml', <<-YAML + YAML + + app 'development' + + assert_nil app.secrets.not_defined + end + + test "config.secret_key_base over-writes a blank secrets.secret_key_base" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_key_base = "iaminallyoursecretkeybase" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + 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.config.secret_token = "b3c631c314c0bbca50c1b2843150fe33" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_equal 'b3c631c314c0bbca50c1b2843150fe33', app.config.secret_token + assert_equal nil, app.secrets.secret_key_base + assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator + end + + test "uses ActiveSupport::LegacyKeyGenerator with config.secret_token as app.key_generator when secrets.secret_key_base is blank" do + app_file 'config/initializers/secret_token.rb', <<-RUBY + Rails.application.config.secret_token = "" + RUBY + app_file 'config/secrets.yml', <<-YAML + development: + secret_key_base: + YAML + + app 'development' + + assert_equal '', app.config.secret_token + assert_equal nil, app.secrets.secret_key_base + assert_raise ArgumentError, /\AA secret is required/ do + app.key_generator + end + end + + test "protect from forgery is the default in a new app" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + render inline: "<%= csrf_meta_tags %>" + end + end + + get "/" + assert last_response.body =~ /csrf\-param/ + end + + test "default form builder specified as a string" do + app_file 'config/initializers/form_builder.rb', <<-RUBY + class CustomFormBuilder < ActionView::Helpers::FormBuilder + def text_field(attribute, *args) + label(attribute) + super(attribute, *args) + end + end + Rails.configuration.action_view.default_form_builder = "CustomFormBuilder" + RUBY + + app_file 'app/models/post.rb', <<-RUBY + class Post + include ActiveModel::Model + attr_accessor :name + end + RUBY + + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def index + render inline: "<%= begin; form_for(Post.new) {|f| f.text_field(:name)}; rescue => e; e.to_s; end %>" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app 'development' + + get "/posts" + assert_match(/label/, last_response.body) + end + + test "default method for update can be changed" do + app_file 'app/models/post.rb', <<-RUBY + class Post + include ActiveModel::Model + def to_key; [1]; end + def persisted?; true; end + end + RUBY + + token = "cf50faa3fe97702ca1ae" + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def show + render inline: "<%= begin; form_for(Post.new) {}; rescue => e; e.to_s; end %>" + end + + def update + render text: "update" + end + + private + + def form_authenticity_token; token; end # stub the authenticy token + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app 'development' + + params = { authenticity_token: token } + + get "/posts/1" + assert_match(/patch/, last_response.body) + + patch "/posts/1", params + assert_match(/update/, last_response.body) + + patch "/posts/1", params + assert_equal 200, last_response.status + + put "/posts/1", params + assert_match(/update/, last_response.body) + + put "/posts/1", params + assert_equal 200, last_response.status + end + + test "request forgery token param can be changed" do + make_basic_app do |application| + application.config.action_controller.request_forgery_protection_token = '_xsrf_token_here' + end + + class ::OmgController < ActionController::Base + def index + render inline: "<%= csrf_meta_tags %>" + end + end + + get "/" + assert_match "_xsrf_token_here", last_response.body + end + + test "sets ActionDispatch.test_app" do + make_basic_app + assert_equal Rails.application, ActionDispatch.test_app + end + + test "sets ActionDispatch::Response.default_charset" do + make_basic_app do |application| + application.config.action_dispatch.default_charset = "utf-16" + end + + assert_equal "utf-16", ActionDispatch::Response.default_charset + end + + test "registers interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.interceptors = MyMailInterceptor + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors") + end + + test "registers multiple interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.interceptors = [MyMailInterceptor, "MyOtherMailInterceptor"] + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailInterceptor, ::MyOtherMailInterceptor], ::Mail.send(:class_variable_get, "@@delivery_interceptors") + end + + test "registers preview interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.preview_interceptors = MyPreviewMailInterceptor + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [ActionMailer::InlinePreviewInterceptor, ::MyPreviewMailInterceptor], ActionMailer::Base.preview_interceptors + end + + test "registers multiple preview interceptors with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.preview_interceptors = [MyPreviewMailInterceptor, "MyOtherPreviewMailInterceptor"] + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [ActionMailer::InlinePreviewInterceptor, MyPreviewMailInterceptor, MyOtherPreviewMailInterceptor], ActionMailer::Base.preview_interceptors + end + + test "default preview interceptor can be removed" do + app_file 'config/initializers/preview_interceptors.rb', <<-RUBY + ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor) + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [], ActionMailer::Base.preview_interceptors + end + + test "registers observers with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.observers = MyMailObserver + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers") + end + + test "registers multiple observers with ActionMailer" do + add_to_config <<-RUBY + config.action_mailer.observers = [MyMailObserver, "MyOtherMailObserver"] + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal [::MyMailObserver, ::MyOtherMailObserver], ::Mail.send(:class_variable_get, "@@delivery_notification_observers") + end + + test "allows setting the queue name for the ActionMailer::DeliveryJob" do + add_to_config <<-RUBY + config.action_mailer.deliver_later_queue_name = 'test_default' + RUBY + + app 'development' + + require "mail" + _ = ActionMailer::Base + + assert_equal 'test_default', ActionMailer::Base.send(:class_variable_get, "@@deliver_later_queue_name") + end + + test "valid timezone is setup correctly" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.time_zone = "Wellington" + RUBY + + app 'development' + + assert_equal "Wellington", Rails.application.config.time_zone + end + + test "raises when an invalid timezone is defined in the config" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.time_zone = "That big hill over yonder hill" + RUBY + + assert_raise(ArgumentError) do + app 'development' + end + end + + test "valid beginning of week is setup correctly" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.beginning_of_week = :wednesday + RUBY + + app 'development' + + assert_equal :wednesday, Rails.application.config.beginning_of_week + end + + test "raises when an invalid beginning of week is defined in the config" do + add_to_config <<-RUBY + config.root = "#{app_path}" + config.beginning_of_week = :invalid + RUBY + + assert_raise(ArgumentError) do + app 'development' + end + end + + test "config.action_view.cache_template_loading with cache_classes default" do + add_to_config "config.cache_classes = true" + + app 'development' + require 'action_view/base' + + assert_equal true, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading without cache_classes default" do + add_to_config "config.cache_classes = false" + + app 'development' + require 'action_view/base' + + assert_equal false, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading = false" do + add_to_config <<-RUBY + config.cache_classes = true + config.action_view.cache_template_loading = false + RUBY + + app 'development' + require 'action_view/base' + + assert_equal false, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading = true" do + add_to_config <<-RUBY + config.cache_classes = false + config.action_view.cache_template_loading = true + RUBY + + app 'development' + require 'action_view/base' + + assert_equal true, ActionView::Resolver.caching? + end + + test "config.action_view.cache_template_loading with cache_classes in an environment" do + build_app(initializers: true) + add_to_env_config "development", "config.cache_classes = false" + + # These requires are to emulate an engine loading Action View before the application + require 'action_view' + require 'action_view/railtie' + require 'action_view/base' + + app 'development' + + assert_equal false, ActionView::Resolver.caching? + end + + test "config.action_dispatch.show_exceptions is sent in env" do + make_basic_app do |application| + application.config.action_dispatch.show_exceptions = true + end + + class ::OmgController < ActionController::Base + def index + render text: env["action_dispatch.show_exceptions"] + end + end + + get "/" + assert_equal 'true', last_response.body + end + + test "config.action_controller.wrap_parameters is set in ActionController::Base" do + app_file 'config/initializers/wrap_parameters.rb', <<-RUBY + ActionController::Base.wrap_parameters format: [:json] + RUBY + + app_file 'app/models/post.rb', <<-RUBY + class Post + def self.attribute_names + %w(title) + end + end + RUBY + + app_file 'app/controllers/application_controller.rb', <<-RUBY + class ApplicationController < ActionController::Base + protect_from_forgery with: :reset_session # as we are testing API here + end + RUBY + + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ApplicationController + def create + render text: params[:post].inspect + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + RUBY + + app 'development' + + post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json" + assert_equal '{"title"=>"foo"}', last_response.body + end + + test "config.action_controller.permit_all_parameters = true" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params[:post].permitted? ? "permitted" : "forbidden" + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.permit_all_parameters = true + RUBY + + app 'development' + + post "/posts", {post: {"title" =>"zomg"}} + assert_equal 'permitted', last_response.body + end + + test "config.action_controller.action_on_unpermitted_parameters = :raise" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params.require(:post).permit(:name) + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + app 'development' + + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + + post "/posts", {post: {"title" =>"zomg"}} + assert_match "We're sorry, but something went wrong", last_response.body + end + + test "config.action_controller.always_permitted_parameters are: controller, action by default" do + app 'development' + assert_equal %w(controller action), ActionController::Parameters.always_permitted_parameters + end + + test "config.action_controller.always_permitted_parameters = ['controller', 'action', 'format']" do + add_to_config <<-RUBY + config.action_controller.always_permitted_parameters = %w( controller action format ) + RUBY + + app 'development' + + assert_equal %w( controller action format ), ActionController::Parameters.always_permitted_parameters + end + + test "config.action_controller.always_permitted_parameters = ['controller','action','format'] does not raise exeception" do + app_file 'app/controllers/posts_controller.rb', <<-RUBY + class PostsController < ActionController::Base + def create + render text: params.permit(post: [:title]) + end + end + RUBY + + add_to_config <<-RUBY + routes.prepend do + resources :posts + end + config.action_controller.always_permitted_parameters = %w( controller action format ) + config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + app 'development' + + assert_equal :raise, ActionController::Parameters.action_on_unpermitted_parameters + + post "/posts", {post: {"title" =>"zomg"}, format: "json"} + assert_equal 200, last_response.status + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by default on development" do + app 'development' + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is :log by default on test" do + app 'test' + + assert_equal :log, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters is false by default on production" do + app 'production' + + assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_dispatch.ignore_accept_header" do + make_basic_app do |application| + application.config.action_dispatch.ignore_accept_header = true + end + + class ::OmgController < ActionController::Base + def index + respond_to do |format| + format.html { render text: "HTML" } + format.xml { render text: "XML" } + end + end + end + + get "/", {}, "HTTP_ACCEPT" => "application/xml" + assert_equal 'HTML', last_response.body + + get "/", { format: :xml }, "HTTP_ACCEPT" => "application/xml" + assert_equal 'XML', last_response.body + end + + test "Rails.application#env_config exists and include some existing parameters" do + make_basic_app + + assert_respond_to app, :env_config + assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters + assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions + assert_equal app.env_config['action_dispatch.logger'], Rails.logger + assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner + assert_equal app.env_config['action_dispatch.key_generator'], Rails.application.key_generator + end + + test "config.colorize_logging default is true" do + make_basic_app + assert app.config.colorize_logging + 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 + end + end + + test "config.session_store with :active_record_store without activerecord-session_store gem" do + assert_raise RuntimeError, /activerecord-session_store/ do + make_basic_app do |application| + application.config.session_store :active_record_store + end + end + end + + test "config.log_level with custom logger" do + make_basic_app do |application| + application.config.logger = Logger.new(STDOUT) + application.config.log_level = :info + end + assert_equal Logger::INFO, Rails.logger.level + end + + test "respond_to? accepts include_private" do + make_basic_app + + assert_not Rails.configuration.respond_to?(:method_missing) + assert Rails.configuration.respond_to?(:method_missing, true) + end + + test "config.active_record.dump_schema_after_migration is false on production" do + build_app + + app 'production' + + assert_not ActiveRecord::Base.dump_schema_after_migration + end + + test "config.active_record.dump_schema_after_migration is true by default on development" do + app 'development' + + assert ActiveRecord::Base.dump_schema_after_migration + end + + test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do + make_basic_app do |application| + application.config.annotations.register_extensions("coffee") do |tag| + /#\s*(#{tag}):?\s*(.*)$/ + end + end + + assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/] + end + + test "rake_tasks block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + rake_tasks do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + + Rails.application.load_tasks + assert Rails.configuration.ran_block + end + + test "generators block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + generators do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + Rails.application.load_generators + assert Rails.configuration.ran_block + end + + test "console block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + console do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + Rails.application.load_console + assert Rails.configuration.ran_block + end + + test "runner block works at instance level" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.ran_block = false + + runner do + config.ran_block = true + end + end + RUBY + + app 'development' + assert_not Rails.configuration.ran_block + + Rails.application.load_runner + assert Rails.configuration.ran_block + end + + test "loading the first existing database configuration available" do + app_file 'config/environments/development.rb', <<-RUBY + + Rails.application.configure do + config.paths.add 'config/database', with: 'config/nonexistent.yml' + config.paths['config/database'] << 'config/database.yml' + end + RUBY + + app 'development' + + assert_kind_of Hash, Rails.application.config.database_configuration + end + + test 'raises with proper error message if no database configuration found' do + FileUtils.rm("#{app_path}/config/database.yml") + app 'development' + err = assert_raises RuntimeError do + Rails.application.config.database_configuration + end + assert_match 'config/database', err.message + end + + test 'config.action_mailer.show_previews defaults to true in development' do + app 'development' + + assert Rails.application.config.action_mailer.show_previews + end + + test 'config.action_mailer.show_previews defaults to false in production' do + app 'production' + + assert_equal false, Rails.application.config.action_mailer.show_previews + end + + test 'config.action_mailer.show_previews can be set in the configuration file' do + add_to_config <<-RUBY + config.action_mailer.show_previews = true + RUBY + + app 'production' + + assert_equal true, Rails.application.config.action_mailer.show_previews + end + + test "config_for loads custom configuration from yaml files" do + app_file 'config/custom.yml', <<-RUBY + development: + key: 'custom key' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + 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 + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + exception = assert_raises(RuntimeError) do + app 'development' + end + + assert_equal "Could not load configuration. No such file - #{app_path}/config/custom.yml", exception.message + end + + test "config_for without the environment configured returns an empty hash" do + app_file 'config/custom.yml', <<-RUBY + test: + key: 'custom key' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal({}, Rails.application.config.my_custom_config) + end + + test "config_for with empty file returns an empty hash" do + app_file 'config/custom.yml', <<-RUBY + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal({}, Rails.application.config.my_custom_config) + end + + test "config_for containing ERB tags should evaluate" do + app_file 'config/custom.yml', <<-RUBY + development: + key: <%= 'custom key' %> + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + app 'development' + + assert_equal 'custom key', Rails.application.config.my_custom_config['key'] + end + + test "config_for with syntax error show a more descriptive exception" do + app_file 'config/custom.yml', <<-RUBY + development: + key: foo: + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom') + RUBY + + exception = assert_raises(RuntimeError) do + app 'development' + end + + assert_match 'YAML syntax error occurred while parsing', exception.message + end + + test "config_for allows overriding the environment" do + app_file 'config/custom.yml', <<-RUBY + test: + key: 'walrus' + production: + key: 'unicorn' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for('custom', env: 'production') + RUBY + require "#{app_path}/config/environment" + + assert_equal 'unicorn', Rails.application.config.my_custom_config['key'] + end + + test "api_only is false by default" do + app 'development' + refute Rails.application.config.api_only + end + + test "api_only generator config is set when api_only is set" do + add_to_config <<-RUBY + config.api_only = true + RUBY + app 'development' + + Rails.application.load_generators + assert Rails.configuration.api_only + end + + test "debug_exception_response_format is :api by default if only_api is enabled" do + add_to_config <<-RUBY + config.api_only = true + RUBY + app 'development' + + assert_equal :api, Rails.configuration.debug_exception_response_format + end + + test "debug_exception_response_format can be override" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + app_file 'config/environments/development.rb', <<-RUBY + Rails.application.configure do + config.debug_exception_response_format = :default + end + RUBY + + app 'development' + + assert_equal :default, Rails.configuration.debug_exception_response_format + end + end +end diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb new file mode 100644 index 0000000000..7bf123d12b --- /dev/null +++ b/railties/test/application/console_test.rb @@ -0,0 +1,165 @@ +require 'isolation/abstract_unit' + +class ConsoleTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def load_environment(sandbox = false) + require "#{rails_root}/config/environment" + Rails.application.sandbox = sandbox + Rails.application.load_console + end + + def irb_context + Object.new.extend(Rails::ConsoleMethods) + end + + def test_app_method_should_return_integration_session + TestHelpers::Rack.send :remove_method, :app + load_environment + console_session = irb_context.app + assert_instance_of ActionDispatch::Integration::Session, console_session + end + + def test_app_can_access_path_helper_method + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + load_environment + console_session = irb_context.app + assert_equal '/foo', console_session.foo_path + end + + def test_new_session_should_return_integration_session + load_environment + session = irb_context.new_session + assert_instance_of ActionDispatch::Integration::Session, session + end + + def test_reload_should_fire_preparation_and_cleanup_callbacks + load_environment + a = b = c = nil + + # TODO: These should be defined on the initializer + ActionDispatch::Reloader.to_cleanup { a = b = c = 1 } + ActionDispatch::Reloader.to_cleanup { b = c = 2 } + ActionDispatch::Reloader.to_prepare { c = 3 } + + # Hide Reloading... output + silence_stream(STDOUT) { irb_context.reload! } + + assert_equal 1, a + assert_equal 2, b + assert_equal 3, c + end + + def test_reload_should_reload_constants + app_file "app/models/user.rb", <<-MODEL + class User + attr_accessor :name + end + MODEL + + load_environment + assert User.new.respond_to?(:name) + + app_file "app/models/user.rb", <<-MODEL + class User + attr_accessor :name, :age + end + MODEL + + assert !User.new.respond_to?(:age) + silence_stream(STDOUT) { irb_context.reload! } + assert User.new.respond_to?(:age) + end + + def test_access_to_helpers + load_environment + helper = irb_context.helper + assert_not_nil helper + assert_instance_of ActionView::Base, helper + assert_equal 'Once upon a time in a world...', + helper.truncate('Once upon a time in a world far far away') + end +end + +begin + require "pty" +rescue LoadError +end + +class FullStackConsoleTest < ActiveSupport::TestCase + def setup + skip "PTY unavailable" unless defined?(PTY) && PTY.respond_to?(:open) + + build_app + app_file 'app/models/post.rb', <<-CODE + class Post < ActiveRecord::Base + end + CODE + system "#{app_path}/bin/rails runner 'Post.connection.create_table :posts'" + + @master, @slave = PTY.open + end + + def teardown + teardown_app + end + + def assert_output(expected, timeout = 1) + timeout = Time.now + timeout + + output = "" + until output.include?(expected) || Time.now > timeout + if IO.select([@master], [], [], 0.1) + output << @master.read(1) + end + end + + assert output.include?(expected), "#{expected.inspect} expected, but got:\n\n#{output}" + end + + def write_prompt(command, expected_output = nil) + @master.puts command + assert_output command + assert_output expected_output if expected_output + assert_output "> " + end + + def spawn_console + Process.spawn( + "#{app_path}/bin/rails console --sandbox", + in: @slave, out: @slave, err: @slave + ) + + assert_output "> ", 30 + end + + def test_sandbox + spawn_console + + write_prompt "Post.count", "=> 0" + write_prompt "Post.create" + write_prompt "Post.count", "=> 1" + @master.puts "quit" + + spawn_console + + write_prompt "Post.count", "=> 0" + write_prompt "Post.transaction { Post.create; raise }" + write_prompt "Post.count", "=> 0" + @master.puts "quit" + end +end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb new file mode 100644 index 0000000000..84cc6e120b --- /dev/null +++ b/railties/test/application/generators_test.rb @@ -0,0 +1,164 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class GeneratorsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def app_const + @app_const ||= Class.new(Rails::Application) + end + + def with_config + require "rails/all" + require "rails/generators" + yield app_const.config + end + + def with_bare_config + require "rails" + require "rails/generators" + yield app_const.config + end + + test "allow running plugin new generator inside Rails app directory" do + FileUtils.cd(rails_root){ `ruby bin/rails plugin new vendor/plugins/bukkits` } + assert File.exist?(File.join(rails_root, "vendor/plugins/bukkits/test/dummy/config/application.rb")) + end + + test "generators default values" do + with_bare_config do |c| + assert_equal(true, c.generators.colorize_logging) + assert_equal({}, c.generators.aliases) + assert_equal({}, c.generators.options) + assert_equal({}, c.generators.fallbacks) + end + end + + test "generators set rails options" do + with_bare_config do |c| + c.generators.orm = :data_mapper + c.generators.test_framework = :rspec + c.generators.helper = false + expected = { rails: { orm: :data_mapper, test_framework: :rspec, helper: false } } + assert_equal(expected, c.generators.options) + end + end + + test "generators set rails aliases" do + with_config do |c| + c.generators.aliases = { rails: { test_framework: "-w" } } + expected = { rails: { test_framework: "-w" } } + assert_equal expected, c.generators.aliases + end + end + + test "generators aliases, options, templates and fallbacks on initialization" do + add_to_config <<-RUBY + config.generators.rails aliases: { test_framework: "-w" } + config.generators.orm :data_mapper + config.generators.test_framework :rspec + config.generators.fallbacks[:shoulda] = :test_unit + config.generators.templates << "some/where" + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert_equal :rspec, Rails::Generators.options[:rails][:test_framework] + assert_equal "-w", Rails::Generators.aliases[:rails][:test_framework] + assert_equal Hash[shoulda: :test_unit], Rails::Generators.fallbacks + assert_equal ["some/where"], Rails::Generators.templates_path + end + + test "generators no color on initialization" do + add_to_config <<-RUBY + config.generators.colorize_logging = false + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert_equal Thor::Base.shell, Thor::Shell::Basic + end + + test "generators with hashes for options and aliases" do + with_bare_config do |c| + c.generators do |g| + g.orm :data_mapper, migration: false + g.plugin aliases: { generator: "-g" }, + generator: true + end + + expected = { + rails: { orm: :data_mapper }, + plugin: { generator: true }, + data_mapper: { migration: false } + } + + assert_equal expected, c.generators.options + assert_equal({ plugin: { generator: "-g" } }, c.generators.aliases) + end + end + + test "generators with string and hash for options should generate symbol keys" do + with_bare_config do |c| + c.generators do |g| + g.orm 'data_mapper', migration: false + end + + expected = { + rails: { orm: :data_mapper }, + data_mapper: { migration: false } + } + + assert_equal expected, c.generators.options + end + end + + test "api only generators hide assets, helper, js and css namespaces and set api option" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert Rails::Generators.hidden_namespaces.include?("assets") + assert Rails::Generators.hidden_namespaces.include?("helper") + assert Rails::Generators.hidden_namespaces.include?("js") + assert Rails::Generators.hidden_namespaces.include?("css") + assert Rails::Generators.options[:rails][:api] + assert_equal false, Rails::Generators.options[:rails][:assets] + assert_equal false, Rails::Generators.options[:rails][:helper] + assert_nil Rails::Generators.options[:rails][:template_engine] + end + + test "api only generators allow overriding generator options" do + add_to_config <<-RUBY + config.generators.helper = true + config.api_only = true + config.generators.template_engine = :my_template + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert Rails::Generators.options[:rails][:api] + assert Rails::Generators.options[:rails][:helper] + assert_equal :my_template, Rails::Generators.options[:rails][:template_engine] + end + end +end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb new file mode 100644 index 0000000000..13f3250f5b --- /dev/null +++ b/railties/test/application/initializers/frameworks_test.rb @@ -0,0 +1,269 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class FrameworksTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + # AC & AM + test "set load paths set only if action controller or action mailer are in use" do + assert_nothing_raised NameError do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + use_frameworks [] + require "#{app_path}/config/environment" + end + end + + test "sets action_controller and action_mailer load paths" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + + expanded_path = File.expand_path("app/views", app_path) + assert_equal expanded_path, ActionController::Base.view_paths[0].to_s + assert_equal expanded_path, ActionMailer::Base.view_paths[0].to_s + end + + 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" } + end + RUBY + + require "#{app_path}/config/environment" + assert_equal "test.rails", ActionMailer::Base.default_url_options[:host] + end + + test "Default to HTTPS for ActionMailer URLs when force_ssl is on" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.force_ssl = true + end + RUBY + + require "#{app_path}/config/environment" + assert_equal "https", ActionMailer::Base.default_url_options[:protocol] + end + + 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 + end + RUBY + + app_file "app/mailers/foo.rb", <<-RUBY + class Foo < ActionMailer::Base + def notify + end + end + RUBY + + require "#{app_path}/config/environment" + assert Foo.method_defined?(:foo_url) + assert Foo.method_defined?(:main_app) + end + + test "allows to not load all helpers for controllers" do + add_to_config "config.action_controller.include_all_helpers = false" + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::Base + end + RUBY + + app_file "app/controllers/foo_controller.rb", <<-RUBY + class FooController < ApplicationController + def included_helpers + render :inline => "<%= from_app_helper -%> <%= from_foo_helper %>" + end + + def not_included_helper + render :inline => "<%= respond_to?(:from_bar_helper) -%>" + end + end + RUBY + + app_file "app/helpers/application_helper.rb", <<-RUBY + module ApplicationHelper + def from_app_helper + "from_app_helper" + end + end + RUBY + + app_file "app/helpers/foo_helper.rb", <<-RUBY + module FooHelper + def from_foo_helper + "from_foo_helper" + end + end + RUBY + + app_file "app/helpers/bar_helper.rb", <<-RUBY + module BarHelper + def from_bar_helper + "from_bar_helper" + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/:controller(/:action)" + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + + get "/foo/included_helpers" + assert_equal "from_app_helper from_foo_helper", last_response.body + + get "/foo/not_included_helper" + assert_equal "false", last_response.body + end + + test "action_controller api executes using all the middleware stack" do + add_to_config "config.api_only = true" + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::API + end + RUBY + + app_file "app/controllers/omg_controller.rb", <<-RUBY + class OmgController < ApplicationController + def show + render json: { omg: 'omg' } + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/:controller(/:action)" + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + + get 'omg/show' + assert_equal '{"omg":"omg"}', last_response.body + end + + # AD + test "action_dispatch extensions are applied to ActionDispatch" do + add_to_config "config.action_dispatch.tld_length = 2" + require "#{app_path}/config/environment" + assert_equal 2, ActionDispatch::Http::URL.tld_length + end + + test "assignment config.encoding to default_charset" do + charset = 'Shift_JIS' + add_to_config "config.encoding = '#{charset}'" + require "#{app_path}/config/environment" + assert_equal charset, ActionDispatch::Response.default_charset + end + + # AS + test "if there's no config.active_support.bare, all of ActiveSupport is required" do + use_frameworks [] + require "#{app_path}/config/environment" + assert_nothing_raised { [1,2,3].sample } + end + + test "config.active_support.bare does not require all of ActiveSupport" do + add_to_config "config.active_support.bare = true" + + use_frameworks [] + + Dir.chdir("#{app_path}/app") do + require "#{app_path}/config/environment" + assert_raises(NoMethodError) { "hello".exclude? "lo" } + end + end + + # AR + test "active_record extensions are applied to ActiveRecord" do + add_to_config "config.active_record.table_name_prefix = 'tbl_'" + require "#{app_path}/config/environment" + assert_equal 'tbl_', ActiveRecord::Base.table_name_prefix + end + + test "database middleware doesn't initialize when activerecord is not in frameworks" do + use_frameworks [] + require "#{app_path}/config/environment" + assert_nil defined?(ActiveRecord::Base) + end + + test "use schema cache dump" do + Dir.chdir(app_path) do + `rails generate model post title:string; + bin/rake db:migrate db:schema:cache:dump` + end + require "#{app_path}/config/environment" + ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test. + assert ActiveRecord::Base.connection.schema_cache.tables("posts") + end + + test "expire schema cache dump" do + Dir.chdir(app_path) do + `rails generate model post title:string; + bin/rake db:migrate db:schema:cache:dump db:rollback` + end + silence_warnings { + require "#{app_path}/config/environment" + assert !ActiveRecord::Base.connection.schema_cache.tables("posts") + } + 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 + 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 + end + end +end diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb new file mode 100644 index 0000000000..b2cea0a8e1 --- /dev/null +++ b/railties/test/application/initializers/hooks_test.rb @@ -0,0 +1,90 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class HooksTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + test "load initializers" do + app_file "config/initializers/foo.rb", "$foo = true" + require "#{app_path}/config/environment" + assert $foo + end + + test "hooks block works correctly without eager_load (before_eager_load is not called)" do + add_to_config <<-RUBY + $initialization_callbacks = [] + config.root = "#{app_path}" + config.eager_load = false + config.before_configuration { $initialization_callbacks << 1 } + config.before_initialize { $initialization_callbacks << 2 } + config.before_eager_load { Boom } + config.after_initialize { $initialization_callbacks << 3 } + RUBY + + require "#{app_path}/config/environment" + assert_equal [1,2,3], $initialization_callbacks + end + + test "hooks block works correctly with eager_load" do + add_to_config <<-RUBY + $initialization_callbacks = [] + config.root = "#{app_path}" + config.eager_load = true + config.before_configuration { $initialization_callbacks << 1 } + config.before_initialize { $initialization_callbacks << 2 } + config.before_eager_load { $initialization_callbacks << 3 } + config.after_initialize { $initialization_callbacks << 4 } + RUBY + + require "#{app_path}/config/environment" + assert_equal [1,2,3,4], $initialization_callbacks + end + + test "after_initialize runs after frameworks have been initialized" do + $activerecord_configurations = nil + add_to_config <<-RUBY + config.after_initialize { $activerecord_configurations = ActiveRecord::Base.configurations } + RUBY + + require "#{app_path}/config/environment" + assert $activerecord_configurations + assert $activerecord_configurations['development'] + end + + test "after_initialize happens after to_prepare in development" do + $order = [] + add_to_config <<-RUBY + config.cache_classes = false + config.after_initialize { $order << :after_initialize } + config.to_prepare { $order << :to_prepare } + RUBY + + require "#{app_path}/config/environment" + assert_equal [:to_prepare, :after_initialize], $order + end + + test "after_initialize happens after to_prepare in production" do + $order = [] + add_to_config <<-RUBY + config.cache_classes = true + config.after_initialize { $order << :after_initialize } + config.to_prepare { $order << :to_prepare } + RUBY + + require "#{app_path}/config/application" + Rails.env.replace "production" + require "#{app_path}/config/environment" + assert_equal [:to_prepare, :after_initialize], $order + end + end +end diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb new file mode 100644 index 0000000000..ab7f29b0f2 --- /dev/null +++ b/railties/test/application/initializers/i18n_test.rb @@ -0,0 +1,289 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class I18nTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + require "rails/all" + end + + def teardown + teardown_app + end + + def load_app + require "#{app_path}/config/environment" + end + + def app + @app ||= Rails.application + end + + def assert_fallbacks(fallbacks) + fallbacks.each do |locale, expected| + actual = I18n.fallbacks[locale] + assert_equal expected, actual, "expected fallbacks for #{locale.inspect} to be #{expected.inspect}, but were #{actual.inspect}" + end + end + + def assert_no_fallbacks + assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + end + + # Locales + test "setting another default locale" do + add_to_config <<-RUBY + config.i18n.default_locale = :de + RUBY + + load_app + assert_equal :de, I18n.default_locale + end + + # Load paths + test "no config locales directory present should return empty load path" do + FileUtils.rm_rf "#{app_path}/config/locales" + load_app + assert_equal [], Rails.application.config.i18n.load_path + end + + test "locale files should be added to the load path" do + app_file "config/another_locale.yml", "en:\nfoo: ~" + + add_to_config <<-RUBY + config.i18n.load_path << config.root.join("config/another_locale.yml").to_s + RUBY + + load_app + assert_equal [ + "#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml" + ], Rails.application.config.i18n.load_path + + assert I18n.load_path.include?("#{app_path}/config/locales/en.yml") + assert I18n.load_path.include?("#{app_path}/config/another_locale.yml") + end + + test "load_path is populated before eager loaded models" do + add_to_config <<-RUBY + config.cache_classes = true + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'app/models/foo.rb', <<-RUBY + class Foo < ActiveRecord::Base + @foo = I18n.t(:foo) + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + assert_equal "1", last_response.body + end + + test "locales are reloaded if they change between requests" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + assert_equal "1", last_response.body + + # Wait a full second so we have time for changes to propagate + sleep(1) + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "2" + YAML + + get "/i18n" + assert_equal "2", last_response.body + end + + test "new locale files are loaded" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + assert_equal "1", last_response.body + + # Wait a full second so we have time for changes to propagate + sleep(1) + + remove_file "config/locales/en.yml" + app_file "config/locales/custom.en.yml", <<-YAML +en: + foo: "2" + YAML + + get "/i18n" + assert_equal "2", last_response.body + end + + test "I18n.load_path is reloaded" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file "config/locales/en.yml", <<-YAML +en: + foo: "1" + YAML + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/i18n', :to => lambda { |env| [200, {}, [I18n.load_path.inspect]] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + load_app + + get "/i18n" + + assert_match "en.yml", last_response.body + + # Wait a full second so we have time for changes to propagate + sleep(1) + + app_file "config/locales/fr.yml", <<-YAML +fr: + foo: "2" + YAML + + get "/i18n" + assert_match "fr.yml", last_response.body + assert_match "en.yml", last_response.body + end + + # Fallbacks + test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do + I18n.backend = Class.new(I18n::Backend::Simple).new + load_app + assert_no_fallbacks + end + + test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do + I18n::Railtie.config.i18n.fallbacks = true + load_app + assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_fallbacks de: [:de, :en] + end + + test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do + I18n::Railtie.config.i18n.fallbacks = true + I18n::Railtie.config.i18n.backend = Class.new(I18n::Backend::Simple).new + load_app + assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks) + assert_fallbacks de: [:de, :en] + end + + test "config.i18n.fallbacks.defaults = [:'en-US'] initializes fallbacks with en-US as a fallback default" do + I18n::Railtie.config.i18n.fallbacks.defaults = [:'en-US'] + load_app + assert_fallbacks de: [:de, :'en-US', :en] + end + + test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do + I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' } + load_app + assert_fallbacks ca: [:ca, :"es-ES", :es, :en] + end + + test "[shortcut] config.i18n.fallbacks = [:'en-US'] initializes fallbacks with en-US as a fallback default" do + I18n::Railtie.config.i18n.fallbacks = [:'en-US'] + load_app + assert_fallbacks de: [:de, :'en-US', :en] + end + + test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do + I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' } + load_app + assert_fallbacks ca: [:ca, :"es-ES", :es, :en] + end + + test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do + I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] + load_app + assert_fallbacks ca: [:ca, :"es-ES", :es, :'en-US', :en] + end + + test "disable config.i18n.enforce_available_locales" do + add_to_config <<-RUBY + config.i18n.enforce_available_locales = false + config.i18n.default_locale = :fr + RUBY + + load_app + assert_equal false, I18n.enforce_available_locales + + assert_nothing_raised do + I18n.locale = :es + end + end + + test "default config.i18n.enforce_available_locales does not override I18n.enforce_available_locales" do + I18n.enforce_available_locales = false + + add_to_config <<-RUBY + config.i18n.default_locale = :fr + RUBY + + load_app + assert_equal false, I18n.enforce_available_locales + + assert_nothing_raised do + I18n.locale = :es + end + end + end +end diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb new file mode 100644 index 0000000000..cd05956356 --- /dev/null +++ b/railties/test/application/initializers/load_path_test.rb @@ -0,0 +1,110 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class LoadPathTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + test "initializing an application adds the application paths to the load path" do + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + assert $:.include?("#{app_path}/app/models") + end + + test "initializing an application allows to load code on lib path inside application class definition" do + app_file "lib/foo.rb", <<-RUBY + module Foo; end + RUBY + + add_to_config <<-RUBY + require "foo" + raise "Expected Foo to be defined" unless defined?(Foo) + RUBY + + assert_nothing_raised do + require "#{app_path}/config/environment" + end + + assert $:.include?("#{app_path}/lib") + end + + test "initializing an application eager load any path under app" do + app_file "app/anything/foo.rb", <<-RUBY + module Foo; end + RUBY + + add_to_config <<-RUBY + config.root = "#{app_path}" + RUBY + + require "#{app_path}/config/environment" + assert Foo + end + + test "eager loading loads parent classes before children" do + app_file "lib/zoo.rb", <<-ZOO + class Zoo ; include ReptileHouse ; end + ZOO + + app_file "lib/zoo/reptile_house.rb", <<-ZOO + module Zoo::ReptileHouse ; end + ZOO + + add_to_config <<-RUBY + config.root = "#{app_path}" + config.eager_load_paths << "#{app_path}/lib" + RUBY + + require "#{app_path}/config/environment" + assert Zoo + end + + test "eager loading accepts Pathnames" do + app_file "lib/foo.rb", <<-RUBY + module Foo; end + RUBY + + add_to_config <<-RUBY + config.eager_load = true + config.eager_load_paths << Pathname.new("#{app_path}/lib") + RUBY + + require "#{app_path}/config/environment" + assert Foo + end + + test "load environment with global" do + $initialize_test_set_from_env = nil + app_file "config/environments/development.rb", <<-RUBY + $initialize_test_set_from_env = 'success' + Rails.application.configure do + config.cache_classes = true + config.time_zone = "Brasilia" + end + RUBY + + assert_nil $initialize_test_set_from_env + add_to_config <<-RUBY + config.root = "#{app_path}" + config.time_zone = "UTC" + RUBY + + require "#{app_path}/config/environment" + assert_equal "success", $initialize_test_set_from_env + assert Rails.application.config.cache_classes + assert_equal "Brasilia", Rails.application.config.time_zone + end + end +end diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb new file mode 100644 index 0000000000..95655b74cf --- /dev/null +++ b/railties/test/application/initializers/notifications_test.rb @@ -0,0 +1,56 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class NotificationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def instrument(*args, &block) + ActiveSupport::Notifications.instrument(*args, &block) + end + + def wait + ActiveSupport::Notifications.notifier.wait + end + + test "rails log_subscribers are added" do + add_to_config <<-RUBY + config.colorize_logging = false + RUBY + + require "#{app_path}/config/environment" + require "active_support/log_subscriber/test_helper" + + logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + ActiveRecord::Base.logger = logger + + # Mimic Active Record notifications + instrument "sql.active_record", name: "SQL", sql: "SHOW tables" + wait + + assert_equal 1, logger.logged(:debug).size + assert_match(/SHOW tables/, logger.logged(:debug).last) + end + + test 'rails load_config_initializer event is instrumented' do + app_file 'config/initializers/foo.rb', '' + + events = [] + callback = ->(*_) { events << _ } + ActiveSupport::Notifications.subscribed(callback, 'load_config_initializer.railties') do + app + end + + assert_equal %w[load_config_initializer.railties], events.map(&:first) + assert_includes events.first.last[:initializer], 'config/initializers/foo.rb' + end + end +end diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb new file mode 100644 index 0000000000..2106708c98 --- /dev/null +++ b/railties/test/application/loading_test.rb @@ -0,0 +1,372 @@ +require 'isolation/abstract_unit' + +class LoadingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "constants in app are autoloaded" do + app_file "app/models/post.rb", <<-MODEL + class Post < ActiveRecord::Base + validates_acceptance_of :title, accept: "omg" + end + MODEL + + require "#{rails_root}/config/environment" + setup_ar! + + p = Post.create(title: 'omg') + assert_equal 1, Post.count + assert_equal 'omg', p.title + p = Post.first + assert_equal 'omg', p.title + end + + test "concerns in app are autoloaded" do + app_file "app/controllers/concerns/trackable.rb", <<-CONCERN + module Trackable + end + CONCERN + + app_file "app/mailers/concerns/email_loggable.rb", <<-CONCERN + module EmailLoggable + end + CONCERN + + app_file "app/models/concerns/orderable.rb", <<-CONCERN + module Orderable + end + CONCERN + + app_file "app/validators/concerns/matchable.rb", <<-CONCERN + module Matchable + end + CONCERN + + require "#{rails_root}/config/environment" + + assert_nothing_raised(NameError) { Trackable } + assert_nothing_raised(NameError) { EmailLoggable } + assert_nothing_raised(NameError) { Orderable } + assert_nothing_raised(NameError) { Matchable } + end + + test "models without table do not panic on scope definitions when loaded" do + app_file "app/models/user.rb", <<-MODEL + class User < ActiveRecord::Base + default_scope { where(published: true) } + end + MODEL + + require "#{rails_root}/config/environment" + setup_ar! + + User + end + + test "load config/environments/environment before Bootstrap initializers" do + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.development_environment_loaded = true + end + RUBY + + add_to_config <<-RUBY + config.before_initialize do + config.loaded = config.development_environment_loaded + end + RUBY + + require "#{app_path}/config/environment" + assert ::Rails.application.config.loaded + end + + test "descendants loaded after framework initialization are cleaned on each request without cache classes" do + add_to_config <<-RUBY + config.cache_classes = false + config.reload_classes_only_on_change = false + RUBY + + app_file "app/models/post.rb", <<-MODEL + class Post < ActiveRecord::Base + end + MODEL + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/load', to: lambda { |env| [200, {}, Post.all] } + get '/unload', to: lambda { |env| [200, {}, []] } + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + setup_ar! + + assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants + get "/load" + assert_equal [ActiveRecord::SchemaMigration, Post], ActiveRecord::Base.descendants + get "/unload" + assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants + end + + test "initialize cant be called twice" do + require "#{app_path}/config/environment" + assert_raise(RuntimeError) { Rails.application.initialize! } + end + + test "reload constants on development" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 1; end + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "1", last_response.body + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 2; end + end + MODEL + + get "/c" + assert_equal "2", last_response.body + end + + test "does not reload constants on development if custom file watcher always returns false" do + add_to_config <<-RUBY + config.cache_classes = false + config.file_watcher = Class.new do + def initialize(*); end + def updated?; false; end + def execute; end + def execute_if_updated; false; end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/c', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 1; end + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "1", last_response.body + + app_file "app/models/user.rb", <<-MODEL + class User + def self.counter; 2; end + end + MODEL + + get "/c" + assert_equal "1", last_response.body + end + + test "added files (like db/schema.rb) also trigger reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + $counter ||= 0 + Rails.application.routes.draw do + get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + $counter += 1 + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "1", last_response.body + + app_file "db/schema.rb", "" + + get "/c" + assert_equal "2", last_response.body + end + + test "dependencies reloading is followed by routes reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + $counter ||= 1 + $counter *= 2 + Rails.application.routes.draw do + get '/c', to: lambda { |env| User.name; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + $counter += 1 + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "3", last_response.body + + app_file "db/schema.rb", "" + + get "/c" + assert_equal "7", last_response.body + end + + test "columns migrations also trigger reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get '/title', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] } + get '/body', to: lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] } + end + RUBY + + app_file "app/models/post.rb", <<-MODEL + class Post < ActiveRecord::Base + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + app_file "db/migrate/1_create_posts.rb", <<-MIGRATION + class CreatePosts < ActiveRecord::Migration + def change + create_table :posts do |t| + t.string :title, default: "TITLE" + end + end + end + MIGRATION + + Dir.chdir(app_path) { `rake db:migrate`} + require "#{rails_root}/config/environment" + + get "/title" + assert_equal "TITLE", last_response.body + + app_file "db/migrate/2_add_body_to_posts.rb", <<-MIGRATION + class AddBodyToPosts < ActiveRecord::Migration + def change + add_column :posts, :body, :text, default: "BODY" + end + end + MIGRATION + + Dir.chdir(app_path) { `rake db:migrate` } + + get "/body" + assert_equal "BODY", last_response.body + end + + test "AC load hooks can be used with metal" do + app_file "app/controllers/omg_controller.rb", <<-RUBY + begin + class OmgController < ActionController::Metal + ActiveSupport.run_load_hooks(:action_controller, self) + def show + self.response_body = ["OK"] + 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 + + require "#{rails_root}/config/environment" + + require 'rack/test' + extend Rack::Test::Methods + + get '/omg/show' + assert_equal 'OK', last_response.body + end + + def test_initialize_can_be_called_at_any_time + require "#{app_path}/config/application" + + assert !Rails.initialized? + assert !Rails.application.initialized? + Rails.initialize! + assert Rails.initialized? + assert Rails.application.initialized? + end + + protected + + def setup_ar! + 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 + end +end diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb new file mode 100644 index 0000000000..643d876a26 --- /dev/null +++ b/railties/test/application/mailer_previews_test.rb @@ -0,0 +1,701 @@ +require 'isolation/abstract_unit' +require 'rack/test' +require 'base64' + +module ApplicationTests + class MailerPreviewsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "/rails/mailers is accessible in development" do + app("development") + get "/rails/mailers" + assert_equal 200, last_response.status + end + + test "/rails/mailers is not accessible in production" do + app("production") + get "/rails/mailers" + assert_equal 404, last_response.status + end + + test "/rails/mailers is accessible with correct configuraiton" do + add_to_config "config.action_mailer.show_previews = true" + app("production") + get "/rails/mailers", {}, {"REMOTE_ADDR" => "4.2.42.42"} + assert_equal 200, last_response.status + end + + test "/rails/mailers is not accessible with show_previews = false" do + add_to_config "config.action_mailer.show_previews = false" + app("development") + get "/rails/mailers" + assert_equal 404, last_response.status + end + + test "/rails/mailers is accessible with globbing route present" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '*foo', to: 'foo#index' + end + RUBY + app("development") + get "/rails/mailers" + assert_equal 200, last_response.status + end + + test "mailer previews are loaded from the default preview_path" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + end + + test "mailer previews are loaded from a custom preview_path" do + add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + end + + test "mailer previews are reloaded across requests" do + app('development') + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + remove_file 'test/mailers/previews/notifier_preview.rb' + sleep(1) + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + end + + test "mailer preview actions are added and removed" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + + def bar + mail to: "to@example.net" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + text_template 'notifier/bar', <<-RUBY + Goodbye, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + + def bar + Notifier.bar + end + end + RUBY + + sleep(1) + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + remove_file 'app/views/notifier/bar.text.erb' + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + sleep(1) + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/rails/mailers/notifier/foo">foo</a></li>', last_response.body + assert_no_match '<li><a href="/rails/mailers/notifier/bar">bar</a></li>', last_response.body + end + + test "mailer previews are reloaded from a custom preview_path" do + add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'" + + app('development') + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + app_file 'lib/mailer_previews/notifier_preview.rb', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + get "/rails/mailers" + assert_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + + remove_file 'lib/mailer_previews/notifier_preview.rb' + sleep(1) + + get "/rails/mailers" + assert_no_match '<h3><a href="/rails/mailers/notifier">Notifier</a></h3>', last_response.body + end + + test "mailer preview not found" do + app('development') + get "/rails/mailers/notifier" + assert last_response.not_found? + assert_match "Mailer preview 'notifier' not found", last_response.body + end + + test "mailer preview email not found" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/bar" + assert last_response.not_found? + assert_match "Email 'bar' not found in NotifierPreview", last_response.body + end + + test "mailer preview NullMail" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + # does not call +mail+ + end + end + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_match "You are trying to preview an email that does not have any content.", last_response.body + assert_match "notifier#foo", last_response.body + end + + test "mailer preview email part not found" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo?part=text%2Fhtml" + assert last_response.not_found? + assert_match "Email part 'text/html' not found in NotifierPreview#foo", last_response.body + end + + test "message header uses full display names" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "Ruby on Rails <core@rubyonrails.org>" + + def foo + mail to: "Andrew White <andyw@pixeltrix.co.uk>", + cc: "David Heinemeier Hansson <david@heinemeierhansson.com>" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match "Ruby on Rails <core@rubyonrails.org>", last_response.body + assert_match "Andrew White <andyw@pixeltrix.co.uk>", last_response.body + assert_match "David Heinemeier Hansson <david@heinemeierhansson.com>", last_response.body + end + + test "part menu selects correct option" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo.html" + assert_equal 200, last_response.status + assert_match '<option selected value="?part=text%2Fhtml">View as HTML email</option>', last_response.body + + get "/rails/mailers/notifier/foo.txt" + assert_equal 200, last_response.status + assert_match '<option selected value="?part=text%2Fplain">View as plain-text email</option>', last_response.body + end + + test "mailer previews create correct links when loaded on a subdirectory" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers", {}, 'SCRIPT_NAME' => '/my_app' + assert_match '<h3><a href="/my_app/rails/mailers/notifier">Notifier</a></h3>', last_response.body + assert_match '<li><a href="/my_app/rails/mailers/notifier/foo">foo</a></li>', last_response.body + end + + test "plain text mailer preview with attachment" do + image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb') + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + end + + test "multipart mailer preview with attachment" do + image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb') + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, World!</p>], last_response.body + end + + test "multipart mailer preview with inline attachment" do + image_file "pixel.png", "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo=" + + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + attachments['pixel.png'] = File.read("#{app_path}/public/images/pixel.png", mode: 'rb') + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + <%= image_tag attachments['pixel.png'].url %> + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, World!</p>], last_response.body + assert_match %r[src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEWzIioca/JlAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJgggo="], last_response.body + end + + test "multipart mailer preview with attached email" do + mailer 'notifier', <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo + message = ::Mail.new do + from 'foo@example.com' + to 'bar@example.com' + subject 'Important Message' + + text_part do + body 'Goodbye, World!' + end + + html_part do + body '<p>Goodbye, World!</p>' + end + end + + attachments['message.eml'] = message.to_s + mail to: "to@example.org" + end + end + RUBY + + text_template 'notifier/foo', <<-RUBY + Hello, World! + RUBY + + html_template 'notifier/foo', <<-RUBY + <p>Hello, World!</p> + RUBY + + mailer_preview 'notifier', <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo + end + end + RUBY + + app('development') + + get "/rails/mailers/notifier/foo" + assert_equal 200, last_response.status + assert_match %r[<iframe seamless name="messageBody"], last_response.body + + get "/rails/mailers/notifier/foo?part=text/plain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, World!</p>], last_response.body + end + + private + def build_app + super + app_file "config/routes.rb", "Rails.application.routes.draw do; end" + end + + def mailer(name, contents) + app_file("app/mailers/#{name}.rb", contents) + end + + def mailer_preview(name, contents) + app_file("test/mailers/previews/#{name}_preview.rb", contents) + end + + def html_template(name, contents) + app_file("app/views/#{name}.html.erb", contents) + end + + def text_template(name, contents) + app_file("app/views/#{name}.text.erb", contents) + end + + def image_file(name, contents) + app_file("public/images/#{name}", Base64.strict_decode64(contents), 'wb') + end + end +end diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb new file mode 100644 index 0000000000..c951dabd6c --- /dev/null +++ b/railties/test/application/middleware/cache_test.rb @@ -0,0 +1,180 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class CacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + require 'rack/test' + extend Rack::Test::Methods + end + + def teardown + teardown_app + end + + def simple_controller + controller :expires, <<-RUBY + class ExpiresController < ApplicationController + def expires_header + expires_in 10, public: !params[:private] + render text: SecureRandom.hex(16) + end + + def expires_etag + render_conditionally(etag: "1") + end + + def expires_last_modified + $last_modified ||= Time.now.utc + render_conditionally(last_modified: $last_modified) + end + + def keeps_if_modified_since + render :text => request.headers['If-Modified-Since'] + end + private + def render_conditionally(headers) + if stale?(headers.merge(public: !params[:private])) + render text: SecureRandom.hex(16) + end + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + end + + def test_cache_keeps_if_modified_since + simple_controller + expected = "Wed, 30 May 1984 19:43:31 GMT" + + get "/expires/keeps_if_modified_since", {}, "HTTP_IF_MODIFIED_SINCE" => expected + + assert_equal 200, last_response.status + assert_equal expected, last_response.body, "cache should have kept If-Modified-Since" + end + + def test_cache_is_disabled_in_dev_mode + simple_controller + app("development") + + get "/expires/expires_header" + assert_nil last_response.headers['X-Rack-Cache'] + + body = last_response.body + + get "/expires/expires_header" + assert_nil last_response.headers['X-Rack-Cache'] + assert_not_equal body, last_response.body + end + + def test_cache_works_with_expires + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_header" + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "max-age=10, public", last_response.headers["Cache-Control"] + + body = last_response.body + + get "/expires/expires_header" + + assert_equal "fresh", last_response.headers["X-Rack-Cache"] + + assert_equal body, last_response.body + end + + def test_cache_works_with_expires_private + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_header", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_equal "private, max-age=10", last_response.headers["Cache-Control"] + + body = last_response.body + + get "/expires/expires_header", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_not_equal body, last_response.body + end + + def test_cache_works_with_etags + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_etag" + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] + + body = last_response.body + etag = last_response.headers["ETag"] + + get "/expires/expires_etag", {}, "If-None-Match" => etag + assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] + assert_equal body, last_response.body + end + + def test_cache_works_with_etags_private + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_etag", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_equal "must-revalidate, private, max-age=0", last_response.headers["Cache-Control"] + + body = last_response.body + etag = last_response.headers["ETag"] + + get "/expires/expires_etag", {private: true}, "If-None-Match" => etag + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_not_equal body, last_response.body + end + + def test_cache_works_with_last_modified + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_last_modified" + assert_equal "miss, store", last_response.headers["X-Rack-Cache"] + assert_equal "public", last_response.headers["Cache-Control"] + + body = last_response.body + last = last_response.headers["Last-Modified"] + + get "/expires/expires_last_modified", {}, "If-Modified-Since" => last + assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] + assert_equal body, last_response.body + end + + def test_cache_works_with_last_modified_private + simple_controller + + add_to_config "config.action_dispatch.rack_cache = true" + + get "/expires/expires_last_modified", private: true + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_equal "must-revalidate, private, max-age=0", last_response.headers["Cache-Control"] + + body = last_response.body + last = last_response.headers["Last-Modified"] + + get "/expires/expires_last_modified", {private: true}, "If-Modified-Since" => last + assert_equal "miss", last_response.headers["X-Rack-Cache"] + assert_not_equal body, last_response.body + end + end +end diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb new file mode 100644 index 0000000000..bbb7627be9 --- /dev/null +++ b/railties/test/application/middleware/cookies_test.rb @@ -0,0 +1,47 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class CookiesTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def new_app + File.expand_path("#{app_path}/../new_app") + end + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + test 'always_write_cookie is true by default in development' do + require 'rails' + Rails.env = 'development' + require "#{app_path}/config/environment" + assert_equal true, ActionDispatch::Cookies::CookieJar.always_write_cookie + end + + test 'always_write_cookie is false by default in production' do + require 'rails' + Rails.env = 'production' + require "#{app_path}/config/environment" + assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie + end + + test 'always_write_cookie can be overridden' do + add_to_config <<-RUBY + config.action_dispatch.always_write_cookie = false + RUBY + + require 'rails' + Rails.env = 'development' + require "#{app_path}/config/environment" + assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie + end + end +end diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb new file mode 100644 index 0000000000..7b4babb13b --- /dev/null +++ b/railties/test/application/middleware/exceptions_test.rb @@ -0,0 +1,125 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareExceptionsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "show exceptions middleware filter backtrace before logging" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise 'oops' + end + end + RUBY + + get "/foo" + assert_equal 500, last_response.status + + log = File.read(Rails.application.config.paths["log"].first) + assert_no_match(/action_dispatch/, log, log) + assert_match(/oops/, log, log) + end + + test "renders active record exceptions as 404" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise ActiveRecord::RecordNotFound + end + end + RUBY + + get "/foo" + assert_equal 404, last_response.status + end + + test "uses custom exceptions app" do + add_to_config <<-RUBY + config.exceptions_app = lambda do |env| + [404, { "Content-Type" => "text/plain" }, ["YOU FAILED"]] + end + RUBY + + app.config.action_dispatch.show_exceptions = true + + get "/foo" + assert_equal 404, last_response.status + assert_equal "YOU FAILED", last_response.body + end + + test "url generation error when action_dispatch.show_exceptions is set raises an exception" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + raise ActionController::UrlGenerationError + end + end + RUBY + + app.config.action_dispatch.show_exceptions = true + + get '/foo' + assert_equal 500, last_response.status + end + + test "unspecified route when action_dispatch.show_exceptions is not set raises an exception" do + app.config.action_dispatch.show_exceptions = false + + assert_raise(ActionController::RoutingError) do + get '/foo' + end + end + + test "unspecified route when action_dispatch.show_exceptions is set shows 404" do + app.config.action_dispatch.show_exceptions = true + + assert_nothing_raised(ActionController::RoutingError) do + get '/foo' + assert_match "The page you were looking for doesn't exist.", last_response.body + end + end + + test "unspecified route when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + assert_nothing_raised(ActionController::RoutingError) do + get '/foo' + assert_match "No route matches", last_response.body + end + end + + test "displays diagnostics message when exception raised in template that contains UTF-8" do + controller :foo, <<-RUBY + class FooController < ActionController::Base + def index + end + end + RUBY + + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + app_file 'app/views/foo/index.html.erb', <<-ERB + <% raise 'boooom' %> + ✓測試テスト시험 + ERB + + get '/foo', :utf8 => '✓' + assert_match(/boooom/, last_response.body) + assert_match(/測試テスト시험/, last_response.body) + end + end +end diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb new file mode 100644 index 0000000000..97d5b5c698 --- /dev/null +++ b/railties/test/application/middleware/remote_ip_test.rb @@ -0,0 +1,78 @@ +require 'ipaddr' +require 'isolation/abstract_unit' +require 'active_support/key_generator' + +module ApplicationTests + class RemoteIpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def remote_ip(env = {}) + remote_ip = nil + env = Rack::MockRequest.env_for("/").merge(env).merge!( + 'action_dispatch.show_exceptions' => false, + 'action_dispatch.key_generator' => ActiveSupport::LegacyKeyGenerator.new('b3c631c314c0bbca50c1b2843150fe33') + ) + + endpoint = Proc.new do |e| + remote_ip = ActionDispatch::Request.new(e).remote_ip + [200, {}, ["Hello"]] + end + + Rails.application.middleware.build(endpoint).call(env) + remote_ip + end + + test "remote_ip works" do + make_basic_app + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1") + end + + test "checks IP spoofing by default" do + make_basic_app + assert_raises(ActionDispatch::RemoteIp::IpSpoofAttackError) do + remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "works with both headers individually" do + make_basic_app + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1") + end + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.2", remote_ip("HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "can disable IP spoofing check" do + make_basic_app do |app| + app.config.action_dispatch.ip_spoofing_check = false + end + + assert_nothing_raised(ActionDispatch::RemoteIp::IpSpoofAttackError) do + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1", "HTTP_CLIENT_IP" => "1.1.1.2") + end + end + + test "remote_ip works with HTTP_X_FORWARDED_FOR" do + make_basic_app + assert_equal "4.2.42.42", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "4.2.42.42") + end + + test "the user can set trusted proxies" do + make_basic_app do |app| + app.config.action_dispatch.trusted_proxies = /^4\.2\.42\.42$/ + end + + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "4.2.42.42") + end + + test "the user can set trusted proxies with an IPAddr argument" do + make_basic_app do |app| + app.config.action_dispatch.trusted_proxies = IPAddr.new('4.2.42.0/24') + end + + assert_equal "1.1.1.1", remote_ip("REMOTE_ADDR" => "1.1.1.1", "HTTP_X_FORWARDED_FOR" => "10.0.0.0,4.2.42.42") + end + end +end diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb new file mode 100644 index 0000000000..be86f1a3b8 --- /dev/null +++ b/railties/test/application/middleware/sendfile_test.rb @@ -0,0 +1,74 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class SendfileTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + define_method :simple_controller do + class ::OmgController < ActionController::Base + def index + send_file __FILE__ + end + end + end + + # x_sendfile_header middleware + test "config.action_dispatch.x_sendfile_header defaults to nil" do + make_basic_app + simple_controller + + get "/" + assert !last_response.headers["X-Sendfile"] + assert_equal File.read(__FILE__), last_response.body + end + + test "config.action_dispatch.x_sendfile_header can be set" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = "X-Sendfile" + end + + simple_controller + + get "/" + assert_equal File.expand_path(__FILE__), last_response.headers["X-Sendfile"] + end + + test "config.action_dispatch.x_sendfile_header is sent to Rack::Sendfile" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = 'X-Lighttpd-Send-File' + end + + simple_controller + + get "/" + assert_equal File.expand_path(__FILE__), last_response.headers["X-Lighttpd-Send-File"] + end + + test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do + make_basic_app do |app| + app.config.action_dispatch.x_sendfile_header = 'X-Sendfile' + app.config.public_file_server.enabled = true + app.paths["public"] = File.join(rails_root, "public") + end + + app_file "public/foo.txt", "foo" + + get "/foo.txt", "HTTP_X_SENDFILE_TYPE" => "X-Sendfile" + assert_equal File.join(rails_root, "public/foo.txt"), last_response.headers["X-Sendfile"] + end + end +end diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb new file mode 100644 index 0000000000..25eadfc387 --- /dev/null +++ b/railties/test/application/middleware/session_test.rb @@ -0,0 +1,342 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareSessionTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "config.force_ssl sets cookie to secure only" do + add_to_config "config.force_ssl = true" + require "#{app_path}/config/environment" + assert app.config.session_options[:secure], "Expected session to be marked as secure" + end + + test "session is not loaded if it's not used" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + if params[:flash] + flash[:notice] = "notice" + end + + head :ok + end + end + + get "/?flash=true" + get "/" + + assert last_request.env["HTTP_COOKIE"] + assert !last_response.headers["Set-Cookie"] + end + + test "session is empty and isn't saved on unverified request when using :null_session protect method" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_session + session[:foo] = 1 + head :ok + end + + def read_session + render text: session[:foo].inspect + end + end + RUBY + + add_to_config <<-RUBY + config.action_controller.allow_forgery_protection = true + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + post '/foo/read_session' # Read session using POST request without CSRF token + assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + + post '/foo/write_session' # Write session using POST request without CSRF token + get '/foo/read_session' # Session shouldn't be changed + assert_equal '1', last_response.body + end + + test "cookie jar is empty and isn't saved on unverified request when using :null_session protect method" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + post ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + protect_from_forgery with: :null_session + + def write_cookie + cookies[:foo] = '1' + head :ok + end + + def read_cookie + render text: cookies[:foo].inspect + end + end + RUBY + + add_to_config <<-RUBY + config.action_controller.allow_forgery_protection = true + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_cookie' + get '/foo/read_cookie' + assert_equal '"1"', last_response.body + + post '/foo/read_cookie' # Read cookie using POST request without CSRF token + assert_equal 'nil', last_response.body # Stored value shouldn't be accessible + + post '/foo/write_cookie' # Write cookie using POST request without CSRF token + get '/foo/read_cookie' # Cookie shouldn't be changed + assert_equal '"1"', last_response.body + end + + test "session using encrypted cookie store" 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_session + session[:foo] = 1 + head :ok + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '1', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + 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 works the same way as encrypted cookie store" 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_session + session[:foo] = 1 + head :ok + end + + def read_session + render text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + RUBY + + require "#{app_path}/config/environment" + + get '/foo/write_session' + get '/foo/read_session' + assert_equal '1', last_response.body + + get '/foo/read_encrypted_cookie' + assert_equal '1', last_response.body + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + 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 text: session[:foo] + end + + def read_encrypted_cookie + render text: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + 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 + + secret = app.key_generator.generate_key('encrypted cookie') + sign_secret = app.key_generator.generate_key('signed encrypted cookie') + encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + + get '/foo/read_raw_cookie' + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)['foo'] + 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 text: session[:foo] + end + + def read_signed_cookie + render text: cookies.signed[:_myapp_session]['foo'] + end + + def read_raw_cookie + render text: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + secrets.secret_key_base = nil + 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_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'] + end + end +end diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb new file mode 100644 index 0000000000..1246e20d94 --- /dev/null +++ b/railties/test/application/middleware/static_test.rb @@ -0,0 +1,68 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class MiddlewareStaticTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + # Regression test to #8907 + # See https://github.com/rails/rails/commit/9cc82b77196d21a5c7021f6dca59ab9b2b158a45#commitcomment-2416514 + test "doesn't set Cache-Control header when it is nil" do + app_file "public/foo.html", 'static' + + require "#{app_path}/config/environment" + + get 'foo' + + assert_not last_response.headers.has_key?('Cache-Control'), "Cache-Control should not be set" + end + + test "headers for static files are configurable" do + app_file "public/about.html", 'static' + add_to_config <<-CONFIG + config.public_file_server.headers = { + "Access-Control-Allow-Origin" => "http://rubyonrails.org", + "Cache-Control" => "public, max-age=60" + } + CONFIG + + require "#{app_path}/config/environment" + + get '/about.html' + + assert_equal 'http://rubyonrails.org', last_response.headers["Access-Control-Allow-Origin"] + assert_equal 'public, max-age=60', last_response.headers["Cache-Control"] + end + + test "public_file_server.index_name defaults to 'index'" do + app_file "public/index.html", "/index.html" + + require "#{app_path}/config/environment" + + get '/' + + assert_equal "/index.html\n", last_response.body + end + + test "public_file_server.index_name configurable" do + app_file "public/other-index.html", "/other-index.html" + add_to_config "config.public_file_server.index_name = 'other-index'" + + require "#{app_path}/config/environment" + + get '/' + + assert_equal "/other-index.html\n", last_response.body + end + end +end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb new file mode 100644 index 0000000000..1434522cce --- /dev/null +++ b/railties/test/application/middleware_test.rb @@ -0,0 +1,298 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class MiddlewareTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "default middleware stack" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "ActionDispatch::LoadInterlock", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "Rack::MethodOverride", + "ActionDispatch::RequestId", + "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods + "ActionDispatch::ShowExceptions", + "ActionDispatch::DebugExceptions", + "ActionDispatch::RemoteIp", + "ActionDispatch::Reloader", + "ActionDispatch::Callbacks", + "ActiveRecord::Migration::CheckPending", + "ActiveRecord::ConnectionAdapters::ConnectionManagement", + "ActiveRecord::QueryCache", + "ActionDispatch::Cookies", + "ActionDispatch::Session::CookieStore", + "ActionDispatch::Flash", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + + test "api middleware stack" do + add_to_config "config.api_only = true" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "ActionDispatch::LoadInterlock", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "ActionDispatch::RequestId", + "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods + "ActionDispatch::ShowExceptions", + "ActionDispatch::DebugExceptions", + "ActionDispatch::RemoteIp", + "ActionDispatch::Reloader", + "ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement", + "ActiveRecord::QueryCache", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + + test "Rack::Cache is not included by default" do + boot! + + assert !middleware.include?("Rack::Cache"), "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" + end + + test "Rack::Cache is present when action_dispatch.rack_cache is set" do + add_to_config "config.action_dispatch.rack_cache = true" + + boot! + + assert middleware.include?("Rack::Cache") + end + + test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert middleware.include?("ActiveRecord::Migration::CheckPending") + end + + test "ActionDispatch::SSL is present when force_ssl is set" do + add_to_config "config.force_ssl = true" + boot! + assert middleware.include?("ActionDispatch::SSL") + end + + test "ActionDispatch::SSL is configured with options when given" do + add_to_config "config.force_ssl = true" + add_to_config "config.ssl_options = { host: 'example.com' }" + boot! + + assert_equal [{host: 'example.com'}], Rails.application.middleware.first.args + end + + test "removing Active Record omits its middleware" do + use_frameworks [] + boot! + assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement") + assert !middleware.include?("ActiveRecord::QueryCache") + assert !middleware.include?("ActiveRecord::Migration::CheckPending") + end + + test "includes interlock if cache_classes is set but eager_load is not" do + add_to_config "config.cache_classes = true" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "includes interlock if cache_classes is off" do + add_to_config "config.cache_classes = false" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "does not include lock if cache_classes is set and so is eager_load" do + add_to_config "config.cache_classes = true" + add_to_config "config.eager_load = true" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_not_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "does not include lock if allow_concurrency is set to :unsafe" do + add_to_config "config.allow_concurrency = :unsafe" + boot! + assert_not_includes middleware, "Rack::Lock" + assert_not_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "includes lock if allow_concurrency is disabled" do + add_to_config "config.allow_concurrency = false" + boot! + assert_includes middleware, "Rack::Lock" + assert_not_includes middleware, "ActionDispatch::LoadInterlock" + end + + test "removes static asset server if public_file_server.enabled is disabled" do + add_to_config "config.public_file_server.enabled = false" + boot! + assert !middleware.include?("ActionDispatch::Static") + end + + test "can delete a middleware from the stack" do + add_to_config "config.middleware.delete ActionDispatch::Static" + boot! + assert !middleware.include?("ActionDispatch::Static") + end + + test "can delete a middleware from the stack even if insert_before is added after delete" do + add_to_config "config.middleware.delete Rack::Runtime" + add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)" + boot! + assert middleware.include?("Rack::Config") + assert_not middleware.include?("Rack::Runtime") + end + + test "can delete a middleware from the stack even if insert_after is added after delete" do + add_to_config "config.middleware.delete Rack::Runtime" + add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)" + boot! + assert middleware.include?("Rack::Config") + assert_not middleware.include?("Rack::Runtime") + end + + test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do + add_to_config "config.action_dispatch.show_exceptions = false" + boot! + assert middleware.include?("ActionDispatch::ShowExceptions") + assert middleware.include?("ActionDispatch::DebugExceptions") + end + + test "removes ActionDispatch::Reloader if cache_classes is true" do + add_to_config "config.cache_classes = true" + boot! + assert !middleware.include?("ActionDispatch::Reloader") + end + + test "use middleware" do + use_frameworks [] + add_to_config "config.middleware.use Rack::Config" + boot! + assert_equal "Rack::Config", middleware.last + end + + test "insert middleware after" do + add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" + boot! + assert_equal "Rack::Config", middleware.second + end + + test 'unshift middleware' do + add_to_config 'config.middleware.unshift Rack::Config' + boot! + assert_equal 'Rack::Config', middleware.first + end + + test "Rails.cache does not respond to middleware" do + add_to_config "config.cache_store = :memory_store" + boot! + assert_equal "Rack::Runtime", middleware.fourth + end + + test "Rails.cache does respond to middleware" do + boot! + assert_equal "Rack::Runtime", middleware.fifth + end + + test "insert middleware before" do + add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config" + boot! + assert_equal "Rack::Config", middleware.first + end + + test "can't change middleware after it's built" do + boot! + assert_raise RuntimeError do + app.config.middleware.use Rack::Config + end + end + + # ConditionalGet + Etag + test "conditional get + etag middlewares handle http caching based on body" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + if params[:nothing] + render text: "" + else + render text: "OMG" + end + end + end + + etag = "W/" + "5af83e3196bf99f440f31f2e1a6c9afe".inspect + + get "/" + assert_equal 200, last_response.status + assert_equal "OMG", last_response.body + assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] + assert_equal etag, last_response.headers["Etag"] + + get "/", {}, "HTTP_IF_NONE_MATCH" => etag + assert_equal 304, last_response.status + assert_equal "", last_response.body + assert_equal nil, last_response.headers["Content-Type"] + assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] + assert_equal etag, last_response.headers["Etag"] + + get "/?nothing=true" + assert_equal 200, last_response.status + assert_equal "", last_response.body + assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "no-cache", last_response.headers["Cache-Control"] + assert_equal nil, last_response.headers["Etag"] + end + + test "ORIGINAL_FULLPATH is passed to env" do + boot! + env = ::Rack::MockRequest.env_for("/foo/?something") + Rails.application.call(env) + + assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"] + end + + private + + def boot! + require "#{app_path}/config/environment" + end + + def middleware + Rails.application.middleware.map(&:klass).map(&:name) + end + end +end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb new file mode 100644 index 0000000000..f2770a9cb4 --- /dev/null +++ b/railties/test/application/multiple_applications_test.rb @@ -0,0 +1,176 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class MultipleApplicationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app(initializers: true) + boot_rails + require "#{rails_root}/config/environment" + Rails.application.config.some_setting = 'something_or_other' + end + + def teardown + teardown_app + end + + def test_cloning_an_application_makes_a_shallow_copy_of_config + clone = Rails.application.clone + + assert_equal Rails.application.config, clone.config, "The cloned application should get a copy of the config" + assert_equal Rails.application.config.some_setting, clone.config.some_setting, "The some_setting on the config should be the same" + end + + def test_inheriting_multiple_times_from_application + new_application_class = Class.new(Rails::Application) + + assert_not_equal Rails.application.object_id, new_application_class.instance.object_id + end + + def test_initialization_of_multiple_copies_of_same_application + application1 = AppTemplate::Application.new + application2 = AppTemplate::Application.new + + assert_not_equal Rails.application.object_id, application1.object_id, "New applications should not be the same as the original application" + assert_not_equal Rails.application.object_id, application2.object_id, "New applications should not be the same as the original application" + end + + def test_initialization_of_application_with_previous_config + application1 = AppTemplate::Application.create(config: Rails.application.config) + application2 = AppTemplate::Application.create + + assert_equal Rails.application.config, application1.config, "Creating a new application while setting an initial config should result in the same config" + assert_not_equal Rails.application.config, application2.config, "New applications without setting an initial config should not have the same config" + end + + def test_initialization_of_application_with_previous_railties + application1 = AppTemplate::Application.create(railties: Rails.application.railties) + application2 = AppTemplate::Application.create + + assert_equal Rails.application.railties, application1.railties + assert_not_equal Rails.application.railties, application2.railties + end + + def test_initialize_new_application_with_all_previous_initialization_variables + application1 = AppTemplate::Application.create( + config: Rails.application.config, + railties: Rails.application.railties, + routes_reloader: Rails.application.routes_reloader, + reloaders: Rails.application.reloaders, + routes: Rails.application.routes, + helpers: Rails.application.helpers, + app_env_config: Rails.application.env_config + ) + + assert_equal Rails.application.config, application1.config + assert_equal Rails.application.railties, application1.railties + assert_equal Rails.application.routes_reloader, application1.routes_reloader + assert_equal Rails.application.reloaders, application1.reloaders + assert_equal Rails.application.routes, application1.routes + assert_equal Rails.application.helpers, application1.helpers + assert_equal Rails.application.env_config, application1.env_config + end + + def test_rake_tasks_defined_on_different_applications_go_to_the_same_class + run_count = 0 + + application1 = AppTemplate::Application.new + application1.rake_tasks do + run_count += 1 + end + + application2 = AppTemplate::Application.new + application2.rake_tasks do + run_count += 1 + end + + require "#{app_path}/config/environment" + + assert_equal 0, run_count, "The count should stay at zero without any calls to the rake tasks" + require 'rake' + require 'rake/testtask' + require 'rdoc/task' + Rails.application.load_tasks + assert_equal 2, run_count, "Calling a rake task should result in two increments to the count" + end + + def test_multiple_applications_can_be_initialized + 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 } + AppTemplate::Application.new.console { run_count += 1 } + + assert_equal 0, run_count, "Without loading the consoles, the count should be 0" + Rails.application.load_console + assert_equal 2, run_count, "There should have been two consoles that increment the count" + end + + def test_generators_run_on_different_applications_go_to_the_same_class + run_count = 0 + AppTemplate::Application.generators { run_count += 1 } + AppTemplate::Application.new.generators { run_count += 1 } + + assert_equal 0, run_count, "Without loading the generators, the count should be 0" + Rails.application.load_generators + assert_equal 2, run_count, "There should have been two generators that increment the count" + end + + def test_runners_run_on_different_applications_go_to_the_same_class + run_count = 0 + AppTemplate::Application.runner { run_count += 1 } + AppTemplate::Application.new.runner { run_count += 1 } + + assert_equal 0, run_count, "Without loading the runners, the count should be 0" + Rails.application.load_runner + assert_equal 2, run_count, "There should have been two runners that increment the count" + end + + def test_isolate_namespace_on_an_application + assert_nil Rails.application.railtie_namespace, "Before isolating namespace, the railtie namespace should be nil" + Rails.application.isolate_namespace(AppTemplate) + assert_equal Rails.application.railtie_namespace, AppTemplate, "After isolating namespace, we should have a namespace" + end + + def test_inserting_configuration_into_application + app = AppTemplate::Application.new(config: Rails.application.config) + 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.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 new_config, app.config, "The application's config should have changed to the new config." + end + end +end diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb new file mode 100644 index 0000000000..4029984ce9 --- /dev/null +++ b/railties/test/application/paths_test.rb @@ -0,0 +1,83 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class PathsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + app_file "config/environments/development.rb", "" + add_to_config <<-RUBY + config.root = "#{app_path}" + config.after_initialize do |app| + app.config.session_store nil + end + RUBY + require "#{app_path}/config/environment" + @paths = Rails.application.config.paths + end + + def teardown + teardown_app + end + + def root(*path) + app_path(*path).to_s + end + + def assert_path(paths, *dir) + assert_equal [root(*dir)], paths.expanded + end + + def assert_in_load_path(*path) + assert $:.any? { |p| File.expand_path(p) == root(*path) }, "Load path does not include '#{root(*path)}'. They are:\n-----\n #{$:.join("\n")}\n-----" + end + + def assert_not_in_load_path(*path) + assert !$:.any? { |p| File.expand_path(p) == root(*path) }, "Load path includes '#{root(*path)}'. They are:\n-----\n #{$:.join("\n")}\n-----" + end + + test "booting up Rails yields a valid paths object" do + assert_path @paths["app/models"], "app/models" + assert_path @paths["app/helpers"], "app/helpers" + assert_path @paths["app/views"], "app/views" + assert_path @paths["lib"], "lib" + assert_path @paths["vendor"], "vendor" + assert_path @paths["tmp"], "tmp" + assert_path @paths["config"], "config" + assert_path @paths["config/locales"], "config/locales/en.yml" + assert_path @paths["config/environment"], "config/environment.rb" + assert_path @paths["config/environments"], "config/environments/development.rb" + + assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first + end + + test "booting up Rails yields a list of paths that are eager" do + eager_load = @paths.eager_load + assert eager_load.include?(root("app/controllers")) + assert eager_load.include?(root("app/helpers")) + assert eager_load.include?(root("app/models")) + end + + test "environments has a glob equal to the current environment" do + assert_equal "#{Rails.env}.rb", @paths["config/environments"].glob + end + + test "load path includes each of the paths in config.paths as long as the directories exist" do + assert_in_load_path "app", "controllers" + assert_in_load_path "app", "models" + assert_in_load_path "app", "helpers" + assert_in_load_path "lib" + assert_in_load_path "vendor" + + assert_not_in_load_path "app", "views" + assert_not_in_load_path "config" + assert_not_in_load_path "config", "locales" + assert_not_in_load_path "config", "environments" + assert_not_in_load_path "tmp" + assert_not_in_load_path "tmp", "cache" + end + end +end diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb new file mode 100644 index 0000000000..3198e12662 --- /dev/null +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -0,0 +1,63 @@ +require 'isolation/abstract_unit' +require 'rack/test' +require 'minitest/mock' + +require 'action_view' +require 'active_support/testing/method_call_assertions' + +class PerRequestDigestCacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ActiveSupport::Testing::MethodCallAssertions + include Rack::Test::Methods + + setup do + build_app + add_to_config 'config.consider_all_requests_local = true' + + app_file 'app/models/customer.rb', <<-RUBY + class Customer < Struct.new(:name, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + resources :customers, only: :index + end + RUBY + + app_file 'app/controllers/customers_controller.rb', <<-RUBY + class CustomersController < ApplicationController + def index + render [ Customer.new('david', 1), Customer.new('dingus', 2) ] + end + end + RUBY + + app_file 'app/views/customers/_customer.html.erb', <<-RUBY + <% cache customer do %> + <%= customer.name %> + <% end %> + RUBY + + require "#{app_path}/config/environment" + end + + teardown :teardown_app + + test "digests are reused when rendering the same template twice" do + get '/customers' + assert_equal 200, last_response.status + + assert_equal [ '8ba099b7749542fe765ff34a6824d548' ], ActionView::Digestor.cache.values + assert_equal %w(david dingus), last_response.body.split.map(&:strip) + end + + test "template digests are cleared before a request" do + assert_called(ActionView::Digestor.cache, :clear) do + get '/customers' + assert_equal 200, last_response.status + end + end +end diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb new file mode 100644 index 0000000000..0082ec9cd2 --- /dev/null +++ b/railties/test/application/rack/logger_test.rb @@ -0,0 +1,56 @@ +require "isolation/abstract_unit" +require "active_support/log_subscriber/test_helper" +require "rack/test" + +module ApplicationTests + module RackTests + class LoggerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ActiveSupport::LogSubscriber::TestHelper + include Rack::Test::Methods + + def setup + build_app + add_to_config <<-RUBY + config.logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new + RUBY + + require "#{app_path}/config/environment" + super + end + + def teardown + super + teardown_app + end + + def logs + @logs ||= Rails.logger.logged(:info).join("\n") + end + + test "logger logs proper HTTP GET verb and path" do + get "/blah" + wait + assert_match 'Started GET "/blah"', logs + end + + test "logger logs proper HTTP HEAD verb and path" do + head "/blah" + wait + assert_match 'Started HEAD "/blah"', logs + end + + test "logger logs HTTP verb override" do + post "/", _method: 'put' + wait + assert_match 'Started PUT "/"', logs + end + + test "logger logs HEAD requests" do + post "/", _method: 'head' + wait + assert_match 'Started HEAD "/"', logs + end + end + end +end diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb new file mode 100644 index 0000000000..49ac9fc66c --- /dev/null +++ b/railties/test/application/rackup_test.rb @@ -0,0 +1,43 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class RackupTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def rackup + require "rack" + app, _ = Rack::Builder.parse_file("#{app_path}/config.ru") + app + end + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "rails app is present" do + assert File.exist?(app_path("config")) + end + + test "config.ru can be racked up" do + Dir.chdir app_path do + @app = rackup + assert_welcome get("/") + end + end + + test "Rails.application is available after config.ru has been racked up" do + rackup + assert_kind_of Rails::Application, Rails.application + end + + test "the config object is available on the application object" do + rackup + assert_equal 'UTC', Rails.application.config.time_zone + end + end +end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb new file mode 100644 index 0000000000..0b0fb50fe1 --- /dev/null +++ b/railties/test/application/rake/dbs_test.rb @@ -0,0 +1,313 @@ +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" + +module ApplicationTests + module RakeTests + class RakeDbsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + end + + def database_url_db_name + "db/database_url_db.sqlite3" + end + + def set_database_url + ENV['DATABASE_URL'] = "sqlite3:#{database_url_db_name}" + # ensure it's using the DATABASE_URL + FileUtils.rm_rf("#{app_path}/config/database.yml") + end + + def db_create_and_drop(expected_database) + Dir.chdir(app_path) do + output = `bin/rake db:create` + assert_empty output + assert File.exist?(expected_database) + assert_equal expected_database, ActiveRecord::Base.connection_config[:database] + output = `bin/rake db:drop` + assert_empty output + assert !File.exist?(expected_database) + end + end + + 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 + require "#{app_path}/config/environment" + set_database_url + db_create_and_drop database_url_db_name + end + + def with_database_existing + Dir.chdir(app_path) do + set_database_url + `bin/rake db:create` + yield + `bin/rake db:drop` + end + end + + test 'db:create failure because database exists' do + with_database_existing do + output = `bin/rake db:create 2>&1` + assert_match(/already exists/, output) + assert_equal 0, $?.exitstatus + end + end + + def with_bad_permissions + Dir.chdir(app_path) do + set_database_url + FileUtils.chmod("-w", "db") + yield + FileUtils.chmod("+w", "db") + end + end + + test 'db:create failure because bad permissions' do + with_bad_permissions do + output = `bin/rake db:create 2>&1` + assert_match(/Couldn't create database/, output) + assert_equal 1, $?.exitstatus + end + end + + test 'db:drop failure because database does not exist' do + Dir.chdir(app_path) do + output = `bin/rake db:drop 2>&1` + assert_match(/does not exist/, output) + assert_equal 0, $?.exitstatus + end + end + + test 'db:drop failure because bad permissions' do + with_database_existing do + with_bad_permissions do + output = `bin/rake db:drop 2>&1` + assert_match(/Couldn't drop/, output) + assert_equal 1, $?.exitstatus + end + end + end + + def db_migrate_and_status(expected_database) + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate` + output = `bin/rake db:migrate:status` + assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) + assert_match(/up\s+\d{14}\s+Create books/, output) + end + end + + test 'db:migrate and db:migrate:status without database_url' do + require "#{app_path}/config/environment" + db_migrate_and_status ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:migrate and db:migrate:status with database_url' do + require "#{app_path}/config/environment" + set_database_url + db_migrate_and_status database_url_db_name + end + + def db_schema_dump + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:schema:dump` + schema_dump = File.read("db/schema.rb") + assert_match(/create_table \"books\"/, schema_dump) + end + end + + test 'db:schema:dump without database_url' do + db_schema_dump + end + + test 'db:schema:dump with database_url' do + set_database_url + db_schema_dump + end + + def db_fixtures_load(expected_database) + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake 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 + + test 'db:fixtures:load without database_url' do + require "#{app_path}/config/environment" + db_fixtures_load ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:fixtures:load with database_url' do + require "#{app_path}/config/environment" + set_database_url + db_fixtures_load database_url_db_name + end + + test 'db:fixtures:load with namespaced fixture' do + require "#{app_path}/config/environment" + Dir.chdir(app_path) do + `bin/rails generate model admin::book title:string; + bin/rake db:migrate db:fixtures:load` + require "#{app_path}/app/models/admin/book" + assert_equal 2, Admin::Book.count + end + end + + def db_structure_dump_and_load(expected_database) + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:structure:dump` + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE \"books\"/, structure_dump) + `bin/rake environment db:drop db:structure:load` + assert_match expected_database, ActiveRecord::Base.connection_config[:database] + require "#{app_path}/app/models/book" + #if structure is not loaded correctly, exception would be raised + assert_equal 0, Book.count + end + end + + test 'db:structure:dump and db:structure:load without database_url' do + require "#{app_path}/config/environment" + db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]['database'] + end + + test 'db:structure:dump and db:structure:load with database_url' do + require "#{app_path}/config/environment" + set_database_url + db_structure_dump_and_load database_url_db_name + end + + test 'db:structure:dump does not dump schema information when no migrations are used' do + Dir.chdir(app_path) do + # create table without migrations + `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + + stderr_output = capture(:stderr) { `bin/rake db:structure:dump` } + assert_empty stderr_output + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE \"posts\"/, structure_dump) + end + end + + test 'db:schema:load and db:structure:load do not purge the existing database' do + Dir.chdir(app_path) do + `bin/rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table(:comments) {} + end + RUBY + + list_tables = lambda { `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip } + + assert_equal '["posts"]', list_tables[] + `bin/rake db:schema:load` + assert_equal '["posts", "comments", "schema_migrations"]', list_tables[] + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + SQL + + `bin/rake db:structure:load` + assert_equal '["posts", "comments", "schema_migrations", "users"]', list_tables[] + end + end + + test "db:schema:load with inflections" do + Dir.chdir(app_path) do + app_file 'config/initializers/inflection.rb', <<-RUBY + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'goose', 'geese' + end + RUBY + app_file 'config/initializers/primary_key_table_name.rb', <<-RUBY + ActiveRecord::Base.primary_key_prefix_type = :table_name + RUBY + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: 20140423102712) do + create_table("goose".pluralize) do |t| + t.string :name + end + end + RUBY + + `bin/rake db:schema:load` + + tables = `bin/rails runner 'p ActiveRecord::Base.connection.tables'`.strip + assert_match(/"geese"/, tables) + + columns = `bin/rails runner 'p ActiveRecord::Base.connection.columns("geese").map(&:name)'`.strip + assert_equal columns, '["gooseid", "name"]' + end + end + + def db_test_load_structure + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate db:structure:dump db:test:load_structure` + ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Base.establish_connection :test + require "#{app_path}/app/models/book" + #if structure is not loaded correctly, exception would be raised + assert_equal 0, Book.count + assert_match ActiveRecord::Base.configurations['test']['database'], + ActiveRecord::Base.connection_config[:database] + end + end + + test 'db:test:load_structure without database_url' do + require "#{app_path}/config/environment" + db_test_load_structure + 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" + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: "1") do + create_table :users do |t| + t.string :name + end + end + RUBY + + app_file 'db/seeds.rb', <<-RUBY + puts ActiveRecord::Base.connection_config[:database] + RUBY + + Dir.chdir(app_path) do + database_path = `bin/rake db:setup` + assert_equal "development.sqlite3", File.basename(database_path.strip) + end + ensure + ENV["RAILS_ENV"] = @old_rails_env + ENV["RACK_ENV"] = @old_rack_env + end + end + end + end +end diff --git a/railties/test/application/rake/framework_test.rb b/railties/test/application/rake/framework_test.rb new file mode 100644 index 0000000000..ec57af79f6 --- /dev/null +++ b/railties/test/application/rake/framework_test.rb @@ -0,0 +1,48 @@ +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" + +module ApplicationTests + module RakeTests + class FrameworkTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + end + + def load_tasks + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + + Rails.application.load_tasks + end + + test 'requiring the rake task should not define method .app_generator on Object' do + require "#{app_path}/config/environment" + + load_tasks + + assert_raise NameError do + Object.method(:app_generator) + end + end + + test 'requiring the rake task should not define method .invoke_from_app_generator on Object' do + require "#{app_path}/config/environment" + + load_tasks + + assert_raise NameError do + Object.method(:invoke_from_app_generator) + end + end + end + end +end diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb new file mode 100644 index 0000000000..6b74707959 --- /dev/null +++ b/railties/test/application/rake/migrations_test.rb @@ -0,0 +1,228 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeMigrationsTest < ActiveSupport::TestCase + def setup + build_app + boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_app + end + + test 'running migrations with given scope' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string` + + app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION + class AMigration < ActiveRecord::Migration + end + MIGRATION + + output = `bin/rake db:migrate SCOPE=bukkits` + assert_no_match(/create_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/add_column\(:users, :email, :string\)/, output) + + assert_match(/AMigration: migrated/, output) + + output = `bin/rake db:migrate SCOPE=bukkits VERSION=0` + assert_no_match(/drop_table\(:users\)/, output) + assert_no_match(/CreateUsers/, output) + assert_no_match(/remove_column\(:users, :email\)/, output) + + assert_match(/AMigration: reverted/, output) + end + end + + test 'model and migration generator with change syntax' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string` + + output = `bin/rake db:migrate` + assert_match(/create_table\(:users\)/, output) + assert_match(/CreateUsers: migrated/, output) + assert_match(/add_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: migrated/, output) + + output = `bin/rake db:rollback STEP=2` + assert_match(/drop_table\(:users\)/, output) + assert_match(/CreateUsers: reverted/, output) + assert_match(/remove_column\(:users, :email, :string\)/, output) + assert_match(/AddEmailToUsers: reverted/, output) + end + end + + test 'migration status when schema migrations table is not present' do + output = Dir.chdir(app_path){ `bin/rake db:migrate:status 2>&1` } + assert_equal "Schema migrations table does not exist yet.\n", output + end + + test 'test migration status' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=1` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) + end + end + + test 'migration status without timestamps' do + add_to_config('config.active_record.timestamped_migrations = false') + + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=1` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) + end + end + + test 'test migration status after rollback and redo' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=2` + output = `bin/rake db:migrate:status` + + assert_match(/down\s+\d{14}\s+Create users/, output) + assert_match(/down\s+\d{14}\s+Add email to users/, output) + + `bin/rake db:migrate:redo` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+Add email to users/, output) + end + end + + test 'migration status after rollback and redo without timestamps' do + add_to_config('config.active_record.timestamped_migrations = false') + + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) + + `bin/rake db:rollback STEP=2` + output = `bin/rake db:migrate:status` + + assert_match(/down\s+\d{3,}\s+Create users/, output) + assert_match(/down\s+\d{3,}\s+Add email to users/, output) + + `bin/rake db:migrate:redo` + output = `bin/rake db:migrate:status` + + assert_match(/up\s+\d{3,}\s+Create users/, output) + assert_match(/up\s+\d{3,}\s+Add email to users/, output) + end + end + + test 'running migrations with not timestamp head migration files' do + Dir.chdir(app_path) do + + app_file "db/migrate/1_one_migration.rb", <<-MIGRATION + class OneMigration < ActiveRecord::Migration + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration + end + MIGRATION + + `bin/rake db:migrate` + + output = `bin/rake db:migrate:status` + + assert_match(/up\s+001\s+One migration/, output) + assert_match(/up\s+002\s+Two migration/, output) + end + end + + test 'schema generation when dump_schema_after_migration is set' do + add_to_config('config.active_record.dump_schema_after_migration = false') + + Dir.chdir(app_path) do + `bin/rails generate model book title:string` + output = `bin/rails generate model author name:string` + version = output =~ %r{[^/]+db/migrate/(\d+)_create_authors\.rb} && $1 + + `bin/rake db:migrate db:rollback db:forward db:migrate:up db:migrate:down VERSION=#{version}` + assert !File.exist?("db/schema.rb"), "should not dump schema when configured not to" + end + + add_to_config('config.active_record.dump_schema_after_migration = true') + + Dir.chdir(app_path) do + `bin/rails generate model reviews book_id:integer` + `bin/rake db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "reviews"/, structure_dump) + end + end + + test 'default schema generation after migration' do + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rake db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "books"/, structure_dump) + end + end + + test 'test migration status migrated file is deleted' do + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate migration add_email_to_users email:string; + bin/rake db:migrate + rm db/migrate/*email*.rb` + + output = `bin/rake db:migrate:status` + File.write('test.txt', output) + + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) + end + end + end + end +end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb new file mode 100644 index 0000000000..c87515f00f --- /dev/null +++ b/railties/test/application/rake/notes_test.rb @@ -0,0 +1,157 @@ +require "isolation/abstract_unit" +require 'rails/source_annotation_extractor' + +module ApplicationTests + module RakeTests + class RakeNotesTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + require "rails/all" + super + end + + def teardown + super + teardown_app + end + + test 'notes finds notes for certain file_types' do + app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>" + app_file "app/assets/javascripts/application.js", "// TODO: note in js" + app_file "app/assets/stylesheets/application.css", "// TODO: note in css" + app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" + app_file "lib/tasks/task.rake", "# TODO: note in rake" + app_file 'app/views/home/index.html.builder', '# TODO: note in builder' + app_file 'config/locales/en.yml', '# TODO: note in yml' + app_file 'config/locales/en.yaml', '# TODO: note in yaml' + app_file "app/views/home/index.ruby", "# TODO: note in ruby" + + run_rake_notes do |output, lines| + assert_match(/note in erb/, output) + assert_match(/note in js/, output) + assert_match(/note in css/, output) + assert_match(/note in rake/, output) + assert_match(/note in builder/, output) + assert_match(/note in yml/, output) + assert_match(/note in yaml/, output) + assert_match(/note in ruby/, output) + + assert_equal 9, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'notes finds notes in default directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" + app_file "db/some_seeds.rb", "# TODO: note in db directory" + app_file "lib/some_file.rb", "# TODO: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" + + run_rake_notes do |output, lines| + assert_match(/note in app directory/, output) + assert_match(/note in config directory/, output) + assert_match(/note in db directory/, output) + assert_match(/note in lib directory/, output) + assert_match(/note in test directory/, output) + assert_no_match(/note in some_other directory/, output) + + assert_equal 5, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'notes finds notes in custom directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory" + app_file "db/some_seeds.rb", "# TODO: note in db directory" + app_file "lib/some_file.rb", "# TODO: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" + + run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bin/rake notes" do |output, lines| + assert_match(/note in app directory/, output) + assert_match(/note in config directory/, output) + assert_match(/note in db directory/, output) + assert_match(/note in lib directory/, output) + assert_match(/note in test directory/, output) + + assert_match(/note in some_other directory/, output) + + assert_equal 6, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'custom rake task finds specific notes in specific directories' do + app_file "app/controllers/some_controller.rb", "# TODO: note in app directory" + app_file "lib/some_file.rb", "# OPTIMIZE: note in lib directory\n" << "# FIXME: note in lib directory" + app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory" + + app_file "lib/tasks/notes_custom.rake", <<-EOS + require 'rails/source_annotation_extractor' + task :notes_custom do + tags = 'TODO|FIXME' + opts = { dirs: %w(lib test), tag: true } + SourceAnnotationExtractor.enumerate(tags, opts) + end + EOS + + run_rake_notes "bin/rake notes_custom" do |output, lines| + assert_match(/\[FIXME\] note in lib directory/, output) + assert_match(/\[TODO\] note in test directory/, output) + assert_no_match(/OPTIMIZE/, output) + assert_no_match(/note in app directory/, output) + + assert_equal 2, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end + + test 'register a new extension' do + add_to_config "config.assets.precompile = []" + add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } } + app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" + app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" + + run_rake_notes do |output, lines| + assert_match(/note in scss/, output) + assert_match(/note in sass/, output) + assert_equal 2, lines.size + end + end + + private + + def run_rake_notes(command = 'bin/rake notes') + boot_rails + load_tasks + + Dir.chdir(app_path) do + output = `#{command}` + lines = output.scan(/\[([0-9\s]+)\]\s/).flatten + + yield output, lines + end + end + + def load_tasks + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + + Rails.application.load_tasks + end + + def boot_rails + super + require "#{app_path}/config/environment" + end + end + end +end diff --git a/railties/test/application/rake/restart_test.rb b/railties/test/application/rake/restart_test.rb new file mode 100644 index 0000000000..4cae199e6b --- /dev/null +++ b/railties/test/application/rake/restart_test.rb @@ -0,0 +1,39 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeRestartTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test 'rake restart touches tmp/restart.txt' do + Dir.chdir(app_path) do + `rake restart` + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + sleep(1) + `rake restart` + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime + end + end + + test 'rake restart should work even if tmp folder does not exist' do + Dir.chdir(app_path) do + FileUtils.remove_dir('tmp') + `rake restart` + assert File.exist?('tmp/restart.txt') + end + end + end + end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb new file mode 100644 index 0000000000..0da0928b48 --- /dev/null +++ b/railties/test/application/rake_test.rb @@ -0,0 +1,314 @@ +require "isolation/abstract_unit" +require "active_support/core_ext/string/strip" + +module ApplicationTests + class RakeTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + def test_gems_tasks_are_loaded_first_than_application_ones + app_file "lib/tasks/app.rake", <<-RUBY + $task_loaded = Rake::Task.task_defined?("db:create:all") + RUBY + + require "#{app_path}/config/environment" + ::Rails.application.load_tasks + assert $task_loaded + end + + def test_environment_is_required_in_rake_tasks + app_file "config/environment.rb", <<-RUBY + SuperMiddleware = Struct.new(:app) + + Rails.application.configure do + config.middleware.use SuperMiddleware + end + + Rails.application.initialize! + RUBY + + assert_match("SuperMiddleware", Dir.chdir(app_path){ `bin/rake middleware` }) + end + + def test_initializers_are_executed_in_rake_tasks + add_to_config <<-RUBY + initializer "do_something" do + puts "Doing something..." + end + + rake_tasks do + task do_nothing: :environment do + end + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake do_nothing` } + assert_match "Doing something...", output + end + + def test_does_not_explode_when_accessing_a_model + add_to_config <<-RUBY + rake_tasks do + task do_nothing: :environment do + Hello.new.world + end + end + RUBY + + app_file 'app/models/hello.rb', <<-RUBY + class Hello + def world + puts 'Hello world' + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake do_nothing` } + assert_match 'Hello world', output + end + + def test_should_not_eager_load_model_for_rake + add_to_config <<-RUBY + rake_tasks do + task do_nothing: :environment do + end + end + RUBY + + add_to_env_config 'production', <<-RUBY + config.eager_load = true + RUBY + + app_file 'app/models/hello.rb', <<-RUBY + raise 'should not be pre-required for rake even eager_load=true' + RUBY + + Dir.chdir(app_path) do + assert system('bin/rake do_nothing RAILS_ENV=production'), + 'should not be pre-required for rake even eager_load=true' + end + end + + def test_code_statistics_sanity + assert_match "Code LOC: 7 Test LOC: 0 Code to Test Ratio: 1:0.0", + Dir.chdir(app_path){ `bin/rake stats` } + end + + def test_rake_routes_calls_the_route_inspector + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_with_controller_environment + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + ENV['CONTROLLER'] = 'cart' + output = Dir.chdir(app_path){ `bin/rake routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_displays_message_when_no_routes_are_defined + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + end + RUBY + + assert_equal <<-MESSAGE.strip_heredoc, Dir.chdir(app_path){ `bin/rake routes` } + You don't have any routes defined! + + Please add some routes in config/routes.rb. + + For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html. + MESSAGE + end + + def test_logger_is_flushed_when_exiting_production_rake_tasks + add_to_config <<-RUBY + rake_tasks do + task log_something: :environment do + Rails.logger.error("Sample log message") + end + end + RUBY + + output = Dir.chdir(app_path){ `bin/rake log_something RAILS_ENV=production && cat log/production.log` } + assert_match "Sample log message", output + end + + def test_loading_specific_fixtures + Dir.chdir(app_path) do + `bin/rails generate model user username:string password:string; + bin/rails generate model product name:string; + bin/rake db:migrate` + end + + require "#{rails_root}/config/environment" + + # loading a specific fixture + errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load FIXTURES=products` } + assert $?.success?, errormsg + + assert_equal 2, ::AppTemplate::Application::Product.count + assert_equal 0, ::AppTemplate::Application::User.count + end + + def test_loading_only_yml_fixtures + Dir.chdir(app_path) do + `bin/rake db:migrate` + end + + app_file "test/fixtures/products.csv", "" + + require "#{rails_root}/config/environment" + errormsg = Dir.chdir(app_path) { `bin/rake db:fixtures:load` } + assert $?.success?, errormsg + end + + def test_scaffold_tests_pass_by_default + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string password:string; + bin/rake db:migrate test` + end + + assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + + def test_api_scaffold_tests_pass_by_default + add_to_config <<-RUBY + config.api_only = true + RUBY + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::API + end + RUBY + + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string password:string; + bin/rake db:migrate test` + end + + assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + + def test_scaffold_with_references_columns_tests_pass_when_belongs_to_is_optional + app_file "config/initializers/active_record_belongs_to_required_by_default.rb", + "Rails.application.config.active_record.belongs_to_required_by_default = false" + + output = Dir.chdir(app_path) do + `bin/rails generate scaffold LineItems product:references cart:belongs_to; + bin/rake db:migrate test` + end + + assert_match(/7 runs, 12 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + + def test_db_test_clone_when_using_sql_format + add_to_config "config.active_record.schema_format = :sql" + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string; + bin/rake db:migrate; + bin/rake db:test:clone 2>&1 --trace` + end + assert_match(/Execute db:test:clone_structure/, output) + end + + def test_db_test_prepare_when_using_sql_format + add_to_config "config.active_record.schema_format = :sql" + output = Dir.chdir(app_path) do + `bin/rails generate scaffold user username:string; + bin/rake db:migrate; + bin/rake db:test:prepare 2>&1 --trace` + end + assert_match(/Execute db:test:load_structure/, output) + end + + def test_rake_dump_structure_should_respect_db_structure_env_variable + Dir.chdir(app_path) do + # ensure we have a schema_migrations table to dump + `bin/rake db:migrate db:structure:dump SCHEMA=db/my_structure.sql` + end + assert File.exist?(File.join(app_path, 'db', 'my_structure.sql')) + end + + def test_rake_dump_structure_should_be_called_twice_when_migrate_redo + add_to_config "config.active_record.schema_format = :sql" + + output = Dir.chdir(app_path) do + `bin/rails g model post title:string; + bin/rake db:migrate:redo 2>&1 --trace;` + end + + # expect only Invoke db:structure:dump (first_time) + assert_no_match(/^\*\* Invoke db:structure:dump\s+$/, output) + end + + def test_rake_dump_schema_cache + Dir.chdir(app_path) do + `bin/rails generate model post title:string; + bin/rails generate model product name:string; + bin/rake db:migrate db:schema:cache:dump` + end + assert File.exist?(File.join(app_path, 'db', 'schema_cache.dump')) + end + + def test_rake_clear_schema_cache + Dir.chdir(app_path) do + `bin/rake db:schema:cache:dump db:schema:cache:clear` + end + assert !File.exist?(File.join(app_path, 'db', 'schema_cache.dump')) + end + + def test_copy_templates + Dir.chdir(app_path) do + `bin/rake rails:templates:copy` + %w(controller mailer scaffold).each do |dir| + assert File.exist?(File.join(app_path, 'lib', 'templates', 'erb', dir)) + end + %w(controller helper scaffold_controller assets).each do |dir| + assert File.exist?(File.join(app_path, 'lib', 'templates', 'rails', dir)) + end + end + end + + def test_template_load_initializers + app_file "config/initializers/dummy.rb", "puts 'Hello, World!'" + app_file "template.rb", "" + + output = Dir.chdir(app_path) do + `bin/rake rails:template LOCATION=template.rb` + end + + assert_match(/Hello, World!/, output) + end + + def test_tmp_clear_should_work_if_folder_missing + FileUtils.remove_dir("#{app_path}/tmp") + errormsg = Dir.chdir(app_path) { `bin/rake tmp:clear` } + assert_predicate $?, :success? + assert_empty errormsg + end + end +end diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb new file mode 100644 index 0000000000..b01febd768 --- /dev/null +++ b/railties/test/application/rendering_test.rb @@ -0,0 +1,45 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class RoutingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "Unknown format falls back to HTML template" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'pages/:id', to: 'pages#show' + end + RUBY + + app_file 'app/controllers/pages_controller.rb', <<-RUBY + class PagesController < ApplicationController + layout false + + def show + end + end + RUBY + + app_file 'app/views/pages/show.html.erb', <<-RUBY + <%= params[:id] %> + RUBY + + get '/pages/foo' + assert_equal 200, last_response.status + + get '/pages/foo.bar' + assert_equal 200, last_response.status + end + end +end diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb new file mode 100644 index 0000000000..0777714d35 --- /dev/null +++ b/railties/test/application/routing_test.rb @@ -0,0 +1,480 @@ +require 'isolation/abstract_unit' +require 'rack/test' + +module ApplicationTests + class RoutingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "rails/welcome in development" do + app("development") + get "/" + assert_equal 200, last_response.status + end + + test "rails/info in development" do + app("development") + get "/rails/info" + assert_equal 302, last_response.status + end + + test "rails/info/routes in development" do + app("development") + get "/rails/info/routes" + assert_equal 200, last_response.status + end + + test "rails/info/properties in development" do + app("development") + get "/rails/info/properties" + assert_equal 200, last_response.status + end + + test "root takes precedence over internal welcome controller" do + app("development") + + get '/' + assert_match %r{<h1>Getting started</h1>} , last_response.body + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + root to: "foo#index" + end + RUBY + + get '/' + assert_equal 'foo', last_response.body + end + + test "rails/welcome in production" do + app("production") + get "/" + assert_equal 404, last_response.status + end + + test "rails/info in production" do + app("production") + get "/rails/info" + assert_equal 404, last_response.status + end + + test "rails/info/routes in production" do + app("production") + get "/rails/info/routes" + assert_equal 404, last_response.status + end + + test "rails/info/properties in production" do + app("production") + get "/rails/info/properties" + assert_equal 404, last_response.status + end + + test "simple controller" do + simple_controller + + get '/foo' + assert_equal 'foo', last_response.body + end + + test "simple controller with helper" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render inline: "<%= foo_or_bar? %>" + end + end + RUBY + + app_file 'app/helpers/bar_helper.rb', <<-RUBY + module BarHelper + def foo_or_bar? + "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'bar', last_response.body + end + + test "mount rack app" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog" + # The line below is required because mount sometimes + # fails when a resource route is added. + resource :user + end + RUBY + + get '/blog/archives' + assert_equal '/archives', last_response.body + end + + test "mount named rack app" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: my_blog_path + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + mount lambda { |env| [200, {}, [env["PATH_INFO"]]] }, at: "/blog", as: "my_blog" + get '/foo' => 'foo#index' + end + RUBY + + get '/foo' + assert_equal '/blog', last_response.body + end + + test "multiple controllers" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ActionController::Base + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + + get '/bar' + assert_equal 'bar', last_response.body + end + + test "nested controller" do + controller 'foo', <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller 'admin/foo', <<-RUBY + module Admin + class FooController < ApplicationController + def index + render text: "admin::foo" + end + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'admin/foo', to: 'admin/foo#index' + get 'foo', to: 'foo#index' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + + get '/admin/foo' + assert_equal 'admin::foo', last_response.body + end + + test "routes appending blocks" do + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':controller/:action' + end + RUBY + + add_to_config <<-R + routes.append do + get '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] } + end + R + + app 'development' + + get '/win' + assert_equal 'WIN', last_response.body + + app_file 'config/routes.rb', <<-R + Rails.application.routes.draw do + get 'lol' => 'hello#index' + end + R + + get '/win' + assert_equal 'WIN', last_response.body + end + + {"development" => "baz", "production" => "bar"}.each do |mode, expected| + test "reloads routes when configuration is changed in #{mode}" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def bar + render text: "bar" + end + + def baz + render text: "baz" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#bar' + end + RUBY + + app(mode) + + get '/foo' + assert_equal 'bar', last_response.body + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#baz' + end + RUBY + + sleep 0.1 + + get '/foo' + assert_equal expected, last_response.body + end + end + + test 'routes are loaded just after initialization' do + require "#{app_path}/config/application" + + # Create the rack app just inside after initialize callback + ActiveSupport.on_load(:after_initialize) do + ::InitializeRackApp = lambda { |env| [200, {}, ["InitializeRackApp"]] } + end + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: ::InitializeRackApp + end + RUBY + + get '/foo' + assert_equal "InitializeRackApp", last_response.body + end + + test 'reload_routes! is part of Rails.application API' do + app("development") + assert_nothing_raised do + Rails.application.reload_routes! + end + end + + def test_root_path + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render :text => "foo" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', :to => 'foo#index' + root :to => 'foo#index' + end + RUBY + + remove_file 'public/index.html' + + get '/' + assert_equal 'foo', last_response.body + end + + test 'routes are added and removed when reloading' do + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + end + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + get 'bar', to: 'bar#index' + end + RUBY + + Rails.application.reload_routes! + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 'bar', last_response.body + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#index' + end + RUBY + + Rails.application.reload_routes! + + get '/foo' + assert_equal 'foo', last_response.body + assert_equal '/foo', Rails.application.routes.url_helpers.foo_path + + get '/bar' + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal '/bar', Rails.application.routes.url_helpers.bar_path + end + end + + test 'named routes are cleared when reloading' do + app('development') + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render text: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render text: "bar" + end + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':locale/foo', to: 'foo#index', as: 'foo' + end + RUBY + + get '/en/foo' + assert_equal 'foo', last_response.body + assert_equal '/en/foo', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + get ':locale/bar', to: 'bar#index', as: 'foo' + end + RUBY + + Rails.application.reload_routes! + + get '/en/foo' + assert_equal 404, last_response.status + + get '/en/bar' + assert_equal 'bar', last_response.body + assert_equal '/en/bar', Rails.application.routes.url_helpers.foo_path(:locale => 'en') + end + + test 'resource routing with irregular inflection' do + app_file 'config/initializers/inflection.rb', <<-RUBY + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular 'yazi', 'yazilar' + end + RUBY + + app_file 'config/routes.rb', <<-RUBY + Rails.application.routes.draw do + resources :yazilar + end + RUBY + + controller 'yazilar', <<-RUBY + class YazilarController < ApplicationController + def index + render text: 'yazilar#index' + end + end + RUBY + + get '/yazilars' + assert_equal 404, last_response.status + + get '/yazilar' + assert_equal 200, last_response.status + end + end +end diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb new file mode 100644 index 0000000000..0c180339b4 --- /dev/null +++ b/railties/test/application/runner_test.rb @@ -0,0 +1,89 @@ +require 'isolation/abstract_unit' +require 'env_helpers' + +module ApplicationTests + class RunnerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include EnvHelpers + + def setup + build_app + boot_rails + + # Lets create a model so we have something to play with + app_file "app/models/user.rb", <<-MODEL + class User + def self.count + 42 + end + end + MODEL + end + + def teardown + teardown_app + end + + def test_should_include_runner_in_shebang_line_in_help_without_option + assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner` } + end + + def test_should_include_runner_in_shebang_line_in_help + assert_match "/rails runner", Dir.chdir(app_path) { `bin/rails runner --help` } + end + + def test_should_run_ruby_statement + assert_match "42", Dir.chdir(app_path) { `bin/rails runner "puts User.count"` } + end + + def test_should_run_file + app_file "bin/count_users.rb", <<-SCRIPT + puts User.count + SCRIPT + + assert_match "42", Dir.chdir(app_path) { `bin/rails runner "bin/count_users.rb"` } + end + + def test_should_set_dollar_0_to_file + app_file "bin/dollar0.rb", <<-SCRIPT + puts $0 + SCRIPT + + assert_match "bin/dollar0.rb", Dir.chdir(app_path) { `bin/rails runner "bin/dollar0.rb"` } + end + + def test_should_set_dollar_program_name_to_file + app_file "bin/program_name.rb", <<-SCRIPT + puts $PROGRAM_NAME + SCRIPT + + assert_match "bin/program_name.rb", Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb"` } + end + + def test_with_hook + add_to_config <<-RUBY + runner do |app| + app.config.ran = true + end + RUBY + + assert_match "true", Dir.chdir(app_path) { `bin/rails runner "puts Rails.application.config.ran"` } + end + + def test_default_environment + assert_match "development", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + end + + def test_environment_with_rails_env + with_rails_env "production" do + assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + end + end + + def test_environment_with_rack_env + with_rack_env "production" do + assert_match "production", Dir.chdir(app_path) { `bin/rails runner "puts Rails.env"` } + end + end + end +end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb new file mode 100644 index 0000000000..4965ab7da0 --- /dev/null +++ b/railties/test/application/test_runner_test.rb @@ -0,0 +1,455 @@ +require 'isolation/abstract_unit' +require 'active_support/core_ext/string/strip' +require 'env_helpers' + +module ApplicationTests + class TestRunnerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation, EnvHelpers + + def setup + build_app + create_schema + end + + def teardown + teardown_app + end + + def test_run_single_file + create_test_file :models, 'foo' + create_test_file :models, 'bar' + assert_match "1 runs, 1 assertions, 0 failures", run_test_command("test/models/foo_test.rb") + end + + def test_run_multiple_files + create_test_file :models, 'foo' + create_test_file :models, 'bar' + assert_match "2 runs, 2 assertions, 0 failures", run_test_command("test/models/foo_test.rb test/models/bar_test.rb") + end + + def test_run_file_with_syntax_error + app_file 'test/models/error_test.rb', <<-RUBY + require 'test_helper' + def; end + RUBY + + error = capture(:stderr) { run_test_command('test/models/error_test.rb') } + assert_match "syntax error", error + end + + def test_run_models + create_test_file :models, 'foo' + create_test_file :models, 'bar' + create_test_file :controllers, 'foobar_controller' + run_test_command("test/models").tap do |output| + assert_match "FooTest", output + assert_match "BarTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_helpers + create_test_file :helpers, 'foo_helper' + create_test_file :helpers, 'bar_helper' + create_test_file :controllers, 'foobar_controller' + run_test_command("test/helpers").tap do |output| + assert_match "FooHelperTest", output + assert_match "BarHelperTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_units + skip "we no longer have the concept of unit tests. Just different directories..." + create_test_file :models, 'foo' + create_test_file :helpers, 'bar_helper' + create_test_file :unit, 'baz_unit' + create_test_file :controllers, 'foobar_controller' + run_test_units_command.tap do |output| + assert_match "FooTest", output + assert_match "BarHelperTest", output + assert_match "BazUnitTest", output + assert_match "3 runs, 3 assertions, 0 failures", output + end + end + + def test_run_controllers + create_test_file :controllers, 'foo_controller' + create_test_file :controllers, 'bar_controller' + create_test_file :models, 'foo' + run_test_command("test/controllers").tap do |output| + assert_match "FooControllerTest", output + assert_match "BarControllerTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_mailers + create_test_file :mailers, 'foo_mailer' + create_test_file :mailers, 'bar_mailer' + create_test_file :models, 'foo' + run_test_command("test/mailers").tap do |output| + assert_match "FooMailerTest", output + assert_match "BarMailerTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_jobs + create_test_file :jobs, 'foo_job' + create_test_file :jobs, 'bar_job' + create_test_file :models, 'foo' + run_test_command("test/jobs").tap do |output| + assert_match "FooJobTest", output + assert_match "BarJobTest", output + assert_match "2 runs, 2 assertions, 0 failures", output + end + end + + def test_run_functionals + skip "we no longer have the concept of functional tests. Just different directories..." + create_test_file :mailers, 'foo_mailer' + create_test_file :controllers, 'bar_controller' + create_test_file :functional, 'baz_functional' + create_test_file :models, 'foo' + run_test_functionals_command.tap do |output| + assert_match "FooMailerTest", output + assert_match "BarControllerTest", output + assert_match "BazFunctionalTest", output + assert_match "3 runs, 3 assertions, 0 failures", output + end + end + + def test_run_integration + create_test_file :integration, 'foo_integration' + create_test_file :models, 'foo' + run_test_command("test/integration").tap do |output| + assert_match "FooIntegration", output + assert_match "1 runs, 1 assertions, 0 failures", output + end + end + + def test_run_all_suites + suites = [:models, :helpers, :unit, :controllers, :mailers, :functional, :integration, :jobs] + 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 + end + end + + def test_run_named_test + app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + require 'test_helper' + + class Chu2KoiTest < ActiveSupport::TestCase + def test_rikka + puts 'Rikka' + end + + def test_sanae + puts 'Sanae' + end + end + RUBY + + run_test_command('-n test_rikka test/unit/chu_2_koi_test.rb').tap do |output| + assert_match "Rikka", output + assert_no_match "Sanae", output + end + end + + def test_run_matched_test + app_file 'test/unit/chu_2_koi_test.rb', <<-RUBY + require 'test_helper' + + class Chu2KoiTest < ActiveSupport::TestCase + def test_rikka + puts 'Rikka' + end + + def test_sanae + puts 'Sanae' + end + end + RUBY + + run_test_command('-n /rikka/ test/unit/chu_2_koi_test.rb').tap do |output| + assert_match "Rikka", output + assert_no_match "Sanae", output + end + end + + def test_load_fixtures_when_running_test_suites + create_model_with_fixture + suites = [:models, :helpers, :controllers, :mailers, :integration] + + suites.each do |suite, directory| + directory ||= suite + create_fixture_test directory + assert_match "3 users", run_test_command("test/#{suite}") + Dir.chdir(app_path) { FileUtils.rm_f "test/#{directory}" } + end + end + + def test_run_with_model + skip "These feel a bit odd. Not sure we should keep supporting them." + create_model_with_fixture + create_fixture_test 'models', 'user' + assert_match "3 users", run_task(["test models/user"]) + assert_match "3 users", run_task(["test app/models/user.rb"]) + end + + def test_run_different_environment_using_env_var + skip "no longer possible. Running tests in a different environment should be explicit" + app_file 'test/unit/env_test.rb', <<-RUBY + require 'test_helper' + + class EnvTest < ActiveSupport::TestCase + def test_env + puts Rails.env + end + end + RUBY + + ENV['RAILS_ENV'] = 'development' + assert_match "development", run_test_command('test/unit/env_test.rb') + end + + def test_run_in_test_environment_by_default + create_env_test + + assert_match "Current Environment: test", run_test_command('test/unit/env_test.rb') + end + + def test_run_different_environment + create_env_test + + assert_match "Current Environment: development", + run_test_command("-e development test/unit/env_test.rb") + end + + def test_generated_scaffold_works_with_rails_test + create_scaffold + assert_match "0 failures, 0 errors, 0 skips", run_test_command('') + end + + def test_run_multiple_folders + create_test_file :models, 'account' + create_test_file :controllers, 'accounts_controller' + + run_test_command('test/models test/controllers').tap do |output| + assert_match 'AccountTest', output + assert_match 'AccountsControllerTest', output + assert_match '2 runs, 2 assertions, 0 failures, 0 errors, 0 skips', output + end + end + + def test_run_with_ruby_command + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'declarative syntax works' do + puts 'PostTest' + assert true + end + end + RUBY + + Dir.chdir(app_path) do + `ruby -Itest test/models/post_test.rb`.tap do |output| + assert_match 'PostTest', output + assert_no_match 'is already defined in', output + end + end + end + + def test_mix_files_and_line_filters + create_test_file :models, 'account' + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + def test_post + puts 'PostTest' + assert true + end + + def test_line_filter_does_not_run_this + assert true + end + end + RUBY + + run_test_command('test/models/account_test.rb test/models/post_test.rb:4').tap do |output| + assert_match 'AccountTest', output + assert_match 'PostTest', output + assert_match '2 runs, 2 assertions', output + end + end + + def test_multiple_line_filters + create_test_file :models, 'account' + create_test_file :models, 'post' + + run_test_command('test/models/account_test.rb:4 test/models/post_test.rb:4').tap do |output| + assert_match 'AccountTest', output + assert_match 'PostTest', output + end + end + + def test_line_filter_without_line_runs_all_tests + create_test_file :models, 'account' + + run_test_command('test/models/account_test.rb:').tap do |output| + assert_match 'AccountTest', output + end + end + + def test_shows_filtered_backtrace_by_default + create_backtrace_test + + assert_match 'Rails::BacktraceCleaner', run_test_command('test/unit/backtrace_test.rb') + end + + def test_backtrace_option + create_backtrace_test + + assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb -b') + assert_match 'Minitest::BacktraceFilter', + run_test_command('test/unit/backtrace_test.rb --backtrace') + end + + def test_show_full_backtrace_using_backtrace_environment_variable + create_backtrace_test + + switch_env 'BACKTRACE', 'true' do + assert_match 'Minitest::BacktraceFilter', run_test_command('test/unit/backtrace_test.rb') + end + end + + def test_run_app_without_rails_loaded + # Simulate a real Rails app boot. + app_file 'config/boot.rb', <<-RUBY + ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + + require 'bundler/setup' # Set up gems listed in the Gemfile. + RUBY + + assert_match '0 runs, 0 assertions', run_test_command('') + end + + def test_output_inline_by_default + create_test_file :models, 'post', pass: false + + output = run_test_command('test/models/post_test.rb') + assert_match %r{Running:\n\nPostTest\nF\n\nwups!\n\nbin/rails test test/models/post_test.rb:6}, output + end + + def test_only_inline_failure_output + create_test_file :models, 'post', pass: false + + output = run_test_command('test/models/post_test.rb') + assert_match %r{Finished in.*\n\n1 runs, 1 assertions}, output + end + + def test_fail_fast + create_test_file :models, 'post', pass: false + + assert_match(/Interrupt/, + capture(:stderr) { run_test_command('test/models/post_test.rb --fail-fast') }) + end + + def test_raise_error_when_specified_file_does_not_exist + error = capture(:stderr) { run_test_command('test/not_exists.rb') } + assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) + end + + private + def run_test_command(arguments = 'test/unit/test_test.rb') + Dir.chdir(app_path) { `bin/rails t #{arguments}` } + end + + def create_model_with_fixture + script 'generate model user name:string' + + app_file 'test/fixtures/users.yml', <<-YAML.strip_heredoc + vampire: + id: 1 + name: Koyomi Araragi + crab: + id: 2 + name: Senjougahara Hitagi + cat: + id: 3 + name: Tsubasa Hanekawa + YAML + + run_migration + end + + def create_fixture_test(path = :unit, name = 'test') + app_file "test/#{path}/#{name}_test.rb", <<-RUBY + require 'test_helper' + + class #{name.camelize}Test < ActiveSupport::TestCase + def test_fixture + puts "\#{User.count} users (\#{__FILE__})" + end + end + RUBY + end + + def create_backtrace_test + app_file 'test/unit/backtrace_test.rb', <<-RUBY + require 'test_helper' + + class BacktraceTest < ActiveSupport::TestCase + def test_backtrace + puts Minitest.backtrace_filter + end + end + RUBY + end + + def create_schema + app_file 'db/schema.rb', '' + end + + def create_test_file(path = :unit, name = 'test', pass: true) + app_file "test/#{path}/#{name}_test.rb", <<-RUBY + require 'test_helper' + + class #{name.camelize}Test < ActiveSupport::TestCase + def test_truth + puts "#{name.camelize}Test" + assert #{pass}, 'wups!' + end + end + RUBY + end + + def create_env_test + app_file 'test/unit/env_test.rb', <<-RUBY + require 'test_helper' + + class EnvTest < ActiveSupport::TestCase + def test_env + puts "Current Environment: \#{Rails.env}" + end + end + RUBY + end + + def create_scaffold + script 'generate scaffold user name:string' + Dir.chdir(app_path) { File.exist?('app/models/user.rb') } + run_migration + end + + def run_migration + Dir.chdir(app_path) { `bin/rake db:migrate` } + end + end +end diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb new file mode 100644 index 0000000000..0e997f4ba7 --- /dev/null +++ b/railties/test/application/test_test.rb @@ -0,0 +1,307 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class TestTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + def teardown + teardown_app + end + + test "truth" do + app_file 'test/unit/foo_test.rb', <<-RUBY + require 'test_helper' + + class FooTest < ActiveSupport::TestCase + def test_truth + assert true + end + end + RUBY + + assert_successful_test_run 'unit/foo_test.rb' + end + + test "integration test" do + controller 'posts', <<-RUBY + class PostsController < ActionController::Base + end + RUBY + + app_file 'app/views/posts/index.html.erb', <<-HTML + Posts#index + HTML + + app_file 'test/integration/posts_test.rb', <<-RUBY + require 'test_helper' + + class PostsTest < ActionDispatch::IntegrationTest + def test_index + get '/posts' + assert_response :success + assert_includes @response.body, 'Posts#index' + end + end + RUBY + + assert_successful_test_run 'integration/posts_test.rb' + end + + test "enable full backtraces on test failures" do + app_file 'test/unit/failing_test.rb', <<-RUBY + require 'test_helper' + + class FailingTest < ActiveSupport::TestCase + def test_failure + raise "fail" + end + end + RUBY + + output = run_test_file('unit/failing_test.rb', env: { "BACKTRACE" => "1" }) + assert_match %r{test/unit/failing_test\.rb}, output + assert_match %r{test/unit/failing_test\.rb:4}, output + end + + test "ruby schema migrations" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + app_file 'db/schema.rb', '' + + assert_unsuccessful_run "models/user_test.rb", "Migrations are pending" + + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + end + end + RUBY + + app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY + Rails.application.config.active_record.maintain_test_schema = false + RUBY + + assert_unsuccessful_run "models/user_test.rb", "Could not find table 'users'" + + File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" + + result = assert_successful_test_run('models/user_test.rb') + assert !result.include?("create_table(:users)") + end + + test "sql structure migrations" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + + app_file 'db/structure.sql', '' + app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY + Rails.application.config.active_record.schema_format = :sql + RUBY + + assert_unsuccessful_run "models/user_test.rb", "Migrations are pending" + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); + CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + INSERT INTO schema_migrations (version) VALUES ('#{version}'); + SQL + + app_file 'config/initializers/disable_maintain_test_schema.rb', <<-RUBY + Rails.application.config.active_record.maintain_test_schema = false + RUBY + + assert_unsuccessful_run "models/user_test.rb", "Could not find table 'users'" + + File.delete "#{app_path}/config/initializers/disable_maintain_test_schema.rb" + + assert_successful_test_run('models/user_test.rb') + end + + test "sql structure migrations when adding column to existing table" do + output_1 = script('generate model user name:string') + version_1 = output_1.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + + app_file 'config/initializers/enable_sql_schema_format.rb', <<-RUBY + Rails.application.config.active_record.schema_format = :sql + RUBY + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); + CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + INSERT INTO schema_migrations (version) VALUES ('#{version_1}'); + SQL + + assert_successful_test_run('models/user_test.rb') + + output_2 = script('generate migration add_email_to_users') + version_2 = output_2.match(/(\d+)_add_email_to_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon", email: "jon@doe.com" + end + end + RUBY + + app_file 'db/structure.sql', <<-SQL + CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL); + CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version"); + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "email" varchar(255)); + INSERT INTO schema_migrations (version) VALUES ('#{version_1}'); + INSERT INTO schema_migrations (version) VALUES ('#{version_2}'); + SQL + + assert_successful_test_run('models/user_test.rb') + end + + # 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. + test "manually synchronize test schema after rollback" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + + class UserTest < ActiveSupport::TestCase + test "user" do + assert_equal ["id", "name"], User.columns_hash.keys + end + end + RUBY + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + end + end + RUBY + + assert_successful_test_run "models/user_test.rb" + + # Simulate `db:rollback` + edit of the migration file + `db:migrate` + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + t.integer :age + end + end + RUBY + + assert_successful_test_run "models/user_test.rb" + + Dir.chdir(app_path) { `bin/rake db:test:prepare` } + + assert_unsuccessful_run "models/user_test.rb", <<-ASSERTION +Expected: ["id", "name"] + Actual: ["id", "name", "age"] + ASSERTION + end + + test "hooks for plugins" do + output = script('generate model user name:string') + version = output.match(/(\d+)_create_users\.rb/)[1] + + app_file 'lib/tasks/hooks.rake', <<-RUBY + task :before_hook do + has_user_table = ActiveRecord::Base.connection.table_exists?('users') + puts "before: " + has_user_table.to_s + end + + task :after_hook do + has_user_table = ActiveRecord::Base.connection.table_exists?('users') + puts "after: " + has_user_table.to_s + end + + Rake::Task["db:test:prepare"].enhance [:before_hook] do + Rake::Task[:after_hook].invoke + end + RUBY + app_file 'test/models/user_test.rb', <<-RUBY + require 'test_helper' + class UserTest < ActiveSupport::TestCase + test "user" do + User.create! name: "Jon" + end + end + RUBY + + # Simulate `db:migrate` + app_file 'db/schema.rb', <<-RUBY + ActiveRecord::Schema.define(version: #{version}) do + create_table :users do |t| + t.string :name + end + end + RUBY + + output = assert_successful_test_run "models/user_test.rb" + assert_includes output, "before: false\nafter: true" + + # running tests again won't trigger a schema update + output = assert_successful_test_run "models/user_test.rb" + assert_not_includes output, "before:" + assert_not_includes output, "after:" + end + + private + def assert_unsuccessful_run(name, message) + result = run_test_file(name) + assert_not_equal 0, $?.to_i + assert result.include?(message) + result + end + + def assert_successful_test_run(name) + result = run_test_file(name) + assert_equal 0, $?.to_i, result + result + end + + def run_test_file(name, options = {}) + Dir.chdir(app_path) { `bin/rails test "#{app_path}/test/#{name}" 2>&1` } + end + end +end diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb new file mode 100644 index 0000000000..894e18cb39 --- /dev/null +++ b/railties/test/application/url_generation_test.rb @@ -0,0 +1,59 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + class UrlGenerationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def app + Rails.application + end + + test "it works" do + boot_rails + require "rails" + require "action_controller/railtie" + require "action_view/railtie" + + class MyApp < Rails::Application + secrets.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" + config.session_store :cookie_store, key: "_myapp_session" + config.active_support.deprecation = :log + config.eager_load = false + end + + Rails.application.initialize! + + class ::ApplicationController < ActionController::Base + end + + class ::OmgController < ::ApplicationController + def index + render text: omg_path + end + end + + MyApp.routes.draw do + get "/" => "omg#index", as: :omg + end + + require 'rack/test' + extend Rack::Test::Methods + + get "/" + assert_equal "/", last_response.body + end + + def test_routes_know_the_relative_root + boot_rails + require "rails" + require "action_controller/railtie" + require "action_view/railtie" + + relative_url = '/hello' + ENV["RAILS_RELATIVE_URL_ROOT"] = relative_url + app = Class.new(Rails::Application) + assert_equal relative_url, app.routes.relative_url_root + ENV["RAILS_RELATIVE_URL_ROOT"] = nil + end + end +end |