diff options
Diffstat (limited to 'railties/test/application')
47 files changed, 10345 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..3e17a1efa5 --- /dev/null +++ b/railties/test/application/asset_debugging_test.rb @@ -0,0 +1,169 @@ +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" + 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/rails 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 aren't concatenated when compile is true is on 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(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body) + assert_match(/<script src="\/assets\/xmlhr(\.self)?-([0-z]+)\.js\?body=1"><\/script>/, last_response.body) + end + + test "public path and tag methods are not over-written by the asset pipeline" do + contents = "doesnotexist" + cases = { + asset_path: %r{/#{contents}}, + image_path: %r{/images/#{contents}}, + video_path: %r{/videos/#{contents}}, + audio_path: %r{/audios/#{contents}}, + font_path: %r{/fonts/#{contents}}, + javascript_path: %r{/javascripts/#{contents}}, + stylesheet_path: %r{/stylesheets/#{contents}}, + image_tag: %r{<img src="/images/#{contents}"}, + favicon_link_tag: %r{<link rel="shortcut icon" type="image/x-icon" href="/images/#{contents}" />}, + stylesheet_link_tag: %r{<link rel="stylesheet" media="screen" href="/stylesheets/#{contents}.css" />}, + javascript_include_tag: %r{<script src="/javascripts/#{contents}.js">}, + audio_tag: %r{<audio src="/audios/#{contents}"></audio>}, + video_tag: %r{<video src="/videos/#{contents}"></video>} + } + + cases.each do |(view_method, tag_match)| + app_file "app/views/posts/index.html.erb", "<%= #{view_method} '#{contents}', skip_pipeline: true %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body + assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{tag_match}, but did not: #{body}") + end + end + + test "public url methods are not over-written by the asset pipeline" do + contents = "doesnotexist" + cases = { + asset_url: %r{http://example.org/#{contents}}, + image_url: %r{http://example.org/images/#{contents}}, + video_url: %r{http://example.org/videos/#{contents}}, + audio_url: %r{http://example.org/audios/#{contents}}, + font_url: %r{http://example.org/fonts/#{contents}}, + javascript_url: %r{http://example.org/javascripts/#{contents}}, + stylesheet_url: %r{http://example.org/stylesheets/#{contents}}, + } + + cases.each do |(view_method, tag_match)| + app_file "app/views/posts/index.html.erb", "<%= #{view_method} '#{contents}', skip_pipeline: true %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body + assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{tag_match}, but did not: #{body}") + end + end + + test "{ skip_pipeline: true } does not use the asset pipeline" do + cases = { + /\/assets\/application-.*.\.js/ => {}, + /application.js/ => { skip_pipeline: true }, + } + cases.each do |(tag_match, options_hash)| + app_file "app/views/posts/index.html.erb", "<%= asset_path('application.js', #{options_hash}) %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body.strip + assert_match(tag_match, body, "Expected `asset_path` with `#{options_hash}` to produce a match to #{tag_match}, but did not: #{body}") + end + end + + test "public_compute_asset_path does not use the asset pipeline" do + cases = { + compute_asset_path: /\/assets\/application-.*.\.js/, + public_compute_asset_path: /application.js/, + } + + cases.each do |(view_method, tag_match)| + app_file "app/views/posts/index.html.erb", "<%= #{ view_method } 'application.js' %>" + + app "development" + + class ::PostsController < ActionController::Base ; end + + get "/posts?debug_assets=true" + + body = last_response.body.strip + assert_match(tag_match, body, "Expected `#{view_method}` to produce a match to #{ tag_match }, but did not: #{ body }") + end + 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..f38cacd6da --- /dev/null +++ b/railties/test/application/assets_test.rb @@ -0,0 +1,522 @@ +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) + end + + def teardown + teardown_app + end + + def precompile!(env = nil) + with_env env.to_h do + quietly do + precompile_task = "bin/rails 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/rails 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/index-*.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 "sprockets cache is not shared between environments" do + app_file "app/assets/images/rails.png", "notactuallyapng" + app_file "app/assets/stylesheets/application.css.erb", "body { background: '<%= asset_path('rails.png') %>'; }" + add_to_env_config "production", 'config.assets.prefix = "production_assets"' + + precompile! + + assert_file_exists("#{app_path}/public/assets/application-*.css") + + file = Dir["#{app_path}/public/assets/application-*.css"].first + assert_match(/assets\/rails-([0-z]+)\.png/, File.read(file)) + + precompile! RAILS_ENV: "production" + + assert_file_exists("#{app_path}/public/production_assets/application-*.css") + + file = Dir["#{app_path}/public/production_assets/application-*.css"].first + assert_match(/production_assets\/rails-([0-z]+)\.png/, File.read(file)) + 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/**/*"] + 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 plain: "ok" + end + end + + get "/omg" + assert_equal "ok", last_response.body + + get "/assets/demo.js" + assert_match "alert()", last_response.body + assert_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_match(/Post;/, 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.self.js', last_response.body) + get "/posts", {}, "HTTPS" => "on" + assert_match('src="https://example.com/assets/application.self.js', last_response.body) + end + + test "asset urls should be protocol-relative if no request is in scope" do + 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..0fb995900f --- /dev/null +++ b/railties/test/application/bin_setup_test.rb @@ -0,0 +1,60 @@ +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/test.log", "zomg!") + + assert_equal "[]", list_tables.call + assert_equal 5, File.size("log/test.log") + assert_not File.exist?("tmp/restart.txt") + `bin/setup 2>&1` + assert_equal 0, File.size("log/test.log") + assert_equal '["articles", "schema_migrations", "ar_internal_metadata"]', 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` + + # Ignore line that's only output by Bundler < 1.14 + output.sub!(/^Resolving dependencies\.\.\.\n/, "") + + assert_equal(<<-OUTPUT, output) +== Installing dependencies == +The Gemfile's dependencies are satisfied + +== Preparing database == +Created database 'db/development.sqlite3' +Created database 'db/test.sqlite3' + +== 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..8360b7bf4b --- /dev/null +++ b/railties/test/application/configuration/custom_test.rb @@ -0,0 +1,45 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module ConfigurationTests + class CustomTest < ActiveSupport::TestCase + def setup + build_app + FileUtils.rm_rf("#{app_path}/config/environments") + end + + def teardown + teardown_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_nil x.nil_debugger + assert_nil x.i_do_not_exist.zomg + + # test that custom configuration responds to all messages + assert_equal true, x.respond_to?(:i_do_not_exist) + assert_kind_of Method, x.method(:i_do_not_exist) + assert_kind_of ActiveSupport::OrderedOptions, x.i_do_not_exist + end + + private + 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..9f62ca8eb8 --- /dev/null +++ b/railties/test/application/configuration_test.rb @@ -0,0 +1,1746 @@ +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 + suppress_default_config + end + + def teardown + teardown_app + FileUtils.rm_rf(new_app) if File.directory?(new_app) + end + + def suppress_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 "Rails.env falls back to development if RAILS_ENV is blank and RACK_ENV is nil" do + with_rails_env("") do + assert_equal "development", Rails.env + end + end + + test "Rails.env falls back to development if RACK_ENV is blank and RAILS_ENV is nil" do + with_rack_env("") do + assert_equal "development", Rails.env + end + end + + test "By default logs tags are not set in development" do + restore_default_config + + with_rails_env "development" do + app "development" + assert Rails.application.config.log_tags.blank? + end + end + + test "By default logs are tagged with :request_id in production" do + restore_default_config + + with_rails_env "production" do + app "production" + assert_equal [:request_id], Rails.application.config.log_tags + 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::Current + 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_equal Rails.application.routes_reloader, AppTemplate::Application.routes_reloader + end + + test "Rails::Application responds to paths" do + app "development" + 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}/app/models/application_record.rb") + FileUtils.rm_rf("#{app_path}/app/mailers/application_mailer.rb") + 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, STDOUT logging is enabled when RAILS_LOG_TO_STDOUT is set" do + restore_default_config + + with_rails_env "production" do + switch_env "RAILS_LOG_TO_STDOUT", "1" do + app "production" + assert ActiveSupport::Logger.logger_outputs_to?(app.config.logger, STDOUT) + 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 "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 plain: 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 "shared secrets saved in config/secrets.yml are loaded in app secrets" do + app_file "config/secrets.yml", <<-YAML + shared: + api_key: 3b7cd727 + YAML + + app "development" + + assert_equal "3b7cd727", app.secrets.api_key + end + + test "shared secrets will yield to environment specific secrets" do + app_file "config/secrets.yml", <<-YAML + shared: + api_key: 3b7cd727 + + development: + api_key: abc12345 + YAML + + app "development" + + assert_equal "abc12345", app.secrets.api_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_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_nil app.secrets.secret_key_base + e = assert_raise ArgumentError do + app.key_generator + end + assert_match(/\AA secret is required/, e.message) + end + + test "that nested keys are symbolized the same as parents for hashes more than one level deep" do + app_file "config/secrets.yml", <<-YAML + development: + smtp_settings: + address: "smtp.example.com" + user_name: "postmaster@example.com" + password: "697361616320736c6f616e2028656c6f7265737429" + YAML + + app "development" + + assert_equal "697361616320736c6f616e2028656c6f7265737429", app.secrets.smtp_settings[:password] + 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 "form_with can be configured with form_with_generates_remote_forms" do + app_file "config/initializers/form_builder.rb", <<-RUBY + Rails.configuration.action_view.form_with_generates_remote_forms = false + 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_with(model: 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_no_match(/data-remote/, last_response.body) + end + + test "form_with generates remote forms by default" do + 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_with(model: 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(/data-remote/, 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 plain: "update" + end + + private + + def form_authenticity_token(*args); token; end # stub the authenticity 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.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.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.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.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.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 plain: request.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 plain: 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 '<ActionController::Parameters {"title"=>"foo"} permitted: false>', 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 plain: 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 plain: 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" + + force_lazy_load_hooks { ActionController::Base } + + 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" + + force_lazy_load_hooks { ActionController::Base } + + 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" + + force_lazy_load_hooks { ActionController::Base } + + 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 exception" do + app_file "app/controllers/posts_controller.rb", <<-RUBY + class PostsController < ActionController::Base + def create + render plain: 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" + + force_lazy_load_hooks { ActionController::Base } + + 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" + + force_lazy_load_hooks { ActionController::Base } + + 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" + + force_lazy_load_hooks { ActionController::Base } + + 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" + + force_lazy_load_hooks { ActionController::Base } + + assert_equal false, ActionController::Parameters.action_on_unpermitted_parameters + end + + test "config.action_controller.default_protect_from_forgery is true by default" do + app "development" + + assert_equal true, ActionController::Base.default_protect_from_forgery + assert_includes ActionController::Base.__callbacks[:process_action].map(&:filter), :verify_authenticity_token + end + + test "config.action_controller.permit_all_parameters can be configured in an initializer" do + app_file "config/initializers/permit_all_parameters.rb", <<-RUBY + Rails.application.config.action_controller.permit_all_parameters = true + RUBY + + app "development" + + force_lazy_load_hooks { ActionController::Base } + assert_equal true, ActionController::Parameters.permit_all_parameters + end + + test "config.action_controller.always_permitted_parameters can be configured in an initializer" do + app_file "config/initializers/always_permitted_parameters.rb", <<-RUBY + Rails.application.config.action_controller.always_permitted_parameters = [] + RUBY + + app "development" + + force_lazy_load_hooks { ActionController::Base } + assert_equal [], ActionController::Parameters.always_permitted_parameters + end + + test "config.action_controller.action_on_unpermitted_parameters can be configured in an initializer" do + app_file "config/initializers/action_on_unpermitted_parameters.rb", <<-RUBY + Rails.application.config.action_controller.action_on_unpermitted_parameters = :raise + RUBY + + app "development" + + force_lazy_load_hooks { ActionController::Base } + assert_equal :raise, 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 plain: "HTML" } + format.xml { render plain: "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_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 + e = assert_raise RuntimeError do + make_basic_app do |application| + application.config.session_store :active_record_store + end + end + assert_match(/activerecord-session_store/, e.message) + end + + test "default session store initializer does not overwrite the user defined session store even if it is disabled" do + make_basic_app do |application| + application.config.session_store :disabled + end + + assert_nil app.config.session_store + end + + test "default session store initializer sets session store to cookie store" do + session_options = { key: "_myapp_session", cookie_only: true } + make_basic_app + + assert_equal ActionDispatch::Session::CookieStore, app.config.session_store + assert_equal session_options, app.config.session_options + 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 "loads database.yml using shared keys" do + app_file "config/database.yml", <<-YAML + shared: + username: bobby + adapter: sqlite3 + + development: + database: 'dev_db' + YAML + + app "development" + + ar_config = Rails.application.config.database_configuration + assert_equal "sqlite3", ar_config["development"]["adapter"] + assert_equal "bobby", ar_config["development"]["username"] + assert_equal "dev_db", ar_config["development"]["database"] + end + + test "loads database.yml using shared keys for undefined environments" do + app_file "config/database.yml", <<-YAML + shared: + username: bobby + adapter: sqlite3 + database: 'dev_db' + YAML + + app "development" + + ar_config = Rails.application.config.database_configuration + assert_equal "sqlite3", ar_config["development"]["adapter"] + assert_equal "bobby", ar_config["development"]["username"] + assert_equal "dev_db", ar_config["development"]["database"] + 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 uses the Pathname object if it is provided" do + app_file "config/custom.yml", <<-RUBY + development: + key: 'custom key' + RUBY + + add_to_config <<-RUBY + config.my_custom_config = config_for(Pathname.new(Rails.root.join("config/custom.yml"))) + 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 "default SQLite3Adapter.represent_boolean_as_integer for 5.1 is false" do + remove_from_config '.*config\.load_defaults.*\n' + add_to_top_of_config <<-RUBY + config.load_defaults 5.1 + RUBY + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app "development" + force_lazy_load_hooks { Post } + + assert_not ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + end + + test "default SQLite3Adapter.represent_boolean_as_integer for new installs is true" do + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app "development" + force_lazy_load_hooks { Post } + + assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + end + + test "represent_boolean_as_integer should be able to set via config.active_record.sqlite3.represent_boolean_as_integer" do + remove_from_config '.*config\.load_defaults.*\n' + + app_file "config/initializers/new_framework_defaults_5_2.rb", <<-RUBY + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + RUBY + + app_file "app/models/post.rb", <<-RUBY + class Post < ActiveRecord::Base + end + RUBY + + app "development" + force_lazy_load_hooks { Post } + + assert ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + 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 api_only 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 overridden" 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 + + test "controller force_ssl declaration can be used even if session_store is disabled" do + make_basic_app do |application| + application.config.session_store :disabled + end + + class ::OmgController < ActionController::Base + force_ssl + + def index + render plain: "Yay! You're on Rails!" + end + end + + get "/" + + assert_equal 301, last_response.status + assert_equal "https://example.org/", last_response.location + end + + private + def force_lazy_load_hooks + yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it. + 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..31bef82ccc --- /dev/null +++ b/railties/test/application/console_test.rb @@ -0,0 +1,156 @@ +require "isolation/abstract_unit" +require "console_helpers" + +class ConsoleTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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 + ActiveSupport::Reloader.to_complete { a = b = c = 1 } + ActiveSupport::Reloader.to_complete { b = c = 2 } + ActiveSupport::Reloader.to_prepare { c = 3 } + + irb_context.reload!(false) + + 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) + irb_context.reload!(false) + 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 + +class FullStackConsoleTest < ActiveSupport::TestCase + include ConsoleHelpers + + def setup + skip "PTY unavailable" unless available_pty? + + 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 write_prompt(command, expected_output = nil) + @master.puts command + assert_output command, @master + assert_output expected_output, @master if expected_output + assert_output "> ", @master + end + + def spawn_console(options) + Process.spawn( + "#{app_path}/bin/rails console #{options}", + in: @slave, out: @slave, err: @slave + ) + + assert_output "> ", @master, 30 + end + + def test_sandbox + spawn_console("--sandbox") + + write_prompt "Post.count", "=> 0" + write_prompt "Post.create" + write_prompt "Post.count", "=> 1" + @master.puts "quit" + + spawn_console("--sandbox") + + write_prompt "Post.count", "=> 0" + write_prompt "Post.transaction { Post.create; raise }" + write_prompt "Post.count", "=> 0" + @master.puts "quit" + end + + def test_environment_option_and_irb_option + spawn_console("test -- --verbose") + + write_prompt "a = 1", "a = 1" + write_prompt "puts Rails.env", "puts Rails.env\r\ntest" + @master.puts "quit" + end +end diff --git a/railties/test/application/current_attributes_integration_test.rb b/railties/test/application/current_attributes_integration_test.rb new file mode 100644 index 0000000000..5653ec0be1 --- /dev/null +++ b/railties/test/application/current_attributes_integration_test.rb @@ -0,0 +1,84 @@ +require "isolation/abstract_unit" +require "rack/test" + +class CurrentAttributesIntegrationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include Rack::Test::Methods + + setup do + build_app + + app_file "app/models/current.rb", <<-RUBY + class Current < ActiveSupport::CurrentAttributes + attribute :customer + + resets { Time.zone = "UTC" } + + def customer=(customer) + super + Time.zone = customer.try(:time_zone) + end + end + RUBY + + app_file "app/models/customer.rb", <<-RUBY + class Customer < Struct.new(:name) + def time_zone + "Copenhagen" + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/customers/:action", controller: :customers + end + RUBY + + app_file "app/controllers/customers_controller.rb", <<-RUBY + class CustomersController < ApplicationController + def set_current_customer + Current.customer = Customer.new("david") + render :index + end + + def set_no_customer + render :index + end + end + RUBY + + app_file "app/views/customers/index.html.erb", <<-RUBY + <%= Current.customer.try(:name) || 'noone' %>,<%= Time.zone.name %> + RUBY + + require "#{app_path}/config/environment" + end + + teardown :teardown_app + + test "current customer is assigned and cleared" do + get "/customers/set_current_customer" + assert_equal 200, last_response.status + assert_match(/david,Copenhagen/, last_response.body) + + get "/customers/set_no_customer" + assert_equal 200, last_response.status + assert_match(/noone,UTC/, last_response.body) + end + + test "resets after execution" do + assert_nil Current.customer + assert_equal "UTC", Time.zone.name + + Rails.application.executor.wrap do + Current.customer = Customer.new("david") + + assert_equal "david", Current.customer.name + assert_equal "Copenhagen", Time.zone.name + end + + assert_nil Current.customer + assert_equal "UTC", Time.zone.name + end +end diff --git a/railties/test/application/dbconsole_test.rb b/railties/test/application/dbconsole_test.rb new file mode 100644 index 0000000000..5d89d0e44d --- /dev/null +++ b/railties/test/application/dbconsole_test.rb @@ -0,0 +1,76 @@ +require "isolation/abstract_unit" +require "console_helpers" + +module ApplicationTests + class DBConsoleTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include ConsoleHelpers + + def setup + skip "PTY unavailable" unless available_pty? + + build_app + end + + def teardown + teardown_app + end + + def test_use_value_defined_in_environment_file_in_database_yml + Dir.chdir(app_path) do + app_file "config/database.yml", <<-YAML + development: + database: <%= Rails.application.config.database %> + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + YAML + + app_file "config/environments/development.rb", <<-RUBY + Rails.application.configure do + config.database = "db/development.sqlite3" + end + RUBY + end + + master, slave = PTY.open + spawn_dbconsole(slave) + assert_output("sqlite>", master) + ensure + master.puts ".exit" + end + + def test_respect_environment_option + Dir.chdir(app_path) do + app_file "config/database.yml", <<-YAML + default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + + development: + <<: *default + database: db/development.sqlite3 + + production: + <<: *default + database: db/production.sqlite3 + YAML + end + + master, slave = PTY.open + spawn_dbconsole(slave, "-e production") + assert_output("sqlite>", master) + + master.puts "pragma database_list;" + assert_output("production.sqlite3", master) + ensure + master.puts ".exit" + end + + private + def spawn_dbconsole(fd, options = nil) + Process.spawn("#{app_path}/bin/rails dbconsole #{options}", in: fd, out: fd, err: fd) + end + end +end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb new file mode 100644 index 0000000000..fe581db286 --- /dev/null +++ b/railties/test/application/generators_test.rb @@ -0,0 +1,198 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class GeneratorsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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_includes Rails::Generators.hidden_namespaces, "assets" + assert_includes Rails::Generators.hidden_namespaces, "helper" + assert_includes Rails::Generators.hidden_namespaces, "js" + assert_includes Rails::Generators.hidden_namespaces, "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 + + test "api only generator generate mailer views" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + FileUtils.cd(rails_root) { `bin/rails generate mailer notifier foo` } + assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.text.erb")) + assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.html.erb")) + end + + test "ARGV is mutated as expected" do + require "#{app_path}/config/environment" + Rails::Command.const_set("APP_PATH", "rails/all") + + FileUtils.cd(rails_root) do + ARGV = ["mailer", "notifier", "foo"] + Rails::Command.const_set("ARGV", ARGV) + quietly { Rails::Command.invoke :generate, ARGV } + + assert_equal ["notifier", "foo"], ARGV + end + + Rails::Command.send(:remove_const, "APP_PATH") + end + + test "help does not show hidden namespaces" do + FileUtils.cd(rails_root) do + output = `bin/rails generate --help` + assert_no_match "active_record:migration", output + + output = `bin/rails destroy --help` + assert_no_match "active_record:migration", output + end + end + end +end diff --git a/railties/test/application/help_test.rb b/railties/test/application/help_test.rb new file mode 100644 index 0000000000..0c3fe8bfa3 --- /dev/null +++ b/railties/test/application/help_test.rb @@ -0,0 +1,23 @@ +require "isolation/abstract_unit" + +class HelpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "command works" do + output = Dir.chdir(app_path) { `bin/rails help` } + assert_match "The most common rails commands are", output + end + + test "short-cut alias works" do + output = Dir.chdir(app_path) { `bin/rails -h` } + assert_match "The most common rails commands are", output + 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..eb2c578f91 --- /dev/null +++ b/railties/test/application/initializers/frameworks_test.rb @@ -0,0 +1,274 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class FrameworksTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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 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/rails 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.data_sources("posts") + end + + test "expire schema cache dump" do + Dir.chdir(app_path) do + `rails generate model post title:string; + bin/rails db:migrate db:schema:cache:dump db:rollback` + end + require "#{app_path}/config/environment" + assert !ActiveRecord::Base.connection.schema_cache.data_sources("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 + + test "connections checked out during initialization are returned to the pool" do + app_file "config/initializers/active_record.rb", <<-RUBY + ActiveRecord::Base.connection + RUBY + require "#{app_path}/config/environment" + assert !ActiveRecord::Base.connection_pool.active_connection? + 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..36926c50ff --- /dev/null +++ b/railties/test/application/initializers/hooks_test.rb @@ -0,0 +1,89 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class HooksTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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..cee198bd01 --- /dev/null +++ b/railties/test/application/initializers/i18n_test.rb @@ -0,0 +1,294 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class I18nTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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_not_includes I18n.backend.class.included_modules, 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_includes I18n.load_path, "#{app_path}/config/locales/en.yml" + assert_includes I18n.load_path, "#{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_includes I18n.backend.class.included_modules, 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_includes I18n.backend.class.included_modules, 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 ca => es-ES" do + I18n::Railtie.config.i18n.fallbacks = [{ 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 "[shortcut] config.i18n.fallbacks = { ca: :en } initializes fallbacks with a mapping ca => :en" do + I18n::Railtie.config.i18n.fallbacks = { ca: :en } + load_app + assert_fallbacks ca: [:ca, :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..dbefb22837 --- /dev/null +++ b/railties/test/application/initializers/load_path_test.rb @@ -0,0 +1,109 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class LoadPathTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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_includes $:, "#{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_includes $:, "#{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..b847ac6b36 --- /dev/null +++ b/railties/test/application/initializers/notifications_test.rb @@ -0,0 +1,55 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class NotificationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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/integration_test_case_test.rb b/railties/test/application/integration_test_case_test.rb new file mode 100644 index 0000000000..1118e5037a --- /dev/null +++ b/railties/test/application/integration_test_case_test.rb @@ -0,0 +1,73 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class IntegrationTestCaseTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup do + build_app + end + + teardown do + teardown_app + end + + test "resets Action Mailer test deliveries" do + script("generate mailer BaseMailer welcome") + + app_file "test/integration/mailer_integration_test.rb", <<-RUBY + require 'test_helper' + + class MailerIntegrationTest < ActionDispatch::IntegrationTest + setup do + @old_delivery_method = ActionMailer::Base.delivery_method + ActionMailer::Base.delivery_method = :test + end + + teardown do + ActionMailer::Base.delivery_method = @old_delivery_method + end + + 2.times do |i| + define_method "test_resets_deliveries_\#{i}" do + BaseMailer.welcome.deliver_now + assert_equal 1, ActionMailer::Base.deliveries.count + end + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails test 2>&1` } + assert_equal 0, $?.to_i, output + assert_match(/0 failures, 0 errors/, output) + end + end + + class IntegrationTestDefaultApp < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + setup do + build_app + end + + teardown do + teardown_app + end + + test "app method of integration tests returns test_app by default" do + app_file "test/integration/default_app_test.rb", <<-RUBY + require 'test_helper' + + class DefaultAppIntegrationTest < ActionDispatch::IntegrationTest + def test_app_returns_action_dispatch_test_app_by_default + assert_equal ActionDispatch.test_app, app + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails test 2>&1` } + assert_equal 0, $?.to_i, output + assert_match(/0 failures, 0 errors/, output) + 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..c75a25bc6f --- /dev/null +++ b/railties/test/application/loading_test.rb @@ -0,0 +1,371 @@ +require "isolation/abstract_unit" + +class LoadingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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 { Trackable } + assert_nothing_raised { EmailLoggable } + assert_nothing_raised { Orderable } + assert_nothing_raised { 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::InternalMetadata], ActiveRecord::Base.descendants + get "/load" + assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, Post], ActiveRecord::Base.descendants + get "/unload" + assert_equal [ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata], 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::Current + 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::Current + 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 + + private + + 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..f5c013dab6 --- /dev/null +++ b/railties/test/application/mailer_previews_test.rb @@ -0,0 +1,785 @@ +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 + 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 configuration" 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 "mailer preview receives query params" do + mailer "notifier", <<-RUBY + class Notifier < ActionMailer::Base + default from: "from@example.com" + + def foo(name) + @name = name + mail to: "to@example.org" + end + end + RUBY + + html_template "notifier/foo", <<-RUBY + <p>Hello, <%= @name %>!</p> + RUBY + + text_template "notifier/foo", <<-RUBY + Hello, <%= @name %>! + RUBY + + mailer_preview "notifier", <<-RUBY + class NotifierPreview < ActionMailer::Preview + def foo + Notifier.foo(params[:name] || "World") + end + end + RUBY + + app("development") + + get "/rails/mailers/notifier/foo.txt" + assert_equal 200, last_response.status + assert_match '<iframe seamless name="messageBody" src="?part=text%2Fplain">', last_response.body + assert_match '<option selected value="?part=text%2Fplain">', last_response.body + assert_match '<option value="?part=text%2Fhtml">', last_response.body + + get "/rails/mailers/notifier/foo?part=text%2Fplain" + assert_equal 200, last_response.status + assert_match %r[Hello, World!], last_response.body + + get "/rails/mailers/notifier/foo.html?name=Ruby" + assert_equal 200, last_response.status + assert_match '<iframe seamless name="messageBody" src="?name=Ruby&part=text%2Fhtml">', last_response.body + assert_match '<option selected value="?name=Ruby&part=text%2Fhtml">', last_response.body + assert_match '<option value="?name=Ruby&part=text%2Fplain">', last_response.body + + get "/rails/mailers/notifier/foo?name=Ruby&part=text%2Fhtml" + assert_equal 200, last_response.status + assert_match %r[<p>Hello, Ruby!</p>], 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 + + test "multipart mailer preview with empty parts" 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 + RUBY + + html_template "notifier/foo", <<-RUBY + 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/plain" + assert_equal 200, last_response.status + + get "/rails/mailers/notifier/foo?part=text/html" + assert_equal 200, last_response.status + 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..93b5263bf7 --- /dev/null +++ b/railties/test/application/middleware/cache_test.rb @@ -0,0 +1,179 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class CacheTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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 plain: 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 plain: request.headers['If-Modified-Since'] + end + private + def render_conditionally(headers) + if stale?(headers.merge(public: !params[:private])) + render plain: 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"] + + etag = last_response.headers["ETag"] + + get "/expires/expires_etag", {}, "HTTP_IF_NONE_MATCH" => etag + assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] + assert_equal 304, last_response.status + assert_equal "", 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 }, "HTTP_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"] + + last = last_response.headers["Last-Modified"] + + get "/expires/expires_last_modified", {}, "HTTP_IF_MODIFIED_SINCE" => last + assert_equal "stale, valid, store", last_response.headers["X-Rack-Cache"] + assert_equal 304, last_response.status + assert_equal "", 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 }, "HTTP_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..1e4b5d086c --- /dev/null +++ b/railties/test/application/middleware/cookies_test.rb @@ -0,0 +1,46 @@ +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 + 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..fe07ad3cbe --- /dev/null +++ b/railties/test/application/middleware/exceptions_test.rb @@ -0,0 +1,138 @@ +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 + 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 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 do + get "/foo" + assert_match "No route matches", last_response.body + end + end + + test "routing to an nonexistent controller when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + resources :articles + end + RUBY + + app.config.action_dispatch.show_exceptions = true + app.config.consider_all_requests_local = true + + get "/articles" + assert_match "<title>Action Controller: Exception caught</title>", last_response.body + 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..c34d9d6ee7 --- /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 do + assert_equal "1.1.1.1", remote_ip("HTTP_X_FORWARDED_FOR" => "1.1.1.1") + end + assert_nothing_raised 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 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..4938402fdc --- /dev/null +++ b/railties/test/application/middleware/sendfile_test.rb @@ -0,0 +1,73 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class SendfileTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + 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..a14ea589ed --- /dev/null +++ b/railties/test/application/middleware/session_test.rb @@ -0,0 +1,457 @@ +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 + 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 by default" 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 "config.force_ssl doesn't set cookie to secure only when changed from default" do + add_to_config "config.force_ssl = true" + add_to_config "config.ssl_options = { secure_cookies: false }" + require "#{app_path}/config/environment" + assert !app.config.session_options[: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 plain: 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 plain: 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 plain: session[:foo] + end + + def read_encrypted_cookie + render plain: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + 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 + + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) + + get "/foo/read_raw_cookie" + assert_equal 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 plain: session[:foo] + end + + def read_encrypted_cookie + render plain: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + + require "#{app_path}/config/environment" + + get "/foo/write_session" + get "/foo/read_session" + assert_equal "1", last_response.body + + get "/foo/read_encrypted_cookie" + assert_equal "1", last_response.body + + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) + + get "/foo/read_raw_cookie" + assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] + end + + test "session upgrading signature to encryption cookie store upgrades session to encrypted mode" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render plain: session[:foo] + end + + def read_encrypted_cookie + render plain: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + + require "#{app_path}/config/environment" + + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body + + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body + + get "/foo/read_encrypted_cookie" + assert_equal "2", last_response.body + + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) + + get "/foo/read_raw_cookie" + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] + end + + test "session upgrading from AES-CBC-HMAC encryption to AES-GCM encryption" 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 + # AES-256-CBC with SHA1 HMAC + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "TlgrdS85aUpDd1R2cDlPWlR6K0FJeGExckwySjZ2Z0pkR3d2QnRObGxZT25aalJWYWVvbFVLcHF4d0VQVDdSaFF2QjFPbG9MVjJzeWp3YjcyRUlKUUU2ZlR4bXlSNG9ZUkJPRUtld0E3dVU9LS0xNDZXbGpRZ3NjdW43N2haUEZJSUNRPT0=--3639b5ce54c09495cfeaae928cd5634e0c4b2e96" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render plain: session[:foo] + end + + def read_encrypted_cookie + render plain: cookies.encrypted[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + # Use a static key + secrets.secret_key_base = "known key base" + + # Enable AEAD cookies + config.action_dispatch.use_authenticated_cookie_encryption = true + RUBY + + require "#{app_path}/config/environment" + + get "/foo/write_raw_session" + get "/foo/read_session" + assert_equal "1", last_response.body + + get "/foo/write_session" + get "/foo/read_session" + assert_equal "2", last_response.body + + get "/foo/read_encrypted_cookie" + assert_equal "2", last_response.body + + cipher = "aes-256-gcm" + secret = app.key_generator.generate_key("authenticated encrypted cookie") + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len(cipher)], cipher: cipher) + + get "/foo/read_raw_cookie" + assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] + end + + test "session upgrading legacy signed cookies to new signed cookies" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':controller(/:action)' + end + RUBY + + controller :foo, <<-RUBY + class FooController < ActionController::Base + def write_raw_session + # {"session_id"=>"1965d95720fffc123941bdfb7d2e6870", "foo"=>1} + cookies[:_myapp_session] = "BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTE5NjVkOTU3MjBmZmZjMTIzOTQxYmRmYjdkMmU2ODcwBjsAVEkiCGZvbwY7AEZpBg==--315fb9931921a87ae7421aec96382f0294119749" + head :ok + end + + def write_session + session[:foo] = session[:foo] + 1 + head :ok + end + + def read_session + render plain: session[:foo] + end + + def read_signed_cookie + render plain: cookies.signed[:_myapp_session]['foo'] + end + + def read_raw_cookie + render plain: cookies[:_myapp_session] + end + end + RUBY + + add_to_config <<-RUBY + secrets.secret_token = "3b7cd727ee24e8444053437c36cc66c4" + 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 + + test "calling reset_session on request does not trigger an error for API apps" do + add_to_config "config.api_only = true" + + controller :test, <<-RUBY + class TestController < ApplicationController + def dump_flash + request.reset_session + render plain: 'It worked!' + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/dump_flash' => "test#dump_flash" + end + RUBY + + require "#{app_path}/config/environment" + + get "/dump_flash" + + assert_equal 200, last_response.status + assert_equal "It worked!", last_response.body + + assert_not_includes Rails.application.middleware, ActionDispatch::Flash + end + + test "cookie_only is set to true even if user tries to overwrite it" do + add_to_config "config.session_store :cookie_store, key: '_myapp_session', cookie_only: false" + require "#{app_path}/config/environment" + assert app.config.session_options[:cookie_only], "Expected cookie_only to be set to true" + 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..5cd3e4325e --- /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..0a6e5b52e9 --- /dev/null +++ b/railties/test/application/middleware_test.rb @@ -0,0 +1,310 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class MiddlewareTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + FileUtils.rm_rf "#{app_path}/config/environments" + end + + def teardown + teardown_app + end + + def app + @app ||= Rails.application + end + + test "default middleware stack" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "ActionDispatch::Executor", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "Rack::MethodOverride", + "ActionDispatch::RequestId", + "ActionDispatch::RemoteIp", + "Rails::Rack::Logger", + "ActionDispatch::ShowExceptions", + "ActionDispatch::DebugExceptions", + "ActionDispatch::Reloader", + "ActionDispatch::Callbacks", + "ActiveRecord::Migration::CheckPending", + "ActionDispatch::Cookies", + "ActionDispatch::Session::CookieStore", + "ActionDispatch::Flash", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + + test "api middleware stack" do + add_to_config "config.api_only = true" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "ActionDispatch::Executor", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "ActionDispatch::RequestId", + "ActionDispatch::RemoteIp", + "Rails::Rack::Logger", + "ActionDispatch::ShowExceptions", + "ActionDispatch::DebugExceptions", + "ActionDispatch::Reloader", + "ActionDispatch::Callbacks", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + + test "middleware dependencies" do + boot! + + # The following array-of-arrays describes dependencies between + # middlewares: the first item in each list depends on the + # remaining items (and therefore must occur later in the + # middleware stack). + + dependencies = [ + # Logger needs a fully "corrected" request environment + %w(Rails::Rack::Logger Rack::MethodOverride ActionDispatch::RequestId ActionDispatch::RemoteIp), + + # Serving public/ doesn't invoke user code, so it should skip + # locks etc + %w(ActionDispatch::Executor ActionDispatch::Static), + + # Errors during reload must be reported + %w(ActionDispatch::Reloader ActionDispatch::ShowExceptions ActionDispatch::DebugExceptions), + + # Outright dependencies + %w(ActionDispatch::Static Rack::Sendfile), + %w(ActionDispatch::Flash ActionDispatch::Session::CookieStore), + %w(ActionDispatch::Session::CookieStore ActionDispatch::Cookies), + ] + + require "tsort" + sorted = TSort.tsort((middleware | dependencies.flatten).method(:each), + lambda { |n, &b| dependencies.each { |m, *ds| ds.each(&b) if m == n } }) + assert_equal sorted, middleware + end + + test "Rack::Cache is not included by default" do + boot! + + assert_not_includes middleware, "Rack::Cache", "Rack::Cache is not included in the default stack unless you set config.action_dispatch.rack_cache" + end + + test "Rack::Cache is present when action_dispatch.rack_cache is set" do + add_to_config "config.action_dispatch.rack_cache = true" + + boot! + + assert_includes middleware, "Rack::Cache" + end + + test "ActiveRecord::Migration::CheckPending is present when active_record.migration_error is set to :page_load" do + add_to_config "config.active_record.migration_error = :page_load" + + boot! + + assert_includes middleware, "ActiveRecord::Migration::CheckPending" + end + + test "ActionDispatch::SSL is present when force_ssl is set" do + add_to_config "config.force_ssl = true" + boot! + assert_includes middleware, "ActionDispatch::SSL" + end + + test "ActionDispatch::SSL is configured with options when given" do + add_to_config "config.force_ssl = true" + add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }" + boot! + + assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware.first.args + end + + test "removing Active Record omits its middleware" do + use_frameworks [] + boot! + assert_not_includes middleware, "ActiveRecord::Migration::CheckPending" + end + + test "includes executor" do + boot! + assert_includes middleware, "ActionDispatch::Executor" + end + + test "does not include lock if cache_classes is set and so is eager_load" do + add_to_config "config.cache_classes = true" + add_to_config "config.eager_load = true" + boot! + assert_not_includes middleware, "Rack::Lock" + end + + test "does not include lock if allow_concurrency is set to :unsafe" do + add_to_config "config.allow_concurrency = :unsafe" + boot! + assert_not_includes middleware, "Rack::Lock" + end + + test "includes lock if allow_concurrency is disabled" do + add_to_config "config.allow_concurrency = false" + boot! + assert_includes middleware, "Rack::Lock" + end + + test "removes static asset server if public_file_server.enabled is disabled" do + add_to_config "config.public_file_server.enabled = false" + boot! + assert_not_includes middleware, "ActionDispatch::Static" + end + + test "can delete a middleware from the stack" do + add_to_config "config.middleware.delete ActionDispatch::Static" + boot! + assert_not_includes middleware, "ActionDispatch::Static" + end + + test "can delete a middleware from the stack even if insert_before is added after delete" do + add_to_config "config.middleware.delete Rack::Runtime" + add_to_config "config.middleware.insert_before(Rack::Runtime, Rack::Config)" + boot! + assert_includes middleware, "Rack::Config" + assert_not middleware.include?("Rack::Runtime") + end + + test "can delete a middleware from the stack even if insert_after is added after delete" do + add_to_config "config.middleware.delete Rack::Runtime" + add_to_config "config.middleware.insert_after(Rack::Runtime, Rack::Config)" + boot! + assert_includes middleware, "Rack::Config" + assert_not middleware.include?("Rack::Runtime") + end + + test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do + add_to_config "config.action_dispatch.show_exceptions = false" + boot! + assert_includes middleware, "ActionDispatch::ShowExceptions" + assert_includes middleware, "ActionDispatch::DebugExceptions" + end + + test "removes ActionDispatch::Reloader if cache_classes is true" do + add_to_config "config.cache_classes = true" + boot! + assert_not_includes middleware, "ActionDispatch::Reloader" + end + + test "use middleware" do + use_frameworks [] + add_to_config "config.middleware.use Rack::Config" + boot! + assert_equal "Rack::Config", middleware.last + end + + test "insert middleware after" do + add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" + boot! + assert_equal "Rack::Config", middleware.second + end + + test "unshift middleware" do + add_to_config "config.middleware.unshift Rack::Config" + boot! + assert_equal "Rack::Config", middleware.first + end + + test "Rails.cache does not respond to middleware" do + add_to_config "config.cache_store = :memory_store" + boot! + assert_equal "Rack::Runtime", middleware.fourth + end + + test "Rails.cache does respond to middleware" do + boot! + assert_equal "Rack::Runtime", middleware.fifth + end + + test "insert middleware before" do + add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config" + boot! + assert_equal "Rack::Config", middleware.first + end + + test "can't change middleware after it's built" do + boot! + assert_raise RuntimeError do + app.config.middleware.use Rack::Config + end + end + + # ConditionalGet + Etag + test "conditional get + etag middlewares handle http caching based on body" do + make_basic_app + + class ::OmgController < ActionController::Base + def index + if params[:nothing] + render plain: "" + else + render plain: "OMG" + end + end + end + + etag = "W/" + "c00862d1c6c1cf7c1b49388306e7b3c1".inspect + + get "/" + assert_equal 200, last_response.status + assert_equal "OMG", last_response.body + assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] + assert_equal etag, last_response.headers["Etag"] + + get "/", {}, "HTTP_IF_NONE_MATCH" => etag + assert_equal 304, last_response.status + assert_equal "", last_response.body + assert_nil last_response.headers["Content-Type"] + assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] + assert_equal etag, last_response.headers["Etag"] + + get "/?nothing=true" + assert_equal 200, last_response.status + assert_equal "", last_response.body + assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"] + assert_equal "no-cache", last_response.headers["Cache-Control"] + assert_nil last_response.headers["Etag"] + end + + test "ORIGINAL_FULLPATH is passed to env" do + boot! + env = ::Rack::MockRequest.env_for("/foo/?something") + Rails.application.call(env) + + assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"] + end + + private + + def boot! + require "#{app_path}/config/environment" + end + + def middleware + Rails.application.middleware.map(&:klass).map(&:name) + end + end +end diff --git a/railties/test/application/multiple_applications_test.rb b/railties/test/application/multiple_applications_test.rb new file mode 100644 index 0000000000..26b810af73 --- /dev/null +++ b/railties/test/application/multiple_applications_test.rb @@ -0,0 +1,175 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class MultipleApplicationsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app(initializers: true) + 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..515205296c --- /dev/null +++ b/railties/test/application/paths_test.rb @@ -0,0 +1,82 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class PathsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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_includes eager_load, root("app/controllers") + assert_includes eager_load, root("app/helpers") + assert_includes eager_load, 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..6e6996a6ba --- /dev/null +++ b/railties/test/application/per_request_digest_cache_test.rb @@ -0,0 +1,70 @@ +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 + + def cache_key + [ name, id ].join("/") + end + 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 + self.perform_caching = true + + 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 + + values = ActionView::LookupContext::DetailsKey.digest_caches.first.values + assert_equal [ "8ba099b7749542fe765ff34a6824d548" ], 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::LookupContext::DetailsKey, :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..e71bcbc536 --- /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..2943e9ee5d --- /dev/null +++ b/railties/test/application/rackup_test.rb @@ -0,0 +1,42 @@ +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 + 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..3216121de3 --- /dev/null +++ b/railties/test/application/rake/dbs_test.rb @@ -0,0 +1,318 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeDbsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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/rails db:create` + assert_match(/Created database/, output) + assert File.exist?(expected_database) + assert_equal expected_database, ActiveRecord::Base.connection_config[:database] + output = `bin/rails db:drop` + assert_match(/Dropped database/, 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/rails db:create` + yield + `bin/rails db:drop` + end + end + + test "db:create failure because database exists" do + with_database_existing do + output = `bin/rails 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/rails 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/rails db:drop:_unsafe --trace 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/rails 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/rails db:migrate` + output = `bin/rails 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/rails 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/rails db:migrate db:fixtures:load` + assert_match expected_database, ActiveRecord::Base.connection_config[:database] + require "#{app_path}/app/models/book" + assert_equal 2, Book.count + end + end + + 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/rails 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/rails db:migrate db:structure:dump` + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"books\"/, structure_dump) + `bin/rails 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/rails db:structure:dump` } + assert_empty stderr_output + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"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/rails db:schema:load` + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] + + app_file "db/structure.sql", <<-SQL + CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); + SQL + + `bin/rails db:structure:load` + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "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/rails 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 + + test "db:schema:load fails if schema.rb doesn't exist yet" do + Dir.chdir(app_path) do + stderr_output = capture(:stderr) { `bin/rails db:schema:load` } + assert_match(/Run `rails db:migrate` to create it/, stderr_output) + end + end + + def db_test_load_structure + Dir.chdir(app_path) do + `bin/rails generate model book title:string; + bin/rails 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/rails 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/dev_test.rb b/railties/test/application/rake/dev_test.rb new file mode 100644 index 0000000000..4f992d9c8d --- /dev/null +++ b/railties/test/application/rake/dev_test.rb @@ -0,0 +1,43 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeDevTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "dev:cache creates file and outputs message" do + Dir.chdir(app_path) do + output = `rails dev:cache` + assert File.exist?("tmp/caching-dev.txt") + assert_match(/Development mode is now being cached/, output) + end + end + + test "dev:cache deletes file and outputs message" do + Dir.chdir(app_path) do + `rails dev:cache` # Create caching file. + output = `rails dev:cache` # Delete caching file. + assert_not File.exist?("tmp/caching-dev.txt") + assert_match(/Development mode is no longer being cached/, output) + end + end + + test "dev:cache removes server.pid also" do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/pids") + FileUtils.touch("tmp/pids/server.pid") + `rails dev:cache` + assert_not File.exist?("tmp/pids/server.pid") + 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..40488a6aab --- /dev/null +++ b/railties/test/application/rake/framework_test.rb @@ -0,0 +1,46 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class FrameworkTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + 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/log_test.rb b/railties/test/application/rake/log_test.rb new file mode 100644 index 0000000000..fdd3c71fe8 --- /dev/null +++ b/railties/test/application/rake/log_test.rb @@ -0,0 +1,33 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class LogTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "log:clear clear all environments log files by default" do + Dir.chdir(app_path) do + File.open("config/environments/staging.rb", "w") + + File.write("log/staging.log", "staging") + File.write("log/test.log", "test") + File.write("log/dummy.log", "dummy") + + `rails log:clear` + + assert_equal 0, File.size("log/test.log") + assert_equal 0, File.size("log/staging.log") + assert_equal 5, File.size("log/dummy.log") + 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..51dfe2ef98 --- /dev/null +++ b/railties/test/application/rake/migrations_test.rb @@ -0,0 +1,304 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeMigrationsTest < ActiveSupport::TestCase + def setup + build_app + 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::Current + end + MIGRATION + + output = `bin/rails 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/rails 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 "migration with empty version" do + Dir.chdir(app_path) do + output = `bin/rails db:migrate VERSION= 2>&1` + assert_match(/Empty VERSION provided/, output) + + output = `bin/rails db:migrate:redo VERSION= 2>&1` + assert_match(/Empty VERSION provided/, output) + + output = `bin/rails db:migrate:up VERSION= 2>&1` + assert_match(/VERSION is required/, output) + + output = `bin/rails db:migrate:up 2>&1` + assert_match(/VERSION is required/, output) + + output = `bin/rails db:migrate:down VERSION= 2>&1` + assert_match(/VERSION is required - To go down one migration, use db:rollback/, output) + + output = `bin/rails db:migrate:down 2>&1` + assert_match(/VERSION is required - To go down one migration, use db:rollback/, 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/rails 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/rails 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/rails db:migrate:status 2>&1` } + assert_equal "Schema migrations table does not exist yet.\n", output + end + + 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/rails db:migrate` + + output = `bin/rails 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/rails db:rollback STEP=1` + output = `bin/rails 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/rails db:migrate` + + output = `bin/rails 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/rails db:rollback STEP=1` + output = `bin/rails 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 "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/rails db:migrate` + + output = `bin/rails 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/rails db:rollback STEP=2` + output = `bin/rails 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/rails db:migrate:redo` + output = `bin/rails 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 forward" 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/rails db:migrate` + + output = `bin/rails 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/rails db:rollback STEP=2` + output = `bin/rails 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/rails db:forward STEP=2` + output = `bin/rails 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 "raise error on any move when current migration does not exist" 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/rails db:migrate + rm db/migrate/*email*.rb` + + output = `bin/rails db:migrate:status` + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) + + output = `bin/rails db:rollback 2>&1` + assert_match(/rails aborted!/, output) + assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output) + assert_match(/No migration with version number\s\d{14}\./, output) + + output = `bin/rails db:migrate:status` + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, output) + + output = `bin/rails db:forward 2>&1` + assert_match(/rails aborted!/, output) + assert_match(/ActiveRecord::UnknownMigrationVersionError:/, output) + assert_match(/No migration with version number\s\d{14}\./, output) + + output = `bin/rails db:migrate:status` + assert_match(/up\s+\d{14}\s+Create users/, output) + assert_match(/up\s+\d{14}\s+\** NO FILE \**/, 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/rails db:migrate` + + output = `bin/rails 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/rails db:rollback STEP=2` + output = `bin/rails 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/rails db:migrate:redo` + output = `bin/rails 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::Current + end + MIGRATION + + app_file "db/migrate/02_two_migration.rb", <<-MIGRATION + class TwoMigration < ActiveRecord::Migration::Current + end + MIGRATION + + `bin/rails db:migrate` + + output = `bin/rails 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/rails 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/rails 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/rails db:migrate` + + structure_dump = File.read("db/schema.rb") + assert_match(/create_table "books"/, structure_dump) + end + end + + 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/rails db:migrate + rm db/migrate/*email*.rb` + + output = `bin/rails db:migrate:status` + + 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..e7ffea2e71 --- /dev/null +++ b/railties/test/application/rake/notes_test.rb @@ -0,0 +1,168 @@ +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/rails 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/rails 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 + + test "register additional directories" do + app_file "spec/spec_helper.rb", "# TODO: note in spec" + app_file "spec/models/user_spec.rb", "# TODO: note in model spec" + add_to_config ' config.annotations.register_directories("spec") ' + + run_rake_notes do |output, lines| + assert_match(/note in spec/, output) + assert_match(/note in model spec/, output) + assert_equal 2, lines.size + end + end + + private + + def run_rake_notes(command = "bin/rails 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 + 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..6ebd2d5461 --- /dev/null +++ b/railties/test/application/rake/restart_test.rb @@ -0,0 +1,47 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class RakeRestartTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "rails restart touches tmp/restart.txt" do + Dir.chdir(app_path) do + `bin/rails restart` + assert File.exist?("tmp/restart.txt") + + prev_mtime = File.mtime("tmp/restart.txt") + sleep(1) + `bin/rails restart` + curr_mtime = File.mtime("tmp/restart.txt") + assert_not_equal prev_mtime, curr_mtime + end + end + + test "rails restart should work even if tmp folder does not exist" do + Dir.chdir(app_path) do + FileUtils.remove_dir("tmp") + `bin/rails restart` + assert File.exist?("tmp/restart.txt") + end + end + + test "rails restart removes server.pid also" do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/pids") + FileUtils.touch("tmp/pids/server.pid") + `bin/rails restart` + assert_not File.exist?("tmp/pids/server.pid") + end + end + end + end +end diff --git a/railties/test/application/rake/tmp_test.rb b/railties/test/application/rake/tmp_test.rb new file mode 100644 index 0000000000..8423a98f84 --- /dev/null +++ b/railties/test/application/rake/tmp_test.rb @@ -0,0 +1,43 @@ +require "isolation/abstract_unit" + +module ApplicationTests + module RakeTests + class TmpTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "tmp:clear clear cache, socket and screenshot files" do + Dir.chdir(app_path) do + FileUtils.mkdir_p("tmp/cache") + FileUtils.touch("tmp/cache/cache_file") + + FileUtils.mkdir_p("tmp/sockets") + FileUtils.touch("tmp/sockets/socket_file") + + FileUtils.mkdir_p("tmp/screenshots") + FileUtils.touch("tmp/screenshots/fail.png") + + `rails tmp:clear` + + assert_not File.exist?("tmp/cache/cache_file") + assert_not File.exist?("tmp/sockets/socket_file") + assert_not File.exist?("tmp/screenshots/fail.png") + end + end + + test "tmp:clear should work if folder missing" do + FileUtils.remove_dir("#{app_path}/tmp") + errormsg = Dir.chdir(app_path) { `bin/rails tmp:clear` } + assert_predicate $?, :success? + assert_empty errormsg + 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..134106812d --- /dev/null +++ b/railties/test/application/rake_test.rb @@ -0,0 +1,395 @@ +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 + 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 + + test "task is protected when previous migration was production" do + Dir.chdir(app_path) do + output = `bin/rails generate model product name:string; + env RAILS_ENV=production bin/rails db:create db:migrate; + env RAILS_ENV=production bin/rails db:test:prepare test 2>&1` + + assert_match(/ActiveRecord::ProtectedEnvironmentError/, output) + end + end + + def test_not_protected_when_previous_migration_was_not_production + Dir.chdir(app_path) do + output = `bin/rails generate model product name:string; + env RAILS_ENV=test bin/rails db:create db:migrate; + env RAILS_ENV=test bin/rails db:test:prepare test 2>&1` + + refute_match(/ActiveRecord::ProtectedEnvironmentError/, output) + end + 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/rails 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/rails 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/rails 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/rails 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: 25 Test LOC: 0 Code to Test Ratio: 1:0.0", + Dir.chdir(app_path) { `bin/rails stats` } + end + + def test_rails_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/rails routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_singular_resource_output_in_rake_routes + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + resource :post + end + RUBY + + expected_output = [" Prefix Verb URI Pattern Controller#Action", + " new_post GET /post/new(.:format) posts#new", + "edit_post GET /post/edit(.:format) posts#edit", + " post GET /post(.:format) posts#show", + " PATCH /post(.:format) posts#update", + " PUT /post(.:format) posts#update", + " DELETE /post(.:format) posts#destroy", + " POST /post(.:format) posts#create\n"].join("\n") + + output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } + assert_equal expected_output, output + end + + def test_rails_routes_with_global_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + post '/cart', to: 'cart#create' + get '/basketballs', to: 'basketball#index' + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails routes -g show` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path) { `bin/rails routes -g POST` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n POST /cart(.:format) cart#create\n", output + + output = Dir.chdir(app_path) { `bin/rails routes -g basketballs` } + assert_equal " Prefix Verb URI Pattern Controller#Action\n" \ + "basketballs GET /basketballs(.:format) basketball#index\n", output + end + + def test_rails_routes_with_controller_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + output = Dir.chdir(app_path) { `bin/rails routes -c cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path) { `bin/rails routes -c Cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path) { `bin/rails routes -c CartController` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rails_routes_with_namespaced_controller_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + namespace :admin do + resource :post + end + end + RUBY + expected_output = [" Prefix Verb URI Pattern Controller#Action", + " new_admin_post GET /admin/post/new(.:format) admin/posts#new", + "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit", + " admin_post GET /admin/post(.:format) admin/posts#show", + " PATCH /admin/post(.:format) admin/posts#update", + " PUT /admin/post(.:format) admin/posts#update", + " DELETE /admin/post(.:format) admin/posts#destroy", + " POST /admin/post(.:format) admin/posts#create\n"].join("\n") + + output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` } + assert_equal expected_output, output + + output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } + assert_equal expected_output, output + end + + def test_rails_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/rails 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_rake_routes_with_rake_options + 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 --rakefile Rakefile routes` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + 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/rails 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/rails db:migrate` + end + + require "#{rails_root}/config/environment" + + # loading a specific fixture + errormsg = Dir.chdir(app_path) { `bin/rails 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/rails db:migrate` + end + + app_file "test/fixtures/products.csv", "" + + require "#{rails_root}/config/environment" + errormsg = Dir.chdir(app_path) { `bin/rails 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; + RAILS_ENV=test bin/rails db:migrate test` + end + + assert_match(/7 runs, 9 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; + RAILS_ENV=test bin/rails 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_by_default + output = Dir.chdir(app_path) do + `bin/rails generate model Product; + bin/rails generate model Cart; + bin/rails generate scaffold LineItems product:references cart:belongs_to; + RAILS_ENV=test bin/rails db:migrate test` + end + + assert_match(/7 runs, 9 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, 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/rails db:migrate; + bin/rails 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/rails 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/rails 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/rails db:migrate db:schema:cache:dump` + end + assert File.exist?(File.join(app_path, "db", "schema_cache.yml")) + end + + def test_rake_clear_schema_cache + Dir.chdir(app_path) do + `bin/rails db:schema:cache:dump db:schema:cache:clear` + end + assert !File.exist?(File.join(app_path, "db", "schema_cache.yml")) + end + + def test_copy_templates + Dir.chdir(app_path) do + `bin/rails app: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/rails app:template LOCATION=template.rb` + end + + assert_match(/Hello, World!/, output) + 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..ccafc5b6f1 --- /dev/null +++ b/railties/test/application/rendering_test.rb @@ -0,0 +1,44 @@ +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 + 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..bc7580d6f4 --- /dev/null +++ b/railties/test/application/routing_test.rb @@ -0,0 +1,680 @@ +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 + 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 "/rails/info routes are accessible with globbing route present" do + app("development") + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '*foo', to: 'foo#index' + end + RUBY + + get "/rails/info" + assert_equal 302, last_response.status + + get "rails/info/routes" + assert_equal 200, last_response.status + + get "rails/info/properties" + assert_equal 200, last_response.status + end + + test "root takes precedence over internal welcome controller" do + app("development") + + assert_welcome get("/") + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render plain: "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 plain: 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 plain: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ActionController::Base + def index + render plain: "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 plain: "foo" + end + end + RUBY + + controller "admin/foo", <<-RUBY + module Admin + class FooController < ApplicationController + def index + render plain: "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", "http://www.apple.com", "/dashboard"], + "production" => ["bar", "http://www.microsoft.com", "/profile"] + }.each do |mode, (expected_action, expected_url, expected_mapping)| + test "reloads routes when configuration is changed in #{mode}" do + controller :foo, <<-RUBY + class FooController < ApplicationController + def bar + render plain: "bar" + end + + def baz + render plain: "baz" + end + + def custom + render plain: custom_url + end + + def mapping + render plain: url_for(User.new) + end + end + RUBY + + app_file "app/models/user.rb", <<-RUBY + class User + extend ActiveModel::Naming + include ActiveModel::Conversion + + def self.model_name + @_model_name ||= ActiveModel::Name.new(self.class, nil, "User") + end + + def persisted? + false + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#bar' + get 'custom', to: 'foo#custom' + get 'mapping', to: 'foo#mapping' + + direct(:custom) { "http://www.microsoft.com" } + resolve("User") { "/profile" } + end + RUBY + + app(mode) + + get "/foo" + assert_equal "bar", last_response.body + + get "/custom" + assert_equal "http://www.microsoft.com", last_response.body + + get "/mapping" + assert_equal "/profile", last_response.body + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get 'foo', to: 'foo#baz' + get 'custom', to: 'foo#custom' + get 'mapping', to: 'foo#mapping' + + direct(:custom) { "http://www.apple.com" } + resolve("User") { "/dashboard" } + end + RUBY + + sleep 0.1 + + get "/foo" + assert_equal expected_action, last_response.body + + get "/custom" + assert_equal expected_url, last_response.body + + get "/mapping" + assert_equal expected_mapping, 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 plain: "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 plain: "foo" + end + + def custom + render plain: custom_url + end + + def mapping + render plain: url_for(User.new) + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render plain: "bar" + end + end + RUBY + + app_file "app/models/user.rb", <<-RUBY + class User + extend ActiveModel::Naming + include ActiveModel::Conversion + + def self.model_name + @_model_name ||= ActiveModel::Name.new(self.class, nil, "User") + end + + def persisted? + false + 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' + + get 'custom', to: 'foo#custom' + direct(:custom) { 'http://www.apple.com' } + + get 'mapping', to: 'foo#mapping' + resolve('User') { '/profile' } + 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 + + get "/custom" + assert_equal "http://www.apple.com", last_response.body + assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url + + get "/mapping" + assert_equal "/profile", last_response.body + assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new) + + 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 + + get "/custom" + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.custom_url + end + + get "/mapping" + assert_equal 404, last_response.status + assert_raises NoMethodError do + assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new) + end + end + + test "named routes are cleared when reloading" do + app("development") + + controller :foo, <<-RUBY + class FooController < ApplicationController + def index + render plain: "foo" + end + end + RUBY + + controller :bar, <<-RUBY + class BarController < ApplicationController + def index + render plain: "bar" + end + end + RUBY + + app_file "app/models/user.rb", <<-RUBY + class User + extend ActiveModel::Naming + include ActiveModel::Conversion + + def self.model_name + @_model_name ||= ActiveModel::Name.new(self.class, nil, "User") + end + + def persisted? + false + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':locale/foo', to: 'foo#index', as: 'foo' + get 'users', to: 'foo#users', as: 'users' + direct(:microsoft) { 'http://www.microsoft.com' } + resolve('User') { '/profile' } + end + RUBY + + get "/en/foo" + assert_equal "foo", last_response.body + assert_equal "/en/foo", Rails.application.routes.url_helpers.foo_path(locale: "en") + assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url + assert_equal "/profile", Rails.application.routes.url_helpers.polymorphic_path(User.new) + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get ':locale/bar', to: 'bar#index', as: 'foo' + get 'users', to: 'foo#users', as: 'users' + direct(:apple) { 'http://www.apple.com' } + 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") + assert_equal "http://www.apple.com", Rails.application.routes.url_helpers.apple_url + assert_equal "/users", Rails.application.routes.url_helpers.polymorphic_path(User.new) + + assert_raises NoMethodError do + assert_equal "http://www.microsoft.com", Rails.application.routes.url_helpers.microsoft_url + end + 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 plain: 'yazilar#index' + end + end + RUBY + + get "/yazilars" + assert_equal 404, last_response.status + + get "/yazilar" + assert_equal 200, last_response.status + end + + test "reloading routes removes methods and doesn't undefine them" do + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/url', to: 'url#index' + end + RUBY + + app_file "app/models/url_helpers.rb", <<-RUBY + module UrlHelpers + def foo_path + "/foo" + end + end + RUBY + + app_file "app/models/context.rb", <<-RUBY + class Context + include UrlHelpers + include Rails.application.routes.url_helpers + end + RUBY + + controller "url", <<-RUBY + class UrlController < ApplicationController + def index + context = Context.new + render plain: context.foo_path + end + end + RUBY + + get "/url" + assert_equal "/foo", last_response.body + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/url', to: 'url#index' + get '/bar', to: 'foo#index', as: 'foo' + end + RUBY + + Rails.application.reload_routes! + + get "/url" + assert_equal "/bar", last_response.body + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/url', to: 'url#index' + end + RUBY + + Rails.application.reload_routes! + + get "/url" + assert_equal "/foo", last_response.body + 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..81f717b2c3 --- /dev/null +++ b/railties/test/application/runner_test.rb @@ -0,0 +1,133 @@ +require "isolation/abstract_unit" +require "env_helpers" + +module ApplicationTests + class RunnerTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + include EnvHelpers + + def setup + build_app + + # 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_set_argv_when_running_code + output = Dir.chdir(app_path) { + # Both long and short args, at start and end of ARGV + `bin/rails runner "puts ARGV.join(',')" --foo a1 -b a2 a3 --moo` + } + assert_equal "--foo,a1,-b,a2,a3,--moo", output.chomp + 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_no_minitest_loaded_in_production_mode + app_file "bin/print_features.rb", <<-SCRIPT + p $LOADED_FEATURES.grep(/minitest/) + SCRIPT + assert_match "[]", Dir.chdir(app_path) { + `RAILS_ENV=production bin/rails runner "bin/print_features.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_passes_extra_args_to_file + app_file "bin/program_name.rb", <<-SCRIPT + p ARGV + SCRIPT + + assert_match %w( a b ).to_s, Dir.chdir(app_path) { `bin/rails runner "bin/program_name.rb" a b` } + end + + def test_should_run_stdin + app_file "bin/count_users.rb", <<-SCRIPT + puts User.count + SCRIPT + + assert_match "42", Dir.chdir(app_path) { `cat bin/count_users.rb | bin/rails runner -` } + 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_runner_detects_syntax_errors + output = Dir.chdir(app_path) { `bin/rails runner "puts 'hello world" 2>&1` } + assert_not $?.success? + assert_match "unterminated string meets end of file", output + end + + def test_runner_detects_bad_script_name + output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` } + assert_not $?.success? + assert_match "undefined local variable or method `iuiqwiourowe' for", output + 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..bcd311c461 --- /dev/null +++ b/railties/test/application/test_runner_test.rb @@ -0,0 +1,769 @@ +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_via_backwardscompatibility + require "minitest/rails_plugin" + + assert_nothing_raised do + Minitest.run_via[:ruby] = true + end + + assert Minitest.run_via[:ruby] + 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_single_file_with_absolute_path + create_test_file :models, "foo" + create_test_file :models, "bar" + assert_match "1 runs, 1 assertions, 0 failures", run_test_command("#{app_path}/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_multiple_files_with_absolute_paths + create_test_file :models, "foo" + create_test_file :controllers, "foobar_controller" + create_test_file :models, "bar" + + assert_match "2 runs, 2 assertions, 0 failures", run_test_command("#{app_path}/test/models/foo_test.rb #{app_path}/test/controllers/foobar_controller_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 + create_test_file :models, "foo" + create_test_file :helpers, "bar_helper" + create_test_file :unit, "baz_unit" + create_test_file :controllers, "foobar_controller" + + Dir.chdir(app_path) do + `bin/rails test:units`.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 + 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 + create_test_file :mailers, "foo_mailer" + create_test_file :controllers, "bar_controller" + create_test_file :functional, "baz_functional" + create_test_file :models, "foo" + + Dir.chdir(app_path) do + `bin/rails test:functionals`.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 + 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_generated_controller_works_with_rails_test + create_controller + 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_multiple_folders_with_absolute_paths + create_test_file :models, "account" + create_test_file :controllers, "accounts_controller" + create_test_file :helpers, "foo_helper" + + run_test_command("#{app_path}/test/models #{app_path}/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_more_than_one_line_filter + app_file "test/models/post_test.rb", <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test "first filter" do + puts 'PostTest:FirstFilter' + assert true + end + + test "second filter" do + puts 'PostTest:SecondFilter' + assert true + end + + test "line filter does not run this" do + assert true + end + end + RUBY + + run_test_command("test/models/post_test.rb:4:9").tap do |output| + assert_match "PostTest:FirstFilter", output + assert_match "PostTest:SecondFilter", output + assert_match "2 runs, 2 assertions", output + end + end + + def test_more_than_one_line_filter_with_multiple_files + app_file "test/models/account_test.rb", <<-RUBY + require 'test_helper' + + class AccountTest < ActiveSupport::TestCase + test "first filter" do + puts 'AccountTest:FirstFilter' + assert true + end + + test "second filter" do + puts 'AccountTest:SecondFilter' + assert true + end + + test "line filter does not run this" do + assert true + end + end + RUBY + + app_file "test/models/post_test.rb", <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test "first filter" do + puts 'PostTest:FirstFilter' + assert true + end + + test "second filter" do + puts 'PostTest:SecondFilter' + assert true + end + + test "line filter does not run this" do + assert true + end + end + RUBY + + run_test_command("test/models/account_test.rb:4:9 test/models/post_test.rb:4:9").tap do |output| + assert_match "AccountTest:FirstFilter", output + assert_match "AccountTest:SecondFilter", output + assert_match "PostTest:FirstFilter", output + assert_match "PostTest:SecondFilter", output + assert_match "4 runs, 4 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_filters_trigger_only_one_runnable + app_file "test/models/post_test.rb", <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'truth' do + assert true + end + end + + class SecondPostTest < ActiveSupport::TestCase + test 'truth' do + assert false, 'ran second runnable' + end + end + RUBY + + # Pass seed guaranteeing failure. + run_test_command("test/models/post_test.rb:4 --seed 30410").tap do |output| + assert_no_match "ran second runnable", output + assert_match "1 runs, 1 assertions", output + end + end + + def test_line_filter_with_minitest_string_filter + app_file "test/models/post_test.rb", <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'by line' do + puts 'by line' + assert true + end + + test 'by name' do + puts 'by name' + assert true + end + end + RUBY + + run_test_command("test/models/post_test.rb:4 -n test_by_name").tap do |output| + assert_match "by line", output + assert_match "by name", output + assert_match "2 runs, 2 assertions", 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', __dir__) + + 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") + expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nbin/rails test test/models/post_test.rb:4\n\n\n\n} + assert_match expect, 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 + + def test_pass_TEST_env_on_rake_test + create_test_file :models, "account" + create_test_file :models, "post", pass: false + # This specifically verifies TEST for backwards compatibility with rake test + # as bin/rails test already supports running tests from a single file more cleanly. + output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` } + + assert_match "PostTest", output, "passing TEST= should run selected test" + assert_no_match "AccountTest", output, "passing TEST= should only run selected test" + assert_match "1 runs, 1 assertions", output + end + + def test_pass_rake_options + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile --trace=stdout test` } + + assert_match "1 runs, 1 assertions", output + assert_match "Execute test", output + end + + def test_rails_db_create_all_restores_db_connection + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rails db:create:all db:migrate && echo ".tables" | rails dbconsole` } + assert_match "ar_internal_metadata", output, "tables should be dumped" + end + + def test_rails_db_create_all_restores_db_connection_after_drop + create_test_file :models, "account" + Dir.chdir(app_path) { `bin/rails db:create:all` } # create all to avoid warnings + output = Dir.chdir(app_path) { `bin/rails db:drop:all db:create:all db:migrate && echo ".tables" | rails dbconsole` } + assert_match "ar_internal_metadata", output, "tables should be dumped" + end + + def test_rake_passes_TESTOPTS_to_minitest + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake test TESTOPTS=-v` } + assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test" + end + + def test_rake_passes_multiple_TESTOPTS_to_minitest + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake test TESTOPTS='-v --seed=1234'` } + assert_match "AccountTest#test_truth", output, "passing TEST= should run selected test" + assert_match "seed=1234", output, "passing TEST= should run selected test" + end + + def test_rake_runs_multiple_test_tasks + create_test_file :models, "account" + create_test_file :controllers, "accounts_controller" + output = Dir.chdir(app_path) { `bin/rake test:models test:controllers TESTOPTS='-v'` } + assert_match "AccountTest#test_truth", output + assert_match "AccountsControllerTest#test_truth", output + end + + def test_rake_db_and_test_tasks_parses_args_correctly + create_test_file :models, "account" + output = Dir.chdir(app_path) { `bin/rake db:migrate test:models TESTOPTS='-v' && echo ".tables" | rails dbconsole` } + assert_match "AccountTest#test_truth", output + assert_match "ar_internal_metadata", output + end + + def test_warnings_option + app_file "test/models/warnings_test.rb", <<-RUBY + require 'test_helper' + def test_warnings + a = 1 + end + RUBY + assert_match(/warning: assigned but unused variable/, + capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") }) + end + + def test_reset_sessions_before_rollback_on_system_tests + app_file "test/system/reset_session_before_rollback_test.rb", <<-RUBY + require "application_system_test_case" + + class ResetSessionBeforeRollbackTest < ApplicationSystemTestCase + def teardown_fixtures + puts "rollback" + super + end + + Capybara.singleton_class.prepend(Module.new do + def reset_sessions! + puts "reset sessions" + super + end + end) + + test "dummy" do + end + end + RUBY + + run_test_command("test/system/reset_session_before_rollback_test.rb").tap do |output| + assert_match "reset sessions\nrollback", output + assert_match "1 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output + end + end + + def test_system_tests_are_not_run_with_the_default_test_command + app_file "test/system/dummy_test.rb", <<-RUBY + require "application_system_test_case" + + class DummyTest < ApplicationSystemTestCase + test "something" do + assert true + end + end + RUBY + + run_test_command("").tap do |output| + assert_match "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output + end + end + + def test_system_tests_are_not_run_through_rake_test + app_file "test/system/dummy_test.rb", <<-RUBY + require "application_system_test_case" + + class DummyTest < ApplicationSystemTestCase + test "something" do + assert true + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake test` } + assert_match "0 runs, 0 assertions, 0 failures, 0 errors, 0 skips", output + end + + def test_system_tests_are_run_through_rake_test_when_given_in_TEST + app_file "test/system/dummy_test.rb", <<-RUBY + require "application_system_test_case" + + class DummyTest < ApplicationSystemTestCase + test "something" do + assert true + end + end + RUBY + + output = Dir.chdir(app_path) { `bin/rake test TEST=test/system/dummy_test.rb` } + assert_match "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips", output + 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 create_controller + script "generate controller admin/dashboard index" + end + + def run_migration + Dir.chdir(app_path) { `bin/rails 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..32d2a6857c --- /dev/null +++ b/railties/test/application/test_test.rb @@ -0,0 +1,338 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class TestTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "simple successful test" 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 "after_run" do + app_file "test/unit/foo_test.rb", <<-RUBY + require 'test_helper' + + Minitest.after_run { puts "WORLD" } + Minitest.after_run { puts "HELLO" } + + class FooTest < ActiveSupport::TestCase + def test_truth + assert true + end + end + RUBY + + result = assert_successful_test_run "unit/foo_test.rb" + assert_equal ["HELLO", "WORLD"], result.scan(/HELLO|WORLD/) # only once and in correct order + end + + test "simple failed test" do + app_file "test/unit/foo_test.rb", <<-RUBY + require 'test_helper' + + class FooTest < ActiveSupport::TestCase + def test_truth + assert false + end + end + RUBY + + assert_unsuccessful_run "unit/foo_test.rb", "Failed assertion" + 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_not_includes result, "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/rails 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_includes result, 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..37f129475c --- /dev/null +++ b/railties/test/application/url_generation_test.rb @@ -0,0 +1,57 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class UrlGenerationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def app + Rails.application + end + + test "it works" do + 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 plain: 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 + 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 diff --git a/railties/test/application/version_test.rb b/railties/test/application/version_test.rb new file mode 100644 index 0000000000..6b419ae7ae --- /dev/null +++ b/railties/test/application/version_test.rb @@ -0,0 +1,24 @@ +require "isolation/abstract_unit" +require "rails/gem_version" + +class VersionTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test "command works" do + output = Dir.chdir(app_path) { `bin/rails version` } + assert_equal "Rails #{Rails.gem_version}\n", output + end + + test "short-cut alias works" do + output = Dir.chdir(app_path) { `bin/rails -v` } + assert_equal "Rails #{Rails.gem_version}\n", output + end +end |