Fix account destroy cascade gaps

Account incineration could leave orphaned records due to missing cascade
declarations and async jobs failing when the account was already gone.

Cascade fixes:
- Add Account::Searchable concern to clean up Search::Query (delete_all)
  and Search::Record (destroy_all, respects SQLite FTS dependent: :destroy)
- Add before_destroy in Account::Storage to delete storage entries
- Suppress storage entry recording during incineration so attachment
  purge callbacks don't create entries or enqueue materialize jobs
- Guard Access#clean_inaccessible_data_later with unless user.destroyed?
  to avoid enqueuing pointless jobs during user cascade

Job tenancy:
- Extract AccountTenanted concern from the global ActiveJob initializer
  into ApplicationJob (include) with targeted prepends for
  ActionMailer::MailDeliveryJob and Turbo broadcast jobs
- Defer account resolution from deserialize to perform so that missing
  accounts raise DeserializationError inside the execution path where
  discard_on can handle it

Tests:
- Comprehensive incineration test covering 35 model types with
  before/after assertions and full enqueued job processing
- Mailer deliver_later test verifying account context survives job
  serialization for multi-account users
- Turbo broadcast test verifying account-scoped URLs in rendered partials
This commit is contained in:
Donal McBreen
2026-04-01 12:24:21 +01:00
parent d9f176aeb0
commit e51f0bfee7
17 changed files with 292 additions and 42 deletions
+17
View File
@@ -50,6 +50,23 @@ class NotificationTest < ActiveSupport::TestCase
assert_turbo_stream_broadcasts [ kevin, :notifications ]
end
test "broadcast job renders account-scoped URLs" do
kevin = users(:kevin)
account = kevin.account
notifications(:layout_commented_kevin).destroy
# Enqueue the broadcast job but don't run it yet
notification = Notification.create!(user: kevin, source: events(:layout_commented), creator: users(:david))
broadcasts = capture_turbo_stream_broadcasts([ kevin, :notifications ]) do
perform_enqueued_jobs
end
assert broadcasts.any?, "Expected at least one broadcast"
html = broadcasts.last.to_s
assert_includes html, "href=\"#{account.slug}/", "Broadcast should include account slug in URLs"
end
test "broadcasting on update when read removes" do
notification = notifications(:layout_commented_kevin)