示例#1
0
def create_default_notification_preferences():
    """
    This function installs two default system-defined Notification Preferences
    for daily and weekly Digests.
    """
    store_provider = SQLNotificationStoreProvider()

    daily_digest_preference = NotificationPreference(
        name=const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME,
        display_name=_('Receive daily email'),
        display_description=_('This setting will cause a daily digest of all notifications to be sent to your'
                              ' registered email address'),
        default_value=const.NOTIFICATIONS_PREFERENCE_DAILYDIGEST_DEFAULT
    )

    store_provider.save_notification_preference(daily_digest_preference)

    weekly_digest_preference = NotificationPreference(
        name=const.NOTIFICATION_WEEKLY_DIGEST_PREFERENCE_NAME,
        display_name=_('Receive weekly email'),
        display_description=_('This setting will cause a weekly digest of all notifications to be sent to your'
                              ' registered email address'),
        default_value=const.NOTIFICATIONS_PREFERENCE_WEEKLYDIGEST_DEFAULT
    )

    store_provider.save_notification_preference(weekly_digest_preference)
示例#2
0
 def setUp(self):
     """
     Setup the tests values.
     """
     self.provider = SQLNotificationStoreProvider()
     self.test_user_id = 1
     self.notification_type = NotificationType(
         name='foo.bar.baz',
         renderer='foo.renderer',
         renderer_context={'param1': 'value1'},
     )
示例#3
0
class PurgeNotificationsCommandTest(TestCase):
    """
    Test suite for the management command
    """
    def setUp(self):
        """
        Setup the tests values.
        """
        self.provider = SQLNotificationStoreProvider()
        self.test_user_id = 1
        self.notification_type = NotificationType(
            name='foo.bar.baz',
            renderer='foo.renderer',
            renderer_context={'param1': 'value1'},
        )

    def test_purge_command_check(self):
        """
        Invoke the Management Command
        """
        msg_type = self.provider.save_notification_type(self.notification_type)
        msg1 = self.provider.save_notification_message(
            NotificationMessage(namespace='namespace1',
                                msg_type=msg_type,
                                payload={'foo': 'bar'}))

        msg2 = self.provider.save_notification_message(
            NotificationMessage(namespace='namespace1',
                                msg_type=msg_type,
                                payload={'test': 'test'}))

        # now reset the time to 66 days ago
        # in order to save the user notification message in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=66)
        with freeze_time(reset_time):
            self.provider.save_user_notification(
                UserNotification(user_id=self.test_user_id, msg=msg1))

            self.provider.save_user_notification(
                UserNotification(user_id=self.test_user_id, msg=msg2))

        # user notifications count
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id, filters={'namespace': 'namespace1'}), 2)
        # run the management command for purging notifications.
        force_purge.Command().handle()

        # now get the user notification count.
        # count should be 0 at that moment. because
        # all the notifications have been deleted.
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id, filters={'namespace': 'namespace1'}), 0)
def create_default_notification_preferences():
    """
    This function installs two default system-defined Notification Preferences
    for daily and weekly Digests.
    """
    store_provider = SQLNotificationStoreProvider()

    daily_digest_preference = NotificationPreference(
        name=const.NOTIFICATION_DAILY_DIGEST_PREFERENCE_NAME,
        display_name=_('Receive daily email'),
        display_description=_('This setting will cause a daily digest of all notifications to be sent to your'
                              ' registered email address'),
        default_value=const.NOTIFICATIONS_PREFERENCE_DAILYDIGEST_DEFAULT
    )

    store_provider.save_notification_preference(daily_digest_preference)

    weekly_digest_preference = NotificationPreference(
        name=const.NOTIFICATION_WEEKLY_DIGEST_PREFERENCE_NAME,
        display_name=_('Receive weekly email'),
        display_description=_('This setting will cause a weekly digest of all notifications to be sent to your'
                              ' registered email address'),
        default_value=const.NOTIFICATIONS_PREFERENCE_WEEKLYDIGEST_DEFAULT
    )

    store_provider.save_notification_preference(weekly_digest_preference)
 def setUp(self):
     """
     Setup the tests values.
     """
     self.provider = SQLNotificationStoreProvider()
     self.test_user_id = 1
     self.notification_type = NotificationType(
         name='foo.bar.baz',
         renderer='foo.renderer',
         renderer_context={
             'param1': 'value1'
         },
     )
