123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'rails_helper'
- require 'models/application_model_examples'
- RSpec.describe OnlineNotification, type: :model do
- subject(:online_notification) { create(:online_notification, o: ticket) }
- let(:ticket) { create(:ticket) }
- it_behaves_like 'ApplicationModel', can_param: { sample_data_attribute: :seen }
- describe '#related_object' do
- it 'returns ticket' do
- expect(online_notification.related_object).to eq ticket
- end
- end
- describe '.add' do
- describe 'validations' do
- describe 'referenced object' do
- it 'raises RuntimeError for invalid object name' do
- expect do
- described_class.add(
- type: 'create',
- object: 'TicketNotExisting',
- o_id: 123,
- seen: false,
- user_id: create(:agent).id,
- created_by_id: 1,
- updated_by_id: 1,
- created_at: 10.months.ago,
- updated_at: 10.months.ago,
- )
- end.to raise_error(RuntimeError)
- end
- it 'raises RecordNotFound if object does not exist' do
- expect do
- described_class.add(
- type: 'create',
- object: 'Ticket',
- o_id: 123,
- seen: false,
- user_id: create(:agent).id,
- created_by_id: 1,
- updated_by_id: 1,
- created_at: 10.months.ago,
- updated_at: 10.months.ago,
- )
- end.to raise_error(ActiveRecord::RecordNotFound)
- end
- end
- end
- end
- describe '.list' do
- let(:user) { create(:agent, groups: [group]) }
- let(:another_user) { create(:agent, groups: [group]) }
- let(:group) { create(:group) }
- let(:ticket) { create(:ticket, group: group) }
- let(:another_ticket) { create(:ticket, group: group) }
- let(:notification_1) { create(:online_notification, o: ticket, user: user) }
- let(:notification_2) { create(:online_notification, o: ticket, user: another_user) }
- let(:notification_3) { create(:online_notification, o: another_ticket, user: user) }
- before do
- notification_1 && notification_2 && notification_3
- end
- it 'returns notifications for a given user' do
- expect(described_class.list(user))
- .to contain_exactly(notification_1, notification_3)
- end
- context 'when user looses access to one of the referenced tickets' do
- before do
- another_ticket.update! group: create(:group)
- end
- it 'with ensure_access flag it returns notifications given user has access to' do
- expect(described_class.list(user, access: 'full'))
- .to contain_exactly(notification_1)
- end
- it 'without ensure_access flag it returns all notifications given user has' do
- expect(described_class.list(user, access: 'ignore'))
- .to contain_exactly(notification_1, notification_3)
- end
- end
- end
- describe 'notification creation', performs_jobs: true do
- let(:group) { create(:group) }
- let(:agent1) { create(:agent, groups: [group]) }
- let(:agent2) { create(:agent, groups: [group]) }
- let(:agent3) { create(:agent) }
- let(:customer) { create(:customer) }
- let(:system) { User.lookup(login: '-') }
- let(:ticket) do
- create(:ticket,
- group: group,
- customer: customer,
- owner: ticket_owner,
- state_name: state_name,
- updated_by: ticket_author,
- created_by: ticket_author)
- end
- let(:first_article) do
- create(:ticket_article,
- ticket: ticket,
- type_name: 'phone',
- sender_name: 'Customer',
- from: 'Unit Test <unittest@example.com>',
- body: 'Unit Test 123',
- internal: false,
- updated_by: article_author,
- created_by: article_author)
- end
- def notifications_scope(type_name)
- described_class.where(
- object_lookup_id: ObjectLookup.by_name('Ticket'),
- type_lookup_id: TypeLookup.by_name(type_name),
- o_id: ticket.id
- )
- end
- around do |example|
- ApplicationHandleInfo.use('application_server') do
- example.run
- end
- end
- before do
- agent1 && agent2 && agent3 && customer
- first_article
- perform_enqueued_jobs commit_transaction: true
- end
- shared_examples 'destroyable notifications' do
- let(:destroyable_ticket) { ticket }
- it 'removes all notifications when destroying a ticket' do
- destroyable_ticket.destroy
- expect(described_class.list_by_object('Ticket', destroyable_ticket.id))
- .to be_blank
- end
- end
- context 'when closed ticket owner is system' do
- let(:ticket_owner) { system }
- let(:ticket_author) { agent1 }
- let(:article_author) { agent1 }
- let(:state_name) { 'closed' }
- it 'adds already seen create notification for the other agent' do
- expect(notifications_scope('create'))
- .to contain_exactly(
- have_attributes(user: agent2, created_by: agent1, seen: true)
- )
- end
- it_behaves_like 'destroyable notifications'
- context 'when customer updates a closed ticket' do
- before do
- ticket.update!(
- state_id: Ticket::State.lookup(name: 'open').id,
- priority_id: Ticket::Priority.lookup(name: '1 low').id,
- created_by_id: customer.id,
- updated_by_id: customer.id,
- )
- perform_enqueued_jobs commit_transaction: true
- end
- it 'adds unseen update notifications for both agents' do
- expect(notifications_scope('update'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: false),
- have_attributes(user: agent2, created_by: customer, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- end
- end
- context 'when closed ticket owner is agent and started by customer' do
- let(:ticket_owner) { agent1 }
- let(:ticket_author) { customer }
- let(:article_author) { customer }
- let(:state_name) { 'closed' }
- it 'adds unseen create notification for owner agent' do
- expect(notifications_scope('create'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- context 'when customer updates a closed ticket' do
- before do
- ticket.update!(
- state_id: Ticket::State.lookup(name: 'open').id,
- priority_id: Ticket::Priority.lookup(name: '1 low').id,
- created_by_id: customer.id,
- updated_by_id: customer.id,
- )
- perform_enqueued_jobs commit_transaction: true
- end
- it 'adds unseen update notifications for owner agent' do
- expect(notifications_scope('update'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: false),
- )
- end
- it_behaves_like 'destroyable notifications'
- end
- end
- context 'when new ticket owner is system and started by agent' do
- let(:ticket_owner) { system }
- let(:ticket_author) { agent1 }
- let(:article_author) { agent1 }
- let(:state_name) { 'new' }
- it 'adds unseen create notification for another agent' do
- expect(notifications_scope('create'))
- .to contain_exactly(
- have_attributes(user: agent2, created_by: agent1, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- context 'when customer closes ticket' do
- before do
- ticket.update!(
- state_id: Ticket::State.lookup(name: 'closed').id,
- priority_id: Ticket::Priority.lookup(name: '1 low').id,
- created_by_id: customer.id,
- updated_by_id: customer.id,
- )
- perform_enqueued_jobs commit_transaction: true
- end
- it 'sends notifications to both agents', :aggregate_failures do
- expect(NotificationFactory::Mailer.already_sent?(ticket, agent1, 'update')).to eq(1)
- expect(NotificationFactory::Mailer.already_sent?(ticket, agent2, 'update')).to eq(1)
- end
- it 'adds seen notification to both agents' do
- expect(notifications_scope('update'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: true),
- have_attributes(user: agent2, created_by: customer, seen: true),
- )
- end
- it_behaves_like 'destroyable notifications'
- context 'when phone article by customer is added' do
- before do
- create(:ticket_article, :inbound_phone, ticket: ticket, updated_by: customer, created_by: customer)
- perform_enqueued_jobs commit_transaction: true
- end
- it 'sends notifications to both agents', :aggregate_failures do
- expect(NotificationFactory::Mailer.already_sent?(ticket, agent1, 'update')).to eq(2)
- expect(NotificationFactory::Mailer.already_sent?(ticket, agent2, 'update')).to eq(2)
- end
- it 'adds unseen notifications to both agents' do
- expect(notifications_scope('update'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: true),
- have_attributes(user: agent2, created_by: customer, seen: true),
- have_attributes(user: agent1, created_by: customer, seen: false),
- have_attributes(user: agent2, created_by: customer, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- end
- end
- end
- context 'when new ticket owner is agent and created by customer' do
- let(:ticket_owner) { agent1 }
- let(:ticket_author) { customer }
- let(:article_author) { customer }
- let(:state_name) { 'new' }
- it 'adds notification to owner agent' do
- expect(notifications_scope('create'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- context 'when ticket is updated to open' do
- before do
- ticket.update!(
- state_id: Ticket::State.lookup(name: 'open').id,
- priority_id: Ticket::Priority.lookup(name: '1 low').id,
- created_by_id: customer.id,
- updated_by_id: customer.id,
- )
- perform_enqueued_jobs commit_transaction: true
- end
- it 'adds notification to owner agent' do
- expect(notifications_scope('update'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: false),
- )
- end
- it_behaves_like 'destroyable notifications'
- end
- end
- context 'when new ticket owner is system and created by customer' do
- let(:ticket_owner) { system }
- let(:ticket_author) { agent1 }
- let(:article_author) { agent1 }
- let(:state_name) { 'new' }
- it 'adds unseen notification to another agent' do
- expect(notifications_scope('create'))
- .to contain_exactly(
- have_attributes(user: agent2, created_by: agent1, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- context 'when ticket is updated by customer to open' do
- before do
- ticket.update!(
- state_id: Ticket::State.lookup(name: 'open').id,
- priority_id: Ticket::Priority.lookup(name: '1 low').id,
- created_by_id: customer.id,
- updated_by_id: customer.id,
- )
- perform_enqueued_jobs commit_transaction: true
- end
- it 'adds unseen notification to both agents' do
- expect(notifications_scope('update'))
- .to contain_exactly(
- have_attributes(user: agent1, created_by: customer, seen: false),
- have_attributes(user: agent2, created_by: customer, seen: false)
- )
- end
- it_behaves_like 'destroyable notifications'
- end
- end
- context 'when merging tickets' do
- let(:ticket_owner) { system }
- let(:ticket_author) { agent1 }
- let(:article_author) { agent1 }
- let(:state_name) { 'open' }
- let(:target_ticket) { create(:ticket, group: group) }
- before do
- ticket && target_ticket
- perform_enqueued_jobs
- ticket.merge_to(
- ticket_id: target_ticket.id,
- user_id: 1,
- )
- perform_enqueued_jobs
- end
- it 'notifications for origin ticket are still available' do
- expect(described_class.list_by_object('Ticket', ticket.id))
- .to be_present
- end
- it 'notifications for target ticket are still available' do
- expect(described_class.list_by_object('Ticket', target_ticket.id))
- .to be_present
- end
- it 'origin ticket has no unseen notifications' do
- expect(described_class.list_by_object('Ticket', ticket.id))
- .not_to exist(seen: false)
- end
- it 'target ticket has new notifications that are not seen notifications' do
- expect(described_class.list_by_object('Ticket', target_ticket.id))
- .to exist(seen: false)
- end
- it_behaves_like 'destroyable notifications'
- it_behaves_like 'destroyable notifications' do
- let(:destroyable_ticket) { target_ticket }
- end
- end
- end
- describe '.cleanup' do
- let(:max_age) { 1.week }
- let(:own_seen) { 1.minute }
- let(:auto_seen) { 10.minutes }
- let(:user) { create(:agent) }
- let(:notification) { create(:online_notification, user: user, seen: false, created_by: user) }
- before { notification }
- context 'when seen' do
- context 'when seen by user' do
- before do
- travel 10.minutes
- notification.update!(seen: true, updated_by: user)
- end
- it 'stays if it was just seen' do
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).to exist(notification.id)
- end
- it 'deleted after own seen time passes' do
- travel own_seen + 1.minute
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).not_to exist(notification.id)
- end
- end
- context 'when seen by another user' do
- before do
- travel 10.minutes
- notification.update!(seen: true)
- end
- it 'stays if it was just seen' do
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).to exist(notification.id)
- end
- it 'not deleted after own seen time passes' do
- travel own_seen + 1.minute
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).to exist(notification.id)
- end
- it 'deleted after auto seen time passes' do
- travel auto_seen + 1.minute
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).not_to exist(notification.id)
- end
- end
- end
- context 'when not seen' do
- it 'stays if it is fresh' do
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).to exist(notification.id)
- end
- it 'stays after own seen time passes' do
- travel own_seen + 1.minute
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).to exist(notification.id)
- end
- it 'stays after auto seen time passes' do
- travel auto_seen + 1.minute
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).to exist(notification.id)
- end
- it 'deleted after max time passes' do
- travel max_age + 1.day
- described_class.cleanup(max_age.ago, own_seen.ago, auto_seen.ago)
- expect(described_class).not_to exist(notification.id)
- end
- end
- describe 'notifying agents' do
- it 'notifies agents affected by both cleanup strategies' do
- agent2 = create(:agent)
- agent3 = create(:agent)
- create(:online_notification, user: user, seen: false, created_at: 1.year.ago)
- create(:online_notification, user: user, seen: true, updated_at: 1.month.ago)
- create(:online_notification, user: agent2, seen: true, updated_at: 1.month.ago)
- create(:online_notification, user: agent3, seen: false)
- allow(described_class).to receive(:cleanup_notify_agents)
- described_class.cleanup
- expect(described_class)
- .to have_received(:cleanup_notify_agents).with(contain_exactly(user.id, agent2.id))
- end
- end
- end
- describe '.cleanup_notify_agents' do
- it 'sends notifications to given users' do
- agent1 = create(:agent)
- _ = create(:agent)
- allow(Sessions).to receive(:send_to)
- described_class.cleanup_notify_agents([agent1])
- expect(Sessions).to have_received(:send_to).once
- end
- end
- describe '.seen_state?' do
- let(:group) { create(:group) }
- let(:agent1) { create(:agent, groups: [group]) }
- let(:agent2) { create(:agent, groups: [group]) }
- let(:customer) { create(:customer) }
- let(:system) { User.lookup(login: '-') }
- let(:ticket) do
- create(:ticket,
- group: group,
- customer: customer,
- owner_id: owner_id,
- state_name: state_name,
- updated_by_id: editor_id)
- end
- let(:first_article) do
- create(:ticket_article, :inbound_email, ticket: ticket)
- end
- context 'when state is new' do
- let(:state_name) { 'new' }
- let(:owner_id) { agent1.id }
- let(:editor_id) { 1 }
- it { expect(described_class).not_to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).not_to be_seen_state(ticket, agent2.id) }
- end
- context 'when state is pending reminder' do
- let(:state_name) { 'pending reminder' }
- context 'when owner is an agent and updated by another agent' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is system and updated by agent' do
- let(:owner_id) { 1 }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).not_to be_seen_state(ticket, agent2.id) }
- end
- context 'when updated by owner' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent1.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- end
- context 'when state is pending close' do
- let(:state_name) { 'pending close' }
- context 'when updated by owner' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent1.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is an agent and updated by another agent' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is system and updated by agent' do
- let(:owner_id) { 1 }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- end
- context 'when state is open' do
- let(:state_name) { 'open' }
- context 'when updated by owner' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent1.id }
- it { expect(described_class).not_to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).not_to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is an agent and updated by another agent' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent2.id }
- it { expect(described_class).not_to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).not_to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is system and updated by agent' do
- let(:owner_id) { 1 }
- let(:editor_id) { agent2.id }
- it { expect(described_class).not_to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).not_to be_seen_state(ticket, agent2.id) }
- end
- end
- context 'when state is closed' do
- let(:state_name) { 'closed' }
- context 'when updated by owner' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent1.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is an agent and updated by another agent' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).not_to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is system and updated by agent' do
- let(:owner_id) { 1 }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- end
- context 'when state is merged' do
- let(:state_name) { 'merged' }
- context 'when updated by owner' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent1.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is an agent and updated by another agent' do
- let(:owner_id) { agent1.id }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- context 'when owner is system and updated by agent' do
- let(:owner_id) { 1 }
- let(:editor_id) { agent2.id }
- it { expect(described_class).to be_seen_state(ticket) }
- it { expect(described_class).to be_seen_state(ticket, agent1.id) }
- it { expect(described_class).to be_seen_state(ticket, agent2.id) }
- end
- end
- end
- end
|