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.

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