import re
import mock
from datetime import time, date, datetime, timedelta

import transaction

from Products.Naaya.tests.NaayaTestCase import NaayaTestCase
from Products.Naaya.NyFolder import addNyFolder
from Products.NaayaCore.NotificationTool.utils import (
    walk_subscriptions, divert_notifications, get_modified_objects)
from Products.NaayaCore.NotificationTool import constants
from Products.NaayaCore.EmailTool import EmailTool
from naaya.content.document.document_item import addNyDocument
from naaya.core.zope2util import path_in_site


class BaseNotificationsTest(NaayaTestCase):
    def afterSetUp(self):
        notif_tool = self.portal.getNotificationTool()
        self._notif_config = dict(notif_tool.config)
        self._notifications = []
        self._subscriptions_to_remove = []
        self.patches = []
        self.object_timestamps = []

        divert_notifications(True, save_to=self._notifications)
        addNyFolder(self.portal, 'fol1', contributor='contributor')
        addNyFolder(self.portal, 'fol2', contributor='contributor')

        def get_modified_objects(site, when_start, when_end):
            for ob_path, modif_datetime in self.object_timestamps:
                if when_start < modif_datetime < when_end:
                    yield ('', site.unrestrictedTraverse(ob_path))

        self.patches.append(mock.patch(
            'Products.NaayaCore.NotificationTool.utils.get_modified_objects',
            get_modified_objects))

        transaction.commit()

        for patch in self.patches:  # Apply patches
            patch.start()

    def beforeTearDown(self):
        notif_tool = self.portal.getNotificationTool()
        for args in self._subscriptions_to_remove:
            notif_tool.remove_account_subscription(*args)
        self.portal.manage_delObjects(['fol1', 'fol2'])
        notif_tool.config.update(self._notif_config)
        divert_notifications(False)

        for patch in self.patches:  # Remove patches
            patch.stop()

        transaction.commit()

    def _fetch_test_notifications(self):
        notifications = list(self._notifications)
        self._notifications[:] = []
        return notifications

    def add_account_subscription(self, *args):
        """
        create a subscription; it will be auto-removed in
        beforeTearDown.
        """
        self.portal.getNotificationTool().add_account_subscription(*args)
        self._subscriptions_to_remove.append(args)

    def walk_site_subscriptions(self):
        return set((sub.user_id, path_in_site(obj),
                   sub.notif_type, sub.lang) for
                   obj, n, sub in walk_subscriptions(self.portal))