class PurgeNotificationsCommandTest(TestCase):
    """
    Test suite for the management command
    """

    def setUp(self):
        """
        Setup the tests values.
        """
        self.provider = SQLNotificationStoreProvider()
        self.test_user_id = 1
        self.notification_type = NotificationType(
            name='foo.bar.baz',
            renderer='foo.renderer',
            renderer_context={
                'param1': 'value1'
            },
        )

    def test_purge_command_check(self):
        """
        Invoke the Management Command
        """
        msg_type = self.provider.save_notification_type(self.notification_type)
        msg1 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'foo': 'bar'
            }
        ))

        msg2 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'test': 'test'
            }
        ))

        # now reset the time to 66 days ago
        # in order to save the user notification message in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=66)
        with freeze_time(reset_time):
            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg1
            ))

            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg2
            ))

        # user notifications count
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            2
        )
        # run the management command for purging notifications.
        force_purge.Command().handle()

        # now get the user notification count.
        # count should be 0 at that moment. because
        # all the notifications have been deleted.
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            0
        )
示例#7
0
 def setUp(self):
     """
     Setup the test case
     """
     self.provider = SQLNotificationStoreProvider()
     self.test_user_id = 1
示例#8
0
class TestSQLStoreProvider(TestCase):
    """
    This class exercises all of the implementation methods for the
    abstract DataProvider class
    """

    def setUp(self):
        """
        Setup the test case
        """
        self.provider = SQLNotificationStoreProvider()
        self.test_user_id = 1

    def _save_notification_type(self):
        """
        Helper to set up a notification_type
        """

        notification_type = NotificationType(
            name='foo.bar.baz',
            renderer='foo.renderer',
            renderer_context={
                'param1': 'value1'
            },
        )

        result = self.provider.save_notification_type(notification_type)

        return result

    def test_save_notification_type(self):
        """
        Happy path saving (and retrieving) a new message type
        """

        with self.assertNumQueries(3):
            notification_type = self._save_notification_type()

        self.assertIsNotNone(notification_type)

        with self.assertNumQueries(1):
            result = self.provider.get_notification_type(notification_type.name)

        self.assertIsNotNone(result)
        self.assertEqual(result, notification_type)

        with self.assertNumQueries(1):
            result_set = self.provider.get_all_notification_types()

        self.assertEqual(len(result_set), 1)
        self.assertEqual(result_set[0], notification_type)

        # re-getting notification type should pull from cache
        # so there should be no round-trips to SQL
        with self.assertNumQueries(0):
            result = self.provider.get_notification_type(notification_type.name)

        self.assertIsNotNone(result)
        self.assertEqual(result, notification_type)

        # re-save and make sure the cache entry got invalidated
        with self.assertNumQueries(3):
            notification_type = self._save_notification_type()

        # since we invalidated the cached entry on the last save
        # when we re-query we'll hit another SQL round trip
        with self.assertNumQueries(1):
            result = self.provider.get_notification_type(notification_type.name)

        self.assertIsNotNone(result)
        self.assertEqual(result, notification_type)

    def _save_notification_preference(self, name, display_name, display_description=''):
        """
        Helper method to create a new notification_preference
        """
        test_notification_preference = NotificationPreference(
            name=name,
            display_name=display_name,
            display_description=display_description
        )

        with self.assertNumQueries(3):
            test_notification_preference_saved = self.provider.save_notification_preference(test_notification_preference)

        self.assertIsNotNone(test_notification_preference_saved)
        self.assertIsNotNone(test_notification_preference_saved.name)
        return test_notification_preference_saved

    def _save_user_notification_preference(self, preference_name, user_id, value):
        """
        Helper method to create a new user_notification_preference
        """
        notification_preference = self._save_notification_preference(
            name=preference_name,
            display_name='Test Preference'
        )
        test_user_notification_preference = UserNotificationPreferences(
            user_id=user_id,
            preference=notification_preference,
            value=value
        )

        user_notification_preference = self.provider.set_user_preference(test_user_notification_preference)

        self.assertIsNotNone(user_notification_preference)
        self.assertIsNotNone(user_notification_preference.user_id)
        self.assertEqual(user_notification_preference.preference, notification_preference)
        return user_notification_preference

    def _save_new_notification(self, payload='This is a test payload'):
        """
        Helper method to create a new notification
        """

        msg_type = self._save_notification_type()

        msg = NotificationMessage(
            msg_type=msg_type,
            payload={
                'foo': 'bar',
                'one': 1,
                'none': None,
                'datetime': datetime.utcnow(),
                'iso8601-fakeout': '--T::',  # something to throw off the iso8601 parser heuristic
            },
            resolve_links={
                'param1': 'value1'
            },
            object_id='foo-item'
        )

        with self.assertNumQueries(1):
            result = self.provider.save_notification_message(msg)

        self.assertIsNotNone(result)
        self.assertIsNotNone(result.id)
        return result

    def test_save_notification(self):
        """
        Test saving a single notification message
        """

        self._save_new_notification()

    def test_mark_user_notification_read(self):
        """

        """
        msg_type = self._save_notification_type()
        for __ in range(10):
            msg = self.provider.save_notification_message(NotificationMessage(
                namespace='namespace1',
                msg_type=msg_type,
                payload={
                    'foo': 'bar'
                }
            ))

            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg
            ))

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1',
                }
            ),
            10
        )
        self.provider.mark_user_notifications_read(self.test_user_id)

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1',
                    'read': False
                }
            ),
            0
        )

    def test_mark_read_namespaced(self):
        """

        """

        msg_type = self._save_notification_type()

        def _gen_notifications(count, namespace):
            """
            Helper to generate notifications
            """
            for __ in range(count):
                msg = self.provider.save_notification_message(NotificationMessage(
                    namespace=namespace,
                    msg_type=msg_type,
                    payload={
                        'foo': 'bar'
                    }
                ))

                self.provider.save_user_notification(UserNotification(
                    user_id=self.test_user_id,
                    msg=msg
                ))

        _gen_notifications(5, 'namespace1')
        _gen_notifications(5, 'namespace2')

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1',
                }
            ),
            5
        )

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace2',
                }
            ),
            5
        )

        # just mark notifications in namespace1
        # as read
        self.provider.mark_user_notifications_read(
            self.test_user_id,
            filters={
                'namespace': 'namespace1'
            }
        )

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1',
                    'read': False
                }
            ),
            0
        )

        # namespace2's message should still be there
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace2',
                    'read': False
                }
            ),
            5
        )

    def test_update_notification(self):
        """
        Save and then update notification
        """

        msg = self._save_new_notification()
        msg.payload = {
            'updated': True,
        }

        saved_msg = self.provider.save_notification_message(msg)

        self.assertEqual(msg, saved_msg)

    def test_load_notification(self):
        """
        Save and fetch a new notification
        """

        msg = self._save_new_notification()

        with self.assertNumQueries(1):
            fetched_msg = self.provider.get_notification_message_by_id(msg.id)

        self.assertIsNotNone(fetched_msg)
        self.assertEqual(msg.id, fetched_msg.id)
        self.assertEqual(msg.payload, fetched_msg.payload)
        self.assertEqual(msg.msg_type.name, fetched_msg.msg_type.name)
        self.assertEqual(msg.resolve_links, fetched_msg.resolve_links)
        self.assertEqual(msg.object_id, fetched_msg.object_id)

        # by not selecting_related (default True), this will cause another round
        # trip to the database
        with self.assertNumQueries(2):
            fetched_msg = self.provider.get_notification_message_by_id(
                msg.id,
                options={
                    'select_related': False,
                }
            )

        self.assertIsNotNone(fetched_msg)
        self.assertEqual(msg.id, fetched_msg.id)
        self.assertEqual(msg.payload, fetched_msg.payload)
        self.assertEqual(msg.msg_type.name, fetched_msg.msg_type.name)

    def test_load_nonexisting_notification(self):
        """
        Negative testing when trying to load a notification that does not exist
        """

        with self.assertRaises(ItemNotFoundError):
            with self.assertNumQueries(1):
                self.provider.get_notification_message_by_id(0)

    def test_bad_message_msg(self):
        """
        Negative testing when trying to update a msg which does not exist already
        """

        msg = self._save_new_notification()
        with self.assertRaises(ItemNotFoundError):
            msg.id = 9999999
            self.provider.save_notification_message(msg)

    def test_cant_find_notification_type(self):
        """
        Negative test for loading notification type
        """

        with self.assertRaises(ItemNotFoundError):
            self.provider.get_notification_type('non-existing')

    def test_update_notification_type(self):
        """
        Assert that we cannot change a notification type
        """

        notification_type = NotificationType(
            name='foo.bar.baz',
            renderer='foo.renderer',
        )

        with self.assertNumQueries(3):
            self.provider.save_notification_type(notification_type)

        # This should be fine saving again, since nothing is changing

        with self.assertNumQueries(3):
            self.provider.save_notification_type(notification_type)

    def test_get_no_notifications_for_user(self):
        """
        Make sure that get_num_notifications_for_user and get_notifications_for_user
        return 0 and empty set respectively
        """

        with self.assertNumQueries(1):
            self.assertEqual(
                self.provider.get_num_notifications_for_user(self.test_user_id),
                0
            )

        with self.assertNumQueries(1):
            self.assertFalse(
                self.provider.get_notifications_for_user(self.test_user_id)
            )

    @mock.patch('edx_notifications.const.NOTIFICATION_MAX_LIST_SIZE', 1)
    def test_over_limit_counting(self):
        """
        Verifies that our counting operations will work as expected even when
        our count is greater that the NOTIFICATION_MAX_LIST_SIZE which is
        the maximum page size
        """

        self.assertEqual(const.NOTIFICATION_MAX_LIST_SIZE, 1)

        msg_type = self._save_notification_type()

        for __ in range(10):
            msg = self.provider.save_notification_message(NotificationMessage(
                namespace='namespace1',
                msg_type=msg_type,
                payload={
                    'foo': 'bar'
                }
            ))

            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg
            ))

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1',
                }
            ),
            10
        )

    def _setup_user_notifications(self):
        """
        Helper to build out some
        """

        msg_type = self._save_notification_type()

        # set up some notifications

        msg1 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'foo': 'bar',
                'one': 1,
                'none': None,
                'datetime': datetime.utcnow(),
                'iso8601-fakeout': '--T::',  # something to throw off the iso8601 parser heuristic
            }
        ))

        map1 = self.provider.save_user_notification(UserNotification(
            user_id=self.test_user_id,
            msg=msg1
        ))

        msg_type2 = self.provider.save_notification_type(
            NotificationType(
                name='foo.bar.another',
                renderer='foo.renderer',
            )
        )

        msg2 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace2',
            msg_type=msg_type2,
            payload={
                'foo': 'baz',
                'one': 1,
                'none': None,
                'datetime': datetime.utcnow(),
                'iso8601-fakeout': '--T::',  # something to throw off the iso8601 parser heuristic
            }
        ))

        map2 = self.provider.save_user_notification(UserNotification(
            user_id=self.test_user_id,
            msg=msg2
        ))

        return map1, msg1, map2, msg2

    def test_get_notifications_for_user(self):
        """
        Test retrieving notifications for a user
        """

        # set up some notifications

        map1, msg1, map2, msg2 = self._setup_user_notifications()

        # read back and compare the notifications
        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(self.test_user_id)

            # most recent one should be first
            self.assertEqual(notifications[0].msg, msg2)
            self.assertEqual(notifications[1].msg, msg1)

        #
        # test file namespace filtering
        #
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1',
                }
            ),
            1
        )

        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            )

            self.assertEqual(notifications[0].msg, msg1)

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace2'
                }
            ),
            1
        )

        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace2'
                }
            )

            self.assertEqual(notifications[0].msg, msg2)

        #
        # test read filtering, should be none right now
        #
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'read': True,
                    'unread': False
                }
            ),
            0
        )

        # if you ask for both not read and not unread, that should be a ValueError as that
        # combination makes no sense
        with self.assertRaises(ValueError):
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'read': False,
                    'unread': False
                }
            )

        # test for filtering by msg_type
        with self.assertNumQueries(1):
            self.assertEqual(
                self.provider.get_num_notifications_for_user(
                    self.test_user_id,
                    filters={
                        'type_name': msg1.msg_type.name
                    }
                ),
                1
            )

        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                filters={
                    'type_name': msg1.msg_type.name
                }
            )

            self.assertEqual(len(notifications), 1)
            self.assertEqual(notifications[0].msg, msg1)

        # test with msg_type and namespace combos
        with self.assertNumQueries(1):
            self.assertEqual(
                self.provider.get_num_notifications_for_user(
                    self.test_user_id,
                    filters={
                        'namespace': 'does-not-exist',
                        'type_name': msg1.msg_type.name
                    }
                ),
                0
            )

        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'does-not-exist',
                    'type_name': msg1.msg_type.name
                }
            )
            self.assertEqual(len(notifications), 0)

        #
        # test start_date and end_date filtering.
        #

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'start_date': msg1.created,
                    'end_date': msg2.created + timedelta(days=1)
                }
            ),
            2
        )

        # filters by end_date
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'start_date': msg1.created + timedelta(days=1)
                }
            ),
            0
        )

        # filters by end_date
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'end_date': msg2.created + timedelta(days=1)
                }
            ),
            2
        )

        notifications = self.provider.get_notifications_for_user(
            self.test_user_id,
            filters={
                'start_date': msg1.created,
                'end_date': msg2.created + timedelta(days=1)
            }
        )
        self.assertEqual(len(notifications), 2)
        self.assertEqual(notifications[0].msg, msg2)
        self.assertEqual(notifications[1].msg, msg1)

        # update the created time for msg2 data object.
        user_msg = SQLUserNotification.objects.get(msg_id=msg2.id)
        user_msg.created = msg2.created - timedelta(days=1)
        user_msg.save()

        # now the msg 2 should not be in the filtered_list
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'start_date': msg1.created,
                    'end_date': datetime.now(pytz.UTC) + timedelta(days=1)
                }
            ),
            1
        )

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'start_date': msg1.created
                }
            ),
            1
        )

        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'end_date': user_msg.created - timedelta(days=1)
                }
            ),
            0
        )

    def test_bad_user_msg_update(self):
        """
        Test exception when trying to update a non-existing
        ID
        """

        # set up some notifications
        map1, __, __, __ = self._setup_user_notifications()

        with self.assertRaises(ItemNotFoundError):
            map1.id = -1
            self.provider.save_user_notification(map1)

    def test_read_unread_flags(self):
        """
        Test read/unread falgs
        """

        # set up some notifications
        map1, msg1, map2, msg2 = self._setup_user_notifications()

        # mark one as read
        map1.read_at = datetime.utcnow()

        # Not sure I understand why Django ORM is making 3 calls here
        # seems like it should just be 2. I might investigate more
        # later, but writes are less of a priority as this
        # will be read intensive
        with self.assertNumQueries(3):
            self.provider.save_user_notification(map1)

        # there should be one read notification
        with self.assertNumQueries(1):
            self.assertEqual(
                self.provider.get_num_notifications_for_user(
                    self.test_user_id,
                    filters={
                        'read': True,
                        'unread': False
                    }
                ),
                1
            )

        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                filters={
                    'read': True,
                    'unread': False
                }
            )
            self.assertEqual(notifications[0].msg, msg1)

        # there should be one unread notification
        with self.assertNumQueries(1):
            self.assertEqual(
                self.provider.get_num_notifications_for_user(
                    self.test_user_id,
                    filters={
                        'read': False,
                        'unread': True
                    }
                ),
                1
            )

        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                filters={
                    'read': False,
                    'unread': True
                }
            )
            self.assertEqual(notifications[0].msg, msg2)

    def test_get_notifications_paging(self):
        """
        Test retrieving notifications for a user
        """

        # make sure we can't pass along a huge limit size
        with self.assertRaises(ValueError):
            self.provider.get_notifications_for_user(
                self.test_user_id,
                options={
                    'limit': const.NOTIFICATION_MAX_LIST_SIZE + 1
                }
            )

        # set up some notifications
        map1, msg1, map2, msg2 = self._setup_user_notifications()

        # test limit, we should only get the first one
        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                options={
                    'limit': 1,
                }
            )

            self.assertEqual(len(notifications), 1)
            # most recent one should be first
            self.assertEqual(notifications[0].msg, msg2)

        # test limit with offset, we should only get the 2nd one
        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                options={
                    'limit': 1,
                    'offset': 1,
                }
            )

            self.assertEqual(len(notifications), 1)
            # most recent one should be first, so msg1 should be 2nd
            self.assertEqual(notifications[0].msg, msg1)

        # test that limit should be able to exceed bounds
        with self.assertNumQueries(1):
            notifications = self.provider.get_notifications_for_user(
                self.test_user_id,
                options={
                    'offset': 1,
                    'limit': 2,
                }
            )

            self.assertEqual(len(notifications), 1)
            # most recent one should be first, so msg1 should be 2nd
            self.assertEqual(notifications[0].msg, msg1)

    def test_bulk_user_notification_create(self):
        """
        Test that we can create new UserNotifications using an optimized
        code path to minimize round trips to the database
        """

        msg_type = self._save_notification_type()

        # set up some notifications

        msg = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'foo': 'bar',
                'one': 1,
                'none': None,
                'datetime': datetime.utcnow(),
                'iso8601-fakeout': '--T::',  # something to throw off the iso8601 parser heuristic
            }
        ))

        user_msgs = []
        for user_id in range(const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE):
            user_msgs.append(
                UserNotification(user_id=user_id, msg=msg)
            )

        # assert that this only takes one round-trip to the database
        # to insert all of them
        with self.assertNumQueries(1):
            self.provider.bulk_create_user_notification(user_msgs)

        # now make sure that we can query each one
        for user_id in range(const.NOTIFICATION_BULK_PUBLISH_CHUNK_SIZE):
            notifications = self.provider.get_notifications_for_user(user_id)

            self.assertEqual(len(notifications), 1)
            self.assertEqual(notifications[0].msg, msg)

        # now test if we send in a size too large that an exception is raised
        user_msgs.append(
            UserNotification(user_id=user_id, msg=msg)
        )

        with self.assertRaises(BulkOperationTooLarge):
            self.provider.bulk_create_user_notification(user_msgs)

    def test_save_timer(self):
        """
        Save, update, and get a simple timer object
        """

        timer = NotificationCallbackTimer(
            name='timer1',
            callback_at=datetime.now(pytz.UTC) - timedelta(0, 1),
            class_name='foo.bar',
            context={
                'one': 'two'
            },
            is_active=True,
            periodicity_min=120,
        )
        timer_saved = self.provider.save_notification_timer(timer)

        timer_executed = NotificationCallbackTimer(
            name='timer2',
            callback_at=datetime.now(pytz.UTC) - timedelta(0, 2),
            class_name='foo.bar',
            context={
                'one': 'two'
            },
            is_active=True,
            periodicity_min=120,
            executed_at=datetime.now(pytz.UTC),
            err_msg='ooops',
        )
        timer_executed_saved = self.provider.save_notification_timer(timer_executed)

        timer_read = self.provider.get_notification_timer(timer_saved.name)
        self.assertEqual(timer_saved, timer_read)
        self.assertTrue(isinstance(timer_read.context, dict))

        timer_executed_read = self.provider.get_notification_timer(timer_executed_saved.name)
        self.assertEqual(timer_executed_saved, timer_executed_read)

        timers_not_executed = self.provider.get_all_active_timers()
        self.assertEqual(len(timers_not_executed), 1)

        timers_incl_executed = self.provider.get_all_active_timers(include_executed=True)
        self.assertEqual(len(timers_incl_executed), 2)

    def test_save_update_time(self):
        """
        Verify the update case of saving a timer
        """

        timer = NotificationCallbackTimer(
            name='timer1',
            callback_at=datetime.now(pytz.UTC) - timedelta(0, 1),
            class_name='foo.bar',
            context={
                'one': 'two'
            },
            is_active=True,
            periodicity_min=120,
        )
        timer_saved = self.provider.save_notification_timer(timer)

        timer_saved.executed_at = datetime.now(pytz.UTC)
        timer_saved.err_msg = "Ooops"

        timer_saved_twice = self.provider.save_notification_timer(timer)

        timer_read = self.provider.get_notification_timer(timer_saved_twice.id)
        self.assertEqual(timer_saved_twice, timer_read)

    def test_update_is_active_timer(self):
        """
        Verify that we can change the is_active flag on
        a timer
        """

        timer = NotificationCallbackTimer(
            name='timer1',
            callback_at=datetime.now(pytz.UTC) - timedelta(0, 1),
            class_name='foo.bar',
            context={
                'one': 'two'
            },
            is_active=True,
            periodicity_min=120,
        )
        timer_saved = self.provider.save_notification_timer(timer)

        timer_read = self.provider.get_notification_timer(timer_saved.id)

        timer_read.is_active = False

        timer_saved_twice = self.provider.save_notification_timer(timer_read)

        timer_read = self.provider.get_notification_timer(timer_saved_twice.id)
        self.assertEqual(timer_saved_twice, timer_read)

    def test_get_nonexisting_timer(self):
        """
        Verifies that an exception is thrown when trying to load a non-existing
        timer_id
        """

        with self.assertRaises(ItemNotFoundError):
            self.provider.get_notification_timer('foo')

    def test_save_notification_preference(self):
        """
        test save notification preference in the store provide.
        """
        notification_preference = self._save_notification_preference(
            name='test_notification_preference',
            display_name="Test Preference",
            display_description="This is the test preference"
        )

        with self.assertNumQueries(1):
            notification_preference_read = self.provider.get_notification_preference(
                notification_preference.name
            )
        self.assertEqual(notification_preference, notification_preference_read)

    def test_save_update_notification_preference(self):
        """
        test update the saved notification preference
        """
        with self.assertNumQueries(3):
            notification_preference = self._save_notification_preference(
                name='test_notification_preference',
                display_name="Test Preference",
                display_description="This is the test preference"
            )

        notification_preference.display_name = 'Updated Test Preference'
        with self.assertNumQueries(3):
            notification_preference_saved_twice = self.provider.save_notification_preference(notification_preference)

        with self.assertNumQueries(1):
            notification_preference_read = self.provider.get_notification_preference(
                notification_preference_saved_twice.name
            )
        self.assertEqual(notification_preference_saved_twice, notification_preference_read)

    def test_get_all_notification_preferences(self):
        """
        test to get all the user notification preferences.
        """
        test_notification_preference = self._save_notification_preference(
            name='test_notification_preference,',
            display_name="Test Preference",
            display_description="This is the test preference"
        )

        test2_notification_preference = self._save_notification_preference(
            name='notification_preference2',
            display_name="Test Preference 2",
            display_description="This is the second test preference"
        )

        with self.assertNumQueries(1):
            all_notification_preferences = self.provider.get_all_notification_preferences()

        self.assertEqual(len(all_notification_preferences), 2)
        self.assertEqual(all_notification_preferences[0], test_notification_preference)
        self.assertEqual(all_notification_preferences[1], test2_notification_preference)

    def test_get_non_existing_notification_preference(self):
        """
        Verifies that an exception is thrown when trying to load a non-existing
        notification_preference
        """

        with self.assertRaises(ItemNotFoundError):
            self.provider.get_notification_preference('foo')

    def test_get_saved_user_preference(self):
        """
        test to get the saved the user preference.
        """
        user_notification_preference = self._save_user_notification_preference(
            preference_name='Test Preference 1',
            user_id=1,
            value='User Preference 1'
        )
        with self.assertNumQueries(2):
            read_user_notification_preference = self.provider.get_user_preference(
                user_notification_preference.user_id,
                user_notification_preference.preference.name
            )
        self.assertEqual(user_notification_preference, read_user_notification_preference)

    def test_update_saved_user_preference(self):
        """
        test to get the updated user preference
        """
        user_notification_preferences = self._save_user_notification_preference(
            preference_name='Test Preference 1',
            user_id=1,
            value='User Preference 1')
        user_notification_preferences.value = 'Updated user Preference'
        user_notification_preferences.user_id = 2

        with self.assertNumQueries(2):
            updated_user_preferences = self.provider.set_user_preference(user_notification_preferences)

        with self.assertNumQueries(2):
            read_updated_user_notification_preferences = self.provider.get_user_preference(
                updated_user_preferences.user_id,
                updated_user_preferences.preference.name
            )
        self.assertEqual(user_notification_preferences, read_updated_user_notification_preferences)

    def test_get_non_existing_user_notification_preferences(self):
        """
        Verifies that an exception is thrown when trying to load a non-existing
        user_notification_preference
        """

        with self.assertRaises(ItemNotFoundError):
            self.provider.get_user_preference(4, 'foo')

    def test_get_all_user_preferences(self):
        """
        test to get all the preferences for the users.
        """
        user_id = 1
        for i in range(5):
            self._save_user_notification_preference(
                preference_name='test_preference{i}'.format(i=i + 1),
                user_id=user_id,
                value='User Preferences'
            )
        with self.assertNumQueries(6):
            result = self.provider.get_all_user_preferences_for_user(user_id)
        self.assertEqual(len(result), 5)

    def test_get_all_user_preferences_with_name(self):
        """
        Test all user preferences with name
        """
        user_preference1 = self._save_user_notification_preference(
            preference_name='test_preference',
            user_id=1,
            value='User-Preferences'
        )

        user_preference2 = self._save_user_notification_preference(
            preference_name='test_preference',
            user_id=2,
            value='User-Preferences'
        )

        # make sure we can't pass along a huge limit size
        with self.assertNumQueries(0):
            with self.assertRaises(ValueError):
                self.provider.get_all_user_preferences_with_name(
                    name='test_preference',
                    value='User-Preferences',
                    size=const.USER_PREFERENCE_MAX_LIST_SIZE + 1
                )

        # test limit, we should only get the first one
        with self.assertNumQueries(2):
            user_preferences = self.provider.get_all_user_preferences_with_name(
                name='test_preference',
                value='User-Preferences',
                size=1
            )
            self.assertEqual(len(user_preferences), 1)
            # most recent one should be first
            self.assertEqual(user_preferences[0], user_preference1)

        # test limit with offset, we should only get the 2nd one
        with self.assertNumQueries(2):
            user_preferences = self.provider.get_all_user_preferences_with_name(
                name='test_preference',
                value='User-Preferences',
                size=1,
                offset=1,
            )

            self.assertEqual(len(user_preferences), 1)
            # most recent one should be first, so user_preferences should be 2nd
            self.assertEqual(user_preferences[0], user_preference2)

        # test that limit should be able to exceed bounds
        with self.assertNumQueries(2):
            user_preferences = self.provider.get_all_user_preferences_with_name(
                name='test_preference',
                value='User-Preferences',
                size=2,
                offset=1,
            )

            self.assertEqual(len(user_preferences), 1)
            # most recent one should be first, so user_preferences should be 2nd
            self.assertEqual(user_preferences[0], user_preference2)

    def test_purge_expired_unread_notifications(self):
        """
        Test to check for the older unread messages.
        If exists delete those messages from the database.
        """
        msg_type = self._save_notification_type()
        msg1 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'foo': 'bar'
            }
        ))

        msg2 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'test': 'test'
            }
        ))

        # now reset the time to 10 days ago
        # in order to save the user notification message in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=10)
        with freeze_time(reset_time):
            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg1
            ))

        # now reset the time to 2 days ago
        # in order to save the user notification message in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=2)
        with freeze_time(reset_time):
            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg2
            ))

        # user notifications count
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            2
        )

        # purge older unread messages.
        purge_unread_messages_older_than = datetime.now(pytz.UTC) - timedelta(days=6)
        self.provider.purge_expired_notifications(purge_unread_messages_older_than=purge_unread_messages_older_than)

        # now get the user notification count.
        # count should be 1 at that moment. because
        # only 1 notification has been deleted.
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            1
        )

    def test_purge_expired_read_notifications(self):
        """
        Test to check for the older read messages.
        If exists delete those messages from the database.
        """

        msg_type = self._save_notification_type()
        msg1 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'foo': 'bar'
            }
        ))

        msg2 = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'test': 'test'
            }
        ))

        # now reset the time to 10 days ago
        # in order to save the user notification messages in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=10)
        with freeze_time(reset_time):
            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg1
            ))

            # mark the user notification as read.
            self.provider.mark_user_notifications_read(self.test_user_id)

        # now reset the time to 2 days ago
        # in order to save the user notification messages in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=2)
        with freeze_time(reset_time):
            self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg2
            ))

            # mark the user notification as read.
            self.provider.mark_user_notifications_read(self.test_user_id)

        # user notifications count
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            2
        )

        # purge older read messages.
        purge_older_read_messages = datetime.now(pytz.UTC) - timedelta(days=6)
        self.provider.purge_expired_notifications(purge_read_messages_older_than=purge_older_read_messages)

        # now get the user notification count.
        # count should be 1 at that moment. because
        # only 1 notification has been deleted.
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            1
        )

    @mock.patch('edx_notifications.const.NOTIFICATION_ARCHIVE_ENABLED', True)
    def test_archive_the_purged_notifications(self):
        """
        Test to check that deleting user notification should be archive.
        """
        msg_type = self._save_notification_type()
        msg = self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={
                'test': 'test'
            }
        ))

        # now reset the time to 7 days ago
        # in order to save the user notification message in the past.
        reset_time = datetime.now(pytz.UTC) - timedelta(days=7)
        with freeze_time(reset_time):
            user_notification = self.provider.save_user_notification(UserNotification(
                user_id=self.test_user_id,
                msg=msg
            ))

            # mark the user notification as read.
            self.provider.mark_user_notifications_read(self.test_user_id)

        self.assertEqual(SQLUserNotificationArchive.objects.all().count(), 0)

        # purge older read messages.
        purge_older_read_messages = datetime.now(pytz.UTC) - timedelta(days=6)
        self.provider.purge_expired_notifications(purge_read_messages_older_than=purge_older_read_messages)

        # now get the user notification count.
        # count should be 0 at that moment. because
        # 1 notification has been deleted.
        self.assertEqual(
            self.provider.get_num_notifications_for_user(
                self.test_user_id,
                filters={
                    'namespace': 'namespace1'
                }
            ),
            0
        )
        # Notification should be archived
        # count should be increased by 1.
        self.assertEqual(SQLUserNotificationArchive.objects.all().count(), 1)

        archived_notification = SQLUserNotificationArchive.objects.all()[0]
        self.assertEqual(archived_notification.msg_id, user_notification.msg.id)
        self.assertEqual(archived_notification.user_id, user_notification.user_id)

    def test_get_all_namespaces(self):
        """
        Verify that we can get a list of all namespaces
        """

        msg_type = self._save_notification_type()
        self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={'foo': 'bar'}
        ))

        self.provider.save_notification_message(NotificationMessage(
            namespace='namespace2',
            msg_type=msg_type,
            payload={'foo': 'bar'}
        ))

        self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={'foo': 'bar'}
        ))

        self.provider.save_notification_message(NotificationMessage(
            namespace='namespace3',
            msg_type=msg_type,
            payload={'foo': 'bar'}
        ))

        self.provider.save_notification_message(NotificationMessage(
            namespace='namespace1',
            msg_type=msg_type,
            payload={'foo': 'bar'}
        ))

        namespaces = self.provider.get_all_namespaces()

        self.assertEqual(len(namespaces), 3)
        self.assertIn('namespace1', namespaces)
        self.assertIn('namespace2', namespaces)
        self.assertIn('namespace3', namespaces)