From 1ecdf2b899f419eed12029acbe15583d6a5e37ea Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 13 Sep 2026 22:53:07 +0200 Subject: [PATCH] test: cover TransactionalFixtures, Widget --- test/test_testing_transactional.rb | 142 +++++++++++++++++++++++++++++ test/test_view.rb | 12 +++ 2 files changed, 154 insertions(+) create mode 100644 test/test_testing_transactional.rb diff --git a/test/test_testing_transactional.rb b/test/test_testing_transactional.rb new file mode 100644 index 0000000..d94c3dc --- /dev/null +++ b/test/test_testing_transactional.rb @@ -0,0 +1,142 @@ +require 'minitest/autorun' +require 'prism' +require 'tmpdir' + +# Fixtures shared by the two test classes below live in their own module so +# the dedicated Store/Model pair is built exactly once, at file-load time, +# and both classes (the "victim" whose #run gets exercised, and the class +# that verifies it) see the same Widget table. +module TransactionalFixtures + DIR = Dir.mktmpdir('prism-transactional-tests') + STORE = Prism::Orm::Store.new(File.join(DIR, 'test.pstore')) + STORE.create_table(:widgets, %w[id name]) + + class Widget < Prism::Orm::Model + self.store = STORE + table_name :widgets + + class << self + attr_accessor :rollback_count + end + self.rollback_count = 0 + + after_rollback { self.class.rollback_count += 1 } + end + + # A second, INDEPENDENT Store (own file, own PStore, own TransactionRegistry) + # standing in for something like a separate jobs queue database that a + # single test method might write to alongside the main one. + OTHER_STORE = Prism::Orm::Store.new(File.join(DIR, 'other.pstore')) + OTHER_STORE.create_table(:gadgets, %w[id name]) + + class Gadget < Prism::Orm::Model + self.store = OTHER_STORE + table_name :gadgets + end +end + +# Auto-discovered and run by Minitest like any other test class in this +# suite (contributing its own pass/fail to the run) - AND instantiated and +# `.run` manually, more than once, by TestTestingTransactional below to +# prove writes never survive between runs. Both uses must be safe together, +# since transactional rollback makes every run self-contained: whichever +# caller runs it, the widgets table is back to empty by the time #run returns. +class TransactionalVictimTest < Minitest::Test + include Prism::Testing::IntegrationTest + use_transactional_tests(store: TransactionalFixtures::STORE) + + def test_creates_exactly_one_widget + TransactionalFixtures::Widget.create!(name: 'gizmo') + assert_equal 1, TransactionalFixtures::Widget.count + end +end + +# Writes to BOTH independent stores in a single test method - proves the +# `stores:` (plural) option rolls back every one of them, not just the +# first or the last. +class MultiStoreVictimTest < Minitest::Test + include Prism::Testing::IntegrationTest + use_transactional_tests(stores: [TransactionalFixtures::STORE, TransactionalFixtures::OTHER_STORE]) + + def test_creates_a_widget_and_a_gadget + TransactionalFixtures::Widget.create!(name: 'gizmo') + TransactionalFixtures::Gadget.create!(name: 'sprocket') + assert_equal 1, TransactionalFixtures::Widget.count + assert_equal 1, TransactionalFixtures::Gadget.count + end +end + +# Regression guard for classes that never call `use_transactional_tests` - +# their #run must behave exactly as plain Minitest::Test, unaffected by the +# override living in the shared module. +class PlainIntegrationSampleTest < Minitest::Test + include Prism::Testing::IntegrationTest + + def test_noop + assert true + end +end + +class TestTestingTransactional < Minitest::Test + Widget = TransactionalFixtures::Widget + + def test_writes_are_rolled_back_once_the_test_finishes + assert_equal 0, Widget.count + result = TransactionalVictimTest.new(:test_creates_exactly_one_widget).run + assert result.passed?, result.failures.map(&:message).join("\n") + assert_equal 0, Widget.count, 'the widget created inside the test should not survive it' + end + + def test_running_the_same_test_twice_never_sees_a_leftover_row + result1 = TransactionalVictimTest.new(:test_creates_exactly_one_widget).run + result2 = TransactionalVictimTest.new(:test_creates_exactly_one_widget).run + assert result1.passed?, result1.failures.map(&:message).join("\n") + assert result2.passed?, result2.failures.map(&:message).join("\n") + assert_equal 0, Widget.count + end + + def test_after_rollback_callbacks_fire_when_the_transaction_unwinds + before = Widget.rollback_count + result = TransactionalVictimTest.new(:test_creates_exactly_one_widget).run + assert result.passed?, result.failures.map(&:message).join("\n") + assert_equal before + 1, Widget.rollback_count + end + + def test_classes_that_never_opt_in_are_unaffected + result = PlainIntegrationSampleTest.new(:test_noop).run + assert result.passed?, result.failures.map(&:message).join("\n") + end + + def test_transactional_tests_store_is_inherited_by_subclasses + subclass = Class.new(TransactionalVictimTest) + assert_equal TransactionalFixtures::STORE, subclass.transactional_tests_store + end + + def test_default_store_option_resolves_to_prism_orm_store_lazily + klass = Class.new(Minitest::Test) do + include Prism::Testing::IntegrationTest + use_transactional_tests + end + assert_equal :default, klass.transactional_tests_store + assert_equal [:default], klass.transactional_tests_stores + end + + def test_multiple_stores_all_roll_back_after_the_test_finishes + assert_equal 0, TransactionalFixtures::Widget.count + assert_equal 0, TransactionalFixtures::Gadget.count + + result = MultiStoreVictimTest.new(:test_creates_a_widget_and_a_gadget).run + assert result.passed?, result.failures.map(&:message).join("\n") + + assert_equal 0, TransactionalFixtures::Widget.count, + 'the widget in the FIRST configured store must not survive the test' + assert_equal 0, TransactionalFixtures::Gadget.count, + 'the gadget in the SECOND, independent store must not survive the test either' + end + + def test_stores_option_is_exposed_as_an_array_and_store_returns_its_first_element + assert_equal [TransactionalFixtures::STORE, TransactionalFixtures::OTHER_STORE], + MultiStoreVictimTest.transactional_tests_stores + assert_equal TransactionalFixtures::STORE, MultiStoreVictimTest.transactional_tests_store + end +end diff --git a/test/test_view.rb b/test/test_view.rb index c76f0e5..9a2f83c 100644 --- a/test/test_view.rb +++ b/test/test_view.rb @@ -52,4 +52,16 @@ class ViewTest < Minitest::Test error = assert_raises(Prism::View::TemplateNotFound) { @view.render('posts/missing') } assert_includes error.message, 'posts/missing' end + + def test_render_emits_a_render_template_event_per_template_file + events = [] + subscriber = Prism::Instrumentation.subscribe('render_template.prism') { |event| events << event } + begin + @view.render('posts/index', locals: { greeting: 'hi' }, layout: :application) + ensure + Prism::Instrumentation.unsubscribe(subscriber) + end + + assert_equal %w[posts/index layouts/application], events.map { |e| e.payload[:identifier] } + end end