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.

425 lines
17 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 Tecnativa - Antonio Espinosa
  3. # Copyright 2017 Tecnativa - David Vidal
  4. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  5. from odoo.tools import mute_logger
  6. from odoo.tests.common import TransactionCase
  7. from odoo.exceptions import UserError, ValidationError
  8. import mock
  9. _packagepath = 'odoo.addons.mail_tracking_mailgun'
  10. class TestMailgun(TransactionCase):
  11. def mail_send(self):
  12. mail = self.env['mail.mail'].create({
  13. 'subject': 'Test subject',
  14. 'email_from': 'from@example.com',
  15. 'email_to': self.recipient,
  16. 'body_html': '<p>This is a test message</p>',
  17. })
  18. mail.send()
  19. # Search tracking created
  20. tracking_email = self.env['mail.tracking.email'].search([
  21. ('mail_id', '=', mail.id),
  22. ])
  23. return mail, tracking_email
  24. def setUp(self):
  25. super(TestMailgun, self).setUp()
  26. self.recipient = 'to@example.com'
  27. self.mail, self.tracking_email = self.mail_send()
  28. self.api_key = 'key-12345678901234567890123456789012'
  29. self.domain = 'example.com'
  30. self.token = 'f1349299097a51b9a7d886fcb5c2735b426ba200ada6e9e149'
  31. self.timestamp = '1471021089'
  32. self.signature = ('4fb6d4dbbe10ce5d620265dcd7a3c0b8'
  33. 'ca0dede1433103891bc1ae4086e9d5b2')
  34. self.env['ir.config_parameter'].set_param(
  35. 'mailgun.apikey', self.api_key)
  36. self.env['ir.config_parameter'].set_param(
  37. 'mail.catchall.domain', self.domain)
  38. self.env['ir.config_parameter'].set_param(
  39. 'mailgun.validation_key', self.api_key)
  40. self.env['ir.config_parameter'].set_param(
  41. 'mailgun.auto_check_partner_email', '')
  42. self.event = {
  43. 'Message-Id': '<xxx.xxx.xxx-openerp-xxx-res.partner@test_db>',
  44. 'X-Mailgun-Sid': 'WyIwNjgxZSIsICJ0b0BleGFtcGxlLmNvbSIsICI3MG'
  45. 'I0MWYiXQ==',
  46. 'token': self.token,
  47. 'timestamp': self.timestamp,
  48. 'signature': self.signature,
  49. 'domain': 'example.com',
  50. 'message-headers': '[]',
  51. 'recipient': self.recipient,
  52. 'odoo_db': self.env.cr.dbname,
  53. 'tracking_email_id': '%s' % self.tracking_email.id
  54. }
  55. self.metadata = {
  56. 'ip': '127.0.0.1',
  57. 'user_agent': False,
  58. 'os_family': False,
  59. 'ua_family': False,
  60. }
  61. self.partner = self.env['res.partner'].create({
  62. 'name': 'Mr. Odoo',
  63. 'email': 'mrodoo@example.com',
  64. })
  65. self.response = {
  66. "items": [{
  67. "log-level": "info",
  68. "id": "oXAVv5URCF-dKv8c6Sa7T",
  69. "timestamp": 1509119329.0,
  70. "message": {
  71. "headers": {
  72. "to": "test@test.com",
  73. "message-id": "test-id@f187c54734e8",
  74. "from": "Mr. Odoo <mrodoo@odoo.com>",
  75. "subject": "This is a test"
  76. },
  77. },
  78. "event": "delivered",
  79. "recipient": "to@example.com",
  80. }]
  81. }
  82. def event_search(self, event_type):
  83. event = self.env['mail.tracking.event'].search([
  84. ('tracking_email_id', '=', self.tracking_email.id),
  85. ('event_type', '=', event_type),
  86. ])
  87. self.assertTrue(event)
  88. return event
  89. def test_no_api_key(self):
  90. self.env['ir.config_parameter'].set_param('mailgun.apikey', '')
  91. self.test_event_delivered()
  92. with self.assertRaises(ValidationError):
  93. self.env['mail.tracking.email']._mailgun_values()
  94. def test_no_domain(self):
  95. self.env['ir.config_parameter'].set_param('mail.catchall.domain', '')
  96. self.test_event_delivered()
  97. with self.assertRaises(ValidationError):
  98. self.env['mail.tracking.email']._mailgun_values()
  99. # now we set an specific domain for Mailgun:
  100. # i.e: we configure new EU zone without loosing old domain statistics
  101. self.env['ir.config_parameter'].set_param(
  102. 'mailgun.domain', 'eu.example.com')
  103. self.test_event_delivered()
  104. @mute_logger('odoo.addons.mail_tracking_mailgun.models'
  105. '.mail_tracking_email')
  106. def test_bad_signature(self):
  107. self.event.update({
  108. 'event': 'delivered',
  109. 'signature': 'bad_signature',
  110. })
  111. response = self.env['mail.tracking.email'].event_process(
  112. None, self.event, self.metadata)
  113. self.assertEqual('ERROR: Signature', response)
  114. @mute_logger('odoo.addons.mail_tracking_mailgun.models'
  115. '.mail_tracking_email')
  116. def test_bad_event_type(self):
  117. self.event.update({
  118. 'event': 'bad_event',
  119. })
  120. response = self.env['mail.tracking.email'].event_process(
  121. None, self.event, self.metadata)
  122. self.assertEqual('ERROR: Event type not supported', response)
  123. @mute_logger('odoo.addons.mail_tracking_mailgun.models'
  124. '.mail_tracking_email')
  125. def test_bad_db(self):
  126. self.event.update({
  127. 'event': 'delivered',
  128. 'odoo_db': 'bad_db',
  129. })
  130. response = self.env['mail.tracking.email'].event_process(
  131. None, self.event, self.metadata)
  132. self.assertEqual('ERROR: Invalid DB', response)
  133. def test_bad_ts(self):
  134. timestamp = '7a' # Now time will be used instead
  135. signature = ('06cc05680f6e8110e59b41152b2d1c0f'
  136. '1045d755ef2880ff922344325c89a6d4')
  137. self.event.update({
  138. 'event': 'delivered',
  139. 'timestamp': timestamp,
  140. 'signature': signature,
  141. })
  142. response = self.env['mail.tracking.email'].event_process(
  143. None, self.event, self.metadata)
  144. self.assertEqual('OK', response)
  145. @mute_logger('odoo.addons.mail_tracking_mailgun.models'
  146. '.mail_tracking_email')
  147. def test_tracking_not_found(self):
  148. self.event.update({
  149. 'event': 'delivered',
  150. 'tracking_email_id': 'bad_id',
  151. })
  152. response = self.env['mail.tracking.email'].event_process(
  153. None, self.event, self.metadata)
  154. self.assertEqual('ERROR: Tracking not found', response)
  155. # https://documentation.mailgun.com/user_manual.html#tracking-deliveries
  156. def test_event_delivered(self):
  157. self.event.update({
  158. 'event': 'delivered',
  159. })
  160. response = self.env['mail.tracking.email'].event_process(
  161. None, self.event, self.metadata)
  162. self.assertEqual('OK', response)
  163. events = self.event_search('delivered')
  164. for event in events:
  165. self.assertEqual(event.timestamp, float(self.timestamp))
  166. self.assertEqual(event.recipient, self.recipient)
  167. # https://documentation.mailgun.com/user_manual.html#tracking-opens
  168. def test_event_opened(self):
  169. ip = '127.0.0.1'
  170. user_agent = 'Odoo Test/8.0 Gecko Firefox/11.0'
  171. os_family = 'Linux'
  172. ua_family = 'Firefox'
  173. ua_type = 'browser'
  174. self.event.update({
  175. 'event': 'opened',
  176. 'city': 'Mountain View',
  177. 'country': 'US',
  178. 'region': 'CA',
  179. 'client-name': ua_family,
  180. 'client-os': os_family,
  181. 'client-type': ua_type,
  182. 'device-type': 'desktop',
  183. 'ip': ip,
  184. 'user-agent': user_agent,
  185. })
  186. response = self.env['mail.tracking.email'].event_process(
  187. None, self.event, self.metadata)
  188. self.assertEqual('OK', response)
  189. event = self.event_search('open')
  190. self.assertEqual(event.timestamp, float(self.timestamp))
  191. self.assertEqual(event.recipient, self.recipient)
  192. self.assertEqual(event.ip, ip)
  193. self.assertEqual(event.user_agent, user_agent)
  194. self.assertEqual(event.os_family, os_family)
  195. self.assertEqual(event.ua_family, ua_family)
  196. self.assertEqual(event.ua_type, ua_type)
  197. self.assertEqual(event.mobile, False)
  198. self.assertEqual(event.user_country_id.code, 'US')
  199. # https://documentation.mailgun.com/user_manual.html#tracking-clicks
  200. def test_event_clicked(self):
  201. ip = '127.0.0.1'
  202. user_agent = 'Odoo Test/8.0 Gecko Firefox/11.0'
  203. os_family = 'Linux'
  204. ua_family = 'Firefox'
  205. ua_type = 'browser'
  206. url = 'https://odoo-community.org'
  207. self.event.update({
  208. 'event': 'clicked',
  209. 'city': 'Mountain View',
  210. 'country': 'US',
  211. 'region': 'CA',
  212. 'client-name': ua_family,
  213. 'client-os': os_family,
  214. 'client-type': ua_type,
  215. 'device-type': 'tablet',
  216. 'ip': ip,
  217. 'user-agent': user_agent,
  218. 'url': url,
  219. })
  220. response = self.env['mail.tracking.email'].event_process(
  221. None, self.event, self.metadata, event_type='click')
  222. self.assertEqual('OK', response)
  223. event = self.event_search('click')
  224. self.assertEqual(event.timestamp, float(self.timestamp))
  225. self.assertEqual(event.recipient, self.recipient)
  226. self.assertEqual(event.ip, ip)
  227. self.assertEqual(event.user_agent, user_agent)
  228. self.assertEqual(event.os_family, os_family)
  229. self.assertEqual(event.ua_family, ua_family)
  230. self.assertEqual(event.ua_type, ua_type)
  231. self.assertEqual(event.mobile, True)
  232. self.assertEqual(event.url, url)
  233. # https://documentation.mailgun.com/user_manual.html#tracking-unsubscribes
  234. def test_event_unsubscribed(self):
  235. ip = '127.0.0.1'
  236. user_agent = 'Odoo Test/8.0 Gecko Firefox/11.0'
  237. os_family = 'Linux'
  238. ua_family = 'Firefox'
  239. ua_type = 'browser'
  240. self.event.update({
  241. 'event': 'unsubscribed',
  242. 'city': 'Mountain View',
  243. 'country': 'US',
  244. 'region': 'CA',
  245. 'client-name': ua_family,
  246. 'client-os': os_family,
  247. 'client-type': ua_type,
  248. 'device-type': 'mobile',
  249. 'ip': ip,
  250. 'user-agent': user_agent,
  251. })
  252. response = self.env['mail.tracking.email'].event_process(
  253. None, self.event, self.metadata)
  254. self.assertEqual('OK', response)
  255. event = self.event_search('unsub')
  256. self.assertEqual(event.timestamp, float(self.timestamp))
  257. self.assertEqual(event.recipient, self.recipient)
  258. self.assertEqual(event.ip, ip)
  259. self.assertEqual(event.user_agent, user_agent)
  260. self.assertEqual(event.os_family, os_family)
  261. self.assertEqual(event.ua_family, ua_family)
  262. self.assertEqual(event.ua_type, ua_type)
  263. self.assertEqual(event.mobile, True)
  264. # https://documentation.mailgun.com/
  265. # user_manual.html#tracking-spam-complaints
  266. def test_event_complained(self):
  267. self.event.update({
  268. 'event': 'complained',
  269. })
  270. response = self.env['mail.tracking.email'].event_process(
  271. None, self.event, self.metadata)
  272. self.assertEqual('OK', response)
  273. event = self.event_search('spam')
  274. self.assertEqual(event.timestamp, float(self.timestamp))
  275. self.assertEqual(event.recipient, self.recipient)
  276. self.assertEqual(event.error_type, 'spam')
  277. # https://documentation.mailgun.com/user_manual.html#tracking-bounces
  278. def test_event_bounced(self):
  279. code = '550'
  280. error = ("5.1.1 The email account does not exist.\n"
  281. "5.1.1 double-checking the recipient's email address")
  282. notification = "Please, check recipient's email address"
  283. self.event.update({
  284. 'event': 'bounced',
  285. 'code': code,
  286. 'error': error,
  287. 'notification': notification,
  288. })
  289. response = self.env['mail.tracking.email'].event_process(
  290. None, self.event, self.metadata)
  291. self.assertEqual('OK', response)
  292. event = self.event_search('hard_bounce')
  293. self.assertEqual(event.timestamp, float(self.timestamp))
  294. self.assertEqual(event.recipient, self.recipient)
  295. self.assertEqual(event.error_type, code)
  296. self.assertEqual(event.error_description, error)
  297. self.assertEqual(event.error_details, notification)
  298. # https://documentation.mailgun.com/user_manual.html#tracking-failures
  299. def test_event_dropped(self):
  300. reason = 'hardfail'
  301. code = '605'
  302. description = 'Not delivering to previously bounced address'
  303. self.event.update({
  304. 'event': 'dropped',
  305. 'reason': reason,
  306. 'code': code,
  307. 'description': description,
  308. })
  309. response = self.env['mail.tracking.email'].event_process(
  310. None, self.event, self.metadata)
  311. self.assertEqual('OK', response)
  312. event = self.event_search('reject')
  313. self.assertEqual(event.timestamp, float(self.timestamp))
  314. self.assertEqual(event.recipient, self.recipient)
  315. self.assertEqual(event.error_type, reason)
  316. self.assertEqual(event.error_description, code)
  317. self.assertEqual(event.error_details, description)
  318. @mock.patch(_packagepath + '.models.res_partner.requests')
  319. def test_email_validity(self, mock_request):
  320. self.partner.email_bounced = False
  321. mock_request.get.return_value.apparent_encoding = 'ascii'
  322. mock_request.get.return_value.status_code = 200
  323. mock_request.get.return_value.json.return_value = {
  324. 'is_valid': True,
  325. 'mailbox_verification': 'true',
  326. }
  327. # Trigger email auto validation in partner
  328. self.env['ir.config_parameter'].set_param(
  329. 'mailgun.auto_check_partner_email', 'True')
  330. self.partner.email = 'info@tecnativa.com'
  331. self.assertFalse(self.partner.email_bounced)
  332. self.partner.email = 'xoxoxoxo@tecnativa.com'
  333. # Not a valid mailbox
  334. mock_request.get.return_value.json.return_value = {
  335. 'is_valid': True,
  336. 'mailbox_verification': 'false',
  337. }
  338. with self.assertRaises(UserError):
  339. self.partner.check_email_validity()
  340. # Not a valid mail address
  341. mock_request.get.return_value.json.return_value = {
  342. 'is_valid': False,
  343. 'mailbox_verification': 'false',
  344. }
  345. with self.assertRaises(UserError):
  346. self.partner.check_email_validity()
  347. # Unable to fully validate
  348. mock_request.get.return_value.json.return_value = {
  349. 'is_valid': True,
  350. 'mailbox_verification': 'unknown',
  351. }
  352. with self.assertRaises(UserError):
  353. self.partner.check_email_validity()
  354. self.assertTrue(self.partner.email_bounced)
  355. @mock.patch(_packagepath + '.models.res_partner.requests')
  356. def test_email_validity_exceptions(self, mock_request):
  357. mock_request.get.return_value.status_code = 404
  358. with self.assertRaises(UserError):
  359. self.partner.check_email_validity()
  360. self.env['ir.config_parameter'].set_param('mailgun.validation_key', '')
  361. with self.assertRaises(UserError):
  362. self.partner.check_email_validity()
  363. @mock.patch(_packagepath + '.models.res_partner.requests')
  364. def test_bounced(self, mock_request):
  365. self.partner.email_bounced = True
  366. mock_request.get.return_value.status_code = 404
  367. self.partner.check_email_bounced()
  368. self.assertFalse(self.partner.email_bounced)
  369. mock_request.get.return_value.status_code = 200
  370. self.partner.force_set_bounced()
  371. self.partner.check_email_bounced()
  372. self.assertTrue(self.partner.email_bounced)
  373. mock_request.delete.return_value.status_code = 200
  374. self.partner.force_unset_bounced()
  375. self.assertFalse(self.partner.email_bounced)
  376. def test_email_bounced_set(self):
  377. message_number = len(self.partner.message_ids) + 1
  378. self.partner._email_bounced_set('test_error', False)
  379. self.assertEqual(len(self.partner.message_ids), message_number)
  380. self.partner.email = ""
  381. self.partner._email_bounced_set('test_error', False)
  382. self.assertEqual(len(self.partner.message_ids), message_number)
  383. @mock.patch(_packagepath + '.models.mail_tracking_email.requests')
  384. def test_manual_check(self, mock_request):
  385. mock_request.get.return_value.json.return_value = self.response
  386. mock_request.get.return_value.status_code = 200
  387. self.tracking_email.action_manual_check_mailgun()
  388. event = self.env['mail.tracking.event'].search(
  389. [('mailgun_id', '=', self.response['items'][0]['id'])])
  390. self.assertEqual(event.event_type, self.response['items'][0]['event'])
  391. @mock.patch(_packagepath + '.models.mail_tracking_email.requests')
  392. def test_manual_check_exceptions(self, mock_request):
  393. mock_request.get.return_value.status_code = 404
  394. with self.assertRaises(ValidationError):
  395. self.tracking_email.action_manual_check_mailgun()
  396. mock_request.get.return_value.status_code = 200
  397. mock_request.get.return_value.json.return_value = {}
  398. with self.assertRaises(ValidationError):
  399. self.tracking_email.action_manual_check_mailgun()