ticket_spec.rb 74 KB

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