1# -*- coding: utf-8 -*- 2 3import re 4import werkzeug 5 6from odoo import tools 7from odoo.addons.mass_mailing.tests.common import MassMailCommon 8from odoo.addons.sms.tests.common import SMSCase, SMSCommon 9 10 11class MassSMSCase(SMSCase): 12 13 # ------------------------------------------------------------ 14 # ASSERTS 15 # ------------------------------------------------------------ 16 17 def assertSMSStatistics(self, recipients_info, mailing, records, check_sms=True): 18 """ Deprecated, remove in 14.4 """ 19 return self.assertSMSTraces(recipients_info, mailing, records, check_sms=check_sms) 20 21 def assertSMSTraces(self, recipients_info, mailing, records, 22 check_sms=True, sent_unlink=False, 23 sms_links_info=None): 24 """ Check content of traces. Traces are fetched based on a given mailing 25 and records. Their content is compared to recipients_info structure that 26 holds expected information. Links content may be checked, notably to 27 assert shortening or unsubscribe links. Sms.sms records may optionally 28 be checked. 29 30 :param recipients_info: list[{ 31 # TRACE 32 'partner': res.partner record (may be empty), 33 'number': number used for notification (may be empty, computed based on partner), 34 'state': outgoing / sent / ignored / bounced / exception / opened (outgoing by default), 35 'record: linked record, 36 # SMS.SMS 37 'content': optional: if set, check content of sent SMS; 38 'failure_type': error code linked to sms failure (see ``error_code`` 39 field on ``sms.sms`` model); 40 }, 41 { ... }]; 42 :param mailing: a mailing.mailing record from which traces have been 43 generated; 44 :param records: records given to mailing that generated traces. It is 45 used notably to find traces using their IDs; 46 :param check_sms: if set, check sms.sms records that should be linked to traces; 47 :param sent_unlink: it True, sent sms.sms are deleted and we check gateway 48 output result instead of actual sms.sms records; 49 :param sms_links_info: if given, should follow order of ``recipients_info`` 50 and give details about links. See ``assertLinkShortenedHtml`` helper for 51 more details about content to give; 52 ] 53 """ 54 # map trace state to sms state 55 state_mapping = { 56 'sent': 'sent', 57 'outgoing': 'outgoing', 58 'exception': 'error', 59 'ignored': 'canceled', 60 'bounced': 'error', 61 } 62 traces = self.env['mailing.trace'].search([ 63 ('mass_mailing_id', 'in', mailing.ids), 64 ('res_id', 'in', records.ids) 65 ]) 66 67 self.assertTrue(all(s.model == records._name for s in traces)) 68 # self.assertTrue(all(s.utm_campaign_id == mailing.campaign_id for s in traces)) 69 self.assertEqual(set(s.res_id for s in traces), set(records.ids)) 70 71 # check each trace 72 if not sms_links_info: 73 sms_links_info = [None] * len(recipients_info) 74 for recipient_info, link_info, record in zip(recipients_info, sms_links_info, records): 75 partner = recipient_info.get('partner', self.env['res.partner']) 76 number = recipient_info.get('number') 77 state = recipient_info.get('state', 'outgoing') 78 content = recipient_info.get('content', None) 79 if number is None and partner: 80 number = partner._sms_get_recipients_info()[partner.id]['sanitized'] 81 82 trace = traces.filtered( 83 lambda t: t.sms_number == number and t.state == state and (t.res_id == record.id if record else True) 84 ) 85 self.assertTrue(len(trace) == 1, 86 'SMS: found %s notification for number %s, (state: %s) (1 expected)' % (len(trace), number, state)) 87 self.assertTrue(bool(trace.sms_sms_id_int)) 88 89 if check_sms: 90 if state == 'sent': 91 if sent_unlink: 92 self.assertSMSIapSent([number], content=content) 93 else: 94 self.assertSMS(partner, number, 'sent', content=content) 95 elif state in state_mapping: 96 sms_state = state_mapping[state] 97 error_code = recipient_info['failure_type'] if state in ('exception', 'ignored', 'bounced') else None 98 self.assertSMS(partner, number, sms_state, error_code=error_code, content=content) 99 else: 100 raise NotImplementedError() 101 102 if link_info: 103 # shortened links are directly included in sms.sms record as well as 104 # in sent sms (not like mails who are post-processed) 105 sms_sent = self._find_sms_sent(partner, number) 106 sms_sms = self._find_sms_sms(partner, number, state_mapping[state]) 107 for (url, is_shortened, add_link_params) in link_info: 108 if url == 'unsubscribe': 109 url = '%s/sms/%d/%s' % (mailing.get_base_url(), mailing.id, trace.sms_code) 110 link_params = {'utm_medium': 'SMS', 'utm_source': mailing.name} 111 if add_link_params: 112 link_params.update(**add_link_params) 113 self.assertLinkShortenedText( 114 sms_sms.body, 115 (url, is_shortened), 116 link_params=link_params, 117 ) 118 self.assertLinkShortenedText( 119 sms_sent['body'], 120 (url, is_shortened), 121 link_params=link_params, 122 ) 123 124 # ------------------------------------------------------------ 125 # GATEWAY TOOLS 126 # ------------------------------------------------------------ 127 128 def gateway_sms_click(self, mailing, record): 129 """ Simulate a click on a sent SMS. Usage: giving a partner and/or 130 a number, find an SMS sent to him, find shortened links in its body 131 and call add_click to simulate a click. """ 132 trace = mailing.mailing_trace_ids.filtered(lambda t: t.model == record._name and t.res_id == record.id) 133 sms_sent = self._find_sms_sent(self.env['res.partner'], trace.sms_number) 134 self.assertTrue(bool(sms_sent)) 135 return self.gateway_sms_sent_click(sms_sent) 136 137 def gateway_sms_sent_click(self, sms_sent): 138 """ When clicking on a link in a SMS we actually don't have any 139 easy information in body, only body. We currently click on all found 140 shortened links. """ 141 for url in re.findall(tools.TEXT_URL_REGEX, sms_sent['body']): 142 if '/r/' in url: # shortened link, like 'http://localhost:8069/r/LBG/s/53' 143 parsed_url = werkzeug.urls.url_parse(url) 144 path_items = parsed_url.path.split('/') 145 code, sms_sms_id = path_items[2], int(path_items[4]) 146 trace_id = self.env['mailing.trace'].sudo().search([('sms_sms_id_int', '=', sms_sms_id)]).id 147 148 self.env['link.tracker.click'].sudo().add_click( 149 code, 150 ip='100.200.300.400', 151 country_code='BE', 152 mailing_trace_id=trace_id 153 ) 154 155 156class MassSMSCommon(MassMailCommon, SMSCommon, MassSMSCase): 157 158 @classmethod 159 def setUpClass(cls): 160 super(MassSMSCommon, cls).setUpClass() 161