123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- require 'rails_helper'
- RSpec.describe Cti::CallerId do
- describe '.extract_numbers' do
- context 'for strings containing arbitrary numbers (<6 digits long)' do
- it 'returns an empty array' do
- expect(described_class.extract_numbers(<<~INPUT.chomp)).to be_empty
- some text
- test 123
- INPUT
- end
- end
- context 'for strings containing a phone number with "(0)" after country code' do
- it 'returns the number in an array, without the leading "(0)"' do
- expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(['4930600000000'])
- Lorem ipsum dolor sit amet, consectetuer +49 (0) 30 60 00 00 00-0
- adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.
- Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
- ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu,
- pretium quis, sem. Nulla consequat massa quis enim. Donec pede
- justo, fringilla vel.
- INPUT
- end
- end
- context 'for strings containing a phone number with leading 0 (no country code)' do
- it 'returns the number in an array, using default country code (49)' do
- expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(['4994221000'])
- GS Oberalteich
- Telefon 09422 1000
- E-Mail:
- INPUT
- end
- end
- context 'for strings containing multiple phone numbers' do
- it 'returns all numbers in an array' do
- expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(%w[41812886393 41763467214])
- Tel +41 81 288 63 93 / +41 76 346 72 14 ...
- INPUT
- end
- end
- context 'for strings containing US-formatted numbers' do
- it 'returns the numbers in an array correctly' do
- expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(%w[19494310000 19494310001])
- P: +1 (949) 431 0000
- F: +1 (949) 431 0001
- W: https://zammad.com
- INPUT
- end
- end
- end
- describe '.normalize_number' do
- it 'does not modify digit-only strings (starting with 1-9)' do
- expect(described_class.normalize_number('5754321')).to eq('5754321')
- end
- it 'strips whitespace' do
- expect(described_class.normalize_number('622 32281')).to eq('62232281')
- end
- it 'strips hyphens' do
- expect(described_class.normalize_number('1-888-407-4747')).to eq('18884074747')
- end
- it 'strips leading pluses' do
- expect(described_class.normalize_number('+49 30 53 00 00 000')).to eq('4930530000000')
- expect(described_class.normalize_number('+49 160 0000000')).to eq('491600000000')
- end
- it 'replaces a single leading zero with the default country code (49)' do
- expect(described_class.normalize_number('092213212')).to eq('4992213212')
- expect(described_class.normalize_number('0271233211')).to eq('49271233211')
- expect(described_class.normalize_number('022 1234567')).to eq('49221234567')
- expect(described_class.normalize_number('09 123 32112')).to eq('49912332112')
- expect(described_class.normalize_number('021 2331231')).to eq('49212331231')
- expect(described_class.normalize_number('021 321123123')).to eq('4921321123123')
- expect(described_class.normalize_number('0150 12345678')).to eq('4915012345678')
- expect(described_class.normalize_number('021-233-9123')).to eq('49212339123')
- end
- it 'strips two leading zeroes' do
- expect(described_class.normalize_number('0049 1234 123456789')).to eq('491234123456789')
- expect(described_class.normalize_number('0043 30 60 00 00 00-0')).to eq('4330600000000')
- end
- it 'strips leading zero from "(0x)" at start of number or after country code' do
- expect(described_class.normalize_number('(09)1234321')).to eq('4991234321')
- expect(described_class.normalize_number('+49 (0) 30 60 00 00 00-0')).to eq('4930600000000')
- expect(described_class.normalize_number('0043 (0) 30 60 00 00 00-0')).to eq('4330600000000')
- end
- end
- describe '.lookup' do
- context 'when given an unrecognized number' do
- it 'returns an empty array' do
- expect(described_class.lookup('1')).to eq([])
- end
- end
- context 'when given a recognized number' do
- subject!(:caller_id) { create(:caller_id, caller_id: number) }
- let(:number) { '1234567890' }
- it 'returns an array with the corresponding CallerId' do
- expect(described_class.lookup(number)).to contain_exactly(caller_id)
- end
- context 'shared by multiple CallerIds' do
- context '(for different users)' do
- subject!(:caller_ids) do
- # rubocop:disable FactoryBot/CreateList
- [create(:caller_id, caller_id: number, user: create(:user)),
- create(:caller_id, caller_id: number, user: create(:user))]
- # rubocop:enable FactoryBot/CreateList
- end
- it 'returns all corresponding CallerId records' do
- expect(described_class.lookup(number)).to match_array(caller_ids)
- end
- end
- context '(for the same user)' do
- subject!(:caller_ids) { create_list(:caller_id, 2, caller_id: number) }
- it 'returns one corresponding CallerId record by MAX(id)' do
- expect(described_class.lookup(number)).to match_array(caller_ids.last(1))
- end
- end
- context '(some for the same user, some for another)' do
- subject!(:caller_ids) do
- [*create_list(:caller_id, 2, caller_id: number, user: create(:user)),
- create(:caller_id, caller_id: number, user: create(:user))]
- end
- it 'returns one CallerId record per unique #user_id, by MAX(id)' do
- expect(described_class.lookup(number)).to match_array(caller_ids.last(2))
- end
- end
- end
- end
- end
- describe '.rebuild' do
- context 'when a User record contains a valid phone number' do
- let!(:user) { create(:agent, phone: '+49 123 456') }
- context 'and no corresponding CallerId exists' do
- it 'generates a CallerId record (with #level "known")' do
- described_class.destroy_all # CallerId already generated in User callback
- expect { described_class.rebuild }
- .to change { described_class.exists?(user_id: user.id, caller_id: '49123456', level: 'known') }
- .to(true)
- end
- end
- it 'does not create duplicate CallerId records' do
- expect { described_class.rebuild }.not_to change(described_class, :count)
- end
- end
- context 'when no User exists for a given CallerId record' do
- subject!(:caller_id) { create(:caller_id) }
- it 'deletes the CallerId record' do
- expect { described_class.rebuild }
- .to change { described_class.exists?(caller_id.id) }.to(false)
- end
- end
- context 'when two User records contains the same valid phone number' do
- let!(:users) { create_list(:agent, 2, phone: '+49 123 456') }
- it 'generates two corresponding CallerId records (with #level "known")' do
- described_class.destroy_all # CallerId already generated in User callback
- expect { described_class.rebuild }
- .to change { described_class.exists?(user_id: users.first.id, caller_id: '49123456', level: 'known') }
- .to(true)
- .and change { described_class.exists?(user_id: users.last.id, caller_id: '49123456', level: 'known') }
- .to(true)
- end
- end
- context 'when an Article record contains a valid phone number in its body' do
- let!(:article) { create(:ticket_article, body: <<~BODY, sender_name: sender_name) }
- some message
- Fon (GEL): +49 123-456 Mi-Fr
- BODY
- context 'and comes from a customer' do
- let(:sender_name) { 'Customer' }
- it 'generates a CallerId record (with #level "maybe")' do
- described_class.destroy_all # CallerId already generated in Article observer job
- expect { described_class.rebuild }
- .to change { described_class.exists?(user_id: article.created_by_id, caller_id: '49123456', level: 'maybe') }
- .to(true)
- end
- end
- context 'and comes from an Agent' do
- let(:sender_name) { 'Agent' }
- it 'does not generate a CallerId record' do
- expect { described_class.rebuild }
- .not_to change { described_class.exists?(caller_id: '49123456') }
- end
- end
- end
- end
- describe '.maybe_add' do
- let(:attributes) { attributes_for(:caller_id) }
- it 'wraps .find_or_initialize_by (passing only five defining attributes)' do
- expect(described_class)
- .to receive(:find_or_initialize_by)
- .with(attributes.slice(:caller_id, :level, :object, :o_id, :user_id))
- .and_call_original
- described_class.maybe_add(attributes)
- end
- context 'if no matching record found' do
- it 'adds given #comment attribute' do
- expect { described_class.maybe_add(attributes.merge(comment: 'foo')) }
- .to change(described_class, :count).by(1)
- expect(described_class.last.comment).to eq('foo')
- end
- end
- context 'if matching record found' do
- let(:attributes) { caller_id.attributes.symbolize_keys }
- let(:caller_id) { create(:caller_id) }
- it 'ignores given #comment attribute' do
- expect(described_class.maybe_add(attributes.merge(comment: 'foo')))
- .to eq(caller_id)
- expect(caller_id.comment).to be_blank
- end
- end
- end
- describe '.known_agents_by_number' do
- context 'with known agent caller_id' do
- let!(:agent1) { create(:agent, phone: '0123456') }
- let!(:agent2) { create(:agent, phone: '0123457') }
- it 'gives matching agents' do
- expect(described_class.known_agents_by_number('49123456'))
- .to contain_exactly(agent1)
- end
- end
- context 'with known customer caller_id' do
- let!(:customer1) { create(:customer, phone: '0123456') }
- it 'returns an empty array' do
- expect(described_class.known_agents_by_number('49123456')).to eq([])
- end
- end
- context 'with maybe caller_id', performs_jobs: true do
- let(:ticket1) do
- create(:ticket_article, created_by_id: customer2.id, body: 'some text 0123457') # create ticket
- performs_jobs commit_transaction: true
- end
- let!(:customer2) { create(:customer) }
- it 'returns an empty array' do
- expect(described_class.known_agents_by_number('49123457').count).to eq(0)
- end
- end
- end
- describe 'callbacks' do
- subject!(:caller_id) { build(:cti_caller_id, caller_id: phone) }
- let(:phone) { '1234567890' }
- describe 'on creation' do
- it 'adopts CTI Logs from same number (via UpdateCtiLogsByCallerJob)' do
- expect(UpdateCtiLogsByCallerJob).to receive(:perform_later)
- caller_id.save
- end
- it 'splits job into fg and bg (for more responsive UI – see #2057)' do
- expect(UpdateCtiLogsByCallerJob).to receive(:perform_now).with(phone, limit: 20)
- expect(UpdateCtiLogsByCallerJob).to receive(:perform_later).with(phone, limit: 40, offset: 20)
- caller_id.save
- end
- it 'skips jobs on import_mode true' do
- Setting.set('import_mode', true)
- expect(UpdateCtiLogsByCallerJob).not_to receive(:perform_now)
- expect(UpdateCtiLogsByCallerJob).not_to receive(:perform_later)
- caller_id.save
- end
- end
- describe 'on destruction' do
- before { caller_id.save }
- it 'orphans CTI Logs from same number (via UpdateCtiLogsByCallerJob)' do
- expect(UpdateCtiLogsByCallerJob).to receive(:perform_later).with(phone)
- caller_id.destroy
- end
- end
- end
- end
|