class NotificationsUnitTest(BaseNotificationsTest):
    """ unit test for notifications """

    def afterSetUp(self):
        super(NotificationsUnitTest, self).afterSetUp()
        self.portal.getNotificationTool().config.update({
            'enable_instant': True,
            'enable_daily': True,
            'enable_weekly': True,
            'enable_monthly': True,
        })
        transaction.commit()

    def test_add_account_subscription(self):
        self.add_account_subscription('gigel', '', 'instant', 'en')
        self.add_account_subscription('contributor', '', 'instant', 'en')

        subs = self.walk_site_subscriptions()
        self.assertEqual(len(subs), 2)
        self.assertEqual(subs, set([
            ('gigel', '', 'instant', 'en'),
            ('contributor', '', 'instant', 'en'), ]))

    def test_anonymous_account_subscription(self):
        """ Add anonymous notification.
        This sends an email with a confirmation key. Confirm with confirm
        method and verify if subscription is in the container.

        """
        diverted_mail = EmailTool.divert_mail()
        notif_tool = self.portal.getNotificationTool()
        notif_tool.add_anonymous_subscription(email='some@email.com',
                                              lang='en', location='',
                                              notif_type='instant',
                                              content_types=[])
        assert diverted_mail[0][1] == ['some@email.com']
        assert diverted_mail[0][3] == 'from.zope@example.com'
        assert diverted_mail[0][0].find('confirm?key=') != -1
        confirm_key = re.search('confirm\?key=(\w+)',
                                diverted_mail[0][0]).group(1)
        notif_tool.confirm(key=confirm_key)
        for obj, n, sub in walk_subscriptions(self.portal):
            if sub.email == 'some@email.com':
                break
        else:
            raise "No subscription found"
        EmailTool.divert_mail(False)

    def test_weekly_notification_interval(self):
        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('user1', '', 'weekly', 'en')
        self.add_account_subscription('user2', '', 'weekly', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a')
        addNyDocument(self.portal['fol1'], id='doc_b')
        self.object_timestamps = [
            ('fol1/doc_a', datetime(2009, 7, 16)),
            ('fol1/doc_b', datetime(2009, 8, 3)),
        ]

        notif_tool._send_newsletter('weekly',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))
        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at '
             u'http://nohost/portal/fol1 by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at '
             u'http://nohost/portal/fol2 by "".'),
            ('from.zope@example.com', 'user1@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_b (http://nohost/portal/fol1/doc_b)\n\n\n'
             u'If you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html'),
            ('from.zope@example.com', 'user2@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_b (http://nohost/portal/fol1/doc_b)\n\n\n'
             u'If you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html')
        ]))

    def test_notification_type_checking(self):
        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('user1', 'fol1', 'instant', 'en')
        self.add_account_subscription('user2', 'fol1', 'daily', 'en')
        self.add_account_subscription('user3', 'fol1', 'weekly', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a')
        addNyDocument(self.portal['fol1'], id='doc_b')
        self.object_timestamps = [
            ('fol1/doc_a', datetime(2009, 7, 16)),
            ('fol1/doc_b', datetime(2009, 8, 3)),
        ]

        notif_tool._send_newsletter('daily',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))
        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/fol2 '
             u'by "".'),
            ('from.zope@example.com', 'user2@example.com',
             u'Changed items - daily digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_b (http://nohost/portal/fol1/doc_b)\n\n\n'
             u'If you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html')
        ]))

        notif_tool._send_newsletter('weekly',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))
        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'user3@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_b (http://nohost/portal/fol1/doc_b)\n\n\n'
             u'If you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html')
        ]))

        notif_tool._send_newsletter('monthly',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))
        self.assertEqual(set(self._fetch_test_notifications()), set())

    def test_weekly_notification_paths(self):
        notif_tool = self.portal.getNotificationTool()

        addNyFolder(self.portal['fol1'], 'g', contributor='admin')
        addNyFolder(self.portal['fol1'], 'h', contributor='admin')
        addNyDocument(self.portal['fol1']['g'], id='doc_a')
        addNyDocument(self.portal['fol1']['h'], id='doc_b')
        addNyDocument(self.portal['fol1'], id='doc_c', discussion=0)

        self.add_account_subscription('user1', 'fol1/g', 'weekly', 'en')
        self.add_account_subscription('user2', 'fol1/h', 'weekly', 'en')
        self.add_account_subscription('user3', 'fol1', 'weekly', 'en')

        self.object_timestamps = [
            ('fol1/g/doc_a', datetime(2009, 8, 3)),
            ('fol1/h/doc_b', datetime(2009, 8, 3)),
            ('fol1/doc_c', datetime(2009, 8, 3)),
        ]

        notif_tool._send_newsletter('weekly',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))
        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/fol2 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: g',
             u'The item "g" has been created at http://nohost/portal/fol1/g '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: h',
             u'The item "h" has been created at http://nohost/portal/fol1/h '
             u'by "".'),
            ('from.zope@example.com', 'user1@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_a (http://nohost/portal/fol1/g/doc_a)\n\n\n'
             u'If you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html'),
            ('from.zope@example.com', 'user3@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_a (http://nohost/portal/fol1/g/doc_a)\n\n: '
             u'doc_b (http://nohost/portal/fol1/h/doc_b)\n\n: doc_c '
             u'(http://nohost/portal/fol1/doc_c)\n\n\nIf you want to '
             u'unsubscribe from these notifications follow the link below:'
             u'\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html'),
            ('from.zope@example.com', 'user2@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_b (http://nohost/portal/fol1/h/doc_b)\n\n\n'
             u'If you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html')
        ]))

    def test_instant_notifications(self):
        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('user1', 'fol1', 'instant', 'en')
        self.add_account_subscription('user2', 'fol2', 'instant', 'en')
        self.add_account_subscription('user3', '', 'instant', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a')
        self.portal.fol1.doc_a.approved = True
        self.object_timestamps = [
            ('fol1/doc_a', datetime(2009, 8, 3)),
        ]
        notif_tool.notify_instant(self.portal['fol1']['doc_a'], 'somebody')
        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/fol2 '
             u'by "".'),
            ('from.zope@example.com', 'user1@example.com',
             u'Instant notification for object: doc_a',
             u'The item "doc_a" has been created at http://nohost/portal/fol1/'
             u'doc_a by "".\n\n\nIf you want to unsubscribe from these '
             u'notifications follow the link below:\n\nhttp://nohost/portal/'
             u'portal_notification/my_subscriptions_html'),
            ('from.zope@example.com', 'user3@example.com',
             u'Instant notification for object: doc_a',
             u'The item "doc_a" has been created at http://nohost/portal/fol1/'
             u'doc_a by "".\n\n\nIf you want to unsubscribe from these '
             u'notifications follow the link below:\n\nhttp://nohost/portal/'
             u'portal_notification/my_subscriptions_html')
        ]))

    def test_sitemap(self):
        """ make sure sitemap returns the right stuff """

        notif_tool = self.portal.getNotificationTool()
        expected = [{'attributes': {'title': ''},
                     'children': [{'attributes': {'title': 'info'},
                                   'children': [],
                                   'data': {'icon': 'misc_/Naaya/NyFolder.png',
                                            'title': 'Information'}}],
                     'data': {'title': 'Naaya Test Site',
                              'icon': 'misc_/Naaya/Site.gif'}}]
        self.assertEqual(notif_tool._sitemap_dict({'node': ''}), expected)
        self.assertEqual(notif_tool._sitemap_dict({'node': 'info'}), [])


