e51f0bfee7
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
88 lines
2.6 KiB
Ruby
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
|