caller_id_spec.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Cti::CallerId do
  4. describe '.extract_numbers' do
  5. context 'for strings containing arbitrary numbers (<6 digits long)' do
  6. it 'returns an empty array' do
  7. expect(described_class.extract_numbers(<<~INPUT.chomp)).to be_empty
  8. some text
  9. test 123
  10. INPUT
  11. end
  12. end
  13. context 'for strings containing a phone number with "(0)" after country code' do
  14. it 'returns the number in an array, without the leading "(0)"' do
  15. expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(['4930600000000'])
  16. Lorem ipsum dolor sit amet, consectetuer +49 (0) 30 60 00 00 00-0
  17. adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.
  18. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur
  19. ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu,
  20. pretium quis, sem. Nulla consequat massa quis enim. Donec pede
  21. justo, fringilla vel.
  22. INPUT
  23. end
  24. end
  25. context 'for strings containing a phone number with leading 0 (no country code)' do
  26. it 'returns the number in an array, using default country code (49)' do
  27. expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(['4994221000'])
  28. GS Oberalteich
  29. Telefon 09422 1000
  30. E-Mail:
  31. INPUT
  32. end
  33. end
  34. context 'for strings containing multiple phone numbers' do
  35. it 'returns all numbers in an array' do
  36. expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(%w[41812886393 41763467214])
  37. Tel +41 81 288 63 93 / +41 76 346 72 14 ...
  38. INPUT
  39. end
  40. end
  41. context 'for strings containing US-formatted numbers' do
  42. it 'returns the numbers in an array correctly' do
  43. expect(described_class.extract_numbers(<<~INPUT.chomp)).to eq(%w[19494310000 19494310001])
  44. P: +1 (949) 431 0000
  45. F: +1 (949) 431 0001
  46. W: https://zammad.com
  47. INPUT
  48. end
  49. end
  50. end
  51. describe '.normalize_number' do
  52. it 'does not modify digit-only strings (starting with 1-9)' do
  53. expect(described_class.normalize_number('5754321')).to eq('5754321')
  54. end
  55. it 'strips whitespace' do
  56. expect(described_class.normalize_number('622 32281')).to eq('62232281')
  57. end
  58. it 'strips hyphens' do
  59. expect(described_class.normalize_number('1-888-407-4747')).to eq('18884074747')
  60. end
  61. it 'strips leading pluses' do
  62. expect(described_class.normalize_number('+49 30 53 00 00 000')).to eq('4930530000000')
  63. expect(described_class.normalize_number('+49 160 0000000')).to eq('491600000000')
  64. end
  65. it 'replaces a single leading zero with the default country code (49)' do
  66. expect(described_class.normalize_number('092213212')).to eq('4992213212')
  67. expect(described_class.normalize_number('0271233211')).to eq('49271233211')
  68. expect(described_class.normalize_number('022 1234567')).to eq('49221234567')
  69. expect(described_class.normalize_number('09 123 32112')).to eq('49912332112')
  70. expect(described_class.normalize_number('021 2331231')).to eq('49212331231')
  71. expect(described_class.normalize_number('021 321123123')).to eq('4921321123123')
  72. expect(described_class.normalize_number('0150 12345678')).to eq('4915012345678')
  73. expect(described_class.normalize_number('021-233-9123')).to eq('49212339123')
  74. end
  75. it 'strips two leading zeroes' do
  76. expect(described_class.normalize_number('0049 1234 123456789')).to eq('491234123456789')
  77. expect(described_class.normalize_number('0043 30 60 00 00 00-0')).to eq('4330600000000')
  78. end
  79. it 'strips leading zero from "(0x)" at start of number or after country code' do
  80. expect(described_class.normalize_number('(09)1234321')).to eq('4991234321')
  81. expect(described_class.normalize_number('+49 (0) 30 60 00 00 00-0')).to eq('4930600000000')
  82. expect(described_class.normalize_number('0043 (0) 30 60 00 00 00-0')).to eq('4330600000000')
  83. end
  84. end
  85. describe '.lookup' do
  86. context 'when given an unrecognized number' do
  87. it 'returns an empty array' do
  88. expect(described_class.lookup('1')).to eq([])
  89. end
  90. end
  91. context 'when given a recognized number' do
  92. subject!(:caller_id) { create(:caller_id, caller_id: number) }
  93. let(:number) { '1234567890' }
  94. it 'returns an array with the corresponding CallerId' do
  95. expect(described_class.lookup(number)).to contain_exactly(caller_id)
  96. end
  97. context 'shared by multiple CallerIds' do
  98. context '(for different users)' do
  99. subject!(:caller_ids) do
  100. # rubocop:disable FactoryBot/CreateList
  101. [create(:caller_id, caller_id: number, user: create(:user)),
  102. create(:caller_id, caller_id: number, user: create(:user))]
  103. # rubocop:enable FactoryBot/CreateList
  104. end
  105. it 'returns all corresponding CallerId records' do
  106. expect(described_class.lookup(number)).to match_array(caller_ids)
  107. end
  108. end
  109. context '(for the same user)' do
  110. subject!(:caller_ids) { create_list(:caller_id, 2, caller_id: number) }
  111. it 'returns one corresponding CallerId record by MAX(id)' do
  112. expect(described_class.lookup(number)).to match_array(caller_ids.last(1))
  113. end
  114. end
  115. context '(some for the same user, some for another)' do
  116. subject!(:caller_ids) do
  117. [*create_list(:caller_id, 2, caller_id: number, user: create(:user)),
  118. create(:caller_id, caller_id: number, user: create(:user))]
  119. end
  120. it 'returns one CallerId record per unique #user_id, by MAX(id)' do
  121. expect(described_class.lookup(number)).to match_array(caller_ids.last(2))
  122. end
  123. end
  124. end
  125. end
  126. end
  127. describe '.rebuild' do
  128. context 'when a User record contains a valid phone number' do
  129. let!(:user) { create(:agent, phone: '+49 123 456') }
  130. context 'and no corresponding CallerId exists' do
  131. it 'generates a CallerId record (with #level "known")' do
  132. described_class.destroy_all # CallerId already generated in User callback
  133. expect { described_class.rebuild }
  134. .to change { described_class.exists?(user_id: user.id, caller_id: '49123456', level: 'known') }
  135. .to(true)
  136. end
  137. end
  138. it 'does not create duplicate CallerId records' do
  139. expect { described_class.rebuild }.not_to change(described_class, :count)
  140. end
  141. end
  142. context 'when no User exists for a given CallerId record' do
  143. subject!(:caller_id) { create(:caller_id) }
  144. it 'deletes the CallerId record' do
  145. expect { described_class.rebuild }
  146. .to change { described_class.exists?(caller_id.id) }.to(false)
  147. end
  148. end
  149. context 'when two User records contains the same valid phone number' do
  150. let!(:users) { create_list(:agent, 2, phone: '+49 123 456') }
  151. it 'generates two corresponding CallerId records (with #level "known")' do
  152. described_class.destroy_all # CallerId already generated in User callback
  153. expect { described_class.rebuild }
  154. .to change { described_class.exists?(user_id: users.first.id, caller_id: '49123456', level: 'known') }
  155. .to(true)
  156. .and change { described_class.exists?(user_id: users.last.id, caller_id: '49123456', level: 'known') }
  157. .to(true)
  158. end
  159. end
  160. context 'when an Article record contains a valid phone number in its body' do
  161. let!(:article) { create(:ticket_article, body: <<~BODY, sender_name: sender_name) }
  162. some message
  163. Fon (GEL): +49 123-456 Mi-Fr
  164. BODY
  165. context 'and comes from a customer' do
  166. let(:sender_name) { 'Customer' }
  167. it 'generates a CallerId record (with #level "maybe")' do
  168. described_class.destroy_all # CallerId already generated in Article observer job
  169. expect { described_class.rebuild }
  170. .to change { described_class.exists?(user_id: article.created_by_id, caller_id: '49123456', level: 'maybe') }
  171. .to(true)
  172. end
  173. end
  174. context 'and comes from an Agent' do
  175. let(:sender_name) { 'Agent' }
  176. it 'does not generate a CallerId record' do
  177. expect { described_class.rebuild }
  178. .not_to change { described_class.exists?(caller_id: '49123456') }
  179. end
  180. end
  181. end
  182. end
  183. describe '.maybe_add' do
  184. let(:attributes) { attributes_for(:caller_id) }
  185. it 'wraps .find_or_initialize_by (passing only five defining attributes)' do
  186. expect(described_class)
  187. .to receive(:find_or_initialize_by)
  188. .with(attributes.slice(:caller_id, :level, :object, :o_id, :user_id))
  189. .and_call_original
  190. described_class.maybe_add(attributes)
  191. end
  192. context 'if no matching record found' do
  193. it 'adds given #comment attribute' do
  194. expect { described_class.maybe_add(attributes.merge(comment: 'foo')) }
  195. .to change(described_class, :count).by(1)
  196. expect(described_class.last.comment).to eq('foo')
  197. end
  198. end
  199. context 'if matching record found' do
  200. let(:attributes) { caller_id.attributes.symbolize_keys }
  201. let(:caller_id) { create(:caller_id) }
  202. it 'ignores given #comment attribute' do
  203. expect(described_class.maybe_add(attributes.merge(comment: 'foo')))
  204. .to eq(caller_id)
  205. expect(caller_id.comment).to be_blank
  206. end
  207. end
  208. end
  209. describe '.known_agents_by_number' do
  210. context 'with known agent caller_id' do
  211. let!(:agent1) { create(:agent, phone: '0123456') }
  212. let!(:agent2) { create(:agent, phone: '0123457') }
  213. it 'gives matching agents' do
  214. expect(described_class.known_agents_by_number('49123456'))
  215. .to contain_exactly(agent1)
  216. end
  217. end
  218. context 'with known customer caller_id' do
  219. let!(:customer1) { create(:customer, phone: '0123456') }
  220. it 'returns an empty array' do
  221. expect(described_class.known_agents_by_number('49123456')).to eq([])
  222. end
  223. end
  224. context 'with maybe caller_id', performs_jobs: true do
  225. let(:ticket1) do
  226. create(:ticket_article, created_by_id: customer2.id, body: 'some text 0123457') # create ticket
  227. performs_jobs commit_transaction: true
  228. end
  229. let!(:customer2) { create(:customer) }
  230. it 'returns an empty array' do
  231. expect(described_class.known_agents_by_number('49123457').count).to eq(0)
  232. end
  233. end
  234. end
  235. describe 'callbacks' do
  236. subject!(:caller_id) { build(:cti_caller_id, caller_id: phone) }
  237. let(:phone) { '1234567890' }
  238. describe 'on creation' do
  239. it 'adopts CTI Logs from same number (via UpdateCtiLogsByCallerJob)' do
  240. expect(UpdateCtiLogsByCallerJob).to receive(:perform_later)
  241. caller_id.save
  242. end
  243. it 'splits job into fg and bg (for more responsive UI – see #2057)' do
  244. expect(UpdateCtiLogsByCallerJob).to receive(:perform_now).with(phone, limit: 20)
  245. expect(UpdateCtiLogsByCallerJob).to receive(:perform_later).with(phone, limit: 40, offset: 20)
  246. caller_id.save
  247. end
  248. it 'skips jobs on import_mode true' do
  249. Setting.set('import_mode', true)
  250. expect(UpdateCtiLogsByCallerJob).not_to receive(:perform_now)
  251. expect(UpdateCtiLogsByCallerJob).not_to receive(:perform_later)
  252. caller_id.save
  253. end
  254. end
  255. describe 'on destruction' do
  256. before { caller_id.save }
  257. it 'orphans CTI Logs from same number (via UpdateCtiLogsByCallerJob)' do
  258. expect(UpdateCtiLogsByCallerJob).to receive(:perform_later).with(phone)
  259. caller_id.destroy
  260. end
  261. end
  262. end
  263. end