log_spec.rb 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Cti::Log do
  4. subject(:user) { create(:user, roles: Role.where(name: 'Agent'), phone: phone) }
  5. let(:phone) { '' }
  6. let(:log) { create(:'cti/log') }
  7. describe '.log' do
  8. it 'returns a hash with :list and :assets keys' do
  9. expect(described_class.log(user)).to match(hash_including(:list, :assets))
  10. end
  11. context 'when pretty is not generated' do
  12. let(:log) { create(:'cti/log') }
  13. before do
  14. log.update_column(:preferences, nil)
  15. end
  16. it 'does fallback generate the pretty value' do
  17. expect(log.reload.attributes['from_pretty']).to eq('+49 30 609854180')
  18. end
  19. end
  20. context 'when over 60 Log records exist' do
  21. subject!(:cti_logs) do
  22. 61.times.map do |_i| # rubocop:disable Performance/TimesMap
  23. travel 1.second
  24. create(:'cti/log')
  25. end
  26. end
  27. it 'returns the 60 latest ones in the :list key' do
  28. expect(described_class.log(user)[:list]).to match_array(cti_logs.last(60))
  29. end
  30. end
  31. context 'when Log records have arrays of CallerId attributes in #preferences[:to] / #preferences[:from]' do
  32. subject!(:cti_log) { create(:'cti/log', preferences: { from: [caller_id] }) }
  33. let(:caller_id) { create(:caller_id) }
  34. let(:caller_user) { User.find_by(id: caller_id.user_id) }
  35. it 'returns a hash of the CallerId Users and their assets in the :assets key' do
  36. expect(described_class.log(user)[:assets]).to eq(caller_user.assets({}))
  37. end
  38. end
  39. context 'when a notify map is defined' do
  40. subject!(:cti_logs) do
  41. [create(:'cti/log', queue: 'queue0'),
  42. create(:'cti/log', queue: 'queue2'),
  43. create(:'cti/log', queue: 'queue3'),
  44. create(:'cti/log', queue: 'queue4')]
  45. end
  46. before do
  47. cti_config = Setting.get('cti_config')
  48. cti_config[:notify_map] = [ { queue: 'queue4', user_ids: [user.id.to_s] } ]
  49. Setting.set('cti_config', cti_config)
  50. end
  51. it 'returns one matching log record' do
  52. expect(described_class.log(user)[:list]).to contain_exactly(cti_logs[3])
  53. end
  54. end
  55. end
  56. describe '.push_caller_list_update?' do
  57. let!(:existing_logs) { create_list(:'cti/log', 60) }
  58. let(:log) { create(:'cti/log') }
  59. context 'when given log is older than existing logs' do
  60. before { travel(-10.seconds) }
  61. it 'return false' do
  62. expect(described_class.push_caller_list_update?(log)).to be false
  63. end
  64. end
  65. context 'when given log is newer than existing logs' do
  66. before { travel(10.seconds) }
  67. it 'return true' do
  68. expect(described_class.push_caller_list_update?(log)).to be true
  69. end
  70. end
  71. end
  72. describe '.process' do
  73. let(:attributes) do
  74. {
  75. 'cause' => cause,
  76. 'event' => event,
  77. 'user' => 'user 1',
  78. 'from' => '49123456',
  79. 'to' => '49123457',
  80. 'call_id' => '1',
  81. 'direction' => 'in',
  82. }
  83. end
  84. let(:cause) { '' }
  85. context 'for event "newCall"' do
  86. let(:event) { 'newCall' }
  87. context 'with unrecognized "call_id"' do
  88. it 'creates a new Log record' do
  89. expect { described_class.process(attributes) }
  90. .to change(described_class, :count).by(1)
  91. expect(described_class.last.attributes)
  92. .to include(
  93. 'call_id' => '1',
  94. 'state' => 'newCall',
  95. 'done' => false,
  96. 'queue' => '49123457',
  97. 'from' => '49123456',
  98. 'from_comment' => nil,
  99. 'from_pretty' => '+49 491 23456',
  100. 'start_at' => nil,
  101. 'end_at' => nil,
  102. 'to' => '49123457',
  103. 'to_comment' => 'user 1',
  104. 'to_pretty' => '+49 491 23457'
  105. )
  106. end
  107. context 'for direction "in", with a CallerId record matching the "from" number' do
  108. let!(:caller_id) { create(:caller_id, caller_id: '49123456') }
  109. before { attributes.merge!('direction' => 'in') }
  110. it 'saves that CallerId’s attributes in the new Log’s #preferences[:from] attribute' do
  111. described_class.process(attributes)
  112. expect(described_class.last.preferences[:from].first)
  113. .to include(caller_id.attributes.except('created_at')) # Checking equality of Time objects is error-prone
  114. end
  115. end
  116. context 'for direction "out", with a CallerId record matching the "to" number' do
  117. let!(:caller_id) { create(:caller_id, caller_id: '49123457') }
  118. before { attributes.merge!('direction' => 'out') }
  119. it 'saves that CallerId’s attributes in the new Log’s #preferences[:to] attribute' do
  120. described_class.process(attributes)
  121. expect(described_class.last.preferences[:to].first)
  122. .to include(caller_id.attributes.except('created_at')) # Checking equality of Time objects is error-prone
  123. end
  124. end
  125. end
  126. context 'with recognized "call_id"' do
  127. before { create(:'cti/log', call_id: '1') }
  128. it 'raises an error' do
  129. expect { described_class.process(attributes) }.to raise_error(%r{call_id \S+ already exists!})
  130. end
  131. end
  132. end
  133. context 'for event "answer"' do
  134. let(:event) { 'answer' }
  135. context 'with unrecognized "call_id"' do
  136. it 'raises an error' do
  137. expect { described_class.process(attributes) }.to raise_error(%r{No such call_id})
  138. end
  139. end
  140. context 'with recognized "call_id"' do
  141. context 'for Log with #state "newCall"' do
  142. let(:log) { create(:'cti/log', call_id: 1, state: 'newCall', done: false) }
  143. it 'returns early with no changes' do
  144. expect { described_class.process(attributes) }
  145. .to change { log.reload.state }.to('answer')
  146. .and change { log.reload.done }.to(true)
  147. end
  148. end
  149. context 'for Log with #state "hangup"' do
  150. let(:log) { create(:'cti/log', call_id: 1, state: 'hangup', done: false) }
  151. it 'returns early with no changes' do
  152. expect { described_class.process(attributes) }
  153. .not_to change(log, :reload)
  154. end
  155. end
  156. end
  157. end
  158. context 'for event "hangup"' do
  159. let(:event) { 'hangup' }
  160. context 'with unrecognized "call_id"' do
  161. it 'raises an error' do
  162. expect { described_class.process(attributes) }.to raise_error(%r{No such call_id})
  163. end
  164. end
  165. context 'with recognized "call_id"' do
  166. context 'for Log with #state "newCall"' do
  167. let(:log) { create(:'cti/log', call_id: 1, state: 'newCall', done: false) }
  168. it 'sets attributes #state: "hangup", #done: false' do
  169. expect { described_class.process(attributes) }
  170. .to change { log.reload.state }.to('hangup')
  171. .and not_change { log.reload.done }
  172. end
  173. context 'when call is forwarded' do
  174. let(:cause) { 'forwarded' }
  175. it 'sets attributes #state: "hangup", #done: true' do
  176. expect { described_class.process(attributes) }
  177. .to change { log.reload.state }.to('hangup')
  178. .and change { log.reload.done }.to(true)
  179. end
  180. end
  181. end
  182. context 'for Log with #state "answer"' do
  183. let(:log) { create(:'cti/log', call_id: 1, state: 'answer', done: true) }
  184. it 'sets attributes #state: "hangup"' do
  185. expect { described_class.process(attributes) }
  186. .to change { log.reload.state }.to('hangup')
  187. .and not_change { log.reload.done }
  188. end
  189. context 'when call is sent to voicemail' do
  190. before { log.update(to_comment: 'voicemail') }
  191. it 'sets attributes #state: "hangup", #done: false' do
  192. expect { described_class.process(attributes) }
  193. .to change { log.reload.state }.to('hangup')
  194. .and change { log.reload.done }.to(false)
  195. end
  196. end
  197. end
  198. end
  199. end
  200. context 'for preferences.from verification', performs_jobs: true do
  201. subject(:log) do
  202. described_class.process(attributes)
  203. end
  204. let(:customer_of_ticket) { create(:customer) }
  205. let(:ticket_sample) do
  206. ticket_article = create(:ticket_article, created_by_id: customer_of_ticket.id, body: 'some text 0123457')
  207. perform_enqueued_jobs commit_transaction: true
  208. ticket_article
  209. end
  210. let(:caller_id) { '0123456' }
  211. let(:attributes) do
  212. {
  213. 'cause' => '',
  214. 'event' => 'newCall',
  215. 'user' => 'user 1',
  216. 'from' => caller_id,
  217. 'to' => '49123450',
  218. 'call_id' => '1',
  219. 'direction' => 'in',
  220. }
  221. end
  222. context 'with now related customer' do
  223. it 'gives no caller information' do
  224. expect(log.preferences[:from]).to be_nil
  225. end
  226. end
  227. context 'with related known customer' do
  228. let!(:customer) { create(:customer, phone: '0123456') }
  229. it 'gives caller information' do
  230. expect(log.preferences[:from].count).to eq(1)
  231. expect(log.preferences[:from].first)
  232. .to include(
  233. 'level' => 'known',
  234. 'user_id' => customer.id,
  235. )
  236. end
  237. end
  238. context 'with related known customers' do
  239. let!(:customer1) { create(:customer, phone: '0123456') }
  240. let!(:customer2) { create(:customer, phone: '0123456') }
  241. it 'gives caller information' do
  242. expect(log.preferences[:from].count).to eq(2)
  243. expect(log.preferences[:from].first)
  244. .to include(
  245. 'level' => 'known',
  246. 'user_id' => customer2.id,
  247. )
  248. end
  249. end
  250. context 'with related maybe customer' do
  251. let(:caller_id) { '0123457' }
  252. let!(:ticket) { ticket_sample }
  253. it 'gives caller information' do
  254. expect(log.preferences[:from].count).to eq(1)
  255. expect(log.preferences[:from].first)
  256. .to include(
  257. 'level' => 'maybe',
  258. 'user_id' => customer_of_ticket.id,
  259. )
  260. end
  261. end
  262. context 'with related maybe and known customer' do
  263. let(:caller_id) { '0123457' }
  264. let!(:customer) { create(:customer, phone: '0123457') }
  265. let!(:ticket) { ticket_sample }
  266. it 'gives caller information' do
  267. expect(log.preferences[:from].count).to eq(1)
  268. expect(log.preferences[:from].first)
  269. .to include(
  270. 'level' => 'known',
  271. 'user_id' => customer.id,
  272. )
  273. end
  274. end
  275. end
  276. end
  277. describe 'Callbacks -' do
  278. describe 'Updating agent sessions:' do
  279. before { allow(Sessions).to receive(:send_to).with(any_args) }
  280. context 'on creation' do
  281. it 'pushes "cti_list_push" event' do
  282. User.with_permissions('cti.agent').each do |u|
  283. expect(Sessions).to receive(:send_to).with(u.id, { event: 'cti_list_push' })
  284. end
  285. create(:cti_log)
  286. end
  287. context 'with over 60 existing Log records' do
  288. before { create_list(:cti_log, 60) }
  289. it '(always) pushes "cti_list_push" event' do
  290. User.with_permissions('cti.agent').each do |u|
  291. expect(Sessions).to receive(:send_to).with(u.id, { event: 'cti_list_push' })
  292. end
  293. create(:cti_log)
  294. end
  295. end
  296. end
  297. context 'on update' do
  298. subject!(:log) { create(:cti_log) }
  299. it 'pushes "cti_list_push" event' do
  300. User.with_permissions('cti.agent').each do |u|
  301. expect(Sessions).to receive(:send_to).with(u.id, { event: 'cti_list_push' })
  302. end
  303. log.touch
  304. end
  305. context 'when among the latest 60 Log records' do
  306. before { create_list(:cti_log, 59) }
  307. it 'pushes "cti_list_push" event' do
  308. User.with_permissions('cti.agent').each do |u|
  309. expect(Sessions).to receive(:send_to).with(u.id, { event: 'cti_list_push' })
  310. end
  311. log.touch
  312. end
  313. end
  314. context 'when not among the latest 60 Log records' do
  315. before { create_list(:cti_log, 60) }
  316. it 'does NOT push "cti_list_push" event' do
  317. User.with_permissions('cti.agent').each do |u|
  318. expect(Sessions).not_to receive(:send_to).with(u.id, { event: 'cti_list_push' })
  319. end
  320. log.touch
  321. end
  322. end
  323. end
  324. end
  325. end
  326. describe '#from_pretty' do
  327. context 'with complete, E164 international numbers' do
  328. subject(:log) { create(:cti_log, from: '4930609854180') }
  329. it 'gives the number in prettified format' do
  330. expect(log.from_pretty).to eq('+49 30 609854180')
  331. end
  332. end
  333. context 'with private network numbers' do
  334. subject(:log) { create(:cti_log, from: '007') }
  335. it 'gives the number unaltered' do
  336. expect(log.from_pretty).to eq('007')
  337. end
  338. end
  339. end
  340. describe '#to_pretty' do
  341. context 'with complete, E164 international numbers' do
  342. subject(:log) { create(:cti_log, to: '4930609811111') }
  343. it 'gives the number in prettified format' do
  344. expect(log.to_pretty).to eq('+49 30 609811111')
  345. end
  346. end
  347. context 'with private network numbers' do
  348. subject(:log) { create(:cti_log, to: '008') }
  349. it 'gives the number unaltered' do
  350. expect(log.to_pretty).to eq('008')
  351. end
  352. end
  353. end
  354. describe '.queues_of_user' do
  355. context 'without notify_map and no own phone number' do
  356. it 'gives an empty array' do
  357. expect(described_class.queues_of_user(user, Setting.get('cti_config'))).to eq([])
  358. end
  359. end
  360. context 'with notify_map and no own phone number' do
  361. before do
  362. cti_config = Setting.get('cti_config')
  363. cti_config[:notify_map] = [ { queue: 'queue4', user_ids: [user.id.to_s] } ]
  364. Setting.set('cti_config', cti_config)
  365. end
  366. it 'gives an array with queue' do
  367. expect(described_class.queues_of_user(user, Setting.get('cti_config'))).to eq(['queue4'])
  368. end
  369. end
  370. context 'with notify_map and with own phone number' do
  371. let(:phone) { '012345678' }
  372. before do
  373. cti_config = Setting.get('cti_config')
  374. cti_config[:notify_map] = [ { queue: 'queue4', user_ids: [user.id.to_s] } ]
  375. Setting.set('cti_config', cti_config)
  376. end
  377. it 'gives an array with queue and phone number' do
  378. expect(described_class.queues_of_user(user, Setting.get('cti_config'))).to eq(%w[queue4 4912345678])
  379. end
  380. end
  381. end
  382. describe '#best_customer_id_of_log_entry' do
  383. subject(:log1) do
  384. described_class.process(
  385. 'event' => 'newCall',
  386. 'user' => 'user 1',
  387. 'from' => '01234599',
  388. 'to' => '49123450',
  389. 'call_id' => '1',
  390. 'direction' => 'in',
  391. )
  392. end
  393. let!(:agent1) { create(:agent, phone: '01234599') }
  394. let!(:customer2) { create(:customer, phone: '') }
  395. let!(:ticket_article1) { create(:ticket_article, created_by_id: customer2.id, body: 'some text 01234599') }
  396. context 'with agent1 (known), customer1 (known) and customer2 (maybe)' do
  397. let!(:customer1) { create(:customer, phone: '01234599') }
  398. it 'gives customer1' do
  399. expect(log1.best_customer_id_of_log_entry).to eq(customer1.id)
  400. end
  401. end
  402. context 'with agent1 (known) and customer2 (maybe)' do
  403. it 'gives customer2' do
  404. expect(log1.best_customer_id_of_log_entry).to eq(agent1.id)
  405. end
  406. end
  407. end
  408. describe '#to_json' do
  409. it 'includes virtual attributes' do
  410. expect(log.as_json).to include('from_pretty', 'to_pretty')
  411. end
  412. end
  413. end