def test_match_recipient_address(self): matcher = RecipientMatcher(self.env) expected = (None, 0, '*****@*****.**') self.assertEqual(expected, matcher.match_recipient('*****@*****.**')) self.assertEqual(expected, matcher.match_recipient('<*****@*****.**>')) self.assertEqual(expected, matcher.match_recipient( 'Name name <*****@*****.**>')) self.assertEqual(expected, matcher.match_recipient( u'Námë ńämé <*****@*****.**>'))
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return # CC field is stored as comma-separated string. Parse to set. chrome = Chrome(self.env) to_set = lambda cc: set(chrome.cc_list(cc)) cc_set = to_set(event.target['cc'] or '') # Harvest previous CC field if 'fields' in event.changes and 'cc' in event.changes['fields']: cc_set.update(to_set(event.changes['fields']['cc']['old'])) matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for cc in cc_set: recipient = matcher.match_recipient(cc) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4] if sid: sids.add((sid, auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return ticket = event.target owners = [ticket['owner']] # Harvest previous owner if 'fields' in event.changes and 'owner' in event.changes['fields']: owners.append(event.changes['fields']['owner']['old']) matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for owner in owners: recipient = matcher.match_recipient(owner) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4] if sid: sids.add((sid, auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return # CC field is stored as comma-separated string. Parse to set. chrome = Chrome(self.env) to_set = lambda cc: set(chrome.cc_list(cc)) cc_set = to_set(event.target['cc'] or '') # Harvest previous CC field if 'fields' in event.changes and 'cc' in event.changes['fields']: cc_set.update(to_set(event.changes['fields']['cc']['old'])) matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for cc in cc_set: recipient = matcher.match_recipient(cc) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4]) if sid: sids.add((sid,auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def _ticket_change_subscribers(subscriber, candidates): if not candidates: return if not isinstance(candidates, (list, set, tuple)): candidates = [candidates] # Get members of permission groups groups = PermissionSystem(subscriber.env).get_groups_dict() for cc in set(candidates): if cc in groups: candidates.remove(cc) candidates.update(groups[cc]) matcher = RecipientMatcher(subscriber.env) klass = subscriber.__class__.__name__ sids = set() for candidate in candidates: recipient = matcher.match_recipient(candidate) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in subscriber.default_subscriptions(): yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4] if sid: sids.add((sid, auth)) for s in Subscription.find_by_sids_and_class(subscriber.env, sids, klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return updaters = [row[0] for row in self.env.db_query(""" SELECT DISTINCT author FROM ticket_change WHERE ticket=%s """, (event.target.id, ))] matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for previous_updater in updaters: if previous_updater == event.author: continue recipient = matcher.match_recipient(previous_updater) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4]) if sid: sids.add((sid,auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return ticket = event.target owners = [ticket['owner']] # Harvest previous owner if 'fields' in event.changes and 'owner' in event.changes['fields']: owners.append(event.changes['fields']['owner']['old']) matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for owner in owners: recipient = matcher.match_recipient(owner) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4]) if sid: sids.add((sid,auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category == 'batchmodify': for ticket_event in event.get_ticket_change_events(self.env): for m in self.matches(ticket_event): yield m return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return ticket = event.target matcher = RecipientMatcher(self.env) recipient = matcher.match_recipient(ticket['reporter']) if not recipient: return sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4]) if sid: klass = self.__class__.__name__ for s in Subscription.find_by_sids_and_class( self.env, ((sid, auth), ), klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return updaters = [ row[0] for row in self.env.db_query( """ SELECT DISTINCT author FROM ticket_change WHERE ticket=%s """, (event.target.id, )) ] matcher = RecipientMatcher(self.env) klass = self.__class__.__name__ sids = set() for previous_updater in updaters: if previous_updater == event.author: continue recipient = matcher.match_recipient(previous_updater) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4] if sid: sids.add((sid, auth)) for s in Subscription.find_by_sids_and_class(self.env, sids, klass): yield s.subscription_tuple()
def test_match_recipient_use_short_addr(self): self.config.set('notification', 'use_short_addr', 'enabled') with self.env.db_transaction: self._add_session('user1') self._add_session('user2', email='user2-email') self._add_session('user3', email='*****@*****.**') self._add_session('*****@*****.**', email='user4') matcher = RecipientMatcher(self.env) self.assertEqual(('user1', 1, 'user1'), matcher.match_recipient('user1')) self.assertEqual(('user2', 1, 'user2-email'), matcher.match_recipient('user2')) self.assertEqual(('user3', 1, '*****@*****.**'), matcher.match_recipient('user3')) self.assertEqual(('*****@*****.**', 1, 'user4'), matcher.match_recipient('*****@*****.**')) self.assertEqual((None, 0, 'user9'), matcher.match_recipient('user9'))
def decorate_message(self, event, message, charset): if event.realm != 'ticket': return from_email = self._get_from_email(event) if event.category == 'batchmodify': tickets = sort_tickets_by_priority(self.env, event.target) subject = self._format_subj_batchmodify(tickets) targetid = ','.join(map(str, tickets)) msgid = self._get_message_id(targetid, from_email, event.time) else: subject = self._format_subj(event) ticket = event.target targetid = '%08d' % ticket.id more = ticket['reporter'] or '' msgid = self._get_message_id(targetid, from_email, None, more) url = self.env.abs_href.ticket(ticket.id) if event.category != 'created': set_header(message, 'In-Reply-To', msgid, charset) set_header(message, 'References', msgid, charset) msgid = self._get_message_id(targetid, from_email, event.time, more) cnum = ticket.get_comment_number(event.time) if cnum is not None: url += '#comment:%d' % cnum set_header(message, 'X-Trac-Ticket-ID', ticket.id, charset) set_header(message, 'X-Trac-Ticket-URL', url, charset) # When owner, reporter and updater are listed in the Cc header, # move the address to To header. if NotificationSystem(self.env).use_public_cc: to_addrs = set() matcher = RecipientMatcher(self.env) for rcpt in ticket['owner'], ticket['reporter'], event.author: rcpt = matcher.match_recipient(rcpt) if not rcpt: continue addr = rcpt[2] if addr: to_addrs.add(addr) if to_addrs: cc_addrs = get_message_addresses(message, 'Cc') to_addrs &= set(addr for name, addr in cc_addrs) if to_addrs: cc_header = ', '.join(create_header('Cc', (name, addr), charset) for name, addr in cc_addrs if addr not in to_addrs) if cc_header: set_header(message, 'Cc', cc_header, charset) elif 'Cc' in message: del message['Cc'] to_header = ', '.join(sorted(to_addrs)) set_header(message, 'To', to_header, charset) set_header(message, 'Subject', subject, charset) set_header(message, 'Message-ID', msgid, charset)
def test_match_recipient_ignore_domains(self): self.config.set('notification', 'ignore_domains', 'example.net,example.com') with self.env.db_transaction: self._add_session('user1', email='*****@*****.**') self._add_session('user2', email='*****@*****.**') self._add_session('user3', email='*****@*****.**') self._add_session('*****@*****.**') self._add_session('*****@*****.**') self._add_session('*****@*****.**') matcher = RecipientMatcher(self.env) # authenticated users self.assertEqual(('user1', 1, '*****@*****.**'), matcher.match_recipient('user1')) self.assertEqual(None, matcher.match_recipient('user2')) self.assertEqual(None, matcher.match_recipient('user3')) self.assertEqual(('*****@*****.**', 1, '*****@*****.**'), matcher.match_recipient('*****@*****.**')) self.assertEqual(None, matcher.match_recipient('*****@*****.**')) self.assertEqual(None, matcher.match_recipient('*****@*****.**')) # anonymous users self.assertEqual((None, 0, '*****@*****.**'), matcher.match_recipient('*****@*****.**')) self.assertEqual(None, matcher.match_recipient('*****@*****.**')) self.assertEqual(None, matcher.match_recipient('*****@*****.**'))
def _ticket_change_subscribers(subscriber, candidates): if not candidates: return if not isinstance(candidates, (list, set, tuple)): candidates = [candidates] matcher = RecipientMatcher(subscriber.env) klass = subscriber.__class__.__name__ sids = set() for candidate in candidates: recipient = matcher.match_recipient(candidate) if not recipient: continue sid, auth, addr = recipient # Default subscription for s in subscriber.default_subscriptions(): yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4] if sid: sids.add((sid, auth)) for s in Subscription.find_by_sids_and_class(subscriber.env, sids, klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return matcher = RecipientMatcher(self.env) recipient = matcher.match_recipient(event.author) if not recipient: return sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4]) if sid: klass = self.__class__.__name__ for s in Subscription.find_by_sids_and_class(self.env, ((sid,auth),), klass): yield s.subscription_tuple()
def matches(self, event): if event.realm != 'ticket': return if event.category not in ('created', 'changed', 'attachment added', 'attachment deleted'): return matcher = RecipientMatcher(self.env) recipient = matcher.match_recipient(event.author) if not recipient: return sid, auth, addr = recipient # Default subscription for s in self.default_subscriptions(): yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4] if sid: klass = self.__class__.__name__ for s in Subscription \ .find_by_sids_and_class(self.env, ((sid, auth),), klass): yield s.subscription_tuple()
def test_match_recipient_smtp_default_domain(self): self.config.set('notification', 'smtp_default_domain', 'default.example.net') with self.env.db_transaction: self._add_session('user1') self._add_session('user2', email='user2-email') self._add_session('user3', email='*****@*****.**') self._add_session('*****@*****.**', email='user4') self._add_session('*****@*****.**') matcher = RecipientMatcher(self.env) self.assertEqual(('user1', 1, '*****@*****.**'), matcher.match_recipient('user1')) self.assertEqual(('user2', 1, '*****@*****.**'), matcher.match_recipient('user2')) self.assertEqual(('user3', 1, '*****@*****.**'), matcher.match_recipient('user3')) self.assertEqual(('*****@*****.**', 1, '*****@*****.**'), matcher.match_recipient('*****@*****.**')) self.assertEqual(('*****@*****.**', 1, '*****@*****.**'), matcher.match_recipient('*****@*****.**')) self.assertEqual((None, 0, '*****@*****.**'), matcher.match_recipient('user9'))
class NotifyEmail(Notify): """Baseclass for notification by email. :since 1.1.3: deprecated and will be removed in 1.3.1 """ from_email = 'trac+tickets@localhost' subject = '' template_name = None nodomaddr_re = RecipientMatcher.nodomaddr_re addrsep_re = re.compile(r'[;\s,]+') def __init__(self, env): super(NotifyEmail, self).__init__(env) self.recipient_matcher = RecipientMatcher(env) self.shortaddr_re = self.recipient_matcher.shortaddr_re self.longaddr_re = self.recipient_matcher.longaddr_re self._ignore_domains = self.recipient_matcher.ignore_domains self.name_map = self.recipient_matcher.name_map self.email_map = self.recipient_matcher.email_map notify_sys = NotificationSystem(self.env) self._charset = create_charset(notify_sys.mime_encoding) def notify(self, resid, subject, author=None): self.subject = subject config = self.config['notification'] if not config.getbool('smtp_enabled'): return from_email, from_name = '', '' if author and config.getbool('smtp_from_author'): from_email = self.get_smtp_address(author) if from_email: from_name = self.name_map.get(author, '') if not from_name: mo = self.longaddr_re.search(author) if mo: from_name = mo.group(1) if not from_email: from_email = config.get('smtp_from') from_name = config.get('smtp_from_name') or self.env.project_name self.replyto_email = config.get('smtp_replyto') self.from_email = from_email or self.replyto_email self.from_name = from_name if not self.from_email and not self.replyto_email: message = tag( tag.p(_('Unable to send email due to identity crisis.')), # convert explicitly to `Fragment` to avoid breaking message # when passing `LazyProxy` object to `Fragment` tag.p(to_fragment(tag_( "Neither %(from_)s nor %(reply_to)s are specified in the " "configuration.", from_=tag.strong("[notification] smtp_from"), reply_to=tag.strong("[notification] smtp_replyto"))))) raise TracError(message, _("SMTP Notification Error")) Notify.notify(self, resid) def format_header(self, key, name, email=None): header = create_header(key, name, self._charset) if not email: return header else: header = str(header).replace('\\', r'\\') \ .replace('"', r'\"') return '"%s" <%s>' % (header, email) def add_headers(self, msg, headers): for h in headers: msg[h] = self.encode_header(h, headers[h]) def get_smtp_address(self, address): recipient = self.recipient_matcher.match_recipient(address) if not recipient: return None return recipient[2] def encode_header(self, key, value): if isinstance(value, tuple): return self.format_header(key, value[0], value[1]) mo = self.longaddr_re.match(value) if mo: return self.format_header(key, mo.group(1), mo.group(2)) return self.format_header(key, value) def _format_body(self): stream = self.template.generate(**self.data) # don't translate the e-mail stream t = deactivate() try: return stream.render('text', encoding='utf-8') finally: reactivate(t) def send(self, torcpts, ccrcpts, mime_headers={}): body = self._format_body() public_cc = self.config.getbool('notification', 'use_public_cc') headers = { 'X-Mailer': 'Trac %s, by Edgewall Software' % self.env.trac_version, 'X-Trac-Version': self.env.trac_version, 'X-Trac-Project': self.env.project_name, 'X-URL': self.env.project_url, 'Precedence': 'bulk', 'Auto-Submitted': 'auto-generated', 'Subject': self.subject, 'From': (self.from_name, self.from_email) if self.from_name else self.from_email, 'Reply-To': self.replyto_email } def build_addresses(rcpts): """Format and remove invalid addresses""" return filter(lambda x: x, [self.get_smtp_address(addr) for addr in rcpts]) def remove_dup(rcpts, all): """Remove duplicates""" tmp = [] for rcpt in rcpts: if not rcpt in all: tmp.append(rcpt) all.append(rcpt) return tmp, all notify_sys = NotificationSystem(self.env) toaddrs = build_addresses(torcpts) ccaddrs = build_addresses(ccrcpts) accaddrs = notify_sys.smtp_always_cc_list bccaddrs = notify_sys.smtp_always_bcc_list recipients = [] toaddrs, recipients = remove_dup(toaddrs, recipients) ccaddrs, recipients = remove_dup(ccaddrs, recipients) accaddrs, recipients = remove_dup(accaddrs, recipients) bccaddrs, recipients = remove_dup(bccaddrs, recipients) # if there is not valid recipient, leave immediately if len(recipients) < 1: self.env.log.info("no recipient for a ticket notification") return pcc = accaddrs if public_cc: pcc += ccaddrs if toaddrs: headers['To'] = ', '.join(toaddrs) if pcc: headers['Cc'] = ', '.join(pcc) headers['Date'] = formatdate() msg = create_mime_text(body, 'plain', self._charset) self.add_headers(msg, headers) self.add_headers(msg, mime_headers) NotificationSystem(self.env).send_email(self.from_email, recipients, msg.as_string())
def test_match_recipient_admit_domains(self): self.config.set('notification', 'admit_domains', 'LOCALDOMAIN') with self.env.db_transaction: self._add_session('user1', email='user1@localhost') self._add_session('user2', email='user2@localdomain') self._add_session('user3', email='*****@*****.**') self._add_session('user4@localhost') self._add_session('user5@localdomain') self._add_session('*****@*****.**') self._add_session('user7@localhost', email='*****@*****.**') self._add_session('user8@localdomain', email='user8@localhost') self._add_session('*****@*****.**', email='user9@localdomain') matcher = RecipientMatcher(self.env) # authenticated users self.assertEqual(None, matcher.match_recipient('user1')) self.assertEqual(('user2', 1, 'user2@localdomain'), matcher.match_recipient('user2')) self.assertEqual(('user3', 1, '*****@*****.**'), matcher.match_recipient('user3')) self.assertEqual(None, matcher.match_recipient('user4@localhost')) self.assertEqual(('user5@localdomain', 1, 'user5@localdomain'), matcher.match_recipient('user5@localdomain')) self.assertEqual(('*****@*****.**', 1, '*****@*****.**'), matcher.match_recipient('*****@*****.**')) self.assertEqual(('user7@localhost', 1, '*****@*****.**'), matcher.match_recipient('user7@localhost')) self.assertEqual(None, matcher.match_recipient('user8@localdomain')) self.assertEqual(('*****@*****.**', 1, 'user9@localdomain'), matcher.match_recipient('*****@*****.**')) # anonymous users self.assertEqual(None, matcher.match_recipient('foobar')) self.assertEqual(None, matcher.match_recipient('anon@localhost')) self.assertEqual((None, 0, 'anon@localdomain'), matcher.match_recipient('anon@localdomain')) self.assertEqual((None, 0, '*****@*****.**'), matcher.match_recipient('*****@*****.**'))
class NotifyEmail(Notify): """Baseclass for notification by email. :since 1.1.3: deprecated and will be removed in 1.3.1 """ from_email = 'trac+tickets@localhost' subject = '' template_name = None nodomaddr_re = RecipientMatcher.nodomaddr_re addrsep_re = re.compile(r'[;\s,]+') def __init__(self, env): super(NotifyEmail, self).__init__(env) self.recipient_matcher = RecipientMatcher(env) self.shortaddr_re = self.recipient_matcher.shortaddr_re self.longaddr_re = self.recipient_matcher.longaddr_re self._ignore_domains = self.recipient_matcher.ignore_domains self.name_map = self.recipient_matcher.name_map self.email_map = self.recipient_matcher.email_map notify_sys = NotificationSystem(self.env) self._charset = create_charset(notify_sys.mime_encoding) def notify(self, resid, subject, author=None): self.subject = subject config = self.config['notification'] if not config.getbool('smtp_enabled'): return from_email, from_name = '', '' if author and config.getbool('smtp_from_author'): from_email = self.get_smtp_address(author) if from_email: from_name = self.name_map.get(author, '') if not from_name: mo = self.longaddr_re.search(author) if mo: from_name = mo.group(1) if not from_email: from_email = config.get('smtp_from') from_name = config.get('smtp_from_name') or self.env.project_name self.replyto_email = config.get('smtp_replyto') self.from_email = from_email or self.replyto_email self.from_name = from_name if not self.from_email and not self.replyto_email: message = tag( tag.p(_('Unable to send email due to identity crisis.')), # convert explicitly to `Fragment` to avoid breaking message # when passing `LazyProxy` object to `Fragment` tag.p( to_fragment( tag_( "Neither %(from_)s nor %(reply_to)s are specified in the " "configuration.", from_=tag.strong("[notification] smtp_from"), reply_to=tag.strong( "[notification] smtp_replyto"))))) raise TracError(message, _("SMTP Notification Error")) Notify.notify(self, resid) def format_header(self, key, name, email=None): header = create_header(key, name, self._charset) if not email: return header else: header = str(header).replace('\\', r'\\') \ .replace('"', r'\"') return '"%s" <%s>' % (header, email) def add_headers(self, msg, headers): for h in headers: msg[h] = self.encode_header(h, headers[h]) def get_smtp_address(self, address): recipient = self.recipient_matcher.match_recipient(address) if not recipient: return None return recipient[2] def encode_header(self, key, value): if isinstance(value, tuple): return self.format_header(key, value[0], value[1]) mo = self.longaddr_re.match(value) if mo: return self.format_header(key, mo.group(1), mo.group(2)) return self.format_header(key, value) def _format_body(self): stream = self.template.generate(**self.data) # don't translate the e-mail stream t = deactivate() try: return stream.render('text', encoding='utf-8') finally: reactivate(t) def send(self, torcpts, ccrcpts, mime_headers={}): body = self._format_body() public_cc = self.config.getbool('notification', 'use_public_cc') headers = { 'X-Mailer': 'Trac %s, by Edgewall Software' % self.env.trac_version, 'X-Trac-Version': self.env.trac_version, 'X-Trac-Project': self.env.project_name, 'X-URL': self.env.project_url, 'Precedence': 'bulk', 'Auto-Submitted': 'auto-generated', 'Subject': self.subject, 'From': (self.from_name, self.from_email) if self.from_name else self.from_email, 'Reply-To': self.replyto_email } def build_addresses(rcpts): """Format and remove invalid addresses""" return filter(lambda x: x, [self.get_smtp_address(addr) for addr in rcpts]) def remove_dup(rcpts, all): """Remove duplicates""" tmp = [] for rcpt in rcpts: if not rcpt in all: tmp.append(rcpt) all.append(rcpt) return tmp, all notify_sys = NotificationSystem(self.env) toaddrs = build_addresses(torcpts) ccaddrs = build_addresses(ccrcpts) accaddrs = notify_sys.smtp_always_cc_list bccaddrs = notify_sys.smtp_always_bcc_list recipients = [] toaddrs, recipients = remove_dup(toaddrs, recipients) ccaddrs, recipients = remove_dup(ccaddrs, recipients) accaddrs, recipients = remove_dup(accaddrs, recipients) bccaddrs, recipients = remove_dup(bccaddrs, recipients) # if there is not valid recipient, leave immediately if len(recipients) < 1: self.env.log.info("no recipient for a ticket notification") return pcc = accaddrs if public_cc: pcc += ccaddrs if toaddrs: headers['To'] = ', '.join(toaddrs) if pcc: headers['Cc'] = ', '.join(pcc) headers['Date'] = formatdate() msg = create_mime_text(body, 'plain', self._charset) self.add_headers(msg, headers) self.add_headers(msg, mime_headers) NotificationSystem(self.env).send_email(self.from_email, recipients, msg.as_string())
def test_match_recipient_anonymous(self): matcher = RecipientMatcher(self.env) self.assertEqual(None, matcher.match_recipient('anonymous'))
def test_match_recipient_empty(self): matcher = RecipientMatcher(self.env) self.assertEqual(None, matcher.match_recipient(None)) self.assertEqual(None, matcher.match_recipient(u''))