diff --git a/spec/factories/calendar.rb b/spec/factories/calendar.rb index 4cc992b11..a0a174e4f 100644 --- a/spec/factories/calendar.rb +++ b/spec/factories/calendar.rb @@ -4,6 +4,7 @@ FactoryBot.define do timezone { 'Europe/Berlin' } default { true } ical_url { nil } + business_hours do { mon: { @@ -36,7 +37,43 @@ FactoryBot.define do } } end + created_by_id { 1 } updated_by_id { 1 } + + trait :'24/7' do + business_hours do + { + mon: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + tue: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + wed: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + thu: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + fri: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + sat: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + sun: { + active: true, + timeframes: [ ['00:00', '23:59'] ] + }, + } + end + end end end diff --git a/spec/models/ticket_spec.rb b/spec/models/ticket_spec.rb index fa149275c..9cc22e427 100644 --- a/spec/models/ticket_spec.rb +++ b/spec/models/ticket_spec.rb @@ -380,6 +380,165 @@ RSpec.describe Ticket, type: :model do end end end + + describe '#escalation_at' do + before { travel_to(Time.current) } # freeze time + let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) } + let(:calendar) { create(:calendar, :'24/7') } + + context 'with no SLAs in the system' do + it 'defaults to nil' do + expect(ticket.escalation_at).to be(nil) + end + end + + context 'with an SLA in the system' do + before { sla } # create sla + + it 'is set based on SLA’s #first_response_time' do + expect(ticket.escalation_at.to_i) + .to eq(1.hour.from_now.to_i) + end + + context 'after first agent’s response' do + before { ticket } # create ticket + let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') } + + it 'is updated based on the SLA’s #update_time' do + travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same + + expect { article } + .to change { ticket.reload.escalation_at.to_i } + .to eq(3.hours.from_now.to_i) + end + + context 'when new #update_time is later than original #solution_time' do + it 'is updated based on the original #solution_time' do + travel(2.hours) # time is frozen: if we don't travel forward, pre- and post-update values will be the same + + expect { article } + .to change { ticket.reload.escalation_at.to_i } + .to eq(4.hours.after(ticket.created_at).to_i) + end + end + end + end + + context 'when updated after an SLA has been added to the system' do + before { ticket } # create ticket + before { sla } # create sla + + it 'is updated based on the new SLA’s #first_response_time' do + expect { ticket.save! } + .to change { ticket.escalation_at.to_i }.from(0).to(1.hour.from_now.to_i) + end + end + + context 'when updated after all SLAs have been removed from the system' do + before { sla } # create sla + before { ticket } # create ticket + before { sla.destroy } + + it 'is set to nil' do + expect { ticket.save! } + .to change { ticket.escalation_at }.to(nil) + end + end + end + + describe '#first_response_escalation_at' do + before { travel_to(Time.current) } # freeze time + let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) } + let(:calendar) { create(:calendar, :'24/7') } + + context 'with no SLAs in the system' do + it 'defaults to nil' do + expect(ticket.first_response_escalation_at).to be(nil) + end + end + + context 'with an SLA in the system' do + before { sla } # create sla + + it 'is set based on SLA’s #first_response_time' do + expect(ticket.first_response_escalation_at.to_i) + .to eq(1.hour.from_now.to_i) + end + + context 'after first agent’s response' do + before { ticket } # create ticket + let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') } + + it 'does not change' do + expect { article }.not_to change { ticket.first_response_escalation_at } + end + end + end + end + + describe '#update_escalation_at' do + before { travel_to(Time.current) } # freeze time + let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) } + let(:calendar) { create(:calendar, :'24/7') } + + context 'with no SLAs in the system' do + it 'defaults to nil' do + expect(ticket.update_escalation_at).to be(nil) + end + end + + context 'with an SLA in the system' do + before { sla } # create sla + + it 'is set based on SLA’s #update_time' do + expect(ticket.update_escalation_at.to_i) + .to eq(3.hours.from_now.to_i) + end + + context 'after first agent’s response' do + before { ticket } # create ticket + let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') } + + it 'is updated based on the SLA’s #update_time' do + travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same + + expect { article } + .to change { ticket.reload.update_escalation_at.to_i } + .to(3.hours.from_now.to_i) + end + end + end + end + + describe '#close_escalation_at' do + before { travel_to(Time.current) } # freeze time + let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) } + let(:calendar) { create(:calendar, :'24/7') } + + context 'with no SLAs in the system' do + it 'defaults to nil' do + expect(ticket.close_escalation_at).to be(nil) + end + end + + context 'with an SLA in the system' do + before { sla } # create sla + + it 'is set based on SLA’s #solution_time' do + expect(ticket.close_escalation_at.to_i) + .to eq(4.hours.from_now.to_i) + end + + context 'after first agent’s response' do + before { ticket } # create ticket + let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') } + + it 'does not change' do + expect { article }.not_to change { ticket.close_escalation_at } + end + end + end + end end describe 'Associations:' do diff --git a/test/unit/ticket_escalation_test.rb b/test/unit/ticket_escalation_test.rb deleted file mode 100644 index dab3b9e65..000000000 --- a/test/unit/ticket_escalation_test.rb +++ /dev/null @@ -1,225 +0,0 @@ -require 'test_helper' - -class TicketEscalationTest < ActiveSupport::TestCase - test 'ticket create' do - ticket = Ticket.new( - title: 'some value 123', - group: Group.lookup(name: 'Users'), - customer_id: 2, - updated_by_id: 1, - created_by_id: 1, - ) - ticket.save! - assert(ticket, 'ticket created') - assert_not(ticket.escalation_at) - assert_not(ticket.has_changes_to_save?) - - article = Ticket::Article.create!( - ticket_id: ticket.id, - type_id: Ticket::Article::Type.find_by(name: 'note').id, - sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id, - body: 'some body', - internal: false, - updated_by_id: 1, - created_by_id: 1, - ) - assert_not(article.has_changes_to_save?) - assert_not(ticket.has_changes_to_save?) - - calendar = Calendar.create_or_update( - name: 'Escalation Test', - timezone: 'Europe/Berlin', - business_hours: { - mon: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - tue: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - wed: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - thu: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - fri: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - sat: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - sun: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - }, - default: true, - ical_url: nil, - updated_by_id: 1, - created_by_id: 1, - ) - - sla = Sla.create_or_update( - name: 'test sla 1', - condition: { - 'ticket.title' => { - operator: 'contains', - value: 'some value 123', - }, - }, - first_response_time: 60, - update_time: 180, - solution_time: 240, - calendar_id: calendar.id, - updated_by_id: 1, - created_by_id: 1, - ) - - ticket = Ticket.new( - title: 'some value 123', - group: Group.lookup(name: 'Users'), - customer_id: 2, - updated_by_id: 1, - created_by_id: 1, - ) - ticket.save! - assert(ticket, 'ticket created') - ticket_escalation_at = ticket.escalation_at - assert(ticket.escalation_at) - assert_not(ticket.has_changes_to_save?) - - article = Ticket::Article.create!( - ticket_id: ticket.id, - type_id: Ticket::Article::Type.find_by(name: 'note').id, - sender_id: Ticket::Article::Sender.find_by(name: 'Customer').id, - body: 'some body', - internal: false, - updated_by_id: 1, - created_by_id: 1, - ) - assert_not(article.has_changes_to_save?) - assert_not(ticket.has_changes_to_save?) - - travel 1.second - - sla.first_response_time = 30 - sla.save! - - ticket.save! - assert_not(ticket.has_changes_to_save?) - assert(ticket.escalation_at) - assert_in_delta((ticket_escalation_at - 30.minutes).to_i, ticket.escalation_at.to_i, 90) - - sla.destroy! - calendar.destroy! - - ticket.save! - assert_not(ticket.has_changes_to_save?) - assert_not(ticket.escalation_at) - - end - - test 'email process and reply via email' do - - calendar = Calendar.create_or_update( - name: 'Escalation Test', - timezone: 'Europe/Berlin', - business_hours: { - mon: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - tue: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - wed: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - thu: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - fri: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - sat: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - sun: { - active: true, - timeframes: [ ['00:00', '23:59'] ] - }, - }, - default: true, - ical_url: nil, - updated_by_id: 1, - created_by_id: 1, - ) - - sla = Sla.create_or_update( - name: 'test sla 1', - condition: { - 'ticket.title' => { - operator: 'contains', - value: 'some value 123', - }, - }, - first_response_time: 60, - update_time: 180, - solution_time: 240, - calendar_id: calendar.id, - updated_by_id: 1, - created_by_id: 1, - ) - - email = "From: Bob Smith -To: zammad@example.com -Subject: some value 123 - -Some Text" - - ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email) - ticket_p.reload - assert(ticket_p.escalation_at) - assert_in_delta(ticket_p.first_response_escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 90) - assert_in_delta(ticket_p.update_escalation_at.to_i, (ticket_p.created_at + 3.hours).to_i, 90) - assert_in_delta(ticket_p.close_escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 90) - assert_in_delta(ticket_p.escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 90) - - travel 3.hours - article = nil - - ticket_p.with_lock do - article = Ticket::Article.create!( - ticket_id: ticket_p.id, - from: 'some_sender@example.com', - to: 'some_recipient@example.com', - subject: 'some subject', - message_id: 'some@id', - body: 'some message', - internal: false, - sender: Ticket::Article::Sender.where(name: 'Agent').first, - type: Ticket::Article::Type.where(name: 'email').first, - updated_by_id: 1, - created_by_id: 1, - ) - end - - ticket_p.reload - assert_in_delta(ticket_p.first_response_escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 90) - assert_in_delta(ticket_p.update_escalation_at.to_i, (ticket_p.last_contact_agent_at + 3.hours).to_i, 90) - assert_in_delta(ticket_p.close_escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 90) - assert_in_delta(ticket_p.escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 90) - - end -end