rails/railties/test/application/routing_test.rb
Justin Searls 3b83758680 Enable force_ssl=true in production by default
I will admit to deploying an app into production and leaving it there for weeks before realizing that authenticated traffic was being transported un-secured HTTP. I'd been operating under the false assumption that `config.force_ssl` would be `true` in production by default for new apps.

Suggesting this change to gauge interest and start a conversation. Since this option was introduced, the state of the web has really changed with Let's Encrypt certificates, and HTTPS has become table stakes for most hosting services. It feels like the time is right to enable Strict-Transport-Security by default for new apps.

Co-authored-by: Aaron Patterson <aaron@rubyonrails.org>
Co-authored-by: Guillermo Iguaran <guilleiguaran@gmail.com>
Co-authored-by: vinibispo <vini.bispo015@gmail.com>
2023-06-09 16:27:10 -07:00

795 lines
20 KiB
Ruby

# frozen_string_literal: true
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 "appended root takes precedence over internal welcome controller" do
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
end
Rails.application.routes.append do
get "/", to: "foo#index"
end
RUBY
app("development")
get "/"
assert_equal "foo", last_response.body
end
test "rails/welcome in production" do
app("production")
get("/", {}, "HTTPS" => "on")
assert_equal 404, last_response.status
end
test "rails/info in production" do
app("production")
get("/rails/info", {}, "HTTPS" => "on")
assert_equal 404, last_response.status
end
test "rails/info/routes in production" do
app("production")
get("/rails/info/routes", {}, "HTTPS" => "on")
assert_equal 404, last_response.status
end
test "rails/info/properties in production" do
app("production")
get("/rails/info/properties", {}, "HTTPS" => "on")
assert_equal 404, last_response.status
end
test "rails/health in production" do
app("production")
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
get "up" => "rails/health#show", as: :rails_health_check
end
RUBY
get("/up", {}, "HTTPS" => "on")
assert_equal 200, last_response.status
end
test "simple controller" do
simple_controller
app "development"
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
app "development"
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
app "development"
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
app "development"
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
app "development"
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
app "development"
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
test "routes drawing from config/routes" do
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
draw :external
end
RUBY
app_file "config/routes/external.rb", <<-RUBY
get ':controller/:action'
RUBY
controller :success, <<-RUBY
class SuccessController < ActionController::Base
def index
render plain: "success!"
end
end
RUBY
app "development"
get "/success/index"
assert_equal "success!", 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
draw :external
get 'custom', to: 'foo#custom'
get 'mapping', to: 'foo#mapping'
direct(:custom) { "http://www.microsoft.com" }
resolve("User") { "/profile" }
end
RUBY
app_file "config/routes/external.rb", <<-RUBY
get 'foo', to: 'foo#bar'
RUBY
app(mode)
https = (mode == "production" ? "on" : "off")
get("/foo", {}, "HTTPS" => https)
assert_equal "bar", last_response.body
get("/custom", {}, "HTTPS" => https)
assert_equal "http://www.microsoft.com", last_response.body
get("/mapping", {}, "HTTPS" => https)
assert_equal "/profile", last_response.body
app_file "config/routes.rb", <<-RUBY
Rails.application.routes.draw do
draw :another_external
get 'custom', to: 'foo#custom'
get 'mapping', to: 'foo#mapping'
direct(:custom) { "http://www.apple.com" }
resolve("User") { "/dashboard" }
end
RUBY
app_file "config/routes/another_external.rb", <<-RUBY
get 'foo', to: 'foo#baz'
RUBY
sleep 0.1
get("/foo", {}, "HTTPS" => https)
assert_equal expected_action, last_response.body
get("/custom", {}, "HTTPS" => https)
assert_equal expected_url, last_response.body
get("/mapping", {}, "HTTPS" => https)
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
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
Rails.application.routes.url_helpers.bar_path
end
get "/custom"
assert_equal 404, last_response.status
assert_raises NoMethodError do
Rails.application.routes.url_helpers.custom_url
end
get "/mapping"
assert_equal 404, last_response.status
assert_raises NoMethodError do
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
Rails.application.routes.url_helpers.microsoft_url
end
end
test "resource routing with irregular inflection" do
app("development")
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("development")
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
test "request to rails/welcome for api_only app is successful" do
add_to_config <<-RUBY
config.api_only = true
config.action_dispatch.show_exceptions = :none
config.action_controller.allow_forgery_protection = true
RUBY
app "development"
get "/"
assert_equal 200, last_response.status
end
test "request to rails/welcome is successful when default_protect_from_forgery is false" do
add_to_config <<-RUBY
config.action_dispatch.show_exceptions = :none
config.action_controller.default_protect_from_forgery = false
RUBY
app "development"
get "/"
assert_equal 200, last_response.status
end
end
end