Merge lp:~cjwatson/launchpad/more-pop-notifications into lp:launchpad
- more-pop-notifications
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | 17722 |
Proposed branch: | lp:~cjwatson/launchpad/more-pop-notifications |
Merge into: | lp:launchpad |
Diff against target: |
1092 lines (+179/-243) 8 files modified
lib/lp/archivepublisher/tests/test_generate_ppa_htaccess.py (+4/-15) lib/lp/archiveuploader/tests/test_ppauploadprocessor.py (+17/-26) lib/lp/archiveuploader/tests/test_uploadprocessor.py (+84/-108) lib/lp/bugs/doc/externalbugtracker-debbugs.txt (+9/-9) lib/lp/bugs/mail/tests/test_handler.py (+16/-45) lib/lp/services/mail/tests/incomingmail.txt (+17/-20) lib/lp/soyuz/tests/test_packageupload.py (+17/-19) lib/lp/testing/__init__.py (+15/-1) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/more-pop-notifications |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+270371@code.launchpad.net |
Commit message
Use pop_notifications in more places to avoid fragile patterns with stub.test_emails, and introduce an assertEmailQueu
Description of the change
This is kind of yak-shaving. I've been making some progress on https:/
I've gone through the cases where test_emails was used directly and converted most of them to pop_notifications or something based on it. This branch contains only changes to files that contained unsafe uses of test_emails; the rest can come later.
Checking the length of the mail queue is a common pattern in tests, so I introduced an assertEmailQueu
Preview Diff
1 | === modified file 'lib/lp/archivepublisher/tests/test_generate_ppa_htaccess.py' |
2 | --- lib/lp/archivepublisher/tests/test_generate_ppa_htaccess.py 2014-10-29 06:04:09 +0000 |
3 | +++ lib/lp/archivepublisher/tests/test_generate_ppa_htaccess.py 2015-09-10 16:19:01 +0000 |
4 | @@ -1,4 +1,4 @@ |
5 | -# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
6 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
7 | # GNU Affero General Public License version 3 (see the file LICENSE). |
8 | |
9 | """Test the generate_ppa_htaccess.py script. """ |
10 | @@ -33,7 +33,6 @@ |
11 | from lp.registry.interfaces.teammembership import TeamMembershipStatus |
12 | from lp.services.config import config |
13 | from lp.services.log.logger import BufferLogger |
14 | -from lp.services.mail import stub |
15 | from lp.services.osutils import ( |
16 | ensure_directory_exists, |
17 | remove_if_exists, |
18 | @@ -281,9 +280,7 @@ |
19 | self.assertNotDeactivated(tokens[person]) |
20 | |
21 | # Ensure that a cancellation email was sent. |
22 | - num_emails = len(stub.test_emails) |
23 | - self.assertEqual( |
24 | - num_emails, 1, "Expected 1 email, got %s" % num_emails) |
25 | + self.assertEmailQueueLength(1) |
26 | |
27 | # Promiscuous_person now leaves team1, but does not lose his |
28 | # token because he's also in team2. No other tokens are |
29 | @@ -298,9 +295,7 @@ |
30 | self.assertNotDeactivated(tokens[person]) |
31 | |
32 | # Ensure that a cancellation email was not sent. |
33 | - num_emails = len(stub.test_emails) |
34 | - self.assertEqual( |
35 | - num_emails, 0, "Expected no emails, got %s" % num_emails) |
36 | + self.assertEmailQueueLength(0) |
37 | |
38 | # Team 2 now leaves parent_team, and all its members lose their |
39 | # tokens. |
40 | @@ -486,10 +481,6 @@ |
41 | |
42 | script.sendCancellationEmail(tokens[0]) |
43 | |
44 | - num_emails = len(stub.test_emails) |
45 | - self.assertEqual( |
46 | - num_emails, 1, "Expected 1 email, got %s" % num_emails) |
47 | - |
48 | [email] = pop_notifications() |
49 | self.assertEqual( |
50 | email['Subject'], |
51 | @@ -537,9 +528,7 @@ |
52 | |
53 | script.sendCancellationEmail(token) |
54 | |
55 | - num_emails = len(stub.test_emails) |
56 | - self.assertEqual( |
57 | - num_emails, 0, "Expected 0 emails, got %s" % num_emails) |
58 | + self.assertEmailQueueLength(0) |
59 | |
60 | def test_getTimeToSyncFrom(self): |
61 | # Sync from 1s before previous start to catch anything made during the |
62 | |
63 | === modified file 'lib/lp/archiveuploader/tests/test_ppauploadprocessor.py' |
64 | --- lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2015-08-26 13:41:21 +0000 |
65 | +++ lib/lp/archiveuploader/tests/test_ppauploadprocessor.py 2015-09-10 16:19:01 +0000 |
66 | @@ -9,7 +9,7 @@ |
67 | |
68 | __metaclass__ = type |
69 | |
70 | -from email import message_from_string |
71 | +from operator import itemgetter |
72 | import os |
73 | import shutil |
74 | |
75 | @@ -27,7 +27,6 @@ |
76 | from lp.services.config import config |
77 | from lp.services.database.constants import UTC_NOW |
78 | from lp.services.librarian.interfaces import ILibraryFileAliasSet |
79 | -from lp.services.mail import stub |
80 | from lp.soyuz.enums import ( |
81 | PackagePublishingStatus, |
82 | PackageUploadStatus, |
83 | @@ -43,6 +42,7 @@ |
84 | from lp.soyuz.tests.fakepackager import FakePackager |
85 | from lp.soyuz.tests.test_publishing import SoyuzTestPublisher |
86 | from lp.testing.dbuser import switch_dbuser |
87 | +from lp.testing.mail_helpers import pop_notifications |
88 | |
89 | |
90 | class TestPPAUploadProcessorBase(TestUploadProcessorBase): |
91 | @@ -87,20 +87,19 @@ |
92 | "X-Launchpad-PPA" header; it defaults to "name16" and should be |
93 | explicitly set to None for non-PPA or rejection notifications. |
94 | """ |
95 | - for item in expected: |
96 | + notifications = self.assertEmailQueueLength( |
97 | + len(expected), sort_key=itemgetter('X-Envelope-To')) |
98 | + |
99 | + for item, msg in zip(expected, notifications): |
100 | recipient = item.get("recipient", self.name16_recipient) |
101 | contents = item.get("contents", []) |
102 | ppa_header = item.get("ppa_header", "name16") |
103 | |
104 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
105 | - msg = message_from_string(raw_msg) |
106 | - |
107 | # This is now a non-multipart message. |
108 | self.assertFalse(msg.is_multipart()) |
109 | body = msg.get_payload(decode=True) |
110 | |
111 | - clean_recipients = [r.strip() for r in to_addrs] |
112 | - self.assertContentEqual([recipient], clean_recipients) |
113 | + self.assertEqual(recipient, msg['X-Envelope-To']) |
114 | |
115 | subject = "Subject: %s\n" % msg['Subject'] |
116 | body = subject + body |
117 | @@ -112,10 +111,6 @@ |
118 | self.assertIn('X-Launchpad-PPA', msg.keys()) |
119 | self.assertEqual(msg['X-Launchpad-PPA'], ppa_header) |
120 | |
121 | - self.assertEqual( |
122 | - [], stub.test_emails, |
123 | - "%d emails left over" % len(stub.test_emails)) |
124 | - |
125 | def checkFilesRestrictedInLibrarian(self, queue_item, condition): |
126 | """Check the libraryfilealias restricted flag. |
127 | |
128 | @@ -300,8 +295,8 @@ |
129 | self.assertEqual( |
130 | self.uploadprocessor.last_processed_upload.queue_root.status, |
131 | PackageUploadStatus.DONE) |
132 | - # Consume the test email so the assertion futher down does not fail. |
133 | - _from_addr, _to_addrs, _raw_msg = stub.test_emails.pop() |
134 | + # Consume the test email so the assertion further down does not fail. |
135 | + pop_notifications() |
136 | |
137 | # The SourcePackageRelease still has a component of universe: |
138 | pub_foo = self.name16.archive.getPublishedSources(name=u"bar").one() |
139 | @@ -320,8 +315,7 @@ |
140 | self.build_uploadprocessor, upload_dir, build=build) |
141 | |
142 | # No mails are sent for successful binary uploads. |
143 | - self.assertEqual(len(stub.test_emails), 0, |
144 | - "Unexpected email generated on binary upload.") |
145 | + self.assertEmailQueueLength(0) |
146 | |
147 | # Publish the binary. |
148 | [queue_item] = self.breezy.getPackageUploads( |
149 | @@ -477,7 +471,7 @@ |
150 | name12.preferredemail.email) |
151 | self.assertEmails([ |
152 | {"ppa_header": "cprov", "recipient": expected_recipient} |
153 | - for expected_recipient in reversed(sorted(expected_recipients))]) |
154 | + for expected_recipient in sorted(expected_recipients)]) |
155 | |
156 | def testPPADistroSeriesOverrides(self): |
157 | """It's possible to override target distroseries of PPA uploads. |
158 | @@ -850,9 +844,7 @@ |
159 | # Please note: this upload goes to the Ubuntu main archive. |
160 | upload_dir = self.queueUpload("bar_1.0-10") |
161 | self.processUpload(self.uploadprocessor, upload_dir) |
162 | - # Discard the announcement email and check the acceptance message |
163 | - # content. |
164 | - stub.test_emails.pop() |
165 | + pop_notifications() |
166 | |
167 | self.assertEqual( |
168 | self.uploadprocessor.last_processed_upload.queue_root.status, |
169 | @@ -991,9 +983,9 @@ |
170 | u'Files specified in DSC are broken or missing, skipping package ' |
171 | u'unpack verification.') |
172 | |
173 | - # Also, the email generated should be sane. |
174 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
175 | - msg = message_from_string(raw_msg) |
176 | + # Also, the email generated should be sane. Any of the multiple |
177 | + # notifications will do. |
178 | + msg = pop_notifications()[-1] |
179 | body = msg.get_payload(decode=True) |
180 | |
181 | self.assertTrue( |
182 | @@ -1015,8 +1007,7 @@ |
183 | self.processUpload(self.uploadprocessor, upload_dir) |
184 | |
185 | # The email generated should be sane. |
186 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
187 | - msg = message_from_string(raw_msg) |
188 | + [msg] = pop_notifications() |
189 | body = msg.get_payload(decode=True) |
190 | |
191 | self.assertTrue( |
192 | @@ -1148,7 +1139,7 @@ |
193 | queue_item.setAccepted() |
194 | queue_item.realiseUpload() |
195 | transaction.commit() |
196 | - stub.test_emails.pop() |
197 | + pop_notifications() |
198 | |
199 | # Now upload a 3.0 (quilt) source with missing orig*.tar.* to a |
200 | # PPA. All of the missing files will be retrieved from the |
201 | |
202 | === modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py' |
203 | --- lib/lp/archiveuploader/tests/test_uploadprocessor.py 2015-08-25 14:05:24 +0000 |
204 | +++ lib/lp/archiveuploader/tests/test_uploadprocessor.py 2015-09-10 16:19:01 +0000 |
205 | @@ -9,7 +9,6 @@ |
206 | "TestUploadProcessorBase", |
207 | ] |
208 | |
209 | -from email import message_from_string |
210 | import os |
211 | import shutil |
212 | from StringIO import StringIO |
213 | @@ -65,7 +64,6 @@ |
214 | BufferLogger, |
215 | DevNullLogger, |
216 | ) |
217 | -from lp.services.mail import stub |
218 | from lp.soyuz.enums import ( |
219 | ArchivePermissionType, |
220 | ArchivePurpose, |
221 | @@ -357,12 +355,16 @@ |
222 | over after checking the ones in expected. |
223 | """ |
224 | |
225 | - for item in expected: |
226 | + if allow_leftover: |
227 | + notifications = pop_notifications() |
228 | + self.assertTrue(len(notifications) >= len(expected)) |
229 | + else: |
230 | + notifications = self.assertEmailQueueLength(len(expected)) |
231 | + |
232 | + for item, msg in zip(expected, notifications): |
233 | recipient = item.get("recipient", self.name16_recipient) |
234 | contents = item.get("contents", []) |
235 | |
236 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
237 | - msg = message_from_string(raw_msg) |
238 | # This is now a MIMEMultipart message. |
239 | body = msg.get_payload(0) |
240 | body = body.get_payload(decode=True) |
241 | @@ -370,8 +372,7 @@ |
242 | # Only check the recipient if the caller didn't explicitly pass |
243 | # "recipient": None. |
244 | if recipient is not None: |
245 | - clean_recipients = [r.strip() for r in to_addrs] |
246 | - self.assertContentEqual([recipient], clean_recipients) |
247 | + self.assertEqual(recipient, msg['X-Envelope-To']) |
248 | |
249 | subject = "Subject: %s\n" % msg['Subject'] |
250 | body = subject + body |
251 | @@ -381,11 +382,6 @@ |
252 | content in body, |
253 | "Expect: '%s'\nGot:\n%s" % (content, body)) |
254 | |
255 | - if not allow_leftover: |
256 | - self.assertEqual( |
257 | - [], stub.test_emails, |
258 | - "%d emails left over" % len(stub.test_emails)) |
259 | - |
260 | def PGPSignatureNotPreserved(self, archive=None): |
261 | """PGP signatures should be removed from .changes files. |
262 | |
263 | @@ -423,13 +419,11 @@ |
264 | |
265 | def _checkPartnerUploadEmailSuccess(self): |
266 | """Ensure partner uploads generate the right email.""" |
267 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
268 | - foo_bar = "foo.bar@canonical.com" |
269 | - self.assertEqual([e.strip() for e in to_addrs], [foo_bar]) |
270 | + [msg] = pop_notifications() |
271 | + self.assertEqual("foo.bar@canonical.com", msg["X-Envelope-To"]) |
272 | self.assertTrue( |
273 | - "rejected" not in raw_msg, |
274 | - "Expected acceptance email not rejection. Actually Got:\n%s" |
275 | - % raw_msg) |
276 | + "rejected" not in str(msg), |
277 | + "Expected acceptance email not rejection. Actually Got:\n%s" % msg) |
278 | |
279 | def testInstantiate(self): |
280 | """UploadProcessor should instantiate""" |
281 | @@ -563,13 +557,13 @@ |
282 | self.processUpload(uploadprocessor, upload_dir) |
283 | |
284 | # Check the mailer stub has a rejection email for Daniel |
285 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
286 | + [msg] = pop_notifications() |
287 | # This is now a MIMEMultipart message. |
288 | - msg = message_from_string(raw_msg) |
289 | body = msg.get_payload(0) |
290 | body = body.get_payload(decode=True) |
291 | |
292 | - self.assertEqual(["daniel.silverstone@canonical.com"], to_addrs) |
293 | + self.assertEqual( |
294 | + "daniel.silverstone@canonical.com", msg["X-Envelope-To"]) |
295 | self.assertTrue("Unhandled exception processing upload: Exception " |
296 | "raised by BrokenUploadPolicy for testing." |
297 | in body) |
298 | @@ -599,15 +593,14 @@ |
299 | self.processUpload(uploadprocessor, upload_dir) |
300 | |
301 | # Check it went ok to the NEW queue and all is going well so far. |
302 | - foo_bar = "foo.bar@canonical.com" |
303 | daniel = "daniel.silverstone@canonical.com" |
304 | - for expected_to_addr in foo_bar, daniel: |
305 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
306 | - to_addrs = [e.strip() for e in to_addrs] |
307 | - self.assertContentEqual(to_addrs, [expected_to_addr]) |
308 | + foo_bar = "foo.bar@canonical.com" |
309 | + notifications = self.assertEmailQueueLength(2) |
310 | + for expected_to_addr, msg in zip((daniel, foo_bar), notifications): |
311 | + self.assertEqual(expected_to_addr, msg["X-Envelope-To"]) |
312 | self.assertTrue( |
313 | - "NEW" in raw_msg, "Expected email containing 'NEW', got:\n%s" |
314 | - % raw_msg) |
315 | + "NEW" in str(msg), |
316 | + "Expected email containing 'NEW', got:\n%s" % msg) |
317 | |
318 | # Accept and publish the upload. |
319 | # This is required so that the next upload of a later version of |
320 | @@ -635,13 +628,12 @@ |
321 | self.processUpload(uploadprocessor, upload_dir) |
322 | |
323 | # Verify we get an email talking about awaiting approval. |
324 | - for expected_to_addr in foo_bar, daniel: |
325 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
326 | - to_addrs = [e.strip() for e in to_addrs] |
327 | - self.assertContentEqual(to_addrs, [expected_to_addr]) |
328 | - self.assertTrue("Waiting for approval" in raw_msg, |
329 | - "Expected an 'upload awaits approval' email.\n" |
330 | - "Got:\n%s" % raw_msg) |
331 | + notifications = self.assertEmailQueueLength(2) |
332 | + for expected_to_addr, msg in zip((daniel, foo_bar), notifications): |
333 | + self.assertEqual(expected_to_addr, msg["X-Envelope-To"]) |
334 | + self.assertTrue( |
335 | + "Waiting for approval" in str(msg), |
336 | + "Expected an 'upload awaits approval' email.\nGot:\n%s" % msg) |
337 | |
338 | # And verify that the queue item is in the unapproved state. |
339 | queue_items = self.breezy.getPackageUploads( |
340 | @@ -918,10 +910,10 @@ |
341 | self.processUpload(uploadprocessor, upload_dir) |
342 | |
343 | # Check that it was rejected appropriately. |
344 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
345 | - self.assertTrue( |
346 | - "Partner archive for distro '%s' not found" % self.ubuntu.name |
347 | - in raw_msg) |
348 | + [msg] = pop_notifications() |
349 | + self.assertIn( |
350 | + "Partner archive for distro '%s' not found" % self.ubuntu.name, |
351 | + str(msg)) |
352 | |
353 | def testMixedPartnerUploadFails(self): |
354 | """Uploads with partner and non-partner files are rejected. |
355 | @@ -937,13 +929,12 @@ |
356 | self.processUpload(uploadprocessor, upload_dir) |
357 | |
358 | # Check that it was rejected. |
359 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
360 | - foo_bar = "foo.bar@canonical.com" |
361 | - self.assertEqual([e.strip() for e in to_addrs], [foo_bar]) |
362 | + [msg] = pop_notifications() |
363 | + self.assertEqual("foo.bar@canonical.com", msg["X-Envelope-To"]) |
364 | self.assertTrue( |
365 | - "Cannot mix partner files with non-partner." in raw_msg, |
366 | + "Cannot mix partner files with non-partner." in str(msg), |
367 | "Expected email containing 'Cannot mix partner files with " |
368 | - "non-partner.', got:\n%s" % raw_msg) |
369 | + "non-partner.', got:\n%s" % msg) |
370 | |
371 | def testPartnerReusingOrigFromPartner(self): |
372 | """Partner uploads reuse 'orig.tar.gz' from the partner archive.""" |
373 | @@ -977,8 +968,7 @@ |
374 | self.processUpload(uploadprocessor, upload_dir) |
375 | # Discard the announcement email and check the acceptance message |
376 | # content. |
377 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
378 | - msg = message_from_string(raw_msg) |
379 | + msg = pop_notifications()[-1] |
380 | # This is now a MIMEMultipart message. |
381 | body = msg.get_payload(0) |
382 | body = body.get_payload(decode=True) |
383 | @@ -1095,11 +1085,10 @@ |
384 | self.processUpload(uploadprocessor, upload_dir) |
385 | |
386 | # Check it went ok to the NEW queue and all is going well so far. |
387 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
388 | + [msg] = pop_notifications() |
389 | self.assertTrue( |
390 | - "NEW" in raw_msg, |
391 | - "Expected email containing 'NEW', got:\n%s" |
392 | - % raw_msg) |
393 | + "NEW" in str(msg), |
394 | + "Expected email containing 'NEW', got:\n%s" % msg) |
395 | |
396 | # Accept and publish the upload. |
397 | partner_archive = getUtility(IArchiveSet).getByDistroPurpose( |
398 | @@ -1198,10 +1187,10 @@ |
399 | # Check it is rejected. |
400 | expect_msg = ("Partner uploads must be for the RELEASE or " |
401 | "PROPOSED pocket.") |
402 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
403 | + [msg] = pop_notifications() |
404 | self.assertTrue( |
405 | - expect_msg in raw_msg, |
406 | - "Expected email with %s, got:\n%s" % (expect_msg, raw_msg)) |
407 | + expect_msg in str(msg), |
408 | + "Expected email with %s, got:\n%s" % (expect_msg, msg)) |
409 | |
410 | # And an oops should be filed for the error. |
411 | error_report = self.oopses[new_index] |
412 | @@ -1428,10 +1417,9 @@ |
413 | upload_dir = self.queueUpload("bar_1.0-1_lzma") |
414 | self.processUpload(uploadprocessor, upload_dir) |
415 | # Make sure it went ok: |
416 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
417 | - self.assertTrue( |
418 | - "rejected" not in raw_msg, |
419 | - "Failed to upload bar source:\n%s" % raw_msg) |
420 | + [msg] = pop_notifications() |
421 | + self.assertFalse( |
422 | + "rejected" in str(msg), "Failed to upload bar source:\n%s" % msg) |
423 | self.publishPackage("bar", "1.0-1") |
424 | # Clear out emails generated during upload. |
425 | pop_notifications() |
426 | @@ -1441,11 +1429,7 @@ |
427 | self.processUpload(uploadprocessor, upload_dir) |
428 | |
429 | # Successful binary uploads won't generate any email. |
430 | - if len(stub.test_emails) != 0: |
431 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
432 | - self.assertEqual( |
433 | - len(stub.test_emails), 0, |
434 | - "Expected no emails! Actually got:\n%s" % raw_msg) |
435 | + self.assertEmailQueueLength(0) |
436 | |
437 | # Check in the queue to see if it really made it: |
438 | queue_items = self.breezy.getPackageUploads( |
439 | @@ -1472,10 +1456,9 @@ |
440 | upload_dir = self.queueUpload("bar_1.0-1_xz") |
441 | self.processUpload(uploadprocessor, upload_dir) |
442 | # Make sure it went ok: |
443 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
444 | - self.assertTrue( |
445 | - "rejected" not in raw_msg, |
446 | - "Failed to upload bar source:\n%s" % raw_msg) |
447 | + [msg] = pop_notifications() |
448 | + self.assertFalse( |
449 | + "rejected" in str(msg), "Failed to upload bar source:\n%s" % msg) |
450 | self.publishPackage("bar", "1.0-1") |
451 | # Clear out emails generated during upload. |
452 | pop_notifications() |
453 | @@ -1485,11 +1468,7 @@ |
454 | self.processUpload(uploadprocessor, upload_dir) |
455 | |
456 | # Successful binary uploads won't generate any email. |
457 | - if len(stub.test_emails) != 0: |
458 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
459 | - self.assertEqual( |
460 | - len(stub.test_emails), 0, |
461 | - "Expected no emails! Actually got:\n%s" % raw_msg) |
462 | + self.assertEmailQueueLength(0) |
463 | |
464 | # Check in the queue to see if it really made it: |
465 | queue_items = self.breezy.getPackageUploads( |
466 | @@ -1683,8 +1662,8 @@ |
467 | self.processUpload(uploadprocessor, upload_dir) |
468 | |
469 | # Check that it failed (permissions were granted for wrong series). |
470 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
471 | - msg = message_from_string(raw_msg) |
472 | + # Any of the multiple notifications will do. |
473 | + msg = pop_notifications()[-1] |
474 | self.assertEqual( |
475 | msg['Subject'], '[ubuntu] bar_1.0-2_source.changes (Rejected)') |
476 | |
477 | @@ -1747,16 +1726,16 @@ |
478 | expected = [] |
479 | expected.append({ |
480 | "contents": base_contents + [ |
481 | - "You are receiving this email because you made this upload."], |
482 | - "recipient": "foo.bar@canonical.com", |
483 | - }) |
484 | - expected.append({ |
485 | - "contents": base_contents + [ |
486 | "You are receiving this email because you are the most " |
487 | "recent person", |
488 | "listed in this package's changelog."], |
489 | "recipient": "daniel.silverstone@canonical.com", |
490 | }) |
491 | + expected.append({ |
492 | + "contents": base_contents + [ |
493 | + "You are receiving this email because you made this upload."], |
494 | + "recipient": "foo.bar@canonical.com", |
495 | + }) |
496 | self.assertEmails(expected) |
497 | |
498 | def test30QuiltUploadToUnsupportingSeriesIsRejected(self): |
499 | @@ -1774,11 +1753,11 @@ |
500 | upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt") |
501 | self.processUpload(uploadprocessor, upload_dir) |
502 | # Make sure it was rejected. |
503 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
504 | + [msg] = pop_notifications() |
505 | self.assertTrue( |
506 | "bar_1.0-1.dsc: format '3.0 (quilt)' is not permitted in " |
507 | - "breezy." in raw_msg, |
508 | - "Source was not rejected properly:\n%s" % raw_msg) |
509 | + "breezy." in str(msg), |
510 | + "Source was not rejected properly:\n%s" % msg) |
511 | |
512 | def test30QuiltUpload(self): |
513 | """Ensure that 3.0 (quilt) uploads work properly. """ |
514 | @@ -1792,10 +1771,9 @@ |
515 | upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt") |
516 | self.processUpload(uploadprocessor, upload_dir) |
517 | # Make sure it went ok: |
518 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
519 | - self.assertTrue( |
520 | - "rejected" not in raw_msg, |
521 | - "Failed to upload bar source:\n%s" % raw_msg) |
522 | + [msg] = pop_notifications() |
523 | + self.assertFalse( |
524 | + "rejected" in str(msg), "Failed to upload bar source:\n%s" % msg) |
525 | spph = self.publishPackage("bar", "1.0-1") |
526 | |
527 | self.assertEquals( |
528 | @@ -1827,10 +1805,9 @@ |
529 | upload_dir = self.queueUpload("bar_1.0-1_3.0-quilt") |
530 | self.processUpload(uploadprocessor, upload_dir) |
531 | # Make sure it went ok: |
532 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
533 | - self.assertTrue( |
534 | - "rejected" not in raw_msg, |
535 | - "Failed to upload bar source:\n%s" % raw_msg) |
536 | + [msg] = pop_notifications() |
537 | + self.assertFalse( |
538 | + "rejected" in str(msg), "Failed to upload bar source:\n%s" % msg) |
539 | self.publishPackage("bar", "1.0-1") |
540 | |
541 | # Upload another source sharing the same (component) orig. |
542 | @@ -1865,10 +1842,9 @@ |
543 | upload_dir = self.queueUpload("bar_1.0_3.0-native") |
544 | self.processUpload(uploadprocessor, upload_dir) |
545 | # Make sure it went ok: |
546 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
547 | - self.assertTrue( |
548 | - "rejected" not in raw_msg, |
549 | - "Failed to upload bar source:\n%s" % raw_msg) |
550 | + [msg] = pop_notifications() |
551 | + self.assertFalse( |
552 | + "rejected" in str(msg), "Failed to upload bar source:\n%s" % msg) |
553 | spph = self.publishPackage("bar", "1.0") |
554 | |
555 | self.assertEquals( |
556 | @@ -1890,11 +1866,11 @@ |
557 | upload_dir = self.queueUpload("bar_1.0-1_1.0-bzip2") |
558 | self.processUpload(uploadprocessor, upload_dir) |
559 | # Make sure it was rejected. |
560 | - from_addr, to_addrs, raw_msg = stub.test_emails.pop() |
561 | + [msg] = pop_notifications() |
562 | self.assertTrue( |
563 | "bar_1.0-1.dsc: is format 1.0 but uses bzip2 compression." |
564 | - in raw_msg, |
565 | - "Source was not rejected properly:\n%s" % raw_msg) |
566 | + in str(msg), |
567 | + "Source was not rejected properly:\n%s" % msg) |
568 | |
569 | def testUploadToWrongPocketIsRejected(self): |
570 | # Uploads to the wrong pocket are rejected. |
571 | @@ -1922,16 +1898,16 @@ |
572 | expected = [] |
573 | expected.append({ |
574 | "contents": base_contents + [ |
575 | - "You are receiving this email because you made this upload."], |
576 | - "recipient": "foo.bar@canonical.com", |
577 | - }) |
578 | - expected.append({ |
579 | - "contents": base_contents + [ |
580 | "You are receiving this email because you are the most " |
581 | "recent person", |
582 | "listed in this package's changelog."], |
583 | "recipient": "daniel.silverstone@canonical.com", |
584 | }) |
585 | + expected.append({ |
586 | + "contents": base_contents + [ |
587 | + "You are receiving this email because you made this upload."], |
588 | + "recipient": "foo.bar@canonical.com", |
589 | + }) |
590 | self.assertEmails(expected) |
591 | |
592 | def testPGPSignatureNotPreserved(self): |
593 | @@ -1969,7 +1945,7 @@ |
594 | self.assertEqual(UploadStatusEnum.REJECTED, result) |
595 | self.assertLogContains( |
596 | "INFO Failed to parse changes file") |
597 | - self.assertEqual(len(stub.test_emails), 0) |
598 | + self.assertEmailQueueLength(0) |
599 | self.assertEqual([], self.oopses) |
600 | |
601 | def test_ddeb_upload_overrides(self): |
602 | @@ -2137,13 +2113,13 @@ |
603 | queue_entry=leaf_name) |
604 | self.options.context = 'buildd' |
605 | self.options.builds = True |
606 | - last_stub_mail_count = len(stub.test_emails) |
607 | + pop_notifications() |
608 | BuildUploadHandler(self.uploadprocessor, self.incoming_folder, |
609 | leaf_name).process() |
610 | self.layer.txn.commit() |
611 | # No emails are sent on success |
612 | - self.assertEquals(len(stub.test_emails), last_stub_mail_count) |
613 | - self.assertEquals(BuildStatus.FULLYBUILT, build.status) |
614 | + self.assertEmailQueueLength(0) |
615 | + self.assertEqual(BuildStatus.FULLYBUILT, build.status) |
616 | # Upon full build the upload log is unset. |
617 | self.assertIs(None, build.upload_log) |
618 | |
619 | @@ -2192,7 +2168,7 @@ |
620 | See bug 778437. |
621 | """ |
622 | self.doSuccessRecipeBuild() |
623 | - self.assertEquals(len(pop_notifications()), 0, pop_notifications) |
624 | + self.assertEmailQueueLength(0) |
625 | |
626 | def doFailureRecipeBuild(self): |
627 | # A source package recipe build will fail if no files are present. |
628 | @@ -2284,7 +2260,7 @@ |
629 | status=PackageUploadStatus.ACCEPTED, |
630 | version=u"1.0-1", name=u"bar") |
631 | queue_item.setDone() |
632 | - stub.test_emails = [] |
633 | + pop_notifications() |
634 | |
635 | build.buildqueue_record.markAsBuilding(self.factory.makeBuilder()) |
636 | build.updateStatus(status) |
637 | @@ -2300,7 +2276,7 @@ |
638 | "bar_1.0-1_binary", queue_entry=leaf_name) |
639 | self.options.context = 'buildd' |
640 | self.options.builds = True |
641 | - self.assertEqual([], stub.test_emails) |
642 | + self.assertEmailQueueLength(0) |
643 | BuildUploadHandler(self.uploadprocessor, self.incoming_folder, |
644 | leaf_name).process() |
645 | self.layer.txn.commit() |
646 | |
647 | === modified file 'lib/lp/bugs/doc/externalbugtracker-debbugs.txt' |
648 | --- lib/lp/bugs/doc/externalbugtracker-debbugs.txt 2015-03-13 19:05:50 +0000 |
649 | +++ lib/lp/bugs/doc/externalbugtracker-debbugs.txt 2015-09-10 16:19:01 +0000 |
650 | @@ -596,15 +596,15 @@ |
651 | ... '<123456@launchpad.net>') |
652 | '<123456@launchpad.net>' |
653 | |
654 | -We can look in the test_emails list for the mail that would have been |
655 | -sent. |
656 | - |
657 | - >>> from lp.services.mail import stub |
658 | - >>> recipent, to_addrs, comment_email = stub.test_emails.pop() |
659 | - >>> print to_addrs |
660 | - ['1234@example.com'] |
661 | - |
662 | - >>> print comment_email |
663 | +We can look for the mail that would have been sent. |
664 | + |
665 | + >>> from lp.testing.mail_helpers import pop_notifications |
666 | + >>> [msg] = pop_notifications() |
667 | + >>> print msg['X-Envelope-To'] |
668 | + 1234@example.com |
669 | + |
670 | + >>> print str(msg) |
671 | + From ... |
672 | Content-Type... |
673 | Message-Id: <123456@launchpad.net> |
674 | To: 1234@example.com |
675 | |
676 | === modified file 'lib/lp/bugs/mail/tests/test_handler.py' |
677 | --- lib/lp/bugs/mail/tests/test_handler.py 2012-09-17 16:13:40 +0000 |
678 | +++ lib/lp/bugs/mail/tests/test_handler.py 2015-09-10 16:19:01 +0000 |
679 | @@ -1,11 +1,10 @@ |
680 | -# Copyright 2010-2012 Canonical Ltd. This software is licensed under the |
681 | +# Copyright 2010-2015 Canonical Ltd. This software is licensed under the |
682 | # GNU Affero General Public License version 3 (see the file LICENSE). |
683 | |
684 | """Test MaloneHandler.""" |
685 | |
686 | __metaclass__ = type |
687 | |
688 | -import email |
689 | import time |
690 | |
691 | import transaction |
692 | @@ -32,7 +31,6 @@ |
693 | from lp.registry.enums import BugSharingPolicy |
694 | from lp.services.config import config |
695 | from lp.services.identity.interfaces.emailaddress import EmailAddressStatus |
696 | -from lp.services.mail import stub |
697 | from lp.services.webapp.authorization import LaunchpadSecurityPolicy |
698 | from lp.testing import ( |
699 | celebrity_logged_in, |
700 | @@ -124,12 +122,11 @@ |
701 | response = handler.extractAndAuthenticateCommands( |
702 | message, 'help@bugs.launchpad.net') |
703 | mail_handled, add_comment_to_bug, commands = response |
704 | - self.assertEquals(mail_handled, True) |
705 | - emails = self.getSentMail() |
706 | - self.assertEquals(1, len(emails)) |
707 | - self.assertEquals(['non@eg.dom'], emails[0][1]) |
708 | - self.assertTrue( |
709 | - 'Subject: Launchpad Bug Tracker Email Interface' in emails[0][2]) |
710 | + self.assertTrue(mail_handled) |
711 | + emails = self.assertEmailQueueLength(1) |
712 | + self.assertEqual('non@eg.dom', emails[0]['X-Envelope-To']) |
713 | + self.assertEqual( |
714 | + 'Launchpad Bug Tracker Email Interface Help', emails[0]['Subject']) |
715 | |
716 | def test_mailToHelpFromUnknownUser(self): |
717 | """Mail from people of no account to help@ is simply dropped. |
718 | @@ -140,8 +137,8 @@ |
719 | mail_handled, add_comment_to_bug, commands = \ |
720 | handler.extractAndAuthenticateCommands(message, |
721 | 'help@bugs.launchpad.net') |
722 | - self.assertEquals(mail_handled, True) |
723 | - self.assertEquals(self.getSentMail(), []) |
724 | + self.assertTrue(mail_handled) |
725 | + self.assertEmailQueueLength(0) |
726 | |
727 | def test_mailToHelp(self): |
728 | """Mail to help@ generates a help command.""" |
729 | @@ -152,19 +149,11 @@ |
730 | mail_handled, add_comment_to_bug, commands = \ |
731 | handler.extractAndAuthenticateCommands(message, |
732 | 'help@bugs.launchpad.net') |
733 | - self.assertEquals(mail_handled, True) |
734 | - emails = self.getSentMail() |
735 | - self.assertEquals(1, len(emails)) |
736 | - self.assertEquals([message['From']], emails[0][1]) |
737 | - self.assertTrue( |
738 | - 'Subject: Launchpad Bug Tracker Email Interface' in emails[0][2]) |
739 | - |
740 | - def getSentMail(self): |
741 | - # Sending mail is (unfortunately) a side effect of parsing the |
742 | - # commands, and unfortunately you must commit the transaction to get |
743 | - # them sent. |
744 | - transaction.commit() |
745 | - return stub.test_emails[:] |
746 | + self.assertEqual(mail_handled, True) |
747 | + emails = self.assertEmailQueueLength(1) |
748 | + self.assertEqual(message['From'], emails[0]['X-Envelope-To']) |
749 | + self.assertEqual( |
750 | + 'Launchpad Bug Tracker Email Interface Help', emails[0]['Subject']) |
751 | |
752 | def getFailureForMessage(self, to_address, from_address=None, body=None): |
753 | mail = self.factory.makeSignedMessage( |
754 | @@ -683,22 +672,6 @@ |
755 | self.timestamp = timestamp |
756 | |
757 | |
758 | -def get_last_email(): |
759 | - from_addr, to_addrs, raw_message = stub.test_emails[-1] |
760 | - sent_msg = email.message_from_string(raw_message) |
761 | - error_mail, original_mail = sent_msg.get_payload() |
762 | - # clear the emails so we don't accidentally get one from a previous test |
763 | - return dict( |
764 | - subject=sent_msg['Subject'], |
765 | - body=error_mail.get_payload(decode=True)) |
766 | - |
767 | - |
768 | -BAD_SIGNATURE_TIMESTAMP_MESSAGE = ( |
769 | - 'The message you sent included commands to modify the bug ' |
770 | - 'report, but the\nsignature was (apparently) generated too far ' |
771 | - 'in the past or future.') |
772 | - |
773 | - |
774 | class TestSignatureTimestampValidation(TestCaseWithFactory): |
775 | """GPG signature timestamps are checked for emails containing commands.""" |
776 | |
777 | @@ -715,10 +688,9 @@ |
778 | handler = MaloneHandler() |
779 | with person_logged_in(self.factory.makePerson()): |
780 | handler.process(msg, msg['To']) |
781 | - transaction.commit() |
782 | # Since there were no commands in the poorly-timestamped message, no |
783 | # error emails were generated. |
784 | - self.assertEqual(stub.test_emails, []) |
785 | + self.assertEmailQueueLength(0) |
786 | |
787 | def test_bad_timestamp_but_no_commands(self): |
788 | # If an email message's GPG signature's timestamp is too far in the |
789 | @@ -732,10 +704,9 @@ |
790 | msg.signature = FakeSignature(timestamp=now + one_week) |
791 | handler = MaloneHandler() |
792 | # Clear old emails before potentially generating more. |
793 | - del stub.test_emails[:] |
794 | + pop_notifications() |
795 | with person_logged_in(self.factory.makePerson()): |
796 | handler.process(msg, msg['To']) |
797 | - transaction.commit() |
798 | # Since there were no commands in the poorly-timestamped message, no |
799 | # error emails were generated. |
800 | - self.assertEqual(stub.test_emails, []) |
801 | + self.assertEmailQueueLength(0) |
802 | |
803 | === modified file 'lib/lp/services/mail/tests/incomingmail.txt' |
804 | --- lib/lp/services/mail/tests/incomingmail.txt 2015-07-08 16:05:11 +0000 |
805 | +++ lib/lp/services/mail/tests/incomingmail.txt 2015-09-10 16:19:01 +0000 |
806 | @@ -121,8 +121,9 @@ |
807 | wasn't a handler registered for that domain, an OOPS was recorded with |
808 | a link to the original message. |
809 | |
810 | - >>> from lp.services.mail import stub |
811 | - >>> print stub.test_emails[-1][2] |
812 | + >>> from lp.testing.mail_helpers import pop_notifications |
813 | + >>> print str(pop_notifications()[-1]) |
814 | + From ... |
815 | Content-Type: multipart/mixed... |
816 | ... |
817 | To: Sample Person <test@canonical.com> |
818 | @@ -140,8 +141,6 @@ |
819 | Subject: Signed Email |
820 | ... |
821 | |
822 | - >>> stub.test_emails = [] |
823 | - |
824 | --------------------------------------------- |
825 | Mail from Persons not registered in Launchpad |
826 | --------------------------------------------- |
827 | @@ -156,7 +155,7 @@ |
828 | >>> msgid not in foo_handler.handledMails |
829 | True |
830 | |
831 | - >>> stub.test_emails = [] |
832 | + >>> _ = pop_notifications() |
833 | |
834 | However, bar_handler specifies that it can handle such emails: |
835 | |
836 | @@ -171,7 +170,7 @@ |
837 | >>> msgid in bar_handler.handledMails |
838 | True |
839 | |
840 | - >>> stub.test_emails = [] |
841 | + >>> _ = pop_notifications() |
842 | |
843 | --------------------------------------------------------- |
844 | Mail from Persons with with an inactive Launchpad account |
845 | @@ -260,7 +259,8 @@ |
846 | An exception is raised, an OOPS is recorded, and an email is sent back |
847 | to the user, citing the OOPS ID, with the original message attached. |
848 | |
849 | - >>> print stub.test_emails[-1][2] |
850 | + >>> print str(pop_notifications()[-1]) |
851 | + From ... |
852 | Content-Type: multipart/mixed... |
853 | ... |
854 | To: Foo Bar <foo.bar@canonical.com> |
855 | @@ -279,8 +279,6 @@ |
856 | Subject: doesn't matter |
857 | ... |
858 | |
859 | - >>> stub.test_emails = [] |
860 | - |
861 | Unauthorized exceptions, which are ignored for the purpose of OOPS |
862 | reporting in the web interface, are not ignored in the email interface. |
863 | |
864 | @@ -305,7 +303,8 @@ |
865 | ... |
866 | Unauthorized |
867 | |
868 | - >>> print stub.test_emails[-1][2] |
869 | + >>> print str(pop_notifications()[-1]) |
870 | + From ... |
871 | Content-Type: multipart/mixed... |
872 | ... |
873 | To: Foo Bar <foo.bar@canonical.com> |
874 | @@ -324,8 +323,6 @@ |
875 | Subject: doesn't matter |
876 | ... |
877 | |
878 | - >>> stub.test_emails = [] |
879 | - |
880 | ------------- |
881 | DB exceptions |
882 | ------------- |
883 | @@ -382,6 +379,7 @@ |
884 | There is only one mail left in the mail box - the one sent back to |
885 | the user reporting the error: |
886 | |
887 | + >>> from lp.services.mail import stub |
888 | >>> len(stub.test_emails) |
889 | 1 |
890 | |
891 | @@ -412,7 +410,7 @@ |
892 | 2 |
893 | |
894 | >>> LibrarianLayer.reveal() |
895 | - >>> stub.test_emails = [] |
896 | + >>> _ = pop_notifications() |
897 | |
898 | ---------------- |
899 | Handling bounces |
900 | @@ -425,7 +423,6 @@ |
901 | |
902 | Emails with an empty Return-Path header should be dropped: |
903 | |
904 | - >>> stub.test_emails = [] |
905 | >>> msg = read_test_message('signed_detached.txt') |
906 | >>> msg.replace_header('To', '123@foo.com') |
907 | >>> msg['Return-Path'] = '<>' |
908 | @@ -437,8 +434,8 @@ |
909 | Since this happens way too often, as we seem to get more spam than |
910 | legitimate email, an email is not sent about it to the errors-list. |
911 | |
912 | - >>> len(stub.test_emails) |
913 | - 0 |
914 | + >>> pop_notifications() |
915 | + [] |
916 | |
917 | If the content type is multipart/report, it's most likely a DSN |
918 | (RFC 3464), so those get dropped as well. Normally a DSN should have an |
919 | @@ -455,8 +452,8 @@ |
920 | >>> msgid in foo_handler.handledMails |
921 | False |
922 | |
923 | - >>> len(stub.test_emails) |
924 | - 0 |
925 | + >>> pop_notifications() |
926 | + [] |
927 | |
928 | Email with the Precedence header are probably from an auto-responder or |
929 | another robot. We also drop those. |
930 | @@ -470,8 +467,8 @@ |
931 | >>> msgid in foo_handler.handledMails |
932 | False |
933 | |
934 | - >>> len(stub.test_emails) |
935 | - 0 |
936 | + >>> pop_notifications() |
937 | + [] |
938 | |
939 | |
940 | .. Doctest cleanup |
941 | |
942 | === modified file 'lib/lp/soyuz/tests/test_packageupload.py' |
943 | --- lib/lp/soyuz/tests/test_packageupload.py 2015-09-04 12:19:07 +0000 |
944 | +++ lib/lp/soyuz/tests/test_packageupload.py 2015-09-10 16:19:01 +0000 |
945 | @@ -29,7 +29,6 @@ |
946 | from lp.services.job.interfaces.job import JobStatus |
947 | from lp.services.job.runner import JobRunner |
948 | from lp.services.librarian.browser import ProxiedLibraryFileAlias |
949 | -from lp.services.mail import stub |
950 | from lp.soyuz.adapters.overrides import SourceOverride |
951 | from lp.soyuz.enums import ( |
952 | PackagePublishingStatus, |
953 | @@ -192,10 +191,11 @@ |
954 | with dbuser(config.IPackageUploadNotificationJobSource.dbuser): |
955 | JobRunner([job]).runAll() |
956 | |
957 | - def assertEmail(self, expected_to_addrs): |
958 | - """Pop an email from the stub queue and check its recipients.""" |
959 | - _, to_addrs, _ = stub.test_emails.pop() |
960 | - self.assertEqual(expected_to_addrs, to_addrs) |
961 | + def assertEmails(self, expected_to_addrs): |
962 | + """Pop emails from the stub queue and check their recipients.""" |
963 | + notifications = self.assertEmailQueueLength(len(expected_to_addrs)) |
964 | + for expected_to_addr, msg in zip(expected_to_addrs, notifications): |
965 | + self.assertEqual(expected_to_addr, msg["X-Envelope-To"]) |
966 | |
967 | def test_acceptFromQueue_source_sends_email(self): |
968 | # Accepting a source package sends emails to the announcement list |
969 | @@ -204,10 +204,11 @@ |
970 | upload, uploader = self.makeSourcePackageUpload() |
971 | upload.acceptFromQueue() |
972 | self.runPackageUploadNotificationJob() |
973 | - self.assertEqual(2, len(stub.test_emails)) |
974 | # Emails sent are the uploader's notification and the announcement: |
975 | - self.assertEmail([uploader.preferredemail.email]) |
976 | - self.assertEmail(["autotest_changes@ubuntu.com"]) |
977 | + self.assertEmails([ |
978 | + uploader.preferredemail.email, |
979 | + "autotest_changes@ubuntu.com", |
980 | + ]) |
981 | |
982 | def test_acceptFromQueue_source_backports_sends_no_announcement(self): |
983 | # Accepting a source package into BACKPORTS does not send an |
984 | @@ -219,10 +220,9 @@ |
985 | pocket=PackagePublishingPocket.BACKPORTS) |
986 | upload.acceptFromQueue() |
987 | self.runPackageUploadNotificationJob() |
988 | - self.assertEqual(1, len(stub.test_emails)) |
989 | # Only one email is sent, to the person in the changed-by field. No |
990 | # announcement email is sent. |
991 | - self.assertEmail([uploader.preferredemail.email]) |
992 | + self.assertEmails([uploader.preferredemail.email]) |
993 | |
994 | def test_acceptFromQueue_source_translations_sends_no_email(self): |
995 | # Accepting source packages in the "translations" section (i.e. |
996 | @@ -235,7 +235,7 @@ |
997 | upload.acceptFromQueue() |
998 | self.runPackageUploadNotificationJob() |
999 | self.assertEqual("DONE", upload.status.name) |
1000 | - self.assertEqual(0, len(stub.test_emails)) |
1001 | + self.assertEmailQueueLength(0) |
1002 | |
1003 | def test_acceptFromQueue_source_creates_builds(self): |
1004 | # Accepting a source package creates build records. |
1005 | @@ -281,7 +281,7 @@ |
1006 | upload, _ = self.makeBuildPackageUpload() |
1007 | upload.acceptFromQueue() |
1008 | self.runPackageUploadNotificationJob() |
1009 | - self.assertEqual(0, len(stub.test_emails)) |
1010 | + self.assertEmailQueueLength(0) |
1011 | |
1012 | def test_acceptFromQueue_handles_duplicates(self): |
1013 | # Duplicate queue entries are handled sensibly. |
1014 | @@ -329,8 +329,7 @@ |
1015 | upload, uploader = self.makeSourcePackageUpload() |
1016 | upload.rejectFromQueue(self.factory.makePerson()) |
1017 | self.runPackageUploadNotificationJob() |
1018 | - self.assertEqual(1, len(stub.test_emails)) |
1019 | - self.assertEmail([uploader.preferredemail.email]) |
1020 | + self.assertEmails([uploader.preferredemail.email]) |
1021 | |
1022 | def test_rejectFromQueue_binary_sends_email(self): |
1023 | # Rejecting a binary package sends an email to the uploader. |
1024 | @@ -338,8 +337,7 @@ |
1025 | upload, uploader = self.makeBuildPackageUpload() |
1026 | upload.rejectFromQueue(self.factory.makePerson()) |
1027 | self.runPackageUploadNotificationJob() |
1028 | - self.assertEqual(1, len(stub.test_emails)) |
1029 | - self.assertEmail([uploader.preferredemail.email]) |
1030 | + self.assertEmails([uploader.preferredemail.email]) |
1031 | |
1032 | def test_rejectFromQueue_source_translations_sends_no_email(self): |
1033 | # Rejecting a language pack sends no email. |
1034 | @@ -350,7 +348,7 @@ |
1035 | section_name="translations") |
1036 | upload.rejectFromQueue(self.factory.makePerson()) |
1037 | self.runPackageUploadNotificationJob() |
1038 | - self.assertEqual(0, len(stub.test_emails)) |
1039 | + self.assertEmailQueueLength(0) |
1040 | |
1041 | def test_rejectFromQueue_source_with_reason(self): |
1042 | # Rejecting a source package with a reason includes it in the email to |
1043 | @@ -360,10 +358,10 @@ |
1044 | person = self.factory.makePerson() |
1045 | upload.rejectFromQueue(user=person, comment='Because.') |
1046 | self.runPackageUploadNotificationJob() |
1047 | - self.assertEqual(1, len(stub.test_emails)) |
1048 | + [msg] = self.assertEmailQueueLength(1) |
1049 | self.assertIn( |
1050 | 'Rejected:\nRejected by %s: Because.' % person.displayname, |
1051 | - stub.test_emails[0][-1]) |
1052 | + str(msg)) |
1053 | |
1054 | |
1055 | class TestPackageUploadSecurity(TestCaseWithFactory): |
1056 | |
1057 | === modified file 'lib/lp/testing/__init__.py' |
1058 | --- lib/lp/testing/__init__.py 2015-08-03 12:59:18 +0000 |
1059 | +++ lib/lp/testing/__init__.py 2015-09-10 16:19:01 +0000 |
1060 | @@ -1,4 +1,4 @@ |
1061 | -# Copyright 2009-2011 Canonical Ltd. This software is licensed under the |
1062 | +# Copyright 2009-2015 Canonical Ltd. This software is licensed under the |
1063 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1064 | |
1065 | from __future__ import absolute_import |
1066 | @@ -184,6 +184,7 @@ |
1067 | from lp.testing.dbuser import switch_dbuser |
1068 | from lp.testing.fixture import CaptureOops |
1069 | from lp.testing.karma import KarmaRecorder |
1070 | +from lp.testing.mail_helpers import pop_notifications |
1071 | |
1072 | # The following names have been imported for the purpose of being |
1073 | # exported. They are referred to here to silence lint warnings. |
1074 | @@ -744,6 +745,19 @@ |
1075 | sorted( |
1076 | expected_permissions[permission] - attribute_names))) |
1077 | |
1078 | + def assertEmailQueueLength(self, length, sort_key=None): |
1079 | + """Pop the email queue, assert its length, and return it. |
1080 | + |
1081 | + This commits the transaction as part of pop_notifications. |
1082 | + """ |
1083 | + notifications = pop_notifications(sort_key=sort_key) |
1084 | + self.assertEqual( |
1085 | + length, len(notifications), |
1086 | + "Expected %d emails, got %d:\n\n%s" % ( |
1087 | + length, len(notifications), |
1088 | + "\n\n".join(str(n) for n in notifications))) |
1089 | + return notifications |
1090 | + |
1091 | |
1092 | class TestCaseWithFactory(TestCase): |
1093 |
I'd consider making assertEmailQueu eLength include the actual set of emails in the mismatch, but it's no worse than it was before.