Files
fizzy/test/models/notification_test.rb
T
Donal McBreen e51f0bfee7 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
2026-04-06 10:04:24 +01:00

88 lines
2.6 KiB
Ruby

require "test_helper"
class NotificationTest < ActiveSupport::TestCase
test "read marks notification as read" do
notification = notifications(:logo_assignment_kevin)
notification.update!(read_at: nil, unread_count: 2)
assert_changes -> { notification.reload.read? }, from: false, to: true do
notification.read
end
assert_equal 0, notification.unread_count
end
test "unread marks notification as unread" do
notification = notifications(:logo_assignment_kevin)
notification.read
assert_changes -> { notification.reload.read? }, from: true, to: false do
notification.unread
end
assert_equal 1, notification.unread_count
end
test "read_all marks all notifications and resets unread counts" do
kevin = users(:kevin)
kevin.notifications.unread.read_all
assert kevin.notifications.reload.all?(&:read?)
assert kevin.notifications.reload.all? { |n| n.unread_count == 0 }
end
test "unread_count tracks notification count per card" do
notification = notifications(:logo_assignment_kevin)
assert_equal 2, notification.unread_count
end
test "broadcasting on create prepends" do
kevin = users(:kevin)
layout = cards(:layout)
notifications(:layout_commented_kevin).destroy
perform_enqueued_jobs do
Notification.create!(user: kevin, source: events(:layout_commented), creator: users(:david))
end
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)
assert_turbo_stream_broadcasts([ notification.user, :notifications ], count: 1) do
perform_enqueued_jobs do
notification.read
end
end
end
test "broadcasting on destroy removes" do
notification = notifications(:logo_assignment_kevin)
assert_turbo_stream_broadcasts([ notification.user, :notifications ], count: 1) do
notification.destroy
end
end
end