trigger_spec.rb 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137
  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/has_xss_sanitized_note_examples'
  5. RSpec.describe Trigger, type: :model do
  6. subject(:trigger) { create(:trigger, condition: condition, perform: perform) }
  7. it_behaves_like 'ApplicationModel', can_assets: { selectors: %i[condition perform] }
  8. it_behaves_like 'HasXssSanitizedNote', model_factory: :trigger
  9. describe 'validation' do
  10. it 'uses Validations::VerifyPerformRulesValidator' do
  11. expect(described_class).to have_validator(Validations::VerifyPerformRulesValidator).on(:perform)
  12. end
  13. end
  14. describe 'Send-email triggers' do
  15. before do
  16. described_class.destroy_all # Default DB state includes three sample triggers
  17. trigger # create subject trigger
  18. end
  19. let(:perform) do
  20. {
  21. 'notification.email' => {
  22. 'recipient' => 'ticket_customer',
  23. 'subject' => 'foo',
  24. 'body' => 'some body with >snip<#{article.body_as_html}>/snip<', # rubocop:disable Lint/InterpolationCheck
  25. }
  26. }
  27. end
  28. shared_examples 'include ticket attachment' do
  29. context 'notification.email include_attachments' do
  30. let(:perform) do
  31. {
  32. 'notification.email' => {
  33. 'recipient' => 'ticket_customer',
  34. 'subject' => 'Example subject',
  35. 'body' => 'Example body',
  36. }
  37. }.deep_merge(additional_options).deep_stringify_keys
  38. end
  39. let(:ticket) { create(:ticket) }
  40. shared_examples 'add a new article' do
  41. it 'adds a new article' do
  42. expect { TransactionDispatcher.commit }
  43. .to change(ticket.articles, :count).by(1)
  44. end
  45. end
  46. shared_examples 'add attachment to new article' do
  47. include_examples 'add a new article'
  48. it 'adds attachment to the new article' do
  49. ticket && trigger
  50. TransactionDispatcher.commit
  51. article = ticket.articles.last
  52. expect(article.type.name).to eq('email')
  53. expect(article.sender.name).to eq('System')
  54. expect(article.attachments.count).to eq(1)
  55. expect(article.attachments[0].filename).to eq('some_file.pdf')
  56. expect(article.attachments[0].preferences['Content-ID']).to eq('image/pdf@01CAB192.K8H512Y9')
  57. end
  58. end
  59. shared_examples 'does not add attachment to new article' do
  60. include_examples 'add a new article'
  61. it 'does not add attachment to the new article' do
  62. ticket && trigger
  63. TransactionDispatcher.commit
  64. article = ticket.articles.last
  65. expect(article.type.name).to eq('email')
  66. expect(article.sender.name).to eq('System')
  67. expect(article.attachments.count).to eq(0)
  68. end
  69. end
  70. context 'with include attachment present' do
  71. let(:additional_options) do
  72. {
  73. 'notification.email' => {
  74. include_attachments: 'true'
  75. }
  76. }
  77. end
  78. context 'when ticket has an attachment' do
  79. before do
  80. UserInfo.current_user_id = 1
  81. ticket_article = create(:ticket_article, ticket: ticket)
  82. create(:store,
  83. object: 'Ticket::Article',
  84. o_id: ticket_article.id,
  85. data: 'dGVzdCAxMjM=',
  86. filename: 'some_file.pdf',
  87. preferences: {
  88. 'Content-Type': 'image/pdf',
  89. 'Content-ID': 'image/pdf@01CAB192.K8H512Y9',
  90. })
  91. end
  92. include_examples 'add attachment to new article'
  93. end
  94. context 'when ticket does not have an attachment' do
  95. include_examples 'does not add attachment to new article'
  96. end
  97. end
  98. context 'with include attachment not present' do
  99. let(:additional_options) do
  100. {
  101. 'notification.email' => {
  102. include_attachments: 'false'
  103. }
  104. }
  105. end
  106. context 'when ticket has an attachment' do
  107. before do
  108. UserInfo.current_user_id = 1
  109. ticket_article = create(:ticket_article, ticket: ticket)
  110. create(:store,
  111. object: 'Ticket::Article',
  112. o_id: ticket_article.id,
  113. data: 'dGVzdCAxMjM=',
  114. filename: 'some_file.pdf',
  115. preferences: {
  116. 'Content-Type': 'image/pdf',
  117. 'Content-ID': 'image/pdf@01CAB192.K8H512Y9',
  118. })
  119. end
  120. include_examples 'does not add attachment to new article'
  121. end
  122. context 'when ticket does not have an attachment' do
  123. include_examples 'does not add attachment to new article'
  124. end
  125. end
  126. end
  127. end
  128. context 'for condition "ticket created"' do
  129. let(:condition) do
  130. { 'ticket.action' => { 'operator' => 'is', 'value' => 'create' } }
  131. end
  132. context 'when ticket is created directly' do
  133. let!(:ticket) { create(:ticket) }
  134. it 'fires (without altering ticket state)' do
  135. expect { TransactionDispatcher.commit }
  136. .to change(Ticket::Article, :count).by(1)
  137. .and not_change { ticket.reload.state.name }.from('new')
  138. end
  139. end
  140. context 'when ticket has tags' do
  141. let(:tag1) { create(:'tag/item', name: 't1') }
  142. let(:tag2) { create(:'tag/item', name: 't2') }
  143. let(:tag3) { create(:'tag/item', name: 't3') }
  144. let!(:ticket) do
  145. ticket = create(:ticket)
  146. create(:tag, o: ticket, tag_item: tag1)
  147. create(:tag, o: ticket, tag_item: tag2)
  148. create(:tag, o: ticket, tag_item: tag3)
  149. ticket
  150. end
  151. let(:perform) do
  152. {
  153. 'notification.email' => {
  154. 'recipient' => 'ticket_customer',
  155. 'subject' => 'foo',
  156. 'body' => 'some body with #{ticket.tags}', # rubocop:disable Lint/InterpolationCheck
  157. }
  158. }
  159. end
  160. it 'fires body with replaced tags' do
  161. TransactionDispatcher.commit
  162. expect(Ticket::Article.last.body).to eq('some body with t1, t2, t3')
  163. end
  164. end
  165. context 'when ticket is created via Channel::EmailParser.process' do
  166. before { create(:email_address, groups: [Group.first]) }
  167. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail001.box')) }
  168. it 'fires (without altering ticket state)' do
  169. expect { Channel::EmailParser.new.process({}, raw_email) }
  170. .to change(Ticket, :count).by(1)
  171. .and change(Ticket::Article, :count).by(2)
  172. expect(Ticket.last.state.name).to eq('new')
  173. end
  174. end
  175. context 'when ticket is created via Channel::EmailParser.process with inline image' do
  176. before { create(:email_address, groups: [Group.first]) }
  177. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail010.box')) }
  178. it 'fires (without altering ticket state)' do
  179. expect { Channel::EmailParser.new.process({}, raw_email) }
  180. .to change(Ticket, :count).by(1)
  181. .and change(Ticket::Article, :count).by(2)
  182. expect(Ticket.last.state.name).to eq('new')
  183. article = Ticket::Article.last
  184. expect(article.type.name).to eq('email')
  185. expect(article.sender.name).to eq('System')
  186. expect(article.attachments.count).to eq(1)
  187. expect(article.attachments[0].filename).to eq('image001.jpg')
  188. expect(article.attachments[0].preferences['Content-ID']).to eq('image001.jpg@01CDB132.D8A510F0')
  189. expect(article.body).to eq(<<~RAW.chomp
  190. some body with &gt;snip&lt;<div>
  191. <p>Herzliche Grüße aus Oberalteich sendet Herrn Smith</p>
  192. <p> </p>
  193. <p>Sepp Smith - Dipl.Ing. agr. (FH)</p>
  194. <p>Geschäftsführer der example Straubing-Bogen</p>
  195. <p>Klosterhof 1 | 94327 Bogen-Oberalteich</p>
  196. <p>Tel: 09422-505601 | Fax: 09422-505620</p>
  197. <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>
  198. <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>
  199. <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>
  200. <p><span lang="EN-US"><p> </p></span></p>
  201. </div>&gt;/snip&lt;
  202. RAW
  203. )
  204. end
  205. end
  206. context 'notification.email recipient' do
  207. let!(:ticket) { create(:ticket) }
  208. let!(:recipient1) { create(:user, email: 'test1@zammad-test.com') }
  209. let!(:recipient2) { create(:user, email: 'test2@zammad-test.com') }
  210. let!(:recipient3) { create(:user, email: 'test3@zammad-test.com') }
  211. let(:perform) do
  212. {
  213. 'notification.email' => {
  214. 'recipient' => recipient,
  215. 'subject' => 'Hello',
  216. 'body' => 'World!'
  217. }
  218. }
  219. end
  220. before { TransactionDispatcher.commit }
  221. context 'mix of recipient group keyword and single recipient users' do
  222. let(:recipient) { [ 'ticket_customer', "userid_#{recipient1.id}", "userid_#{recipient2.id}", "userid_#{recipient3.id}" ] }
  223. it 'contains all recipients' do
  224. expect(ticket.articles.last.to).to eq("#{ticket.customer.email}, #{recipient1.email}, #{recipient2.email}, #{recipient3.email}")
  225. end
  226. context 'duplicate recipient' do
  227. let(:recipient) { [ 'ticket_customer', "userid_#{ticket.customer.id}" ] }
  228. it 'contains only one recipient' do
  229. expect(ticket.articles.last.to).to eq(ticket.customer.email.to_s)
  230. end
  231. end
  232. end
  233. context 'list of single users only' do
  234. let(:recipient) { [ "userid_#{recipient1.id}", "userid_#{recipient2.id}", "userid_#{recipient3.id}" ] }
  235. it 'contains all recipients' do
  236. expect(ticket.articles.last.to).to eq("#{recipient1.email}, #{recipient2.email}, #{recipient3.email}")
  237. end
  238. context 'assets' do
  239. it 'resolves Users from recipient list' do
  240. expect(trigger.assets({})[:User].keys).to include(recipient1.id, recipient2.id, recipient3.id)
  241. end
  242. context 'single entry' do
  243. let(:recipient) { "userid_#{recipient1.id}" }
  244. it 'resolves User from recipient list' do
  245. expect(trigger.assets({})[:User].keys).to include(recipient1.id)
  246. end
  247. end
  248. end
  249. end
  250. context 'recipient group keyword only' do
  251. let(:recipient) { 'ticket_customer' }
  252. it 'contains matching recipient' do
  253. expect(ticket.articles.last.to).to eq(ticket.customer.email.to_s)
  254. end
  255. end
  256. end
  257. context 'active S/MIME integration' do
  258. before do
  259. Setting.set('smime_integration', true)
  260. create(:smime_certificate, :with_private, fixture: system_email_address)
  261. create(:smime_certificate, fixture: customer_email_address)
  262. end
  263. let(:system_email_address) { 'smime1@example.com' }
  264. let(:customer_email_address) { 'smime2@example.com' }
  265. let(:email_address) { create(:email_address, email: system_email_address) }
  266. let(:group) { create(:group, email_address: email_address) }
  267. let(:customer) { create(:customer, email: customer_email_address) }
  268. let(:security_preferences) { Ticket::Article.last.preferences[:security] }
  269. let(:perform) do
  270. {
  271. 'notification.email' => {
  272. 'recipient' => 'ticket_customer',
  273. 'subject' => 'Subject dummy.',
  274. 'body' => 'Body dummy.',
  275. }.merge(security_configuration)
  276. }
  277. end
  278. let!(:ticket) { create(:ticket, group: group, customer: customer) }
  279. context 'sending articles' do
  280. before do
  281. TransactionDispatcher.commit
  282. end
  283. context 'expired certificate' do
  284. let(:system_email_address) { 'expiredsmime1@example.com' }
  285. let(:security_configuration) do
  286. {
  287. 'sign' => 'always',
  288. 'encryption' => 'always',
  289. }
  290. end
  291. it 'creates unsigned article' do
  292. expect(security_preferences[:sign][:success]).to be false
  293. expect(security_preferences[:encryption][:success]).to be true
  294. end
  295. end
  296. context 'sign and encryption not set' do
  297. let(:security_configuration) { {} }
  298. it 'does not sign or encrypt' do
  299. expect(security_preferences[:sign][:success]).to be false
  300. expect(security_preferences[:encryption][:success]).to be false
  301. end
  302. end
  303. context 'sign and encryption disabled' do
  304. let(:security_configuration) do
  305. {
  306. 'sign' => 'no',
  307. 'encryption' => 'no',
  308. }
  309. end
  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 is enabled' do
  316. let(:security_configuration) do
  317. {
  318. 'sign' => 'always',
  319. 'encryption' => 'no',
  320. }
  321. end
  322. it 'signs' do
  323. expect(security_preferences[:sign][:success]).to be true
  324. expect(security_preferences[:encryption][:success]).to be false
  325. end
  326. end
  327. context 'encryption enabled' do
  328. let(:security_configuration) do
  329. {
  330. 'sign' => 'no',
  331. 'encryption' => 'always',
  332. }
  333. end
  334. it 'encrypts' do
  335. expect(security_preferences[:sign][:success]).to be false
  336. expect(security_preferences[:encryption][:success]).to be true
  337. end
  338. end
  339. context 'sign and encryption enabled' do
  340. let(:security_configuration) do
  341. {
  342. 'sign' => 'always',
  343. 'encryption' => 'always',
  344. }
  345. end
  346. it 'signs and encrypts' do
  347. expect(security_preferences[:sign][:success]).to be true
  348. expect(security_preferences[:encryption][:success]).to be true
  349. end
  350. end
  351. end
  352. context 'discard' do
  353. context 'sign' do
  354. let(:security_configuration) do
  355. {
  356. 'sign' => 'discard',
  357. }
  358. end
  359. context 'group without certificate' do
  360. let(:group) { create(:group) }
  361. it 'does not fire' do
  362. expect { TransactionDispatcher.commit }
  363. .not_to change(Ticket::Article, :count)
  364. end
  365. end
  366. end
  367. context 'encryption' do
  368. let(:security_configuration) do
  369. {
  370. 'encryption' => 'discard',
  371. }
  372. end
  373. context 'customer without certificate' do
  374. let(:customer) { create(:customer) }
  375. it 'does not fire' do
  376. expect { TransactionDispatcher.commit }
  377. .not_to change(Ticket::Article, :count)
  378. end
  379. end
  380. end
  381. context 'mixed' do
  382. context 'sign' do
  383. let(:security_configuration) do
  384. {
  385. 'encryption' => 'always',
  386. 'sign' => 'discard',
  387. }
  388. end
  389. context 'group without certificate' do
  390. let(:group) { create(:group) }
  391. it 'does not fire' do
  392. expect { TransactionDispatcher.commit }
  393. .not_to change(Ticket::Article, :count)
  394. end
  395. end
  396. end
  397. context 'encryption' do
  398. let(:security_configuration) do
  399. {
  400. 'encryption' => 'discard',
  401. 'sign' => 'always',
  402. }
  403. end
  404. context 'customer without certificate' do
  405. let(:customer) { create(:customer) }
  406. it 'does not fire' do
  407. expect { TransactionDispatcher.commit }
  408. .not_to change(Ticket::Article, :count)
  409. end
  410. end
  411. end
  412. end
  413. end
  414. end
  415. include_examples 'include ticket attachment'
  416. end
  417. context 'for condition "ticket updated"' do
  418. let(:condition) do
  419. { 'ticket.action' => { 'operator' => 'is', 'value' => 'update' } }
  420. end
  421. let!(:ticket) { create(:ticket).tap { TransactionDispatcher.commit } }
  422. context 'when new article is created directly' do
  423. context 'with empty #preferences hash' do
  424. let!(:article) { create(:ticket_article, ticket: ticket) }
  425. it 'fires (without altering ticket state)' do
  426. expect { TransactionDispatcher.commit }
  427. .to change { ticket.reload.articles.count }.by(1)
  428. .and not_change { ticket.reload.state.name }.from('new')
  429. end
  430. end
  431. context 'with #preferences { "send-auto-response" => false }' do
  432. let!(:article) do
  433. create(:ticket_article,
  434. ticket: ticket,
  435. preferences: { 'send-auto-response' => false })
  436. end
  437. it 'does not fire' do
  438. expect { TransactionDispatcher.commit }
  439. .not_to change { ticket.reload.articles.count }
  440. end
  441. end
  442. end
  443. context 'when new article is created via Channel::EmailParser.process' do
  444. context 'with a regular message' do
  445. let!(:article) do
  446. create(:ticket_article,
  447. ticket: ticket,
  448. message_id: raw_email[%r{(?<=^References: )\S*}],
  449. subject: raw_email[%r{(?<=^Subject: Re: ).*$}])
  450. end
  451. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail005.box')) }
  452. it 'fires (without altering ticket state)' do
  453. expect { Channel::EmailParser.new.process({}, raw_email) }
  454. .to not_change { Ticket.count }
  455. .and change { ticket.reload.articles.count }.by(2)
  456. .and not_change { ticket.reload.state.name }.from('new')
  457. end
  458. end
  459. context 'with delivery-failed "bounce message"' do
  460. let!(:article) do
  461. create(:ticket_article,
  462. ticket: ticket,
  463. message_id: raw_email[%r{(?<=^Message-ID: )\S*}])
  464. end
  465. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail055.box')) }
  466. it 'does not fire' do
  467. expect { Channel::EmailParser.new.process({}, raw_email) }
  468. .to change { ticket.reload.articles.count }.by(1)
  469. end
  470. end
  471. end
  472. # https://github.com/zammad/zammad/issues/3991
  473. context 'when article contains a mention' do
  474. let!(:article) do
  475. create(:ticket_article,
  476. ticket: ticket,
  477. 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>')
  478. end
  479. it 'fires correctly' do
  480. expect { TransactionDispatcher.commit }
  481. .to change { ticket.reload.articles.count }.by(1)
  482. end
  483. end
  484. end
  485. context 'with condition execution_time.calendar_id' do
  486. let(:calendar) { create(:calendar) }
  487. let(:perform) do
  488. { 'ticket.title'=>{ 'value'=>'triggered' } }
  489. end
  490. let!(:ticket) { create(:ticket, title: 'Test Ticket') }
  491. context 'is in working time' do
  492. let(:condition) do
  493. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.all.pluck(:id) }, 'execution_time.calendar_id' => { 'operator' => 'is in working time', 'value' => calendar.id } }
  494. end
  495. it 'does trigger only in working time' do
  496. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  497. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  498. end
  499. it 'does not trigger out of working time' do
  500. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  501. TransactionDispatcher.commit
  502. expect(ticket.reload.title).to eq('Test Ticket')
  503. end
  504. end
  505. context 'is not in working time' do
  506. let(:condition) do
  507. { 'execution_time.calendar_id' => { 'operator' => 'is not in working time', 'value' => calendar.id } }
  508. end
  509. it 'does not trigger in working time' do
  510. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  511. TransactionDispatcher.commit
  512. expect(ticket.reload.title).to eq('Test Ticket')
  513. end
  514. it 'does trigger out of working time' do
  515. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  516. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  517. end
  518. end
  519. end
  520. context 'with article last sender equals system address' do
  521. let!(:ticket) { create(:ticket) }
  522. let(:perform) do
  523. {
  524. 'notification.email' => {
  525. 'recipient' => 'article_last_sender',
  526. 'subject' => 'foo last sender',
  527. 'body' => 'some body with &gt;snip&lt;#{article.body_as_html}&gt;/snip&lt;', # rubocop:disable Lint/InterpolationCheck
  528. }
  529. }
  530. end
  531. let(:condition) do
  532. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.all.pluck(:id) } }
  533. end
  534. let!(:system_address) do
  535. create(:email_address)
  536. end
  537. context 'article with from equal to the a system address' do
  538. let!(:article) do
  539. create(:ticket_article,
  540. ticket: ticket,
  541. from: system_address.email,)
  542. end
  543. it 'does not trigger because of the last article is created my system address' do
  544. expect { TransactionDispatcher.commit }.not_to change { ticket.reload.articles.count }
  545. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  546. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  547. end
  548. end
  549. context 'article with reply_to equal to the a system address' do
  550. let!(:article) do
  551. create(:ticket_article,
  552. ticket: ticket,
  553. from: system_address.email,
  554. reply_to: system_address.email,)
  555. end
  556. it 'does not trigger because of the last article is created my system address' do
  557. expect { TransactionDispatcher.commit }.not_to change { ticket.reload.articles.count }
  558. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  559. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  560. end
  561. end
  562. include_examples 'include ticket attachment'
  563. end
  564. end
  565. context 'with pre condition current_user.id' do
  566. let(:perform) do
  567. { 'ticket.title'=>{ 'value'=>'triggered' } }
  568. end
  569. let(:user) do
  570. user = create(:agent)
  571. user.roles.first.groups << group
  572. user
  573. end
  574. let(:group) { Group.first }
  575. let(:ticket) do
  576. create(:ticket,
  577. title: 'Test Ticket', group: group,
  578. owner_id: user.id, created_by_id: user.id, updated_by_id: user.id)
  579. end
  580. shared_examples 'successful trigger' do |attribute:|
  581. let(:attribute) { attribute }
  582. let(:condition) do
  583. { attribute => { operator: 'is', pre_condition: 'current_user.id', value: '', value_completion: '' } }
  584. end
  585. it "for #{attribute}" do
  586. ticket && trigger
  587. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  588. end
  589. end
  590. it_behaves_like 'successful trigger', attribute: 'ticket.updated_by_id'
  591. it_behaves_like 'successful trigger', attribute: 'ticket.owner_id'
  592. end
  593. describe 'Multi-trigger interactions:' do
  594. let(:ticket) { create(:ticket) }
  595. context 'cascading (i.e., trigger A satisfies trigger B satisfies trigger C)' do
  596. subject!(:triggers) do
  597. [
  598. create(:trigger, condition: initial_state, perform: first_change, name: 'A'),
  599. create(:trigger, condition: first_change, perform: second_change, name: 'B'),
  600. create(:trigger, condition: second_change, perform: third_change, name: 'C')
  601. ]
  602. end
  603. context 'in a chain' do
  604. let(:initial_state) do
  605. {
  606. 'ticket.state_id' => {
  607. 'operator' => 'is',
  608. 'value' => Ticket::State.lookup(name: 'new').id.to_s,
  609. }
  610. }
  611. end
  612. let(:first_change) do
  613. {
  614. 'ticket.state_id' => {
  615. 'operator' => 'is',
  616. 'value' => Ticket::State.lookup(name: 'open').id.to_s,
  617. }
  618. }
  619. end
  620. let(:second_change) do
  621. {
  622. 'ticket.state_id' => {
  623. 'operator' => 'is',
  624. 'value' => Ticket::State.lookup(name: 'closed').id.to_s,
  625. }
  626. }
  627. end
  628. let(:third_change) do
  629. {
  630. 'ticket.state_id' => {
  631. 'operator' => 'is',
  632. 'value' => Ticket::State.lookup(name: 'merged').id.to_s,
  633. }
  634. }
  635. end
  636. context 'in alphabetical order (by name)' do
  637. it 'fires all triggers in sequence' do
  638. expect { TransactionDispatcher.commit }
  639. .to change { ticket.reload.state.name }.to('merged')
  640. end
  641. end
  642. context 'out of alphabetical order (by name)' do
  643. before do
  644. triggers.first.update(name: 'E')
  645. triggers.second.update(name: 'F')
  646. triggers.third.update(name: 'D')
  647. end
  648. context 'with Setting ticket_trigger_recursive: true' do
  649. before { Setting.set('ticket_trigger_recursive', true) }
  650. it 'evaluates triggers in sequence, then loops back to the start and re-evalutes skipped triggers' do
  651. expect { TransactionDispatcher.commit }
  652. .to change { ticket.reload.state.name }.to('merged')
  653. end
  654. end
  655. context 'with Setting ticket_trigger_recursive: false' do
  656. before { Setting.set('ticket_trigger_recursive', false) }
  657. it 'evaluates triggers in sequence, firing only the ones that match' do
  658. expect { TransactionDispatcher.commit }
  659. .to change { ticket.reload.state.name }.to('closed')
  660. end
  661. end
  662. end
  663. end
  664. context 'in circular reference (i.e., trigger A satisfies trigger B satisfies trigger C satisfies trigger A...)' do
  665. let(:initial_state) do
  666. {
  667. 'ticket.priority_id' => {
  668. 'operator' => 'is',
  669. 'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
  670. }
  671. }
  672. end
  673. let(:first_change) do
  674. {
  675. 'ticket.priority_id' => {
  676. 'operator' => 'is',
  677. 'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
  678. }
  679. }
  680. end
  681. let(:second_change) do
  682. {
  683. 'ticket.priority_id' => {
  684. 'operator' => 'is',
  685. 'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
  686. }
  687. }
  688. end
  689. let(:third_change) do
  690. {
  691. 'ticket.priority_id' => {
  692. 'operator' => 'is',
  693. 'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
  694. }
  695. }
  696. end
  697. context 'with Setting ticket_trigger_recursive: true' do
  698. before { Setting.set('ticket_trigger_recursive', true) }
  699. it 'fires each trigger once, without being caught in an endless loop' do
  700. expect { Timeout.timeout(2) { TransactionDispatcher.commit } }
  701. .to not_change { ticket.reload.priority.name }
  702. .and not_raise_error
  703. end
  704. end
  705. context 'with Setting ticket_trigger_recursive: false' do
  706. before { Setting.set('ticket_trigger_recursive', false) }
  707. it 'fires each trigger once, without being caught in an endless loop' do
  708. expect { Timeout.timeout(2) { TransactionDispatcher.commit } }
  709. .to not_change { ticket.reload.priority.name }
  710. .and not_raise_error
  711. end
  712. end
  713. end
  714. end
  715. context 'competing (i.e., trigger A un-satisfies trigger B)' do
  716. subject!(:triggers) do
  717. [
  718. create(:trigger, condition: initial_state, perform: change_a, name: 'A'),
  719. create(:trigger, condition: initial_state, perform: change_b, name: 'B')
  720. ]
  721. end
  722. let(:initial_state) do
  723. {
  724. 'ticket.state_id' => {
  725. 'operator' => 'is',
  726. 'value' => Ticket::State.lookup(name: 'new').id.to_s,
  727. }
  728. }
  729. end
  730. let(:change_a) do
  731. {
  732. 'ticket.state_id' => {
  733. 'operator' => 'is',
  734. 'value' => Ticket::State.lookup(name: 'open').id.to_s,
  735. }
  736. }
  737. end
  738. let(:change_b) do
  739. {
  740. 'ticket.priority_id' => {
  741. 'operator' => 'is',
  742. 'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
  743. }
  744. }
  745. end
  746. it 'evaluates triggers in sequence, firing only the ones that match' do
  747. expect { TransactionDispatcher.commit }
  748. .to change { ticket.reload.state.name }.to('open')
  749. .and not_change { ticket.reload.priority.name }
  750. end
  751. end
  752. end
  753. describe 'multiselect triggers', db_strategy: :reset, mariadb: true do
  754. let(:attribute_name) { 'multiselect' }
  755. let(:condition) do
  756. { "ticket.#{attribute_name}" => { 'operator' => operator, 'value' => trigger_values } }
  757. end
  758. let(:perform) do
  759. { 'article.note' => { 'subject' => 'Test subject note', 'internal' => 'true', 'body' => 'Test body note' } }
  760. end
  761. before do
  762. create :object_manager_attribute_multiselect, name: attribute_name
  763. ObjectManager::Attribute.migration_execute
  764. described_class.destroy_all # Default DB state includes three sample triggers
  765. trigger # create subject trigger
  766. end
  767. context 'when ticket is updated with a multiselect trigger condition', authenticated_as: :owner, db_strategy: :reset do
  768. let(:options) do
  769. {
  770. a: 'a',
  771. b: 'b',
  772. c: 'c',
  773. d: 'd',
  774. e: 'e',
  775. }
  776. end
  777. let(:trigger_values) { %w[a b c] }
  778. let(:group) { create(:group) }
  779. let(:owner) { create(:admin, group_ids: [group.id]) }
  780. let!(:ticket) { create(:ticket, group: group,) }
  781. before do
  782. ticket.update_attribute(attribute_name, ticket_multiselect_values)
  783. end
  784. shared_examples 'updating the ticket with the trigger condition' do
  785. it 'updates the ticket with the trigger condition' do
  786. expect { TransactionDispatcher.commit }
  787. .to change(Ticket::Article, :count).by(1)
  788. end
  789. end
  790. shared_examples 'not updating the ticket with the trigger condition' do
  791. it 'does not update the ticket with the trigger condition' do
  792. expect { TransactionDispatcher.commit }
  793. .to not_change(Ticket::Article, :count)
  794. end
  795. end
  796. context "with 'contains all' used" do
  797. let(:operator) { 'contains all' }
  798. context 'when updated value is the same with trigger value' do
  799. let(:ticket_multiselect_values) { trigger_values }
  800. it_behaves_like 'updating the ticket with the trigger condition'
  801. end
  802. context 'when updated value is different from the trigger value' do
  803. let(:ticket_multiselect_values) { options.values - trigger_values }
  804. it_behaves_like 'not updating the ticket with the trigger condition'
  805. end
  806. context 'when no value is selected' do
  807. let(:ticket_multiselect_values) { ['-'] }
  808. it_behaves_like 'not updating the ticket with the trigger condition'
  809. end
  810. context 'when all value is selected' do
  811. let(:ticket_multiselect_values) { options.values }
  812. it_behaves_like 'updating the ticket with the trigger condition'
  813. end
  814. context 'when updated value contains one of the trigger value' do
  815. let(:ticket_multiselect_values) { [trigger_values.first] }
  816. it_behaves_like 'not updating the ticket with the trigger condition'
  817. end
  818. context 'when updated value does not contain one of the trigger value' do
  819. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  820. it_behaves_like 'not updating the ticket with the trigger condition'
  821. end
  822. end
  823. context "with 'contains one' used" do
  824. let(:operator) { 'contains one' }
  825. context 'when updated value is the same with trigger value' do
  826. let(:ticket_multiselect_values) { trigger_values }
  827. it_behaves_like 'updating the ticket with the trigger condition'
  828. end
  829. context 'when updated value is different from the trigger value' do
  830. let(:ticket_multiselect_values) { options.values - trigger_values }
  831. it_behaves_like 'not updating the ticket with the trigger condition'
  832. end
  833. context 'when no value is selected' do
  834. let(:ticket_multiselect_values) { ['-'] }
  835. it_behaves_like 'not updating the ticket with the trigger condition'
  836. end
  837. context 'when all value is selected' do
  838. let(:ticket_multiselect_values) { options.values }
  839. it_behaves_like 'updating the ticket with the trigger condition'
  840. end
  841. context 'when updated value contains only one of the trigger value' do
  842. let(:ticket_multiselect_values) { [trigger_values.first] }
  843. it_behaves_like 'updating the ticket with the trigger condition'
  844. end
  845. context 'when updated value does not contain one of the trigger value' do
  846. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  847. it_behaves_like 'updating the ticket with the trigger condition'
  848. end
  849. end
  850. context "with 'contains all not' used" do
  851. let(:operator) { 'contains all not' }
  852. context 'when updated value is the same with trigger value' do
  853. let(:ticket_multiselect_values) { trigger_values }
  854. it_behaves_like 'not updating the ticket with the trigger condition'
  855. end
  856. context 'when updated value is different from the trigger value' do
  857. let(:ticket_multiselect_values) { options.values - trigger_values }
  858. it_behaves_like 'updating the ticket with the trigger condition'
  859. end
  860. context 'when no value is selected' do
  861. let(:ticket_multiselect_values) { ['-'] }
  862. it_behaves_like 'updating the ticket with the trigger condition'
  863. end
  864. context 'when all value is selected' do
  865. let(:ticket_multiselect_values) { options.values }
  866. it_behaves_like 'not updating the ticket with the trigger condition'
  867. end
  868. context 'when updated value contains only one of the trigger value' do
  869. let(:ticket_multiselect_values) { [trigger_values.first] }
  870. it_behaves_like 'updating the ticket with the trigger condition'
  871. end
  872. context 'when updated value does not contain one of the trigger value' do
  873. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  874. it_behaves_like 'updating the ticket with the trigger condition'
  875. end
  876. end
  877. context "with 'contains one not' used" do
  878. let(:operator) { 'contains one not' }
  879. context 'when updated value is the same with trigger value' do
  880. let(:ticket_multiselect_values) { trigger_values }
  881. it_behaves_like 'not updating the ticket with the trigger condition'
  882. end
  883. context 'when updated value is different from the trigger value' do
  884. let(:ticket_multiselect_values) { options.values - trigger_values }
  885. it_behaves_like 'updating the ticket with the trigger condition'
  886. end
  887. context 'when no value is selected' do
  888. let(:ticket_multiselect_values) { ['-'] }
  889. it_behaves_like 'updating the ticket with the trigger condition'
  890. end
  891. context 'when all value is selected' do
  892. let(:ticket_multiselect_values) { options.values }
  893. it_behaves_like 'not updating the ticket with the trigger condition'
  894. end
  895. context 'when updated value contains only one of the trigger value' do
  896. let(:ticket_multiselect_values) { [trigger_values.first] }
  897. it_behaves_like 'not updating the ticket with the trigger condition'
  898. end
  899. context 'when updated value does not contain one of the trigger value' do
  900. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  901. it_behaves_like 'not updating the ticket with the trigger condition'
  902. end
  903. end
  904. end
  905. end
  906. end