trigger_spec.rb 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. require 'models/application_model_examples'
  4. require 'models/concerns/has_xss_sanitized_note_examples'
  5. RSpec.describe Trigger, type: :model do
  6. subject(:trigger) { create(:trigger, condition: condition, perform: perform, activator: activator, execution_condition_mode: execution_condition_mode) }
  7. let(:activator) { 'action' }
  8. let(:execution_condition_mode) { 'selective' }
  9. let(:condition) do
  10. { 'ticket.action' => { 'operator' => 'is', 'value' => 'create' } }
  11. end
  12. let(:perform) do
  13. { 'ticket.title'=>{ 'value'=>'triggered' } }
  14. end
  15. it_behaves_like 'ApplicationModel', can_assets: { selectors: %i[condition perform] }
  16. it_behaves_like 'HasXssSanitizedNote', model_factory: :trigger
  17. describe 'validation' do
  18. it 'uses Validations::VerifyPerformRulesValidator' do
  19. expect(described_class).to have_validator(Validations::VerifyPerformRulesValidator).on(:perform)
  20. end
  21. it { is_expected.to validate_presence_of(:activator) }
  22. it { is_expected.to validate_presence_of(:execution_condition_mode) }
  23. it { is_expected.to validate_inclusion_of(:activator).in_array(%w[action time]) }
  24. it { is_expected.to validate_inclusion_of(:execution_condition_mode).in_array(%w[selective always]) }
  25. end
  26. describe 'Send-email triggers' do
  27. before do
  28. described_class.destroy_all # Default DB state includes three sample triggers
  29. trigger # create subject trigger
  30. end
  31. let(:perform) do
  32. {
  33. 'notification.email' => {
  34. 'recipient' => 'ticket_customer',
  35. 'subject' => 'foo',
  36. 'body' => 'some body with >snip<#{article.body_as_html}>/snip<', # rubocop:disable Lint/InterpolationCheck
  37. }
  38. }
  39. end
  40. shared_examples 'include ticket attachment' do
  41. context 'notification.email include_attachments' do
  42. let(:perform) do
  43. {
  44. 'notification.email' => {
  45. 'recipient' => 'ticket_customer',
  46. 'subject' => 'Example subject',
  47. 'body' => 'Example body',
  48. }
  49. }.deep_merge(additional_options).deep_stringify_keys
  50. end
  51. let(:ticket) { create(:ticket) }
  52. shared_examples 'add a new article' do
  53. it 'adds a new article' do
  54. expect { TransactionDispatcher.commit }
  55. .to change(ticket.articles, :count).by(1)
  56. end
  57. end
  58. shared_examples 'add attachment to new article' do
  59. include_examples 'add a new article'
  60. it 'adds attachment to the new article' do
  61. ticket && trigger
  62. TransactionDispatcher.commit
  63. article = ticket.articles.last
  64. expect(article.type.name).to eq('email')
  65. expect(article.sender.name).to eq('System')
  66. expect(article.attachments.count).to eq(1)
  67. expect(article.attachments[0].filename).to eq('some_file.pdf')
  68. expect(article.attachments[0].preferences['Content-ID']).to eq('image/pdf@01CAB192.K8H512Y9')
  69. end
  70. end
  71. shared_examples 'does not add attachment to new article' do
  72. include_examples 'add a new article'
  73. it 'does not add attachment to the new article' do
  74. ticket && trigger
  75. TransactionDispatcher.commit
  76. article = ticket.articles.last
  77. expect(article.type.name).to eq('email')
  78. expect(article.sender.name).to eq('System')
  79. expect(article.attachments.count).to eq(0)
  80. end
  81. end
  82. context 'with include attachment present' do
  83. let(:additional_options) do
  84. {
  85. 'notification.email' => {
  86. include_attachments: 'true'
  87. }
  88. }
  89. end
  90. context 'when ticket has an attachment' do
  91. before do
  92. UserInfo.current_user_id = 1
  93. ticket_article = create(:ticket_article, ticket: ticket)
  94. create(:store,
  95. object: 'Ticket::Article',
  96. o_id: ticket_article.id,
  97. data: 'dGVzdCAxMjM=',
  98. filename: 'some_file.pdf',
  99. preferences: {
  100. 'Content-Type': 'image/pdf',
  101. 'Content-ID': 'image/pdf@01CAB192.K8H512Y9',
  102. })
  103. end
  104. include_examples 'add attachment to new article'
  105. end
  106. context 'when ticket does not have an attachment' do
  107. include_examples 'does not add attachment to new article'
  108. end
  109. end
  110. context 'with include attachment not present' do
  111. let(:additional_options) do
  112. {
  113. 'notification.email' => {
  114. include_attachments: 'false'
  115. }
  116. }
  117. end
  118. context 'when ticket has an attachment' do
  119. before do
  120. UserInfo.current_user_id = 1
  121. ticket_article = create(:ticket_article, ticket: ticket)
  122. create(:store,
  123. object: 'Ticket::Article',
  124. o_id: ticket_article.id,
  125. data: 'dGVzdCAxMjM=',
  126. filename: 'some_file.pdf',
  127. preferences: {
  128. 'Content-Type': 'image/pdf',
  129. 'Content-ID': 'image/pdf@01CAB192.K8H512Y9',
  130. })
  131. end
  132. include_examples 'does not add attachment to new article'
  133. end
  134. context 'when ticket does not have an attachment' do
  135. include_examples 'does not add attachment to new article'
  136. end
  137. end
  138. end
  139. end
  140. context 'for condition "ticket created"' do
  141. let(:condition) do
  142. { 'ticket.action' => { 'operator' => 'is', 'value' => 'create' } }
  143. end
  144. context 'when ticket is created directly' do
  145. let!(:ticket) { create(:ticket) }
  146. it 'fires (without altering ticket state)' do
  147. expect { TransactionDispatcher.commit }
  148. .to change(Ticket::Article, :count).by(1)
  149. .and not_change { ticket.reload.state.name }.from('new')
  150. end
  151. end
  152. context 'when ticket has tags' do
  153. let(:tag1) { create(:'tag/item', name: 't1') }
  154. let(:tag2) { create(:'tag/item', name: 't2') }
  155. let(:tag3) { create(:'tag/item', name: 't3') }
  156. let!(:ticket) do
  157. ticket = create(:ticket)
  158. create(:tag, o: ticket, tag_item: tag1)
  159. create(:tag, o: ticket, tag_item: tag2)
  160. create(:tag, o: ticket, tag_item: tag3)
  161. ticket
  162. end
  163. let(:perform) do
  164. {
  165. 'notification.email' => {
  166. 'recipient' => 'ticket_customer',
  167. 'subject' => 'foo',
  168. 'body' => 'some body with #{ticket.tags}', # rubocop:disable Lint/InterpolationCheck
  169. }
  170. }
  171. end
  172. it 'fires body with replaced tags' do
  173. TransactionDispatcher.commit
  174. expect(Ticket::Article.last.body).to eq('some body with t1, t2, t3')
  175. end
  176. end
  177. context 'when ticket is created via Channel::EmailParser.process' do
  178. before { create(:email_address, groups: [Group.first]) }
  179. let(:raw_email) { Rails.root.join('test/data/mail/mail001.box').read }
  180. it 'fires (without altering ticket state)' do
  181. expect { Channel::EmailParser.new.process({}, raw_email) }
  182. .to change(Ticket, :count).by(1)
  183. .and change(Ticket::Article, :count).by(2)
  184. expect(Ticket.last.state.name).to eq('new')
  185. end
  186. end
  187. context 'when ticket is created via Channel::EmailParser.process with inline image' do
  188. before { create(:email_address, groups: [Group.first]) }
  189. let(:raw_email) { Rails.root.join('test/data/mail/mail010.box').read }
  190. it 'fires (without altering ticket state)' do
  191. expect { Channel::EmailParser.new.process({}, raw_email) }
  192. .to change(Ticket, :count).by(1)
  193. .and change(Ticket::Article, :count).by(2)
  194. expect(Ticket.last.state.name).to eq('new')
  195. article = Ticket::Article.last
  196. expect(article.type.name).to eq('email')
  197. expect(article.sender.name).to eq('System')
  198. expect(article.attachments.count).to eq(1)
  199. expect(article.attachments[0].filename).to eq('image001.jpg')
  200. expect(article.attachments[0].preferences['Content-ID']).to eq('image001.jpg@01CDB132.D8A510F0')
  201. expect(article.body).to eq(<<~RAW.chomp
  202. some body with &gt;snip&lt;<div>
  203. <p>Herzliche Grüße aus Oberalteich sendet Herrn Smith</p>
  204. <p> </p>
  205. <p>Sepp Smith - Dipl.Ing. agr. (FH)</p>
  206. <p>Geschäftsführer der example Straubing-Bogen</p>
  207. <p>Klosterhof 1 | 94327 Bogen-Oberalteich</p>
  208. <p>Tel: 09422-505601 | Fax: 09422-505620</p>
  209. <p><span>Internet: <a href="http://example-straubing-bogen.de/" rel="nofollow noreferrer noopener" target="_blank"><span style="color:blue;">http://example-straubing-bogen.de</span></a></span></p>
  210. <p><span lang="EN-US">Facebook: </span><a href="http://facebook.de/examplesrbog" rel="nofollow noreferrer noopener" target="_blank"><span lang="EN-US" style="color:blue;">http://facebook.de/examplesrbog</span></a><span lang="EN-US"></span></p>
  211. <p><b><span style="color:navy;"><img border="0" src="cid:image001.jpg@01CDB132.D8A510F0" alt="Beschreibung: Beschreibung: efqmLogo" style="width:60px;height:19px;"></span></b><b><span lang="EN-US" style="color:navy;"> - European Foundation für Quality Management</span></b><span lang="EN-US"></span></p>
  212. <p><span lang="EN-US"> </span></p>
  213. </div>&gt;/snip&lt;
  214. RAW
  215. )
  216. end
  217. end
  218. context 'notification.email recipient' do
  219. let!(:ticket) { create(:ticket) }
  220. let!(:recipient1) { create(:user, email: 'test1@zammad-test.com') }
  221. let!(:recipient2) { create(:user, email: 'test2@zammad-test.com') }
  222. let!(:recipient3) { create(:user, email: 'test3@zammad-test.com') }
  223. let(:perform) do
  224. {
  225. 'notification.email' => {
  226. 'recipient' => recipient,
  227. 'subject' => 'Hello',
  228. 'body' => 'World!'
  229. }
  230. }
  231. end
  232. before { TransactionDispatcher.commit }
  233. context 'mix of recipient group keyword and single recipient users' do
  234. let(:recipient) { [ 'ticket_customer', "userid_#{recipient1.id}", "userid_#{recipient2.id}", "userid_#{recipient3.id}" ] }
  235. it 'contains all recipients' do
  236. expect(ticket.articles.last.to).to eq("#{ticket.customer.email}, #{recipient1.email}, #{recipient2.email}, #{recipient3.email}")
  237. end
  238. context 'duplicate recipient' do
  239. let(:recipient) { [ 'ticket_customer', "userid_#{ticket.customer.id}" ] }
  240. it 'contains only one recipient' do
  241. expect(ticket.articles.last.to).to eq(ticket.customer.email.to_s)
  242. end
  243. end
  244. end
  245. context 'list of single users only' do
  246. let(:recipient) { [ "userid_#{recipient1.id}", "userid_#{recipient2.id}", "userid_#{recipient3.id}" ] }
  247. it 'contains all recipients' do
  248. expect(ticket.articles.last.to).to eq("#{recipient1.email}, #{recipient2.email}, #{recipient3.email}")
  249. end
  250. context 'assets' do
  251. it 'resolves Users from recipient list' do
  252. expect(trigger.assets({})[:User].keys).to include(recipient1.id, recipient2.id, recipient3.id)
  253. end
  254. context 'single entry' do
  255. let(:recipient) { "userid_#{recipient1.id}" }
  256. it 'resolves User from recipient list' do
  257. expect(trigger.assets({})[:User].keys).to include(recipient1.id)
  258. end
  259. end
  260. end
  261. end
  262. context 'recipient group keyword only' do
  263. let(:recipient) { 'ticket_customer' }
  264. it 'contains matching recipient' do
  265. expect(ticket.articles.last.to).to eq(ticket.customer.email.to_s)
  266. end
  267. end
  268. end
  269. context 'active S/MIME integration' do
  270. before do
  271. Setting.set('smime_integration', true)
  272. create(:smime_certificate, :with_private, fixture: system_email_address)
  273. create(:smime_certificate, fixture: customer_email_address)
  274. end
  275. let(:system_email_address) { 'smime1@example.com' }
  276. let(:customer_email_address) { 'smime2@example.com' }
  277. let(:email_address) { create(:email_address, email: system_email_address) }
  278. let(:group) { create(:group, email_address: email_address) }
  279. let(:customer) { create(:customer, email: customer_email_address) }
  280. let(:security_preferences) { Ticket::Article.last.preferences[:security] }
  281. let(:perform) do
  282. {
  283. 'notification.email' => {
  284. 'recipient' => 'ticket_customer',
  285. 'subject' => 'Subject dummy.',
  286. 'body' => 'Body dummy.',
  287. }.merge(security_configuration)
  288. }
  289. end
  290. let!(:ticket) { create(:ticket, group: group, customer: customer) }
  291. context 'sending articles' do
  292. before do
  293. TransactionDispatcher.commit
  294. end
  295. context 'expired certificate' do
  296. let(:system_email_address) { 'expiredsmime1@example.com' }
  297. let(:security_configuration) do
  298. {
  299. 'sign' => 'always',
  300. 'encryption' => 'always',
  301. }
  302. end
  303. it 'creates unsigned article' do
  304. expect(security_preferences[:sign][:success]).to be false
  305. expect(security_preferences[:encryption][:success]).to be true
  306. end
  307. end
  308. context 'sign and encryption not set' do
  309. let(:security_configuration) { {} }
  310. it 'does not sign or encrypt' do
  311. expect(security_preferences[:sign][:success]).to be false
  312. expect(security_preferences[:encryption][:success]).to be false
  313. end
  314. end
  315. context 'sign and encryption disabled' do
  316. let(:security_configuration) do
  317. {
  318. 'sign' => 'no',
  319. 'encryption' => 'no',
  320. }
  321. end
  322. it 'does not sign or encrypt' do
  323. expect(security_preferences[:sign][:success]).to be false
  324. expect(security_preferences[:encryption][:success]).to be false
  325. end
  326. end
  327. context 'sign is enabled' do
  328. let(:security_configuration) do
  329. {
  330. 'sign' => 'always',
  331. 'encryption' => 'no',
  332. }
  333. end
  334. it 'signs' do
  335. expect(security_preferences[:sign][:success]).to be true
  336. expect(security_preferences[:encryption][:success]).to be false
  337. end
  338. end
  339. context 'encryption enabled' do
  340. let(:security_configuration) do
  341. {
  342. 'sign' => 'no',
  343. 'encryption' => 'always',
  344. }
  345. end
  346. it 'encrypts' do
  347. expect(security_preferences[:sign][:success]).to be false
  348. expect(security_preferences[:encryption][:success]).to be true
  349. end
  350. end
  351. context 'sign and encryption enabled' do
  352. let(:security_configuration) do
  353. {
  354. 'sign' => 'always',
  355. 'encryption' => 'always',
  356. }
  357. end
  358. it 'signs and encrypts' do
  359. expect(security_preferences[:sign][:success]).to be true
  360. expect(security_preferences[:encryption][:success]).to be true
  361. end
  362. end
  363. end
  364. context 'discard' do
  365. context 'sign' do
  366. let(:security_configuration) do
  367. {
  368. 'sign' => 'discard',
  369. }
  370. end
  371. context 'group without certificate' do
  372. let(:group) { create(:group) }
  373. it 'does not fire' do
  374. expect { TransactionDispatcher.commit }
  375. .not_to change(Ticket::Article, :count)
  376. end
  377. end
  378. end
  379. context 'encryption' do
  380. let(:security_configuration) do
  381. {
  382. 'encryption' => 'discard',
  383. }
  384. end
  385. context 'customer without certificate' do
  386. let(:customer) { create(:customer) }
  387. it 'does not fire' do
  388. expect { TransactionDispatcher.commit }
  389. .not_to change(Ticket::Article, :count)
  390. end
  391. end
  392. end
  393. context 'mixed' do
  394. context 'sign' do
  395. let(:security_configuration) do
  396. {
  397. 'encryption' => 'always',
  398. 'sign' => 'discard',
  399. }
  400. end
  401. context 'group without certificate' do
  402. let(:group) { create(:group) }
  403. it 'does not fire' do
  404. expect { TransactionDispatcher.commit }
  405. .not_to change(Ticket::Article, :count)
  406. end
  407. end
  408. end
  409. context 'encryption' do
  410. let(:security_configuration) do
  411. {
  412. 'encryption' => 'discard',
  413. 'sign' => 'always',
  414. }
  415. end
  416. context 'customer without certificate' do
  417. let(:customer) { create(:customer) }
  418. it 'does not fire' do
  419. expect { TransactionDispatcher.commit }
  420. .not_to change(Ticket::Article, :count)
  421. end
  422. end
  423. end
  424. end
  425. end
  426. end
  427. context 'active PGP integration' do
  428. before do
  429. Setting.set('pgp_integration', true)
  430. create(:pgp_key, :with_private, fixture: system_email_address)
  431. create(:pgp_key, fixture: customer_email_address)
  432. end
  433. let(:system_email_address) { 'pgp1@example.com' }
  434. let(:customer_email_address) { 'pgp2@example.com' }
  435. let(:email_address) { create(:email_address, email: system_email_address) }
  436. let(:group) { create(:group, email_address: email_address) }
  437. let(:customer) { create(:customer, email: customer_email_address) }
  438. let(:security_preferences) { Ticket::Article.last.preferences[:security] }
  439. let(:perform) do
  440. {
  441. 'notification.email' => {
  442. 'recipient' => 'ticket_customer',
  443. 'subject' => 'Subject dummy.',
  444. 'body' => 'Body dummy.',
  445. }.merge(security_configuration)
  446. }
  447. end
  448. let!(:ticket) { create(:ticket, group: group, customer: customer) }
  449. context 'sending articles' do
  450. before do
  451. TransactionDispatcher.commit
  452. end
  453. context 'expired pgp key' do
  454. let(:system_email_address) { 'expiredpgp1@example.com' }
  455. let(:security_configuration) do
  456. {
  457. 'sign' => 'always',
  458. 'encryption' => 'always',
  459. }
  460. end
  461. it 'creates unsigned article' do
  462. expect(security_preferences[:sign][:success]).to be false
  463. expect(security_preferences[:encryption][:success]).to be true
  464. end
  465. end
  466. context 'sign and encryption not set' do
  467. let(:security_configuration) { {} }
  468. it 'does not sign or encrypt' do
  469. expect(security_preferences[:sign][:success]).to be false
  470. expect(security_preferences[:encryption][:success]).to be false
  471. end
  472. end
  473. context 'sign and encryption disabled' do
  474. let(:security_configuration) do
  475. {
  476. 'sign' => 'no',
  477. 'encryption' => 'no',
  478. }
  479. end
  480. it 'does not sign or encrypt' do
  481. expect(security_preferences[:sign][:success]).to be false
  482. expect(security_preferences[:encryption][:success]).to be false
  483. end
  484. end
  485. context 'sign is enabled' do
  486. let(:security_configuration) do
  487. {
  488. 'sign' => 'always',
  489. 'encryption' => 'no',
  490. }
  491. end
  492. it 'signs' do
  493. expect(security_preferences[:sign][:success]).to be true
  494. expect(security_preferences[:encryption][:success]).to be false
  495. end
  496. end
  497. context 'encryption enabled' do
  498. let(:security_configuration) do
  499. {
  500. 'sign' => 'no',
  501. 'encryption' => 'always',
  502. }
  503. end
  504. it 'encrypts' do
  505. expect(security_preferences[:sign][:success]).to be false
  506. expect(security_preferences[:encryption][:success]).to be true
  507. end
  508. end
  509. context 'sign and encryption enabled' do
  510. let(:security_configuration) do
  511. {
  512. 'sign' => 'always',
  513. 'encryption' => 'always',
  514. }
  515. end
  516. it 'signs and encrypts' do
  517. expect(security_preferences[:sign][:success]).to be true
  518. expect(security_preferences[:encryption][:success]).to be true
  519. end
  520. end
  521. end
  522. context 'discard' do
  523. context 'sign' do
  524. let(:security_configuration) do
  525. {
  526. 'sign' => 'discard',
  527. }
  528. end
  529. context 'group without pgp key' do
  530. let(:group) { create(:group) }
  531. it 'does not fire' do
  532. expect { TransactionDispatcher.commit }
  533. .not_to change(Ticket::Article, :count)
  534. end
  535. end
  536. end
  537. context 'encryption' do
  538. let(:security_configuration) do
  539. {
  540. 'encryption' => 'discard',
  541. }
  542. end
  543. context 'customer without pgp key' do
  544. let(:customer) { create(:customer) }
  545. it 'does not fire' do
  546. expect { TransactionDispatcher.commit }
  547. .not_to change(Ticket::Article, :count)
  548. end
  549. end
  550. end
  551. context 'mixed' do
  552. context 'sign' do
  553. let(:security_configuration) do
  554. {
  555. 'encryption' => 'always',
  556. 'sign' => 'discard',
  557. }
  558. end
  559. context 'group without pgp key' do
  560. let(:group) { create(:group) }
  561. it 'does not fire' do
  562. expect { TransactionDispatcher.commit }
  563. .not_to change(Ticket::Article, :count)
  564. end
  565. end
  566. end
  567. context 'encryption' do
  568. let(:security_configuration) do
  569. {
  570. 'encryption' => 'discard',
  571. 'sign' => 'always',
  572. }
  573. end
  574. context 'customer without pgp key' do
  575. let(:customer) { create(:customer) }
  576. it 'does not fire' do
  577. expect { TransactionDispatcher.commit }
  578. .not_to change(Ticket::Article, :count)
  579. end
  580. end
  581. end
  582. end
  583. end
  584. end
  585. include_examples 'include ticket attachment'
  586. end
  587. context 'for condition "ticket updated"' do
  588. let(:condition) do
  589. { 'ticket.action' => { 'operator' => 'is', 'value' => 'update' } }
  590. end
  591. let!(:ticket) { create(:ticket).tap { TransactionDispatcher.commit } }
  592. context 'when new article is created directly' do
  593. context 'with empty #preferences hash' do
  594. let!(:article) { create(:ticket_article, ticket: ticket) }
  595. it 'fires (without altering ticket state)' do
  596. expect { TransactionDispatcher.commit }
  597. .to change { ticket.reload.articles.count }.by(1)
  598. .and not_change { ticket.reload.state.name }.from('new')
  599. end
  600. end
  601. context 'with #preferences { "send-auto-response" => false }' do
  602. let!(:article) do
  603. create(:ticket_article,
  604. ticket: ticket,
  605. preferences: { 'send-auto-response' => false })
  606. end
  607. it 'does not fire' do
  608. expect { TransactionDispatcher.commit }
  609. .not_to change { ticket.reload.articles.count }
  610. end
  611. end
  612. end
  613. context 'when new article is created via Channel::EmailParser.process' do
  614. context 'with a regular message' do
  615. let!(:article) do
  616. create(:ticket_article,
  617. ticket: ticket,
  618. message_id: raw_email[%r{(?<=^References: )\S*}],
  619. subject: raw_email[%r{(?<=^Subject: Re: ).*$}])
  620. end
  621. let(:raw_email) { Rails.root.join('test/data/mail/mail005.box').read }
  622. it 'fires (without altering ticket state)' do
  623. expect { Channel::EmailParser.new.process({}, raw_email) }
  624. .to not_change { Ticket.count }
  625. .and change { ticket.reload.articles.count }.by(2)
  626. .and not_change { ticket.reload.state.name }.from('new')
  627. end
  628. end
  629. context 'with delivery-failed "bounce message"' do
  630. let!(:article) do
  631. create(:ticket_article,
  632. ticket: ticket,
  633. message_id: raw_email[%r{(?<=^Message-ID: )\S*}])
  634. end
  635. let(:raw_email) { Rails.root.join('test/data/mail/mail055.box').read }
  636. it 'does not fire' do
  637. expect { Channel::EmailParser.new.process({}, raw_email) }
  638. .to change { ticket.reload.articles.count }.by(1)
  639. end
  640. end
  641. end
  642. # https://github.com/zammad/zammad/issues/3991
  643. context 'when article contains a mention' do
  644. let!(:article) do
  645. create(:ticket_article,
  646. ticket: ticket,
  647. body: '<a href="http:/#user/profile/1" data-mention-user-id="1" rel="nofollow noreferrer noopener" target="_blank" title="http:/#user/profile/1">Test Admin Agent</a> test<br>')
  648. end
  649. it 'fires correctly' do
  650. expect { TransactionDispatcher.commit }
  651. .to change { ticket.reload.articles.count }.by(1)
  652. end
  653. end
  654. end
  655. context 'with condition execution_time.calendar_id' do
  656. let(:calendar) { create(:calendar) }
  657. let(:perform) do
  658. { 'ticket.title'=>{ 'value'=>'triggered' } }
  659. end
  660. let!(:ticket) { create(:ticket, title: 'Test Ticket') }
  661. context 'is in working time' do
  662. let(:condition) do
  663. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.pluck(:id) }, 'execution_time.calendar_id' => { 'operator' => 'is in working time', 'value' => calendar.id } }
  664. end
  665. it 'does trigger only in working time' do
  666. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  667. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  668. end
  669. it 'does not trigger out of working time' do
  670. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  671. TransactionDispatcher.commit
  672. expect(ticket.reload.title).to eq('Test Ticket')
  673. end
  674. end
  675. context 'is not in working time' do
  676. let(:condition) do
  677. { 'execution_time.calendar_id' => { 'operator' => 'is not in working time', 'value' => calendar.id } }
  678. end
  679. it 'does not trigger in working time' do
  680. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  681. TransactionDispatcher.commit
  682. expect(ticket.reload.title).to eq('Test Ticket')
  683. end
  684. it 'does trigger out of working time' do
  685. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  686. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  687. end
  688. end
  689. end
  690. context 'with article last sender equals system address' do
  691. let!(:ticket) { create(:ticket) }
  692. let(:perform) do
  693. {
  694. 'notification.email' => {
  695. 'recipient' => 'article_last_sender',
  696. 'subject' => 'foo last sender',
  697. 'body' => 'some body with &gt;snip&lt;#{article.body_as_html}&gt;/snip&lt;', # rubocop:disable Lint/InterpolationCheck
  698. }
  699. }
  700. end
  701. let(:condition) do
  702. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.pluck(:id) } }
  703. end
  704. let!(:system_address) do
  705. create(:email_address)
  706. end
  707. context 'article with from equal to the a system address' do
  708. let!(:article) do
  709. create(:ticket_article,
  710. ticket: ticket,
  711. from: system_address.email,)
  712. end
  713. it 'does not trigger because of the last article is created my system address' do
  714. expect { TransactionDispatcher.commit }.not_to change { ticket.reload.articles.count }
  715. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  716. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  717. end
  718. end
  719. context 'article with reply_to equal to the a system address' do
  720. let!(:article) do
  721. create(:ticket_article,
  722. ticket: ticket,
  723. from: system_address.email,
  724. reply_to: system_address.email,)
  725. end
  726. it 'does not trigger because of the last article is created my system address' do
  727. expect { TransactionDispatcher.commit }.not_to change { ticket.reload.articles.count }
  728. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  729. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  730. end
  731. end
  732. include_examples 'include ticket attachment'
  733. end
  734. end
  735. context 'with pre condition current_user.id' do
  736. let(:perform) do
  737. { 'ticket.title'=>{ 'value'=>'triggered' } }
  738. end
  739. let(:user) do
  740. user = create(:agent)
  741. user.roles.first.groups << group
  742. user
  743. end
  744. let(:group) { Group.first }
  745. let(:ticket) do
  746. create(:ticket,
  747. title: 'Test Ticket', group: group,
  748. owner_id: user.id, created_by_id: user.id, updated_by_id: user.id)
  749. end
  750. shared_examples 'successful trigger' do |attribute:|
  751. let(:attribute) { attribute }
  752. let(:condition) do
  753. { attribute => { operator: 'is', pre_condition: 'current_user.id', value: '', value_completion: '' } }
  754. end
  755. it "for #{attribute}" do
  756. ticket && trigger
  757. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  758. end
  759. end
  760. it_behaves_like 'successful trigger', attribute: 'ticket.updated_by_id'
  761. it_behaves_like 'successful trigger', attribute: 'ticket.owner_id'
  762. end
  763. describe 'Multi-trigger interactions:' do
  764. let(:ticket) { create(:ticket) }
  765. context 'cascading (i.e., trigger A satisfies trigger B satisfies trigger C)' do
  766. subject!(:triggers) do
  767. [
  768. create(:trigger, condition: initial_state, perform: first_change, name: 'A'),
  769. create(:trigger, condition: first_change, perform: second_change, name: 'B'),
  770. create(:trigger, condition: second_change, perform: third_change, name: 'C')
  771. ]
  772. end
  773. context 'in a chain' do
  774. let(:initial_state) do
  775. {
  776. 'ticket.state_id' => {
  777. 'operator' => 'is',
  778. 'value' => Ticket::State.lookup(name: 'new').id.to_s,
  779. }
  780. }
  781. end
  782. let(:first_change) do
  783. {
  784. 'ticket.state_id' => {
  785. 'operator' => 'is',
  786. 'value' => Ticket::State.lookup(name: 'open').id.to_s,
  787. }
  788. }
  789. end
  790. let(:second_change) do
  791. {
  792. 'ticket.state_id' => {
  793. 'operator' => 'is',
  794. 'value' => Ticket::State.lookup(name: 'closed').id.to_s,
  795. }
  796. }
  797. end
  798. let(:third_change) do
  799. {
  800. 'ticket.state_id' => {
  801. 'operator' => 'is',
  802. 'value' => Ticket::State.lookup(name: 'merged').id.to_s,
  803. }
  804. }
  805. end
  806. context 'in alphabetical order (by name)' do
  807. it 'fires all triggers in sequence' do
  808. expect { TransactionDispatcher.commit }
  809. .to change { ticket.reload.state.name }.to('merged')
  810. end
  811. end
  812. context 'out of alphabetical order (by name)' do
  813. before do
  814. triggers.first.update(name: 'E')
  815. triggers.second.update(name: 'F')
  816. triggers.third.update(name: 'D')
  817. end
  818. context 'with Setting ticket_trigger_recursive: true' do
  819. before { Setting.set('ticket_trigger_recursive', true) }
  820. it 'evaluates triggers in sequence, then loops back to the start and re-evalutes skipped triggers' do
  821. expect { TransactionDispatcher.commit }
  822. .to change { ticket.reload.state.name }.to('merged')
  823. end
  824. end
  825. context 'with Setting ticket_trigger_recursive: false' do
  826. before { Setting.set('ticket_trigger_recursive', false) }
  827. it 'evaluates triggers in sequence, firing only the ones that match' do
  828. expect { TransactionDispatcher.commit }
  829. .to change { ticket.reload.state.name }.to('closed')
  830. end
  831. end
  832. end
  833. end
  834. context 'in circular reference (i.e., trigger A satisfies trigger B satisfies trigger C satisfies trigger A...)' do
  835. let(:initial_state) do
  836. {
  837. 'ticket.priority_id' => {
  838. 'operator' => 'is',
  839. 'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
  840. }
  841. }
  842. end
  843. let(:first_change) do
  844. {
  845. 'ticket.priority_id' => {
  846. 'operator' => 'is',
  847. 'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
  848. }
  849. }
  850. end
  851. let(:second_change) do
  852. {
  853. 'ticket.priority_id' => {
  854. 'operator' => 'is',
  855. 'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
  856. }
  857. }
  858. end
  859. let(:third_change) do
  860. {
  861. 'ticket.priority_id' => {
  862. 'operator' => 'is',
  863. 'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
  864. }
  865. }
  866. end
  867. context 'with Setting ticket_trigger_recursive: true' do
  868. before { Setting.set('ticket_trigger_recursive', true) }
  869. it 'fires each trigger once, without being caught in an endless loop' do
  870. expect { Timeout.timeout(2) { TransactionDispatcher.commit } }
  871. .to not_change { ticket.reload.priority.name }
  872. .and not_raise_error
  873. end
  874. end
  875. context 'with Setting ticket_trigger_recursive: false' do
  876. before { Setting.set('ticket_trigger_recursive', false) }
  877. it 'fires each trigger once, without being caught in an endless loop' do
  878. expect { Timeout.timeout(2) { TransactionDispatcher.commit } }
  879. .to not_change { ticket.reload.priority.name }
  880. .and not_raise_error
  881. end
  882. end
  883. end
  884. end
  885. context 'competing (i.e., trigger A un-satisfies trigger B)' do
  886. subject!(:triggers) do
  887. [
  888. create(:trigger, condition: initial_state, perform: change_a, name: 'A'),
  889. create(:trigger, condition: initial_state, perform: change_b, name: 'B')
  890. ]
  891. end
  892. let(:initial_state) do
  893. {
  894. 'ticket.state_id' => {
  895. 'operator' => 'is',
  896. 'value' => Ticket::State.lookup(name: 'new').id.to_s,
  897. }
  898. }
  899. end
  900. let(:change_a) do
  901. {
  902. 'ticket.state_id' => {
  903. 'operator' => 'is',
  904. 'value' => Ticket::State.lookup(name: 'open').id.to_s,
  905. }
  906. }
  907. end
  908. let(:change_b) do
  909. {
  910. 'ticket.priority_id' => {
  911. 'operator' => 'is',
  912. 'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
  913. }
  914. }
  915. end
  916. it 'evaluates triggers in sequence, firing only the ones that match' do
  917. expect { TransactionDispatcher.commit }
  918. .to change { ticket.reload.state.name }.to('open')
  919. .and not_change { ticket.reload.priority.name }
  920. end
  921. end
  922. end
  923. describe 'multiselect triggers', db_strategy: :reset, mariadb: true do
  924. let(:attribute_name) { 'multiselect' }
  925. let(:condition) do
  926. { "ticket.#{attribute_name}" => { 'operator' => operator, 'value' => trigger_values } }
  927. end
  928. let(:perform) do
  929. { 'article.note' => { 'subject' => 'Test subject note', 'internal' => 'true', 'body' => 'Test body note' } }
  930. end
  931. before do
  932. create(:object_manager_attribute_multiselect, name: attribute_name)
  933. ObjectManager::Attribute.migration_execute
  934. described_class.destroy_all # Default DB state includes three sample triggers
  935. trigger # create subject trigger
  936. end
  937. context 'when ticket is updated with a multiselect trigger condition', authenticated_as: :owner, db_strategy: :reset do
  938. let(:options) do
  939. {
  940. a: 'a',
  941. b: 'b',
  942. c: 'c',
  943. d: 'd',
  944. e: 'e',
  945. }
  946. end
  947. let(:trigger_values) { %w[a b c] }
  948. let(:group) { create(:group) }
  949. let(:owner) { create(:admin, group_ids: [group.id]) }
  950. let!(:ticket) { create(:ticket, group: group,) }
  951. before do
  952. ticket.update_attribute(attribute_name, ticket_multiselect_values)
  953. end
  954. shared_examples 'updating the ticket with the trigger condition' do
  955. it 'updates the ticket with the trigger condition' do
  956. expect { TransactionDispatcher.commit }
  957. .to change(Ticket::Article, :count).by(1)
  958. end
  959. end
  960. shared_examples 'not updating the ticket with the trigger condition' do
  961. it 'does not update the ticket with the trigger condition' do
  962. expect { TransactionDispatcher.commit }
  963. .to not_change(Ticket::Article, :count)
  964. end
  965. end
  966. context "with 'contains all' used" do
  967. let(:operator) { 'contains all' }
  968. context 'when updated value is the same with trigger value' do
  969. let(:ticket_multiselect_values) { trigger_values }
  970. it_behaves_like 'updating the ticket with the trigger condition'
  971. end
  972. context 'when updated value is different from the trigger value' do
  973. let(:ticket_multiselect_values) { options.values - trigger_values }
  974. it_behaves_like 'not updating the ticket with the trigger condition'
  975. end
  976. context 'when no value is selected' do
  977. let(:ticket_multiselect_values) { ['-'] }
  978. it_behaves_like 'not updating the ticket with the trigger condition'
  979. end
  980. context 'when all value is selected' do
  981. let(:ticket_multiselect_values) { options.values }
  982. it_behaves_like 'updating the ticket with the trigger condition'
  983. end
  984. context 'when updated value contains one of the trigger value' do
  985. let(:ticket_multiselect_values) { [trigger_values.first] }
  986. it_behaves_like 'not updating the ticket with the trigger condition'
  987. end
  988. context 'when updated value does not contain one of the trigger value' do
  989. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  990. it_behaves_like 'not updating the ticket with the trigger condition'
  991. end
  992. end
  993. context "with 'contains one' used" do
  994. let(:operator) { 'contains one' }
  995. context 'when updated value is the same with trigger value' do
  996. let(:ticket_multiselect_values) { trigger_values }
  997. it_behaves_like 'updating the ticket with the trigger condition'
  998. end
  999. context 'when updated value is different from the trigger value' do
  1000. let(:ticket_multiselect_values) { options.values - trigger_values }
  1001. it_behaves_like 'not updating the ticket with the trigger condition'
  1002. end
  1003. context 'when no value is selected' do
  1004. let(:ticket_multiselect_values) { ['-'] }
  1005. it_behaves_like 'not updating the ticket with the trigger condition'
  1006. end
  1007. context 'when all value is selected' do
  1008. let(:ticket_multiselect_values) { options.values }
  1009. it_behaves_like 'updating the ticket with the trigger condition'
  1010. end
  1011. context 'when updated value contains only one of the trigger value' do
  1012. let(:ticket_multiselect_values) { [trigger_values.first] }
  1013. it_behaves_like 'updating the ticket with the trigger condition'
  1014. end
  1015. context 'when updated value does not contain one of the trigger value' do
  1016. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  1017. it_behaves_like 'updating the ticket with the trigger condition'
  1018. end
  1019. end
  1020. context "with 'contains all not' used" do
  1021. let(:operator) { 'contains all not' }
  1022. context 'when updated value is the same with trigger value' do
  1023. let(:ticket_multiselect_values) { trigger_values }
  1024. it_behaves_like 'not updating the ticket with the trigger condition'
  1025. end
  1026. context 'when updated value is different from the trigger value' do
  1027. let(:ticket_multiselect_values) { options.values - trigger_values }
  1028. it_behaves_like 'updating the ticket with the trigger condition'
  1029. end
  1030. context 'when no value is selected' do
  1031. let(:ticket_multiselect_values) { ['-'] }
  1032. it_behaves_like 'updating the ticket with the trigger condition'
  1033. end
  1034. context 'when all value is selected' do
  1035. let(:ticket_multiselect_values) { options.values }
  1036. it_behaves_like 'not updating the ticket with the trigger condition'
  1037. end
  1038. context 'when updated value contains only one of the trigger value' do
  1039. let(:ticket_multiselect_values) { [trigger_values.first] }
  1040. it_behaves_like 'updating the ticket with the trigger condition'
  1041. end
  1042. context 'when updated value does not contain one of the trigger value' do
  1043. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  1044. it_behaves_like 'updating the ticket with the trigger condition'
  1045. end
  1046. end
  1047. context "with 'contains one not' used" do
  1048. let(:operator) { 'contains one not' }
  1049. context 'when updated value is the same with trigger value' do
  1050. let(:ticket_multiselect_values) { trigger_values }
  1051. it_behaves_like 'not updating the ticket with the trigger condition'
  1052. end
  1053. context 'when updated value is different from the trigger value' do
  1054. let(:ticket_multiselect_values) { options.values - trigger_values }
  1055. it_behaves_like 'updating the ticket with the trigger condition'
  1056. end
  1057. context 'when no value is selected' do
  1058. let(:ticket_multiselect_values) { ['-'] }
  1059. it_behaves_like 'updating the ticket with the trigger condition'
  1060. end
  1061. context 'when all value is selected' do
  1062. let(:ticket_multiselect_values) { options.values }
  1063. it_behaves_like 'not updating the ticket with the trigger condition'
  1064. end
  1065. context 'when updated value contains only one of the trigger value' do
  1066. let(:ticket_multiselect_values) { [trigger_values.first] }
  1067. it_behaves_like 'not updating the ticket with the trigger condition'
  1068. end
  1069. context 'when updated value does not contain one of the trigger value' do
  1070. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  1071. it_behaves_like 'not updating the ticket with the trigger condition'
  1072. end
  1073. end
  1074. end
  1075. end
  1076. describe 'Triggers without configured action inside condition are executed differently compared to 5.3 #4550' do
  1077. let(:ticket_match) { create(:ticket, group: Group.first) }
  1078. let(:ticket_no_match) { create(:ticket, group: Group.first, priority: Ticket::Priority.find_by(name: '1 low')) }
  1079. let(:condition) do
  1080. { 'ticket.priority_id' => { 'operator' => 'is', 'value' => Ticket::Priority.where(name: ['2 normal', '3 high']).pluck(:id).map(&:to_s) } }
  1081. end
  1082. let(:perform) do
  1083. { 'article.note' => { 'subject' => 'Test subject note', 'internal' => 'true', 'body' => 'Test body note' } }
  1084. end
  1085. shared_examples 'executing trigger when conditions match' do |execution_condition_mode:|
  1086. it 'does not create an article if the state changes', if: execution_condition_mode == 'selective' do
  1087. ticket_match.update(state: Ticket::State.find_by(name: 'closed'))
  1088. expect { TransactionDispatcher.commit }.not_to change(Ticket::Article, :count)
  1089. end
  1090. it 'does create an article if the state changes', if: execution_condition_mode == 'always' do
  1091. ticket_match.update(state: Ticket::State.find_by(name: 'closed'))
  1092. expect { TransactionDispatcher.commit }.to change(Ticket::Article, :count)
  1093. end
  1094. it 'does create an article if priority changes' do
  1095. ticket_match.update(priority: Ticket::Priority.find_by(name: '3 high'))
  1096. expect { TransactionDispatcher.commit }.to change(Ticket::Article, :count).by(1)
  1097. end
  1098. it 'does create an article if priority matches and new article is created' do
  1099. create(:ticket_article, ticket: ticket_match)
  1100. expect { TransactionDispatcher.commit }.to change(Ticket::Article, :count).by(1)
  1101. end
  1102. end
  1103. shared_examples "not executing trigger when conditions don't match" do
  1104. it 'does not create an article if priority does not match but new article is created' do
  1105. create(:ticket_article, ticket: ticket_no_match)
  1106. expect { TransactionDispatcher.commit }.not_to change(Ticket::Article, :count)
  1107. end
  1108. it 'does not create an article if priority does not match and priority changes to low' do
  1109. ticket_match.update(priority: Ticket::Priority.find_by(name: '1 low'))
  1110. expect { TransactionDispatcher.commit }.not_to change(Ticket::Article, :count)
  1111. end
  1112. end
  1113. before do
  1114. ticket_match
  1115. ticket_no_match
  1116. trigger
  1117. TransactionDispatcher.commit
  1118. end
  1119. context "with execution condition mode 'selective'" do
  1120. it_behaves_like 'executing trigger when conditions match', execution_condition_mode: 'selective'
  1121. it_behaves_like "not executing trigger when conditions don't match"
  1122. end
  1123. context "with execution condition mode 'always'" do
  1124. let(:execution_condition_mode) { 'always' }
  1125. it_behaves_like 'executing trigger when conditions match', execution_condition_mode: 'always'
  1126. it_behaves_like "not executing trigger when conditions don't match"
  1127. end
  1128. end
  1129. context 'when time events are reached', time_zone: 'Europe/London' do
  1130. let(:activator) { 'time' }
  1131. let(:perform) { { 'ticket.title' => { 'value' => 'triggered' } } }
  1132. let(:ticket) { create(:ticket, title: 'Test Ticket', state_name: state_name, pending_time: pending_time) }
  1133. shared_examples 'getting triggered' do |attribute:, operator:, with_pending_time: false, with_escalation: false|
  1134. let(:attribute) { attribute }
  1135. let(:condition) { { attribute => { operator: operator } } }
  1136. let(:state_name) { 'pending reminder' if with_pending_time }
  1137. let(:pending_time) { 1.hour.ago if with_pending_time }
  1138. let(:calendar) { create(:calendar, :'24/7') }
  1139. let(:sla) { create(:sla, :condition_blank, solution_time: 10, calendar: calendar) }
  1140. before do
  1141. sla if with_escalation
  1142. ticket && trigger
  1143. travel 1.hour if with_escalation
  1144. end
  1145. it "gets triggered for attribute: #{attribute}, operator: #{operator}" do
  1146. expect { perform_job(attribute, operator) }
  1147. .to change { ticket.reload.title }
  1148. .to('triggered')
  1149. end
  1150. def job_type(attribute, operator)
  1151. case [attribute, operator]
  1152. in 'ticket.pending_time', _
  1153. 'reminder_reached'
  1154. in 'ticket.escalation_at', 'has reached'
  1155. 'escalation'
  1156. in 'ticket.escalation_at', 'has reached warning'
  1157. 'escalation_warning'
  1158. end
  1159. end
  1160. def perform_job(...)
  1161. TransactionJob.perform_now(
  1162. object: 'Ticket',
  1163. type: job_type(...),
  1164. object_id: ticket.id,
  1165. article_id: nil,
  1166. user_id: 1,
  1167. )
  1168. end
  1169. end
  1170. it_behaves_like 'getting triggered', attribute: 'ticket.pending_time', operator: 'has reached', with_pending_time: true
  1171. it_behaves_like 'getting triggered', attribute: 'ticket.escalation_at', operator: 'has reached', with_escalation: true
  1172. it_behaves_like 'getting triggered', attribute: 'ticket.escalation_at', operator: 'has reached warning', with_escalation: true
  1173. end
  1174. context 'when aticle action is set' do
  1175. let(:activator) { 'action' }
  1176. let(:perform) { { 'ticket.title' => { 'value' => 'triggered' } } }
  1177. let(:ticket) { create(:ticket, title: 'Test Ticket') }
  1178. let(:article) { create(:ticket_article, ticket: ticket) }
  1179. shared_examples 'getting triggered' do |triggered:, operator:, with_article:, type:|
  1180. before do
  1181. ticket && trigger
  1182. article if with_article
  1183. end
  1184. let(:article_id) { with_article ? article.id : nil }
  1185. let(:type) { type }
  1186. let(:condition) do
  1187. { 'article.action' => { 'operator' => operator, 'value' => 'create' } }
  1188. end
  1189. if triggered
  1190. it "gets triggered for article action created operator: #{operator}" do
  1191. expect { TransactionDispatcher.commit }
  1192. .to change { ticket.reload.title }
  1193. .to('triggered')
  1194. end
  1195. else
  1196. it "does not get triggered for article action created operator: #{operator}" do
  1197. expect { TransactionDispatcher.commit }
  1198. .not_to change { ticket.reload.title }
  1199. end
  1200. end
  1201. end
  1202. it_behaves_like 'getting triggered', triggered: true, operator: 'is', with_article: true, type: 'update'
  1203. it_behaves_like 'getting triggered', triggered: false, operator: 'is not', with_article: true, type: 'update'
  1204. it_behaves_like 'getting triggered', triggered: false, operator: 'is', with_article: false, type: 'update'
  1205. it_behaves_like 'getting triggered', triggered: true, operator: 'is not', with_article: false, type: 'update'
  1206. it_behaves_like 'getting triggered', triggered: true, operator: 'is', with_article: true, type: 'create'
  1207. it_behaves_like 'getting triggered', triggered: false, operator: 'is not', with_article: true, type: 'create'
  1208. it_behaves_like 'getting triggered', triggered: false, operator: 'is', with_article: false, type: 'create'
  1209. it_behaves_like 'getting triggered', triggered: true, operator: 'is not', with_article: false, type: 'create'
  1210. end
  1211. describe '#performed_on', current_user_id: 1 do
  1212. let(:ticket) { create(:ticket) }
  1213. before { ticket }
  1214. context 'given action-based trigger' do
  1215. let(:activator) { 'action' }
  1216. it 'does nothing' do
  1217. expect { trigger.performed_on(ticket, activator_type: 'reminder_reached') }
  1218. .not_to change(History, :count)
  1219. end
  1220. end
  1221. context 'given time-based trigger' do
  1222. let(:activator) { 'time' }
  1223. it 'creates history item' do
  1224. expect { trigger.performed_on(ticket, activator_type: 'reminder_reached') }
  1225. .to change(History, :count)
  1226. .by(1)
  1227. end
  1228. end
  1229. end
  1230. describe 'performable_on?', current_user_id: 1 do
  1231. let(:ticket) { create(:ticket) }
  1232. before { ticket }
  1233. context 'given action-based trigger' do
  1234. let(:activator) { 'action' }
  1235. it 'returns nil' do
  1236. expect(trigger.performable_on?(ticket, activator_type: 'reminder_reached'))
  1237. .to be_nil
  1238. end
  1239. end
  1240. context 'given time-based trigger' do
  1241. let(:activator) { 'time' }
  1242. it 'returns true if it was not performed yet' do
  1243. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1244. end
  1245. it 'returns true if it was performed yesterday' do
  1246. travel(-1.day) do
  1247. trigger.performed_on(ticket, activator_type: 'reminder_reached')
  1248. end
  1249. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1250. end
  1251. it 'returns true if it was performed today on another ticket' do
  1252. trigger.performed_on(create(:ticket), activator_type: 'reminder_reached')
  1253. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1254. end
  1255. it 'returns true if it was performed today by another activator' do
  1256. trigger.performed_on(ticket, activator_type: 'escalation')
  1257. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1258. end
  1259. it 'returns false if it was performed today on the same ticket by the same activator and same user' do
  1260. trigger.performed_on(ticket, activator_type: 'reminder_reached')
  1261. expect(trigger).not_to be_performable_on(ticket, activator_type: 'reminder_reached')
  1262. end
  1263. end
  1264. end
  1265. describe 'Log Trigger and Scheduler in Ticket History #4604' do
  1266. let(:ticket) { create(:ticket) }
  1267. context 'when title attribute' do
  1268. it 'does create history entries for the trigger' do
  1269. ticket && trigger
  1270. TransactionDispatcher.commit
  1271. expect(History.last).to have_attributes(
  1272. o_id: ticket.id,
  1273. value_to: 'triggered',
  1274. sourceable: trigger
  1275. )
  1276. end
  1277. end
  1278. context 'when group associated attribute' do
  1279. let(:group) { create(:group) }
  1280. let(:perform) do
  1281. { 'ticket.group_id'=>{ 'value'=> group.id.to_s } }
  1282. end
  1283. it 'does create history entries with source information' do
  1284. ticket && trigger
  1285. TransactionDispatcher.commit
  1286. expect(History.last).to have_attributes(
  1287. o_id: ticket.id,
  1288. value_to: group.name,
  1289. sourceable: trigger
  1290. )
  1291. end
  1292. end
  1293. context 'when internal note article' do
  1294. let(:perform) do
  1295. { 'article.note' => { 'subject' => 'Test subject note', 'internal' => 'true', 'body' => 'Test body note' } }
  1296. end
  1297. it 'does create history entries with source information' do
  1298. ticket && trigger
  1299. TransactionDispatcher.commit
  1300. expect(History.last).to have_attributes(
  1301. o_id: Ticket::Article.last.id,
  1302. related_o_id: ticket.id,
  1303. sourceable: trigger
  1304. )
  1305. end
  1306. end
  1307. context 'when email notification article' do
  1308. let(:perform) do
  1309. {
  1310. 'notification.email' => {
  1311. 'recipient' => 'ticket_customer',
  1312. 'subject' => 'foo',
  1313. 'body' => 'some body with &gt;snip&lt;#{article.body_as_html}&gt;/snip&lt;', # rubocop:disable Lint/InterpolationCheck
  1314. }
  1315. }
  1316. end
  1317. it 'does create history entries with source information' do
  1318. ticket && trigger
  1319. TransactionDispatcher.commit
  1320. expect(History.last).to have_attributes(
  1321. o_id: Ticket::Article.last.id,
  1322. related_o_id: ticket.id,
  1323. sourceable: trigger
  1324. )
  1325. end
  1326. end
  1327. context 'when tags are added' do
  1328. let(:tag) { SecureRandom.uuid }
  1329. let(:perform) do
  1330. { 'ticket.tags'=>{ 'operator' => 'add', 'value' => tag } }
  1331. end
  1332. it 'does create history entries with source information' do
  1333. ticket && trigger
  1334. TransactionDispatcher.commit
  1335. expect(History.last).to have_attributes(
  1336. history_type_id: History::Type.find_by(name: 'added').id,
  1337. o_id: ticket.id,
  1338. sourceable: trigger,
  1339. value_to: tag,
  1340. )
  1341. end
  1342. end
  1343. context 'when tags are removed' do
  1344. let(:tag) { SecureRandom.uuid }
  1345. let(:perform) do
  1346. { 'ticket.tags'=>{ 'operator' => 'remove', 'value' => tag } }
  1347. end
  1348. it 'does create history entries with source information' do
  1349. ticket&.tag_add(tag, 1) && trigger
  1350. TransactionDispatcher.commit
  1351. expect(History.last).to have_attributes(
  1352. history_type_id: History::Type.find_by(name: 'removed').id,
  1353. o_id: ticket.id,
  1354. sourceable: trigger,
  1355. value_to: tag,
  1356. )
  1357. end
  1358. end
  1359. end
  1360. describe 'Trigger fails to set custom timestamp on report #4677', db_strategy: :reset do
  1361. let(:field_name) { SecureRandom.uuid }
  1362. let(:ticket) { create(:ticket) }
  1363. let(:perform_static) do
  1364. { "ticket.#{field_name}" => { 'operator' => 'static', 'value' => '2023-07-18T06:00:00.000Z' } }
  1365. end
  1366. let(:perform_relative) do
  1367. { "ticket.#{field_name}"=>{ 'operator' => 'relative', 'value' => '1', 'range' => 'day' } }
  1368. end
  1369. before do
  1370. travel_to DateTime.new 2023, 0o7, 13, 10, 0o0
  1371. end
  1372. context 'when datetime' do
  1373. before do
  1374. create(:object_manager_attribute_datetime, object_name: 'Ticket', name: field_name, display: field_name)
  1375. ObjectManager::Attribute.migration_execute
  1376. end
  1377. context 'when static' do
  1378. let(:perform) { perform_static }
  1379. it 'does set the value' do
  1380. ticket && trigger
  1381. TransactionDispatcher.commit
  1382. expect(ticket.reload[field_name]).to eq(Time.zone.parse('2023-07-18T06:00:00.000Z'))
  1383. end
  1384. end
  1385. context 'when relative' do
  1386. let(:perform) { perform_relative }
  1387. it 'does set the value' do
  1388. ticket && trigger
  1389. TransactionDispatcher.commit
  1390. expect(ticket.reload[field_name]).to eq(1.day.from_now)
  1391. end
  1392. end
  1393. end
  1394. context 'when date' do
  1395. before do
  1396. create(:object_manager_attribute_date, object_name: 'Ticket', name: field_name, display: field_name)
  1397. ObjectManager::Attribute.migration_execute
  1398. end
  1399. context 'when static' do
  1400. let(:perform) { perform_static }
  1401. it 'does set the value' do
  1402. ticket && trigger
  1403. TransactionDispatcher.commit
  1404. expect(ticket.reload[field_name]).to eq(Time.zone.parse('2023-07-18'))
  1405. end
  1406. end
  1407. context 'when relative' do
  1408. let(:perform) { perform_relative }
  1409. it 'does set the value' do
  1410. ticket && trigger
  1411. TransactionDispatcher.commit
  1412. expect(ticket.reload[field_name]).to eq(1.day.from_now.to_date)
  1413. end
  1414. end
  1415. end
  1416. end
  1417. describe 'Trigger with new regular expression operators' do
  1418. let(:execution_condition_mode) { 'always' }
  1419. before do
  1420. ticket_match && ticket_no_match && trigger
  1421. TransactionDispatcher.commit
  1422. end
  1423. context 'when the title is used in the conditions' do
  1424. let(:perform) do
  1425. { 'ticket.title'=> { 'value'=> 'Changed by trigger' } }
  1426. end
  1427. context 'when the operator is "matches regex"' do
  1428. let(:ticket_match) { create(:ticket, title: 'Welcome to Zammad') }
  1429. let(:ticket_no_match) { create(:ticket, title: 'Spam') }
  1430. let(:condition) do
  1431. { 'ticket.title' => { operator: 'matches regex', value: '^welcome' } }
  1432. end
  1433. it 'does execute the trigger and perform changes', :aggregate_failures do
  1434. expect(ticket_match.reload.title).to eq('Changed by trigger')
  1435. expect(ticket_no_match.reload.title).to eq('Spam')
  1436. end
  1437. end
  1438. context 'when the operator is "does not match regex"' do
  1439. let(:ticket_no_match) { create(:ticket, title: 'Welcome to Zammad') }
  1440. let(:ticket_match) { create(:ticket, title: 'Spam') }
  1441. let(:condition) do
  1442. { 'ticket.title' => { operator: 'does not match regex', value: '^welcome' } }
  1443. end
  1444. it 'does not execute the trigger and perform no changes', :aggregate_failures do
  1445. expect(ticket_no_match.reload.title).to eq('Welcome to Zammad')
  1446. expect(ticket_match.reload.title).to eq('Changed by trigger')
  1447. end
  1448. end
  1449. end
  1450. end
  1451. describe 'Extend trigger conditions with an article accounted time entry flag #4760' do
  1452. let!(:ticket) { create(:ticket) }
  1453. before do
  1454. ticket && article && trigger
  1455. end
  1456. context 'when time accounting is present' do
  1457. let!(:article) { create(:ticket_time_accounting, :for_article, ticket: ticket) }
  1458. context 'with is set' do
  1459. let(:condition) do
  1460. { 'article.time_accounting'=> { 'operator' => 'is set' } }
  1461. end
  1462. it 'does trigger' do
  1463. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  1464. end
  1465. end
  1466. context 'with not set' do
  1467. let(:condition) do
  1468. { 'article.time_accounting'=> { 'operator' => 'not set' } }
  1469. end
  1470. it 'does not trigger' do
  1471. expect { TransactionDispatcher.commit }.to not_change { ticket.reload.title }
  1472. end
  1473. end
  1474. end
  1475. context 'when time accounting is blank' do
  1476. let!(:article) { create(:ticket_article, ticket: ticket) }
  1477. context 'with is set' do
  1478. let(:condition) do
  1479. { 'article.time_accounting'=> { 'operator' => 'is set' } }
  1480. end
  1481. it 'does trigger' do
  1482. expect { TransactionDispatcher.commit }.to not_change { ticket.reload.title }
  1483. end
  1484. end
  1485. context 'with not set' do
  1486. let(:condition) do
  1487. { 'article.time_accounting'=> { 'operator' => 'not set' } }
  1488. end
  1489. it 'does not trigger' do
  1490. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  1491. end
  1492. end
  1493. end
  1494. end
  1495. end