class NotificationsRestrictedUnitTest(BaseNotificationsTest):
    """ unit test for notifications on restricted objects """

    def afterSetUp(self):
        super(NotificationsRestrictedUnitTest, self).afterSetUp()
        self.portal.getNotificationTool().config.update({
            'enable_instant': True,
            'enable_daily': True,
            'enable_weekly': True,
            'enable_monthly': True,
        })
        transaction.commit()

    def test_restricted_instant(self):
        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('reviewer', 'fol1', 'instant', 'en')
        self.add_account_subscription('user2', 'fol1', 'instant', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a', contributor='admin')
        self.portal.fol1.doc_a.approved = True
        addNyDocument(self.portal['fol1'], id='doc_b', contributor='admin')
        self.portal.fol1.doc_b.approved = True
        self.portal['fol1']['doc_a']._View_Permission = ('Reviewer',)
        self.portal['fol1']['doc_b']._View_Permission = ('Contributor',)

        notif_tool.notify_instant(self.portal['fol1']['doc_a'], 'somebody')
        notif_tool.notify_instant(self.portal['fol1']['doc_b'], 'somebody')
        notifications = self._fetch_test_notifications()
        self.assertEqual(set(notifications), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/'
             u'fol2 by "".'),
            ('from.zope@example.com', 'reviewer@example.com',
             u'Instant notification for object: doc_a',
             u'The item "doc_a" has been created at http://nohost/portal/fol1/'
             u'doc_a by "".\n\n\nIf you want to unsubscribe from these '
             u'notifications follow the link below:\n\nhttp://nohost/portal/'
             u'portal_notification/my_subscriptions_html'),
            ('from.zope@example.com', 'user2@example.com',
             u'Instant notification for object: doc_b',
             u'The item "doc_b" has been created at http://nohost/portal/fol1/'
             u'doc_b by "".\n\n\nIf you want to unsubscribe from these '
             u'notifications follow the link below:\n\nhttp://nohost/portal/'
             u'portal_notification/my_subscriptions_html')
        ]))

    def test_restricted_periodic(self):

        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('reviewer', 'fol1', 'weekly', 'en')
        self.add_account_subscription('user2', 'fol1', 'weekly', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a', contributor='admin')
        addNyDocument(self.portal['fol1'], id='doc_b', contributor='admin')
        self.portal['fol1']['doc_a']._View_Permission = ('Reviewer',)
        self.portal['fol1']['doc_b']._View_Permission = ('Contributor',)

        self.object_timestamps = [
            ('fol1/doc_a', datetime(2009, 8, 3)),
            ('fol1/doc_b', datetime(2009, 8, 3)),
        ]

        notif_tool._send_newsletter('weekly',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))

        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/fol2 '
             u'by "".'),
            ('from.zope@example.com', 'reviewer@example.com',
             u'Changed items - weekly digest', u'Following items were '
             u'modified/added in the "Naaya Test Site" portal.\n\n: doc_a '
             u'(http://nohost/portal/fol1/doc_a)\n\n\nIf you want to '
             u'unsubscribe from these notifications follow the link below:'
             u'\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html'),
            ('from.zope@example.com', 'user2@example.com',
             u'Changed items - weekly digest',
             u'Following items were modified/added in the "Naaya Test Site" '
             u'portal.\n\n: doc_b (http://nohost/portal/fol1/doc_b)\n\n\nIf '
             u'you want to unsubscribe from these notifications follow the '
             u'link below:\n\nhttp://nohost/portal/portal_notification/'
             u'my_subscriptions_html')
        ]))


