email_parser_spec.rb 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552
  1. # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe Channel::EmailParser, type: :model do
  4. describe '#parse' do
  5. # regression test for issue 2390 - Add a postmaster filter to not show emails with potential issue
  6. describe 'handling HTML links in message content' do
  7. context 'with under 5,000 links' do
  8. it 'parses message content as normal' do
  9. expect(described_class.new.parse(<<~RAW)[:body]).to start_with('<a href="https://zammad.com/"')
  10. From: nicole.braun@zammad.com
  11. Content-Type: text/html
  12. <html><body>
  13. #{Array.new(10) { '<a href="https://zammad.com/">Dummy Link</a>' }.join(' ')}
  14. </body></html>
  15. RAW
  16. end
  17. end
  18. context 'with 5,000+ links' do
  19. it 'replaces message content with error message' do
  20. expect(described_class.new.parse(<<~RAW)).to include('body' => Channel::EmailParser::EXCESSIVE_LINKS_MSG)
  21. From: nicole.braun@zammad.com
  22. Content-Type: text/html
  23. <html><body>
  24. #{Array.new(5001) { '<a href="https://zammad.com/">Dummy Link</a>' }.join(' ')}
  25. </body></html>
  26. RAW
  27. end
  28. end
  29. end
  30. describe 'handling Japanese email in ISO-2022-JP encoding' do
  31. let(:mail_file) { Rails.root.join('test/data/mail/mail091.box') }
  32. let(:raw_mail) { File.read(mail_file) }
  33. let(:parsed) { described_class.new.parse(raw_mail) }
  34. it { expect(parsed['body']).to eq '<div>このアドレスへのメルマガを解除してください。</div>' }
  35. it { expect(parsed['subject']).to eq 'メルマガ解除' }
  36. end
  37. end
  38. describe '#process' do
  39. let(:raw_mail) { File.read(mail_file) }
  40. before { Trigger.destroy_all } # triggers may cause additional articles to be created
  41. describe 'auto-creating new users' do
  42. context 'with one unrecognized email address' do
  43. it 'creates one new user' do
  44. expect { described_class.new.process({}, <<~RAW) }.to change(User, :count).by(1)
  45. From: #{Faker::Internet.unique.email}
  46. RAW
  47. end
  48. end
  49. context 'with a large number of unrecognized recipient addresses' do
  50. it 'never creates more than 40 users' do
  51. expect { described_class.new.process({}, <<~RAW) }.to change(User, :count).by(40)
  52. From: nicole.braun@zammad.org
  53. To: #{Array.new(20) { Faker::Internet.unique.email }.join(', ')}
  54. Cc: #{Array.new(21) { Faker::Internet.unique.email }.join(', ')}
  55. RAW
  56. end
  57. end
  58. end
  59. describe 'auto-updating existing users' do
  60. context 'with a previous email with no real name in the From: header' do
  61. let!(:customer) { described_class.new.process({}, previous_email).first.customer }
  62. let(:previous_email) { <<~RAW.chomp }
  63. From: customer@example.com
  64. To: myzammad@example.com
  65. Subject: test sender name update 1
  66. Some Text
  67. RAW
  68. context 'and a new email with a real name in the From: header' do
  69. let(:new_email) { <<~RAW.chomp }
  70. From: Max Smith <customer@example.com>
  71. To: myzammad@example.com
  72. Subject: test sender name update 2
  73. Some Text
  74. RAW
  75. it 'updates the customer’s #firstname and #lastname' do
  76. expect { described_class.new.process({}, new_email) }
  77. .to change { customer.reload.firstname }.from('').to('Max')
  78. .and change { customer.reload.lastname }.from('').to('Smith')
  79. end
  80. end
  81. end
  82. end
  83. describe 'creating new tickets' do
  84. context 'when subject contains no ticket reference' do
  85. let(:raw_mail) { <<~RAW.chomp }
  86. From: foo@bar.com
  87. To: baz@qux.net
  88. Subject: Foo
  89. Lorem ipsum dolor
  90. RAW
  91. it 'creates a ticket and article' do
  92. expect { described_class.new.process({}, raw_mail) }
  93. .to change(Ticket, :count).by(1)
  94. .and change(Ticket::Article, :count).by_at_least(1)
  95. end
  96. it 'sets #title to email subject' do
  97. described_class.new.process({}, raw_mail)
  98. expect(Ticket.last.title).to eq('Foo')
  99. end
  100. it 'sets #state to "new"' do
  101. described_class.new.process({}, raw_mail)
  102. expect(Ticket.last.state.name).to eq('new')
  103. end
  104. context 'when no channel is given but a group with the :to address exists' do
  105. let!(:email_address) { create(:email_address, email: 'baz@qux.net', channel: nil) }
  106. let!(:group) { create(:group, name: 'baz headquarter', email_address: email_address) }
  107. let!(:channel) do
  108. channel = create(:email_channel, group: group)
  109. email_address.update(channel: channel)
  110. channel
  111. end
  112. it 'sets the group based on the :to field' do
  113. described_class.new.process({}, raw_mail)
  114. expect(Ticket.last.group.id).to eq(group.id)
  115. end
  116. end
  117. context 'when from address matches an existing agent' do
  118. let!(:agent) { create(:agent, email: 'foo@bar.com') }
  119. it 'sets article.sender to "Agent"' do
  120. described_class.new.process({}, raw_mail)
  121. expect(Ticket::Article.last.sender.name).to eq('Agent')
  122. end
  123. it 'sets ticket.state to "new"' do
  124. described_class.new.process({}, raw_mail)
  125. expect(Ticket.last.state.name).to eq('new')
  126. end
  127. end
  128. context 'when from address matches an existing agent customer' do
  129. let!(:agent_customer) { create(:agent_and_customer, email: 'foo@bar.com') }
  130. let!(:ticket) { create(:ticket, customer: agent_customer) }
  131. let!(:raw_email) { <<~RAW.chomp }
  132. From: foo@bar.com
  133. To: myzammad@example.com
  134. Subject: [#{Setting.get('ticket_hook') + Setting.get('ticket_hook_divider') + ticket.number}] test
  135. Lorem ipsum dolor
  136. RAW
  137. it 'sets article.sender to "Customer"' do
  138. described_class.new.process({}, raw_email)
  139. expect(Ticket::Article.last.sender.name).to eq('Customer')
  140. end
  141. end
  142. context 'when from address matches an existing customer' do
  143. let!(:customer) { create(:customer, email: 'foo@bar.com') }
  144. it 'sets article.sender to "Customer"' do
  145. described_class.new.process({}, raw_mail)
  146. expect(Ticket.last.articles.first.sender.name).to eq('Customer')
  147. end
  148. it 'sets ticket.state to "new"' do
  149. described_class.new.process({}, raw_mail)
  150. expect(Ticket.last.state.name).to eq('new')
  151. end
  152. end
  153. context 'when from address is unrecognized' do
  154. it 'sets article.sender to "Customer"' do
  155. described_class.new.process({}, raw_mail)
  156. expect(Ticket.last.articles.first.sender.name).to eq('Customer')
  157. end
  158. end
  159. end
  160. context 'when email contains x-headers' do
  161. let(:raw_mail) { <<~RAW.chomp }
  162. From: foo@bar.com
  163. To: baz@qux.net
  164. Subject: Foo
  165. X-Zammad-Ticket-priority: 3 high
  166. Lorem ipsum dolor
  167. RAW
  168. context 'when channel is not trusted' do
  169. let(:channel) { create(:channel, options: { inbound: { trusted: false } }) }
  170. it 'does not change the priority of the ticket (no channel)' do
  171. described_class.new.process({}, raw_mail)
  172. expect(Ticket.last.priority.name).to eq('2 normal')
  173. end
  174. it 'does not change the priority of the ticket (untrusted)' do
  175. described_class.new.process(channel, raw_mail)
  176. expect(Ticket.last.priority.name).to eq('2 normal')
  177. end
  178. end
  179. context 'when channel is trusted' do
  180. let(:channel) { create(:channel, options: { inbound: { trusted: true } }) }
  181. it 'does not change the priority of the ticket' do
  182. described_class.new.process(channel, raw_mail)
  183. expect(Ticket.last.priority.name).to eq('3 high')
  184. end
  185. end
  186. end
  187. context 'Mentions:' do
  188. let(:agent) { create(:agent) }
  189. let(:raw_mail) { <<~RAW.chomp }
  190. From: foo@bar.com
  191. To: baz@qux.net
  192. Subject: Foo
  193. Lorem ipsum dolor <a data-mention-user-id=\"#{agent.id}\">agent</a>
  194. RAW
  195. it 'creates a ticket and article without mentions and no exception raised' do
  196. expect { described_class.new.process({}, raw_mail) }
  197. .to change(Ticket, :count).by(1)
  198. .and change(Ticket::Article, :count).by_at_least(1)
  199. .and not_change(Mention, :count)
  200. end
  201. end
  202. end
  203. describe 'associating emails to existing tickets' do
  204. let!(:ticket) { create(:ticket) }
  205. let(:ticket_ref) { Setting.get('ticket_hook') + Setting.get('ticket_hook_divider') + ticket.number }
  206. describe 'based on where a ticket reference appears in the message' do
  207. shared_context 'ticket reference in subject' do
  208. let(:raw_mail) { <<~RAW.chomp }
  209. From: me@example.com
  210. To: customer@example.com
  211. Subject: #{ticket_ref}
  212. Lorem ipsum dolor
  213. RAW
  214. end
  215. shared_context 'ticket reference in body' do
  216. let(:raw_mail) { <<~RAW.chomp }
  217. From: me@example.com
  218. To: customer@example.com
  219. Subject: no reference
  220. Lorem ipsum dolor #{ticket_ref}
  221. RAW
  222. end
  223. shared_context 'ticket reference in body (text/html)' do
  224. let(:raw_mail) { <<~RAW.chomp }
  225. From: me@example.com
  226. To: customer@example.com
  227. Subject: no reference
  228. Content-Transfer-Encoding: 7bit
  229. Content-Type: text/html;
  230. <b>Lorem ipsum dolor #{ticket_ref}</b>
  231. RAW
  232. end
  233. shared_context 'ticket reference in text/plain attachment' do
  234. let(:raw_mail) { <<~RAW.chomp }
  235. From: me@example.com
  236. Content-Type: multipart/mixed; boundary="Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2"
  237. Subject: no reference
  238. Date: Sun, 30 Aug 2015 23:20:54 +0200
  239. To: Martin Edenhofer <me@znuny.com>
  240. Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\))
  241. X-Mailer: Apple Mail (2.2104)
  242. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  243. Content-Transfer-Encoding: 7bit
  244. Content-Type: text/plain;
  245. charset=us-ascii
  246. no reference
  247. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  248. Content-Disposition: attachment;
  249. filename=test1.txt
  250. Content-Type: text/plain;
  251. name="test.txt"
  252. Content-Transfer-Encoding: 7bit
  253. Some Text #{ticket_ref}
  254. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--
  255. RAW
  256. end
  257. shared_context 'ticket reference in text/html (as content) attachment' do
  258. let(:raw_mail) { <<~RAW.chomp }
  259. From: me@example.com
  260. Content-Type: multipart/mixed; boundary="Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2"
  261. Subject: no reference
  262. Date: Sun, 30 Aug 2015 23:20:54 +0200
  263. To: Martin Edenhofer <me@znuny.com>
  264. Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\))
  265. X-Mailer: Apple Mail (2.2104)
  266. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  267. Content-Transfer-Encoding: 7bit
  268. Content-Type: text/plain;
  269. charset=us-ascii
  270. no reference
  271. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  272. Content-Disposition: attachment;
  273. filename=test1.txt
  274. Content-Type: text/html;
  275. name="test.txt"
  276. Content-Transfer-Encoding: 7bit
  277. <div>Some Text #{ticket_ref}</div>
  278. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--
  279. RAW
  280. end
  281. shared_context 'ticket reference in text/html (attribute) attachment' do
  282. let(:raw_mail) { <<~RAW.chomp }
  283. From: me@example.com
  284. Content-Type: multipart/mixed; boundary="Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2"
  285. Subject: no reference
  286. Date: Sun, 30 Aug 2015 23:20:54 +0200
  287. To: Martin Edenhofer <me@znuny.com>
  288. Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\))
  289. X-Mailer: Apple Mail (2.2104)
  290. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  291. Content-Transfer-Encoding: 7bit
  292. Content-Type: text/plain;
  293. charset=us-ascii
  294. no reference
  295. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  296. Content-Disposition: attachment;
  297. filename=test1.txt
  298. Content-Type: text/html;
  299. name="test.txt"
  300. Content-Transfer-Encoding: 7bit
  301. <div>Some Text <b data-something="#{ticket_ref}">some text</b></div>
  302. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--
  303. RAW
  304. end
  305. shared_context 'ticket reference in image/jpg attachment' do
  306. let(:raw_mail) { <<~RAW.chomp }
  307. From: me@example.com
  308. Content-Type: multipart/mixed; boundary="Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2"
  309. Subject: no reference
  310. Date: Sun, 30 Aug 2015 23:20:54 +0200
  311. To: Martin Edenhofer <me@znuny.com>
  312. Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\))
  313. X-Mailer: Apple Mail (2.2104)
  314. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  315. Content-Transfer-Encoding: 7bit
  316. Content-Type: text/plain;
  317. charset=us-ascii
  318. no reference
  319. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2
  320. Content-Disposition: attachment;
  321. filename=test1.jpg
  322. Content-Type: image/jpg;
  323. name="test.jpg"
  324. Content-Transfer-Encoding: 7bit
  325. Some Text #{ticket_ref}
  326. --Apple-Mail=_ED77AC8D-FB6F-40E5-8FBE-D41FF5E1BAF2--
  327. RAW
  328. end
  329. shared_context 'ticket reference in In-Reply-To header' do
  330. let(:raw_mail) { <<~RAW.chomp }
  331. From: me@example.com
  332. To: customer@example.com
  333. Subject: no reference
  334. In-Reply-To: #{article.message_id}
  335. Lorem ipsum dolor
  336. RAW
  337. let!(:article) { create(:ticket_article, ticket: ticket, message_id: '<20150830145601.30.608882@edenhofer.zammad.com>') }
  338. end
  339. shared_context 'ticket reference in References header' do
  340. let(:raw_mail) { <<~RAW.chomp }
  341. From: me@example.com
  342. To: customer@example.com
  343. Subject: no reference
  344. References: <DA918CD1-BE9A-4262-ACF6-5001E59291B6@znuny.com> #{article.message_id} <DA918CD1-BE9A-4262-ACF6-5001E59291XX@znuny.com>
  345. Lorem ipsum dolor
  346. RAW
  347. let!(:article) { create(:ticket_article, ticket: ticket, message_id: '<20150830145601.30.608882@edenhofer.zammad.com>') }
  348. end
  349. shared_examples 'adds message to ticket' do
  350. it 'adds message to ticket' do
  351. expect { described_class.new.process({}, raw_mail) }
  352. .to change { ticket.articles.length }.by(1)
  353. end
  354. end
  355. shared_examples 'creates a new ticket' do
  356. it 'creates a new ticket' do
  357. expect { described_class.new.process({}, raw_mail) }
  358. .to change(Ticket, :count).by(1)
  359. .and not_change { ticket.articles.length }
  360. end
  361. end
  362. context 'when not explicitly configured to search anywhere' do
  363. before { Setting.set('postmaster_follow_up_search_in', nil) }
  364. context 'when subject contains ticket reference' do
  365. include_context 'ticket reference in subject'
  366. include_examples 'adds message to ticket'
  367. context 'alongside other, invalid ticket references' do
  368. let(:raw_mail) { <<~RAW.chomp }
  369. From: me@example.com
  370. To: customer@example.com
  371. Subject: [#{Setting.get('ticket_hook') + Setting.get('ticket_hook_divider') + Ticket::Number.generate}] #{ticket_ref}
  372. Lorem ipsum dolor
  373. RAW
  374. include_examples 'adds message to ticket'
  375. end
  376. context 'and ticket is closed' do
  377. before { ticket.update(state: Ticket::State.find_by(name: 'closed')) }
  378. include_examples 'adds message to ticket'
  379. end
  380. context 'but ticket group’s #follow_up_possible attribute is "new_ticket"' do
  381. before { ticket.group.update(follow_up_possible: 'new_ticket') }
  382. context 'and ticket is open' do
  383. include_examples 'adds message to ticket'
  384. end
  385. context 'and ticket is closed' do
  386. before { ticket.update(state: Ticket::State.find_by(name: 'closed')) }
  387. include_examples 'creates a new ticket'
  388. end
  389. context 'and ticket is merged' do
  390. before { ticket.update(state: Ticket::State.find_by(name: 'merged')) }
  391. include_examples 'creates a new ticket'
  392. end
  393. context 'and ticket is removed' do
  394. before { ticket.update(state: Ticket::State.find_by(name: 'removed')) }
  395. include_examples 'creates a new ticket'
  396. end
  397. end
  398. context 'and "ticket_hook" setting is non-default value' do
  399. before { Setting.set('ticket_hook', 'VD-Ticket#') }
  400. include_examples 'adds message to ticket'
  401. end
  402. end
  403. context 'when body contains ticket reference' do
  404. include_context 'ticket reference in body'
  405. include_examples 'creates a new ticket'
  406. end
  407. context 'when text/plain attachment contains ticket reference' do
  408. include_context 'ticket reference in text/plain attachment'
  409. include_examples 'creates a new ticket'
  410. end
  411. context 'when text/html attachment (as content) contains ticket reference' do
  412. include_context 'ticket reference in text/html (as content) attachment'
  413. include_examples 'creates a new ticket'
  414. end
  415. context 'when text/html attachment (attribute) contains ticket reference' do
  416. include_context 'ticket reference in text/html (attribute) attachment'
  417. include_examples 'creates a new ticket'
  418. end
  419. context 'when image/jpg attachment contains ticket reference' do
  420. include_context 'ticket reference in image/jpg attachment'
  421. include_examples 'creates a new ticket'
  422. end
  423. context 'when In-Reply-To header contains article message-id' do
  424. include_context 'ticket reference in In-Reply-To header'
  425. include_examples 'creates a new ticket'
  426. context 'and subject matches article subject' do
  427. let(:raw_mail) { <<~RAW.chomp }
  428. From: customer@example.com
  429. To: me@example.com
  430. Subject: AW: RE: #{article.subject}
  431. In-Reply-To: #{article.message_id}
  432. Lorem ipsum dolor
  433. RAW
  434. include_examples 'adds message to ticket'
  435. end
  436. context 'and "ticket_hook_position" setting is "none"' do
  437. before { Setting.set('ticket_hook_position', 'none') }
  438. let(:raw_mail) { <<~RAW.chomp }
  439. From: customer@example.com
  440. To: me@example.com
  441. Subject: RE: Foo bar
  442. In-Reply-To: #{article.message_id}
  443. Lorem ipsum dolor
  444. RAW
  445. include_examples 'adds message to ticket'
  446. end
  447. end
  448. context 'when References header contains article message-id' do
  449. include_context 'ticket reference in References header'
  450. include_examples 'creates a new ticket'
  451. context 'and Auto-Submitted header reads "auto-replied"' do
  452. let(:raw_mail) { <<~RAW.chomp }
  453. From: me@example.com
  454. To: customer@example.com
  455. Subject: no reference
  456. References: #{article.message_id}
  457. Auto-Submitted: auto-replied
  458. Lorem ipsum dolor
  459. RAW
  460. include_examples 'adds message to ticket'
  461. end
  462. context 'and subject matches article subject' do
  463. let(:raw_mail) { <<~RAW.chomp }
  464. From: customer@example.com
  465. To: me@example.com
  466. Subject: AW: RE: #{article.subject}
  467. References: #{article.message_id}
  468. Lorem ipsum dolor
  469. RAW
  470. include_examples 'adds message to ticket'
  471. end
  472. context 'and "ticket_hook_position" setting is "none"' do
  473. before { Setting.set('ticket_hook_position', 'none') }
  474. let(:raw_mail) { <<~RAW.chomp }
  475. From: customer@example.com
  476. To: me@example.com
  477. Subject: RE: Foo bar
  478. References: #{article.message_id}
  479. Lorem ipsum dolor
  480. RAW
  481. include_examples 'adds message to ticket'
  482. end
  483. end
  484. end
  485. context 'when configured to search body' do
  486. before { Setting.set('postmaster_follow_up_search_in', 'body') }
  487. context 'when subject contains ticket reference' do
  488. include_context 'ticket reference in subject'
  489. include_examples 'adds message to ticket'
  490. end
  491. context 'when body contains ticket reference' do
  492. context 'in visible text' do
  493. include_context 'ticket reference in body'
  494. include_examples 'adds message to ticket'
  495. end
  496. context 'as part of a larger word' do
  497. let(:ticket_ref) { "Foo#{Setting.get('ticket_hook')}#{Setting.get('ticket_hook_divider')}#{ticket.number}bar" }
  498. include_context 'ticket reference in body'
  499. include_examples 'creates a new ticket'
  500. end
  501. context 'between html tags' do
  502. include_context 'ticket reference in body (text/html)'
  503. include_examples 'adds message to ticket'
  504. end
  505. context 'in html attributes' do
  506. let(:ticket_ref) { %(<table bgcolor="#{Setting.get('ticket_hook')}#{Setting.get('ticket_hook_divider')}#{ticket.number}"> </table>) }
  507. include_context 'ticket reference in body (text/html)'
  508. include_examples 'creates a new ticket'
  509. end
  510. end
  511. context 'when text/plain attachment contains ticket reference' do
  512. include_context 'ticket reference in text/plain attachment'
  513. include_examples 'creates a new ticket'
  514. end
  515. context 'when text/html attachment (as content) contains ticket reference' do
  516. include_context 'ticket reference in text/html (as content) attachment'
  517. include_examples 'creates a new ticket'
  518. end
  519. context 'when text/html attachment (attribute) contains ticket reference' do
  520. include_context 'ticket reference in text/html (attribute) attachment'
  521. include_examples 'creates a new ticket'
  522. end
  523. context 'when image/jpg attachment contains ticket reference' do
  524. include_context 'ticket reference in image/jpg attachment'
  525. include_examples 'creates a new ticket'
  526. end
  527. context 'when In-Reply-To header contains article message-id' do
  528. include_context 'ticket reference in In-Reply-To header'
  529. include_examples 'creates a new ticket'
  530. context 'and Auto-Submitted header reads "auto-replied"' do
  531. let(:raw_mail) { <<~RAW.chomp }
  532. From: me@example.com
  533. To: customer@example.com
  534. Subject: no reference
  535. References: #{article.message_id}
  536. Auto-Submitted: auto-replied
  537. Lorem ipsum dolor
  538. RAW
  539. include_examples 'adds message to ticket'
  540. end
  541. end
  542. context 'when References header contains article message-id' do
  543. include_context 'ticket reference in References header'
  544. include_examples 'creates a new ticket'
  545. end
  546. end
  547. context 'when configured to search attachments' do
  548. before { Setting.set('postmaster_follow_up_search_in', 'attachment') }
  549. context 'when subject contains ticket reference' do
  550. include_context 'ticket reference in subject'
  551. include_examples 'adds message to ticket'
  552. end
  553. context 'when body contains ticket reference' do
  554. include_context 'ticket reference in body'
  555. include_examples 'creates a new ticket'
  556. end
  557. context 'when text/plain attachment contains ticket reference' do
  558. include_context 'ticket reference in text/plain attachment'
  559. include_examples 'adds message to ticket'
  560. end
  561. context 'when text/html attachment (as content) contains ticket reference' do
  562. include_context 'ticket reference in text/html (as content) attachment'
  563. include_examples 'adds message to ticket'
  564. end
  565. context 'when text/html attachment (attribute) contains ticket reference' do
  566. include_context 'ticket reference in text/html (attribute) attachment'
  567. include_examples 'creates a new ticket'
  568. end
  569. context 'when image/jpg attachment contains ticket reference' do
  570. include_context 'ticket reference in image/jpg attachment'
  571. include_examples 'creates a new ticket'
  572. end
  573. context 'when In-Reply-To header contains article message-id' do
  574. include_context 'ticket reference in In-Reply-To header'
  575. include_examples 'creates a new ticket'
  576. end
  577. context 'when References header contains article message-id' do
  578. include_context 'ticket reference in References header'
  579. include_examples 'creates a new ticket'
  580. context 'and Auto-Submitted header reads "auto-replied"' do
  581. let(:raw_mail) { <<~RAW.chomp }
  582. From: me@example.com
  583. To: customer@example.com
  584. Subject: no reference
  585. References: #{article.message_id}
  586. Auto-Submitted: auto-replied
  587. Lorem ipsum dolor
  588. RAW
  589. include_examples 'adds message to ticket'
  590. end
  591. end
  592. end
  593. context 'when configured to search headers' do
  594. before { Setting.set('postmaster_follow_up_search_in', 'references') }
  595. context 'when subject contains ticket reference' do
  596. include_context 'ticket reference in subject'
  597. include_examples 'adds message to ticket'
  598. end
  599. context 'when body contains ticket reference' do
  600. include_context 'ticket reference in body'
  601. include_examples 'creates a new ticket'
  602. end
  603. context 'when text/plain attachment contains ticket reference' do
  604. include_context 'ticket reference in text/plain attachment'
  605. include_examples 'creates a new ticket'
  606. end
  607. context 'when text/html attachment (as content) contains ticket reference' do
  608. include_context 'ticket reference in text/html (as content) attachment'
  609. include_examples 'creates a new ticket'
  610. end
  611. context 'when text/html attachment (attribute) contains ticket reference' do
  612. include_context 'ticket reference in text/html (attribute) attachment'
  613. include_examples 'creates a new ticket'
  614. end
  615. context 'when image/jpg attachment contains ticket reference' do
  616. include_context 'ticket reference in image/jpg attachment'
  617. include_examples 'creates a new ticket'
  618. end
  619. context 'when In-Reply-To header contains article message-id' do
  620. include_context 'ticket reference in In-Reply-To header'
  621. include_examples 'adds message to ticket'
  622. end
  623. context 'when References header contains article message-id' do
  624. include_context 'ticket reference in References header'
  625. include_examples 'adds message to ticket'
  626. context 'that matches two separate tickets' do
  627. let!(:newer_ticket) { create(:ticket) }
  628. let!(:newer_article) { create(:ticket_article, ticket: newer_ticket, message_id: article.message_id) }
  629. it 'returns more recently created ticket' do
  630. expect(described_class.new.process({}, raw_mail).first).to eq(newer_ticket)
  631. end
  632. it 'adds message to more recently created ticket' do
  633. expect { described_class.new.process({}, raw_mail) }
  634. .to change { newer_ticket.articles.count }.by(1)
  635. .and not_change { ticket.articles.count }
  636. end
  637. end
  638. context 'and Auto-Submitted header reads "auto-replied"' do
  639. let(:raw_mail) { <<~RAW.chomp }
  640. From: me@example.com
  641. To: customer@example.com
  642. Subject: no reference
  643. References: #{article.message_id}
  644. Auto-Submitted: auto-replied
  645. Lorem ipsum dolor
  646. RAW
  647. include_examples 'adds message to ticket'
  648. end
  649. end
  650. end
  651. context 'when configured to search everything' do
  652. before { Setting.set('postmaster_follow_up_search_in', %w[body attachment references]) }
  653. context 'when subject contains ticket reference' do
  654. include_context 'ticket reference in subject'
  655. include_examples 'adds message to ticket'
  656. end
  657. context 'when body contains ticket reference' do
  658. include_context 'ticket reference in body'
  659. include_examples 'adds message to ticket'
  660. end
  661. context 'when text/plain attachment contains ticket reference' do
  662. include_context 'ticket reference in text/plain attachment'
  663. include_examples 'adds message to ticket'
  664. end
  665. context 'when text/html attachment (as content) contains ticket reference' do
  666. include_context 'ticket reference in text/html (as content) attachment'
  667. include_examples 'adds message to ticket'
  668. end
  669. context 'when text/html attachment (attribute) contains ticket reference' do
  670. include_context 'ticket reference in text/html (attribute) attachment'
  671. include_examples 'creates a new ticket'
  672. end
  673. context 'when image/jpg attachment contains ticket reference' do
  674. include_context 'ticket reference in image/jpg attachment'
  675. include_examples 'creates a new ticket'
  676. end
  677. context 'when In-Reply-To header contains article message-id' do
  678. include_context 'ticket reference in In-Reply-To header'
  679. include_examples 'adds message to ticket'
  680. end
  681. context 'when References header contains article message-id' do
  682. include_context 'ticket reference in References header'
  683. include_examples 'adds message to ticket'
  684. context 'and Auto-Submitted header reads "auto-replied"' do
  685. let(:raw_mail) { <<~RAW.chomp }
  686. From: me@example.com
  687. To: customer@example.com
  688. Subject: no reference
  689. References: #{article.message_id}
  690. Auto-Submitted: auto-replied
  691. Lorem ipsum dolor
  692. RAW
  693. include_examples 'adds message to ticket'
  694. end
  695. end
  696. end
  697. end
  698. context 'for a closed ticket' do
  699. let(:ticket) { create(:ticket, state_name: 'closed') }
  700. let(:raw_mail) { <<~RAW.chomp }
  701. From: me@example.com
  702. To: customer@example.com
  703. Subject: #{ticket_ref}
  704. Lorem ipsum dolor
  705. RAW
  706. it 'reopens it' do
  707. expect { described_class.new.process({}, raw_mail) }
  708. .to change { ticket.reload.state.name }.to('open')
  709. end
  710. context 'when group has follow_up_assignment true' do
  711. let(:group) { create(:group, follow_up_assignment: true) }
  712. let(:agent) { create(:agent, groups: [group]) }
  713. let(:ticket) { create(:ticket, state_name: 'closed', owner: agent, group: group) }
  714. it 'does not change the owner' do
  715. expect { described_class.new.process({}, raw_mail) }
  716. .not_to change { ticket.reload.owner.login }
  717. end
  718. end
  719. context 'when group has follow_up_assignment false' do
  720. let(:group) { create(:group, follow_up_assignment: false) }
  721. let(:agent) { create(:agent, groups: [group]) }
  722. let(:ticket) { create(:ticket, state_name: 'closed', owner: agent, group: group) }
  723. it 'does change the owner' do
  724. expect { described_class.new.process({}, raw_mail) }
  725. .to change { ticket.reload.owner.login }.to eq(User.find(1).login)
  726. end
  727. end
  728. end
  729. end
  730. describe 'assigning ticket.customer' do
  731. let(:agent) { create(:agent) }
  732. let(:customer) { create(:customer) }
  733. let(:raw_mail) { <<~RAW.chomp }
  734. From: #{agent.email}
  735. To: #{customer.email}
  736. Subject: Foo
  737. Lorem ipsum dolor
  738. RAW
  739. context 'when "postmaster_sender_is_agent_search_for_customer" setting is true (default)' do
  740. it 'sets ticket.customer to user with To: email' do
  741. expect { described_class.new.process({}, raw_mail) }
  742. .to change(Ticket, :count).by(1)
  743. expect(Ticket.last.customer).to eq(customer)
  744. end
  745. end
  746. context 'when "postmaster_sender_is_agent_search_for_customer" setting is false' do
  747. before { Setting.set('postmaster_sender_is_agent_search_for_customer', false) }
  748. it 'sets ticket.customer to user with To: email' do
  749. expect { described_class.new.process({}, raw_mail) }
  750. .to change(Ticket, :count).by(1)
  751. expect(Ticket.last.customer).to eq(agent)
  752. end
  753. end
  754. end
  755. describe 'formatting to/from addresses' do
  756. # see https://github.com/zammad/zammad/issues/2198
  757. context 'when sender address contains spaces (#2198)' do
  758. let(:mail_file) { Rails.root.join('test/data/mail/mail071.box') }
  759. let(:sender_email) { 'powerquadrantsystem@example.com' }
  760. it 'removes them before creating a new user' do
  761. expect { described_class.new.process({}, raw_mail) }
  762. .to change { User.exists?(email: sender_email) }
  763. end
  764. it 'marks new user email as invalid' do
  765. described_class.new.process({}, raw_mail)
  766. expect(User.find_by(email: sender_email).preferences)
  767. .to include('mail_delivery_failed' => true)
  768. .and include('mail_delivery_failed_reason' => 'invalid email')
  769. .and include('mail_delivery_failed_data' => a_kind_of(ActiveSupport::TimeWithZone))
  770. end
  771. end
  772. # see https://github.com/zammad/zammad/issues/2254
  773. context 'when sender address contains > (#2254)' do
  774. let(:mail_file) { Rails.root.join('test/data/mail/mail076.box') }
  775. let(:sender_email) { 'millionslotteryspaintransfer@example.com' }
  776. it 'removes them before creating a new user' do
  777. expect { described_class.new.process({}, raw_mail) }
  778. .to change { User.exists?(email: sender_email) }
  779. end
  780. it 'marks new user email as invalid' do
  781. described_class.new.process({}, raw_mail)
  782. expect(User.find_by(email: sender_email).preferences)
  783. .to include('mail_delivery_failed' => true)
  784. .and include('mail_delivery_failed_reason' => 'invalid email')
  785. .and include('mail_delivery_failed_data' => a_kind_of(ActiveSupport::TimeWithZone))
  786. end
  787. end
  788. end
  789. describe 'signature detection' do
  790. let(:raw_mail) { header + File.read(message_file) }
  791. let(:header) { <<~HEADER }
  792. From: Bob.Smith@music.com
  793. To: test@zammad.org
  794. Subject: test
  795. HEADER
  796. context 'for emails from an unrecognized email address' do
  797. let(:message_file) { Rails.root.join('test/data/email_signature_detection/client_a_1.txt') }
  798. it 'does not detect signatures' do
  799. described_class.new.process({}, raw_mail)
  800. expect { Scheduler.worker(true) }
  801. .to not_change { Ticket.last.customer.preferences[:signature_detection] }.from(nil)
  802. .and not_change { Ticket.last.articles.first.preferences[:signature_detection] }.from(nil)
  803. end
  804. end
  805. context 'for emails from a previously processed sender' do
  806. before do
  807. described_class.new.process({}, header + File.read(previous_message_file))
  808. end
  809. let(:previous_message_file) { Rails.root.join('test/data/email_signature_detection/client_a_1.txt') }
  810. let(:message_file) { Rails.root.join('test/data/email_signature_detection/client_a_2.txt') }
  811. it 'sets detected signature on user (in a background job)' do
  812. described_class.new.process({}, raw_mail)
  813. expect { Scheduler.worker(true) }
  814. .to change { Ticket.last.customer.preferences[:signature_detection] }
  815. end
  816. it 'sets line of detected signature on article (in a background job)' do
  817. described_class.new.process({}, raw_mail)
  818. expect { Scheduler.worker(true) }
  819. .to change { Ticket.last.articles.first.preferences[:signature_detection] }.to(20)
  820. end
  821. end
  822. end
  823. describe 'charset handling' do
  824. # see https://github.com/zammad/zammad/issues/2224
  825. context 'when header specifies Windows-1258 charset (#2224)' do
  826. let(:mail_file) { Rails.root.join('test/data/mail/mail072.box') }
  827. it 'does not raise Encoding::ConverterNotFoundError' do
  828. expect { described_class.new.process({}, raw_mail) }
  829. .not_to raise_error
  830. end
  831. end
  832. context 'when attachment for follow up check contains invalid charsets (#2808)' do
  833. let(:mail_file) { Rails.root.join('test/data/mail/mail085.box') }
  834. before { Setting.set('postmaster_follow_up_search_in', %w[attachment body]) }
  835. it 'does not raise Encoding::CompatibilityError:' do
  836. expect { described_class.new.process({}, raw_mail) }
  837. .not_to raise_error
  838. end
  839. end
  840. end
  841. describe 'attachment handling' do
  842. context 'with header "Content-Transfer-Encoding: x-uuencode"' do
  843. let(:mail_file) { Rails.root.join('test/data/mail/mail078-content_transfer_encoding_x_uuencode.box') }
  844. let(:article) { described_class.new.process({}, raw_mail).second }
  845. it 'does not raise RuntimeError' do
  846. expect { described_class.new.process({}, raw_mail) }
  847. .not_to raise_error
  848. end
  849. it 'parses the content correctly' do
  850. expect(article.attachments.first.filename).to eq('PGP_Cmts_on_12-14-01_Pkg.txt')
  851. expect(article.attachments.first.content).to eq('Hello Zammad')
  852. end
  853. end
  854. # https://github.com/zammad/zammad/issues/3529
  855. context 'Attachments sent by Zammad not shown in Outlook' do
  856. subject(:mail) do
  857. Channel::EmailBuild.build(
  858. from: 'sender@example.com',
  859. to: 'recipient@example.com',
  860. body: body,
  861. content_type: 'text/html',
  862. attachments: Store.where(filename: 'super-seven.jpg')
  863. )
  864. end
  865. let(:mail_file) { Rails.root.join('test/data/mail/mail101.box') }
  866. before do
  867. described_class.new.process({}, raw_mail)
  868. end
  869. context 'when no reference in body' do
  870. let(:body) { 'no reference here' }
  871. it 'does not have content disposition inline' do
  872. expect(mail.to_s).to include('Content-Disposition: attachment').and not_include('Content-Disposition: inline')
  873. end
  874. end
  875. context 'when reference in body' do
  876. let(:body) { %(somebody with some text <img src="cid:#{Store.find_by(filename: 'super-seven.jpg').preferences['Content-ID']}">) }
  877. it 'does have content disposition inline' do
  878. expect(mail.to_s).to include('Content-Disposition: inline').and not_include('Content-Disposition: attachment')
  879. end
  880. context 'when encoded as ISO-8859-1' do
  881. let(:body) { super().encode('ISO-8859-1') }
  882. it 'does not raise exception' do
  883. expect { mail.to_s }.not_to raise_error
  884. end
  885. end
  886. end
  887. end
  888. end
  889. describe 'inline image handling' do
  890. # see https://github.com/zammad/zammad/issues/2486
  891. context 'when image is large but not resizable' do
  892. let(:mail_file) { Rails.root.join('test/data/mail/mail079.box') }
  893. let(:attachment) { article.attachments.to_a.find { |i| i.filename == 'a.jpg' } }
  894. let(:article) { described_class.new.process({}, raw_mail).second }
  895. it "doesn't set resizable preference" do
  896. expect(attachment.filename).to eq('a.jpg')
  897. expect(attachment.preferences).not_to include('resizable' => true)
  898. end
  899. end
  900. end
  901. describe 'ServiceNow handling' do
  902. context 'new Ticket' do
  903. let(:mail_file) { Rails.root.join('test/data/mail/mail089.box') }
  904. it 'creates an ExternalSync reference' do
  905. described_class.new.process({}, raw_mail)
  906. expect(ExternalSync.last).to have_attributes(
  907. source: 'ServiceNow-example@service-now.com',
  908. source_id: 'INC678439',
  909. object: 'Ticket',
  910. o_id: Ticket.last.id,
  911. )
  912. end
  913. end
  914. context 'follow up' do
  915. let(:mail_file) { Rails.root.join('test/data/mail/mail090.box') }
  916. let(:ticket) { create(:ticket) }
  917. let!(:external_sync) do
  918. create(:external_sync,
  919. source: 'ServiceNow-example@service-now.com',
  920. source_id: 'INC678439',
  921. object: 'Ticket',
  922. o_id: ticket.id,)
  923. end
  924. it 'adds Article to existing Ticket' do
  925. expect { described_class.new.process({}, raw_mail) }.to change { ticket.reload.articles.count }
  926. end
  927. context 'key insensitive sender address' do
  928. let(:raw_mail) { super().gsub('example@service-now.com', 'Example@Service-Now.com') }
  929. it 'adds Article to existing Ticket' do
  930. expect { described_class.new.process({}, raw_mail) }.to change { ticket.reload.articles.count }
  931. end
  932. end
  933. end
  934. end
  935. describe 'Jira handling' do
  936. context 'new Ticket' do
  937. let(:mail_file) { Rails.root.join('test/data/mail/mail103.box') }
  938. it 'creates an ExternalSync reference' do
  939. described_class.new.process({}, raw_mail)
  940. expect(ExternalSync.last).to have_attributes(
  941. source: 'Jira-example@jira.com',
  942. source_id: 'SYS-422',
  943. object: 'Ticket',
  944. o_id: Ticket.last.id,
  945. )
  946. end
  947. end
  948. context 'follow up' do
  949. let(:mail_file) { Rails.root.join('test/data/mail/mail104.box') }
  950. let(:ticket) { create(:ticket) }
  951. let!(:external_sync) do
  952. create(:external_sync,
  953. source: 'Jira-example@jira.com',
  954. source_id: 'SYS-422',
  955. object: 'Ticket',
  956. o_id: ticket.id,)
  957. end
  958. it 'adds Article to existing Ticket' do
  959. expect { described_class.new.process({}, raw_mail) }.to change { ticket.reload.articles.count }
  960. end
  961. context 'key insensitive sender address' do
  962. let(:raw_mail) { super().gsub('example@service-now.com', 'Example@Service-Now.com') }
  963. it 'adds Article to existing Ticket' do
  964. expect { described_class.new.process({}, raw_mail) }.to change { ticket.reload.articles.count }
  965. end
  966. end
  967. end
  968. end
  969. describe 'XSS protection' do
  970. before do
  971. # XSS processing may run into a timeout on slow CI systems, so turn the timeout off for the test.
  972. stub_const("#{HtmlSanitizer}::PROCESSING_TIMEOUT", nil)
  973. end
  974. let(:article) { described_class.new.process({}, raw_mail).second }
  975. let(:raw_mail) { <<~RAW.chomp }
  976. From: ME Bob <me@example.com>
  977. To: customer@example.com
  978. Subject: some subject
  979. Content-Type: #{content_type}
  980. MIME-Version: 1.0
  981. no HTML <script type="text/javascript">alert(\'XSS\')</script>
  982. RAW
  983. context 'for Content-Type: text/html' do
  984. let(:content_type) { 'text/html' }
  985. it 'removes injected <script> tags from body' do
  986. expect(article.body).to eq('no HTML')
  987. end
  988. end
  989. context 'for Content-Type: text/plain' do
  990. let(:content_type) { 'text/plain' }
  991. it 'leaves body as-is' do
  992. expect(article.body).to eq(<<~SANITIZED.chomp)
  993. no HTML <script type="text/javascript">alert(\'XSS\')</script>
  994. SANITIZED
  995. end
  996. end
  997. end
  998. context 'for “delivery failed” notifications (a.k.a. bounce messages)' do
  999. let(:ticket) { article.ticket }
  1000. let(:article) { create(:ticket_article, sender_name: 'Agent', message_id: message_id) }
  1001. let(:message_id) { raw_mail[%r{(?<=^(References|Message-ID): )\S*}] }
  1002. context 'with future retries (delayed)' do
  1003. let(:mail_file) { Rails.root.join('test/data/mail/mail078.box') }
  1004. context 'on a closed ticket' do
  1005. before { ticket.update(state: Ticket::State.find_by(name: 'closed')) }
  1006. it 'sets #preferences on resulting ticket to { "send-auto-responses" => false, "is-auto-reponse" => true }' do
  1007. article = described_class.new.process({}, raw_mail).second
  1008. expect(article.preferences)
  1009. .to include('send-auto-response' => false, 'is-auto-response' => true)
  1010. end
  1011. it 'returns a Mail object with an x-zammad-out-of-office header' do
  1012. output_mail = described_class.new.process({}, raw_mail).last
  1013. expect(output_mail).to include('x-zammad-out-of-office': true)
  1014. end
  1015. it 'finds the article referenced in the bounce message headers, then adds the bounce message to its ticket' do
  1016. expect { described_class.new.process({}, raw_mail) }
  1017. .to change { ticket.articles.count }.by(1)
  1018. end
  1019. it 'does not re-open the ticket' do
  1020. expect { described_class.new.process({}, raw_mail) }
  1021. .not_to change { ticket.reload.state.name }.from('closed')
  1022. end
  1023. end
  1024. end
  1025. context 'with no future retries (undeliverable): sample input 1' do
  1026. let(:mail_file) { Rails.root.join('test/data/mail/mail033-undelivered-mail-returned-to-sender.box') }
  1027. context 'for original message sent by Agent' do
  1028. it 'sets #preferences on resulting ticket to { "send-auto-responses" => false, "is-auto-reponse" => true }' do
  1029. article = described_class.new.process({}, raw_mail).second
  1030. expect(article.preferences)
  1031. .to include('send-auto-response' => false, 'is-auto-response' => true)
  1032. end
  1033. it 'finds the article referenced in the bounce message headers, then adds the bounce message to its ticket' do
  1034. expect { described_class.new.process({}, raw_mail) }
  1035. .to change { ticket.articles.count }.by(1)
  1036. end
  1037. it 'does not alter the ticket state' do
  1038. expect { described_class.new.process({}, raw_mail) }
  1039. .not_to change { ticket.reload.state.name }.from('open')
  1040. end
  1041. end
  1042. context 'for original message sent by Customer' do
  1043. let(:article) { create(:ticket_article, sender_name: 'Customer', message_id: message_id) }
  1044. it 'sets #preferences on resulting ticket to { "send-auto-responses" => false, "is-auto-reponse" => true }' do
  1045. article = described_class.new.process({}, raw_mail).second
  1046. expect(article.preferences)
  1047. .to include('send-auto-response' => false, 'is-auto-response' => true)
  1048. end
  1049. it 'finds the article referenced in the bounce message headers, then adds the bounce message to its ticket' do
  1050. expect { described_class.new.process({}, raw_mail) }
  1051. .to change { ticket.articles.count }.by(1)
  1052. end
  1053. it 'does not alter the ticket state' do
  1054. expect { described_class.new.process({}, raw_mail) }
  1055. .not_to change { ticket.reload.state.name }.from('new')
  1056. end
  1057. end
  1058. end
  1059. context 'with no future retries (undeliverable): sample input 2' do
  1060. let(:mail_file) { Rails.root.join('test/data/mail/mail055.box') }
  1061. it 'finds the article referenced in the bounce message headers, then adds the bounce message to its ticket' do
  1062. expect { described_class.new.process({}, raw_mail) }
  1063. .to change { ticket.articles.count }.by(1)
  1064. end
  1065. it 'does not alter the ticket state' do
  1066. expect { described_class.new.process({}, raw_mail) }
  1067. .not_to change { ticket.reload.state.name }.from('open')
  1068. end
  1069. end
  1070. end
  1071. context 'for “out-of-office” notifications (a.k.a. auto-response messages)' do
  1072. let(:raw_mail) { <<~RAW.chomp }
  1073. From: me@example.com
  1074. To: customer@example.com
  1075. Subject: #{subject_line}
  1076. Some Text
  1077. RAW
  1078. let(:subject_line) { 'Lorem ipsum dolor' }
  1079. it 'applies the OutOfOfficeCheck filter to given message' do
  1080. expect(Channel::Filter::OutOfOfficeCheck)
  1081. .to receive(:run)
  1082. .with(kind_of(Hash), hash_including(subject: subject_line), kind_of(Hash))
  1083. described_class.new.process({}, raw_mail)
  1084. end
  1085. context 'on an existing, closed ticket' do
  1086. let(:ticket) { create(:ticket, state_name: 'closed') }
  1087. let(:subject_line) { ticket.subject_build('Lorem ipsum dolor') }
  1088. context 'when OutOfOfficeCheck filter applies x-zammad-out-of-office: false' do
  1089. before do
  1090. allow(Channel::Filter::OutOfOfficeCheck)
  1091. .to receive(:run) { |_, mail_hash| mail_hash[:'x-zammad-out-of-office'] = false }
  1092. end
  1093. it 're-opens a closed ticket' do
  1094. expect { described_class.new.process({}, raw_mail) }
  1095. .to not_change(Ticket, :count)
  1096. .and change { ticket.reload.state.name }.to('open')
  1097. end
  1098. end
  1099. context 'when OutOfOfficeCheck filter applies x-zammad-out-of-office: true' do
  1100. before do
  1101. allow(Channel::Filter::OutOfOfficeCheck)
  1102. .to receive(:run) { |_, mail_hash| mail_hash[:'x-zammad-out-of-office'] = true }
  1103. end
  1104. it 'does not re-open a closed ticket' do
  1105. expect { described_class.new.process({}, raw_mail) }
  1106. .to not_change(Ticket, :count)
  1107. .and not_change { ticket.reload.state.name }
  1108. end
  1109. end
  1110. end
  1111. end
  1112. describe 'suppressing normal Ticket::Article callbacks' do
  1113. context 'from sender: "Agent"' do
  1114. let(:agent) { create(:agent) }
  1115. it 'does not dispatch an email on article creation' do
  1116. expect(TicketArticleCommunicateEmailJob).not_to receive(:perform_later)
  1117. described_class.new.process({}, <<~RAW.chomp)
  1118. From: #{agent.email}
  1119. To: customer@example.com
  1120. Subject: some subject
  1121. Some Text
  1122. RAW
  1123. end
  1124. end
  1125. end
  1126. end
  1127. describe '#compose_postmaster_reply' do
  1128. let(:raw_incoming_mail) { File.read(Rails.root.join('test/data/mail/mail010.box')) }
  1129. shared_examples 'postmaster reply' do
  1130. it 'composes postmaster reply' do
  1131. reply = described_class.new.send(:compose_postmaster_reply, raw_incoming_mail, locale)
  1132. expect(reply[:to]).to eq('smith@example.com')
  1133. expect(reply[:content_type]).to eq('text/plain')
  1134. expect(reply[:subject]).to eq(expected_subject)
  1135. expect(reply[:body]).to eq(expected_body)
  1136. end
  1137. end
  1138. context 'for English locale (en)' do
  1139. include_examples 'postmaster reply' do
  1140. let(:locale) { 'en' }
  1141. let(:expected_subject) { '[undeliverable] Message too large' }
  1142. let(:expected_body) do
  1143. body = <<~BODY
  1144. Dear Smith Sepp,
  1145. Unfortunately your email titled \"Gruß aus Oberalteich\" could not be delivered to one or more recipients.
  1146. Your message was 0.01 MB but we only accept messages up to 10 MB.
  1147. Please reduce the message size and try again. Thank you for your understanding.
  1148. Regretfully,
  1149. Postmaster of zammad.example.com
  1150. BODY
  1151. body.gsub(%r{\n}, "\r\n")
  1152. end
  1153. end
  1154. end
  1155. context 'for German locale (de)' do
  1156. include_examples 'postmaster reply' do
  1157. let(:locale) { 'de' }
  1158. let(:expected_subject) { '[Unzustellbar] Nachricht zu groß' }
  1159. let(:expected_body) do
  1160. body = <<~BODY
  1161. Hallo Smith Sepp,
  1162. Ihre E-Mail mit dem Betreff \"Gruß aus Oberalteich\" konnte nicht an einen oder mehrere Empfänger zugestellt werden.
  1163. Die Nachricht hatte eine Größe von 0.01 MB, wir akzeptieren jedoch nur E-Mails mit einer Größe von bis zu 10 MB.
  1164. Bitte reduzieren Sie die Größe Ihrer Nachricht und versuchen Sie es erneut. Vielen Dank für Ihr Verständnis.
  1165. Mit freundlichen Grüßen
  1166. Postmaster von zammad.example.com
  1167. BODY
  1168. body.gsub(%r{\n}, "\r\n")
  1169. end
  1170. end
  1171. end
  1172. end
  1173. describe '#mail_to_group' do
  1174. context 'when EmailAddress exists' do
  1175. context 'when gives address matches exactly' do
  1176. let(:group) { create(:group) }
  1177. let(:channel) { create(:email_channel, group: group) }
  1178. let!(:email_address) { create(:email_address, channel: channel) }
  1179. it 'returns the Channel Group' do
  1180. expect(described_class.mail_to_group(email_address.email)).to eq(group)
  1181. end
  1182. end
  1183. context 'when gives address matches key insensitive' do
  1184. let(:group) { create(:group) }
  1185. let(:channel) { create(:email_channel, group: group) }
  1186. let(:address) { 'KeyInsensitive@example.COM' }
  1187. let!(:email_address) { create(:email_address, email: address, channel: channel) }
  1188. it 'returns the Channel Group' do
  1189. expect(described_class.mail_to_group(address)).to eq(group)
  1190. end
  1191. end
  1192. context 'when no Channel is assigned' do
  1193. let!(:email_address) { create(:email_address, channel: nil) }
  1194. it 'returns nil' do
  1195. expect(described_class.mail_to_group(email_address.email)).to be_nil
  1196. end
  1197. end
  1198. context 'when Channel has no Group assigned' do
  1199. let(:channel) { create(:email_channel, group: nil) }
  1200. let!(:email_address) { create(:email_address, channel: channel) }
  1201. it 'returns nil' do
  1202. expect(described_class.mail_to_group(email_address.email)).to be_nil
  1203. end
  1204. end
  1205. end
  1206. context 'when given address is not parse-able' do
  1207. let(:address) { 'this_is_not_a_valid_email_address' }
  1208. it 'returns nil' do
  1209. expect(described_class.mail_to_group(address)).to be_nil
  1210. end
  1211. end
  1212. end
  1213. end