email_build_test.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. require 'test_helper'
  3. class EmailBuildTest < ActiveSupport::TestCase
  4. test 'document complete check' do
  5. html = '<b>test</b>'
  6. result = Channel::EmailBuild.html_complete_check(html)
  7. assert(result.start_with?('<!DOCTYPE'), 'test 1')
  8. assert(result !~ %r{^.+?<!DOCTYPE}, 'test 1')
  9. assert(result.include?('<html>'), 'test 1')
  10. assert(result.include?('font-family'), 'test 1')
  11. assert(result.include?('<b>test</b>'), 'test 1')
  12. html = 'invalid <!DOCTYPE html><html><b>test</b></html>'
  13. result = Channel::EmailBuild.html_complete_check(html)
  14. assert(result !~ %r{^<!DOCTYPE}, 'test 2')
  15. assert(result =~ %r{^.+?<!DOCTYPE}, 'test 2')
  16. assert(result.include?('<html>'), 'test 2')
  17. assert(result !~ %r{font-family}, 'test 2')
  18. assert(result.include?('<b>test</b>'), 'test 2')
  19. # Issue #1230, missing backslashes
  20. # 'Test URL: \\storage\project\100242-Inc'
  21. html = '<b>Test URL</b>: \\\\storage\\project\\100242-Inc'
  22. result = Channel::EmailBuild.html_complete_check(html)
  23. assert(result.include?(html), 'backslashes must be kept')
  24. end
  25. test 'html email + attachment check' do
  26. html = <<~MSG_HTML.chomp
  27. <!DOCTYPE html>
  28. <html>
  29. <head>
  30. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  31. </head>
  32. <body style="font-family:Geneva,Helvetica,Arial,sans-serif; font-size: 12px;">
  33. <div>&gt; Welcome!</div><div>&gt;</div><div>&gt; Thank you for installing Zammad. äöüß</div><div>&gt;</div>
  34. </body>
  35. </html>
  36. MSG_HTML
  37. mail = Channel::EmailBuild.build(
  38. from: 'sender@example.com',
  39. to: 'recipient@example.com',
  40. body: html,
  41. content_type: 'text/html',
  42. attachments: [
  43. {
  44. 'Mime-Type' => 'image/png',
  45. :content => 'xxx',
  46. :filename => 'somename.png'
  47. }
  48. ],
  49. )
  50. text_should = <<~MSG_TEXT.chomp
  51. > Welcome!\r
  52. >\r
  53. > Thank you for installing Zammad. äöüß\r
  54. >\r
  55. MSG_TEXT
  56. assert_equal(text_should, mail.text_part.body.to_s)
  57. html_should = <<~MSG_HTML.chomp
  58. <!DOCTYPE html>\r
  59. <html>\r
  60. <head>\r
  61. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>\r
  62. </head>\r
  63. <body style="font-family:Geneva,Helvetica,Arial,sans-serif; font-size: 12px;">\r
  64. <div>&gt; Welcome!</div><div>&gt;</div><div>&gt; Thank you for installing Zammad. äöüß</div><div>&gt;</div>\r
  65. </body>\r
  66. </html>
  67. MSG_HTML
  68. assert_equal(html_should, mail.html_part.body.to_s)
  69. parser = Channel::EmailParser.new
  70. data = parser.parse(mail.to_s)
  71. # check body
  72. should = '<div>&gt; Welcome!</div><div>&gt;</div><div>&gt; Thank you for installing Zammad. äöüß</div><div>&gt;</div>'
  73. assert_equal(should, data[:body])
  74. assert_equal('text/html', data[:content_type])
  75. # check count of attachments, only 2, because 3 part is text message and is already in body
  76. assert_equal(2, data[:attachments].length)
  77. # check attachments
  78. data[:attachments]&.each do |attachment|
  79. case attachment[:filename]
  80. when 'message.html'
  81. assert_nil(attachment[:preferences]['Content-ID'])
  82. assert_equal(true, attachment[:preferences]['content-alternative'])
  83. assert_equal('text/html', attachment[:preferences]['Mime-Type'])
  84. assert_equal('UTF-8', attachment[:preferences]['Charset'])
  85. when 'somename.png'
  86. assert_nil(attachment[:preferences]['Content-ID'])
  87. assert_nil(attachment[:preferences]['content-alternative'])
  88. assert_equal('image/png', attachment[:preferences]['Mime-Type'])
  89. assert_equal('UTF-8', attachment[:preferences]['Charset'])
  90. else
  91. assert(false, "invalid attachment, should not be there, #{attachment.inspect}")
  92. end
  93. end
  94. end
  95. test 'plain email + attachment check' do
  96. text = <<~MSG_TEXT.chomp
  97. > Welcome!
  98. >
  99. > Thank you for installing Zammad. äöüß
  100. >
  101. MSG_TEXT
  102. mail = Channel::EmailBuild.build(
  103. from: 'sender@example.com',
  104. to: 'recipient@example.com',
  105. body: text,
  106. attachments: [
  107. {
  108. 'Mime-Type' => 'image/png',
  109. :content => 'xxx',
  110. :filename => 'somename.png'
  111. }
  112. ],
  113. )
  114. text_should = <<~MSG_TEXT.chomp
  115. > Welcome!\r
  116. >\r
  117. > Thank you for installing Zammad. äöüß\r
  118. >\r
  119. MSG_TEXT
  120. assert_equal(text_should, mail.text_part.body.to_s)
  121. assert_nil(mail.html_part)
  122. assert_equal('image/png; filename=somename.png', mail.attachments[0].content_type)
  123. parser = Channel::EmailParser.new
  124. data = parser.parse(mail.to_s)
  125. # check body
  126. assert_equal(text, data[:body])
  127. # check count of attachments, 2
  128. assert_equal(1, data[:attachments].length)
  129. # check attachments
  130. data[:attachments]&.each do |attachment|
  131. if attachment[:filename] == 'somename.png'
  132. assert_nil(attachment[:preferences]['Content-ID'])
  133. assert_nil(attachment[:preferences]['content-alternative'])
  134. assert_equal('image/png', attachment[:preferences]['Mime-Type'])
  135. assert_equal('UTF-8', attachment[:preferences]['Charset'])
  136. else
  137. assert(false, "invalid attachment, should not be there, #{attachment.inspect}")
  138. end
  139. end
  140. end
  141. test 'plain email + attachment check 2' do
  142. ticket1 = Ticket.create!(
  143. title: 'some article helper test1',
  144. group: Group.lookup(name: 'Users'),
  145. customer_id: 2,
  146. state: Ticket::State.lookup(name: 'new'),
  147. priority: Ticket::Priority.lookup(name: '2 normal'),
  148. updated_by_id: 1,
  149. created_by_id: 1,
  150. )
  151. assert(ticket1, 'ticket created')
  152. # create inbound article #1
  153. article1 = Ticket::Article.create!(
  154. ticket_id: ticket1.id,
  155. from: 'some_sender@example.com',
  156. to: 'some_recipient@example.com',
  157. subject: 'some subject',
  158. message_id: 'some@id',
  159. content_type: 'text/html',
  160. body: 'some message article helper test1 <div><img style="width: 85.5px; height: 49.5px" src="cid:15.274327094.140938@zammad.example.com">asdasd<img src="cid:15.274327094.140939@zammad.example.com"><br>',
  161. internal: false,
  162. sender: Ticket::Article::Sender.find_by(name: 'Customer'),
  163. type: Ticket::Article::Type.find_by(name: 'email'),
  164. updated_by_id: 1,
  165. created_by_id: 1,
  166. )
  167. store1 = Store.add(
  168. object: 'Ticket::Article',
  169. o_id: article1.id,
  170. data: 'content_file1_normally_should_be_an_ics_calendar_file',
  171. filename: 'schedule.ics',
  172. preferences: {
  173. 'Mime-Type' => 'text/calendar'
  174. },
  175. created_by_id: 1,
  176. )
  177. text = <<~MSG_TEXT.chomp
  178. > Welcome!
  179. >
  180. > Thank you for installing Zammad. äöüß
  181. >
  182. MSG_TEXT
  183. mail = Channel::EmailBuild.build(
  184. from: 'sender@example.com',
  185. to: 'recipient@example.com',
  186. body: text,
  187. attachments: [
  188. store1
  189. ],
  190. )
  191. text_should = <<~MSG_TEXT.chomp
  192. > Welcome!\r
  193. >\r
  194. > Thank you for installing Zammad. äöüß\r
  195. >\r
  196. MSG_TEXT
  197. assert_equal(text_should, mail.text_part.body.to_s)
  198. assert_nil(mail.html_part)
  199. assert_equal('text/calendar; filename=schedule.ics', mail.attachments[0].content_type)
  200. parser = Channel::EmailParser.new
  201. data = parser.parse(mail.to_s)
  202. # check body
  203. assert_equal(text, data[:body])
  204. # check count of attachments, 2
  205. assert_equal(1, data[:attachments].length)
  206. # check attachments
  207. data[:attachments]&.each do |attachment|
  208. if attachment[:filename] == 'schedule.ics'
  209. assert(attachment[:preferences]['Content-ID'])
  210. assert_nil(attachment[:preferences]['content-alternative'])
  211. assert_equal('text/calendar', attachment[:preferences]['Mime-Type'])
  212. assert_equal('UTF-8', attachment[:preferences]['Charset'])
  213. else
  214. assert(false, "invalid attachment, should not be there, #{attachment.inspect}")
  215. end
  216. end
  217. end
  218. test 'plain email + without attachment check' do
  219. text = <<~MSG_TEXT.chomp
  220. > Welcome!
  221. >
  222. > Thank you for installing Zammad. äöüß
  223. >
  224. MSG_TEXT
  225. mail = Channel::EmailBuild.build(
  226. from: 'sender@example.com',
  227. to: 'recipient@example.com',
  228. body: text,
  229. )
  230. text_should = <<~MSG_TEXT.chomp
  231. > Welcome!\r
  232. >\r
  233. > Thank you for installing Zammad. äöüß\r
  234. >\r
  235. MSG_TEXT
  236. assert_equal(text_should, mail.body.to_s)
  237. assert_nil(mail.html_part)
  238. parser = Channel::EmailParser.new
  239. data = parser.parse(mail.to_s)
  240. # check body
  241. assert_equal(text, data[:body])
  242. # check count of attachments, 0
  243. assert_equal(0, data[:attachments].length)
  244. end
  245. test 'email - html email client fixes' do
  246. # https://github.com/martini/zammad/issues/165
  247. html_raw = '<blockquote type="cite">some
  248. text
  249. </blockquote>
  250. 123
  251. <blockquote type="cite">some
  252. text
  253. </blockquote>'
  254. html_with_fixes = Channel::EmailBuild.html_mail_client_fixes(html_raw)
  255. assert_not_equal(html_with_fixes, html_raw)
  256. html_should = '<blockquote type="cite" style="border-left: 2px solid blue; margin: 0 0 16px; padding: 8px 12px 8px 12px;">some
  257. text
  258. </blockquote>
  259. 123
  260. <blockquote type="cite" style="border-left: 2px solid blue; margin: 0 0 16px; padding: 8px 12px 8px 12px;">some
  261. text
  262. </blockquote>'
  263. assert_equal(html_should, html_with_fixes)
  264. html_raw = '<p>some
  265. text
  266. </p>
  267. <p>123</p>'
  268. html_with_fixes = Channel::EmailBuild.html_mail_client_fixes(html_raw)
  269. assert_not_equal(html_with_fixes, html_raw)
  270. html_should = '<p style="margin: 0;">some
  271. text
  272. </p>
  273. <p style="margin: 0;">123</p>'
  274. assert_equal(html_should, html_with_fixes)
  275. html_raw = '<p>sometext</p><hr><p>123</p>'
  276. html_with_fixes = Channel::EmailBuild.html_mail_client_fixes(html_raw)
  277. assert_not_equal(html_with_fixes, html_raw)
  278. html_should = '<p style="margin: 0;">sometext</p><hr style="margin-top: 6px; margin-bottom: 6px; border: 0; border-top: 1px solid #dfdfdf;"><p style="margin: 0;">123</p>'
  279. assert_equal(html_should, html_with_fixes)
  280. end
  281. test 'from checks' do
  282. quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody @ "Company"', 'some.body@example.com')
  283. assert_equal('"Somebody @ \"Company\"" <some.body@example.com>', quoted_in_one_line)
  284. quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody', 'some.body@example.com')
  285. assert_equal('Somebody <some.body@example.com>', quoted_in_one_line)
  286. quoted_in_one_line = Channel::EmailBuild.recipient_line('Somebody | Some Org', 'some.body@example.com')
  287. assert_equal('"Somebody | Some Org" <some.body@example.com>', quoted_in_one_line)
  288. quoted_in_one_line = Channel::EmailBuild.recipient_line('Test Admin Agent via Support', 'some.body@example.com')
  289. assert_equal('"Test Admin Agent via Support" <some.body@example.com>', quoted_in_one_line)
  290. end
  291. # #2362 - Attached text files get prepended on e-mail reply instead of appended
  292. test 'plain email + text attachment' do
  293. ticket1 = Ticket.create!(
  294. title: 'some article text attachment test',
  295. group: Group.lookup(name: 'Users'),
  296. customer_id: 2,
  297. state: Ticket::State.lookup(name: 'new'),
  298. priority: Ticket::Priority.lookup(name: '2 normal'),
  299. updated_by_id: 1,
  300. created_by_id: 1,
  301. )
  302. assert(ticket1, 'ticket created')
  303. article1 = Ticket::Article.create!(
  304. ticket_id: ticket1.id,
  305. from: 'some_sender@example.com',
  306. to: 'some_recipient@example.com',
  307. subject: 'some subject',
  308. message_id: 'some@id',
  309. content_type: 'text/html',
  310. body: 'some message article helper test1 <div><img style="width: 85.5px; height: 49.5px" src="cid:15.274327094.140938@zammad.example.com">asdasd<img src="cid:15.274327094.140939@zammad.example.com"><br>',
  311. internal: false,
  312. sender: Ticket::Article::Sender.find_by(name: 'Customer'),
  313. type: Ticket::Article::Type.find_by(name: 'email'),
  314. updated_by_id: 1,
  315. created_by_id: 1,
  316. )
  317. text = <<~MSG_TEXT.chomp
  318. > Welcome!
  319. >
  320. > Email Content
  321. MSG_TEXT
  322. store1 = Store.add(
  323. object: 'Ticket::Article',
  324. o_id: article1.id,
  325. data: 'Text Content',
  326. filename: 'text_file.txt',
  327. preferences: {
  328. 'Mime-Type' => 'text/plain'
  329. },
  330. created_by_id: 1,
  331. )
  332. mail = Channel::EmailBuild.build(
  333. from: 'sender@example.com',
  334. to: 'recipient@example.com',
  335. body: text,
  336. attachments: [
  337. store1
  338. ],
  339. )
  340. File.write('append_test.eml', mail.to_s)
  341. # Email Content should appear before the Text Content within the raw email
  342. assert_match(%r{Email Content[\s\S]*Text Content}, mail.to_s)
  343. end
  344. end