Merge lp:~sinzui/launchpad/mailing-list-email-0 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Approved by: Māris Fogels
Approved revision: no longer in the source branch.
Merged at revision: 12078
Proposed branch: lp:~sinzui/launchpad/mailing-list-email-0
Merge into: lp:launchpad
Diff against target: 2048 lines (+452/-1460)
16 files modified
lib/lp/registry/tests/test_mailinglistapi.py (+8/-0)
lib/lp/services/mailman/doc/basic-integration.txt (+1/-5)
lib/lp/services/mailman/doc/bounces.txt (+0/-54)
lib/lp/services/mailman/doc/contact-address.txt (+0/-379)
lib/lp/services/mailman/doc/decorations.txt (+0/-169)
lib/lp/services/mailman/doc/messages.txt (+0/-33)
lib/lp/services/mailman/doc/postings.txt (+0/-812)
lib/lp/services/mailman/monkeypatches/__init__.py (+1/-1)
lib/lp/services/mailman/monkeypatches/lphandler.py (+2/-2)
lib/lp/services/mailman/monkeypatches/lpstanding.py (+2/-4)
lib/lp/services/mailman/monkeypatches/mm_cfg.py.in (+1/-1)
lib/lp/services/mailman/testing/__init__.py (+3/-0)
lib/lp/services/mailman/tests/test_lphandler.py (+83/-0)
lib/lp/services/mailman/tests/test_lpheaders.py (+103/-0)
lib/lp/services/mailman/tests/test_lpstanding.py (+59/-0)
lib/lp/services/mailman/tests/test_mm_cfg.py (+189/-0)
To merge this branch: bzr merge lp:~sinzui/launchpad/mailing-list-email-0
Reviewer Review Type Date Requested Status
Māris Fogels (community) Approve
Review via email: mp+43656@code.launchpad.net

Description of the change

Fix the footer in mailman emails.

    Launchpad bug: https://bugs.launchpad.net/bugs/689431
    Pre-implementation: no one
    Test command: ./bin/test -vv -t mailman.tests.test_

Bug #689431 [change the mailing lists default signature]
    The mailman email signature is not interpreted by the mail clients like a
    real signature and appear in the quote when you write a reply.

--------------------------------------------------------------------

RULES

Bug #689431 [change the mailing lists default signature]
    * Used '-- ' like all other Lp emails
    * Replace the decorations test with a unittest
    * If time permits, add tests for lpstanding and lphandler

QA

Bug #689431 [change the mailing lists default signature]
    * Send an email to a list.
    * verify the footer is separated from the body by '-- '

LINT

    lib/lp/services/mailman/doc/basic-integration.txt
    lib/lp/services/mailman/doc/contact-address.txt
    lib/lp/services/mailman/doc/postings.txt
    lib/lp/services/mailman/monkeypatches/lphandler.py
    lib/lp/services/mailman/monkeypatches/lpstanding.py
    lib/lp/services/mailman/monkeypatches/mm_cfg.py.in
    lib/lp/services/mailman/testing/__init__.py
    lib/lp/services/mailman/tests/test_lphandler.py
    lib/lp/services/mailman/tests/test_lpheaders.py
    lib/lp/services/mailman/tests/test_lpstanding.py

^ There is lint in the old doctests. I can clean up the white space and
line length issues before I land my branch.

IMPLEMENTATION

Changed the mailman footer, made it like other footers from Launchpad. Added
test_lpheaders to replace decorations.txt. The `mlist.use_dollar_strings`
change was required to test the actual formatting because lp lists are
automatically set to use $substitutions.
    lib/lp/services/mailman/doc/basic-integration.txt
    lib/lp/services/mailman/doc/contact-address.txt
    lib/lp/services/mailman/doc/postings.txt
    lib/lp/services/mailman/monkeypatches/mm_cfg.py.in
    lib/lp/services/mailman/testing/__init__.p
    lib/lp/services/mailman/tests/test_lpheaders.py

Added replacement tests for lpstanding and lphandler. Both the the modules
were needed updating to get the xmlrpc proxy using the proper method that
can also be tested. I deleted the standing/first post moderation section of
the posting.txt since it was not directly testing our standing code.
    lib/lp/services/mailman/monkeypatches/lphandler.py
    lib/lp/services/mailman/monkeypatches/lpstanding.py
    lib/lp/services/mailman/tests/test_lphandler.py
    lib/lp/services/mailman/tests/test_lpstanding.py

To post a comment you must log in.
Revision history for this message
Māris Fogels (mars) wrote :

Hi Curtis,

This change looks good to me, r=mars. Great job updating the test suite, too.

