ticket_spec.rb 82 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350
  1. # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
  2. require 'rails_helper'
  3. require 'models/application_model_examples'
  4. require 'models/concerns/can_be_imported_examples'
  5. require 'models/concerns/can_csv_import_examples'
  6. require 'models/concerns/checks_core_workflow_examples'
  7. require 'models/concerns/has_history_examples'
  8. require 'models/concerns/has_tags_examples'
  9. require 'models/concerns/has_taskbars_examples'
  10. require 'models/concerns/has_xss_sanitized_note_examples'
  11. require 'models/concerns/has_object_manager_attributes_examples'
  12. require 'models/tag/writes_to_ticket_history_examples'
  13. require 'models/ticket/calls_stats_ticket_reopen_log_examples'
  14. require 'models/ticket/enqueues_user_ticket_counter_job_examples'
  15. require 'models/ticket/escalation_examples'
  16. require 'models/ticket/resets_pending_time_seconds_examples'
  17. require 'models/ticket/sets_close_time_examples'
  18. require 'models/ticket/sets_last_owner_update_time_examples'
  19. RSpec.describe Ticket, type: :model do
  20. subject(:ticket) { create(:ticket) }
  21. it_behaves_like 'ApplicationModel'
  22. it_behaves_like 'CanBeImported'
  23. it_behaves_like 'CanCsvImport'
  24. it_behaves_like 'ChecksCoreWorkflow'
  25. it_behaves_like 'HasHistory', history_relation_object: ['Ticket::Article', 'Mention']
  26. it_behaves_like 'HasTags'
  27. it_behaves_like 'TagWritesToTicketHistory'
  28. it_behaves_like 'HasTaskbars'
  29. it_behaves_like 'HasXssSanitizedNote', model_factory: :ticket
  30. it_behaves_like 'HasObjectManagerAttributes'
  31. it_behaves_like 'Ticket::Escalation'
  32. it_behaves_like 'TicketCallsStatsTicketReopenLog'
  33. it_behaves_like 'TicketEnqueuesTicketUserTicketCounterJob'
  34. it_behaves_like 'TicketResetsPendingTimeSeconds'
  35. it_behaves_like 'TicketSetsCloseTime'
  36. it_behaves_like 'TicketSetsLastOwnerUpdateTime'
  37. describe 'Class methods:' do
  38. describe '.selectors' do
  39. # https://github.com/zammad/zammad/issues/1769
  40. context 'when matching multiple tickets, each with multiple articles' do
  41. let(:tickets) { create_list(:ticket, 2) }
  42. before do
  43. create(:ticket_article, ticket: tickets.first, from: 'asdf1@blubselector.de')
  44. create(:ticket_article, ticket: tickets.first, from: 'asdf2@blubselector.de')
  45. create(:ticket_article, ticket: tickets.first, from: 'asdf3@blubselector.de')
  46. create(:ticket_article, ticket: tickets.last, from: 'asdf4@blubselector.de')
  47. create(:ticket_article, ticket: tickets.last, from: 'asdf5@blubselector.de')
  48. create(:ticket_article, ticket: tickets.last, from: 'asdf6@blubselector.de')
  49. end
  50. let(:condition) do
  51. {
  52. 'article.from' => {
  53. operator: 'contains',
  54. value: 'blubselector.de',
  55. },
  56. }
  57. end
  58. it 'returns a list of unique tickets (i.e., no duplicates)' do
  59. expect(described_class.selectors(condition, limit: 100, access: 'full'))
  60. .to match_array([2, tickets.to_a])
  61. end
  62. end
  63. end
  64. end
  65. describe 'Instance methods:' do
  66. describe '#merge_to' do
  67. let(:target_ticket) { create(:ticket) }
  68. context 'when source ticket has Links' do
  69. let(:linked_tickets) { create_list(:ticket, 3) }
  70. let(:links) { linked_tickets.map { |l| create(:link, from: ticket, to: l) } }
  71. it 'reassigns all links to the target ticket after merge' do
  72. expect { ticket.merge_to(ticket_id: target_ticket.id, user_id: 1) }
  73. .to change { links.each(&:reload).map(&:link_object_source_value) }
  74. .to(Array.new(3) { target_ticket.id })
  75. end
  76. end
  77. context 'when attempting to cross-merge (i.e., to merge B → A after merging A → B)' do
  78. before { target_ticket.merge_to(ticket_id: ticket.id, user_id: 1) }
  79. it 'raises an error' do
  80. expect { ticket.merge_to(ticket_id: target_ticket.id, user_id: 1) }
  81. .to raise_error('ticket already merged, no merge into merged ticket possible')
  82. end
  83. end
  84. context 'when attempting to self-merge (i.e., to merge A → A)' do
  85. it 'raises an error' do
  86. expect { ticket.merge_to(ticket_id: ticket.id, user_id: 1) }
  87. .to raise_error("Can't merge ticket with it self!")
  88. end
  89. end
  90. context 'when both tickets are linked with the same parent (parent->child)' do
  91. let(:parent) { create(:ticket) }
  92. before do
  93. create(:link,
  94. link_type: 'child',
  95. link_object_source_value: ticket.id,
  96. link_object_target_value: parent.id)
  97. create(:link,
  98. link_type: 'child',
  99. link_object_source_value: target_ticket.id,
  100. link_object_target_value: parent.id)
  101. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  102. end
  103. it 'does remove the link from the merged ticket' do
  104. links = Link.list(
  105. link_object: 'Ticket',
  106. link_object_value: ticket.id
  107. )
  108. expect(links.count).to eq(1) # one link to the source ticket (no parent link)
  109. end
  110. it 'does not remove the link from the target ticket' do
  111. links = Link.list(
  112. link_object: 'Ticket',
  113. link_object_value: target_ticket.id
  114. )
  115. expect(links.count).to eq(2) # one link to the merged ticket + parent link
  116. end
  117. end
  118. context 'when both tickets are linked with the same parent (child->parent)' do
  119. let(:parent) { create(:ticket) }
  120. before do
  121. create(:link,
  122. link_type: 'child',
  123. link_object_source_value: parent.id,
  124. link_object_target_value: ticket.id)
  125. create(:link,
  126. link_type: 'child',
  127. link_object_source_value: parent.id,
  128. link_object_target_value: target_ticket.id)
  129. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  130. end
  131. it 'does remove the link from the merged ticket' do
  132. links = Link.list(
  133. link_object: 'Ticket',
  134. link_object_value: ticket.id
  135. )
  136. expect(links.count).to eq(1) # one link to the source ticket (no parent link)
  137. end
  138. it 'does not remove the link from the target ticket' do
  139. links = Link.list(
  140. link_object: 'Ticket',
  141. link_object_value: target_ticket.id
  142. )
  143. expect(links.count).to eq(2) # one link to the merged ticket + parent link
  144. end
  145. end
  146. context 'when both tickets are linked with the same parent (different link types)' do
  147. let(:parent) { create(:ticket) }
  148. before do
  149. create(:link,
  150. link_type: 'normal',
  151. link_object_source_value: parent.id,
  152. link_object_target_value: ticket.id)
  153. create(:link,
  154. link_type: 'child',
  155. link_object_source_value: parent.id,
  156. link_object_target_value: target_ticket.id)
  157. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  158. end
  159. it 'does remove the link from the merged ticket' do
  160. links = Link.list(
  161. link_object: 'Ticket',
  162. link_object_value: ticket.id
  163. )
  164. expect(links.count).to eq(1) # one link to the source ticket (no normal link)
  165. end
  166. it 'does not remove the link from the target ticket' do
  167. links = Link.list(
  168. link_object: 'Ticket',
  169. link_object_value: target_ticket.id
  170. )
  171. expect(links.count).to eq(3) # one lin to the merged ticket + parent link + normal link
  172. end
  173. end
  174. context 'when both tickets having mentions to the same user' do
  175. let(:watcher) { create(:agent) }
  176. before do
  177. create(:mention, mentionable: ticket, user: watcher)
  178. create(:mention, mentionable: target_ticket, user: watcher)
  179. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  180. end
  181. it 'does remove the link from the merged ticket' do
  182. expect(target_ticket.mentions.count).to eq(1) # one mention to watcher user
  183. end
  184. end
  185. context 'when merging' do
  186. let(:merge_user) { create(:user) }
  187. before do
  188. # create target ticket early
  189. # to avoid a race condition
  190. # when creating the history entries
  191. target_ticket
  192. travel 5.minutes
  193. ticket.merge_to(ticket_id: target_ticket.id, user_id: merge_user.id)
  194. end
  195. # Issue #2469 - Add information "Ticket merged" to History
  196. it 'creates history entries in both the origin ticket and the target ticket' do
  197. expect(target_ticket.history_get.size).to eq 2
  198. target_history = target_ticket.history_get.last
  199. expect(target_history['object']).to eq 'Ticket'
  200. expect(target_history['type']).to eq 'received_merge'
  201. expect(target_history['created_by_id']).to eq merge_user.id
  202. expect(target_history['o_id']).to eq target_ticket.id
  203. expect(target_history['id_to']).to eq target_ticket.id
  204. expect(target_history['id_from']).to eq ticket.id
  205. expect(ticket.history_get.size).to eq 4
  206. origin_history = ticket.reload.history_get[1]
  207. expect(origin_history['object']).to eq 'Ticket'
  208. expect(origin_history['type']).to eq 'merged_into'
  209. expect(origin_history['created_by_id']).to eq merge_user.id
  210. expect(origin_history['o_id']).to eq ticket.id
  211. expect(origin_history['id_to']).to eq target_ticket.id
  212. expect(origin_history['id_from']).to eq ticket.id
  213. end
  214. it 'sends ExternalSync.migrate' do
  215. allow(ExternalSync).to receive(:migrate)
  216. ticket.merge_to(ticket_id: target_ticket.id, user_id: merge_user.id)
  217. expect(ExternalSync).to have_received(:migrate).with('Ticket', ticket.id, target_ticket.id)
  218. end
  219. # Issue #2960 - Ticket removal of merged / linked tickets doesn't remove references
  220. context 'and deleting the origin ticket' do
  221. it 'adds reference number and title to the target ticket' do
  222. expect { ticket.destroy }
  223. .to change { target_ticket.history_get.find { |elem| elem.fetch('type') == 'received_merge' }['value_from'] }
  224. .to("##{ticket.number} #{ticket.title}")
  225. end
  226. end
  227. # Issue #2960 - Ticket removal of merged / linked tickets doesn't remove references
  228. context 'and deleting the target ticket' do
  229. it 'adds reference number and title to the origin ticket' do
  230. expect { target_ticket.destroy }
  231. .to change { ticket.history_get.find { |elem| elem.fetch('type') == 'merged_into' }['value_to'] }
  232. .to("##{target_ticket.number} #{target_ticket.title}")
  233. end
  234. end
  235. end
  236. # https://github.com/zammad/zammad/issues/3105
  237. context 'when merge actions triggers exist', :performs_jobs do
  238. before do
  239. ticket && target_ticket
  240. merged_into_trigger && received_merge_trigger && update_trigger
  241. allow_any_instance_of(described_class).to receive(:perform_changes) do |ticket, trigger| # rubocop:disable RSpec/AnyInstance
  242. log << { ticket: ticket.id, trigger: trigger.id }
  243. end
  244. perform_enqueued_jobs do
  245. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  246. end
  247. end
  248. let(:merged_into_trigger) { create(:trigger, :conditionable, condition_ticket_action: 'update.merged_into') }
  249. let(:received_merge_trigger) { create(:trigger, :conditionable, condition_ticket_action: 'update.received_merge') }
  250. let(:update_trigger) { create(:trigger, :conditionable, condition_ticket_action: 'update') }
  251. let(:log) { [] }
  252. it 'merge_into triggered with source ticket' do
  253. expect(log).to include({ ticket: ticket.id, trigger: merged_into_trigger.id })
  254. end
  255. it 'received_merge not triggered with source ticket' do
  256. expect(log).not_to include({ ticket: ticket.id, trigger: received_merge_trigger.id })
  257. end
  258. it 'update not triggered with source ticket' do
  259. expect(log).not_to include({ ticket: ticket.id, trigger: update_trigger.id })
  260. end
  261. it 'merge_into not triggered with target ticket' do
  262. expect(log).not_to include({ ticket: target_ticket.id, trigger: merged_into_trigger.id })
  263. end
  264. it 'received_merge triggered with target ticket' do
  265. expect(log).to include({ ticket: target_ticket.id, trigger: received_merge_trigger.id })
  266. end
  267. it 'update not triggered with target ticket' do
  268. expect(log).not_to include({ ticket: target_ticket.id, trigger: update_trigger.id })
  269. end
  270. end
  271. # https://github.com/zammad/zammad/issues/3105
  272. context 'when user has notifications enabled', :performs_jobs do
  273. before do
  274. user
  275. allow(OnlineNotification).to receive(:add) do |**args|
  276. next if args[:object] != 'Ticket'
  277. log << { type: :online, event: args[:type], ticket_id: args[:o_id], user_id: args[:user_id] }
  278. end
  279. allow(NotificationFactory::Mailer).to receive(:notification) do |**args|
  280. log << { type: :email, event: args[:template], ticket_id: args[:objects][:ticket].id, user_id: args[:user].id }
  281. end
  282. perform_enqueued_jobs do
  283. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  284. end
  285. end
  286. let(:user) { create(:agent, :preferencable, notification_group_ids: [ticket, target_ticket].map(&:group_id), groups: [ticket, target_ticket].map(&:group)) }
  287. let(:log) { [] }
  288. it 'merge_into notification sent with source ticket' do
  289. expect(log).to include({ type: :online, event: 'update.merged_into', ticket_id: ticket.id, user_id: user.id })
  290. end
  291. it 'received_merge notification not sent with source ticket' do
  292. expect(log).not_to include({ type: :online, event: 'update.received_merge', ticket_id: ticket.id, user_id: user.id })
  293. end
  294. it 'update notification not sent with source ticket' do
  295. expect(log).not_to include({ type: :online, event: 'update', ticket_id: ticket.id, user_id: user.id })
  296. end
  297. it 'merge_into notification not sent with target ticket' do
  298. expect(log).not_to include({ type: :online, event: 'update.merged_into', ticket_id: target_ticket.id, user_id: user.id })
  299. end
  300. it 'received_merge notification sent with target ticket' do
  301. expect(log).to include({ type: :online, event: 'update.received_merge', ticket_id: target_ticket.id, user_id: user.id })
  302. end
  303. it 'update notification not sent with target ticket' do
  304. expect(log).not_to include({ type: :online, event: 'update', ticket_id: target_ticket.id, user_id: user.id })
  305. end
  306. it 'merge_into email sent with source ticket' do
  307. expect(log).to include({ type: :email, event: 'ticket_update_merged_into', ticket_id: ticket.id, user_id: user.id })
  308. end
  309. it 'received_merge email not sent with source ticket' do
  310. expect(log).not_to include({ type: :email, event: 'ticket_update_received_merge', ticket_id: ticket.id, user_id: user.id })
  311. end
  312. it 'update email not sent with source ticket' do
  313. expect(log).not_to include({ type: :email, event: 'ticket_update', ticket_id: ticket.id, user_id: user.id })
  314. end
  315. it 'merge_into email not sent with target ticket' do
  316. expect(log).not_to include({ type: :email, event: 'ticket_update_merged_into', ticket_id: target_ticket.id, user_id: user.id })
  317. end
  318. it 'received_merge email sent with target ticket' do
  319. expect(log).to include({ type: :email, event: 'ticket_update_received_merge', ticket_id: target_ticket.id, user_id: user.id })
  320. end
  321. it 'update email not sent with target ticket' do
  322. expect(log).not_to include({ type: :email, event: 'ticket_update', ticket_id: target_ticket.id, user_id: user.id })
  323. end
  324. end
  325. # https://github.com/zammad/zammad/issues/3105
  326. context 'when sending notification email correct template', :performs_jobs do
  327. before do
  328. user
  329. allow(NotificationFactory::Mailer).to receive(:send) do |**args|
  330. log << args[:subject]
  331. end
  332. perform_enqueued_jobs do
  333. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  334. end
  335. end
  336. let(:user) { create(:agent, :preferencable, notification_group_ids: [ticket, target_ticket].map(&:group_id), groups: [ticket, target_ticket].map(&:group)) }
  337. let(:log) { [] }
  338. it 'is used for merged_into' do
  339. expect(log).to include(start_with("Ticket (#{ticket.title}) was merged into another ticket"))
  340. end
  341. it 'is used for received_merge' do
  342. expect(log).to include(start_with("Another ticket was merged into ticket (#{target_ticket.title})"))
  343. end
  344. end
  345. context 'ApplicationHandleInfo context' do
  346. it 'gets switched to "merge"' do
  347. allow(ApplicationHandleInfo).to receive('context=')
  348. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  349. expect(ApplicationHandleInfo).to have_received('context=').with('merge').at_least(1)
  350. end
  351. it 'reverts back to default' do
  352. allow(ApplicationHandleInfo).to receive('context=')
  353. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  354. expect(ApplicationHandleInfo.context).not_to eq 'merge'
  355. end
  356. end
  357. end
  358. describe '#perform_changes' do
  359. # a `performable` can be a Trigger or a Job
  360. # we use DuckTyping and expect that a performable
  361. # implements the following interface
  362. let(:performable) { OpenStruct.new(id: 1, perform: perform) }
  363. # Regression test for https://github.com/zammad/zammad/issues/2001
  364. describe 'argument handling' do
  365. let(:perform) do
  366. {
  367. 'notification.email' => {
  368. body: "Hello \#{ticket.customer.firstname} \#{ticket.customer.lastname},",
  369. recipient: %w[article_last_sender ticket_owner ticket_customer ticket_agents],
  370. subject: "Autoclose (\#{ticket.title})"
  371. }
  372. }
  373. end
  374. it 'does not mutate contents of "perform" hash' do
  375. expect { ticket.perform_changes(performable, 'trigger', {}, 1) }
  376. .not_to change { perform }
  377. end
  378. end
  379. context 'with "ticket.state_id" key in "perform" hash' do
  380. let(:perform) do
  381. {
  382. 'ticket.state_id' => {
  383. 'value' => Ticket::State.lookup(name: 'closed').id
  384. }
  385. }
  386. end
  387. it 'changes #state to specified value' do
  388. expect { ticket.perform_changes(performable, 'trigger', ticket, User.first) }
  389. .to change { ticket.reload.state.name }.to('closed')
  390. end
  391. end
  392. # Test for backwards compatibility after PR https://github.com/zammad/zammad/pull/2862
  393. context 'with "pending_time" => { "value": DATE } in "perform" hash' do
  394. let(:perform) do
  395. {
  396. 'ticket.state_id' => {
  397. 'value' => Ticket::State.lookup(name: 'pending reminder').id.to_s
  398. },
  399. 'ticket.pending_time' => {
  400. 'value' => timestamp,
  401. },
  402. }
  403. end
  404. let(:timestamp) { Time.zone.now }
  405. it 'changes pending date to given date' do
  406. freeze_time do
  407. expect { ticket.perform_changes(performable, 'trigger', ticket, User.first) }
  408. .to change(ticket, :pending_time).to(be_within(1.minute).of(timestamp))
  409. end
  410. end
  411. end
  412. # Test for PR https://github.com/zammad/zammad/pull/2862
  413. context 'with "pending_time" => { "operator": "relative" } in "perform" hash' do
  414. shared_examples 'verify' do
  415. it 'verify relative pending time rule' do
  416. freeze_time do
  417. interval = relative_value.send(relative_range).from_now
  418. expect { ticket.perform_changes(performable, 'trigger', ticket, User.first) }
  419. .to change(ticket, :pending_time).to(be_within(1.minute).of(interval))
  420. end
  421. end
  422. end
  423. let(:perform) do
  424. {
  425. 'ticket.state_id' => {
  426. 'value' => Ticket::State.lookup(name: 'pending reminder').id.to_s
  427. },
  428. 'ticket.pending_time' => {
  429. 'operator' => 'relative',
  430. 'value' => relative_value,
  431. 'range' => relative_range_config
  432. },
  433. }
  434. end
  435. let(:relative_range_config) { relative_range.to_s.singularize }
  436. context 'and value in days' do
  437. let(:relative_value) { 2 }
  438. let(:relative_range) { :days }
  439. include_examples 'verify'
  440. end
  441. context 'and value in minutes' do
  442. let(:relative_value) { 60 }
  443. let(:relative_range) { :minutes }
  444. include_examples 'verify'
  445. end
  446. end
  447. context 'with "ticket.action" => { "value" => "delete" } in "perform" hash' do
  448. let(:perform) do
  449. {
  450. 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s },
  451. 'ticket.action' => { 'value' => 'delete' },
  452. }
  453. end
  454. it 'performs a ticket deletion on a ticket' do
  455. expect { ticket.perform_changes(performable, 'trigger', ticket, User.first) }
  456. .to change(ticket, :destroyed?).to(true)
  457. end
  458. end
  459. context 'with a "notification.email" trigger' do
  460. # Regression test for https://github.com/zammad/zammad/issues/1543
  461. #
  462. # If a new article fires an email notification trigger,
  463. # and then another article is added to the same ticket
  464. # before that trigger is performed,
  465. # the email template's 'article' var should refer to the originating article,
  466. # not the newest one.
  467. #
  468. # (This occurs whenever one action fires multiple email notification triggers.)
  469. context 'when two articles are created before the trigger fires once (race condition)' do
  470. let!(:article) { create(:ticket_article, ticket: ticket) }
  471. let!(:new_article) { create(:ticket_article, ticket: ticket) }
  472. let(:trigger) do
  473. build(:trigger,
  474. perform: {
  475. 'notification.email' => {
  476. body: '',
  477. recipient: 'ticket_customer',
  478. subject: ''
  479. }
  480. })
  481. end
  482. # required by Ticket#perform_changes for email notifications
  483. before { article.ticket.group.update(email_address: create(:email_address)) }
  484. it 'passes the first article to NotificationFactory::Mailer' do
  485. expect(NotificationFactory::Mailer)
  486. .to receive(:template)
  487. .with(hash_including(objects: { ticket: ticket, article: article }))
  488. .at_least(:once)
  489. .and_call_original
  490. expect(NotificationFactory::Mailer)
  491. .not_to receive(:template)
  492. .with(hash_including(objects: { ticket: ticket, article: new_article }))
  493. ticket.perform_changes(trigger, 'trigger', { article_id: article.id }, 1)
  494. end
  495. end
  496. end
  497. context 'with a notification trigger' do
  498. # https://github.com/zammad/zammad/issues/2782
  499. #
  500. # Notification triggers should log notification as private or public
  501. # according to given configuration
  502. let(:user) { create(:admin, mobile: '+37061010000') }
  503. before { ticket.group.users << user }
  504. let(:perform) do
  505. {
  506. notification_key => {
  507. body: 'Old programmers never die. They just branch to a new address.',
  508. recipient: 'ticket_agents',
  509. subject: 'Old programmers never die. They just branch to a new address.'
  510. }
  511. }.deep_merge(additional_options).deep_stringify_keys
  512. end
  513. let(:notification_key) { "notification.#{notification_type}" }
  514. shared_examples 'verify log visibility status' do
  515. shared_examples 'notification trigger' do
  516. it 'adds Ticket::Article' do
  517. expect { ticket.perform_changes(performable, 'trigger', ticket, user) }
  518. .to change { ticket.articles.count }.by(1)
  519. end
  520. it 'new Ticket::Article visibility reflects setting' do
  521. ticket.perform_changes(performable, 'trigger', ticket, User.first)
  522. new_article = ticket.articles.reload.last
  523. expect(new_article.internal).to be target_internal_value
  524. end
  525. end
  526. context 'when set to private' do
  527. let(:additional_options) do
  528. {
  529. notification_key => {
  530. internal: true
  531. }
  532. }
  533. end
  534. let(:target_internal_value) { true }
  535. it_behaves_like 'notification trigger'
  536. end
  537. context 'when set to internal' do
  538. let(:additional_options) do
  539. {
  540. notification_key => {
  541. internal: false
  542. }
  543. }
  544. end
  545. let(:target_internal_value) { false }
  546. it_behaves_like 'notification trigger'
  547. end
  548. context 'when no selection was made' do # ensure previously created triggers default to public
  549. let(:additional_options) do
  550. {}
  551. end
  552. let(:target_internal_value) { false }
  553. it_behaves_like 'notification trigger'
  554. end
  555. end
  556. context 'dispatching email' do
  557. let(:notification_type) { :email }
  558. include_examples 'verify log visibility status'
  559. end
  560. shared_examples 'add a new article' do
  561. it 'adds a new article' do
  562. expect { ticket.perform_changes(performable, 'trigger', ticket, user) }
  563. .to change { ticket.articles.count }.by(1)
  564. end
  565. end
  566. shared_examples 'add attachment to new article' do
  567. include_examples 'add a new article'
  568. it 'adds attachment to the new article' do
  569. ticket.perform_changes(performable, 'trigger', ticket, user)
  570. article = ticket.articles.last
  571. expect(article.type.name).to eq('email')
  572. expect(article.sender.name).to eq('System')
  573. expect(article.attachments.count).to eq(1)
  574. expect(article.attachments[0].filename).to eq('some_file.pdf')
  575. expect(article.attachments[0].preferences['Content-ID']).to eq('image/pdf@01CAB192.K8H512Y9')
  576. end
  577. end
  578. shared_examples 'does not add attachment to new article' do
  579. include_examples 'add a new article'
  580. it 'does not add attachment to the new article' do
  581. ticket.perform_changes(performable, 'trigger', ticket, user)
  582. article = ticket.articles.last
  583. expect(article.type.name).to eq('email')
  584. expect(article.sender.name).to eq('System')
  585. expect(article.attachments.count).to eq(0)
  586. end
  587. end
  588. context 'dispatching email with include attachment present' do
  589. let(:notification_type) { :email }
  590. let(:additional_options) do
  591. {
  592. notification_key => {
  593. include_attachments: 'true'
  594. }
  595. }
  596. end
  597. context 'when ticket has an attachment' do
  598. before do
  599. UserInfo.current_user_id = 1
  600. ticket_article = create(:ticket_article, ticket: ticket)
  601. Store.add(
  602. object: 'Ticket::Article',
  603. o_id: ticket_article.id,
  604. data: 'dGVzdCAxMjM=',
  605. filename: 'some_file.pdf',
  606. preferences: {
  607. 'Content-Type': 'image/pdf',
  608. 'Content-ID': 'image/pdf@01CAB192.K8H512Y9',
  609. },
  610. created_by_id: 1,
  611. )
  612. end
  613. include_examples 'add attachment to new article'
  614. end
  615. context 'when ticket does not have an attachment' do
  616. include_examples 'does not add attachment to new article'
  617. end
  618. end
  619. context 'dispatching email with include attachment not present' do
  620. let(:notification_type) { :email }
  621. let(:additional_options) do
  622. {
  623. notification_key => {
  624. include_attachments: 'false'
  625. }
  626. }
  627. end
  628. context 'when ticket has an attachment' do
  629. before do
  630. UserInfo.current_user_id = 1
  631. ticket_article = create(:ticket_article, ticket: ticket)
  632. Store.add(
  633. object: 'Ticket::Article',
  634. o_id: ticket_article.id,
  635. data: 'dGVzdCAxMjM=',
  636. filename: 'some_file.pdf',
  637. preferences: {
  638. 'Content-Type': 'image/pdf',
  639. 'Content-ID': 'image/pdf@01CAB192.K8H512Y9',
  640. },
  641. created_by_id: 1,
  642. )
  643. end
  644. include_examples 'does not add attachment to new article'
  645. end
  646. context 'when ticket does not have an attachment' do
  647. include_examples 'does not add attachment to new article'
  648. end
  649. end
  650. context 'dispatching SMS' do
  651. let(:notification_type) { :sms }
  652. before { create(:channel, area: 'Sms::Notification') }
  653. include_examples 'verify log visibility status'
  654. end
  655. end
  656. context 'with a "notification.webhook" trigger', performs_jobs: true do
  657. let(:webhook) { create(:webhook, endpoint: 'http://api.example.com/webhook', signature_token: '53CR3t') }
  658. let(:trigger) do
  659. create(:trigger,
  660. perform: {
  661. 'notification.webhook' => { 'webhook_id' => webhook.id }
  662. })
  663. end
  664. it 'schedules the webhooks notification job' do
  665. expect { ticket.perform_changes(trigger, 'trigger', {}, 1) }.to have_enqueued_job(TriggerWebhookJob).with(trigger, ticket, nil)
  666. end
  667. end
  668. end
  669. describe '#trigger_based_notification?' do
  670. let(:ticket) { create(:ticket) }
  671. context 'with a normal user' do
  672. let(:customer) { create(:customer) }
  673. it 'send trigger base notification' do
  674. expect(ticket.send(:trigger_based_notification?, customer)).to eq(true)
  675. end
  676. end
  677. context 'with a permanent failed user' do
  678. let(:failed_date) { 1.second.ago }
  679. let(:customer) do
  680. user = create(:customer)
  681. user.preferences.merge!(mail_delivery_failed: true, mail_delivery_failed_data: failed_date)
  682. user.save!
  683. user
  684. end
  685. it 'send no trigger base notification' do
  686. expect(ticket.send(:trigger_based_notification?, customer)).to eq(false)
  687. end
  688. context 'with failed date 61 days ago' do
  689. let(:failed_date) { 61.days.ago }
  690. it 'send trigger base notification' do
  691. expect(ticket.send(:trigger_based_notification?, customer)).to eq(true)
  692. end
  693. end
  694. end
  695. end
  696. describe '#subject_build' do
  697. context 'with default "ticket_hook_position" setting ("right")' do
  698. it 'returns the given string followed by a ticket reference (of the form "[Ticket#123]")' do
  699. expect(ticket.subject_build('foo'))
  700. .to eq("foo [Ticket##{ticket.number}]")
  701. end
  702. context 'and a non-default value for the "ticket_hook" setting' do
  703. before { Setting.set('ticket_hook', 'bar baz') }
  704. it 'replaces "Ticket#" with the new ticket hook' do
  705. expect(ticket.subject_build('foo'))
  706. .to eq("foo [bar baz#{ticket.number}]")
  707. end
  708. end
  709. context 'and a non-default value for the "ticket_hook_divider" setting' do
  710. before { Setting.set('ticket_hook_divider', ': ') }
  711. it 'inserts the new ticket hook divider between "Ticket#" and the ticket number' do
  712. expect(ticket.subject_build('foo'))
  713. .to eq("foo [Ticket#: #{ticket.number}]")
  714. end
  715. end
  716. context 'when the given string already contains a ticket reference, but in the wrong place' do
  717. it 'moves the ticket reference to the end' do
  718. expect(ticket.subject_build("[Ticket##{ticket.number}] foo"))
  719. .to eq("foo [Ticket##{ticket.number}]")
  720. end
  721. end
  722. context 'when the given string already contains an alternately formatted ticket reference' do
  723. it 'reformats the ticket reference' do
  724. expect(ticket.subject_build("foo [Ticket#: #{ticket.number}]"))
  725. .to eq("foo [Ticket##{ticket.number}]")
  726. end
  727. end
  728. end
  729. context 'with alternate "ticket_hook_position" setting ("left")' do
  730. before { Setting.set('ticket_hook_position', 'left') }
  731. it 'returns a ticket reference (of the form "[Ticket#123]") followed by the given string' do
  732. expect(ticket.subject_build('foo'))
  733. .to eq("[Ticket##{ticket.number}] foo")
  734. end
  735. context 'and a non-default value for the "ticket_hook" setting' do
  736. before { Setting.set('ticket_hook', 'bar baz') }
  737. it 'replaces "Ticket#" with the new ticket hook' do
  738. expect(ticket.subject_build('foo'))
  739. .to eq("[bar baz#{ticket.number}] foo")
  740. end
  741. end
  742. context 'and a non-default value for the "ticket_hook_divider" setting' do
  743. before { Setting.set('ticket_hook_divider', ': ') }
  744. it 'inserts the new ticket hook divider between "Ticket#" and the ticket number' do
  745. expect(ticket.subject_build('foo'))
  746. .to eq("[Ticket#: #{ticket.number}] foo")
  747. end
  748. end
  749. context 'when the given string already contains a ticket reference, but in the wrong place' do
  750. it 'moves the ticket reference to the start' do
  751. expect(ticket.subject_build("foo [Ticket##{ticket.number}]"))
  752. .to eq("[Ticket##{ticket.number}] foo")
  753. end
  754. end
  755. context 'when the given string already contains an alternately formatted ticket reference' do
  756. it 'reformats the ticket reference' do
  757. expect(ticket.subject_build("[Ticket#: #{ticket.number}] foo"))
  758. .to eq("[Ticket##{ticket.number}] foo")
  759. end
  760. end
  761. end
  762. end
  763. describe '#last_original_update_at' do
  764. let(:result) { ticket.last_original_update_at }
  765. it 'returns initial customer enquiry time when customer contacted repeatedly' do
  766. ticket
  767. target = create(:ticket_article, :inbound_email, ticket: ticket)
  768. travel 10.minutes
  769. create(:ticket_article, :inbound_email, ticket: ticket)
  770. expect(result).to eq target.created_at
  771. end
  772. it 'returns agent contact time when customer did not respond to agent reach out' do
  773. ticket
  774. create(:ticket_article, :outbound_email, ticket: ticket)
  775. expect(result).to eq ticket.last_contact_agent_at
  776. end
  777. it 'returns nil if no customer response' do
  778. ticket
  779. expect(result).to be_nil
  780. end
  781. context 'with customer enquiry and agent response' do
  782. before do
  783. ticket
  784. create(:ticket_article, :inbound_email, ticket: ticket)
  785. travel 10.minutes
  786. create(:ticket_article, :outbound_email, ticket: ticket)
  787. travel 10.minutes
  788. end
  789. it 'returns last customer enquiry time when agent did not respond yet' do
  790. target = create(:ticket_article, :inbound_email, ticket: ticket)
  791. expect(result).to eq target.created_at
  792. end
  793. it 'returns agent response time when agent responded to customer enquiry' do
  794. expect(result).to eq ticket.last_contact_agent_at
  795. end
  796. end
  797. end
  798. describe '#param_cleanup' do
  799. it 'does only remove parameters which are invalid and not the complete params hash if one element is invalid (#3743)' do
  800. expect(described_class.param_cleanup({ state_id: 3, customer_id: 'guess:1234' }, true, false, false)).to eq({ 'state_id' => 3 })
  801. end
  802. end
  803. end
  804. describe 'Attributes:' do
  805. describe '#owner' do
  806. let(:original_owner) { create(:agent, groups: [ticket.group]) }
  807. before { ticket.update(owner: original_owner) }
  808. context 'when assigned directly' do
  809. context 'to an active agent belonging to ticket.group' do
  810. let(:agent) { create(:agent, groups: [ticket.group]) }
  811. it 'can be set' do
  812. expect { ticket.update(owner: agent) }
  813. .to change { ticket.reload.owner }.to(agent)
  814. end
  815. end
  816. context 'to an agent not belonging to ticket.group' do
  817. let(:agent) { create(:agent, groups: [other_group]) }
  818. let(:other_group) { create(:group) }
  819. it 'resets to default user (id: 1) instead' do
  820. expect { ticket.update(owner: agent) }
  821. .to change { ticket.reload.owner }.to(User.first)
  822. end
  823. end
  824. context 'to an inactive agent' do
  825. let(:agent) { create(:agent, groups: [ticket.group], active: false) }
  826. it 'resets to default user (id: 1) instead' do
  827. expect { ticket.update(owner: agent) }
  828. .to change { ticket.reload.owner }.to(User.first)
  829. end
  830. end
  831. context 'to a non-agent' do
  832. let(:agent) { create(:customer, groups: [ticket.group]) }
  833. it 'resets to default user (id: 1) instead' do
  834. expect { ticket.update(owner: agent) }
  835. .to change { ticket.reload.owner }.to(User.first)
  836. end
  837. end
  838. end
  839. context 'when the ticket is updated for any other reason' do
  840. context 'if original owner is still an active agent belonging to ticket.group' do
  841. it 'does not change' do
  842. expect { create(:ticket_article, ticket: ticket) }
  843. .not_to change { ticket.reload.owner }
  844. end
  845. end
  846. context 'if original owner has left ticket.group' do
  847. before { original_owner.groups = [] }
  848. it 'resets to default user (id: 1)' do
  849. expect { create(:ticket_article, ticket: ticket) }
  850. .to change { ticket.reload.owner }.to(User.first)
  851. end
  852. end
  853. context 'if original owner has become inactive' do
  854. before { original_owner.update(active: false) }
  855. it 'resets to default user (id: 1)' do
  856. expect { create(:ticket_article, ticket: ticket) }
  857. .to change { ticket.reload.owner }.to(User.first)
  858. end
  859. end
  860. context 'if original owner has lost agent status' do
  861. before { original_owner.roles = [create(:role)] }
  862. it 'resets to default user (id: 1)' do
  863. expect { create(:ticket_article, ticket: ticket) }
  864. .to change { ticket.reload.owner }.to(User.first)
  865. end
  866. end
  867. context 'when the Ticket is closed' do
  868. before do
  869. ticket.update!(state: Ticket::State.lookup(name: 'closed'))
  870. end
  871. context 'if original owner is still an active agent belonging to ticket.group' do
  872. it 'does not change' do
  873. expect { create(:ticket_article, ticket: ticket) }
  874. .not_to change { ticket.reload.owner }
  875. end
  876. end
  877. context 'if original owner has left ticket.group' do
  878. before { original_owner.groups = [] }
  879. it 'does not change' do
  880. expect { create(:ticket_article, ticket: ticket) }
  881. .not_to change { ticket.reload.owner }
  882. end
  883. end
  884. context 'if original owner has become inactive' do
  885. before { original_owner.update(active: false) }
  886. it 'does not change' do
  887. expect { create(:ticket_article, ticket: ticket) }
  888. .not_to change { ticket.reload.owner }
  889. end
  890. end
  891. context 'if original owner has lost agent status' do
  892. before { original_owner.roles = [create(:role)] }
  893. it 'does not change' do
  894. expect { create(:ticket_article, ticket: ticket) }
  895. .not_to change { ticket.reload.owner }
  896. end
  897. end
  898. end
  899. end
  900. end
  901. describe '#state' do
  902. context 'when originally "new" (default)' do
  903. context 'and a customer article is added' do
  904. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Customer') }
  905. it 'stays "new"' do
  906. expect { article }
  907. .not_to change { ticket.state.name }.from('new')
  908. end
  909. end
  910. context 'and a non-customer article is added' do
  911. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  912. it 'switches to "open"' do
  913. expect { article }
  914. .to change { ticket.reload.state.name }.from('new').to('open')
  915. end
  916. end
  917. end
  918. context 'when originally "closed"' do
  919. before { ticket.update(state: Ticket::State.find_by(name: 'closed')) }
  920. context 'when a non-customer article is added' do
  921. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  922. it 'stays "closed"' do
  923. expect { article }.not_to change { ticket.reload.state.name }
  924. end
  925. end
  926. end
  927. end
  928. describe '#pending_time' do
  929. subject(:ticket) { create(:ticket, pending_time: 2.days.from_now) }
  930. context 'when #state is updated to any non-"pending" value' do
  931. it 'is reset to nil' do
  932. expect { ticket.update!(state: Ticket::State.lookup(name: 'open')) }
  933. .to change(ticket, :pending_time).to(nil)
  934. end
  935. end
  936. # Regression test for commit 92f227786f298bad1ccaf92d4478a7062ea6a49f
  937. context 'when #state is updated to nil (violating DB NOT NULL constraint)' do
  938. it 'does not prematurely raise within the callback (#reset_pending_time)' do
  939. expect { ticket.update!(state: nil) }
  940. .to raise_error(ActiveRecord::StatementInvalid)
  941. end
  942. end
  943. end
  944. describe '#escalation_at' do
  945. before { travel_to(Time.current) } # freeze time
  946. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  947. let(:calendar) { create(:calendar, :'24/7') }
  948. context 'with no SLAs in the system' do
  949. it 'defaults to nil' do
  950. expect(ticket.escalation_at).to be(nil)
  951. end
  952. end
  953. context 'with an SLA in the system' do
  954. before { sla } # create sla
  955. it 'is set based on SLA’s #first_response_time' do
  956. expect(ticket.reload.escalation_at.to_i)
  957. .to eq(1.hour.from_now.to_i)
  958. end
  959. context 'after first agent’s response' do
  960. before { ticket } # create ticket
  961. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  962. it 'is updated based on the SLA’s #close_escalation_at' do
  963. travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  964. expect { article }
  965. .to change { ticket.reload.escalation_at }
  966. .to(ticket.reload.close_escalation_at)
  967. end
  968. context 'when new #update_time is later than original #solution_time' do
  969. it 'is updated based on the original #solution_time' do
  970. travel(2.hours) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  971. expect { article }
  972. .to change { ticket.reload.escalation_at }
  973. .to(4.hours.after(ticket.created_at))
  974. end
  975. end
  976. end
  977. end
  978. context 'when updated after an SLA has been added to the system' do
  979. before do
  980. ticket # create ticket
  981. sla # create sla
  982. end
  983. it 'is updated based on the new SLA’s #first_response_time' do
  984. expect { ticket.save! }
  985. .to change { ticket.reload.escalation_at.to_i }.from(0).to(1.hour.from_now.to_i)
  986. end
  987. end
  988. context 'when updated after all SLAs have been removed from the system' do
  989. before do
  990. sla # create sla
  991. ticket # create ticket
  992. sla.destroy
  993. end
  994. it 'is set to nil' do
  995. expect { ticket.save! }
  996. .to change { ticket.reload.escalation_at }.to(nil)
  997. end
  998. end
  999. context 'when within last (relative)' do
  1000. let(:first_response_time) { 5 }
  1001. let(:sla) { create(:sla, calendar: calendar, first_response_time: first_response_time) }
  1002. let(:within_condition) do
  1003. { 'ticket.escalation_at'=>{ 'operator' => 'within last (relative)', 'value' => '30', 'range' => 'minute' } }
  1004. end
  1005. before do
  1006. sla
  1007. travel_to '2020-11-05 11:37:00'
  1008. ticket = create(:ticket)
  1009. create(:ticket_article, :inbound_email, ticket: ticket)
  1010. travel_to '2020-11-05 11:50:00'
  1011. end
  1012. context 'when in range' do
  1013. it 'does find the ticket' do
  1014. count, _tickets = described_class.selectors(within_condition, limit: 2_000, execution_time: true)
  1015. expect(count).to eq(1)
  1016. end
  1017. end
  1018. context 'when out of range' do
  1019. let(:first_response_time) { 500 }
  1020. it 'does not find the ticket' do
  1021. count, _tickets = described_class.selectors(within_condition, limit: 2_000, execution_time: true)
  1022. expect(count).to eq(0)
  1023. end
  1024. end
  1025. end
  1026. context 'when till (relative)' do
  1027. let(:first_response_time) { 5 }
  1028. let(:sla) { create(:sla, calendar: calendar, first_response_time: first_response_time) }
  1029. let(:condition) do
  1030. { 'ticket.escalation_at'=>{ 'operator' => 'till (relative)', 'value' => '30', 'range' => 'minute' } }
  1031. end
  1032. before do
  1033. sla
  1034. travel_to '2020-11-05 11:37:00'
  1035. ticket = create(:ticket)
  1036. create(:ticket_article, :inbound_email, ticket: ticket)
  1037. travel_to '2020-11-05 11:50:00'
  1038. end
  1039. context 'when in range' do
  1040. it 'does find the ticket' do
  1041. count, _tickets = described_class.selectors(condition, limit: 2_000, execution_time: true)
  1042. expect(count).to eq(1)
  1043. end
  1044. end
  1045. context 'when out of range' do
  1046. let(:first_response_time) { 500 }
  1047. it 'does not find the ticket' do
  1048. count, _tickets = described_class.selectors(condition, limit: 2_000, execution_time: true)
  1049. expect(count).to eq(0)
  1050. end
  1051. end
  1052. end
  1053. context 'when from (relative)' do
  1054. let(:first_response_time) { 5 }
  1055. let(:sla) { create(:sla, calendar: calendar, first_response_time: first_response_time) }
  1056. let(:condition) do
  1057. { 'ticket.escalation_at'=>{ 'operator' => 'from (relative)', 'value' => '30', 'range' => 'minute' } }
  1058. end
  1059. before do
  1060. sla
  1061. travel_to '2020-11-05 11:37:00'
  1062. ticket = create(:ticket)
  1063. create(:ticket_article, :inbound_email, ticket: ticket)
  1064. end
  1065. context 'when in range' do
  1066. it 'does find the ticket' do
  1067. travel_to '2020-11-05 11:50:00'
  1068. count, _tickets = described_class.selectors(condition, limit: 2_000, execution_time: true)
  1069. expect(count).to eq(1)
  1070. end
  1071. end
  1072. context 'when out of range' do
  1073. let(:first_response_time) { 5 }
  1074. it 'does not find the ticket' do
  1075. travel_to '2020-11-05 13:50:00'
  1076. count, _tickets = described_class.selectors(condition, limit: 2_000, execution_time: true)
  1077. expect(count).to eq(0)
  1078. end
  1079. end
  1080. end
  1081. context 'when within next (relative)' do
  1082. let(:first_response_time) { 5 }
  1083. let(:sla) { create(:sla, calendar: calendar, first_response_time: first_response_time) }
  1084. let(:within_condition) do
  1085. { 'ticket.escalation_at'=>{ 'operator' => 'within next (relative)', 'value' => '30', 'range' => 'minute' } }
  1086. end
  1087. before do
  1088. sla
  1089. travel_to '2020-11-05 11:50:00'
  1090. ticket = create(:ticket)
  1091. create(:ticket_article, :inbound_email, ticket: ticket)
  1092. travel_to '2020-11-05 11:37:00'
  1093. end
  1094. context 'when in range' do
  1095. it 'does find the ticket' do
  1096. count, _tickets = described_class.selectors(within_condition, limit: 2_000, execution_time: true)
  1097. expect(count).to eq(1)
  1098. end
  1099. end
  1100. context 'when out of range' do
  1101. let(:first_response_time) { 500 }
  1102. it 'does not find the ticket' do
  1103. count, _tickets = described_class.selectors(within_condition, limit: 2_000, execution_time: true)
  1104. expect(count).to eq(0)
  1105. end
  1106. end
  1107. end
  1108. end
  1109. describe '#first_response_escalation_at' do
  1110. before { travel_to(Time.current) } # freeze time
  1111. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  1112. let(:calendar) { create(:calendar, :'24/7') }
  1113. context 'with no SLAs in the system' do
  1114. it 'defaults to nil' do
  1115. expect(ticket.first_response_escalation_at).to be(nil)
  1116. end
  1117. end
  1118. context 'with an SLA in the system' do
  1119. before { sla } # create sla
  1120. it 'is set based on SLA’s #first_response_time' do
  1121. expect(ticket.reload.first_response_escalation_at.to_i)
  1122. .to eq(1.hour.from_now.to_i)
  1123. end
  1124. context 'after first agent’s response' do
  1125. before { ticket } # create ticket
  1126. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  1127. it 'is cleared' do
  1128. expect { article }.to change { ticket.reload.first_response_escalation_at }.to(nil)
  1129. end
  1130. end
  1131. end
  1132. end
  1133. describe '#update_escalation_at' do
  1134. before { travel_to(Time.current) } # freeze time
  1135. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  1136. let(:calendar) { create(:calendar, :'24/7') }
  1137. context 'with no SLAs in the system' do
  1138. it 'defaults to nil' do
  1139. expect(ticket.update_escalation_at).to be(nil)
  1140. end
  1141. end
  1142. context 'with an SLA in the system' do
  1143. before { sla } # create sla
  1144. it 'is set based on SLA’s #update_time' do
  1145. travel 1.minute
  1146. create(:ticket_article, ticket: ticket, sender_name: 'Customer')
  1147. expect(ticket.reload.update_escalation_at.to_i)
  1148. .to eq(3.hours.from_now.to_i)
  1149. end
  1150. context 'after first agent’s response' do
  1151. before { ticket } # create ticket
  1152. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  1153. it 'is updated based on the SLA’s #update_time' do
  1154. create(:ticket_article, ticket: ticket, sender_name: 'Customer')
  1155. travel(1.minute)
  1156. expect { article }
  1157. .to change { ticket.reload.update_escalation_at }
  1158. .to(nil)
  1159. end
  1160. end
  1161. end
  1162. end
  1163. describe '#close_escalation_at' do
  1164. before { travel_to(Time.current) } # freeze time
  1165. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  1166. let(:calendar) { create(:calendar, :'24/7') }
  1167. context 'with no SLAs in the system' do
  1168. it 'defaults to nil' do
  1169. expect(ticket.close_escalation_at).to be(nil)
  1170. end
  1171. end
  1172. context 'with an SLA in the system' do
  1173. before { sla } # create sla
  1174. it 'is set based on SLA’s #solution_time' do
  1175. expect(ticket.reload.close_escalation_at.to_i)
  1176. .to eq(4.hours.from_now.to_i)
  1177. end
  1178. context 'after first agent’s response' do
  1179. before { ticket } # create ticket
  1180. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  1181. it 'does not change' do
  1182. expect { article }.not_to change(ticket, :close_escalation_at)
  1183. end
  1184. end
  1185. end
  1186. end
  1187. end
  1188. describe 'Associations:' do
  1189. describe '#organization' do
  1190. subject(:ticket) { build(:ticket, customer: customer, organization: nil) }
  1191. let(:customer) { create(:customer, :with_org) }
  1192. context 'on creation' do
  1193. it 'automatically adopts the organization of its #customer' do
  1194. expect { ticket.save }
  1195. .to change(ticket, :organization).to(customer.organization)
  1196. end
  1197. end
  1198. context 'on update of #customer.organization' do
  1199. context 'to nil' do
  1200. it 'automatically updates to #customer’s new value' do
  1201. ticket.save
  1202. expect { customer.update(organization: nil) }
  1203. .to change { ticket.reload.organization }.to(nil)
  1204. end
  1205. end
  1206. context 'to a different organization' do
  1207. let(:new_org) { create(:organization) }
  1208. it 'automatically updates to #customer’s new value' do
  1209. ticket.save
  1210. expect { customer.update(organization: new_org) }
  1211. .to change { ticket.reload.organization }.to(new_org)
  1212. end
  1213. end
  1214. end
  1215. end
  1216. end
  1217. describe '.search' do
  1218. shared_examples 'search permissions' do
  1219. let(:group) { create(:group) }
  1220. before do
  1221. ticket
  1222. end
  1223. shared_examples 'permitted' do
  1224. it 'finds Ticket' do
  1225. expect(described_class.search(query: ticket.number, current_user: current_user).count).to eq(1)
  1226. end
  1227. end
  1228. shared_examples 'no permission' do
  1229. it "doesn't find Ticket" do
  1230. expect(described_class.search(query: ticket.number, current_user: current_user)).to be_blank
  1231. end
  1232. end
  1233. context 'Agent with Group access' do
  1234. let(:ticket) do
  1235. ticket = create(:ticket, group: group)
  1236. create(:ticket_article, ticket: ticket)
  1237. ticket
  1238. end
  1239. let(:current_user) { create(:agent, groups: [group]) }
  1240. it_behaves_like 'permitted'
  1241. end
  1242. context 'when Agent is Customer of Ticket' do
  1243. let(:ticket) do
  1244. ticket = create(:ticket, customer: current_user)
  1245. create(:ticket_article, ticket: ticket)
  1246. ticket
  1247. end
  1248. let(:current_user) { create(:agent_and_customer) }
  1249. it_behaves_like 'permitted'
  1250. end
  1251. context 'for Organization access' do
  1252. let(:ticket) do
  1253. ticket = create(:ticket, customer: customer)
  1254. create(:ticket_article, ticket: ticket)
  1255. ticket
  1256. end
  1257. let(:customer) { create(:customer, organization: organization) }
  1258. context 'when Organization is shared' do
  1259. let(:organization) { create(:organization, shared: true) }
  1260. context 'for unrelated Agent' do
  1261. let(:current_user) { create(:agent) }
  1262. it_behaves_like 'no permission'
  1263. end
  1264. context 'for Agent in same Organization' do
  1265. let(:current_user) { create(:agent_and_customer, organization: organization) }
  1266. it_behaves_like 'permitted'
  1267. end
  1268. context 'for Customer of Ticket' do
  1269. let(:current_user) { customer }
  1270. it_behaves_like 'permitted'
  1271. end
  1272. end
  1273. context 'when Organization is not shared' do
  1274. let(:organization) { create(:organization, shared: false) }
  1275. context 'for unrelated Agent' do
  1276. let(:current_user) { create(:agent) }
  1277. it_behaves_like 'no permission'
  1278. end
  1279. context 'for Agent in same Organization' do
  1280. let(:current_user) { create(:agent_and_customer, organization: organization) }
  1281. it_behaves_like 'no permission'
  1282. end
  1283. context 'for Customer of Ticket' do
  1284. let(:current_user) { customer }
  1285. it_behaves_like 'permitted'
  1286. end
  1287. end
  1288. end
  1289. end
  1290. context 'with searchindex', searchindex: true do
  1291. include_examples 'search permissions' do
  1292. before do
  1293. configure_elasticsearch(required: true, rebuild: true)
  1294. end
  1295. end
  1296. end
  1297. context 'without searchindex' do
  1298. include_examples 'search permissions'
  1299. end
  1300. end
  1301. describe 'Callbacks & Observers -' do
  1302. describe 'NULL byte handling (via ChecksAttributeValuesAndLength concern):' do
  1303. it 'removes them from title on creation, if necessary (postgres doesn’t like them)' do
  1304. expect { create(:ticket, title: "some title \u0000 123") }
  1305. .not_to raise_error
  1306. end
  1307. end
  1308. describe 'XSS protection:' do
  1309. subject(:ticket) { create(:ticket, title: title) }
  1310. let(:title) { 'test 123 <script type="text/javascript">alert("XSS!");</script>' }
  1311. it 'does not sanitize title' do
  1312. expect(ticket.title).to eq(title)
  1313. end
  1314. end
  1315. describe 'Cti::CallerId syncing:' do
  1316. subject(:ticket) { build(:ticket) }
  1317. before { allow(Cti::CallerId).to receive(:build) }
  1318. it 'adds numbers in article bodies (via Cti::CallerId.build)' do
  1319. expect(Cti::CallerId).to receive(:build).with(ticket)
  1320. ticket.save
  1321. TransactionDispatcher.commit
  1322. Scheduler.worker(true)
  1323. end
  1324. end
  1325. describe 'Touching associations on update:' do
  1326. subject(:ticket) { create(:ticket, customer: customer) }
  1327. let(:customer) { create(:customer, organization: organization) }
  1328. let(:organization) { create(:organization) }
  1329. let(:other_customer) { create(:customer, organization: other_organization) }
  1330. let(:other_organization) { create(:organization) }
  1331. context 'on creation' do
  1332. it 'touches its customer and his organization' do
  1333. expect { ticket }
  1334. .to change { customer.reload.updated_at }
  1335. .and change { organization.reload.updated_at }
  1336. end
  1337. end
  1338. context 'on destruction' do
  1339. before { ticket }
  1340. it 'touches its customer and his organization' do
  1341. expect { ticket.destroy }
  1342. .to change { customer.reload.updated_at }
  1343. .and change { organization.reload.updated_at }
  1344. end
  1345. end
  1346. context 'when customer association is changed' do
  1347. it 'touches both old and new customer, and their organizations' do
  1348. expect { ticket.update(customer: other_customer) }
  1349. .to change { customer.reload.updated_at }
  1350. .and change { organization.reload.updated_at }
  1351. .and change { other_customer.reload.updated_at }
  1352. .and change { other_organization.reload.updated_at }
  1353. end
  1354. end
  1355. end
  1356. describe 'Association & attachment management:' do
  1357. it 'deletes all related ActivityStreams on destroy' do
  1358. create_list(:activity_stream, 3, o: ticket)
  1359. expect { ticket.destroy }
  1360. .to change { ActivityStream.exists?(activity_stream_object_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  1361. .to(false)
  1362. end
  1363. it 'deletes all related Links on destroy' do
  1364. create(:link, from: ticket, to: create(:ticket))
  1365. create(:link, from: create(:ticket), to: ticket)
  1366. create(:link, from: ticket, to: create(:ticket))
  1367. expect { ticket.destroy }
  1368. .to change { Link.where('link_object_source_value = :id OR link_object_target_value = :id', id: ticket.id).any? }
  1369. .to(false)
  1370. end
  1371. it 'deletes all related Articles on destroy' do
  1372. create_list(:ticket_article, 3, ticket: ticket)
  1373. expect { ticket.destroy }
  1374. .to change { Ticket::Article.exists?(ticket: ticket) }
  1375. .to(false)
  1376. end
  1377. it 'deletes all related OnlineNotifications on destroy' do
  1378. create_list(:online_notification, 3, o: ticket)
  1379. expect { ticket.destroy }
  1380. .to change { OnlineNotification.where(object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id).any? }
  1381. .to(false)
  1382. end
  1383. it 'deletes all related Tags on destroy' do
  1384. create_list(:tag, 3, o: ticket)
  1385. expect { ticket.destroy }
  1386. .to change { Tag.exists?(tag_object_id: Tag::Object.lookup(name: 'Ticket').id, o_id: ticket.id) }
  1387. .to(false)
  1388. end
  1389. it 'deletes all related Histories on destroy' do
  1390. create_list(:history, 3, o: ticket)
  1391. expect { ticket.destroy }
  1392. .to change { History.exists?(history_object_id: History::Object.lookup(name: 'Ticket').id, o_id: ticket.id) }
  1393. .to(false)
  1394. end
  1395. it 'deletes all related Karma::ActivityLogs on destroy' do
  1396. create_list(:'karma/activity_log', 3, o: ticket)
  1397. expect { ticket.destroy }
  1398. .to change { Karma::ActivityLog.exists?(object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  1399. .to(false)
  1400. end
  1401. it 'deletes all related RecentViews on destroy' do
  1402. create_list(:recent_view, 3, o: ticket)
  1403. expect { ticket.destroy }
  1404. .to change { RecentView.exists?(recent_view_object_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  1405. .to(false)
  1406. end
  1407. it 'destroys all related dependencies' do
  1408. refs_known = { 'Ticket::Article' => { 'ticket_id'=>1 },
  1409. 'Ticket::TimeAccounting' => { 'ticket_id'=>1 },
  1410. 'Ticket::Flag' => { 'ticket_id'=>1 } }
  1411. ticket = create(:ticket)
  1412. article = create(:ticket_article, ticket: ticket)
  1413. accounting = create(:ticket_time_accounting, ticket: ticket)
  1414. flag = create(:ticket_flag, ticket: ticket)
  1415. refs_ticket = Models.references('Ticket', ticket.id, true)
  1416. expect(refs_ticket).to eq(refs_known)
  1417. ticket.destroy
  1418. expect { ticket.reload }.to raise_exception(ActiveRecord::RecordNotFound)
  1419. expect { article.reload }.to raise_exception(ActiveRecord::RecordNotFound)
  1420. expect { accounting.reload }.to raise_exception(ActiveRecord::RecordNotFound)
  1421. expect { flag.reload }.to raise_exception(ActiveRecord::RecordNotFound)
  1422. end
  1423. context 'when ticket is generated from email (with attachments)' do
  1424. subject(:ticket) { Channel::EmailParser.new.process({}, raw_email).first }
  1425. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail001.box')) }
  1426. it 'adds attachments to the Store{::File,::Provider::DB} tables' do
  1427. expect { ticket }
  1428. .to change(Store, :count).by(2)
  1429. .and change { Store::File.count }.by(2)
  1430. .and change { Store::Provider::DB.count }.by(2)
  1431. end
  1432. context 'and subsequently destroyed' do
  1433. it 'deletes all related attachments' do
  1434. ticket # create ticket
  1435. expect { ticket.destroy }
  1436. .to change(Store, :count).by(-2)
  1437. .and change { Store::File.count }.by(-2)
  1438. .and change { Store::Provider::DB.count }.by(-2)
  1439. end
  1440. end
  1441. context 'and a duplicate ticket is generated from the same email' do
  1442. before { ticket } # create ticket
  1443. let(:duplicate) { Channel::EmailParser.new.process({}, raw_email).first }
  1444. it 'adds duplicate attachments to the Store table only' do
  1445. expect { duplicate }
  1446. .to change(Store, :count).by(2)
  1447. .and change { Store::File.count }.by(0)
  1448. .and change { Store::Provider::DB.count }.by(0)
  1449. end
  1450. context 'when only the duplicate ticket is destroyed' do
  1451. it 'deletes only the duplicate attachments' do
  1452. duplicate # create ticket
  1453. expect { duplicate.destroy }
  1454. .to change(Store, :count).by(-2)
  1455. .and change { Store::File.count }.by(0)
  1456. .and change { Store::Provider::DB.count }.by(0)
  1457. end
  1458. it 'deletes all related attachments' do
  1459. duplicate.destroy
  1460. expect { ticket.destroy }
  1461. .to change(Store, :count).by(-2)
  1462. .and change { Store::File.count }.by(-2)
  1463. .and change { Store::Provider::DB.count }.by(-2)
  1464. end
  1465. end
  1466. end
  1467. end
  1468. end
  1469. describe 'Ticket lifecycle order-of-operations:' do
  1470. subject!(:ticket) { create(:ticket) }
  1471. let!(:agent) { create(:agent, groups: [group]) }
  1472. let(:group) { create(:group) }
  1473. before do
  1474. create(
  1475. :trigger,
  1476. condition: { 'ticket.action' => { 'operator' => 'is', 'value' => 'create' } },
  1477. perform: { 'ticket.group_id' => { 'value' => group.id } }
  1478. )
  1479. end
  1480. it 'fires triggers before new ticket notifications are sent' do
  1481. expect { TransactionDispatcher.commit }
  1482. .to change { ticket.reload.group }.to(group)
  1483. expect { Scheduler.worker(true) }
  1484. .to change { NotificationFactory::Mailer.already_sent?(ticket, agent, 'email') }.to(1)
  1485. end
  1486. end
  1487. describe 'Ticket has changed attributes:' do
  1488. subject!(:ticket) { create(:ticket) }
  1489. let(:group) { create(:group) }
  1490. let(:condition_field) { nil }
  1491. shared_examples 'updated ticket group with trigger condition' do
  1492. it 'updated ticket group with has changed trigger condition' do
  1493. expect { TransactionDispatcher.commit }.to change { ticket.reload.group }.to(group)
  1494. end
  1495. end
  1496. before do
  1497. create(
  1498. :trigger,
  1499. condition: { "ticket.#{condition_field}" => { 'operator' => 'has changed', 'value' => 'create' } },
  1500. perform: { 'ticket.group_id' => { 'value' => group.id } }
  1501. )
  1502. ticket.update!(condition_field => Time.zone.now)
  1503. end
  1504. context "when changing 'first_response_at' attribute" do
  1505. let(:condition_field) { 'first_response_at' }
  1506. include_examples 'updated ticket group with trigger condition'
  1507. end
  1508. context "when changing 'close_at' attribute" do
  1509. let(:condition_field) { 'close_at' }
  1510. include_examples 'updated ticket group with trigger condition'
  1511. end
  1512. context "when changing 'last_contact_agent_at' attribute" do
  1513. let(:condition_field) { 'last_contact_agent_at' }
  1514. include_examples 'updated ticket group with trigger condition'
  1515. end
  1516. context "when changing 'last_contact_customer_at' attribute" do
  1517. let(:condition_field) { 'last_contact_customer_at' }
  1518. include_examples 'updated ticket group with trigger condition'
  1519. end
  1520. context "when changing 'last_contact_at' attribute" do
  1521. let(:condition_field) { 'last_contact_at' }
  1522. include_examples 'updated ticket group with trigger condition'
  1523. end
  1524. end
  1525. end
  1526. describe 'Mentions:', sends_notification_emails: true do
  1527. context 'when notifications' do
  1528. let(:prefs_matrix_no_mentions) do
  1529. { 'notification_config' =>
  1530. { 'matrix' =>
  1531. { 'create' => { 'criteria' => { 'owned_by_me' => true, 'owned_by_nobody' => true, 'subscribed' => false, 'no' => true }, 'channel' => { 'email' => true, 'online' => true } },
  1532. 'update' => { 'criteria' => { 'owned_by_me' => true, 'owned_by_nobody' => true, 'subscribed' => false, 'no' => true }, 'channel' => { 'email' => true, 'online' => true } },
  1533. 'reminder_reached' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => false, 'no' => false }, 'channel' => { 'email' => false, 'online' => false } },
  1534. 'escalation' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => false, 'no' => false }, 'channel' => { 'email' => false, 'online' => false } } } } }
  1535. end
  1536. let(:prefs_matrix_only_mentions) do
  1537. { 'notification_config' =>
  1538. { 'matrix' =>
  1539. { 'create' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => true, 'online' => true } },
  1540. 'update' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => true, 'online' => true } },
  1541. 'reminder_reached' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => false, 'online' => false } },
  1542. 'escalation' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => false, 'online' => false } } } } }
  1543. end
  1544. let(:prefs_matrix_only_mentions_groups) do
  1545. { 'notification_config' =>
  1546. { 'matrix' =>
  1547. { 'create' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => true, 'online' => true } },
  1548. 'update' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => true, 'online' => true } },
  1549. 'reminder_reached' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => false, 'online' => false } },
  1550. 'escalation' => { 'criteria' => { 'owned_by_me' => false, 'owned_by_nobody' => false, 'subscribed' => true, 'no' => false }, 'channel' => { 'email' => false, 'online' => false } } },
  1551. 'group_ids' => [create(:group).id, create(:group).id, create(:group).id] } }
  1552. end
  1553. let(:mention_group) { create(:group) }
  1554. let(:no_access_group) { create(:group) }
  1555. let(:user_only_mentions) { create(:agent, groups: [mention_group], preferences: prefs_matrix_only_mentions) }
  1556. let(:user_read_mentions) { create(:agent, groups: [mention_group], preferences: prefs_matrix_only_mentions_groups) }
  1557. let(:user_no_mentions) { create(:agent, groups: [mention_group], preferences: prefs_matrix_no_mentions) }
  1558. let(:ticket) { create(:ticket, group: mention_group, owner: user_no_mentions) }
  1559. it 'does inform mention user about the ticket update' do
  1560. create(:mention, mentionable: ticket, user: user_only_mentions)
  1561. create(:mention, mentionable: ticket, user: user_read_mentions)
  1562. create(:mention, mentionable: ticket, user: user_no_mentions)
  1563. TransactionDispatcher.commit
  1564. Scheduler.worker(true)
  1565. check_notification do
  1566. ticket.update(priority: Ticket::Priority.find_by(name: '3 high'))
  1567. TransactionDispatcher.commit
  1568. Scheduler.worker(true)
  1569. sent(
  1570. template: 'ticket_update',
  1571. user: user_no_mentions,
  1572. )
  1573. sent(
  1574. template: 'ticket_update',
  1575. user: user_read_mentions,
  1576. )
  1577. sent(
  1578. template: 'ticket_update',
  1579. user: user_only_mentions,
  1580. )
  1581. end
  1582. end
  1583. it 'does not inform mention user about the ticket update' do
  1584. ticket
  1585. TransactionDispatcher.commit
  1586. Scheduler.worker(true)
  1587. check_notification do
  1588. ticket.update(priority: Ticket::Priority.find_by(name: '3 high'))
  1589. TransactionDispatcher.commit
  1590. Scheduler.worker(true)
  1591. sent(
  1592. template: 'ticket_update',
  1593. user: user_no_mentions,
  1594. )
  1595. not_sent(
  1596. template: 'ticket_update',
  1597. user: user_read_mentions,
  1598. )
  1599. not_sent(
  1600. template: 'ticket_update',
  1601. user: user_only_mentions,
  1602. )
  1603. end
  1604. end
  1605. it 'does inform mention user about ticket creation' do
  1606. check_notification do
  1607. ticket = create(:ticket, owner: user_no_mentions, group: mention_group)
  1608. create(:mention, mentionable: ticket, user: user_read_mentions)
  1609. create(:mention, mentionable: ticket, user: user_only_mentions)
  1610. TransactionDispatcher.commit
  1611. Scheduler.worker(true)
  1612. sent(
  1613. template: 'ticket_create',
  1614. user: user_no_mentions,
  1615. )
  1616. sent(
  1617. template: 'ticket_create',
  1618. user: user_read_mentions,
  1619. )
  1620. sent(
  1621. template: 'ticket_create',
  1622. user: user_only_mentions,
  1623. )
  1624. end
  1625. end
  1626. it 'does not inform mention user about ticket creation' do
  1627. check_notification do
  1628. create(:ticket, owner: user_no_mentions, group: mention_group)
  1629. TransactionDispatcher.commit
  1630. Scheduler.worker(true)
  1631. sent(
  1632. template: 'ticket_create',
  1633. user: user_no_mentions,
  1634. )
  1635. not_sent(
  1636. template: 'ticket_create',
  1637. user: user_read_mentions,
  1638. )
  1639. not_sent(
  1640. template: 'ticket_create',
  1641. user: user_only_mentions,
  1642. )
  1643. end
  1644. end
  1645. it 'does not inform mention user about ticket creation because of no permissions' do
  1646. check_notification do
  1647. ticket = create(:ticket, group: no_access_group)
  1648. create(:mention, mentionable: ticket, user: user_read_mentions)
  1649. create(:mention, mentionable: ticket, user: user_only_mentions)
  1650. TransactionDispatcher.commit
  1651. Scheduler.worker(true)
  1652. not_sent(
  1653. template: 'ticket_create',
  1654. user: user_read_mentions,
  1655. )
  1656. not_sent(
  1657. template: 'ticket_create',
  1658. user: user_only_mentions,
  1659. )
  1660. end
  1661. end
  1662. end
  1663. context 'selectors' do
  1664. let(:mention_group) { create(:group) }
  1665. let(:ticket_mentions) { create(:ticket, group: mention_group) }
  1666. let(:ticket_normal) { create(:ticket, group: mention_group) }
  1667. let(:user_mentions) { create(:agent, groups: [mention_group]) }
  1668. let(:user_no_mentions) { create(:agent, groups: [mention_group]) }
  1669. before do
  1670. described_class.destroy_all
  1671. ticket_normal
  1672. user_no_mentions
  1673. create(:mention, mentionable: ticket_mentions, user: user_mentions)
  1674. end
  1675. it 'pre condition is not_set' do
  1676. condition = {
  1677. 'ticket.mention_user_ids' => {
  1678. pre_condition: 'not_set',
  1679. operator: 'is',
  1680. },
  1681. }
  1682. expect(described_class.selectors(condition, limit: 100, access: 'full'))
  1683. .to match_array([1, [ticket_normal].to_a])
  1684. end
  1685. it 'pre condition is not not_set' do
  1686. condition = {
  1687. 'ticket.mention_user_ids' => {
  1688. pre_condition: 'not_set',
  1689. operator: 'is not',
  1690. },
  1691. }
  1692. expect(described_class.selectors(condition, limit: 100, access: 'full'))
  1693. .to match_array([1, [ticket_mentions].to_a])
  1694. end
  1695. it 'pre condition is current_user.id' do
  1696. condition = {
  1697. 'ticket.mention_user_ids' => {
  1698. pre_condition: 'current_user.id',
  1699. operator: 'is',
  1700. },
  1701. }
  1702. expect(described_class.selectors(condition, limit: 100, access: 'full', current_user: user_mentions))
  1703. .to match_array([1, [ticket_mentions].to_a])
  1704. end
  1705. it 'pre condition is not current_user.id' do
  1706. condition = {
  1707. 'ticket.mention_user_ids' => {
  1708. pre_condition: 'current_user.id',
  1709. operator: 'is not',
  1710. },
  1711. }
  1712. expect(described_class.selectors(condition, limit: 100, access: 'full', current_user: user_mentions))
  1713. .to match_array([0, []])
  1714. end
  1715. it 'pre condition is specific' do
  1716. condition = {
  1717. 'ticket.mention_user_ids' => {
  1718. pre_condition: 'specific',
  1719. operator: 'is',
  1720. value: user_mentions.id
  1721. },
  1722. }
  1723. expect(described_class.selectors(condition, limit: 100, access: 'full'))
  1724. .to match_array([1, [ticket_mentions].to_a])
  1725. end
  1726. it 'pre condition is not specific' do
  1727. condition = {
  1728. 'ticket.mention_user_ids' => {
  1729. pre_condition: 'specific',
  1730. operator: 'is not',
  1731. value: user_mentions.id
  1732. },
  1733. }
  1734. expect(described_class.selectors(condition, limit: 100, access: 'full'))
  1735. .to match_array([0, []])
  1736. end
  1737. end
  1738. end
  1739. describe '.search_index_attribute_lookup_oversized?' do
  1740. subject!(:ticket) { create(:ticket) }
  1741. context 'when payload is ok' do
  1742. let(:current_payload_size) { 3.megabyte }
  1743. it 'return false' do
  1744. expect(ticket.send(:search_index_attribute_lookup_oversized?, current_payload_size)).to eq false
  1745. end
  1746. end
  1747. context 'when payload is bigger' do
  1748. let(:current_payload_size) { 350.megabyte }
  1749. it 'return true' do
  1750. expect(ticket.send(:search_index_attribute_lookup_oversized?, current_payload_size)).to eq true
  1751. end
  1752. end
  1753. end
  1754. describe '.search_index_attribute_lookup_file_oversized?' do
  1755. subject!(:store) do
  1756. Store.add(
  1757. object: 'SomeObject',
  1758. o_id: 1,
  1759. data: 'a' * ((1024**2) * 2.4), # with 2.4 mb
  1760. filename: 'test.TXT',
  1761. created_by_id: 1,
  1762. )
  1763. end
  1764. context 'when total payload is ok' do
  1765. let(:current_payload_size) { 200.megabyte }
  1766. it 'return false' do
  1767. expect(ticket.send(:search_index_attribute_lookup_file_oversized?, store, current_payload_size)).to eq false
  1768. end
  1769. end
  1770. context 'when total payload is oversized' do
  1771. let(:current_payload_size) { 299.megabyte }
  1772. it 'return true' do
  1773. expect(ticket.send(:search_index_attribute_lookup_file_oversized?, store, current_payload_size)).to eq true
  1774. end
  1775. end
  1776. end
  1777. describe '.search_index_attribute_lookup_file_ignored?' do
  1778. context 'when attachment is indexable' do
  1779. subject!(:store_with_indexable_extention) do
  1780. Store.add(
  1781. object: 'SomeObject',
  1782. o_id: 1,
  1783. data: 'some content',
  1784. filename: 'test.TXT',
  1785. created_by_id: 1,
  1786. )
  1787. end
  1788. it 'return false' do
  1789. expect(ticket.send(:search_index_attribute_lookup_file_ignored?, store_with_indexable_extention)).to eq false
  1790. end
  1791. end
  1792. context 'when attachment is no indexable' do
  1793. subject!(:store_without_indexable_extention) do
  1794. Store.add(
  1795. object: 'SomeObject',
  1796. o_id: 1,
  1797. data: 'some content',
  1798. filename: 'test.BIN',
  1799. created_by_id: 1,
  1800. )
  1801. end
  1802. it 'return true' do
  1803. expect(ticket.send(:search_index_attribute_lookup_file_ignored?, store_without_indexable_extention)).to eq true
  1804. end
  1805. end
  1806. end
  1807. describe '.search_index_attribute_lookup' do
  1808. subject!(:ticket) { create(:ticket) }
  1809. let(:search_index_attribute_lookup) do
  1810. article1 = create(:ticket_article, ticket: ticket)
  1811. Store.add(
  1812. object: 'Ticket::Article',
  1813. o_id: article1.id,
  1814. data: 'some content',
  1815. filename: 'some_file.bin',
  1816. preferences: {
  1817. 'Content-Type' => 'text/plain',
  1818. },
  1819. created_by_id: 1,
  1820. )
  1821. Store.add(
  1822. object: 'Ticket::Article',
  1823. o_id: article1.id,
  1824. data: 'a' * ((1024**2) * 2.4), # with 2.4 mb
  1825. filename: 'some_file.pdf',
  1826. preferences: {
  1827. 'Content-Type' => 'image/pdf',
  1828. },
  1829. created_by_id: 1,
  1830. )
  1831. Store.add(
  1832. object: 'Ticket::Article',
  1833. o_id: article1.id,
  1834. data: 'a' * ((1024**2) * 5.8), # with 5,8 mb
  1835. filename: 'some_file.txt',
  1836. preferences: {
  1837. 'Content-Type' => 'text/plain',
  1838. },
  1839. created_by_id: 1,
  1840. )
  1841. create(:ticket_article, ticket: ticket, body: 'a' * ((1024**2) * 1.2)) # body with 1,2 mb
  1842. create(:ticket_article, ticket: ticket)
  1843. ticket.search_index_attribute_lookup
  1844. end
  1845. context 'when es_attachment_max_size_in_mb takes all attachments' do
  1846. before { Setting.set('es_attachment_max_size_in_mb', 15) }
  1847. it 'verify count of articles' do
  1848. expect(search_index_attribute_lookup['article'].count).to eq 3
  1849. end
  1850. it 'verify count of attachments' do
  1851. expect(search_index_attribute_lookup['article'][0]['attachment'].count).to eq 2
  1852. end
  1853. it 'verify if pdf exists' do
  1854. expect(search_index_attribute_lookup['article'][0]['attachment'][0]['_name']).to eq 'some_file.pdf'
  1855. end
  1856. it 'verify if txt exists' do
  1857. expect(search_index_attribute_lookup['article'][0]['attachment'][1]['_name']).to eq 'some_file.txt'
  1858. end
  1859. end
  1860. context 'when es_attachment_max_size_in_mb takes only one attachment' do
  1861. before { Setting.set('es_attachment_max_size_in_mb', 4) }
  1862. it 'verify count of articles' do
  1863. expect(search_index_attribute_lookup['article'].count).to eq 3
  1864. end
  1865. it 'verify count of attachments' do
  1866. expect(search_index_attribute_lookup['article'][0]['attachment'].count).to eq 1
  1867. end
  1868. it 'verify if pdf exists' do
  1869. expect(search_index_attribute_lookup['article'][0]['attachment'][0]['_name']).to eq 'some_file.pdf'
  1870. end
  1871. end
  1872. context 'when es_attachment_max_size_in_mb takes no attachment' do
  1873. before { Setting.set('es_attachment_max_size_in_mb', 2) }
  1874. it 'verify count of articles' do
  1875. expect(search_index_attribute_lookup['article'].count).to eq 3
  1876. end
  1877. it 'verify count of attachments' do
  1878. expect(search_index_attribute_lookup['article'][0]['attachment'].count).to eq 0
  1879. end
  1880. end
  1881. context 'when es_total_max_size_in_mb takes no attachment and no oversized article' do
  1882. before { Setting.set('es_total_max_size_in_mb', 1) }
  1883. it 'verify count of articles' do
  1884. expect(search_index_attribute_lookup['article'].count).to eq 2
  1885. end
  1886. it 'verify count of attachments' do
  1887. expect(search_index_attribute_lookup['article'][0]['attachment'].count).to eq 0
  1888. end
  1889. end
  1890. end
  1891. end