Merge lp:~allenap/launchpad/bug-mail-case-sensitive-domain-bug-48464-fix into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 12057
Proposed branch: lp:~allenap/launchpad/bug-mail-case-sensitive-domain-bug-48464-fix
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/bug-mail-case-sensitive-domain-bug-48464-refactor
Diff against target: 163 lines (+65/-44)
3 files modified
lib/lp/services/mail/handlers.py (+19/-35)
lib/lp/services/mail/tests/incomingmail.txt (+5/-3)
lib/lp/services/mail/tests/test_handlers.py (+41/-6)
To merge this branch: bzr merge lp:~allenap/launchpad/bug-mail-case-sensitive-domain-bug-48464-fix
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+43547@code.launchpad.net

Commit message

[r=abentley][ui=none][bug=48464] Treat target domains on incoming mail without regard to case.

Description of the change

This makes MailHandler case-insensitive to domains, moves its doctests into unit tests, and updates the integration test, incomingmail.txt, to demonstrate case-insensitivity.

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

The code looks good.

Please add docs for all the tests. The copyright date for test_handlers should be 2009-2010, not just 2010.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/mail/handlers.py'
--- lib/lp/services/mail/handlers.py 2010-12-13 20:43:45 +0000
+++ lib/lp/services/mail/handlers.py 2010-12-13 20:43:46 +0000
@@ -2,6 +2,9 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
5__all__ = [
6 "mail_handlers",
7 ]
58
6from canonical.config import config9from canonical.config import config
7from lp.answers.mail.handler import AnswerTrackerHandler10from lp.answers.mail.handler import AnswerTrackerHandler
@@ -13,50 +16,31 @@
13class MailHandlers:16class MailHandlers:
14 """All the registered mail handlers."""17 """All the registered mail handlers."""
1518
19 DEFAULT = (
20 (config.launchpad.bugs_domain, MaloneHandler),
21 (config.launchpad.specs_domain, SpecificationHandler),
22 (config.answertracker.email_domain, AnswerTrackerHandler),
23 # XXX flacoste 2007-04-23 Backward compatibility for old domain.
24 # We probably want to remove it in the future.
25 ('support.launchpad.net', AnswerTrackerHandler),
26 (config.launchpad.code_domain, CodeHandler),
27 )
28
16 def __init__(self):29 def __init__(self):
17 self._handlers = {30 self._handlers = {}
18 config.launchpad.bugs_domain: MaloneHandler(),31 for domain, handler_factory in self.DEFAULT:
19 config.launchpad.specs_domain: SpecificationHandler(),32 self.add(domain, handler_factory())
20 config.answertracker.email_domain: AnswerTrackerHandler(),
21 # XXX flacoste 2007-04-23 Backward compatibility for old domain.
22 # We probably want to remove it in the future.
23 'support.launchpad.net': AnswerTrackerHandler(),
24 config.launchpad.code_domain: CodeHandler(),
25 }
2633
27 def get(self, domain):34 def get(self, domain):
28 """Return the handler for the given email domain.35 """Return the handler for the given email domain.
2936
30 Return None if no such handler exists.37 Return None if no such handler exists.
31
32 >>> handlers = MailHandlers()
33 >>> handlers.get('bugs.launchpad.net') #doctest: +ELLIPSIS
34 <...MaloneHandler...>
35 >>> handlers.get('no.such.domain') is None
36 True
37 """38 """
38 return self._handlers.get(domain)39 return self._handlers.get(domain.lower())
3940
40 def add(self, domain, handler):41 def add(self, domain, handler):
41 """Adds a handler for a domain.42 """Adds a handler for a domain."""
4243 self._handlers[domain.lower()] = handler
43 >>> handlers = MailHandlers()
44 >>> handlers.get('some.domain') is None
45 True
46 >>> handler = object()
47 >>> handlers.add('some.domain', handler)
48 >>> handlers.get('some.domain') is handler
49 True
50
51 If there already is a handler for the domain, the old one will
52 get overwritten:
53
54 >>> new_handler = object()
55 >>> handlers.add('some.domain', new_handler)
56 >>> handlers.get('some.domain') is new_handler
57 True
58 """
59 self._handlers[domain] = handler
6044
6145
62mail_handlers = MailHandlers()46mail_handlers = MailHandlers()
6347
=== modified file 'lib/lp/services/mail/tests/incomingmail.txt'
--- lib/lp/services/mail/tests/incomingmail.txt 2010-12-13 20:43:45 +0000
+++ lib/lp/services/mail/tests/incomingmail.txt 2010-12-13 20:43:46 +0000
@@ -50,17 +50,19 @@
50 >>> from canonical.testing.layers import LaunchpadZopelessLayer50 >>> from canonical.testing.layers import LaunchpadZopelessLayer
51 >>> from canonical.launchpad.mail import sendmail as original_sendmail51 >>> from canonical.launchpad.mail import sendmail as original_sendmail
5252
53For these examples, we don't want the Precedence header added.53For these examples, we don't want the Precedence header added. Domains
54are treated without regard to case: for incoming mail, foo.com and
55FOO.COM are treated equivalently.
5456
55 >>> def sendmail(msg, to_addrs=None):57 >>> def sendmail(msg, to_addrs=None):
56 ... return original_sendmail(msg, to_addrs=to_addrs, bulk=False)58 ... return original_sendmail(msg, to_addrs=to_addrs, bulk=False)
5759
58 >>> LaunchpadZopelessLayer.switchDbUser('launchpad')60 >>> LaunchpadZopelessLayer.switchDbUser('launchpad')
59 >>> msgids = {'foo.com': [], 'bar.com': [], 'baz.com': []}61 >>> msgids = {'foo.com': [], 'bar.com': [], 'baz.com': []}
60 >>> for domain in ('foo.com', 'bar.com', 'foo.com', 'baz.com'):62 >>> for domain in ('foo.com', 'bar.com', 'FOO.COM', 'baz.com'):
61 ... msg = read_test_message('signed_detached.txt')63 ... msg = read_test_message('signed_detached.txt')
62 ... msg.replace_header('To', '123@%s' % domain)64 ... msg.replace_header('To', '123@%s' % domain)
63 ... msgids[domain].append("<%s>" % sendmail(msg))65 ... msgids[domain.lower()].append("<%s>" % sendmail(msg))
6466
65handleMail will check the timestamp on signed messages, but the signatures67handleMail will check the timestamp on signed messages, but the signatures
66on our testdata are old, and in these tests we don't care to be told.68on our testdata are old, and in these tests we don't care to be told.
6769
=== modified file 'lib/lp/services/mail/tests/test_handlers.py'
--- lib/lp/services/mail/tests/test_handlers.py 2010-12-13 20:43:45 +0000
+++ lib/lp/services/mail/tests/test_handlers.py 2010-12-13 20:43:46 +0000
@@ -1,10 +1,45 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
55
6from doctest import DocTestSuite6from lp.services.mail.handlers import MailHandlers
77from lp.testing import TestCase
88
9def test_suite():9
10 return DocTestSuite('lp.services.mail.handlers')10class TestMailHandlers(TestCase):
11 """Tests for the `MailHandlers` class."""
12
13 def test_get(self):
14 # MailHandlers.get() should return the registered handler for the
15 # given domain.
16 handlers = MailHandlers()
17 self.assertIsNot(None, handlers.get("bugs.launchpad.net"))
18 self.assertIs(None, handlers.get("no.such.domain"))
19
20 def test_get_is_case_insensitive(self):
21 # The domain passed to get() is treated case-insentitively.
22 handlers = MailHandlers()
23 handler = object()
24 handlers.add("some.domain", handler)
25 self.assertIs(handler, handlers.get("some.domain"))
26 self.assertIs(handler, handlers.get("SOME.DOMAIN"))
27 self.assertIs(handler, handlers.get("Some.Domain"))
28
29 def test_add_for_new_domain(self):
30 # MailHandlers.add() registers a handler for the given domain.
31 handlers = MailHandlers()
32 self.assertIs(None, handlers.get("some.domain"))
33 handler = object()
34 handlers.add("some.domain", handler)
35 self.assertIs(handler, handlers.get("some.domain"))
36
37 def test_add_for_existing_domain(self):
38 # When adding a new handler for an already congfigured domain, the
39 # existing handler is overwritten.
40 handlers = MailHandlers()
41 handler1 = object()
42 handlers.add("some.domain", handler1)
43 handler2 = object()
44 handlers.add("some.domain", handler2)
45 self.assertIs(handler2, handlers.get("some.domain"))