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.

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