diff options
Diffstat (limited to 'actionpack/test')
-rw-r--r-- | actionpack/test/abstract_unit.rb | 6 | ||||
-rw-r--r-- | actionpack/test/controller/http_basic_authentication_test.rb | 21 | ||||
-rw-r--r-- | actionpack/test/controller/metal/renderers_test.rb | 42 | ||||
-rw-r--r-- | actionpack/test/controller/parameters/parameters_permit_test.rb | 18 | ||||
-rw-r--r-- | actionpack/test/controller/render_other_test.rb | 24 | ||||
-rw-r--r-- | actionpack/test/controller/renderers_test.rb | 90 | ||||
-rw-r--r-- | actionpack/test/controller/request_forgery_protection_test.rb | 172 | ||||
-rw-r--r-- | actionpack/test/dispatch/request_test.rb | 21 | ||||
-rw-r--r-- | actionpack/test/dispatch/routing/inspector_test.rb | 38 | ||||
-rw-r--r-- | actionpack/test/dispatch/routing_test.rb | 21 | ||||
-rw-r--r-- | actionpack/test/dispatch/ssl_test.rb | 45 |
11 files changed, 456 insertions, 42 deletions
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index ef7aab72c6..604ba267d6 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -371,6 +371,12 @@ module RoutingTestHelpers end end +class MetalRenderingController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + include ActionController::Renderers +end + class ResourcesController < ActionController::Base def index() head :ok end alias_method :show, :index diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 0a5e5402b9..adcf259317 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -5,6 +5,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase before_action :authenticate, only: :index before_action :authenticate_with_request, only: :display before_action :authenticate_long_credentials, only: :show + before_action :auth_with_special_chars, only: :special_creds http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search @@ -20,6 +21,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase render plain: 'Only for loooooong credentials' end + def special_creds + render plain: 'Only for special credentials' + end + def search render plain: 'All inline' end @@ -40,6 +45,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase end end + def auth_with_special_chars + authenticate_or_request_with_http_basic do |username, password| + username == 'login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' && password == 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t' + end + end + def authenticate_long_credentials authenticate_or_request_with_http_basic do |username, password| username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890' @@ -100,7 +111,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_no_match(/\n/, result) end - test "succesful authentication with uppercase authorization scheme" do + test "successful authentication with uppercase authorization scheme" do @request.env['HTTP_AUTHORIZATION'] = "BASIC #{::Base64.encode64("lifo:world")}" get :index @@ -133,6 +144,14 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_equal 'Definitely Maybe', @response.body end + test "authentication request with valid credential special chars" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials('login!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t', 'pwd:!@#$%^&*()_+{}[];"\',./<>?`~ \n\r\t') + get :special_creds + + assert_response :success + assert_equal 'Only for special credentials', @response.body + end + test "authenticate with class method" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath') get :search diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb new file mode 100644 index 0000000000..007866a559 --- /dev/null +++ b/actionpack/test/controller/metal/renderers_test.rb @@ -0,0 +1,42 @@ +require 'abstract_unit' +require 'active_support/core_ext/hash/conversions' + +class MetalRenderingJsonController < MetalRenderingController + class Model + def to_json(options = {}) + { a: 'b' }.to_json(options) + end + + def to_xml(options = {}) + { a: 'b' }.to_xml(options) + end + end + + use_renderers :json + + def one + render json: Model.new + end + + def two + render xml: Model.new + end +end + +class RenderersMetalTest < ActionController::TestCase + tests MetalRenderingJsonController + + def test_render_json + get :one + assert_response :success + assert_equal({ a: 'b' }.to_json, @response.body) + assert_equal 'application/json', @response.content_type + end + + def test_render_xml + get :two + assert_response :success + assert_equal(" ", @response.body) + assert_equal 'text/plain', @response.content_type + end +end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index f23aa599c1..896bda2597 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -294,8 +294,16 @@ class ParametersPermitTest < ActiveSupport::TestCase end test "to_unsafe_h returns unfiltered params" do - assert @params.to_h.is_a? ActiveSupport::HashWithIndifferentAccess - assert_not @params.to_h.is_a? ActionController::Parameters + assert @params.to_unsafe_h.is_a? ActiveSupport::HashWithIndifferentAccess + assert_not @params.to_unsafe_h.is_a? ActionController::Parameters + end + + test "to_unsafe_h returns unfiltered params even after accessing few keys" do + params = ActionController::Parameters.new("f"=>{"language_facet"=>["Tibetan"]}) + expected = {"f"=>{"language_facet"=>["Tibetan"]}} + + assert params['f'].is_a? ActionController::Parameters + assert_equal expected, params.to_unsafe_h end test "to_h only deep dups Ruby collections" do @@ -325,4 +333,10 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal({ 'companies' => [ company, :acme ] }, params.to_unsafe_h) assert_not company.dupped end + + test "included? returns true when the key is present" do + assert @params.include? :person + assert @params.include? 'person' + assert_not @params.include? :gorilla + end end diff --git a/actionpack/test/controller/render_other_test.rb b/actionpack/test/controller/render_other_test.rb deleted file mode 100644 index 1f5215ac55..0000000000 --- a/actionpack/test/controller/render_other_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'abstract_unit' - - -class RenderOtherTest < ActionController::TestCase - class TestController < ActionController::Base - def render_simon_says - render :simon => "foo" - end - end - - tests TestController - - def test_using_custom_render_option - ActionController.add_renderer :simon do |says, options| - self.content_type = Mime[:text] - self.response_body = "Simon says: #{says}" - end - - get :render_simon_says - assert_equal "Simon says: foo", @response.body - ensure - ActionController.remove_renderer :simon - end -end diff --git a/actionpack/test/controller/renderers_test.rb b/actionpack/test/controller/renderers_test.rb new file mode 100644 index 0000000000..e6c2e4636e --- /dev/null +++ b/actionpack/test/controller/renderers_test.rb @@ -0,0 +1,90 @@ +require 'abstract_unit' +require 'controller/fake_models' +require 'active_support/logger' + +class RenderersTest < ActionController::TestCase + class XmlRenderable + def to_xml(options) + options[:root] ||= "i-am-xml" + "<#{options[:root]}/>" + end + end + class JsonRenderable + def as_json(options={}) + hash = { :a => :b, :c => :d, :e => :f } + hash.except!(*options[:except]) if options[:except] + hash + end + + def to_json(options = {}) + super :except => [:c, :e] + end + end + class CsvRenderable + def to_csv + "c,s,v" + end + end + class TestController < ActionController::Base + + def render_simon_says + render :simon => "foo" + end + + def respond_to_mime + respond_to do |type| + type.json do + render json: JsonRenderable.new + end + type.js { render json: 'JS', callback: 'alert' } + type.csv { render csv: CsvRenderable.new } + type.xml { render xml: XmlRenderable.new } + type.html { render body: "HTML" } + type.rss { render body: "RSS" } + type.all { render body: "Nothing" } + type.any(:js, :xml) { render body: "Either JS or XML" } + end + end + end + + tests TestController + + def setup + # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get + # a more accurate simulation of what happens in "real life". + super + @controller.logger = ActiveSupport::Logger.new(nil) + end + + def test_using_custom_render_option + ActionController.add_renderer :simon do |says, options| + self.content_type = Mime[:text] + self.response_body = "Simon says: #{says}" + end + + get :render_simon_says + assert_equal "Simon says: foo", @response.body + ensure + ActionController.remove_renderer :simon + end + + def test_raises_missing_template_no_renderer + assert_raise ActionView::MissingTemplate do + get :respond_to_mime, format: 'csv' + end + assert_equal Mime[:csv], @response.content_type + assert_equal "", @response.body + end + + def test_adding_csv_rendering_via_renderers_add + ActionController::Renderers.add :csv do |value, options| + send_data value.to_csv, type: Mime[:csv] + end + @request.accept = "text/csv" + get :respond_to_mime, format: 'csv' + assert_equal Mime[:csv], @response.content_type + assert_equal "c,s,v", @response.body + ensure + ActionController::Renderers.remove :csv + end +end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 87a8ed3dc9..1984ad8825 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -128,6 +128,23 @@ class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsin end end +class PerFormTokensController < ActionController::Base + protect_from_forgery :with => :exception + self.per_form_csrf_tokens = true + + def index + render inline: "<%= form_tag (params[:form_path] || '/per_form_tokens/post_one'), method: (params[:form_method] || :post) %>" + end + + def post_one + render plain: '' + end + + def post_two + render plain: '' + end +end + # common test methods module RequestForgeryProtectionTests def setup @@ -623,3 +640,158 @@ class CustomAuthenticityParamControllerTest < ActionController::TestCase end end end + +class PerFormTokensControllerTest < ActionController::TestCase + def test_per_form_token_is_same_size_as_global_token + get :index + expected = ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH + actual = @controller.send(:per_form_csrf_token, session, '/path', 'post').size + assert_equal expected, actual + end + + def test_accepts_token_for_correct_path_and_method + get :index + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) + expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') + assert_equal expected, actual + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + assert_nothing_raised do + post :post_one, params: {custom_authenticity_token: form_token} + end + assert_response :success + end + + def test_rejects_token_for_incorrect_path + get :index + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) + expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') + assert_equal expected, actual + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_two' + assert_raises(ActionController::InvalidAuthenticityToken) do + post :post_two, params: {custom_authenticity_token: form_token} + end + end + + def test_rejects_token_for_incorrect_method + get :index + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) + expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') + assert_equal expected, actual + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + assert_raises(ActionController::InvalidAuthenticityToken) do + patch :post_one, params: {custom_authenticity_token: form_token} + end + end + + def test_accepts_global_csrf_token + get :index + + token = @controller.send(:form_authenticity_token) + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + assert_nothing_raised do + post :post_one, params: {custom_authenticity_token: token} + end + assert_response :success + end + + def test_ignores_params + get :index, params: {form_path: '/per_form_tokens/post_one?foo=bar'} + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + actual = @controller.send(:unmask_token, Base64.strict_decode64(form_token)) + expected = @controller.send(:per_form_csrf_token, session, '/per_form_tokens/post_one', 'post') + assert_equal expected, actual + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one?foo=baz' + assert_nothing_raised do + post :post_one, params: {custom_authenticity_token: form_token, baz: 'foo'} + end + assert_response :success + end + + def test_ignores_trailing_slash_during_generation + get :index, params: {form_path: '/per_form_tokens/post_one/'} + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one' + assert_nothing_raised do + post :post_one, params: {custom_authenticity_token: form_token} + end + assert_response :success + end + + def test_ignores_trailing_slash_during_validation + get :index + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one/' + assert_nothing_raised do + post :post_one, params: {custom_authenticity_token: form_token} + end + assert_response :success + end + + def test_method_is_case_insensitive + get :index, params: {form_method: "POST"} + + form_token = nil + assert_select 'input[name=custom_authenticity_token]' do |elts| + form_token = elts.first['value'] + assert_not_nil form_token + end + + # This is required because PATH_INFO isn't reset between requests. + @request.env['PATH_INFO'] = '/per_form_tokens/post_one/' + assert_nothing_raised do + post :post_one, params: {custom_authenticity_token: form_token} + end + assert_response :success + end +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 7dd9d05e62..0edad72fd9 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -897,6 +897,27 @@ class RequestFormat < BaseRequestTest ActionDispatch::Request.ignore_accept_header = old_ignore_accept_header end end + + test "format taken from the path extension" do + request = stub_request 'PATH_INFO' => '/foo.xml' + assert_called(request, :parameters, times: 1, returns: {}) do + assert_equal [Mime[:xml]], request.formats + end + + request = stub_request 'PATH_INFO' => '/foo.123' + assert_called(request, :parameters, times: 1, returns: {}) do + assert_equal [Mime[:html]], request.formats + end + end + + test "formats from accept headers have higher precedence than path extension" do + request = stub_request 'HTTP_ACCEPT' => 'application/json', + 'PATH_INFO' => '/foo.xml' + + assert_called(request, :parameters, times: 1, returns: {}) do + assert_equal [Mime[:json]], request.formats + end + end end class RequestMimeType < BaseRequestTest diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index a17d07c40b..7382c267c7 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -7,6 +7,9 @@ class MountedRackApp end end +class Rails::DummyController +end + module ActionDispatch module Routing class RoutesInspectorTest < ActiveSupport::TestCase @@ -331,6 +334,41 @@ module ActionDispatch " cart GET /cart(.:format) cart#show" ], output end + + def test_routes_with_undefined_filter + output = draw(:filter => 'Rails::MissingController') do + get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + end + + assert_equal [ + "The controller Rails::MissingController does not exist!", + "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + ], output + end + + def test_no_routes_matched_filter + output = draw(:filter => 'rails/dummy') do + get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ + end + + assert_equal [ + "No routes were found for this controller", + "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + ], output + end + + def test_no_routes_were_defined + output = draw(:filter => 'Rails::DummyController') { } + + assert_equal [ + "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." + ], output + end + end end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 82222a141c..62d65ec5c0 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -3578,6 +3578,27 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'HEAD', @response.body end + def test_passing_action_parameters_to_url_helpers_raises_error_if_parameters_are_not_permitted + draw do + root :to => 'projects#index' + end + params = ActionController::Parameters.new(id: '1') + + assert_raises ArgumentError do + root_path(params) + end + end + + def test_passing_action_parameters_to_url_helpers_is_allowed_if_parameters_are_permitted + draw do + root :to => 'projects#index' + end + params = ActionController::Parameters.new(id: '1') + params.permit! + + assert_equal '/?id=1', root_path(params) + end + private def draw(&block) diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 7a5b8393dc..c66a0e6a7a 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -12,25 +12,31 @@ class SSLTest < ActionDispatch::IntegrationTest end class RedirectSSLTest < SSLTest - def assert_not_redirected(url, headers: {}) - self.app = build_app + + def assert_not_redirected(url, headers: {}, redirect: {}, deprecated_host: nil, + deprecated_port: nil) + + self.app = build_app ssl_options: { redirect: redirect, + host: deprecated_host, port: deprecated_port + } + get url, headers: headers assert_response :ok end - def assert_redirected(host: nil, port: nil, status: 301, body: [], - deprecated_host: nil, deprecated_port: nil, + def assert_redirected(redirect: {}, deprecated_host: nil, deprecated_port: nil, from: 'http://a/b?c=d', to: from.sub('http', 'https')) - self.app = build_app ssl_options: { - redirect: { host: host, port: port, status: status, body: body }, + redirect = { status: 301, body: [] }.merge(redirect) + + self.app = build_app ssl_options: { redirect: redirect, host: deprecated_host, port: deprecated_port } get from - assert_response status + assert_response redirect[:status] || 301 assert_redirected_to to - assert_equal body.join, @response.body + assert_equal redirect[:body].join, @response.body end test 'https is not redirected' do @@ -46,31 +52,31 @@ class RedirectSSLTest < SSLTest end test 'redirect with non-301 status' do - assert_redirected status: 307 + assert_redirected redirect: { status: 307 } end test 'redirect with custom body' do - assert_redirected body: ['foo'] + assert_redirected redirect: { body: ['foo'] } end test 'redirect to specific host' do - assert_redirected host: 'ssl', to: 'https://ssl/b?c=d' + assert_redirected redirect: { host: 'ssl' }, to: 'https://ssl/b?c=d' end test 'redirect to default port' do - assert_redirected port: 443 + assert_redirected redirect: { port: 443 } end test 'redirect to non-default port' do - assert_redirected port: 8443, to: 'https://a:8443/b?c=d' + assert_redirected redirect: { port: 8443 }, to: 'https://a:8443/b?c=d' end test 'redirect to different host and non-default port' do - assert_redirected host: 'ssl', port: 8443, to: 'https://ssl:8443/b?c=d' + assert_redirected redirect: { host: 'ssl', port: 8443 }, to: 'https://ssl:8443/b?c=d' end test 'redirect to different host including port' do - assert_redirected host: 'ssl:443', to: 'https://ssl:443/b?c=d' + assert_redirected redirect: { host: 'ssl:443' }, to: 'https://ssl:443/b?c=d' end test ':host is deprecated, moved within redirect: { host: … }' do @@ -84,6 +90,10 @@ class RedirectSSLTest < SSLTest assert_redirected deprecated_port: 1, to: 'https://a:1/b?c=d' end end + + test 'no redirect with redirect set to false' do + assert_not_redirected 'http://example.org', redirect: false + end end class StrictTransportSecurityTest < SSLTest @@ -187,6 +197,11 @@ class SecureCookiesTest < SSLTest assert_cookies 'problem=def; path=/; Secure; HttpOnly' end + def test_cookies_as_not_secure_with_secure_cookies_disabled + get headers: { 'Set-Cookie' => DEFAULT }, ssl_options: { secure_cookies: false } + assert_cookies(*DEFAULT.split("\n")) + end + def test_no_cookies get assert_nil response.headers['Set-Cookie'] |