From daa6632b393fa91e03fa0f6de3c8a41b39237632 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 12:36:52 +0200 Subject: [PATCH] test: cover BaseWithFiltersController, stamp_base --- test/test_controller_filters.rb | 61 ++++++++++++++++ test/test_controller_rescue_from.rb | 104 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 test/test_controller_rescue_from.rb diff --git a/test/test_controller_filters.rb b/test/test_controller_filters.rb index 812d492..238de91 100644 --- a/test/test_controller_filters.rb +++ b/test/test_controller_filters.rb @@ -40,6 +40,48 @@ class FilterApp < Prism::Application get '/public', to: 'filter#public_action' end +class BaseWithFiltersController < Prism::Controller + before_action :stamp_base, only: %i[index] + after_action :stamp_base_after, only: %i[index] + + private + + def stamp_base + @trail = (@trail || []) << 'base-before' + end + + def stamp_base_after + self.class.trail_after << 'base-before-runs-first?' + end +end + +class InheritedFiltersController < BaseWithFiltersController + before_action :stamp_sub, only: %i[index] + + def self.trail_after + @trail_after ||= [] + end + + def index + json(trail: @trail) + end + + def other + json(trail: @trail) + end + + private + + def stamp_sub + @trail << 'sub-before' + end +end + +class InheritedFiltersApp < Prism::Application + get '/inherited', to: 'inherited_filters#index' + get '/inherited-other', to: 'inherited_filters#other' +end + class ControllerFiltersTest < Minitest::Test include TestSupport @@ -51,6 +93,10 @@ class ControllerFiltersTest < Minitest::Test FilterApp.call(env_for(method, path, **opts)) end + def call_inherited(method, path, **opts) + InheritedFiltersApp.call(env_for(method, path, **opts)) + end + def test_inline_before_action_block_runs_and_scoped_to_only _, _, body = call('GET', '/index?token=ok') assert_equal ['inline-before'], JSON.parse(body.join)['log'] @@ -83,4 +129,19 @@ class ControllerFiltersTest < Minitest::Test call('GET', '/public') refute_includes FilterController.after_log, 'public_action' end + + def test_before_action_declared_on_a_superclass_runs_for_a_subclass_action + _, _, body = call_inherited('GET', '/inherited') + assert_equal %w[base-before sub-before], JSON.parse(body.join)['trail'] + end + + def test_after_action_declared_on_a_superclass_runs_for_a_subclass_action + call_inherited('GET', '/inherited') + assert_includes InheritedFiltersController.trail_after, 'base-before-runs-first?' + end + + def test_inherited_before_action_only_option_still_scopes_to_the_declared_action + _, _, body = call_inherited('GET', '/inherited-other') + assert_nil JSON.parse(body.join)['trail'] + end end diff --git a/test/test_controller_rescue_from.rb b/test/test_controller_rescue_from.rb new file mode 100644 index 0000000..879bd06 --- /dev/null +++ b/test/test_controller_rescue_from.rb @@ -0,0 +1,104 @@ +require 'minitest/autorun' +require 'prism' +require 'support' + +class RescueFromMissingRecordError < StandardError; end +class RescueFromSpecificError < RescueFromMissingRecordError; end + +class RescueFromBaseController < Prism::Controller + rescue_from StandardError, with: :handle_any_error + + private + + def handle_any_error(exception) + halt(500, "base handled #{exception.class}") + end +end + +# Declares a MORE specific handler than its superclass's catch-all - proves +# subclass handlers win even though the superclass's StandardError handler +# would also match. +class RescueFromController < RescueFromBaseController + rescue_from RescueFromMissingRecordError, with: :handle_missing_record + rescue_from ArgumentError do |exception| + halt(400, "block handled #{exception.message}") + end + + def boom_missing + raise RescueFromMissingRecordError, 'gone' + end + + def boom_specific_subclass + raise RescueFromSpecificError, 'gone' + end + + def boom_argument + raise ArgumentError, 'bad input' + end + + def boom_unhandled + raise ZeroDivisionError, 'nope' + end + + def before_action_boom + raise RescueFromMissingRecordError, 'blocked before the action ran' + end + + before_action :before_action_boom, only: %i[protected_action] + + def protected_action + halt(200, 'should never reach here') + end + + private + + def handle_missing_record(exception) + halt(404, "missing #{exception.message}") + end +end + +class RescueFromApp < Prism::Application + get '/missing', to: 'rescue_from#boom_missing' + get '/missing_subclass', to: 'rescue_from#boom_specific_subclass' + get '/argument', to: 'rescue_from#boom_argument' + get '/unhandled', to: 'rescue_from#boom_unhandled' + get '/protected', to: 'rescue_from#protected_action' +end + +class ControllerRescueFromTest < Minitest::Test + include TestSupport + + def call(method, path, **opts) + RescueFromApp.call(env_for(method, path, **opts)) + end + + def test_specific_handler_declared_on_the_subclass_wins_over_the_superclass_catch_all + status, _, body = call('GET', '/missing') + assert_equal 404, status + assert_equal 'missing gone', body.join + end + + def test_is_a_matching_catches_a_subclass_of_the_registered_exception + status, _, body = call('GET', '/missing_subclass') + assert_equal 404, status + assert_equal 'missing gone', body.join + end + + def test_block_handler_runs_with_the_controller_as_self + status, _, body = call('GET', '/argument') + assert_equal 400, status + assert_equal 'block handled bad input', body.join + end + + def test_falls_back_to_the_superclass_catch_all_when_no_specific_handler_matches + status, _, body = call('GET', '/unhandled') + assert_equal 500, status + assert_equal 'base handled ZeroDivisionError', body.join + end + + def test_rescue_from_also_catches_exceptions_raised_inside_before_actions + status, _, body = call('GET', '/protected') + assert_equal 404, status + assert_equal 'missing blocked before the action ran', body.join + end +end