seed(); NotificationSetting::query()->where('trigger_key', 'sla_breached')->update(['enabled' => true]); $ticket = makeTicket(); $assignee = operatorUser('assignee-sla@example.com'); $ticket->update(['assignee_id' => $assignee->id]); app(TicketService::class)->notify($ticket->fresh(), 'sla_breached'); // NotificationPreference::DEFAULTS['escalation'] has scope_mine=true, so // without the notify()->notifyStaffForCategory() dedup, the assignee // would receive this twice: once as the fixed NotificationSetting // recipient, once again from the scope_mine fan-out. Notification::assertSentToTimes($assignee, TicketNotification::class, 1); }); test('a staff member with the e-mail column off for an event still gets the bell but not a mail', function () { Notification::fake(); $this->seed(); $operator = operatorUser('bell-only@example.com'); NotificationPreference::query()->create(array_merge( ['user_id' => $operator->id, 'event_category' => 'ticket_update'], array_merge(NotificationPreference::DEFAULTS['ticket_update'], ['scope_all' => true, 'email' => false]) )); NotificationSetting::query()->where('trigger_key', 'priority_changed')->update(['enabled' => true]); $ticket = makeTicket(); app(TicketService::class)->setPriority($ticket, 'high'); Notification::assertSentTo($operator, TicketNotification::class, function ($notification, $channels) { return $channels === ['database']; }); }); test('a staff member with the e-mail column on for an event gets both the bell and a mail', function () { Notification::fake(); $this->seed(); $operator = operatorUser('bell-and-mail@example.com'); NotificationPreference::query()->create(array_merge( ['user_id' => $operator->id, 'event_category' => 'ticket_update'], array_merge(NotificationPreference::DEFAULTS['ticket_update'], ['scope_all' => true, 'email' => true]) )); NotificationSetting::query()->where('trigger_key', 'priority_changed')->update(['enabled' => true]); $ticket = makeTicket(); app(TicketService::class)->setPriority($ticket, 'high'); Notification::assertSentTo($operator, TicketNotification::class, function ($notification, $channels) { return $channels === ['mail', 'database']; }); }); test('the operator performing the action is never notified about their own change', function () { Notification::fake(); $this->seed(); NotificationSetting::query()->where('trigger_key', 'priority_changed')->update(['enabled' => true]); $actor = operatorUser('actor@example.com'); $this->actingAs($actor); $ticket = makeTicket(); app(TicketService::class)->setPriority($ticket, 'high'); Notification::assertNotSentTo($actor, TicketNotification::class); }); test('disabling a trigger instance-wide silences the staff fan-out too, regardless of any individual preference', function () { Notification::fake(); $this->seed(); $operator = operatorUser('kill-switch@example.com'); NotificationPreference::query()->create(array_merge( ['user_id' => $operator->id, 'event_category' => 'ticket_update'], array_merge(NotificationPreference::DEFAULTS['ticket_update'], ['scope_all' => true, 'email' => true]) )); NotificationSetting::query()->where('trigger_key', 'priority_changed')->update(['enabled' => false]); $ticket = makeTicket(); app(TicketService::class)->setPriority($ticket, 'high'); Notification::assertNotSentTo($operator, TicketNotification::class); });