You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

353 lines
12 KiB

  1. # No need to translate tests
  2. # pylint: disable=translation-required
  3. from email.message import EmailMessage
  4. import mock
  5. import odoo
  6. from odoo import SUPERUSER_ID
  7. from odoo.tests import TransactionCase
  8. from odoo.addons.base.models.ir_mail_server import IrMailServer
  9. from ..models.mail import (
  10. MESSAGE_PREFIX,
  11. encode_msg_id,
  12. encode_msg_id_legacy,
  13. random_string,
  14. )
  15. @odoo.tests.tagged("post_install", "-at_install")
  16. class TestEmail(TransactionCase):
  17. def setUp(self):
  18. super(TestEmail, self).setUp()
  19. self.partner = self.env["res.partner"].create({"name": "Test Dude"})
  20. self.partner2 = self.env["res.partner"].create({"name": "Dudette"})
  21. self.demo_user = self.env.ref("base.user_demo")
  22. self.subtype_comment = self.env.ref("mail.mt_comment")
  23. self.subtype_note = self.env.ref("mail.mt_note")
  24. self.MailMessage = self.env["mail.message"]
  25. self.ConfigParam = self.env["ir.config_parameter"]
  26. # Create server configuration
  27. self.outgoing_server = self.env["ir.mail_server"].create(
  28. {
  29. "name": "Outgoing SMTP Server for Unit Tests",
  30. "sequence": 1,
  31. "smtp_host": "localhost",
  32. "smtp_port": "9999",
  33. "smtp_encryption": "none",
  34. "smtp_user": "doesnt",
  35. "smtp_pass": "exist",
  36. "reply_to_method": "msg_id",
  37. "force_email_reply_to_domain": "example.com",
  38. "force_email_from": "odoo@example.com",
  39. }
  40. )
  41. @staticmethod
  42. def create_email_message():
  43. message = EmailMessage()
  44. message[
  45. "Content-Type"
  46. ] = 'multipart/mixed; boundary="===============2590914155756834027=="'
  47. message["MIME-Version"] = "1.0"
  48. message[
  49. "Message-Id"
  50. ] = "<CAD-eYi=a264_3DcrYSDU5yc_fwYoHonZ3H+{}@mail.gmail.com>".format(
  51. random_string(6)
  52. )
  53. message["Subject"] = "1"
  54. message["From"] = "Miku Laitinen <miku@avoin.systems>"
  55. message["Reply-To"] = "YourCompany Eurooppa <sales@avoin.onmicrosoft.com>"
  56. message["To"] = '"Erik N. French" <ErikNFrench@armyspy.com>'
  57. message["Date"] = "Mon, 06 May 2019 14:16:38 -0000"
  58. return message
  59. def test_reply_to_method_msg_id(self):
  60. # Make administrator follow the partner
  61. self.partner.message_subscribe([self.env.user.partner_id.id])
  62. # Send a message to the followers of the partner
  63. thread_msg = self.partner.with_user(self.demo_user).message_post(
  64. body="dummy message.", message_type="comment", subtype="mail.mt_comment"
  65. )
  66. # Make sure the message headers look right.. or not
  67. # mail_msg = thread_msg.notification_ids[0]
  68. # Get the encoded message address
  69. encoded_msg_id = encode_msg_id(thread_msg.id, self.env)
  70. # Try to read an incoming email
  71. message = self.create_email_message()
  72. del message["To"]
  73. message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format(
  74. MESSAGE_PREFIX, encoded_msg_id
  75. )
  76. thread_id = self.env["mail.thread"].message_process(
  77. model=False, message=message.as_string()
  78. )
  79. self.assertEqual(
  80. thread_msg.res_id,
  81. thread_id,
  82. "The incoming email wasn't connected to the correct thread",
  83. )
  84. # Make sure the message is a comment
  85. incoming_msg1 = self.MailMessage.search(
  86. [("message_id", "=", message["Message-Id"])]
  87. )
  88. self.assertEqual(
  89. incoming_msg1.message_type,
  90. "email",
  91. "The incoming message was created as a type {} instead of a email.".format(
  92. incoming_msg1.message_type
  93. ),
  94. )
  95. self.assertEqual(
  96. incoming_msg1.subtype_id,
  97. self.subtype_comment,
  98. "The incoming message was created as a subtype {} instead of a comment.".format(
  99. incoming_msg1.subtype_id
  100. ),
  101. )
  102. # Try to read another incoming email
  103. message = self.create_email_message()
  104. del message["To"]
  105. message["To"] = '"Erik N. French" <ErikNFrench+{}HURDURLUR@armyspy.com>'.format(
  106. MESSAGE_PREFIX
  107. )
  108. message["In-Reply-To"] = thread_msg.message_id
  109. thread_id = self.env["mail.thread"].message_process(
  110. model=False, message=message.as_string()
  111. )
  112. self.assertEqual(
  113. thread_msg.res_id,
  114. thread_id,
  115. "The incoming email wasn't connected to the correct thread",
  116. )
  117. # Make sure the message is a comment
  118. incoming_msg2 = self.MailMessage.search(
  119. [("message_id", "=", message["Message-Id"])]
  120. )
  121. self.assertEqual(
  122. incoming_msg2.message_type,
  123. "email",
  124. "The incoming message was created as a type {} instead of a email.".format(
  125. incoming_msg2.message_type
  126. ),
  127. )
  128. self.assertEqual(
  129. incoming_msg2.subtype_id,
  130. self.subtype_comment,
  131. "The incoming message was created as a subtype {} instead of a comment.".format(
  132. incoming_msg2.subtype_id
  133. ),
  134. )
  135. def test_reply_to_method_msg_id_priority(self):
  136. """
  137. In this test we will inject the wrong Message-Id to the incoming
  138. email messages References-header and see if Odoo will prioritize
  139. the custom Reply-To address over the References-header. It should.
  140. :return:
  141. """
  142. # Make administrator follow the partner
  143. self.partner.message_subscribe([self.env.user.partner_id.id])
  144. # Send a message to the followers of the partner
  145. thread_msg = self.partner.with_user(self.demo_user).message_post(
  146. body="dummy message X.", message_type="comment", subtype="mail.mt_comment"
  147. )
  148. # Get the encoded message address
  149. encoded_msg_id = encode_msg_id(thread_msg.id, self.env)
  150. # Send another message to the followers of the partner
  151. thread_msg2 = self.partner2.with_user(self.demo_user).message_post(
  152. body="dummy message X.", message_type="comment", subtype="mail.mt_comment"
  153. )
  154. # Try to read an incoming email
  155. message = self.create_email_message()
  156. del message["To"]
  157. del message["References"]
  158. message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format(
  159. MESSAGE_PREFIX, encoded_msg_id
  160. )
  161. # Inject the wrong References
  162. message["References"] = thread_msg2.message_id
  163. thread_id = self.env["mail.thread"].message_process(
  164. model=False, message=message.as_string()
  165. )
  166. self.assertEqual(
  167. thread_msg.res_id,
  168. thread_id,
  169. "The incoming email wasn't connected to the correct thread",
  170. )
  171. def test_reply_to_method_msg_id_notification(self):
  172. # Make administrator follow the partner
  173. self.partner2.message_subscribe([self.env.user.partner_id.id])
  174. # Send a message to the followers of the partner
  175. thread_msg = self.partner2.with_user(self.demo_user).message_post(
  176. body="dummy message 2.", message_type="comment", subtype="mail.mt_note"
  177. )
  178. # Get the encoded message address
  179. encoded_msg_id = encode_msg_id(thread_msg.id, self.env)
  180. # Try to read an incoming email
  181. message = self.create_email_message()
  182. del message["To"]
  183. message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format(
  184. MESSAGE_PREFIX, encoded_msg_id
  185. )
  186. thread_id = self.env["mail.thread"].message_process(
  187. model=False, message=message.as_string()
  188. )
  189. self.assertEqual(
  190. thread_msg.res_id,
  191. thread_id,
  192. "The incoming email wasn't connected to the correct thread",
  193. )
  194. # Make sure the message is a note
  195. incoming_msg1 = self.MailMessage.search(
  196. [("message_id", "=", message["Message-Id"])]
  197. )
  198. self.assertEqual(
  199. incoming_msg1.message_type,
  200. "email",
  201. "The incoming message was created as a type {} instead of a email.".format(
  202. incoming_msg1.message_type
  203. ),
  204. )
  205. self.assertEqual(
  206. incoming_msg1.subtype_id,
  207. self.subtype_note,
  208. "The incoming message was created as a subtype {} instead of a note.".format(
  209. incoming_msg1.subtype_id
  210. ),
  211. )
  212. def test_reply_to_method_msg_id_legacy(self):
  213. # REMOVE this test when porting to Odoo 14
  214. # Make administrator follow the partner
  215. self.partner2.message_subscribe([self.env.user.partner_id.id])
  216. # Send a message to the followers of the partner
  217. thread_msg = self.partner2.with_user(self.demo_user).message_post(
  218. body="dummy message 2.", message_type="comment", subtype="mail.mt_note"
  219. )
  220. # Get the encoded message address
  221. encoded_msg_id = encode_msg_id_legacy(thread_msg.id, self.env)
  222. # Try to read an incoming email
  223. message = self.create_email_message()
  224. del message["To"]
  225. message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format(
  226. MESSAGE_PREFIX, encoded_msg_id
  227. )
  228. thread_id = self.env["mail.thread"].message_process(
  229. model=False, message=message.as_string()
  230. )
  231. self.assertEqual(
  232. thread_msg.res_id,
  233. thread_id,
  234. "The incoming email wasn't connected to the correct thread",
  235. )
  236. def test_reply_to_method_msg_id_lowercase(self):
  237. # Make administrator follow the partner
  238. self.partner2.message_subscribe([self.env.user.partner_id.id])
  239. # Send a message to the followers of the partner
  240. thread_msg = self.partner2.with_user(self.demo_user).message_post(
  241. body="dummy message 2.", message_type="comment", subtype="mail.mt_note"
  242. )
  243. # Get the encoded message address
  244. encoded_msg_id = encode_msg_id(thread_msg.id, self.env).lower()
  245. # Try to read an incoming email
  246. message = self.create_email_message()
  247. del message["To"]
  248. message["To"] = '"Erik N. French" <ErikNFrench+{}{}@armyspy.com>'.format(
  249. MESSAGE_PREFIX, encoded_msg_id
  250. )
  251. thread_id = self.env["mail.thread"].message_process(
  252. model=False, message=message.as_string()
  253. )
  254. self.assertEqual(
  255. thread_msg.res_id,
  256. thread_id,
  257. "The incoming email wasn't connected to the correct thread",
  258. )
  259. def test_outgoing_msg_id(self):
  260. # Make administrator follow the partner
  261. self.partner2.message_subscribe([SUPERUSER_ID])
  262. with mock.patch.object(IrMailServer, "send_email") as send_email:
  263. # Send a message to the followers of the partner
  264. thread_msg = self.partner2.with_user(self.demo_user).message_post(
  265. body="dummy message 3.",
  266. message_type="comment",
  267. subtype="mail.mt_comment",
  268. )
  269. # Get the encoded message address
  270. encoded_msg_id = encode_msg_id(thread_msg.id, self.env)
  271. self.assertTrue(
  272. send_email.called,
  273. "IrMailServer.send_email wasn't called when sending outgoing email",
  274. )
  275. message = send_email.call_args[0][0]
  276. reply_to_address = "{}{}@{}".format(
  277. MESSAGE_PREFIX,
  278. encoded_msg_id,
  279. self.outgoing_server.force_email_reply_to_domain,
  280. )
  281. # Make sure the subaddress is correct in the Reply-To field
  282. self.assertIn(
  283. reply_to_address,
  284. message["Reply-To"],
  285. "Reply-To address didn't contain the correct subaddress",
  286. )
  287. # Make sure the author name is in the Reply-To field
  288. self.assertIn(
  289. thread_msg.author_id.name,
  290. message["Reply-To"],
  291. "Reply-To address didn't contain the author name",
  292. )
  293. self.assertIn(
  294. self.outgoing_server.force_email_from,
  295. message["From"],
  296. "From address didn't contain the configure From-address",
  297. )