ticket_spec.rb 83 KB

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