def test_(self): """ Verify that C{_associateWithImplementation} is called on the message passed to L{ExistingMessageMIMEStorer.__init__} with the correct values after parsing is finished. WHITEBOX. """ mime = PartMaker('text/plain', 'Hello, world.') class Message: def _associateWithImplementation(self, impl, source): self.impl = impl self.source = source store = Store() deliveryDir = self.mktemp() os.makedirs(deliveryDir) fObj = AtomicFile( filepath.FilePath(deliveryDir).child('tmp.eml').path, filepath.FilePath(deliveryDir).child('message.eml')) source = u'test://blarg' message = Message() storer = ExistingMessageMIMEStorer(store, fObj, source, message) part = storer.feedStringNow(mime.make()) self.assertIdentical(message.impl, part) self.assertEqual(message.source, source) self.assertEqual(message.recipient, u'<No Recipient>') self.assertEqual(message.subject, u'<No Subject>')
def test_messageSourceReplacesIllegalChars(self): """ Test that L{xquotient.exmess.MessageSourceFragment} renders the source of a message with XML-illegal characters replaced """ self.setUpMailStuff() m = self.createMIMEReceiver().feedStringNow( PartMaker('text/html', '\x00 \x01 hi').make()).message f = MessageSourceFragment(m) self.assertEqual(f.source(None, None), PartMaker('text/html', '0x0 0x1 hi').make() + '\n')
class PrintableMessageResourceTestCase(TestCase, MIMEReceiverMixin): """ Tests for L{xquotient.exmess.PrintableMessageResource} """ boringMessage = PartMaker('text/plain', 'plain').make() def setUp(self): self.setUpMailStuff() self.boringMessageItem = self.createMIMEReceiver().feedStringNow( self.boringMessage).message self.resource = PrintableMessageResource(self.boringMessageItem) def test_noActions(self): """ Test that L{PrintableMessageResource.renderHTTP} returns something wrapping an L{ActionlessMessageDetail} """ res = self.resource.renderHTTP(None) self.failUnless(isinstance(res.fragment, ActionlessMessageDetail)) def test_username(self): """ Verify that the L{GenericNavigationAthenaPage} returned from L{PrintableMessageResource.renderHTTP} has the C{username} attribute set to the right value. """ res = self.resource.renderHTTP(None) self.assertEqual(res.username, u'*****@*****.**')
class MessageBodyFragmentTestCase(TestCase, MIMEReceiverMixin): """ Test L{xquotient.exmess.MessageBodyFragment} """ altInsideMixed = PartMaker( 'multipart/mixed', 'mixed', PartMaker('multipart/alternative', 'alt', PartMaker('text/plain', 'plain'), PartMaker('text/html', '<html />')), PartMaker('text/plain', 'plain')).make() def test_alternateMIMETypesAltMixed(self): """ Test that C{text/html} is the only alternate MIME type returned by L{xquotient.exmess.MessageBodyFragment.getAlternateMIMETypes} for L{altInsideMixed} """ self.setUpMailStuff() m = self.createMIMEReceiver().feedStringNow( self.altInsideMixed).message messageBody = MessageBodyFragment(m, 'text/plain') self.assertEqual(list(messageBody.getAlternateMIMETypes()), ['text/html']) def test_getAlternatePartBodyAltMixed(self): """ Test that the parts returned from L{xquotient.exmess.MessageBodyFragment.getAlternatePartBody} are of the type C{text/html} and C{text/plain}, in that order, when asked for C{text/html} part bodies from L{altInsideMixed} """ self.setUpMailStuff() m = self.createMIMEReceiver().feedStringNow( self.altInsideMixed).message messageBody = MessageBodyFragment(m, 'text/plain') messageBody = messageBody.getAlternatePartBody('text/html') self.assertEqual(list(p.type for p in messageBody.parts), ['text/html', 'text/plain']) mixed = PartMaker('multipart/mixed', 'mixed', PartMaker('text/plain', 'plain'), PartMaker('text/html', 'html')).make() def test_getAlternateMIMETypesMixed(self): """ Test that there are no alternate MIME types returned by L{xquotient.exmess.MessageBodyFragment.getAlternateMIMETypes} for L{mixed} """ self.setUpMailStuff() m = self.createMIMEReceiver().feedStringNow(self.mixed).message messageBody = MessageBodyFragment(m, 'text/plain') self.assertEqual(list(messageBody.getAlternateMIMETypes()), [])
def test_unknownMultipartTypeAsMixed(self): """ Test that unknown multipart mime types are rendered with 'multipart/mixed' content types (with their original content). Any "multipart" subtypes that an implementation does not recognize must be treated as being of subtype "mixed". RFC 2046, Section 5.1.3 """ content = PartMaker('multipart/appledouble', 'appledouble', PartMaker('text/applefile', 'applefile'), PartMaker('image/jpeg', 'jpeg')).make() part = self.setUpMailStuff().feedStringNow(content) walkedPart = part.walkMessage('image/jpeg').next() self.assertEquals(walkedPart.type, 'multipart/mixed') self.assertEquals(walkedPart.part, part)
def test_createMessageWithMultipartAttachment(self): """ Test L{xquotient.mimebakery.createMessage} when there is a multipart attachment """ fileItem = self.cabinet.createFileItem( u'a multipart', u'multipart/mixed', PartMaker('multipart/mixed', 'mixed', PartMaker('text/plain', 'text/plain #1'), PartMaker('text/plain', 'text/plain #2')).make()) msg = self._createMessageWithFiles((fileItem, )) multipart = list(msg.impl.walk())[-3] self.assertEquals(multipart.getContentType(), 'multipart/mixed') self._assertFilenameParamEquals(multipart, 'a multipart') (_, textPlain1, textPlain2) = multipart.walk() self.assertEquals(textPlain1.getContentType(), 'text/plain') self.assertEquals(textPlain1.getBody(), 'text/plain #1\n') self.assertEquals(textPlain2.getContentType(), 'text/plain') self.assertEquals(textPlain2.getBody(), 'text/plain #2\n')
def test_unknownTypeAsOctetStream(self): """ Test that unknown non-multipart mime types are rendered as 'application/octet-stream'. See RFC 2046, Section 4 for more details. """ content = PartMaker('image/applefile', 'applefile').make() part = self.setUpMailStuff().feedStringNow(content) walkedPart = part.walkMessage(None).next() self.assertEquals(walkedPart.type, 'application/octet-stream') self.assertEquals(walkedPart.part, part)
def test_createMessageWithMessageAttachment(self): """ Test L{xquotient.mimebakery.createMessage} when there is an attachment of type message/rfc822 """ fileItem = self.cabinet.createFileItem( u'a message', u'message/rfc822', PartMaker('text/plain', 'some text/plain').make()) msg = self._createMessageWithFiles((fileItem, )) rfc822part = list(msg.impl.walk())[-2] self.assertEquals(rfc822part.getContentType(), 'message/rfc822') self._assertFilenameParamEquals(rfc822part, 'a message') (_, textPlainPart) = rfc822part.walk() self.assertEquals(textPlainPart.getContentType(), 'text/plain') self.assertEquals(textPlainPart.getBody(), 'some text/plain\n')
class MessageDataTestCase(unittest.TestCase, PersistenceMixin): """ Test the L{IMessageData} implementation provided by L{Part}. """ fromDisplay = u'\N{LATIN CAPITAL LETTER A WITH GRAVE}lice' fromEmail = u'*****@*****.**' fromAddress = u'%s <%s>' % (fromDisplay, fromEmail) senderDisplay = u'B\N{LATIN SMALL LETTER O WITH GRAVE}b' senderEmail = u'*****@*****.**' senderAddress = u'%s <%s>' % (senderDisplay, senderEmail) replyToDisplay = u'C\N{LATIN SMALL LETTER A WITH GRAVE}rol' replyToEmail = u'*****@*****.**' replyToAddress = u'%s <%s>' % (replyToDisplay, replyToEmail) toDisplay = u'Dav\N{LATIN SMALL LETTER A WITH GRAVE}' toEmail = u'*****@*****.**' toAddress = u'%s <%s>' % (toDisplay, toEmail) ccDisplay = u'\N{LATIN CAPITAL LETTER E WITH GRAVE}ve' ccEmail = u'*****@*****.**' ccAddress = u'%s <%s>' % (ccDisplay, ccEmail) bccDisplay = u'Is\N{LATIN SMALL LETTER A WITH GRAVE}ac' bccEmail = u'*****@*****.**' bccAddress = u'%s <%s>' % (bccDisplay, bccEmail) resentFromDisplay = u'Iv\N{LATIN SMALL LETTER A WITH GRAVE}n' resentFromEmail = u'*****@*****.**' resentFromAddress = u'%s <%s>' % (resentFromDisplay, resentFromEmail) resentToDisplay = u'M\N{LATIN SMALL LETTER A WITH GRAVE}llory' resentToEmail = u'*****@*****.**' resentToAddress = u'%s <%s>' % (resentToDisplay, resentToEmail) def setUp(self): self.headers = { 'from': self._header('From', self.fromAddress), 'sender': self._header('Sender', self.senderAddress), 'reply-to': self._header('Reply-To', self.replyToAddress), 'cc': self._header('Cc', self.ccAddress), 'to': self._header('To', self.toAddress), 'bcc': self._header('Bcc', self.bccAddress), 'resent-from': self._header('Resent-From', self.resentFromAddress), 'resent-to': self._header('Resent-To', self.resentToAddress) } def _header(self, name, value): return (name + ': ' + email.quopriMIME.header_encode(value) + '\n').encode('ascii') def _relationTest(self, headers, relation, email, display): msgSource = msg(relatedAddressesMessage % headers) def checkRelatedAddresses(part): for (kind, addr) in part.relatedAddresses(): if kind == relation: self.assertEqual(addr.email, email) self.assertEqual(addr.display, display) break else: self.fail("Did not find %r" % (relation, )) self._messageTest(msgSource, checkRelatedAddresses) def test_senderAddressWithFrom(self): """ Test that L{Part.relatedAddresses} yields a sender address taken from the C{From} header of a message, if that header is present. """ self._relationTest(self.headers, SENDER_RELATION, self.fromEmail, self.fromDisplay) def test_senderAddressWithSender(self): """ Test that L{Part.relatedAddresses} yields a sender address taken from the C{Sender} header of a message, if that header is present and the C{From} header is not. """ self.headers['from'] = '' self._relationTest(self.headers, SENDER_RELATION, self.senderEmail, self.senderDisplay) def test_senderAddressWithReplyTo(self): """ Test that L{Part.relatedAddresses} yields a sender address taken from the C{Reply-To} header of a message, if that header is present and the C{From} and C{Sender} headers are not. """ self.headers['from'] = self.headers['sender'] = '' self._relationTest(self.headers, SENDER_RELATION, self.replyToEmail, self.replyToDisplay) def test_recipientRelation(self): """ Test that L{Part.relatedAddresses} yields a recipient address taken from the C{To} header of the message, if that header is present. """ self._relationTest(self.headers, RECIPIENT_RELATION, self.toEmail, self.toDisplay) def test_copyRelation(self): """ Test that L{Part.relatedAddresses} yields a recipient address taken from the C{Cc} header of the message, if that header is present. """ self._relationTest(self.headers, COPY_RELATION, self.ccEmail, self.ccDisplay) def test_multiRelation(self): """ Test that L{Part.relatedAddresses} yields multiple recipient addresses taken from the C{Cc} header of the message, if multiples were found. """ self.headers['cc'] = self._header( 'Cc', self.ccAddress + u", " + self.bccAddress) msgSource = msg(relatedAddressesMessage % self.headers) def checkRelations(msg): ccAddrs = [(addr.email, addr.display) for (kind, addr) in msg.relatedAddresses() if kind == COPY_RELATION] self.assertEqual([(self.ccEmail, self.ccDisplay), (self.bccEmail, self.bccDisplay)], ccAddrs) self._messageTest(msgSource, checkRelations) def test_blindCopyRelation(self): """ Test that L{Part.relatedAddresses} yields a recipient address taken from the C{Bcc} header of the message, if that header is present. """ self._relationTest(self.headers, BLIND_COPY_RELATION, self.bccEmail, self.bccDisplay) def test_resentToRelation(self): """ Test that L{Part.relatedAddresses} yields a 'resent to' address taken from the C{Resent-To} header of the message, if that header is present """ self._relationTest(self.headers, RESENT_TO_RELATION, self.resentToEmail, self.resentToDisplay) def test_resentFromRelation(self): """ Test that L{Part.relatedAddresses} yields a 'resent from' address taken from the C{Resent-From} header of the message, if that header is present """ self._relationTest(self.headers, RESENT_FROM_RELATION, self.resentFromEmail, self.resentFromDisplay) def test_noRelations(self): """ Test that L{Part.relatedAddresses} gives back an empty iterator if there are no usable headers in the message. """ msgSource = msg( relatedAddressesMessage % { 'from': '', 'sender': '', 'reply-to': '', 'cc': '', 'to': '', 'bcc': '', 'resent-from': '', 'resent-to': '' }) def checkRelations(msg): self.assertEqual(list(msg.relatedAddresses()), []) self._messageTest(msgSource, checkRelations) alternateMessage = PartMaker('multipart/alternative', 'alt', PartMaker('text/plain', 'plain'), PartMaker('text/html', 'html')).make() def test_getAlternatesAlternate(self): """ Test that L{xquotient.mimestorage.Part.getAlternates} returns C{text/html} as the alternate for a C{text/plain} part inside a C{multipart/alternative} part """ def checkAlternates(p): (alt, plain, html) = p.walk() self.assertEqual(list(plain.getAlternates()), [('text/html', html)]) self._messageTest(self.alternateMessage, checkAlternates)
class PersistenceTestCase(unittest.TestCase, MessageTestMixin, PersistenceMixin): alternative = PartMaker('multipart/alternative', 'alt', PartMaker('text/plain', 'plain-1'), PartMaker('text/html', 'html-1'), PartMaker('image/jpeg', 'hi')).make() def test_readableParts(self): """ Test that L{xquotient.mimestorage.Part.readableParts} returns the text/plain and text/html parts inside a multipart/alternative """ def checkReadable(part): self.assertEquals( list(p.getContentType() for p in part.readableParts()), ['text/plain', 'text/html']) self._messageTest(self.alternative, checkReadable) def assertIndexability(self, msg): fi = ixmantissa.IFulltextIndexable(msg.message) self.assertEquals(fi.uniqueIdentifier(), unicode(msg.message.storeID)) # XXX: the following success fixtures are sketchy. the # '*****@*****.**' token appears twice because it is both the # 'senderDisplay' and the 'sender' of this message. That strikes me as # wrong; it should eliminate the duplicate, and not rely on the details # of the attributes of Message, since ideally those attributes would be # accessed through the Correspondent item anyway. --glyph self.assertEquals(sorted(fi.textParts()), [ u'Hello Bob,\n How are you?\n-A\n', u'a test message, comma separated', u'[email protected] alice example com', u'[email protected] bob example com' ]) self.assertEquals( fi.keywordParts(), { u'subject': u'a test message, comma separated', u'from': u'[email protected] alice example com', u'to': u'[email protected] bob example com' }) self.assertEquals(fi.documentType(), msg.message.typeName) def test_unknownMultipartTypeAsMixed(self): """ Test that unknown multipart mime types are rendered with 'multipart/mixed' content types (with their original content). Any "multipart" subtypes that an implementation does not recognize must be treated as being of subtype "mixed". RFC 2046, Section 5.1.3 """ content = PartMaker('multipart/appledouble', 'appledouble', PartMaker('text/applefile', 'applefile'), PartMaker('image/jpeg', 'jpeg')).make() part = self.setUpMailStuff().feedStringNow(content) walkedPart = part.walkMessage('image/jpeg').next() self.assertEquals(walkedPart.type, 'multipart/mixed') self.assertEquals(walkedPart.part, part) def test_unknownTypeAsOctetStream(self): """ Test that unknown non-multipart mime types are rendered as 'application/octet-stream'. See RFC 2046, Section 4 for more details. """ content = PartMaker('image/applefile', 'applefile').make() part = self.setUpMailStuff().feedStringNow(content) walkedPart = part.walkMessage(None).next() self.assertEquals(walkedPart.type, 'application/octet-stream') self.assertEquals(walkedPart.part, part) def testIndexability(self): """ Test that the result is adaptable to IFulltextIndexable and the resulting object spits out the right data. """ self._messageTest(self.trivialMessage, self.assertIndexability) def testAttachmentsAllHaveLength(self): def checkAttachments(msgitem): for att in msgitem.walkAttachments(): self.assertNotEquals(att.part.bodyLength, None) self.assertNotEquals(att.part.bodyLength, 0) self._messageTest(messageWithEmbeddedMessage, checkAttachments) self._messageTest(truncatedMultipartMessage, checkAttachments) def testRFC822PartLength(self): """ Ensure that message/rfc822 parts have the correct bodyLength. """ def checkAttachments(msgitem): for att in msgitem.walkAttachments(): if att.part.getContentType() == u"message/rfc822": self.assertEquals(att.part.bodyLength, 3428) self._messageTest(messageWithEmbeddedMessage, checkAttachments) def testPartIDs(self): mr = self.setUpMailStuff() part = mr.feedStringNow(self.multipartMessage) self.assertEquals(part.partID, 0) partIDs = list( part.store.query(Part, sort=Part.partID.ascending).getColumn('partID')) self.assertEquals(partIDs, range(len(partIDs))) alternativeInsideMixed = PartMaker( 'multipart/mixed', 'mixed', PartMaker('multipart/alternative', 'alt', PartMaker('text/plain', 'plain-1'), PartMaker('text/html', 'html-1')), PartMaker('text/plain', 'plain-2')).make() def testAlternativeInsideMixed(self): part = self.setUpMailStuff().feedStringNow(self.alternativeInsideMixed) self.assertEquals(part.getContentType(), 'multipart/mixed') def getKidsAndCheckTypes(parent, types): kids = list(part.store.query(Part, Part.parent == parent)) self.assertEquals(list(p.getContentType() for p in kids), types) return kids kids = getKidsAndCheckTypes(part, ['multipart/alternative', 'text/plain']) # RFC 2046, section 5.1.1, says: # "The CRLF preceding the boundary delimiter line is conceptually attached # to the boundary so that it is possible to have a part that does not end # with a CRLF (line break). Body parts that must be considered to end with # line breaks, therefore, must have two CRLFs preceding the boundary # delimiter line, the first of which is part of the preceding body part, # and the second of which is part of the encapsulation boundary." # # - there is only one CRLF between the end of the body of each part # and the next boundary, which would seem to indicate that we shouldn't # have to to embed newlines to get these assertions to pass self.assertEquals(kids[-1].getBody(), 'plain-2\n') gkids = getKidsAndCheckTypes(kids[0], ['text/plain', 'text/html']) self.assertEquals(gkids[0].getBody(), 'plain-1\n') self.assertEquals(gkids[1].getBody(), 'html-1\n') def checkDisplayBodies(parent, ctype, bodies): displayParts = list(parent.walkMessage(ctype)) self.assertEquals(list(p.part.getBody() for p in displayParts), bodies) # we're calling walkMessage() on the multipart/mixed part, # so we should get back 'plain-1' and 'plain-2', not 'html-1', # because it's inside a nested multipart/alternative with 'plain-1' checkDisplayBodies(part, 'text/plain', ['plain-1\n', 'plain-2\n']) # this should still show us 'plain-2', but 'html-1' should get # selected from the multipart/alternative part checkDisplayBodies(part, 'text/html', ['html-1\n', 'plain-2\n']) # now try the same on the multipart/alternative part directly. # the results should be the same, except plain-2 shouldn't be # considered, because it's a sibling part checkDisplayBodies(kids[0], 'text/plain', ['plain-1\n']) checkDisplayBodies(kids[0], 'text/html', ['html-1\n']) typelessMessage = msg("""\ To: you From: nobody haha """) def testContentTypeNotNone(self): self._messageTest( self.typelessMessage, lambda part: self.assertEquals( part.getContentType(), 'text/plain')) datelessMessage = msg("""\ Received: Wed, 15 Feb 2006 03:58:50 GMT Some body """) def testSentWhen(self): def assertSentWhen(part): self.assertEquals( part.message.sentWhen, extime.Time.fromRFC2822("Wed, 15 Feb 2006 03:58:50 GMT")) self._messageTest(self.datelessMessage, assertSentWhen)
class RenderingTestCase(TestCase, MIMEReceiverMixin): aBunchOfRelatedParts = PartMaker( 'multipart/related', 'related', *(list( PartMaker('text/html', '<p>html-' + str(i) + '</p>') for i in xrange(100)) + list(PartMaker('image/gif', '') for i in xrange(100)))).make() def setUp(self): """ Make a copy of the very minimal database for a single test method to mangle. """ receiver = self.setUpMailStuff() makeMessage(receiver, self.aBunchOfRelatedParts, None) def test_messageRendering(self): """ Test rendering of message detail for an extremely complex message. """ msg = self.substore.findUnique(Message) msg.classifyClean() return renderLivePage(ThemedFragmentWrapper(MessageDetail(msg))) def test_inboxRendering(self): """ Test rendering of the inbox with a handful of extremely complex messages in it. """ def deliverMessages(): for i in xrange(5): makeMessage(self.createMIMEReceiver(), self.aBunchOfRelatedParts, None) self.substore.transact(deliverMessages) inbox = self.substore.findUnique(Inbox) composer = compose.Composer(store=self.substore) installOn(composer, self.substore) return renderLivePage(ThemedFragmentWrapper(InboxScreen(inbox))) def test_inboxComposeFragmentRendering(self): """ Test rendering of the L{xquotient.compose.ComposeFragment} returned from L{xquotient.inbox.InboxScreen.getComposer} """ installOn(compose.Composer(store=self.substore), self.substore) inbox = self.substore.findUnique(Inbox) inboxScreen = InboxScreen(inbox) composeFrag = inboxScreen.getComposer() return renderLivePage(ThemedFragmentWrapper(composeFrag)) def test_peopleMessageListRendering(self): mlister = MessageLister(store=self.substore) installOn(mlister, self.substore) p = Person(store=self.substore, name=u'Bob') EmailAddress(store=self.substore, person=p, address=u'bob@internet') for i in xrange(5): testMessageFactory(store=self.substore, subject=unicode(str(i)), receivedWhen=Time(), spam=False, sender=u'bob@internet') self.assertEqual(len(list(mlister.mostRecentMessages(p))), 5) return renderPage( rend.Page(docFactory=loaders.stan(MessageList(mlister, p))))