class NotificationsUnapprovedUnitTest(BaseNotificationsTest):
    """ When an object is created e.g. by a Contributor, and it's not
    approved yet, notifications should not be sent. Rather they should be
    sent when the object becomes approved.

    Tests: #525

    """

    def afterSetUp(self):
        super(NotificationsUnapprovedUnitTest, self).afterSetUp()
        self.portal.getNotificationTool().config.update({
            'enable_instant': True,
            'enable_daily': True,
            'enable_weekly': True,
            'enable_monthly': True,
        })
        transaction.commit()

    def test_instant(self):
        """ Instant notifications for unapproved objects
        When an object is added it calls the
        ``NotificationTool.handle_object_add`` subscriber that sends the
        notifications to the folder maintainers and the notification subribers
        Since we do not call directly that subscriber no notifications will
        be sent. See the tests from ``test_noitifications_functional`` for the
        above case.

        """
        self.add_account_subscription('contributor', 'fol1', 'instant', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a', approved=0,
                      contributor='admin')

        notif_tool = self.portal.getNotificationTool()
        notif_tool.notify_instant(self.portal['fol1']['doc_a'], 'somebody')

        # No notifications should be sent at this point
        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/fol2 '
             u'by "".')
            ]))

    def test_periodic(self):
        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('contributor', 'fol1', 'instant', 'en')

        addNyDocument(self.portal['fol1'], id='doc_a', approved=0,
                      contributor='admin')
        self.object_timestamps = [
            ('fol1/doc_a', datetime(2009, 8, 3)),
        ]

        notif_tool._send_newsletter('weekly',
                                    datetime(2009, 7, 30),
                                    datetime(2009, 8, 5))

        self.assertEqual(set(self._fetch_test_notifications()), set([
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol1',
             u'The item "fol1" has been created at http://nohost/portal/fol1 '
             u'by "".'),
            ('from.zope@example.com', 'site.admin@example.com',
             u'Maintainer notification for object: fol2',
             u'The item "fol2" has been created at http://nohost/portal/fol2 '
             u'by "".')
            ]))


