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