diff --git a/test/test_controller.rb b/test/test_controller.rb new file mode 100644 index 0000000..a41b9bd --- /dev/null +++ b/test/test_controller.rb @@ -0,0 +1,116 @@ +require 'minitest/autorun' +require 'prism' +require 'support' +require 'tmpdir' +require 'fileutils' + +class ControllerTest < Minitest::Test + include TestSupport + + def setup + @views_dir = Dir.mktmpdir('prism-controller-views') + write_views + define_controller + define_app + end + + def teardown + FileUtils.remove_entry(@views_dir) + %i[PostsController PostsApp].each { |name| Object.send(:remove_const, name) if Object.const_defined?(name) } + end + + def write_views + FileUtils.mkdir_p(File.join(@views_dir, 'posts')) + FileUtils.mkdir_p(File.join(@views_dir, 'layouts')) + File.write(File.join(@views_dir, 'layouts', 'application.erb'), <<~ERB) + <%== content %> + ERB + File.write(File.join(@views_dir, 'posts', 'index.erb'), <<~ERB) + + ERB + File.write(File.join(@views_dir, 'posts', '_post.erb'), '
  • <%= title %>
  • ') + File.write(File.join(@views_dir, 'posts', 'show.erb'), '

    <%= id %>

    ') + end + + def define_controller + views_dir = @views_dir + controller_class = Class.new(Prism::Controller) do + layout :application + views_root views_dir + + before_action :set_common, only: %i[index show] + after_action :mark_served + + def self.served_log + @served_log ||= [] + end + + def set_common + @posts = ['first post', 'second post'] + end + + def index + render(:index, locals: { posts: @posts }) + end + + def show + render(:show, layout: nil, locals: { id: params['id'] }) + end + + def create + halt(201, JSON.generate(created: params['title'])) + end + + def mark_served + self.class.served_log << action_name + end + end + Object.const_set(:PostsController, controller_class) + end + + def define_app + app_class = Class.new(Prism::Application) do + get '/posts', to: 'posts#index', as: :posts + get '/posts/:id', to: 'posts#show', as: :post + post '/posts', to: 'posts#create' + end + Object.const_set(:PostsApp, app_class) + end + + def call(method, path, **opts) + PostsApp.call(env_for(method, path, **opts)) + end + + def test_index_renders_with_layout_and_partials + _, _, body = call('GET', '/posts') + html = body.join + assert_includes html, '' + assert_includes html, '
  • first post
  • ' + assert_includes html, '
  • second post
  • ' + end + + def test_show_renders_action_template + _, _, body = call('GET', '/posts/42') + assert_equal '

    42

    ', body.join + end + + def test_before_action_runs_with_only_filter + PostsController.served_log.clear + call('GET', '/posts') + call('GET', '/posts/1') + call('POST', '/posts') + assert_equal %w[index show create], PostsController.served_log + end + + def test_create_action_uses_halt + status, _, body = call('POST', '/posts', headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }, body: 'title=hello') + assert_equal 201, status + assert_equal 'hello', JSON.parse(body.join)['created'] + end + + def test_named_route_url_for + status, _, body = call('GET', '/posts/9') + assert_equal 200, status + assert_equal PostsApp.router.path_to(:post, id: 9), '/posts/9' + end +end diff --git a/test/test_controller_filters.rb b/test/test_controller_filters.rb new file mode 100644 index 0000000..812d492 --- /dev/null +++ b/test/test_controller_filters.rb @@ -0,0 +1,86 @@ +require 'minitest/autorun' +require 'prism' +require 'support' + +class FilterController < Prism::Controller + before_action(only: %i[index]) { @log = (@log || []) << 'inline-before' } + before_action :require_token, except: %i[public_action] + after_action :log_after, except: %i[public_action] + + def self.after_log + @after_log ||= [] + end + + def index + json(log: @log) + end + + def secret + json(reached: true) + end + + def public_action + json(reached: true) + end + + private + + def require_token + halt(401, 'no token') unless params['token'] == 'ok' + end + + def log_after + self.class.after_log << action_name + end +end + +class FilterApp < Prism::Application + get '/index', to: 'filter#index' + get '/secret', to: 'filter#secret' + get '/public', to: 'filter#public_action' +end + +class ControllerFiltersTest < Minitest::Test + include TestSupport + + def setup + FilterController.after_log.clear + end + + def call(method, path, **opts) + FilterApp.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'] + end + + def test_before_action_with_except_still_guards_other_actions + status, _, body = call('GET', '/secret') + assert_equal 401, status + assert_equal 'no token', body.join + end + + def test_before_action_except_skips_the_named_action + status, _, body = call('GET', '/public') + assert_equal 200, status + assert JSON.parse(body.join)['reached'] + end + + def test_halted_before_action_prevents_the_action_body_from_running + status, _, body = call('GET', '/secret') + refute_equal 200, status + refute_includes body.join, 'reached' + end + + def test_after_action_runs_when_before_action_passes + call('GET', '/secret?token=ok') + assert_includes FilterController.after_log, 'secret' + end + + def test_after_action_except_is_honored + call('GET', '/public') + refute_includes FilterController.after_log, 'public_action' + end +end