trigger_spec.rb 38 KB

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