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.

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