caller_id_spec.rb 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # Copyright (C) 2012-2022 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: http://znuny
  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 match_array([caller_id])
  96. end
  97. context 'shared by multiple CallerIds' do
  98. context '(for different users)' do
  99. subject!(:caller_ids) do
  100. [create(:caller_id, caller_id: number, user: create(:user)),
  101. create(:caller_id, caller_id: number, user: create(:user))]
  102. end
  103. it 'returns all corresponding CallerId records' do
  104. expect(described_class.lookup(number)).to match_array(caller_ids)
  105. end
  106. end
  107. context '(for the same user)' do
  108. subject!(:caller_ids) { create_list(:caller_id, 2, caller_id: number) }
  109. it 'returns one corresponding CallerId record by MAX(id)' do
  110. expect(described_class.lookup(number)).to match_array(caller_ids.last(1))
  111. end
  112. end
  113. context '(some for the same user, some for another)' do
  114. subject!(:caller_ids) do
  115. [*create_list(:caller_id, 2, caller_id: number, user: create(:user)),
  116. create(:caller_id, caller_id: number, user: create(:user))]
  117. end
  118. it 'returns one CallerId record per unique #user_id, by MAX(id)' do
  119. expect(described_class.lookup(number)).to match_array(caller_ids.last(2))
  120. end
  121. end
  122. end
  123. end
  124. end
  125. describe '.rebuild' do
  126. context 'when a User record contains a valid phone number' do
  127. let!(:user) { create(:agent, phone: '+49 123 456') }
  128. context 'and no corresponding CallerId exists' do
  129. it 'generates a CallerId record (with #level "known")' do
  130. described_class.destroy_all # CallerId already generated in User callback
  131. expect { described_class.rebuild }
  132. .to change { described_class.exists?(user_id: user.id, caller_id: '49123456', level: 'known') }
  133. .to(true)
  134. end
  135. end
  136. it 'does not create duplicate CallerId records' do
  137. expect { described_class.rebuild }.not_to change(described_class, :count)
  138. end
  139. end
  140. context 'when no User exists for a given CallerId record' do
  141. subject!(:caller_id) { create(:caller_id) }
  142. it 'deletes the CallerId record' do
  143. expect { described_class.rebuild }
  144. .to change { described_class.exists?(caller_id.id) }.to(false)
  145. end
  146. end
  147. context 'when two User records contains the same valid phone number' do
  148. let!(:users) { create_list(:agent, 2, phone: '+49 123 456') }
  149. it 'generates two corresponding CallerId records (with #level "known")' do
  150. described_class.destroy_all # CallerId already generated in User callback
  151. expect { described_class.rebuild }
  152. .to change { described_class.exists?(user_id: users.first.id, caller_id: '49123456', level: 'known') }
  153. .to(true)
  154. .and change { described_class.exists?(user_id: users.last.id, caller_id: '49123456', level: 'known') }
  155. .to(true)
  156. end
  157. end
  158. context 'when an Article record contains a valid phone number in its body' do
  159. let!(:article) { create(:ticket_article, body: <<~BODY, sender_name: sender_name) }
  160. some message
  161. Fon (GEL): +49 123-456 Mi-Fr
  162. BODY
  163. context 'and comes from a customer' do
  164. let(:sender_name) { 'Customer' }
  165. it 'generates a CallerId record (with #level "maybe")' do
  166. described_class.destroy_all # CallerId already generated in Article observer job
  167. expect { described_class.rebuild }
  168. .to change { described_class.exists?(user_id: article.created_by_id, caller_id: '49123456', level: 'maybe') }
  169. .to(true)
  170. end
  171. end
  172. context 'and comes from an Agent' do
  173. let(:sender_name) { 'Agent' }
  174. it 'does not generate a CallerId record' do
  175. expect { described_class.rebuild }
  176. .not_to change { described_class.exists?(caller_id: '49123456') }
  177. end
  178. end
  179. end
  180. end
  181. describe '.maybe_add' do
  182. let(:attributes) { attributes_for(:caller_id) }
  183. it 'wraps .find_or_initialize_by (passing only five defining attributes)' do
  184. expect(described_class)
  185. .to receive(:find_or_initialize_by)
  186. .with(attributes.slice(:caller_id, :level, :object, :o_id, :user_id))
  187. .and_call_original
  188. described_class.maybe_add(attributes)
  189. end
  190. context 'if no matching record found' do
  191. it 'adds given #comment attribute' do
  192. expect { described_class.maybe_add(attributes.merge(comment: 'foo')) }
  193. .to change(described_class, :count).by(1)
  194. expect(described_class.last.comment).to eq('foo')
  195. end
  196. end
  197. context 'if matching record found' do
  198. let(:attributes) { caller_id.attributes.symbolize_keys }
  199. let(:caller_id) { create(:caller_id) }
  200. it 'ignores given #comment attribute' do
  201. expect(described_class.maybe_add(attributes.merge(comment: 'foo')))
  202. .to eq(caller_id)
  203. expect(caller_id.comment).to be_blank
  204. end
  205. end
  206. end
  207. describe '.known_agents_by_number' do
  208. context 'with known agent caller_id' do
  209. let!(:agent1) { create(:agent, phone: '0123456') }
  210. let!(:agent2) { create(:agent, phone: '0123457') }
  211. it 'gives matching agents' do
  212. expect(described_class.known_agents_by_number('49123456'))
  213. .to match_array([agent1])
  214. end
  215. end
  216. context 'with known customer caller_id' do
  217. let!(:customer1) { create(:customer, phone: '0123456') }
  218. it 'returns an empty array' do
  219. expect(described_class.known_agents_by_number('49123456')).to eq([])
  220. end
  221. end
  222. context 'with maybe caller_id' do
  223. let(:ticket1) do
  224. create(:ticket_article, created_by_id: customer2.id, body: 'some text 0123457') # create ticket
  225. TransactionDispatcher.commit
  226. Scheduler.worker(true)
  227. end
  228. let!(:customer2) { create(:customer) }
  229. it 'returns an empty array' do
  230. expect(described_class.known_agents_by_number('49123457').count).to eq(0)
  231. end
  232. end
  233. end
  234. describe 'callbacks' do
  235. subject!(:caller_id) { build(:cti_caller_id, caller_id: phone) }
  236. let(:phone) { '1234567890' }
  237. describe 'on creation' do
  238. it 'adopts CTI Logs from same number (via UpdateCtiLogsByCallerJob)' do
  239. expect(UpdateCtiLogsByCallerJob).to receive(:perform_later)
  240. caller_id.save
  241. end
  242. it 'splits job into fg and bg (for more responsive UI – see #2057)' do
  243. expect(UpdateCtiLogsByCallerJob).to receive(:perform_now).with(phone, limit: 20)
  244. expect(UpdateCtiLogsByCallerJob).to receive(:perform_later).with(phone, limit: 40, offset: 20)
  245. caller_id.save
  246. end
  247. it 'skips jobs on import_mode true' do
  248. Setting.set('import_mode', true)
  249. expect(UpdateCtiLogsByCallerJob).not_to receive(:perform_now)
  250. expect(UpdateCtiLogsByCallerJob).not_to receive(:perform_later)
  251. caller_id.save
  252. end
  253. end
  254. describe 'on destruction' do
  255. before { caller_id.save }
  256. it 'orphans CTI Logs from same number (via UpdateCtiLogsByCallerJob)' do
  257. expect(UpdateCtiLogsByCallerJob).to receive(:perform_later).with(phone)
  258. caller_id.destroy
  259. end
  260. end
  261. end
  262. end