ticket_spec.rb 86 KB

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