class NotificationsCronUnitTest(BaseNotificationsTest):
    """ unit test for notifications """

    def afterSetUp(self):
        super(NotificationsCronUnitTest, self).afterSetUp()
        notif_tool = self.portal.getNotificationTool()
        notif_tool.config['daily_hour'] = 10
        notif_tool.config['weekly_hour'] = 10
        notif_tool.config['weekly_day'] = 3
        notif_tool.config['monthly_hour'] = 10
        notif_tool.config['monthly_day'] = 17

        self._newsletters = []

        def testing_send_newsletter(self2, notif_type, when_start, when_end):
            self._newsletters.append((notif_type, when_start, when_end))
        p = mock.patch(
            'Products.NaayaCore.NotificationTool.NotificationTool'
            '.NotificationTool._send_newsletter', testing_send_newsletter)
        self.patches.append(p)  # This needs to be exited
        p.__enter__()
        transaction.commit()

    def fetch_test_newsletters(self):
        newsletters = set(self._newsletters)
        self._newsletters[:] = []
        return newsletters

    def test_daily(self):
        notif_tool = self.portal.getNotificationTool()

        # disable notifications
        notif_tool.config['enable_daily'] = False
        notif_tool.config['enable_weekly'] = False
        notif_tool.config['enable_monthly'] = False

        # daily notifications are disabled; send nothing
        today = date(2009, 8, 3)
        notif_tool._cron_heartbeat(datetime.combine(today, time(7, 15)))
        self.failUnlessEqual(self.fetch_test_newsletters(), set())

        # enable daily notifications
        notif_tool.config['enable_daily'] = True

        # no previous timestamp; should send daily newsletter
        today_7_15 = datetime.combine(today, time(7, 15))
        notif_tool._cron_heartbeat(today_7_15)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('daily', today_7_15 - timedelta(days=1),
                                   today_7_15)]))

        # again no previous timestamp, but this time after the designated
        # hour; should still send newsletter
        del notif_tool.timestamps['daily']
        today_15_26 = datetime.combine(today, time(15, 26))
        notif_tool._cron_heartbeat(today_15_26)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('daily', today_15_26 - timedelta(days=1),
                                   today_15_26)]))

        # previous timestamp is too close; should send nothing
        notif_tool._cron_heartbeat(datetime.combine(today, time(18, 10)))
        self.failUnlessEqual(self.fetch_test_newsletters(), set())

        # after next regular time; should send newsletter
        tomorrow_10_15 = datetime.combine(today + timedelta(days=1),
                                          time(10, 15))
        notif_tool._cron_heartbeat(tomorrow_10_15)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('daily', today_15_26, tomorrow_10_15)]))

    def test_weekly(self):
        notif_tool = self.portal.getNotificationTool()

        # disable notifications
        notif_tool.config['enable_daily'] = False
        notif_tool.config['enable_weekly'] = False
        notif_tool.config['enable_monthly'] = False

        # weekly notifications are disabled; send nothing
        monday = datetime(2009, 8, 3, 15, 35)  # monday, 15:35
        notif_tool._cron_heartbeat(monday)
        self.failUnlessEqual(self.fetch_test_newsletters(), set())

        # enable weekly notifications
        notif_tool.config['enable_weekly'] = True

        # no previous timestamp; should send weekly newsletter
        notif_tool._cron_heartbeat(monday)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('weekly', monday - timedelta(weeks=1),
                                   monday)]))

        # again no previous timestamp, but this time after the designated
        # day; should still send newsletter
        del notif_tool.timestamps['weekly']
        friday = datetime(2009, 8, 7, 14, 20)  # friday, 14:20
        notif_tool._cron_heartbeat(friday)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('weekly', friday - timedelta(weeks=1),
                                   friday)]))

        # previous timestamp is too close; should send nothing
        notif_tool._cron_heartbeat(monday + timedelta(weeks=1))
        self.failUnlessEqual(self.fetch_test_newsletters(), set())

        # after next regular time; should send newsletter
        next_friday = friday + timedelta(weeks=1, hours=-3)
        # a bit less than one week
        notif_tool._cron_heartbeat(next_friday)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('weekly', friday, next_friday)]))

    def test_monthly(self):
        notif_tool = self.portal.getNotificationTool()

        # disable notifications
        notif_tool.config['enable_daily'] = False
        notif_tool.config['enable_weekly'] = False
        notif_tool.config['enable_monthly'] = False

        # monthly notifications are disabled; send nothing
        today = datetime(2009, 8, 3, 14, 20)
        notif_tool._cron_heartbeat(today)
        self.failUnlessEqual(self.fetch_test_newsletters(), set())

        # enable monthly notifications
        notif_tool.config['enable_monthly'] = True

        # no previous timestamp; should send monthly newsletter
        notif_tool._cron_heartbeat(today)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('monthly', today - timedelta(days=31),
                                   today)]))

        # again no previous timestamp, but this time after the designated
        # day; should still send newsletter
        del notif_tool.timestamps['monthly']
        this_month = datetime(2009, 8, 28, 14, 20)
        notif_tool._cron_heartbeat(this_month)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('monthly', this_month - timedelta(days=31),
                                   this_month)]))

        # previous timestamp is too close; should send nothing
        notif_tool._cron_heartbeat(today + timedelta(days=30))
        self.failUnlessEqual(self.fetch_test_newsletters(), set())

        # after next regular time; should send newsletter
        one_month_later = this_month + timedelta(days=26)
        # a bit less than one month
        notif_tool._cron_heartbeat(one_month_later)
        self.failUnlessEqual(self.fetch_test_newsletters(),
                             set([('monthly', this_month, one_month_later)]))


