trigger_spec.rb 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385
  1. # Copyright (C) 2012-2023 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. include_examples 'include ticket attachment'
  428. end
  429. context 'for condition "ticket updated"' do
  430. let(:condition) do
  431. { 'ticket.action' => { 'operator' => 'is', 'value' => 'update' } }
  432. end
  433. let!(:ticket) { create(:ticket).tap { TransactionDispatcher.commit } }
  434. context 'when new article is created directly' do
  435. context 'with empty #preferences hash' do
  436. let!(:article) { create(:ticket_article, ticket: ticket) }
  437. it 'fires (without altering ticket state)' do
  438. expect { TransactionDispatcher.commit }
  439. .to change { ticket.reload.articles.count }.by(1)
  440. .and not_change { ticket.reload.state.name }.from('new')
  441. end
  442. end
  443. context 'with #preferences { "send-auto-response" => false }' do
  444. let!(:article) do
  445. create(:ticket_article,
  446. ticket: ticket,
  447. preferences: { 'send-auto-response' => false })
  448. end
  449. it 'does not fire' do
  450. expect { TransactionDispatcher.commit }
  451. .not_to change { ticket.reload.articles.count }
  452. end
  453. end
  454. end
  455. context 'when new article is created via Channel::EmailParser.process' do
  456. context 'with a regular message' do
  457. let!(:article) do
  458. create(:ticket_article,
  459. ticket: ticket,
  460. message_id: raw_email[%r{(?<=^References: )\S*}],
  461. subject: raw_email[%r{(?<=^Subject: Re: ).*$}])
  462. end
  463. let(:raw_email) { Rails.root.join('test/data/mail/mail005.box').read }
  464. it 'fires (without altering ticket state)' do
  465. expect { Channel::EmailParser.new.process({}, raw_email) }
  466. .to not_change { Ticket.count }
  467. .and change { ticket.reload.articles.count }.by(2)
  468. .and not_change { ticket.reload.state.name }.from('new')
  469. end
  470. end
  471. context 'with delivery-failed "bounce message"' do
  472. let!(:article) do
  473. create(:ticket_article,
  474. ticket: ticket,
  475. message_id: raw_email[%r{(?<=^Message-ID: )\S*}])
  476. end
  477. let(:raw_email) { Rails.root.join('test/data/mail/mail055.box').read }
  478. it 'does not fire' do
  479. expect { Channel::EmailParser.new.process({}, raw_email) }
  480. .to change { ticket.reload.articles.count }.by(1)
  481. end
  482. end
  483. end
  484. # https://github.com/zammad/zammad/issues/3991
  485. context 'when article contains a mention' do
  486. let!(:article) do
  487. create(:ticket_article,
  488. ticket: ticket,
  489. 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>')
  490. end
  491. it 'fires correctly' do
  492. expect { TransactionDispatcher.commit }
  493. .to change { ticket.reload.articles.count }.by(1)
  494. end
  495. end
  496. end
  497. context 'with condition execution_time.calendar_id' do
  498. let(:calendar) { create(:calendar) }
  499. let(:perform) do
  500. { 'ticket.title'=>{ 'value'=>'triggered' } }
  501. end
  502. let!(:ticket) { create(:ticket, title: 'Test Ticket') }
  503. context 'is in working time' do
  504. let(:condition) do
  505. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.all.pluck(:id) }, 'execution_time.calendar_id' => { 'operator' => 'is in working time', 'value' => calendar.id } }
  506. end
  507. it 'does trigger only in working time' do
  508. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  509. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  510. end
  511. it 'does not trigger out of working time' do
  512. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  513. TransactionDispatcher.commit
  514. expect(ticket.reload.title).to eq('Test Ticket')
  515. end
  516. end
  517. context 'is not in working time' do
  518. let(:condition) do
  519. { 'execution_time.calendar_id' => { 'operator' => 'is not in working time', 'value' => calendar.id } }
  520. end
  521. it 'does not trigger in working time' do
  522. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  523. TransactionDispatcher.commit
  524. expect(ticket.reload.title).to eq('Test Ticket')
  525. end
  526. it 'does trigger out of working time' do
  527. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  528. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  529. end
  530. end
  531. end
  532. context 'with article last sender equals system address' do
  533. let!(:ticket) { create(:ticket) }
  534. let(:perform) do
  535. {
  536. 'notification.email' => {
  537. 'recipient' => 'article_last_sender',
  538. 'subject' => 'foo last sender',
  539. 'body' => 'some body with &gt;snip&lt;#{article.body_as_html}&gt;/snip&lt;', # rubocop:disable Lint/InterpolationCheck
  540. }
  541. }
  542. end
  543. let(:condition) do
  544. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.all.pluck(:id) } }
  545. end
  546. let!(:system_address) do
  547. create(:email_address)
  548. end
  549. context 'article with from equal to the a system address' do
  550. let!(:article) do
  551. create(:ticket_article,
  552. ticket: ticket,
  553. from: system_address.email,)
  554. end
  555. it 'does not trigger because of the last article is created my system address' do
  556. expect { TransactionDispatcher.commit }.not_to change { ticket.reload.articles.count }
  557. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  558. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  559. end
  560. end
  561. context 'article with reply_to equal to the a system address' do
  562. let!(:article) do
  563. create(:ticket_article,
  564. ticket: ticket,
  565. from: system_address.email,
  566. reply_to: system_address.email,)
  567. end
  568. it 'does not trigger because of the last article is created my system address' do
  569. expect { TransactionDispatcher.commit }.not_to change { ticket.reload.articles.count }
  570. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  571. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  572. end
  573. end
  574. include_examples 'include ticket attachment'
  575. end
  576. end
  577. context 'with pre condition current_user.id' do
  578. let(:perform) do
  579. { 'ticket.title'=>{ 'value'=>'triggered' } }
  580. end
  581. let(:user) do
  582. user = create(:agent)
  583. user.roles.first.groups << group
  584. user
  585. end
  586. let(:group) { Group.first }
  587. let(:ticket) do
  588. create(:ticket,
  589. title: 'Test Ticket', group: group,
  590. owner_id: user.id, created_by_id: user.id, updated_by_id: user.id)
  591. end
  592. shared_examples 'successful trigger' do |attribute:|
  593. let(:attribute) { attribute }
  594. let(:condition) do
  595. { attribute => { operator: 'is', pre_condition: 'current_user.id', value: '', value_completion: '' } }
  596. end
  597. it "for #{attribute}" do
  598. ticket && trigger
  599. expect { TransactionDispatcher.commit }.to change { ticket.reload.title }.to('triggered')
  600. end
  601. end
  602. it_behaves_like 'successful trigger', attribute: 'ticket.updated_by_id'
  603. it_behaves_like 'successful trigger', attribute: 'ticket.owner_id'
  604. end
  605. describe 'Multi-trigger interactions:' do
  606. let(:ticket) { create(:ticket) }
  607. context 'cascading (i.e., trigger A satisfies trigger B satisfies trigger C)' do
  608. subject!(:triggers) do
  609. [
  610. create(:trigger, condition: initial_state, perform: first_change, name: 'A'),
  611. create(:trigger, condition: first_change, perform: second_change, name: 'B'),
  612. create(:trigger, condition: second_change, perform: third_change, name: 'C')
  613. ]
  614. end
  615. context 'in a chain' do
  616. let(:initial_state) do
  617. {
  618. 'ticket.state_id' => {
  619. 'operator' => 'is',
  620. 'value' => Ticket::State.lookup(name: 'new').id.to_s,
  621. }
  622. }
  623. end
  624. let(:first_change) do
  625. {
  626. 'ticket.state_id' => {
  627. 'operator' => 'is',
  628. 'value' => Ticket::State.lookup(name: 'open').id.to_s,
  629. }
  630. }
  631. end
  632. let(:second_change) do
  633. {
  634. 'ticket.state_id' => {
  635. 'operator' => 'is',
  636. 'value' => Ticket::State.lookup(name: 'closed').id.to_s,
  637. }
  638. }
  639. end
  640. let(:third_change) do
  641. {
  642. 'ticket.state_id' => {
  643. 'operator' => 'is',
  644. 'value' => Ticket::State.lookup(name: 'merged').id.to_s,
  645. }
  646. }
  647. end
  648. context 'in alphabetical order (by name)' do
  649. it 'fires all triggers in sequence' do
  650. expect { TransactionDispatcher.commit }
  651. .to change { ticket.reload.state.name }.to('merged')
  652. end
  653. end
  654. context 'out of alphabetical order (by name)' do
  655. before do
  656. triggers.first.update(name: 'E')
  657. triggers.second.update(name: 'F')
  658. triggers.third.update(name: 'D')
  659. end
  660. context 'with Setting ticket_trigger_recursive: true' do
  661. before { Setting.set('ticket_trigger_recursive', true) }
  662. it 'evaluates triggers in sequence, then loops back to the start and re-evalutes skipped triggers' do
  663. expect { TransactionDispatcher.commit }
  664. .to change { ticket.reload.state.name }.to('merged')
  665. end
  666. end
  667. context 'with Setting ticket_trigger_recursive: false' do
  668. before { Setting.set('ticket_trigger_recursive', false) }
  669. it 'evaluates triggers in sequence, firing only the ones that match' do
  670. expect { TransactionDispatcher.commit }
  671. .to change { ticket.reload.state.name }.to('closed')
  672. end
  673. end
  674. end
  675. end
  676. context 'in circular reference (i.e., trigger A satisfies trigger B satisfies trigger C satisfies trigger A...)' do
  677. let(:initial_state) do
  678. {
  679. 'ticket.priority_id' => {
  680. 'operator' => 'is',
  681. 'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
  682. }
  683. }
  684. end
  685. let(:first_change) do
  686. {
  687. 'ticket.priority_id' => {
  688. 'operator' => 'is',
  689. 'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
  690. }
  691. }
  692. end
  693. let(:second_change) do
  694. {
  695. 'ticket.priority_id' => {
  696. 'operator' => 'is',
  697. 'value' => Ticket::Priority.lookup(name: '1 low').id.to_s,
  698. }
  699. }
  700. end
  701. let(:third_change) do
  702. {
  703. 'ticket.priority_id' => {
  704. 'operator' => 'is',
  705. 'value' => Ticket::Priority.lookup(name: '2 normal').id.to_s,
  706. }
  707. }
  708. end
  709. context 'with Setting ticket_trigger_recursive: true' do
  710. before { Setting.set('ticket_trigger_recursive', true) }
  711. it 'fires each trigger once, without being caught in an endless loop' do
  712. expect { Timeout.timeout(2) { TransactionDispatcher.commit } }
  713. .to not_change { ticket.reload.priority.name }
  714. .and not_raise_error
  715. end
  716. end
  717. context 'with Setting ticket_trigger_recursive: false' do
  718. before { Setting.set('ticket_trigger_recursive', false) }
  719. it 'fires each trigger once, without being caught in an endless loop' do
  720. expect { Timeout.timeout(2) { TransactionDispatcher.commit } }
  721. .to not_change { ticket.reload.priority.name }
  722. .and not_raise_error
  723. end
  724. end
  725. end
  726. end
  727. context 'competing (i.e., trigger A un-satisfies trigger B)' do
  728. subject!(:triggers) do
  729. [
  730. create(:trigger, condition: initial_state, perform: change_a, name: 'A'),
  731. create(:trigger, condition: initial_state, perform: change_b, name: 'B')
  732. ]
  733. end
  734. let(:initial_state) do
  735. {
  736. 'ticket.state_id' => {
  737. 'operator' => 'is',
  738. 'value' => Ticket::State.lookup(name: 'new').id.to_s,
  739. }
  740. }
  741. end
  742. let(:change_a) do
  743. {
  744. 'ticket.state_id' => {
  745. 'operator' => 'is',
  746. 'value' => Ticket::State.lookup(name: 'open').id.to_s,
  747. }
  748. }
  749. end
  750. let(:change_b) do
  751. {
  752. 'ticket.priority_id' => {
  753. 'operator' => 'is',
  754. 'value' => Ticket::Priority.lookup(name: '3 high').id.to_s,
  755. }
  756. }
  757. end
  758. it 'evaluates triggers in sequence, firing only the ones that match' do
  759. expect { TransactionDispatcher.commit }
  760. .to change { ticket.reload.state.name }.to('open')
  761. .and not_change { ticket.reload.priority.name }
  762. end
  763. end
  764. end
  765. describe 'multiselect triggers', db_strategy: :reset, mariadb: true do
  766. let(:attribute_name) { 'multiselect' }
  767. let(:condition) do
  768. { "ticket.#{attribute_name}" => { 'operator' => operator, 'value' => trigger_values } }
  769. end
  770. let(:perform) do
  771. { 'article.note' => { 'subject' => 'Test subject note', 'internal' => 'true', 'body' => 'Test body note' } }
  772. end
  773. before do
  774. create(:object_manager_attribute_multiselect, name: attribute_name)
  775. ObjectManager::Attribute.migration_execute
  776. described_class.destroy_all # Default DB state includes three sample triggers
  777. trigger # create subject trigger
  778. end
  779. context 'when ticket is updated with a multiselect trigger condition', authenticated_as: :owner, db_strategy: :reset do
  780. let(:options) do
  781. {
  782. a: 'a',
  783. b: 'b',
  784. c: 'c',
  785. d: 'd',
  786. e: 'e',
  787. }
  788. end
  789. let(:trigger_values) { %w[a b c] }
  790. let(:group) { create(:group) }
  791. let(:owner) { create(:admin, group_ids: [group.id]) }
  792. let!(:ticket) { create(:ticket, group: group,) }
  793. before do
  794. ticket.update_attribute(attribute_name, ticket_multiselect_values)
  795. end
  796. shared_examples 'updating the ticket with the trigger condition' do
  797. it 'updates the ticket with the trigger condition' do
  798. expect { TransactionDispatcher.commit }
  799. .to change(Ticket::Article, :count).by(1)
  800. end
  801. end
  802. shared_examples 'not updating the ticket with the trigger condition' do
  803. it 'does not update the ticket with the trigger condition' do
  804. expect { TransactionDispatcher.commit }
  805. .to not_change(Ticket::Article, :count)
  806. end
  807. end
  808. context "with 'contains all' used" do
  809. let(:operator) { 'contains all' }
  810. context 'when updated value is the same with trigger value' do
  811. let(:ticket_multiselect_values) { trigger_values }
  812. it_behaves_like 'updating the ticket with the trigger condition'
  813. end
  814. context 'when updated value is different from the trigger value' do
  815. let(:ticket_multiselect_values) { options.values - trigger_values }
  816. it_behaves_like 'not updating the ticket with the trigger condition'
  817. end
  818. context 'when no value is selected' do
  819. let(:ticket_multiselect_values) { ['-'] }
  820. it_behaves_like 'not updating the ticket with the trigger condition'
  821. end
  822. context 'when all value is selected' do
  823. let(:ticket_multiselect_values) { options.values }
  824. it_behaves_like 'updating the ticket with the trigger condition'
  825. end
  826. context 'when updated value contains one of the trigger value' do
  827. let(:ticket_multiselect_values) { [trigger_values.first] }
  828. it_behaves_like 'not updating the ticket with the trigger condition'
  829. end
  830. context 'when updated value does not contain one of the trigger value' do
  831. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  832. it_behaves_like 'not updating the ticket with the trigger condition'
  833. end
  834. end
  835. context "with 'contains one' used" do
  836. let(:operator) { 'contains one' }
  837. context 'when updated value is the same with trigger value' do
  838. let(:ticket_multiselect_values) { trigger_values }
  839. it_behaves_like 'updating the ticket with the trigger condition'
  840. end
  841. context 'when updated value is different from the trigger value' do
  842. let(:ticket_multiselect_values) { options.values - trigger_values }
  843. it_behaves_like 'not updating the ticket with the trigger condition'
  844. end
  845. context 'when no value is selected' do
  846. let(:ticket_multiselect_values) { ['-'] }
  847. it_behaves_like 'not updating the ticket with the trigger condition'
  848. end
  849. context 'when all value is selected' do
  850. let(:ticket_multiselect_values) { options.values }
  851. it_behaves_like 'updating the ticket with the trigger condition'
  852. end
  853. context 'when updated value contains only one of the trigger value' do
  854. let(:ticket_multiselect_values) { [trigger_values.first] }
  855. it_behaves_like 'updating the ticket with the trigger condition'
  856. end
  857. context 'when updated value does not contain one of the trigger value' do
  858. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  859. it_behaves_like 'updating the ticket with the trigger condition'
  860. end
  861. end
  862. context "with 'contains all not' used" do
  863. let(:operator) { 'contains all not' }
  864. context 'when updated value is the same with trigger value' do
  865. let(:ticket_multiselect_values) { trigger_values }
  866. it_behaves_like 'not updating the ticket with the trigger condition'
  867. end
  868. context 'when updated value is different from the trigger value' do
  869. let(:ticket_multiselect_values) { options.values - trigger_values }
  870. it_behaves_like 'updating the ticket with the trigger condition'
  871. end
  872. context 'when no value is selected' do
  873. let(:ticket_multiselect_values) { ['-'] }
  874. it_behaves_like 'updating the ticket with the trigger condition'
  875. end
  876. context 'when all value is selected' do
  877. let(:ticket_multiselect_values) { options.values }
  878. it_behaves_like 'not updating the ticket with the trigger condition'
  879. end
  880. context 'when updated value contains only one of the trigger value' do
  881. let(:ticket_multiselect_values) { [trigger_values.first] }
  882. it_behaves_like 'updating the ticket with the trigger condition'
  883. end
  884. context 'when updated value does not contain one of the trigger value' do
  885. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  886. it_behaves_like 'updating the ticket with the trigger condition'
  887. end
  888. end
  889. context "with 'contains one not' used" do
  890. let(:operator) { 'contains one not' }
  891. context 'when updated value is the same with trigger value' do
  892. let(:ticket_multiselect_values) { trigger_values }
  893. it_behaves_like 'not updating the ticket with the trigger condition'
  894. end
  895. context 'when updated value is different from the trigger value' do
  896. let(:ticket_multiselect_values) { options.values - trigger_values }
  897. it_behaves_like 'updating the ticket with the trigger condition'
  898. end
  899. context 'when no value is selected' do
  900. let(:ticket_multiselect_values) { ['-'] }
  901. it_behaves_like 'updating the ticket with the trigger condition'
  902. end
  903. context 'when all value is selected' do
  904. let(:ticket_multiselect_values) { options.values }
  905. it_behaves_like 'not updating the ticket with the trigger condition'
  906. end
  907. context 'when updated value contains only one of the trigger value' do
  908. let(:ticket_multiselect_values) { [trigger_values.first] }
  909. it_behaves_like 'not updating the ticket with the trigger condition'
  910. end
  911. context 'when updated value does not contain one of the trigger value' do
  912. let(:ticket_multiselect_values) { options.values - [trigger_values.first] }
  913. it_behaves_like 'not updating the ticket with the trigger condition'
  914. end
  915. end
  916. end
  917. end
  918. describe 'Triggers without configured action inside condition are executed differently compared to 5.3 #4550' do
  919. let(:ticket_match) { create(:ticket, group: Group.first) }
  920. let(:ticket_no_match) { create(:ticket, group: Group.first, priority: Ticket::Priority.find_by(name: '1 low')) }
  921. let(:condition) do
  922. { 'ticket.priority_id' => { 'operator' => 'is', 'value' => Ticket::Priority.where(name: ['2 normal', '3 high']).pluck(:id).map(&:to_s) } }
  923. end
  924. let(:perform) do
  925. { 'article.note' => { 'subject' => 'Test subject note', 'internal' => 'true', 'body' => 'Test body note' } }
  926. end
  927. shared_examples 'executing trigger when conditions match' do |execution_condition_mode:|
  928. it 'does not create an article if the state changes', if: execution_condition_mode == 'selective' do
  929. ticket_match.update(state: Ticket::State.find_by(name: 'closed'))
  930. expect { TransactionDispatcher.commit }.not_to change(Ticket::Article, :count)
  931. end
  932. it 'does create an article if the state changes', if: execution_condition_mode == 'always' do
  933. ticket_match.update(state: Ticket::State.find_by(name: 'closed'))
  934. expect { TransactionDispatcher.commit }.to change(Ticket::Article, :count)
  935. end
  936. it 'does create an article if priority changes' do
  937. ticket_match.update(priority: Ticket::Priority.find_by(name: '3 high'))
  938. expect { TransactionDispatcher.commit }.to change(Ticket::Article, :count).by(1)
  939. end
  940. it 'does create an article if priority matches and new article is created' do
  941. create(:ticket_article, ticket: ticket_match)
  942. expect { TransactionDispatcher.commit }.to change(Ticket::Article, :count).by(1)
  943. end
  944. end
  945. shared_examples "not executing trigger when conditions don't match" do
  946. it 'does not create an article if priority does not match but new article is created' do
  947. create(:ticket_article, ticket: ticket_no_match)
  948. expect { TransactionDispatcher.commit }.not_to change(Ticket::Article, :count)
  949. end
  950. it 'does not create an article if priority does not match and priority changes to low' do
  951. ticket_match.update(priority: Ticket::Priority.find_by(name: '1 low'))
  952. expect { TransactionDispatcher.commit }.not_to change(Ticket::Article, :count)
  953. end
  954. end
  955. before do
  956. ticket_match
  957. ticket_no_match
  958. trigger
  959. TransactionDispatcher.commit
  960. end
  961. context "with execution condition mode 'selective'" do
  962. it_behaves_like 'executing trigger when conditions match', execution_condition_mode: 'selective'
  963. it_behaves_like "not executing trigger when conditions don't match"
  964. end
  965. context "with execution condition mode 'always'" do
  966. let(:execution_condition_mode) { 'always' }
  967. it_behaves_like 'executing trigger when conditions match', execution_condition_mode: 'always'
  968. it_behaves_like "not executing trigger when conditions don't match"
  969. end
  970. end
  971. context 'when time events are reached', time_zone: 'Europe/London' do
  972. let(:activator) { 'time' }
  973. let(:perform) { { 'ticket.title' => { 'value' => 'triggered' } } }
  974. let(:ticket) { create(:ticket, title: 'Test Ticket', state_name: state_name, pending_time: pending_time) }
  975. shared_examples 'getting triggered' do |attribute:, operator:, with_pending_time: false, with_escalation: false|
  976. let(:attribute) { attribute }
  977. let(:condition) { { attribute => { operator: operator } } }
  978. let(:state_name) { 'pending reminder' if with_pending_time }
  979. let(:pending_time) { 1.hour.ago if with_pending_time }
  980. let(:calendar) { create(:calendar, :'24/7') }
  981. let(:sla) { create(:sla, :condition_blank, solution_time: 10, calendar: calendar) }
  982. before do
  983. sla if with_escalation
  984. ticket && trigger
  985. travel 1.hour if with_escalation
  986. end
  987. it "gets triggered for attribute: #{attribute}, operator: #{operator}" do
  988. expect { perform_job(attribute, operator) }
  989. .to change { ticket.reload.title }
  990. .to('triggered')
  991. end
  992. def job_type(attribute, operator)
  993. case [attribute, operator]
  994. in 'ticket.pending_time', _
  995. 'reminder_reached'
  996. in 'ticket.escalation_at', 'has reached'
  997. 'escalation'
  998. in 'ticket.escalation_at', 'has reached warning'
  999. 'escalation_warning'
  1000. end
  1001. end
  1002. def perform_job(...)
  1003. TransactionJob.perform_now(
  1004. object: 'Ticket',
  1005. type: job_type(...),
  1006. object_id: ticket.id,
  1007. article_id: nil,
  1008. user_id: 1,
  1009. )
  1010. end
  1011. end
  1012. it_behaves_like 'getting triggered', attribute: 'ticket.pending_time', operator: 'has reached', with_pending_time: true
  1013. it_behaves_like 'getting triggered', attribute: 'ticket.escalation_at', operator: 'has reached', with_escalation: true
  1014. it_behaves_like 'getting triggered', attribute: 'ticket.escalation_at', operator: 'has reached warning', with_escalation: true
  1015. end
  1016. context 'when aticle action is set' do
  1017. let(:activator) { 'action' }
  1018. let(:perform) { { 'ticket.title' => { 'value' => 'triggered' } } }
  1019. let(:ticket) { create(:ticket, title: 'Test Ticket') }
  1020. let(:article) { create(:ticket_article, ticket: ticket) }
  1021. shared_examples 'getting triggered' do |triggered:, operator:, with_article:, type:|
  1022. before do
  1023. ticket && trigger
  1024. article if with_article
  1025. end
  1026. let(:article_id) { with_article ? article.id : nil }
  1027. let(:type) { type }
  1028. let(:condition) do
  1029. { 'article.action' => { 'operator' => operator, 'value' => 'create' } }
  1030. end
  1031. if triggered
  1032. it "gets triggered for article action created operator: #{operator}" do
  1033. expect { TransactionDispatcher.commit }
  1034. .to change { ticket.reload.title }
  1035. .to('triggered')
  1036. end
  1037. else
  1038. it "does not get triggered for article action created operator: #{operator}" do
  1039. expect { TransactionDispatcher.commit }
  1040. .not_to change { ticket.reload.title }
  1041. end
  1042. end
  1043. end
  1044. it_behaves_like 'getting triggered', triggered: true, operator: 'is', with_article: true, type: 'update'
  1045. it_behaves_like 'getting triggered', triggered: false, operator: 'is not', with_article: true, type: 'update'
  1046. it_behaves_like 'getting triggered', triggered: false, operator: 'is', with_article: false, type: 'update'
  1047. it_behaves_like 'getting triggered', triggered: true, operator: 'is not', with_article: false, type: 'update'
  1048. it_behaves_like 'getting triggered', triggered: true, operator: 'is', with_article: true, type: 'create'
  1049. it_behaves_like 'getting triggered', triggered: false, operator: 'is not', with_article: true, type: 'create'
  1050. it_behaves_like 'getting triggered', triggered: false, operator: 'is', with_article: false, type: 'create'
  1051. it_behaves_like 'getting triggered', triggered: true, operator: 'is not', with_article: false, type: 'create'
  1052. end
  1053. describe '#performed_on', current_user_id: 1 do
  1054. let(:ticket) { create(:ticket) }
  1055. before { ticket }
  1056. context 'given action-based trigger' do
  1057. let(:activator) { 'action' }
  1058. it 'does nothing' do
  1059. expect { trigger.performed_on(ticket, activator_type: 'reminder_reached') }
  1060. .not_to change(History, :count)
  1061. end
  1062. end
  1063. context 'given time-based trigger' do
  1064. let(:activator) { 'time' }
  1065. it 'creates history item' do
  1066. expect { trigger.performed_on(ticket, activator_type: 'reminder_reached') }
  1067. .to change(History, :count)
  1068. .by(1)
  1069. end
  1070. end
  1071. end
  1072. describe 'performable_on?', current_user_id: 1 do
  1073. let(:ticket) { create(:ticket) }
  1074. before { ticket }
  1075. context 'given action-based trigger' do
  1076. let(:activator) { 'action' }
  1077. it 'returns nil' do
  1078. expect(trigger.performable_on?(ticket, activator_type: 'reminder_reached'))
  1079. .to be_nil
  1080. end
  1081. end
  1082. context 'given time-based trigger' do
  1083. let(:activator) { 'time' }
  1084. it 'returns true if it was not performed yet' do
  1085. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1086. end
  1087. it 'returns true if it was performed yesterday' do
  1088. travel(-1.day) do
  1089. trigger.performed_on(ticket, activator_type: 'reminder_reached')
  1090. end
  1091. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1092. end
  1093. it 'returns true if it was performed today on another ticket' do
  1094. trigger.performed_on(create(:ticket), activator_type: 'reminder_reached')
  1095. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1096. end
  1097. it 'returns true if it was performed today by another activator' do
  1098. trigger.performed_on(ticket, activator_type: 'escalation')
  1099. expect(trigger).to be_performable_on(ticket, activator_type: 'reminder_reached')
  1100. end
  1101. it 'returns false if it was performed today on the same ticket by the same activator and same user' do
  1102. trigger.performed_on(ticket, activator_type: 'reminder_reached')
  1103. expect(trigger).not_to be_performable_on(ticket, activator_type: 'reminder_reached')
  1104. end
  1105. end
  1106. end
  1107. end