#####################################################################
#
# test_Email    unit tests for the Email and TransactionalEmail classes
#
# This software is governed by a license. See
# LICENSE.txt for the terms of this license.
#
#####################################################################
__version__ = "$Revision: 1312 $"[11:-2]

# Python imports
import os
from shutil import rmtree
from tempfile import mkdtemp
from unittest import TestCase
from unittest import TestSuite
from unittest import makeSuite
from unittest import main

# Zope imports
import Testing
try:
    import Zope2
except ImportError:    # for Zope 2.7
    import Zope as Zope2
Zope2.startup()

# BBB - Zope 2.8 and on use "transaction.get" instead of get_transaction
try:
    import transaction
    get_transaction = transaction.get
    begin_transaction = transaction.manager.begin
except ImportError:
    # Zope 2.7 and earlier. No need to do anything, get_transaction is 
    # stuffed into __builtins__ in those Zope versions.
    begin_transaction = get_transaction().begin

# MaildropHost imports
from Products.MaildropHost.MaildropHost import Email
from Products.MaildropHost.MaildropHost import TransactionalEmail
from Products.MaildropHost.MaildropHost import MAILDROP_SPOOLS
from Products.MaildropHost.MaildropHost import DEBUG_RECEIVER

BODY = 'This is a test message'
ADDRESS_SEQUENCE = [ '"Root" <root@localhost>\r\n'
                   , '"Postmaster" <postmaster@localhost>'
                   ]
ADDRESS_STRING = '"Root" <root@localhost>\r'
ADDRESS_USTRING = u'"Postmaster" <postmaster@localhost>\n'
NOMAIL = ('.', '..', '.svn', 'CVS')

def _listSpools():
    """ List all spool contents """
    files = []

    for spool in MAILDROP_SPOOLS:
        files.extend([x for x in os.listdir(spool) if x not in NOMAIL])

    return files


class EmailTestBase(TestCase):

    def setUp(self):
        # replace the spool setting from the config file with a temporary
        # spool directory
        self.original_spools = MAILDROP_SPOOLS[:]
        MAILDROP_SPOOLS[:] = [mkdtemp()]

    def tearDown(self):
        # put the original spool setting back.
        rmtree(MAILDROP_SPOOLS[0])
        MAILDROP_SPOOLS[:] = self.original_spools

    def test_instantiation(self, klass):
        ae = self.assertEquals
        email1 = klass(ADDRESS_USTRING, ADDRESS_SEQUENCE, BODY)

        if DEBUG_RECEIVER == '':
            ae(email1.m_to, '"Root" <root@localhost>,"Postmaster" <postmaster@localhost>')
        else:
            ae(email1.m_to, DEBUG_RECEIVER)
        ae(email1.m_from, u'"Postmaster" <postmaster@localhost>')
        ae(email1.body, BODY)

        email2 = klass(ADDRESS_STRING, ADDRESS_USTRING, BODY)
        if DEBUG_RECEIVER == '':
            ae(email2.m_to, u'"Postmaster" <postmaster@localhost>')
        else:
            ae(email2.m_to, DEBUG_RECEIVER)
        ae(email2.m_from, '"Root" <root@localhost>')
        ae(email2.body, BODY)

        email3 = klass(ADDRESS_USTRING, ADDRESS_STRING, BODY)
        if DEBUG_RECEIVER == '':
            ae(email3.m_to, '"Root" <root@localhost>')
        else:
            ae(email3.m_to, DEBUG_RECEIVER)
        ae(email3.m_from, u'"Postmaster" <postmaster@localhost>')
        ae(email3.body, BODY)


class EmailTests(EmailTestBase):

    def test_instantiation(self):
        EmailTestBase.test_instantiation(self, Email)

    def test_send(self):
        # Non-transactional emails write to the spool no matter what
        self.assertEquals(len(_listSpools()), 0)
        email1 = Email(ADDRESS_USTRING, ADDRESS_SEQUENCE, BODY)
        email1.send()

        # Now that the email has been sent, there should only be the 
        # actual email file. The lockfile should be gone.
        self.assertEquals(len(_listSpools()), 1)

        # Make sure we clean up after ourselves...
        os.unlink(email1._tempfile)
        self.assertEquals(len(_listSpools()), 0)


class TransactionalEmailTests(EmailTestBase):

    def _makeAndSend(self):
        email = TransactionalEmail(ADDRESS_USTRING, ADDRESS_SEQUENCE, BODY)
        email.send()
        return email

    def test_instantiation(self):
        EmailTestBase.test_instantiation(self, TransactionalEmail)

    def test_send_notransaction(self):
        # Transactional emails need a successful commit
        self.assertEquals(len(_listSpools()), 0)
        email1 = self._makeAndSend()
        email1_turd = email1._tempfile

        # Now that the email has been sent, there should be two files: The
        # lock file and the actual email. The lockfile stays until the
        # transaction commits.
        self.assertEquals(len(_listSpools()), 2)

        # Make sure we clean up after ourselves...
        os.unlink(email1_turd)
        os.unlink('%s.lck' % email1_turd)
        self.assertEquals(len(_listSpools()), 0)

    def test_send_transaction(self):
        # First of all, make sure we are in a clean transaction
        begin_transaction()

        self.assertEquals(len(_listSpools()), 0)
        email1 = self._makeAndSend()
        email1_turd = email1._tempfile

        # Now that the email has been sent, there should be two files: The
        # lock file and the actual email. The lockfile stays until the
        # transaction commits.
        self.assertEquals(len(_listSpools()), 2)

        # Committing the transaction will remove the lock file so that the
        # maildrop daemon will process the mail file. That means only the
        # mail file itself remains in the spool after the commit.
        get_transaction().commit()
        self.assertEquals(len(_listSpools()), 1)

        # Make sure we clean up after ourselves...
        os.unlink(email1_turd)
        self.assertEquals(len(_listSpools()), 0)

        # abort the current transaction
        get_transaction().abort()
        self.assertEquals(len(_listSpools()), 0)

    def test_send_subtransaction(self):
        # First of all, make sure we are in a clean transaction
        begin_transaction()

        self.assertEquals(len(_listSpools()), 0)
        email1 = self._makeAndSend()

        # Now that the email has been sent, there should be two files: The
        # lock file and the actual email. The lockfile stays until the
        # transaction commits.
        self.assertEquals(len(_listSpools()), 2)

        # Committing a subtransaction should not do anything. Both email file
        # and lockfile should remain!
        try:
            # Savepoints replace subtransactions in Zope 2.8+
            get_transaction().savepoint(optimistic=True)
        except AttributeError:
            # Zope 2.7 and earlier don't know savepoints.
            get_transaction().commit(1)
        self.assertEquals(len(_listSpools()), 2)

        # abort the current transaction, which will clean the spool as well
        get_transaction().abort()
        self.assertEquals(len(_listSpools()), 0)

    def test_send_transaction_abort(self):
        # First of all, make sure we are in a clean transaction
        begin_transaction()

        self.assertEquals(len(_listSpools()), 0)
        email1 = self._makeAndSend()

        # Now that the email has been sent, there should be two files: The
        # lock file and the actual email. The lockfile stays until the
        # transaction commits.
        self.assertEquals(len(_listSpools()), 2)

        # Aborting a transaction should remove the email file and the
        # lockfile.
        get_transaction().abort()
        self.assertEquals(len(_listSpools()), 0)


def test_suite():
    return TestSuite((
        makeSuite(EmailTests),
        makeSuite(TransactionalEmailTests)
        ))

if __name__ == '__main__':
    main(defaultTest='test_suite')