class NotificationsUiApiTest(BaseNotificationsTest):
    """ test the API exposed by NotificationTool to UI code """

    def assertRaisesWithMessage(self, exc, msg, *args):
        try:
            args[0](*args[1:])
            self.fail('Should have raised exception "%s"' % str(exc))
        except exc, e:
            self.assertTrue(str(msg) in str(e),
                            'Exception raised but message is wrong: '
                            '%s not in %s' % (repr(msg), repr(str(e))))

    def test_add_account_subscription(self):
        notif_tool = self.portal.getNotificationTool()

        self.add_account_subscription('user1', '', 'instant', 'en')

        self.add_account_subscription('user2', 'fol1', 'weekly', 'en')

        notif_tool.config['enable_instant'] = False
        self.assertRaisesWithMessage(ValueError,
                                     'Subscribing to instant notifications in '
                                     '"Naaya Test Site" not allowed',
                                     self.add_account_subscription, 'user1',
                                     '', 'instant', 'en')
        notif_tool.config['enable_weekly'] = False
        self.assertRaisesWithMessage(ValueError,
                                     'Subscribing to weekly notifications in '
                                     '"fol1" not allowed',
                                     self.add_account_subscription, 'user2',
                                     'fol1', 'weekly', 'en')

        subs = self.walk_site_subscriptions()
        self.assertEqual(len(subs), 2)
        self.assertEqual(subs, set([
            ('user1', '', 'instant', 'en'),
            ('user2', 'fol1', 'weekly', 'en'), ]))

    def test_remove_subscription(self):
        notif_tool = self.portal.getNotificationTool()

        notif_tool.config['enable_weekly'] = True

        notif_tool.add_account_subscription('user1', 'fol1', 'weekly', 'en')
        self.assertEqual(self.walk_site_subscriptions(), set([
            ('user1', 'fol1', 'weekly', 'en'),
        ]))

        notif_tool.remove_account_subscription('user1', 'fol1', 'weekly', 'en')
        self.assertEqual(self.walk_site_subscriptions(), set())

        self.assertRaisesWithMessage(ValueError, 'Subscription not found',
                                     notif_tool.remove_account_subscription,
                                     'user1', 'fol1', 'weekly', 'en')

    def test_allowed_notif_types(self):
        notif_tool = self.portal.getNotificationTool()

        self.assertEqual(list(notif_tool.available_notif_types(location='')),
                         ['instant', 'daily', 'weekly', 'monthly'])

        notif_tool.config['enable_instant'] = False
        self.assertEqual(list(notif_tool.available_notif_types(location='')),
                         ['daily', 'weekly', 'monthly'])

        notif_tool.config['enable_daily'] = False
        self.assertEqual(list(notif_tool.available_notif_types(location='')),
                         ['weekly', 'monthly'])

        notif_tool.config['enable_weekly'] = False
        self.assertEqual(list(notif_tool.available_notif_types(location='')),
                         ['monthly'])

        notif_tool.config['enable_monthly'] = False
        self.assertEqual(list(notif_tool.available_notif_types(location='')),
                         [])

        notif_tool.config['enable_weekly'] = True
        self.assertEqual(list(notif_tool.available_notif_types(
                              location='fol1')),
                         ['weekly'])


class NotificationsUtilsTest(BaseNotificationsTest):
    """ Testing utility functions of NotificationTool """

    def test_get_modified_objects(self):
        action_logger = self.portal.getActionLogger()
        items = dict(action_logger.items())
        for entry_id, log_entry in items.items():  # clean action logger
            del action_logger[entry_id]

        # Create a few log entries
        action_logger.create(type=constants.LOG_TYPES['approved'],
                             path='info')
        action_logger.create(type=constants.LOG_TYPES['created'],
                             path='fol1')
        action_logger.create(type=constants.LOG_TYPES['modified'],
                             path='fol2')
        # Same as above. `get_modified_objects` should return 3 object
        action_logger.create(type=constants.LOG_TYPES['modified'],
                             path='fol2')

        modified_obj_list = list(get_modified_objects(self.portal,
                                 (datetime.utcnow() - timedelta(minutes=1)),
                                 (datetime.utcnow() + timedelta(minutes=1))))

        self.assertEqual(len(modified_obj_list), 3)
