trigger_spec.rb 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. require 'rails_helper'
  2. require 'models/application_model_examples'
  3. RSpec.describe Trigger, type: :model do
  4. subject(:trigger) { create(:trigger, condition: condition, perform: perform) }
  5. it_behaves_like 'ApplicationModel', can_assets: { selectors: %i[condition perform] }
  6. describe 'validation' do
  7. let(:condition) do
  8. { 'ticket.action' => { 'operator' => 'is', 'value' => 'create' } }
  9. end
  10. let(:perform) do
  11. { 'ticket.title' => { 'value'=>'triggered' } }
  12. end
  13. context 'notification.email' do
  14. context 'missing recipient' do
  15. let(:perform) do
  16. {
  17. 'notification.email' => {
  18. 'subject' => 'Hello',
  19. 'body' => 'World!'
  20. }
  21. }
  22. end
  23. it 'raises an error' do
  24. expect { trigger.save! }.to raise_error(Exceptions::UnprocessableEntity, 'Invalid perform notification.email, recipient is missing!')
  25. end
  26. end
  27. end
  28. end
  29. describe 'Send-email triggers' do
  30. before do
  31. described_class.destroy_all # Default DB state includes three sample triggers
  32. trigger # create subject trigger
  33. end
  34. let(:perform) do
  35. {
  36. 'notification.email' => {
  37. 'recipient' => 'ticket_customer',
  38. 'subject' => 'foo',
  39. 'body' => 'some body with >snip<#{article.body_as_html}>/snip<', # rubocop:disable Lint/InterpolationCheck
  40. }
  41. }
  42. end
  43. context 'for condition "ticket created"' do
  44. let(:condition) do
  45. { 'ticket.action' => { 'operator' => 'is', 'value' => 'create' } }
  46. end
  47. context 'when ticket is created directly' do
  48. let!(:ticket) { create(:ticket) }
  49. it 'fires (without altering ticket state)' do
  50. expect { Observer::Transaction.commit }
  51. .to change(Ticket::Article, :count).by(1)
  52. .and not_change { ticket.reload.state.name }.from('new')
  53. end
  54. end
  55. context 'when ticket is created via Channel::EmailParser.process' do
  56. before { create(:email_address, groups: [Group.first]) }
  57. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail001.box')) }
  58. it 'fires (without altering ticket state)' do
  59. expect { Channel::EmailParser.new.process({}, raw_email) }
  60. .to change(Ticket, :count).by(1)
  61. .and change { Ticket::Article.count }.by(2)
  62. expect(Ticket.last.state.name).to eq('new')
  63. end
  64. end
  65. context 'when ticket is created via Channel::EmailParser.process with inline image' do
  66. before { create(:email_address, groups: [Group.first]) }
  67. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail010.box')) }
  68. it 'fires (without altering ticket state)' do
  69. expect { Channel::EmailParser.new.process({}, raw_email) }
  70. .to change(Ticket, :count).by(1)
  71. .and change { Ticket::Article.count }.by(2)
  72. expect(Ticket.last.state.name).to eq('new')
  73. article = Ticket::Article.last
  74. expect(article.type.name).to eq('email')
  75. expect(article.sender.name).to eq('System')
  76. expect(article.attachments.count).to eq(1)
  77. expect(article.attachments[0].filename).to eq('image001.jpg')
  78. expect(article.attachments[0].preferences['Content-ID']).to eq('image001.jpg@01CDB132.D8A510F0')
  79. expect(article.body).to eq(<<~RAW.chomp
  80. some body with &gt;snip&lt;<div>
  81. <p>Herzliche Grüße aus Oberalteich sendet Herrn Smith</p>
  82. <p> </p>
  83. <p>Sepp Smith - Dipl.Ing. agr. (FH)</p>
  84. <p>Geschäftsführer der example Straubing-Bogen</p>
  85. <p>Klosterhof 1 | 94327 Bogen-Oberalteich</p>
  86. <p>Tel: 09422-505601 | Fax: 09422-505620</p>
  87. <p>Internet: <a href="http://example-straubing-bogen.de/" rel="nofollow noreferrer noopener" target="_blank">http://example-straubing-bogen.de</a></p>
  88. <p>Facebook: <a href="http://facebook.de/examplesrbog" rel="nofollow noreferrer noopener" target="_blank">http://facebook.de/examplesrbog</a></p>
  89. <p><b><img border="0" src="cid:image001.jpg@01CDB132.D8A510F0" alt="Beschreibung: Beschreibung: efqmLogo" style="width:60px;height:19px;"></b><b> - European Foundation für Quality Management</b></p>
  90. <p> </p>
  91. </div>&gt;/snip&lt;
  92. RAW
  93. )
  94. end
  95. end
  96. context 'notification.email recipient' do
  97. let!(:ticket) { create(:ticket) }
  98. let!(:recipient1) { create(:user, email: 'test1@zammad-test.com') }
  99. let!(:recipient2) { create(:user, email: 'test2@zammad-test.com') }
  100. let!(:recipient3) { create(:user, email: 'test3@zammad-test.com') }
  101. let(:perform) do
  102. {
  103. 'notification.email' => {
  104. 'recipient' => recipient,
  105. 'subject' => 'Hello',
  106. 'body' => 'World!'
  107. }
  108. }
  109. end
  110. before { Observer::Transaction.commit }
  111. context 'mix of recipient group keyword and single recipient users' do
  112. let(:recipient) { [ 'ticket_customer', "userid_#{recipient1.id}", "userid_#{recipient2.id}", "userid_#{recipient3.id}" ] }
  113. it 'contains all recipients' do
  114. expect(ticket.articles.last.to).to eq("#{ticket.customer.email}, #{recipient1.email}, #{recipient2.email}, #{recipient3.email}")
  115. end
  116. context 'duplicate recipient' do
  117. let(:recipient) { [ 'ticket_customer', "userid_#{ticket.customer.id}" ] }
  118. it 'contains only one recipient' do
  119. expect(ticket.articles.last.to).to eq(ticket.customer.email.to_s)
  120. end
  121. end
  122. end
  123. context 'list of single users only' do
  124. let(:recipient) { [ "userid_#{recipient1.id}", "userid_#{recipient2.id}", "userid_#{recipient3.id}" ] }
  125. it 'contains all recipients' do
  126. expect(ticket.articles.last.to).to eq("#{recipient1.email}, #{recipient2.email}, #{recipient3.email}")
  127. end
  128. context 'assets' do
  129. it 'resolves Users from recipient list' do
  130. expect(trigger.assets({})[:User].keys).to include(recipient1.id, recipient2.id, recipient3.id)
  131. end
  132. context 'single entry' do
  133. let(:recipient) { "userid_#{recipient1.id}" }
  134. it 'resolves User from recipient list' do
  135. expect(trigger.assets({})[:User].keys).to include(recipient1.id)
  136. end
  137. end
  138. end
  139. end
  140. context 'recipient group keyword only' do
  141. let(:recipient) { 'ticket_customer' }
  142. it 'contains matching recipient' do
  143. expect(ticket.articles.last.to).to eq(ticket.customer.email.to_s)
  144. end
  145. end
  146. end
  147. context 'active S/MIME integration' do
  148. before do
  149. Setting.set('smime_integration', true)
  150. create(:smime_certificate, :with_private, fixture: system_email_address)
  151. create(:smime_certificate, fixture: customer_email_address)
  152. end
  153. let(:system_email_address) { 'smime1@example.com' }
  154. let(:customer_email_address) { 'smime2@example.com' }
  155. let(:email_address) { create(:email_address, email: system_email_address) }
  156. let(:group) { create(:group, email_address: email_address) }
  157. let(:customer) { create(:customer_user, email: customer_email_address) }
  158. let(:security_preferences) { Ticket::Article.last.preferences[:security] }
  159. let(:perform) do
  160. {
  161. 'notification.email' => {
  162. 'recipient' => 'ticket_customer',
  163. 'subject' => 'Subject dummy.',
  164. 'body' => 'Body dummy.',
  165. }.merge(security_configuration)
  166. }
  167. end
  168. let!(:ticket) { create(:ticket, group: group, customer: customer) }
  169. context 'sending articles' do
  170. before do
  171. Observer::Transaction.commit
  172. end
  173. context 'expired certificate' do
  174. let(:system_email_address) { 'expiredsmime1@example.com' }
  175. let(:security_configuration) do
  176. {
  177. 'sign' => 'always',
  178. 'encryption' => 'always',
  179. }
  180. end
  181. it 'creates unsigned article' do
  182. expect(security_preferences[:sign][:success]).to be false
  183. expect(security_preferences[:encryption][:success]).to be true
  184. end
  185. end
  186. context 'sign and encryption not set' do
  187. let(:security_configuration) { {} }
  188. it 'does not sign or encrypt' do
  189. expect(security_preferences[:sign][:success]).to be false
  190. expect(security_preferences[:encryption][:success]).to be false
  191. end
  192. end
  193. context 'sign and encryption disabled' do
  194. let(:security_configuration) do
  195. {
  196. 'sign' => 'no',
  197. 'encryption' => 'no',
  198. }
  199. end
  200. it 'does not sign or encrypt' do
  201. expect(security_preferences[:sign][:success]).to be false
  202. expect(security_preferences[:encryption][:success]).to be false
  203. end
  204. end
  205. context 'sign is enabled' do
  206. let(:security_configuration) do
  207. {
  208. 'sign' => 'always',
  209. 'encryption' => 'no',
  210. }
  211. end
  212. it 'signs' do
  213. expect(security_preferences[:sign][:success]).to be true
  214. expect(security_preferences[:encryption][:success]).to be false
  215. end
  216. end
  217. context 'encryption enabled' do
  218. let(:security_configuration) do
  219. {
  220. 'sign' => 'no',
  221. 'encryption' => 'always',
  222. }
  223. end
  224. it 'encrypts' do
  225. expect(security_preferences[:sign][:success]).to be false
  226. expect(security_preferences[:encryption][:success]).to be true
  227. end
  228. end
  229. context 'sign and encryption enabled' do
  230. let(:security_configuration) do
  231. {
  232. 'sign' => 'always',
  233. 'encryption' => 'always',
  234. }
  235. end
  236. it 'signs and encrypts' do
  237. expect(security_preferences[:sign][:success]).to be true
  238. expect(security_preferences[:encryption][:success]).to be true
  239. end
  240. end
  241. end
  242. context 'discard' do
  243. context 'sign' do
  244. let(:security_configuration) do
  245. {
  246. 'sign' => 'discard',
  247. }
  248. end
  249. context 'group without certificate' do
  250. let(:group) { create(:group) }
  251. it 'does not fire' do
  252. expect { Observer::Transaction.commit }
  253. .to change(Ticket::Article, :count).by(0)
  254. end
  255. end
  256. end
  257. context 'encryption' do
  258. let(:security_configuration) do
  259. {
  260. 'encryption' => 'discard',
  261. }
  262. end
  263. context 'customer without certificate' do
  264. let(:customer) { create(:customer) }
  265. it 'does not fire' do
  266. expect { Observer::Transaction.commit }
  267. .to change(Ticket::Article, :count).by(0)
  268. end
  269. end
  270. end
  271. context 'mixed' do
  272. context 'sign' do
  273. let(:security_configuration) do
  274. {
  275. 'encryption' => 'always',
  276. 'sign' => 'discard',
  277. }
  278. end
  279. context 'group without certificate' do
  280. let(:group) { create(:group) }
  281. it 'does not fire' do
  282. expect { Observer::Transaction.commit }
  283. .to change(Ticket::Article, :count).by(0)
  284. end
  285. end
  286. end
  287. context 'encryption' do
  288. let(:security_configuration) do
  289. {
  290. 'encryption' => 'discard',
  291. 'sign' => 'always',
  292. }
  293. end
  294. context 'customer without certificate' do
  295. let(:customer) { create(:customer) }
  296. it 'does not fire' do
  297. expect { Observer::Transaction.commit }
  298. .to change(Ticket::Article, :count).by(0)
  299. end
  300. end
  301. end
  302. end
  303. end
  304. end
  305. end
  306. context 'for condition "ticket updated"' do
  307. let(:condition) do
  308. { 'ticket.action' => { 'operator' => 'is', 'value' => 'update' } }
  309. end
  310. let!(:ticket) { create(:ticket).tap { Observer::Transaction.commit } }
  311. context 'when new article is created directly' do
  312. context 'with empty #preferences hash' do
  313. let!(:article) { create(:ticket_article, ticket: ticket) }
  314. it 'fires (without altering ticket state)' do
  315. expect { Observer::Transaction.commit }
  316. .to change { ticket.reload.articles.count }.by(1)
  317. .and not_change { ticket.reload.state.name }.from('new')
  318. end
  319. end
  320. context 'with #preferences { "send-auto-response" => false }' do
  321. let!(:article) do
  322. create(:ticket_article,
  323. ticket: ticket,
  324. preferences: { 'send-auto-response' => false })
  325. end
  326. it 'does not fire' do
  327. expect { Observer::Transaction.commit }
  328. .not_to change { ticket.reload.articles.count }
  329. end
  330. end
  331. end
  332. context 'when new article is created via Channel::EmailParser.process' do
  333. context 'with a regular message' do
  334. let!(:article) do
  335. create(:ticket_article,
  336. ticket: ticket,
  337. message_id: raw_email[/(?<=^References: )\S*/],
  338. subject: raw_email[/(?<=^Subject: Re: ).*$/])
  339. end
  340. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail005.box')) }
  341. it 'fires (without altering ticket state)' do
  342. expect { Channel::EmailParser.new.process({}, raw_email) }
  343. .to not_change { Ticket.count }
  344. .and change { ticket.reload.articles.count }.by(2)
  345. .and not_change { ticket.reload.state.name }.from('new')
  346. end
  347. end
  348. context 'with delivery-failed "bounce message"' do
  349. let!(:article) do
  350. create(:ticket_article,
  351. ticket: ticket,
  352. message_id: raw_email[/(?<=^Message-ID: )\S*/])
  353. end
  354. let(:raw_email) { File.read(Rails.root.join('test/data/mail/mail055.box')) }
  355. it 'does not fire' do
  356. expect { Channel::EmailParser.new.process({}, raw_email) }
  357. .to change { ticket.reload.articles.count }.by(1)
  358. end
  359. end
  360. end
  361. end
  362. context 'with condition execution_time.calendar_id' do
  363. let(:calendar) { create(:calendar) }
  364. let(:perform) do
  365. { 'ticket.title'=>{ 'value'=>'triggered' } }
  366. end
  367. let!(:ticket) { create(:ticket, title: 'Test Ticket') }
  368. context 'is in working time' do
  369. let(:condition) do
  370. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.all.pluck(:id) }, 'execution_time.calendar_id' => { 'operator' => 'is in working time', 'value' => calendar.id } }
  371. end
  372. it 'does trigger only in working time' do
  373. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  374. expect { Observer::Transaction.commit }.to change { ticket.reload.title }.to('triggered')
  375. end
  376. it 'does not trigger out of working time' do
  377. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  378. Observer::Transaction.commit
  379. expect(ticket.reload.title).to eq('Test Ticket')
  380. end
  381. end
  382. context 'is not in working time' do
  383. let(:condition) do
  384. { 'execution_time.calendar_id' => { 'operator' => 'is not in working time', 'value' => calendar.id } }
  385. end
  386. it 'does not trigger in working time' do
  387. travel_to Time.zone.parse('2020-02-12T12:00:00Z0')
  388. Observer::Transaction.commit
  389. expect(ticket.reload.title).to eq('Test Ticket')
  390. end
  391. it 'does trigger out of working time' do
  392. travel_to Time.zone.parse('2020-02-12T02:00:00Z0')
  393. expect { Observer::Transaction.commit }.to change { ticket.reload.title }.to('triggered')
  394. end
  395. end
  396. end
  397. context 'with article last sender equals system address' do
  398. let!(:ticket) { create(:ticket) }
  399. let(:perform) do
  400. {
  401. 'notification.email' => {
  402. 'recipient' => 'article_last_sender',
  403. 'subject' => 'foo last sender',
  404. 'body' => 'some body with &gt;snip&lt;#{article.body_as_html}&gt;/snip&lt;', # rubocop:disable Lint/InterpolationCheck
  405. }
  406. }
  407. end
  408. let(:condition) do
  409. { 'ticket.state_id' => { 'operator' => 'is', 'value' => Ticket::State.all.pluck(:id) } }
  410. end
  411. let!(:system_address) do
  412. create(:email_address)
  413. end
  414. context 'article with from equal to the a system address' do
  415. let!(:article) do
  416. create(:ticket_article,
  417. ticket: ticket,
  418. from: system_address.email,)
  419. end
  420. it 'does not trigger because of the last article is created my system address' do
  421. expect { Observer::Transaction.commit }.to change { ticket.reload.articles.count }.by(0)
  422. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  423. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  424. end
  425. end
  426. context 'article with reply_to equal to the a system address' do
  427. let!(:article) do
  428. create(:ticket_article,
  429. ticket: ticket,
  430. from: system_address.email,
  431. reply_to: system_address.email,)
  432. end
  433. it 'does not trigger because of the last article is created my system address' do
  434. expect { Observer::Transaction.commit }.to change { ticket.reload.articles.count }.by(0)
  435. expect(Ticket::Article.where(ticket: ticket).last.subject).not_to eq('foo last sender')
  436. expect(Ticket::Article.where(ticket: ticket).last.to).not_to eq(system_address.email)
  437. end
  438. end
  439. end
  440. end
  441. context 'with pre condition current_user.id' do
  442. let(:perform) do
  443. { 'ticket.title'=>{ 'value'=>'triggered' } }
  444. end
  445. let(:user) do
  446. user = create(:agent_user)
  447. user.roles.first.groups << group
  448. user
  449. end
  450. let(:group) { Group.first }
  451. let(:ticket) do
  452. create(:ticket,
  453. title: 'Test Ticket', group: group,
  454. owner_id: user.id, created_by_id: user.id, updated_by_id: user.id)
  455. end
  456. shared_examples 'successful trigger' do |attribute:|
  457. let(:attribute) { attribute }
  458. let(:condition) do
  459. { attribute => { operator: 'is', pre_condition: 'current_user.id', value: '', value_completion: '' } }
  460. end
  461. it "for #{attribute}" do
  462. ticket && trigger
  463. expect { Observer::Transaction.commit }.to change { ticket.reload.title }.to('triggered')
  464. end
  465. end
  466. it_behaves_like 'successful trigger', attribute: 'ticket.updated_by_id'
  467. it_behaves_like 'successful trigger', attribute: 'ticket.owner_id'
  468. end
  469. end