diff --git a/test/models/room/messages_count_lifecycle_test.rb b/test/models/room/messages_count_lifecycle_test.rb new file mode 100644 index 0000000..295e178 --- /dev/null +++ b/test/models/room/messages_count_lifecycle_test.rb @@ -0,0 +1,102 @@ +require "test_helper" +require "sqlite3" + +# Destructive trigger DDL and foreign connections need their own file so parallel +# workers (and transactional tests in messages_count_test) keep a stable trigger set. +class Room::MessagesCountLifecycleTest < ActiveSupport::TestCase + self.use_transactional_tests = false + + setup do + Room::MessagesCount.ensure! + @room = rooms(:designers) + @other_room = rooms(:pets) + Room::MessagesCount.backfill! + @room.reload + @other_room.reload + end + + teardown do + Message.where("client_message_id LIKE ?", "count-%").delete_all + Room::MessagesCount.ensure! + Room::MessagesCount.backfill! + end + + test "ensure! installs missing triggers without changing existing counts" do + before = @room.reload.messages_count + Room::MessagesCount.uninstall! + + assert_not Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, Room::MessagesCount::INSERT_TRIGGER) + + Room::MessagesCount.ensure! + + assert Room::MessagesCount::TRIGGERS.all? { |name| + Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, name) + } + assert_equal before, @room.reload.messages_count + + assert_difference -> { @room.reload.messages_count }, +1 do + @room.messages.create!(creator: users(:jason), body: "After ensure", client_message_id: "count-ensure") + end + end + + test "ensure! repairs a partial trigger install" do + Room::MessagesCount.uninstall! + ActiveRecord::Base.connection.execute <<~SQL + CREATE TRIGGER #{Room::MessagesCount::INSERT_TRIGGER} AFTER INSERT ON messages + BEGIN + UPDATE rooms SET messages_count = messages_count + 1 WHERE id = NEW.room_id; + END + SQL + + assert Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, Room::MessagesCount::INSERT_TRIGGER) + assert_not Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, Room::MessagesCount::DELETE_TRIGGER) + + Room::MessagesCount.ensure! + + assert Room::MessagesCount::TRIGGERS.all? { |name| + Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, name) + } + end + + test "foreign SQLite connections keep the counter in step" do + path = File.expand_path(ActiveRecord::Base.connection_db_config.database) + now = Time.current.utc.strftime("%Y-%m-%d %H:%M:%S.%6N") + before = @room.reload.messages_count + other_before = @other_room.reload.messages_count + + ActiveRecord::Base.connection_pool.release_connection + + SQLite3::Database.new(path) do |db| + db.busy_timeout = 5_000 + db.execute( + "INSERT INTO messages (room_id, creator_id, client_message_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", + [ @room.id, users(:david).id, "count-foreign-insert", now, now ] + ) + end + + assert_equal before + 1, @room.reload.messages_count + assert_equal @room.messages.count, @room.messages_count + + foreign_id = Message.find_by!(client_message_id: "count-foreign-insert").id + + ActiveRecord::Base.connection_pool.release_connection + + SQLite3::Database.new(path) do |db| + db.busy_timeout = 5_000 + db.execute("UPDATE messages SET room_id = ? WHERE id = ?", [ @other_room.id, foreign_id ]) + end + + assert_equal before, @room.reload.messages_count + assert_equal other_before + 1, @other_room.reload.messages_count + + ActiveRecord::Base.connection_pool.release_connection + + SQLite3::Database.new(path) do |db| + db.busy_timeout = 5_000 + db.execute("DELETE FROM messages WHERE id = ?", [ foreign_id ]) + end + + assert_equal before, @room.reload.messages_count + assert_equal other_before, @other_room.reload.messages_count + end +end diff --git a/test/models/room/messages_count_test.rb b/test/models/room/messages_count_test.rb index 911b21e..3b0e447 100644 --- a/test/models/room/messages_count_test.rb +++ b/test/models/room/messages_count_test.rb @@ -1,5 +1,4 @@ require "test_helper" -require "sqlite3" class Room::MessagesCountTest < ActiveSupport::TestCase setup do @@ -85,102 +84,3 @@ class Room::MessagesCountTest < ActiveSupport::TestCase assert_not reflection.counter_cache_column end end - -# Destructive trigger DDL and foreign connections need a committed DB. -class Room::MessagesCountLifecycleTest < ActiveSupport::TestCase - self.use_transactional_tests = false - - setup do - Room::MessagesCount.ensure! - @room = rooms(:designers) - @other_room = rooms(:pets) - Room::MessagesCount.backfill! - @room.reload - @other_room.reload - end - - teardown do - Message.where("client_message_id LIKE ?", "count-%").delete_all - Room::MessagesCount.ensure! - Room::MessagesCount.backfill! - end - - test "ensure! installs missing triggers without changing existing counts" do - before = @room.reload.messages_count - Room::MessagesCount.uninstall! - - assert_not Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, Room::MessagesCount::INSERT_TRIGGER) - - Room::MessagesCount.ensure! - - assert Room::MessagesCount::TRIGGERS.all? { |name| - Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, name) - } - assert_equal before, @room.reload.messages_count - - assert_difference -> { @room.reload.messages_count }, +1 do - @room.messages.create!(creator: users(:jason), body: "After ensure", client_message_id: "count-ensure") - end - end - - test "ensure! repairs a partial trigger install" do - Room::MessagesCount.uninstall! - ActiveRecord::Base.connection.execute <<~SQL - CREATE TRIGGER #{Room::MessagesCount::INSERT_TRIGGER} AFTER INSERT ON messages - BEGIN - UPDATE rooms SET messages_count = messages_count + 1 WHERE id = NEW.room_id; - END - SQL - - assert Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, Room::MessagesCount::INSERT_TRIGGER) - assert_not Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, Room::MessagesCount::DELETE_TRIGGER) - - Room::MessagesCount.ensure! - - assert Room::MessagesCount::TRIGGERS.all? { |name| - Room::MessagesCount.trigger_installed?(ActiveRecord::Base.connection, name) - } - end - - test "foreign SQLite connections keep the counter in step" do - path = File.expand_path(ActiveRecord::Base.connection_db_config.database) - now = Time.current.utc.strftime("%Y-%m-%d %H:%M:%S.%6N") - before = @room.reload.messages_count - other_before = @other_room.reload.messages_count - - ActiveRecord::Base.connection_pool.release_connection - - SQLite3::Database.new(path) do |db| - db.busy_timeout = 5_000 - db.execute( - "INSERT INTO messages (room_id, creator_id, client_message_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", - [ @room.id, users(:david).id, "count-foreign-insert", now, now ] - ) - end - - assert_equal before + 1, @room.reload.messages_count - assert_equal @room.messages.count, @room.messages_count - - foreign_id = Message.find_by!(client_message_id: "count-foreign-insert").id - - ActiveRecord::Base.connection_pool.release_connection - - SQLite3::Database.new(path) do |db| - db.busy_timeout = 5_000 - db.execute("UPDATE messages SET room_id = ? WHERE id = ?", [ @other_room.id, foreign_id ]) - end - - assert_equal before, @room.reload.messages_count - assert_equal other_before + 1, @other_room.reload.messages_count - - ActiveRecord::Base.connection_pool.release_connection - - SQLite3::Database.new(path) do |db| - db.busy_timeout = 5_000 - db.execute("DELETE FROM messages WHERE id = ?", [ foreign_id ]) - end - - assert_equal before, @room.reload.messages_count - assert_equal other_before, @other_room.reload.messages_count - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb index 6e3fa8b..30b87be 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,31 +6,15 @@ require "mocha/minitest" require "webmock/minitest" require "turbo/broadcastable/test_helper" -# maintain_test_schema! may reload schema.rb after after_initialize; triggers are -# not dumped, so ensure they exist before fixtures insert messages. -Room::MessagesCount.ensure! - WebMock.enable! -module RoomMessagesCountFixtures - # Fixture YAML still inserts messages before rooms via some load paths, so - # INSERT triggers can update zero room rows. Reconcile once after load. - def load_fixtures(config) - fixtures = super - Room::MessagesCount.backfill! - fixtures - end -end - class ActiveSupport::TestCase include ActiveJob::TestHelper - prepend RoomMessagesCountFixtures parallelize(workers: :number_of_processors) - # Prefer rooms before messages when the loader honors declaration order. - fixtures :accounts, :users, :rooms, :memberships, :messages, "action_text/rich_texts", - :boosts, :searches, :sessions, :webhooks, "push/subscriptions" + # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. + fixtures :all include SessionTestHelper, MentionTestHelper, TurboTestHelper, DnsTestHelper @@ -49,4 +33,13 @@ class ActiveSupport::TestCase teardown do WebMock.reset! end + + # fixtures :all inserts messages before rooms, so INSERT triggers cannot count + # yet. Backfill once after load; ensure! covers schema.rb (triggers not dumped). + def load_fixtures(config) + fixtures = super + Room::MessagesCount.ensure! + Room::MessagesCount.backfill! + fixtures + end end