Maris

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/tests/test_mailinglistapi.py'
2--- lib/lp/registry/tests/test_mailinglistapi.py 2010-10-26 15:47:24 +0000
3+++ lib/lp/registry/tests/test_mailinglistapi.py 2010-12-15 16:00:37 +0000
4@@ -9,6 +9,7 @@
5
6 import transaction
7
8+from canonical.config import config
9 from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
10 from canonical.testing.layers import DatabaseFunctionalLayer
11 from lp.registry.tests.mailinglists_helper import new_team
12@@ -75,3 +76,10 @@
13 def test_isRegisteredInLaunchpad_email_without_person(self):
14 self.factory.makeAccount('Me', email='me@fndor.dom')
15 self.assertFalse(self.api.isRegisteredInLaunchpad('me@fndor.dom'))
16+
17+ def test_isRegisteredInLaunchpad_archive_address_is_false(self):
18+ # The Mailman archive address can never be owned by an Lp user
19+ # because such a user would have acces to all lists.
20+ email = config.mailman.archive_address
21+ self.factory.makePerson(email=email)
22+ self.assertFalse(self.api.isRegisteredInLaunchpad(email))
23
24=== modified file 'lib/lp/services/mailman/doc/basic-integration.txt'
25--- lib/lp/services/mailman/doc/basic-integration.txt 2010-09-17 20:46:58 +0000
26+++ lib/lp/services/mailman/doc/basic-integration.txt 2010-12-15 16:00:37 +0000
27@@ -113,8 +113,4 @@
28 X-RcptTo: anne.person@example.com
29 <BLANKLINE>
30 This is a test message.
31- _______________________________________________
32- Mailing list: http://launchpad.dev/~alpha
33- Post to : alpha@lists.launchpad.dev
34- Unsubscribe : http://launchpad.dev/~alpha
35- More help : http://help.launchpad.dev/ListHelp
36+ ...
37
38=== removed file 'lib/lp/services/mailman/doc/bounces.txt'
39--- lib/lp/services/mailman/doc/bounces.txt 2010-10-18 22:24:59 +0000
40+++ lib/lp/services/mailman/doc/bounces.txt 1970-01-01 00:00:00 +0000
41@@ -1,54 +0,0 @@
42-=================
43-Bounce processing
44-=================
45-
46-Currently, we do no bounce processing for Launchpad mailing lists. We still
47-run the bounce processor occasionally though in order to clear any accumulated
48-bounce messages.
49-
50- >>> from lp.services.mailman.testing import helpers
51- >>> list_one = helpers.create_list('itest-one')
52- >>> helpers.subscribe('Anne', 'itest-one')
53-
54-A (faked) DSN bounce message is received from Anne's mail server.
55-
56- >>> from Mailman import mm_cfg
57- >>> from Mailman.Post import inject
58-
59- >>> inject('itest-one', """\
60- ... From: <mailer-daemon@example.com>
61- ... Message-ID: <aardvark>
62- ... MIME-Version: 1.0
63- ... Content-Type: multipart/report; report-type=delivery-status;
64- ... boundary="AAA"
65- ...
66- ... --AAA
67- ... ignore
68- ...
69- ... --AAA
70- ... Content-Type: message/delivery-status
71- ...
72- ... Original-Recipient: rfc822; anne.person@example.com
73- ... Action: failed
74- ...
75- ... --AAA--
76- ... """, qdir=mm_cfg.BOUNCEQUEUE_DIR)
77-
78- # Because bounce processing is disabled, and because the bounce runner
79- # provides no feedback when it discards bounce messages, there really is
80- # no good way to prove that nothing is processed for the right reasons.
81- # This is the best we can do. If bounce processing were not disabled,
82- # this wait would not time out and the getBounceInfo() call below would
83- # return an object. Take my word for it.
84- >>> bounce_watcher.expecting_timeout = True
85- >>> print bounce_watcher.wait_for_processing()
86- Timed out
87-
88-Even though the bounce message was processed, Anne has no bounce score because
89-no bounces are being registered.
90-
91- >>> from Mailman.MailList import MailList
92- >>> mailing_list = MailList('itest-one', lock=False)
93- >>> info = mailing_list.getBounceInfo('anne.person@example.com')
94- >>> print info
95- None
96
97=== removed file 'lib/lp/services/mailman/doc/contact-address.txt'
98--- lib/lp/services/mailman/doc/contact-address.txt 2010-10-19 01:42:48 +0000
99+++ lib/lp/services/mailman/doc/contact-address.txt 1970-01-01 00:00:00 +0000
100@@ -1,379 +0,0 @@
101-================================================
102-Using a mailing list as a team's contact address
103-================================================
104-
105-The contact address for any team can be set to the team's mailing list, in
106-which case all notifications sent by Launchpad to the team will be sent to the
107-mailing list's address.
108-
109-Create a mailing list for the contact address of our new team.
110-
111- >>> from lp.services.mailman.testing import helpers
112- >>> list_one = helpers.create_list('itest-one')
113-
114- # Ignore the new list notification message.
115- >>> smtpd.reset()
116-
117-Anne subscribes to the team and its mailing list.
118-
119- >>> helpers.subscribe('Anne', 'itest-one')
120-
121-The team is set as the project's answer contact.
122-
123- >>> from canonical.launchpad.testing.pages import strip_label
124-
125- >>> from canonical.testing.layers import BaseLayer
126- >>> root_url = BaseLayer.appserver_root_url()
127- >>> browser = Browser('no-priv@canonical.com:test')
128- >>> browser.open('%s/~itest-one/+contactaddress' % root_url)
129- >>> browser.getControl('The Launchpad mailing list').selected = True
130- >>> browser.getControl('Change').click()
131-
132- >>> browser.getLink('Change details').click()
133- >>> browser.getLink('Set contact address').click()
134- >>> control = browser.getControl(name='field.contact_method')
135- >>> [strip_label(label) for label in control.displayValue]
136- ['The Launchpad mailing list for this team...]
137-
138- >>> answers_url = BaseLayer.appserver_root_url('answers')
139- >>> browser.open(
140- ... '%s/firefox/+answer-contact' % answers_url)
141- >>> browser.getControl(name='field.answer_contact_teams').displayValue = [
142- ... 'Itest One']
143- >>> browser.getControl('Continue').click()
144-
145-Now that the contact address for the team is its mailing list, a new questions
146-will be delivered to the mailing list.
147-
148- >>> browser.open('%s/firefox/+addquestion' % answers_url)
149- >>> browser.getControl('Summary').value = 'A new question'
150- >>> browser.getControl('Continue').click()
151- >>> browser.getControl('Description').value = 'More detail.'
152- >>> browser.getControl('Post Question').click()
153-
154-There are now three messages in the mailbox. One message goes to
155-no-priv@canonical.com because he added the question, one goes to the mailing
156-list's archive, and one goes to Anne because she is the mailing list's only
157-member.
158-
159- # The three messages addressed to the mailing list seen on the smtp server
160- # are 1) the original posted message on the +addquestion, 2) the message
161- # as sent to Anne, 3) the message as sent to the archiver.
162- >>> smtpd_watcher.wait_for_list_traffic('itest-one')
163- >>> smtpd_watcher.wait_for_list_traffic('itest-one', personal=True)
164- >>> smtpd_watcher.wait_for_list_traffic('itest-one', personal=True)
165-
166- >>> messages = list(smtpd)
167- >>> len(messages)
168- 3
169-
170-Here is the message to the person who added the question.
171-
172- >>> print messages[0].as_string()
173- Content-Type: text/plain; charset="utf-8"
174- ...
175- X-Launchpad-Question: product=firefox; status=Open; assignee=None;
176- priority=Normal; language=en
177- Reply-To: question...@answers.launchpad.net
178- X-Launchpad-Message-Rationale: Subscriber
179- To: no-priv@canonical.com
180- From: No Privileges Person <question...@answers.launchpad.net>
181- Subject: [Question #...]: A new question
182- ...
183- Sender: bounces@canonical.com
184- Errors-To: bounces@canonical.com
185- Return-Path: bounces@canonical.com
186- Precedence: bulk
187- ...
188- X-MailFrom: bounces@canonical.com
189- X-RcptTo: no-priv@canonical.com
190- <BLANKLINE>
191- New question #... on Mozilla Firefox:
192- http://answers.launchpad.dev:.../firefox/+question/...
193- <BLANKLINE>
194- More detail.
195- <BLANKLINE>
196- -- =
197- <BLANKLINE>
198- You received this question notification because you are a direct
199- subscriber of the question.
200-
201-Here is the message to Anne, the mailing list member.
202-
203- >>> print messages[1].as_string()
204- MIME-Version: 1.0
205- X-Launchpad-Question: product=firefox; status=Open; assignee=None;
206- priority=Normal; language=en
207- X-Launchpad-Message-Rationale: Answer Contact (firefox) @itest-one
208- To: itest-one@lists.launchpad.dev
209- From: No Privileges Person <question...@answers.launchpad.net>
210- ...
211- Subject: [Itest-one] [Question #...]: A new question
212- X-BeenThere: itest-one@lists.launchpad.dev
213- X-Mailman-Version: ...
214- Reply-To: question...@answers.launchpad.net
215- List-Id: <itest-one.lists.launchpad.dev>
216- List-Help: <http://help.launchpad.dev/ListHelp>
217- List-Subscribe: <http://launchpad.dev/~itest-one>
218- List-Unsubscribe: <http://launchpad.dev/~itest-one>
219- List-Post: <mailto:itest-one@lists.launchpad.dev>
220- List-Archive: <http://lists.launchpad.dev/itest-one>
221- List-Owner: <http://launchpad.dev/~itest-one>
222- Content-Type: text/plain; charset="us-ascii"
223- Content-Transfer-Encoding: 7bit
224- Sender: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
225- Errors-To: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
226- ...
227- X-MailFrom: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
228- X-RcptTo: anne.person@example.com
229- <BLANKLINE>
230- New question #... on Mozilla Firefox:
231- http://answers.launchpad.dev:.../firefox/+question/...
232- <BLANKLINE>
233- More detail.
234- <BLANKLINE>
235- --
236- You received this question notification because you are a member of
237- Itest One, which is an answer contact for Mozilla Firefox.
238- _______________________________________________
239- Mailing list: http://launchpad.dev/~itest-one
240- Post to : itest-one@lists.launchpad.dev
241- Unsubscribe : http://launchpad.dev/~itest-one
242- More help : http://help.launchpad.dev/ListHelp
243-
244-Here is the message to the archive submission address.
245-
246- # This is mostly the same as the message to Anne, so we'll be succinct.
247- >>> print messages[2].as_string()
248- MIME-Version: 1.0
249- ...
250- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
251- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
252- ...
253- <BLANKLINE>
254- <BLANKLINE>
255-
256-Similarly, when the team is subscribed to a blueprint, a notification will be
257-sent to Anne and to the archive, via the mailing list.
258-
259- >>> browser.open(
260- ... '%s/firefox/+spec/canvas/+addsubscriber' % root_url)
261- >>> browser.getControl('Subscriber').value = 'itest-one'
262- >>> browser.getControl('Continue').click()
263-
264-Launchpad sends a message to the team's contact address. This message is
265-delivered directly to Mailman and is not available for viewing in the smtp
266-server.
267-
268- >>> smtpd_watcher.wait_for_list_traffic('itest-one')
269-
270-Mailman then delivers two messages to the team mailing list's recipients.
271-One is sent to Anne and the other is sent to the archiver.
272-
273- >>> smtpd_watcher.wait_for_list_traffic('itest-one', personal=True)
274- >>> smtpd_watcher.wait_for_list_traffic('itest-one', personal=True)
275- >>> messages = list(smtpd)
276- >>> len(messages)
277- 2
278- >>> from operator import itemgetter
279- >>> messages.sort(key=itemgetter('sender'))
280-
281-This one gets sent to Anne...
282-
283- >>> print messages[0].as_string()
284- MIME-Version: 1.0
285- To: itest-one@lists.launchpad.dev
286- From: No Privileges Person <no-priv@canonical.com>
287- ...
288- Subject: [Itest-one] [Blueprint canvas] Support <canvas> Objects
289- X-BeenThere: itest-one@lists.launchpad.dev
290- ...
291- Sender: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
292- Errors-To: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
293- ...
294- X-MailFrom: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
295- X-RcptTo: anne.person@example.com
296- <BLANKLINE>
297- You are now subscribed to the blueprint canvas - Support <canvas>
298- Objects.
299- <BLANKLINE>
300- --
301- http://blueprints.launchpad.dev:.../firefox/+spec/canvas
302- _______________________________________________
303- Mailing list: http://launchpad.dev/~itest-one
304- Post to : itest-one@lists.launchpad.dev
305- Unsubscribe : http://launchpad.dev/~itest-one
306- More help : http://help.launchpad.dev/ListHelp
307-
308-...and this one gets sent to the archives.
309-
310- >>> print messages[1].as_string()
311- MIME-Version: 1.0
312- To: itest-one@lists.launchpad.dev
313- From: No Privileges Person <no-priv@canonical.com>
314- ...
315- Subject: [Itest-one] [Blueprint canvas] Support <canvas> Objects
316- X-BeenThere: itest-one@lists.launchpad.dev
317- ...
318- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
319- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
320- ...
321- X-MailFrom: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
322- X-RcptTo: archive@mail-archive.dev
323- <BLANKLINE>
324- You are now subscribed to the blueprint canvas - Support <canvas>
325- Objects.
326- <BLANKLINE>
327- --
328- http://blueprints.launchpad.dev:.../firefox/+spec/canvas
329- _______________________________________________
330- Mailing list: http://launchpad.dev/~itest-one
331- Post to : itest-one@lists.launchpad.dev
332- Unsubscribe : http://launchpad.dev/~itest-one
333- More help : http://help.launchpad.dev/ListHelp
334-
335-The team's contact address is set to each team member individually.
336-Notifications will no longer be sent to the mailing list.
337-
338- >>> browser.open('%s/~itest-one/+contactaddress' % root_url)
339- >>> browser.getControl('Each member individually').selected = True
340- >>> browser.getControl('Change').click()
341-
342- >>> browser.getLink('Change details').click()
343- >>> browser.getLink('Set contact address').click()
344- >>> control = browser.getControl(name='field.contact_method')
345- >>> [strip_label(label) for label in control.displayValue]
346- ['Each member individually']
347-
348- >>> browser.open('%s/firefox/+addquestion' % answers_url)
349- >>> browser.getControl('Summary').value = 'Another question'
350- >>> browser.getControl('Continue').click()
351- >>> browser.getControl('Description').value = 'More detail.'
352- >>> browser.getControl('Post Question').click()
353-
354- >>> smtpd_watcher.expecting_timeout = True
355- >>> smtpd_watcher.wait_for_list_traffic('itest-one')
356- 'Timed out'
357-
358-There are two messages in the mailbox. One message was sent to
359-no-priv@canonical.com because he added the question. The other goes to Anne
360-as a member of the team, since the team's contact address is not set. Nothing
361-is sent to the archive because the mailing list was not used.
362-
363- >>> messages = list(smtpd)
364- >>> len(messages)
365- 2
366-
367- # Because this doesn't go through the mailing list, the Sender header will
368- # be the same for both, so sort on the To field instead.
369- >>> messages.sort(key=itemgetter('to'), reverse=True)
370-
371-This message goes to the submitter of the question.
372-
373- >>> print messages[0].as_string()
374- Content-Type: text/plain; charset="utf-8"
375- ...
376- To: no-priv@canonical.com
377- From: No Privileges Person <question...@answers.launchpad.net>
378- Subject: [Question #...]: Another question
379- ...
380- <BLANKLINE>
381- New question #... on Mozilla Firefox:
382- http://answers.launchpad.dev:8085/firefox/+question/...
383- <BLANKLINE>
384- More detail.
385- <BLANKLINE>
386- -- =
387- <BLANKLINE>
388- You received this question notification because you are a direct
389- subscriber of the question.
390- <BLANKLINE>
391- <BLANKLINE>
392-
393-This message goes to the team members individually, and Anne is the only team
394-member.
395-
396- >>> print messages[1].as_string()
397- Content-Type: text/plain; charset="utf-8"
398- ...
399- Reply-To: question...@answers.launchpad.net
400- X-Launchpad-Message-Rationale: Answer Contact (firefox) @itest-one
401- To: anne.person@example.com
402- From: No Privileges Person <question...@answers.launchpad.net>
403- Subject: [Question #...]: Another question
404- ...
405- <BLANKLINE>
406- New question #... on Mozilla Firefox:
407- http://answers.launchpad.dev:8085/firefox/+question/...
408- <BLANKLINE>
409- More detail.
410- <BLANKLINE>
411- -- =
412- <BLANKLINE>
413- You received this question notification because you are a member of
414- Itest One, which is an answer contact for Mozilla Firefox.
415- <BLANKLINE>
416- <BLANKLINE>
417-
418-When emails are sent directly to the mailing list address they're still
419-delivered since the mailing list is still active.
420-
421- >>> from Mailman.Post import inject
422- >>> inject('itest-one', """\
423- ... From: anne.person@example.com
424- ... To: itest-one@lists.launchpad.dev
425- ... Subject: A member post
426- ... Message-ID: <first-injection>
427- ...
428- ... Hi, I am a member of Launchpad.
429- ... """)
430- >>> smtpd_watcher.wait_for_mbox_delivery('first-injection')
431- >>> smtpd_watcher.wait_for_mbox_delivery('first-injection')
432- >>> messages = list(smtpd)
433-
434-Anne (as the only member of the mailing list) and the archive submission
435-address both get copies of the message.
436-
437- >>> len(messages)
438- 2
439- >>> messages.sort(key=itemgetter('sender'))
440-
441- >>> print messages[0].as_string()
442- From: anne.person@example.com
443- To: itest-one@lists.launchpad.dev
444- Message-ID: <first-injection>
445- Subject: [Itest-one] A member post
446- X-BeenThere: itest-one@lists.launchpad.dev
447- ...
448- Sender: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
449- Errors-To: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
450- ...
451- X-MailFrom: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
452- X-RcptTo: anne.person@example.com
453- <BLANKLINE>
454- Hi, I am a member of Launchpad.
455- _______________________________________________
456- Mailing list: http://launchpad.dev/~itest-one
457- Post to : itest-one@lists.launchpad.dev
458- Unsubscribe : http://launchpad.dev/~itest-one
459- More help : http://help.launchpad.dev/ListHelp
460-
461- >>> print messages[1].as_string()
462- From: anne.person@example.com
463- To: itest-one@lists.launchpad.dev
464- Message-ID: <first-injection>
465- Subject: [Itest-one] A member post
466- X-BeenThere: itest-one@lists.launchpad.dev
467- ...
468- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
469- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
470- ...
471- X-MailFrom: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
472- X-RcptTo: archive@mail-archive.dev
473- <BLANKLINE>
474- Hi, I am a member of Launchpad.
475- _______________________________________________
476- Mailing list: http://launchpad.dev/~itest-one
477- Post to : itest-one@lists.launchpad.dev
478- Unsubscribe : http://launchpad.dev/~itest-one
479- More help : http://help.launchpad.dev/ListHelp
480
481=== removed file 'lib/lp/services/mailman/doc/decorations.txt'
482--- lib/lp/services/mailman/doc/decorations.txt 2010-10-19 01:42:48 +0000
483+++ lib/lp/services/mailman/doc/decorations.txt 1970-01-01 00:00:00 +0000
484@@ -1,169 +0,0 @@
485-===========
486-Decorations
487-===========
488-
489-Messages sent by Mailman to team mailing list recipients have a number of
490-decorations that help users interact better with the list and its archive.
491-These decorations live in the message headers, and in the text footer that
492-Mailman adds to the bottom of very message.
493-
494-A mailing list is created, and Anne subscribes to it.
495-
496- >>> from lp.services.mailman.testing import helpers
497- >>> list_one = helpers.create_list('itest-one')
498- >>> helpers.subscribe('Anne', 'itest-one')
499-
500-Anne sends a message to the list.
501-
502- >>> smtpd.reset()
503- >>> from Mailman.Post import inject
504- >>> inject('itest-one', """\
505- ... From: anne.person@example.com
506- ... To: itest-one@lists.launchpad.dev
507- ... Subject: A member post
508- ... Message-ID: <first-injection>
509- ...
510- ... Hi, I am a member of this team's list.
511- ... """)
512-
513- # Wait for two deliveries, one to Anne and the other to the archiver.
514- >>> smtpd_watcher.wait_for_mbox_delivery('first-injection')
515- >>> smtpd_watcher.wait_for_mbox_delivery('first-injection')
516-
517-
518-VERP headers
519-============
520-
521-VERP stands for Variable Envelope Return Path and it is a technique used to
522-track users for bounce processing. The idea is that you encode the actual
523-subscribed address in the Sender and Errors-To headers so that they can be
524-extracted if a bounce is ever received. This works because remote MTAs must
525-bounce to this address, regardless of how many intervening hops, forwards, or
526-rewrites there are.
527-
528-(Technically, Mailman doesn't do VERP because that's defined as happening in
529-the MTA, but it's close enough in intent and exactly the same in
530-implementation so we use the same terminology in Mailman.)
531-
532- >>> from operator import itemgetter
533- >>> messages = sorted(smtpd, key=itemgetter('sender'))
534- >>> for message in messages:
535- ... print message['sender']
536- ... print message['errors-to']
537- ... print '---'
538- itest-one-bounces+anne.person=example.com@lists.launchpad.dev
539- itest-one-bounces+anne.person=example.com@lists.launchpad.dev
540- ---
541- itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
542- itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
543- ---
544-
545-For the following discussion, it doesn't matter which message we look at in
546-detail, so the last one in the sorted sequence is just fine.
547-
548-
549-RFC 2369 headers
550-================
551-
552-RFC 2369 defines a set of headers for mailing lists, often called the List-*
553-headers due to the common prefix of these headers.
554-
555-
556-List-Help
557----------
558-
559-The List-Help header points to "an instructive website" to provide help to
560-users.
561-
562- >>> print message['list-help']
563- <http://help.launchpad.dev/ListHelp>
564-
565-
566-List-Id
567--------
568-
569-The List-Id header uniquely identifies the mailing list. Mailman crafts the
570-List-Id header using the team name and the host name on which the lists
571-reside.
572-
573- >>> print message['list-id']
574- <itest-one.lists.launchpad.dev>
575-
576-
577-List-Subscribe and List-Unsubscribe
578------------------------------------
579-
580-The List-Unsubscribe header points to the contact address of the mechanism the
581-user can access to unsubscribe from the mailing list. Because we have not yet
582-enabled the email command robot for Launchpad, the only way to unsubscribe is
583-for a user to hit her email settings page. The List-Subscribe header is
584-similar.
585-
586- >>> print message['list-subscribe']
587- <http://launchpad.dev/~itest-one>
588- >>> print message['list-unsubscribe']
589- <http://launchpad.dev/~itest-one>
590-
591-
592-List-Archive
593-------------
594-
595-The List-Archive header describes how to access the archives for this mailing
596-list. It is a list-wide url, not a url-specific to the individual message
597-(see draft RFC 5064 for that).
598-
599-This url should exactly match the link given on the team's overview page.
600-
601- >>> from canonical.testing.layers import BaseLayer
602- >>> root_url = BaseLayer.appserver_root_url()
603- >>> browser = Browser('no-priv@canonical.com:test')
604- >>> browser.open('%s/~itest-one' % root_url)
605- >>> browser.getLink(id='mailing-list-archive')
606- <Link text='View archive' url='http://lists.launchpad.dev/itest-one'>
607- >>> print message['list-archive']
608- <http://lists.launchpad.dev/itest-one>
609-
610-
611-List-Post
612----------
613-
614-The List-Post header is a mailto url that tells people where they can post new
615-messages for this mailing list.
616-
617- >>> print message['list-post']
618- <mailto:itest-one@lists.launchpad.dev>
619-
620-
621-List-Owner
622-----------
623-
624-The List-Owner contains a url pointing to the human contact for the mailing
625-list. This header may be omitted if it is the same as the postmaster, but in
626-our case, we point this at the team owning the mailing list.
627-
628- >>> print message['list-owner']
629- <http://launchpad.dev/~itest-one>
630-
631-
632-Message footer
633-==============
634-
635-Mailman appends a footer of helpful text on each message. This footer
636-contains a link to this message's archive (which should be the same as the RFC
637-5064 link), the message's posting address, the unsubscribe address, etc.
638-The easiest way to see the footer is to just scan the message body for the
639-separator.
640-
641- >>> body_lines = message.get_payload().splitlines(True)
642- >>> for line_number, line in enumerate(body_lines):
643- ... if line.startswith('_____'):
644- ... break
645- ... else:
646- ... raise AssertionError('No footer found')
647- >>> print ''.join(body_lines[line_number + 1:])
648- Mailing list: http://launchpad.dev/~itest-one
649- Post to : itest-one@lists.launchpad.dev
650- Unsubscribe : http://launchpad.dev/~itest-one
651- More help : http://help.launchpad.dev/ListHelp
652- <BLANKLINE>
653- <BLANKLINE>
654
655=== removed file 'lib/lp/services/mailman/doc/messages.txt'
656--- lib/lp/services/mailman/doc/messages.txt 2009-09-24 18:51:29 +0000
657+++ lib/lp/services/mailman/doc/messages.txt 1970-01-01 00:00:00 +0000
658@@ -1,33 +0,0 @@
659-========
660-Messages
661-========
662-
663-Launchpad uses a custom set of message templates that it installs
664-in mailman/templates/site. The installation conforms to the standard
665-way of defining templates that apply to all lists managed by the
666-instance of mailman. The site template directory contains only the
667-templates that are different from the default mailman templates.
668-
669-The postheld message text does not include a link to cancel the posting,
670-which launchpad does not support.
671-
672- >>> from Mailman import Utils
673-
674- >>> postheld_dict = {
675- ... 'listname': 'fake-list',
676- ... 'hostname': 'lists.launchpad.net',
677- ... 'reason': 'The attachment is too large to post to the list.',
678- ... 'sender': 'test@canonical.com',
679- ... 'subject' : "testing post held for large attachment",
680- ... 'admindb_url': 'http://lists.launchpad.net/fake/admin',
681- ... }
682- >>> text, filename = Utils.findtext('postheld.txt', dict=postheld_dict)
683- >>> filename
684- '.../lib/mailman/templates/site/en/postheld.txt'
685- >>> print text
686- Your mail to 'fake-list' with the subject
687- testing post held for large attachment
688- Is being held until the list moderator can review it for approval.
689- ...
690- Either the message will get posted to the list, or you will receive
691- notification of the moderator's decision.
692
693=== removed file 'lib/lp/services/mailman/doc/postings.txt'
694--- lib/lp/services/mailman/doc/postings.txt 2010-12-13 19:58:54 +0000
695+++ lib/lp/services/mailman/doc/postings.txt 1970-01-01 00:00:00 +0000
696@@ -1,812 +0,0 @@
697-=======
698-Posting
699-=======
700-
701-Only Launchpad members are allowed to post to team mailing lists. A message
702-by a sender who is not a Launchpad member will be summarily discarded.
703-
704-A team mailing list is created.
705-
706- >>> from lp.services.mailman.testing import helpers
707- >>> list_one = helpers.create_list('itest-one')
708-
709- # Ignore the list creation notification message.
710- >>> smtpd.reset()
711-
712-
713-Non-member postings
714-===================
715-
716-Postings from addresses that are not registered and verified with Launchpad
717-are summarily discarded.
718-
719-A non-Launchpad member sends a message to the mailing list. This message is
720-discarded immediately.
721-
722- >>> from Mailman.Post import inject
723- >>> inject('itest-one', """\
724- ... From: zperson@example.net
725- ... To: itest-one@lists.launchpad.dev
726- ... Subject: A non-member post
727- ... Message-ID: <aardvark>
728- ...
729- ... Hi, I am not a member of Launchpad (yet).
730- ... """)
731- >>> vette_watcher.wait_for_discard('aardvark')
732- >>> len(list(smtpd))
733- 0
734-
735-
736-Non-validated member postings
737-=============================
738-
739-Similarly, an address that has been registered with Launchpad but not verified
740-will also be summarily discarded. Anne registers with Launchpad but does not
741-validate her alternative address.
742-
743- >>> from lp.registry.tests.mailinglists_helper import (
744- ... get_alternative_email)
745- >>> from canonical.launchpad.ftests import login, logout
746- >>> from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
747-
748- >>> login('admin@canonical.com')
749- >>> anne = factory.makePersonByName('Anne')
750- >>> alternative_email = get_alternative_email(anne)
751-
752- # Simulate Anne not having validated her alternative address.
753- >>> alternative_email.status = EmailAddressStatus.NEW
754- >>> transaction.commit()
755- >>> print alternative_email.email, alternative_email.status.title
756- aperson@example.org New Email Address
757-
758-Anne sends a message from her non-validated alternative address. This message
759-gets discarded.
760-
761- >>> inject('itest-one', """\
762- ... From: aperson@example.org
763- ... To: itest-one@lists.launchpad.dev
764- ... Subject: A non-member post
765- ... Message-ID: <beaver>
766- ...
767- ... Hi, I am not a verified member of Launchpad (yet).
768- ... """)
769- >>> vette_watcher.wait_for_discard('beaver')
770- >>> len(list(smtpd))
771- 0
772-
773-
774-Unsubscribed Launchpad members
775-==============================
776-
777-Anne's preferred email address is still validated, and she posts a message
778-from that address. However, because she has not yet joined the team or
779-subscribed to its mailing list, her messages gets held for moderation by the
780-team's administrator.
781-
782- >>> print anne.preferredemail.email, anne.preferredemail.status.title
783- anne.person@example.com Preferred Email Address
784- >>> smtpd.reset()
785- >>> len(list(smtpd))
786- 0
787- >>> inject('itest-one', """\
788- ... From: anne.person@example.com
789- ... To: itest-one@lists.launchpad.dev
790- ... Subject: An unsubscribed post
791- ... Message-ID: <caribou>
792- ...
793- ... Hi, I am not a member of this team's list (yet).
794- ... """)
795- >>> vette_watcher.wait_for_hold('itest-one', 'caribou')
796-
797-The team administrator will receive a notification of the held message.
798-
799- >>> smtpd_watcher.wait_for_personal_traffic('no-priv@canonical.com')
800- >>> messages = list(smtpd)
801- >>> len(messages)
802- 1
803- >>> print messages[0].as_string()
804- Content-Type: text/plain; charset="utf-8"
805- ...
806- From: Itest One <noreply@launchpad.net>
807- Subject: New mailing list message requiring approval for Itest One
808- ...
809- <BLANKLINE>
810- Hello No Privileges Person,
811- <BLANKLINE>
812- Itest One has a new message requiring your approval.
813- <BLANKLINE>
814- Subject: An unsubscribed post
815- Author name: Anne Person
816- Author url: http://launchpad.dev:.../~anne
817- Date: ...
818- Message-ID: <caribou>
819- <BLANKLINE>
820- A message has been posted to the mailing list for your team, but this
821- message requires your approval before it will be sent to the list
822- members. After reviewing the message, you may approve, discard or
823- reject it.
824- <BLANKLINE>
825- To review all messages pending approval, visit:
826- <BLANKLINE>
827- http://launchpad.dev:.../~itest-one/+mailinglist-moderate
828- <BLANKLINE>
829- Regards,
830- The Launchpad team
831- <BLANKLINE>
832- <BLANKLINE>
833-
834-The message is held for moderation by the team administrator.
835-
836- >>> holds = helpers.pending_hold_ids('itest-one')
837- >>> len(holds)
838- 1
839- >>> helpers.print_mailman_hold('itest-one', holds[0])
840- From: anne.person@example.com
841- To: itest-one@lists.launchpad.dev
842- Subject: An unsubscribed post
843- Message-ID: <caribou>
844- <BLANKLINE>
845- Hi, I am not a member of this team's list (yet).
846- <BLANKLINE>
847-
848-The team administrator can accept, decline or discard this posting. By
849-declining the message, Launchpad forgets about it, while Mailman sends a
850-rejection notice to Anne.
851-
852- >>> from canonical.testing.layers import BaseLayer
853- >>> root_url = BaseLayer.appserver_root_url()
854- >>> browser = Browser('no-priv@canonical.com:test')
855- >>> browser.open('%s/~itest-one/+mailinglist-moderate' % root_url)
856- >>> browser.getControl(name='field.%3Ccaribou%3E').value = [
857- ... 'reject']
858- >>> browser.getControl('Moderate').click()
859- >>> xmlrpc_watcher.wait_for_reject('caribou')
860-
861- >>> holds = helpers.pending_hold_ids('itest-one')
862- >>> len(holds)
863- 0
864-
865- >>> smtpd_watcher.wait_for_personal_traffic('anne.person@example.com')
866- >>> messages = list(smtpd)
867- >>> len(messages)
868- 1
869- >>> print messages[0].as_string()
870- MIME-Version: 1.0
871- ...
872- Subject: Request to mailing list Itest-one rejected
873- From: itest-one-...@lists.launchpad.dev
874- To: anne.person@example.com
875- ...
876-
877-Anne posts another message to the mailing list, but she is still not
878-subscribed to it. The team administrator deems this message to be spam and
879-discards it. No rejection notice is sent to Anne.
880-
881- >>> inject('itest-one', """\
882- ... From: anne.person@example.com
883- ... To: itest-one@lists.launchpad.dev
884- ... Subject: Wanna buy something?
885- ... Message-ID: <donkey>
886- ...
887- ... Hi, do you want to buy something?
888- ... """)
889-
890- >>> vette_watcher.wait_for_hold('itest-one', 'donkey')
891- >>> messages = list(smtpd)
892- >>> len(messages)
893- 1
894- >>> print messages[0].as_string()
895- Content-Type: text/plain; charset="utf-8"
896- ...
897- From: Itest One <noreply@launchpad.net>
898- Subject: New mailing list message requiring approval for Itest One
899- ...
900- <BLANKLINE>
901- Hello No Privileges Person,
902- <BLANKLINE>
903- Itest One has a new message requiring your approval.
904- <BLANKLINE>
905- Subject: Wanna buy something?
906- Author name: Anne Person
907- Author url: http://launchpad.dev:.../~anne
908- Date: ...
909- Message-ID: <donkey>
910- ...
911-
912- >>> browser.open('%s/~itest-one/+mailinglist-moderate' % root_url)
913- >>> browser.getControl(name='field.%3Cdonkey%3E').value = [
914- ... 'discard']
915- >>> browser.getControl('Moderate').click()
916- >>> xmlrpc_watcher.wait_for_discard('donkey')
917-
918- >>> holds = helpers.pending_hold_ids('itest-one')
919- >>> len(holds)
920- 0
921- >>> len(list(smtpd))
922- 0
923-
924-Now Anne posts another message to the mailing list she is not subscribed to.
925-This time, her message is on topic and is accepted.
926-
927- >>> inject('itest-one', """\
928- ... From: anne.person@example.com
929- ... To: itest-one@lists.launchpad.dev
930- ... Subject: Another unsubscribed post
931- ... Message-ID: <elephant>
932- ...
933- ... Hi, I am still not yet a member of this team's list.
934- ... """)
935-
936- >>> vette_watcher.wait_for_hold('itest-one', 'elephant')
937- >>> messages = list(smtpd)
938- >>> len(messages)
939- 1
940- >>> print messages[0].as_string()
941- Content-Type: text/plain; charset="utf-8"
942- ...
943- From: Itest One <noreply@launchpad.net>
944- Subject: New mailing list message requiring approval for Itest One
945- ...
946- <BLANKLINE>
947- Hello No Privileges Person,
948- <BLANKLINE>
949- Itest One has a new message requiring your approval.
950- <BLANKLINE>
951- Subject: Another unsubscribed post
952- Author name: Anne Person
953- Author url: http://launchpad.dev:.../~anne
954- Date: ...
955- Message-ID: <elephant>
956- ...
957-
958- >>> holds = helpers.pending_hold_ids('itest-one')
959- >>> len(holds)
960- 1
961- >>> helpers.print_mailman_hold('itest-one', holds[0])
962- From: anne.person@example.com
963- To: itest-one@lists.launchpad.dev
964- Subject: Another unsubscribed post
965- Message-ID: <elephant>
966- <BLANKLINE>
967- Hi, I am still not yet a member of this team's list.
968- <BLANKLINE>
969-
970- >>> browser.open('%s/~itest-one/+mailinglist-moderate' % root_url)
971- >>> browser.getControl(name='field.%3Celephant%3E').value = [
972- ... 'approve']
973- >>> browser.getControl('Moderate').click()
974- >>> xmlrpc_watcher.wait_for_approval('elephant')
975-
976- >>> holds = helpers.pending_hold_ids('itest-one')
977- >>> len(holds)
978- 0
979-
980-This message is delivered to the archive.
981-
982- >>> smtpd_watcher.wait_for_list_traffic('itest-one', personal=True)
983- >>> messages = list(smtpd)
984- >>> len(messages)
985- 1
986- >>> print messages[0].as_string()
987- From: anne.person@example.com
988- To: itest-one@lists.launchpad.dev
989- Message-ID: <elephant>
990- X-Mailman-Approved-At: ...
991- Subject: [Itest-one] Another unsubscribed post
992- ...
993- List-Id: <itest-one.lists.launchpad.dev>
994- List-Help: <http://help.launchpad.dev/ListHelp>
995- List-Subscribe: <http://launchpad.dev/~itest-one>
996- List-Unsubscribe: <http://launchpad.dev/~itest-one>
997- List-Post: <mailto:itest-one@lists.launchpad.dev>
998- List-Archive: <http://lists.launchpad.dev/itest-one>
999- List-Owner: <http://launchpad.dev/~itest-one>
1000- ...
1001- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1002- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1003- ...
1004- X-MailFrom: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1005- X-RcptTo: archive@mail-archive.dev
1006- <BLANKLINE>
1007- Hi, I am still not yet a member of this team's list.
1008- _______________________________________________
1009- Mailing list: http://launchpad.dev/~itest-one
1010- Post to : itest-one@lists.launchpad.dev
1011- Unsubscribe : http://launchpad.dev/~itest-one
1012- More help : http://help.launchpad.dev/ListHelp
1013-
1014-
1015-Unsubscribed team member
1016-========================
1017-
1018-Once Anne joins the team, she may post to it, though until she subscribes to
1019-the mailing list, she will not get a copy of the message.
1020-
1021- # Reset Anne's MessageApproval so that she'll no longer be pre-approved to
1022- # post to this mailing list.
1023- >>> from canonical.database.sqlbase import sqlvalues
1024- >>> from lp.registry.model.mailinglist import MessageApproval
1025- >>> approvals = MessageApproval.select("""
1026- ... MessageApproval.posted_by = %s
1027- ... """ % sqlvalues(anne))
1028- >>> for approval in approvals:
1029- ... approval.destroySelf()
1030- >>> transaction.commit()
1031-
1032- >>> helpers.ensure_nonmembership('itest-one', anne)
1033-
1034- >>> from zope.component import getUtility
1035- >>> from lp.registry.interfaces.person import IPersonSet
1036- >>> login('admin@canonical.com')
1037- >>> team_one = getUtility(IPersonSet).getByName('itest-one')
1038- >>> anne.join(team_one)
1039- >>> transaction.commit()
1040-
1041- >>> helpers.ensure_membership('itest-one', anne)
1042- >>> helpers.ensure_addresses_are_disabled(
1043- ... 'itest-one', 'anne.person@example.com')
1044- >>> logout()
1045-
1046- >>> inject('itest-one', """\
1047- ... From: anne.person@example.com
1048- ... To: itest-one@lists.launchpad.dev
1049- ... Subject: A member post
1050- ... Message-ID: <emu>
1051- ...
1052- ... Hi, I have just joined this team.
1053- ... """)
1054- >>> smtpd_watcher.wait_for_mbox_delivery('emu')
1055-
1056-Only the archiver gets the message delivery.
1057-
1058- >>> messages = list(smtpd)
1059- >>> len(messages)
1060- 1
1061- >>> print messages[0].as_string()
1062- From: anne.person@example.com
1063- To: itest-one@lists.launchpad.dev
1064- Message-ID: <emu>
1065- Subject: [Itest-one] A member post
1066- ...
1067- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1068- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1069- ...
1070- Hi, I have just joined this team.
1071- _______________________________________________
1072- Mailing list: http://launchpad.dev/~itest-one
1073- Post to : itest-one@lists.launchpad.dev
1074- Unsubscribe : http://launchpad.dev/~itest-one
1075- More help : http://help.launchpad.dev/ListHelp
1076- <BLANKLINE>
1077- <BLANKLINE>
1078-
1079-In addition, the in-house MHonArc archiver has archived this message.
1080-
1081- >>> mhonarc_watcher.wait_for_message_number(2)
1082- >>> helpers.collect_archive_message_ids('itest-one')
1083- ['elephant', 'emu']
1084-
1085-
1086-Verified and registered member postings
1087-=======================================
1088-
1089-Anne now subscribes to the team's mailing list. She gets a copy of each
1090-message posted to the mailing list.
1091-
1092- >>> helpers.subscribe('Anne', 'itest-one')
1093- >>> helpers.ensure_addresses_are_enabled(
1094- ... 'itest-one', 'anne.person@example.com')
1095-
1096- >>> inject('itest-one', """\
1097- ... From: anne.person@example.com
1098- ... To: itest-one@lists.launchpad.dev
1099- ... Subject: A member post
1100- ... Message-ID: <falcon>
1101- ...
1102- ... Hi, I am now a member of this team's list!
1103- ... """)
1104-
1105-Anne's message got posted to the mailing list. Two copies were sent; one to
1106-Anne herself as a member of the mailing list, and another to the
1107-mail-archive.com archiving recipient. These copies are very similar,
1108-differing only in the headers used to do VERP bounce detection.
1109-
1110- >>> smtpd_watcher.wait_for_mbox_delivery('falcon')
1111- >>> smtpd_watcher.wait_for_mbox_delivery('falcon')
1112- >>> messages = list(smtpd)
1113- >>> len(messages)
1114- 2
1115- >>> from operator import itemgetter
1116- >>> messages.sort(key=itemgetter('sender'))
1117-
1118-Here's the copy sent to Anne...
1119-
1120- >>> print messages[0].as_string()
1121- From: anne.person@example.com
1122- To: itest-one@lists.launchpad.dev
1123- Message-ID: <falcon>
1124- Subject: [Itest-one] A member post
1125- ...
1126- Sender: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1127- Errors-To: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1128- ...
1129- X-MailFrom: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1130- X-RcptTo: anne.person@example.com
1131- <BLANKLINE>
1132- Hi, I am now a member of this team's list!
1133- _______________________________________________
1134- Mailing list: http://launchpad.dev/~itest-one
1135- Post to : itest-one@lists.launchpad.dev
1136- Unsubscribe : http://launchpad.dev/~itest-one
1137- More help : http://help.launchpad.dev/ListHelp
1138-
1139-...and the copy sent to the archives.
1140-
1141- >>> print messages[1].as_string()
1142- From: anne.person@example.com
1143- To: itest-one@lists.launchpad.dev
1144- Message-ID: <falcon>
1145- Subject: [Itest-one] A member post
1146- ...
1147- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1148- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1149- ...
1150- X-MailFrom: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1151- X-RcptTo: archive@mail-archive.dev
1152- <BLANKLINE>
1153- Hi, I am now a member of this team's list!
1154- _______________________________________________
1155- Mailing list: http://launchpad.dev/~itest-one
1156- Post to : itest-one@lists.launchpad.dev
1157- Unsubscribe : http://launchpad.dev/~itest-one
1158- More help : http://help.launchpad.dev/ListHelp
1159-
1160-In addition, the in-house MHonArc archiver has archived this message.
1161-
1162- >>> mhonarc_watcher.wait_for_message_number(3)
1163- >>> helpers.collect_archive_message_ids('itest-one')
1164- ['elephant', 'emu', 'falcon']
1165-
1166-Anne cannot post from her alternative email address because it has not been
1167-validated yet.
1168-
1169- >>> inject('itest-one', """\
1170- ... From: aperson@example.org
1171- ... To: itest-one@lists.launchpad.dev
1172- ... Subject: A non-member post
1173- ... Message-ID: <giraffe>
1174- ...
1175- ... Hi, I am not a verified member of this team (yet).
1176- ... """)
1177- >>> vette_watcher.wait_for_discard('giraffe')
1178- >>> len(list(smtpd))
1179- 0
1180-
1181-However, once she validates this address, she can post from it.
1182-
1183- >>> login('admin@canonical.com')
1184- >>> alternative_email.status = EmailAddressStatus.VALIDATED
1185- >>> logout()
1186- >>> transaction.commit()
1187- >>> helpers.ensure_membership('itest-one', anne)
1188-
1189- >>> inject('itest-one', """\
1190- ... From: aperson@example.org
1191- ... To: itest-one@lists.launchpad.dev
1192- ... Subject: A member post
1193- ... Message-ID: <gerbil>
1194- ...
1195- ... Hi, I am now a verified member of this team!
1196- ... """)
1197-
1198- >>> smtpd_watcher.wait_for_mbox_delivery('gerbil')
1199- >>> smtpd_watcher.wait_for_mbox_delivery('gerbil')
1200- >>> messages = list(smtpd)
1201- >>> len(messages)
1202- 2
1203- >>> messages.sort(key=itemgetter('sender'))
1204-
1205- >>> print messages[0].as_string()
1206- From: aperson@example.org
1207- To: itest-one@lists.launchpad.dev
1208- Message-ID: <gerbil>
1209- Subject: [Itest-one] A member post
1210- ...
1211- Sender: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1212- Errors-To: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1213- ...
1214- X-MailFrom: itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1215- X-RcptTo: anne.person@example.com
1216- <BLANKLINE>
1217- Hi, I am now a verified member of this team!
1218- _______________________________________________
1219- Mailing list: http://launchpad.dev/~itest-one
1220- Post to : itest-one@lists.launchpad.dev
1221- Unsubscribe : http://launchpad.dev/~itest-one
1222- More help : http://help.launchpad.dev/ListHelp
1223-
1224- >>> print messages[1].as_string()
1225- From: aperson@example.org
1226- To: itest-one@lists.launchpad.dev
1227- Message-ID: <gerbil>
1228- Subject: [Itest-one] A member post
1229- ...
1230- Sender: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1231- Errors-To: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1232- ...
1233- X-MailFrom: itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1234- X-RcptTo: archive@mail-archive.dev
1235- <BLANKLINE>
1236- Hi, I am now a verified member of this team!
1237- _______________________________________________
1238- Mailing list: http://launchpad.dev/~itest-one
1239- Post to : itest-one@lists.launchpad.dev
1240- Unsubscribe : http://launchpad.dev/~itest-one
1241- More help : http://help.launchpad.dev/ListHelp
1242-
1243-There should also be no held requests pending.
1244-
1245- >>> len(helpers.pending_hold_ids('itest-one'))
1246- 0
1247-
1248-
1249-Leaving and re-joining the team
1250-===============================
1251-
1252-Anne now leaves the team, but she does not unsubscribe from the mailing list.
1253-
1254- >>> login('admin@canonical.com')
1255- >>> team_one = getUtility(IPersonSet).getByName('itest-one')
1256- >>> anne.leave(team_one)
1257- >>> logout()
1258- >>> transaction.commit()
1259- >>> helpers.ensure_nonmembership('itest-one', anne)
1260-
1261-Because she is not a team member, her message is held for moderation.
1262-
1263- >>> inject('itest-one', """\
1264- ... From: aperson@example.org
1265- ... To: itest-one@lists.launchpad.dev
1266- ... Subject: A non-team member post
1267- ... Message-ID: <horse>
1268- ...
1269- ... Hi, I am not a team member.
1270- ... """)
1271- >>> vette_watcher.wait_for_hold('itest-one', 'horse')
1272- >>> messages = list(smtpd)
1273- >>> len(messages)
1274- 1
1275- >>> print messages[0].as_string()
1276- Content-Type: text/plain; charset="utf-8"
1277- ...
1278- Subject: New mailing list message requiring approval for Itest One
1279- ...
1280- Subject: A non-team member post
1281- Author name: Anne Person
1282- Author url: http://launchpad.dev:.../~anne
1283- Date: ...
1284- Message-ID: <horse>
1285- ...
1286-
1287-The team administrator discards the message.
1288-
1289- >>> browser.open('%s/~itest-one/+mailinglist-moderate' % root_url)
1290- >>> browser.getControl(name='field.%3Chorse%3E').value = [
1291- ... 'discard']
1292- >>> browser.getControl('Moderate').click()
1293- >>> xmlrpc_watcher.wait_for_discard('horse')
1294-
1295-Anne re-joins the team and once again can post to the mailing list. She does
1296-not need to re-subscribe to the mailing list.
1297-
1298- >>> login('admin@canonical.com')
1299- >>> anne.join(team_one)
1300- >>> logout()
1301- >>> transaction.commit()
1302- >>> helpers.ensure_membership('itest-one', anne)
1303-
1304- >>> inject('itest-one', """\
1305- ... From: aperson@example.org
1306- ... To: itest-one@lists.launchpad.dev
1307- ... Subject: A posting from a member once again
1308- ... Message-ID: <iguana>
1309- ...
1310- ... Hi, I am a team member again!
1311- ... """)
1312-
1313- # Wait for mailing list deliveries to both recipients.
1314- >>> smtpd_watcher.wait_for_mbox_delivery('iguana')
1315- >>> smtpd_watcher.wait_for_mbox_delivery('iguana')
1316- >>> messages = list(smtpd)
1317- >>> len(messages)
1318- 2
1319- >>> messages.sort(key=itemgetter('sender'))
1320- >>> for message in sorted(messages, key=itemgetter('sender')):
1321- ... print message['sender']
1322- ... print message['subject']
1323- ... print message['message-id']
1324- itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1325- [Itest-one] A posting from a member once again
1326- <iguana>
1327- itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1328- [Itest-one] A posting from a member once again
1329- <iguana>
1330-
1331-
1332-First post moderation
1333-=====================
1334-
1335-Normally, Launchpad members who are not subscribed to the mailing list will
1336-have their posts held for moderation.
1337-
1338- >>> login('admin@canonical.com')
1339- >>> bart = factory.makePersonByName('Bart')
1340- >>> transaction.commit()
1341- >>> bart.personal_standing
1342- <DBItem PersonalStanding.UNKNOWN...
1343-
1344- >>> def print_message_summaries():
1345- ... messages = sorted(smtpd, key=itemgetter('sender'))
1346- ... print 'Number of messages:', len(messages)
1347- ... for message in messages:
1348- ... print message['sender']
1349- ... print ' ', message['message-id']
1350- ... print ' ', message['from']
1351- ... print ' ', message['subject']
1352-
1353- >>> inject('itest-one', """\
1354- ... From: bperson@example.org
1355- ... To: itest-one@lists.launchpad.dev
1356- ... Subject: A non-member post
1357- ... Message-ID: <jackal>
1358- ...
1359- ... Hi, I am not a member of the mailing list.
1360- ... """)
1361- >>> vette_watcher.wait_for_hold('itest-one', 'jackal')
1362- >>> print_message_summaries()
1363- Number of messages: 1
1364- bounces@canonical.com
1365- ...
1366- Itest One <noreply@launchpad.net>
1367- New mailing list message requiring approval for Itest One
1368-
1369-However, a Launchpad member in good standing is allowed to post to any mailing
1370-list.
1371-
1372- >>> from lp.registry.interfaces.person import PersonalStanding
1373- >>> login('admin@canonical.com')
1374- >>> bart.personal_standing = PersonalStanding.GOOD
1375- >>> transaction.commit()
1376-
1377- >>> inject('itest-one', """\
1378- ... From: bart.person@example.com
1379- ... To: itest-one@lists.launchpad.dev
1380- ... Subject: A member in good standing
1381- ... Message-ID: <kestrel>
1382- ...
1383- ... Hi, I am a Launchpad member in good standing!
1384- ... """)
1385-
1386- # Wait for mailing list deliveries to both recipients.
1387- >>> smtpd_watcher.wait_for_mbox_delivery('kestrel')
1388- >>> smtpd_watcher.wait_for_mbox_delivery('kestrel')
1389- >>> print_message_summaries()
1390- Number of messages: 2
1391- itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1392- <kestrel>
1393- bart.person@example.com
1394- [Itest-one] A member in good standing
1395- itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1396- <kestrel>
1397- bart.person@example.com
1398- [Itest-one] A member in good standing
1399-
1400-A member in excellent standing can of course also post to the list.
1401-
1402- >>> bart.personal_standing = PersonalStanding.EXCELLENT
1403- >>> transaction.commit()
1404-
1405- >>> inject('itest-one', """\
1406- ... From: bart.person@example.com
1407- ... To: itest-one@lists.launchpad.dev
1408- ... Subject: A member in excellent standing
1409- ... Message-ID: <llama>
1410- ...
1411- ... Hi, I am a Launchpad member in excellent standing!
1412- ... """)
1413-
1414- # Wait for mailing list deliveries to both recipients.
1415- >>> smtpd_watcher.wait_for_mbox_delivery('llama')
1416- >>> smtpd_watcher.wait_for_mbox_delivery('llama')
1417- >>> print_message_summaries()
1418- Number of messages: 2
1419- itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1420- <llama>
1421- bart.person@example.com
1422- [Itest-one] A member in excellent standing
1423- itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1424- <llama>
1425- bart.person@example.com
1426- [Itest-one] A member in excellent standing
1427-
1428-But a person in poor standing will have their messages held for approval.
1429-
1430- >>> bart.personal_standing = PersonalStanding.POOR
1431- >>> transaction.commit()
1432-
1433- >>> inject('itest-one', """\
1434- ... From: bperson@example.org
1435- ... To: itest-one@lists.launchpad.dev
1436- ... Subject: A non-member post
1437- ... Message-ID: <mongoose>
1438- ...
1439- ... Hi, I am not a member of the mailing list.
1440- ... """)
1441- >>> vette_watcher.wait_for_hold('itest-one', 'mongoose')
1442- >>> print_message_summaries()
1443- Number of messages: 1
1444- bounces@canonical.com
1445- ...
1446- Itest One <noreply@launchpad.net>
1447- New mailing list message requiring approval for Itest One
1448-
1449-Should a non-team member's held post be approved, they are then allowed to
1450-post to just that mailing list without further approval required.
1451-
1452- >>> browser.open('%s/~itest-one/+mailinglist-moderate' % root_url)
1453- >>> browser.getControl(name='field.%3Cmongoose%3E').value = ['approve']
1454- >>> browser.getControl('Moderate').click()
1455- >>> smtpd_watcher.wait_for_mbox_delivery('mongoose')
1456- >>> smtpd_watcher.wait_for_mbox_delivery('mongoose')
1457- >>> helpers.ensure_addresses_are_enabled(
1458- ... 'itest-one', 'bperson@example.org')
1459-
1460- >>> smtpd.reset()
1461- >>> inject('itest-one', """\
1462- ... From: bperson@example.org
1463- ... To: itest-one@lists.launchpad.dev
1464- ... Subject: A non-member post
1465- ... Message-ID: <otter>
1466- ...
1467- ... Hi, I am not a member of the mailing list.
1468- ... """)
1469- >>> smtpd_watcher.wait_for_mbox_delivery('otter')
1470- >>> smtpd_watcher.wait_for_mbox_delivery('otter')
1471- >>> messages = sorted(smtpd, key=itemgetter('sender'))
1472- >>> len(messages)
1473- 2
1474- >>> for message in messages:
1475- ... print message['sender']
1476- ... print message['subject']
1477- ... print message['message-id']
1478- itest-one-bounces+anne.person=example.com@lists.launchpad.dev
1479- [Itest-one] A non-member post
1480- <otter>
1481- itest-one-bounces+archive=mail-archive.dev@lists.launchpad.dev
1482- [Itest-one] A non-member post
1483- <otter>
1484-
1485-
1486-Preventing archiver forgeries
1487-=============================
1488-
1489-We archive messages by sending them to a special address owned by the Mail
1490-Archive <http://www.mail-archive.com>. This address becomes a recipient of
1491-the mailing list, but we also prevent this address from being used to post
1492-messages to the list.
1493-
1494- >>> from canonical.config import config
1495- >>> config.mailman.archive_address
1496- 'archive@mail-archive.dev'
1497- >>> inject('itest-one', """\
1498- ... From: %s
1499- ... To: itest-one@lists.launchpad.dev
1500- ... Subject: A forgery
1501- ... Message-ID: <narwhale>
1502- ...
1503- ... Hi, I am forging me some spam!
1504- ... """ % config.mailman.archive_address)
1505-
1506- >>> vette_watcher.wait_for_discard('narwhale')
1507- >>> len(list(smtpd))
1508- 0
1509
1510=== modified file 'lib/lp/services/mailman/monkeypatches/__init__.py'
1511--- lib/lp/services/mailman/monkeypatches/__init__.py 2010-09-17 20:46:58 +0000
1512+++ lib/lp/services/mailman/monkeypatches/__init__.py 2010-12-15 16:00:37 +0000
1513@@ -29,7 +29,7 @@
1514 # provide everything Mailman needs.
1515 #
1516 # Remember, don't rely on Launchpad's config object in the mm_cfg.py file
1517- # or in the canonical.mailman.monkeypatches.defaults module because
1518+ # or in the lp.services.mailman.monkeypatches.defaults module because
1519 # Mailman will not be able to initialize Launchpad's configuration system.
1520 # Instead, anything that's needed from config should be written to the
1521 # mm_cfg.py file now.
1522
1523=== modified file 'lib/lp/services/mailman/monkeypatches/lphandler.py'
1524--- lib/lp/services/mailman/monkeypatches/lphandler.py 2010-08-20 20:31:18 +0000
1525+++ lib/lp/services/mailman/monkeypatches/lphandler.py 2010-12-15 16:00:37 +0000
1526@@ -5,7 +5,7 @@
1527
1528
1529 import hashlib
1530-import xmlrpclib
1531+from Mailman.Queue import XMLRPCRunner
1532
1533 from Mailman import (
1534 Errors,
1535@@ -42,7 +42,7 @@
1536 # can't talk to Launchpad, I believe it's better to let the message get
1537 # posted to the list than to discard or hold it.
1538 is_member = True
1539- proxy = xmlrpclib.ServerProxy(mm_cfg.XMLRPC_URL)
1540+ proxy = proxy = XMLRPCRunner.get_mailing_list_api_proxy()
1541 # This will fail if we can't talk to Launchpad. That's okay though
1542 # because Mailman's IncomingRunner will re-queue the message and re-start
1543 # processing at this handler.
1544
1545=== modified file 'lib/lp/services/mailman/monkeypatches/lpstanding.py'
1546--- lib/lp/services/mailman/monkeypatches/lpstanding.py 2009-06-25 05:30:52 +0000
1547+++ lib/lp/services/mailman/monkeypatches/lpstanding.py 2010-12-15 16:00:37 +0000
1548@@ -7,9 +7,7 @@
1549 whether list non-members are allowed to post to a mailing list.
1550 """
1551
1552-import xmlrpclib
1553-
1554-from Mailman import mm_cfg
1555+from Mailman.Queue import XMLRPCRunner
1556
1557
1558 def process(mlist, msg, msgdata):
1559@@ -26,7 +24,7 @@
1560 sender = msg.get_sender()
1561 # Ask Launchpad about the standing of this member.
1562 in_good_standing = False
1563- proxy = xmlrpclib.ServerProxy(mm_cfg.XMLRPC_URL)
1564+ proxy = XMLRPCRunner.get_mailing_list_api_proxy()
1565 # This will fail if we can't talk to Launchpad. That's okay though
1566 # because Mailman's IncomingRunner will re-queue the message and re-start
1567 # processing at this handler.
1568
1569=== modified file 'lib/lp/services/mailman/monkeypatches/mm_cfg.py.in'
1570--- lib/lp/services/mailman/monkeypatches/mm_cfg.py.in 2010-09-17 20:46:58 +0000
1571+++ lib/lp/services/mailman/monkeypatches/mm_cfg.py.in 2010-12-15 16:00:37 +0000
1572@@ -44,7 +44,7 @@
1573
1574 SITE_LIST_OWNER = '%(site_list_owner)s'
1575
1576-DEFAULT_MSG_FOOTER = '''_______________________________________________
1577+DEFAULT_MSG_FOOTER = '''--
1578 %(footer)s'''
1579
1580 # Set up MHonArc archiving.
1581
1582=== modified file 'lib/lp/services/mailman/testing/__init__.py'
1583--- lib/lp/services/mailman/testing/__init__.py 2010-12-14 19:42:51 +0000
1584+++ lib/lp/services/mailman/testing/__init__.py 2010-12-15 16:00:37 +0000
1585@@ -63,6 +63,9 @@
1586 mlist.Create(team.name, owner_email, 'password')
1587 mlist.host_name = 'lists.launchpad.dev'
1588 mlist.web_page_url = 'http://lists.launchpad.dev/mailman/'
1589+ mlist.personalize = 1
1590+ mlist.include_rfc2369_headers = False
1591+ mlist.use_dollar_strings = True
1592 mlist.Save()
1593 mlist.addNewMember(owner_email)
1594 return mlist
1595
1596=== added file 'lib/lp/services/mailman/tests/test_lphandler.py'
1597--- lib/lp/services/mailman/tests/test_lphandler.py 1970-01-01 00:00:00 +0000
1598+++ lib/lp/services/mailman/tests/test_lphandler.py 2010-12-15 16:00:37 +0000
1599@@ -0,0 +1,83 @@
1600+# Copyright 20010 Canonical Ltd. This software is licensed under the
1601+# GNU Affero General Public License version 3 (see the file LICENSE).
1602+"""Test the LaunchpadMember monekypatches"""
1603+
1604+__metaclass__ = type
1605+__all__ = []
1606+
1607+import hashlib
1608+
1609+from Mailman import (
1610+ Errors,
1611+ mm_cfg,
1612+ )
1613+from Mailman.Handlers import LaunchpadMember
1614+
1615+from canonical.testing.layers import DatabaseFunctionalLayer
1616+from lp.services.mailman.testing import MailmanTestCase
1617+
1618+
1619+class TestLaunchpadMemberTestCase(MailmanTestCase):
1620+ """Test lphandler.
1621+
1622+ Mailman process() methods quietly return. They may set msg_data key-values
1623+ or raise an error to end processing. This group of tests tests often check
1624+ for errors, but that does not mean there is an error condition, it only
1625+ means message processing has reached a final decision. Messages that do
1626+ not cause a final decision pass-through and the process() methods ends
1627+ without a return.
1628+ """
1629+
1630+ layer = DatabaseFunctionalLayer
1631+
1632+ def setUp(self):
1633+ super(TestLaunchpadMemberTestCase, self).setUp()
1634+ self.team, self.mailing_list = self.factory.makeTeamAndMailingList(
1635+ 'team-1', 'team-1-owner')
1636+ self.mm_list = self.makeMailmanList(self.mailing_list)
1637+
1638+ def tearDown(self):
1639+ super(TestLaunchpadMemberTestCase, self).tearDown()
1640+ self.cleanMailmanList(self.mm_list)
1641+
1642+ def test_messages_from_unknown_senders_are_discarded(self):
1643+ # A massage from an unknown email address is discarded.
1644+ message = self.makeMailmanMessage(
1645+ self.mm_list, 'gerbil@noplace.dom', 'subject', 'any content.')
1646+ msg_data = {}
1647+ args = (self.mm_list, message, msg_data)
1648+ self.assertRaises(
1649+ Errors.DiscardMessage, LaunchpadMember.process, *args)
1650+
1651+ def test_preapproved_messages_are_always_accepted(self):
1652+ # An approved message is accepted even if the email address is
1653+ # unknown.
1654+ message = self.makeMailmanMessage(
1655+ self.mm_list, 'gerbil@noplace.dom', 'subject', 'any content.')
1656+ msg_data = dict(approved=True)
1657+ silence = LaunchpadMember.process(self.mm_list, message, msg_data)
1658+ self.assertEqual(None, silence)
1659+
1660+ def test_messages_from_launchpad_users_are_accepted(self):
1661+ # A message from a launchpad user is accepted.
1662+ lp_user_email = 'chinchila@eg.dom'
1663+ lp_user = self.factory.makePerson(email=lp_user_email)
1664+ message = self.makeMailmanMessage(
1665+ self.mm_list, lp_user_email, 'subject', 'any content.')
1666+ msg_data = {}
1667+ silence = LaunchpadMember.process(self.mm_list, message, msg_data)
1668+ self.assertEqual(None, silence)
1669+
1670+ def test_messages_from_launchpad_itself_are_accepted(self):
1671+ # A message from launchpad itself is accepted. Launchpad will sent
1672+ # a secret.
1673+ message = self.makeMailmanMessage(
1674+ self.mm_list, 'guinea-pig@noplace.dom', 'subject', 'any content.')
1675+ message['message-id'] = 'hamster.hamster'
1676+ hash = hashlib.sha1(mm_cfg.LAUNCHPAD_SHARED_SECRET)
1677+ hash.update(message['message-id'])
1678+ message['x-launchpad-hash'] = hash.hexdigest()
1679+ msg_data = {}
1680+ silence = LaunchpadMember.process(self.mm_list, message, msg_data)
1681+ self.assertEqual(None, silence)
1682+ self.assertEqual(True, msg_data['approved'])
1683
1684=== added file 'lib/lp/services/mailman/tests/test_lpheaders.py'
1685--- lib/lp/services/mailman/tests/test_lpheaders.py 1970-01-01 00:00:00 +0000
1686+++ lib/lp/services/mailman/tests/test_lpheaders.py 2010-12-15 16:00:37 +0000
1687@@ -0,0 +1,103 @@
1688+# Copyright 20010 Canonical Ltd. This software is licensed under the
1689+# GNU Affero General Public License version 3 (see the file LICENSE).
1690+"""Test the lpheaders monekypatches"""
1691+
1692+__metaclass__ = type
1693+__all__ = []
1694+
1695+from Mailman.Handlers import (
1696+ Decorate,
1697+ LaunchpadHeaders,
1698+ )
1699+
1700+from canonical.testing.layers import DatabaseFunctionalLayer
1701+from lp.services.mailman.testing import MailmanTestCase
1702+
1703+
1704+class TestLaunchpadHeadersTestCase(MailmanTestCase):
1705+ """Test lpheaders.
1706+
1707+ Mailman process() methods quietly return. They may set msg_data key-values
1708+ or raise an error to end processing. This group of tests tests often check
1709+ for errors, but that does not mean there is an error condition, it only
1710+ means message processing has reached a final decision. Messages that do
1711+ not cause a final decision pass-through and the process() methods ends
1712+ without a return.
1713+ """
1714+
1715+ layer = DatabaseFunctionalLayer
1716+
1717+ def setUp(self):
1718+ super(TestLaunchpadHeadersTestCase, self).setUp()
1719+ self.team, self.mailing_list = self.factory.makeTeamAndMailingList(
1720+ 'team-1', 'team-1-owner')
1721+ self.mm_list = self.makeMailmanList(self.mailing_list)
1722+ self.lp_user_email = 'albatros@eg.dom'
1723+ self.lp_user = self.factory.makePerson(
1724+ name='albatros', email=self.lp_user_email)
1725+
1726+ def tearDown(self):
1727+ super(TestLaunchpadHeadersTestCase, self).tearDown()
1728+ self.cleanMailmanList(self.mm_list)
1729+
1730+ def test_message_launchpad_headers(self):
1731+ # All messages get updated headers.
1732+ message = self.makeMailmanMessage(
1733+ self.mm_list, self.lp_user_email, 'subject', 'any content.')
1734+ msg_data = {}
1735+ silence = LaunchpadHeaders.process(self.mm_list, message, msg_data)
1736+ self.assertEqual(None, silence)
1737+ self.assertEqual(
1738+ '<team-1.lists.launchpad.dev>', message['List-Id'])
1739+ self.assertEqual(
1740+ '<http://help.launchpad.dev/ListHelp>', message['List-Help'])
1741+ self.assertEqual(
1742+ '<http://launchpad.dev/~team-1>', message['List-Subscribe'])
1743+ self.assertEqual(
1744+ '<http://launchpad.dev/~team-1>', message['List-Unsubscribe'])
1745+ self.assertEqual(
1746+ '<mailto:team-1@lists.launchpad.dev>', message['List-Post'])
1747+ self.assertEqual(
1748+ '<http://lists.launchpad.dev/team-1>', message['List-Archive'])
1749+ self.assertEqual(
1750+ '<http://launchpad.dev/~team-1>', message['List-Owner'])
1751+
1752+ def test_message_decoration_data(self):
1753+ # The lpheaders process method provides decoration-data.
1754+ message = self.makeMailmanMessage(
1755+ self.mm_list, self.lp_user_email, 'subject', 'any content.')
1756+ msg_data = {}
1757+ silence = LaunchpadHeaders.process(self.mm_list, message, msg_data)
1758+ self.assertEqual(None, silence)
1759+ self.assertTrue('decoration-data' in msg_data)
1760+ decoration_data = msg_data['decoration-data']
1761+ self.assertEqual(
1762+ 'http://launchpad.dev/~team-1',
1763+ decoration_data['list_owner'])
1764+ self.assertEqual(
1765+ 'team-1@lists.launchpad.dev',
1766+ decoration_data['list_post'])
1767+ self.assertEqual(
1768+ 'http://launchpad.dev/~team-1',
1769+ decoration_data['list_unsubscribe'])
1770+ self.assertEqual(
1771+ 'http://help.launchpad.dev/ListHelp',
1772+ decoration_data['list_help'])
1773+
1774+ def test_message_decorate_footer(self):
1775+ # The Decorate handler uses the lpheaders decoration-data.
1776+ message = self.makeMailmanMessage(
1777+ self.mm_list, self.lp_user_email, 'subject', 'any content.')
1778+ msg_data = {}
1779+ LaunchpadHeaders.process(self.mm_list, message, msg_data)
1780+ self.assertTrue('decoration-data' in msg_data)
1781+ silence = Decorate.process(self.mm_list, message, msg_data)
1782+ self.assertEqual(None, silence)
1783+ body, footer = message.get_payload()[1].get_payload().rsplit('-- ', 1)
1784+ expected = (
1785+ "\n"
1786+ "Mailing list: http://launchpad.dev/~team-1\n"
1787+ "Post to : team-1@lists.launchpad.dev\n"
1788+ "Unsubscribe : http://launchpad.dev/~team-1\n"
1789+ "More help : http://help.launchpad.dev/ListHelp\n")
1790+ self.assertEqual(expected, footer)
1791
1792=== added file 'lib/lp/services/mailman/tests/test_lpstanding.py'
1793--- lib/lp/services/mailman/tests/test_lpstanding.py 1970-01-01 00:00:00 +0000
1794+++ lib/lp/services/mailman/tests/test_lpstanding.py 2010-12-15 16:00:37 +0000
1795@@ -0,0 +1,59 @@
1796+# Copyright 20010 Canonical Ltd. This software is licensed under the
1797+# GNU Affero General Public License version 3 (see the file LICENSE).
1798+"""Test the lpstanding monekypatches"""
1799+
1800+__metaclass__ = type
1801+__all__ = []
1802+
1803+from Mailman.Handlers import LPStanding
1804+
1805+from canonical.testing.layers import DatabaseFunctionalLayer
1806+from lp.registry.interfaces.person import PersonalStanding
1807+from lp.services.mailman.testing import MailmanTestCase
1808+from lp.testing import celebrity_logged_in
1809+
1810+
1811+class TestLPStandingTestCase(MailmanTestCase):
1812+ """Test lpstanding.
1813+
1814+ Mailman process() methods quietly return. They may set msg_data key-values
1815+ or raise an error to end processing. This group of tests tests often check
1816+ for errors, but that does not mean there is an error condition, it only
1817+ means message processing has reached a final decision. Messages that do
1818+ not cause a final decision pass-through and the process() methods ends
1819+ without a return.
1820+ """
1821+
1822+ layer = DatabaseFunctionalLayer
1823+
1824+ def setUp(self):
1825+ super(TestLPStandingTestCase, self).setUp()
1826+ self.team, self.mailing_list = self.factory.makeTeamAndMailingList(
1827+ 'team-1', 'team-1-owner')
1828+ self.mm_list = self.makeMailmanList(self.mailing_list)
1829+ self.lp_user_email = 'beaver@eg.dom'
1830+ self.lp_user = self.factory.makePerson(email=self.lp_user_email)
1831+
1832+ def tearDown(self):
1833+ super(TestLPStandingTestCase, self).tearDown()
1834+ self.cleanMailmanList(self.mm_list)
1835+
1836+ def test_non_subscriber_without_good_standing_is_not_approved(self):
1837+ # Non-subscribers without good standing are not approved to post.
1838+ message = self.makeMailmanMessage(
1839+ self.mm_list, self.lp_user_email, 'subject', 'any content.')
1840+ msg_data = {}
1841+ silence = LPStanding.process(self.mm_list, message, msg_data)
1842+ self.assertEqual(None, silence)
1843+ self.assertFalse('approved' in msg_data)
1844+
1845+ def test_non_subscriber_with_good_standing_is_approved(self):
1846+ # Non-subscribers with good standing are approved to post.
1847+ with celebrity_logged_in('admin'):
1848+ self.lp_user.personal_standing = PersonalStanding.GOOD
1849+ message = self.makeMailmanMessage(
1850+ self.mm_list, self.lp_user_email, 'subject', 'any content.')
1851+ msg_data = {}
1852+ silence = LPStanding.process(self.mm_list, message, msg_data)
1853+ self.assertEqual(None, silence)
1854+ self.assertTrue(msg_data['approved'])
1855
1856=== added file 'lib/lp/services/mailman/tests/test_mm_cfg.py'
1857--- lib/lp/services/mailman/tests/test_mm_cfg.py 1970-01-01 00:00:00 +0000
1858+++ lib/lp/services/mailman/tests/test_mm_cfg.py 2010-12-15 16:00:37 +0000
1859@@ -0,0 +1,189 @@
1860+# Copyright 20010 Canonical Ltd. This software is licensed under the
1861+# GNU Affero General Public License version 3 (see the file LICENSE).
1862+"""Test the Launchpad defaults monekypatch and mm_cfg."""
1863+
1864+__metaclass__ = type
1865+__all__ = []
1866+
1867+
1868+from Mailman import (
1869+ mm_cfg,
1870+ Utils,
1871+ )
1872+
1873+from canonical.config import config
1874+from canonical.testing.layers import FunctionalLayer
1875+from lp.services.mailman.config import configure_prefix
1876+from lp.services.mailman.monkeypatches import monkey_patch
1877+from lp.testing import TestCase
1878+
1879+
1880+class TestMMCfgDefaultsTestCase(TestCase):
1881+ """Test launchapd default overrides."""
1882+
1883+ layer = FunctionalLayer
1884+
1885+ def test_common_values(self):
1886+ # Launchpad's boolean and string parameters.
1887+ self.assertEqual('unused_mailman_site_list', mm_cfg.MAILMAN_SITE_LIST)
1888+ self.assertEqual(None, mm_cfg.MTA)
1889+ self.assertEqual(3, mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION)
1890+ self.assertEqual(False, mm_cfg.DEFAULT_SEND_REMINDERS)
1891+ self.assertEqual(True, mm_cfg.DEFAULT_SEND_WELCOME_MSG)
1892+ self.assertEqual(False, mm_cfg.DEFAULT_SEND_GOODBYE_MSG)
1893+ self.assertEqual(False, mm_cfg.DEFAULT_DIGESTABLE)
1894+ self.assertEqual(False, mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE)
1895+ self.assertEqual(False, mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL)
1896+ self.assertEqual(True, mm_cfg.VERP_PERSONALIZED_DELIVERIES)
1897+ self.assertEqual(False, mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS)
1898+ self.assertEqual(False, mm_cfg.DEFAULT_BOUNCE_PROCESSING)
1899+
1900+ def test_qrunners(self):
1901+ # The queue runners used by Launchpad.
1902+ runners = [pair[0] for pair in mm_cfg.QRUNNERS if pair[1] == 1]
1903+ expected = [
1904+ 'ArchRunner', 'BounceRunner', 'IncomingRunner', 'OutgoingRunner',
1905+ 'VirginRunner', 'RetryRunner', 'XMLRPCRunner']
1906+ self.assertEqual(expected, runners)
1907+
1908+ def test_global_pipeline(self):
1909+ # The ordered list of handlers used by Launchpad.
1910+ # NB. This is a very important list when debuggin were a message
1911+ # has been touched.
1912+ expected = [
1913+ 'LaunchpadMember', 'SpamDetect', 'Approve', 'Replybot',
1914+ 'LPStanding', 'LPModerate', 'LPSize',
1915+ 'MimeDel', 'Scrubber', 'Emergency', 'Tagger', 'CalcRecips',
1916+ 'AvoidDuplicates', 'Cleanse', 'CleanseDKIM', 'CookHeaders',
1917+ 'LaunchpadHeaders', 'ToDigest', 'ToArchive', 'ToUsenet',
1918+ 'AfterDelivery', 'Acknowledge', 'ToOutgoing']
1919+ self.assertEqual(expected, mm_cfg.GLOBAL_PIPELINE)
1920+
1921+
1922+class TestMMCfgLaunchpadConfigTestCase(TestCase):
1923+ """Test launchapd default overrides.
1924+
1925+ The mailman config is generated from the selected launchpad config.
1926+ The config will either be the test runner or the app server depending
1927+ on the what was previously used when mailman was run. The config must
1928+ be created in setup to ensure predicable values.
1929+ """
1930+
1931+ layer = FunctionalLayer
1932+
1933+ def setUp(self):
1934+ super(TestMMCfgLaunchpadConfigTestCase, self).setUp()
1935+ # Generate a mailman config using this environment's config.
1936+ mailman_path = configure_prefix(config.mailman.build_prefix)
1937+ monkey_patch(mailman_path, config)
1938+ reload(mm_cfg)
1939+
1940+ def test_mail_server(self):
1941+ # Launchpad's smtp config values.
1942+ host, port = config.mailman.smtp.split(':')
1943+ self.assertEqual(host, mm_cfg.SMTPHOST)
1944+ self.assertEqual(int(port), mm_cfg.SMTPPORT)
1945+
1946+ def test_xmlrpc_server(self):
1947+ # Launchpad's smtp config values.
1948+ self.assertEqual(
1949+ config.mailman.xmlrpc_url,
1950+ mm_cfg.XMLRPC_URL)
1951+ self.assertEqual(
1952+ config.mailman.xmlrpc_runner_sleep,
1953+ mm_cfg.XMLRPC_SLEEPTIME)
1954+ self.assertEqual(
1955+ config.mailman.subscription_batch_size,
1956+ mm_cfg.XMLRPC_SUBSCRIPTION_BATCH_SIZE)
1957+ self.assertEqual(
1958+ config.mailman.shared_secret,
1959+ mm_cfg.LAUNCHPAD_SHARED_SECRET)
1960+
1961+ def test_messge_footer(self):
1962+ # Launchpad's email footer.
1963+ self.assertEqual(
1964+ config.mailman.list_help_header,
1965+ mm_cfg.LIST_HELP_HEADER)
1966+ self.assertEqual(
1967+ config.mailman.list_owner_header_template,
1968+ mm_cfg.LIST_SUBSCRIPTION_HEADERS)
1969+ self.assertEqual(
1970+ config.mailman.archive_url_template,
1971+ mm_cfg.LIST_ARCHIVE_HEADER_TEMPLATE)
1972+ self.assertEqual(
1973+ config.mailman.list_owner_header_template,
1974+ mm_cfg.LIST_OWNER_HEADER_TEMPLATE)
1975+ self.assertEqual(
1976+ "-- \n"
1977+ "Mailing list: $list_owner\n"
1978+ "Post to : $list_post\n"
1979+ "Unsubscribe : $list_unsubscribe\n"
1980+ "More help : $list_help\n",
1981+ mm_cfg.DEFAULT_MSG_FOOTER)
1982+
1983+ def test_message_rules(self):
1984+ # Launchpad's rules for handling messages.
1985+ self.assertEqual(
1986+ config.mailman.soft_max_size,
1987+ mm_cfg.LAUNCHPAD_SOFT_MAX_SIZE)
1988+ self.assertEqual(
1989+ config.mailman.hard_max_size,
1990+ mm_cfg.LAUNCHPAD_HARD_MAX_SIZE)
1991+ self.assertEqual(
1992+ config.mailman.register_bounces_every,
1993+ mm_cfg.REGISTER_BOUNCES_EVERY)
1994+
1995+ def test_archive_setup(self):
1996+ # Launchpad's rules for setting up list archives.
1997+ self.assertTrue('-add' in mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
1998+ self.assertTrue('-spammode' in mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
1999+ self.assertTrue('-umask 022'in mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2000+ self.assertTrue(
2001+ '-dbfile'
2002+ '/var/tmp/mailman/archives/private/%(listname)s.mbox/mhonarc.db',
2003+ mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2004+ self.assertTrue(
2005+ '-outdit /var/tmp/mailman/mhonarc/%(listname)s',
2006+ mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2007+ self.assertTrue(
2008+ '-definevar ML-NAME=%(listname)s',
2009+ mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2010+ self.assertTrue(
2011+ '-rcfile var/tmp/mailman/data/lp-mhonarc-common.mrc',
2012+ mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2013+ self.assertTrue(
2014+ '-stderr /var/tmp/mailman/logs/mhonarc',
2015+ mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2016+ self.assertTrue(
2017+ '-stdout /var/tmp/mailman/logs/mhonarc',
2018+ mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2019+ self.assertEqual(
2020+ mm_cfg.PRIVATE_EXTERNAL_ARCHIVER, mm_cfg.PUBLIC_EXTERNAL_ARCHIVER)
2021+
2022+
2023+class TestSiteTemplates(TestCase):
2024+ """Test launchapd site templates."""
2025+
2026+ layer = FunctionalLayer
2027+
2028+ def test_postheld(self):
2029+ postheld_dict = {
2030+ 'listname': 'fake-list',
2031+ 'hostname': 'lists.launchpad.net',
2032+ 'reason': 'XXX',
2033+ 'sender': 'test@canonical.com',
2034+ 'subject': "YYY",
2035+ 'admindb_url': 'http://lists.launchpad.net/fake/admin',
2036+ }
2037+ text, file_name = Utils.findtext('postheld.txt', dict=postheld_dict)
2038+ self.assertTrue(
2039+ file_name.endswith('/lib/mailman/templates/site/en/postheld.txt'))
2040+ self.assertEqual(
2041+ "Your mail to 'fake-list' with the subject\n\n"
2042+ " YYY\n\n"
2043+ "Is being held until the list moderator can review it for "
2044+ "approval.\n\nThe reason it is being held:\n\n"
2045+ " XXX\n\n"
2046+ "Either the message will get posted to the list, or you will "
2047+ "receive\nnotification of the moderator's decision.\n",
2048+ text)