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.

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