email_parser_spec.rb 51 KB

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