ticket_spec.rb 81 KB

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