Exemplo n.º 1
0
    def test_bad_scope(self):
        """
        Make sure we can't register a timer on a user scope that
        does not exist
        """

        with self.assertRaises(ValueError):
            publish_timed_notification(msg=self.msg,
                                       send_at=datetime.now(pytz.UTC) -
                                       timedelta(seconds=1),
                                       scope_name='bad-scope',
                                       scope_context={'user_id': 1})
Exemplo n.º 2
0
    def test_bad_scope(self):
        """
        Make sure we can't register a timer on a user scope that
        does not exist
        """

        with self.assertRaises(ValueError):
            publish_timed_notification(
                msg=self.msg,
                send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
                scope_name='bad-scope',
                scope_context={'user_id': 1}
            )
Exemplo n.º 3
0
    def test_cancel_timer(self):
        """
        Make sure we a cancel a timer
        """

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1}
        )

        cancel_timed_notification(timer.name)

        # fetch the timer again from DB
        updated_timer = self.store.get_notification_timer(timer.name)

        # is_active = False
        self.assertFalse(updated_timer.is_active)

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        # should not have been executed
        self.assertIsNone(updated_timer.executed_at)
Exemplo n.º 4
0
    def test_timed_broadcast(self):
        """
        Tests that we can create a timed notification and make sure it gets
        executed with the timer polling
        """

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
            scope_name='list_scope',
            scope_context={'range': 5}
        )

        # assert we start have with no notifications
        for user_id in range(timer.context['distribution_scope']['scope_context']['range']):
            self.assertEqual(self.store.get_num_notifications_for_user(user_id), 0)

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNone(updated_timer.err_msg)

        # assert we now have a notification
        for user_id in range(timer.context['distribution_scope']['scope_context']['range']):
            self.assertEqual(self.store.get_num_notifications_for_user(user_id), 1)
Exemplo n.º 5
0
    def test_erred_timed_notifications(self):
        """
        Tests that we can create a timed notification and make sure it gets
        executed with the timer polling
        """

        # assert we start have with no notifications
        self.assertEqual(self.store.get_num_notifications_for_user(1), 0)

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
            scope_name='user',
            scope_context={}  # missing user_id
        )

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNotNone(updated_timer.err_msg)
        self.assertIsNotNone(updated_timer.results)
        self.assertIsNotNone(updated_timer.results['errors'])

        # should be no notifications
        self.assertEqual(self.store.get_num_notifications_for_user(1), 0)
Exemplo n.º 6
0
    def test_timed_notifications(self):
        """
        Tests that we can create a timed notification and make sure it gets
        executed with the timer polling
        """

        # assert we start have with no notifications
        self.assertEqual(self.store.get_num_notifications_for_user(1), 0)

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
            scope_name='user',
            scope_context={'user_id': 1}
        )

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNone(updated_timer.err_msg)
        self.assertIsNotNone(updated_timer.results)

        # assert we now have a notification due to the timer executing
        self.assertEqual(self.store.get_num_notifications_for_user(1), 1)

        notifications = self.store.get_notifications_for_user(1)
        self.assertEqual(len(notifications), 1)

        read_user_msg = notifications[0]
        self.assertEqual(read_user_msg.msg.payload, self.msg.get_payload())
        self.assertNotIn('extra', read_user_msg.msg.payload)
Exemplo n.º 7
0
    def test_cancel_timer(self):
        """
        Make sure we a cancel a timer
        """

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1}
        )

        cancel_timed_notification(timer.name)

        # fetch the timer again from DB
        updated_timer = self.store.get_notification_timer(timer.name)

        # is_active = False
        self.assertFalse(updated_timer.is_active)

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        # should not have been executed
        self.assertIsNone(updated_timer.executed_at)
Exemplo n.º 8
0
    def test_timed_broadcast(self):
        """
        Tests that we can create a timed notification and make sure it gets
        executed with the timer polling
        """

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
            scope_name='list_scope',
            scope_context={'range': 5}
        )

        # assert we start have with no notifications
        for user_id in range(timer.context['distribution_scope']['scope_context']['range']):
            self.assertEquals(self.store.get_num_notifications_for_user(user_id), 0)

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNone(updated_timer.err_msg)

        # assert we now have a notification
        for user_id in range(timer.context['distribution_scope']['scope_context']['range']):
            self.assertEquals(self.store.get_num_notifications_for_user(user_id), 1)
Exemplo n.º 9
0
    def test_erred_timed_notifications(self):
        """
        Tests that we can create a timed notification and make sure it gets
        executed with the timer polling
        """

        # assert we start have with no notifications
        self.assertEquals(self.store.get_num_notifications_for_user(1), 0)

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
            scope_name='user',
            scope_context={}  # missing user_id
        )

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNotNone(updated_timer.err_msg)
        self.assertIsNotNone(updated_timer.results)
        self.assertIsNotNone(updated_timer.results['errors'])

        # should be no notifications
        self.assertEquals(self.store.get_num_notifications_for_user(1), 0)
Exemplo n.º 10
0
    def test_timed_notifications(self):
        """
        Tests that we can create a timed notification and make sure it gets
        executed with the timer polling
        """

        # assert we start have with no notifications
        self.assertEquals(self.store.get_num_notifications_for_user(1), 0)

        # set up a timer that is due in the past
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(seconds=1),
            scope_name='user',
            scope_context={'user_id': 1}
        )

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNone(updated_timer.err_msg)
        self.assertIsNotNone(updated_timer.results)

        # assert we now have a notification due to the timer executing
        self.assertEquals(self.store.get_num_notifications_for_user(1), 1)

        notifications = self.store.get_notifications_for_user(1)
        self.assertEqual(len(notifications), 1)

        read_user_msg = notifications[0]
        self.assertEqual(read_user_msg.msg.payload, self.msg.get_payload())
        self.assertNotIn('extra', read_user_msg.msg.payload)
Exemplo n.º 11
0
    def test_wait_for_correct_time(self):
        """
        Make sure timers don't fire too early and they can be rescheduled
        """

        # set up a timer that is due in the future
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) + timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1}
        )

        poll_and_execute_timers()

        # fetch the timer again from DB
        updated_timer = self.store.get_notification_timer(timer.name)

        # should not have executed
        self.assertIsNone(updated_timer.executed_at)

        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(days=1),
            scope_name='user',
            scope_context={'user_id': 1},
            timer_name=timer.name
        )

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNone(updated_timer.err_msg)

        # assert we now have a notification due to the timer executing
        self.assertEqual(self.store.get_num_notifications_for_user(1), 1)
Exemplo n.º 12
0
    def test_wait_for_correct_time(self):
        """
        Make sure timers don't fire too early and they can be rescheduled
        """

        # set up a timer that is due in the future
        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) + timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1}
        )

        poll_and_execute_timers()

        # fetch the timer again from DB
        updated_timer = self.store.get_notification_timer(timer.name)

        # should not have executed
        self.assertIsNone(updated_timer.executed_at)

        timer = publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(days=1),
            scope_name='user',
            scope_context={'user_id': 1},
            timer_name=timer.name
        )

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        self.assertIsNotNone(updated_timer.executed_at)
        self.assertIsNone(updated_timer.err_msg)

        # assert we now have a notification due to the timer executing
        self.assertEquals(self.store.get_num_notifications_for_user(1), 1)
Exemplo n.º 13
0
    def test_update_timer_past_due(self):
        """
        Make sure if we register a timer, update it so that it is in the past,
        that the original timer is cancelled
        """

        # set up a timer that is due in the future
        publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) + timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1},
            timer_name='test-timer',
            ignore_if_past_due=True
        )

        # now update it so that it is in the past
        publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1},
            timer_name='test-timer',
            ignore_if_past_due=True
        )

        # fetch the timer from the DB
        timer = self.store.get_notification_timer('test-timer')

        # should not be active, the the update operation should
        # have marked it as cancelled
        self.assertFalse(timer.is_active)

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        # should not have been executed
        self.assertIsNone(updated_timer.executed_at)

        # now, re-edit and put back to the future
        publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) + timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1},
            timer_name='test-timer',
            ignore_if_past_due=True
        )

        # fetch the timer from the DB
        timer = self.store.get_notification_timer('test-timer')

        # should  be active again
        self.assertTrue(timer.is_active)
Exemplo n.º 14
0
    def test_update_timer_past_due(self):
        """
        Make sure if we register a timer, update it so that it is in the past,
        that the original timer is cancelled
        """

        # set up a timer that is due in the future
        publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) + timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1},
            timer_name='test-timer',
            ignore_if_past_due=True
        )

        # now update it so that it is in the past
        publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) - timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1},
            timer_name='test-timer',
            ignore_if_past_due=True
        )

        # fetch the timer from the DB
        timer = self.store.get_notification_timer('test-timer')

        # should not be active, the the update operation should
        # have marked it as cancelled
        self.assertFalse(timer.is_active)

        poll_and_execute_timers()

        # fetch the timer from the DB as it should be updated
        updated_timer = self.store.get_notification_timer(timer.name)

        # should not have been executed
        self.assertIsNone(updated_timer.executed_at)

        # now, re-edit and put back to the future
        publish_timed_notification(
            msg=self.msg,
            send_at=datetime.now(pytz.UTC) + timedelta(days=1),
            scope_name='user',
            scope_context={'range': 1},
            timer_name='test-timer',
            ignore_if_past_due=True
        )

        # fetch the timer from the DB
        timer = self.store.get_notification_timer('test-timer')

        # should  be active again
        self.assertTrue(timer.is_active)
Exemplo n.º 15
0
    def publish_notification(cls, namespace, msg_type_name, payload, parse_channel_ids, send_at=None, timer_name=None):
        """
        Helper class method to hide some of the inner workings of this channel
        This will work with immediate or timer based publishing.

        'namespace' is an instance of NotificationMessage

        'msg_type' is the type name of the NotificationMessage

        'payload' is the raw data dictionary to send over the mobile clients

        'parse_channel_ids' is a list of Parse channel_ids, which are subscription lists,
        not to be confused with edx-notification's NotificationChannels - an unfortunate
        semantic collision.

        'send_at' is a datetime when this notification should be sent. Note that firing of notifications
        is approximate, so it will not fire BEFORE send_at, but there might be a lag, depending
        on how frequent timer polling is configured in a runtime instance.

        'timer_name' can be used in conjunction with 'send_at'. This is to allow for a fixed
        timer identifier in case the timed notification needs to be updated (or deleted)
        """

        try:
            msg_type = get_notification_type(msg_type_name)
        except ItemNotFoundError:
            msg_type = NotificationType(
                name=msg_type_name,
                renderer='edx_notifications.renderers.basic.JsonRenderer'
            )
            register_notification_type(msg_type)

        msg = NotificationMessage(
            namespace=namespace,
            msg_type=msg_type,
            payload=payload
        )

        if not send_at:
            # send immediately
            publish_notification_to_user(
                user_id=_PARSE_SERVICE_USER_ID,
                msg=msg,
                # we want to make sure we always call this channel provider
                preferred_channel=_PARSE_CHANNEL_NAME,
                channel_context={
                    # tunnel through the parse_channel_id through the
                    # channel context
                    'parse_channel_ids': parse_channel_ids,
                }
            )
        else:
            # time-based sending, use a TimedNotification
            publish_timed_notification(
                msg=msg,
                send_at=send_at,
                scope_name='user',
                scope_context={
                    'user_id': _PARSE_SERVICE_USER_ID
                },
                timer_name=timer_name,
                timer_context={
                    # we want to make sure we always call this channel provider
                    'preferred_channel': _PARSE_CHANNEL_NAME,
                    'channel_context': {
                        # tunnel through the parse_channel_id through
                        # through the channel context
                        'parse_channel_ids': parse_channel_ids,
                    }
                }
            )
Exemplo n.º 16
0
    def publish_notification(cls,
                             namespace,
                             msg_type_name,
                             payload,
                             parse_channel_ids,
                             send_at=None,
                             timer_name=None):
        """
        Helper class method to hide some of the inner workings of this channel
        This will work with immediate or timer based publishing.

        'namespace' is an instance of NotificationMessage

        'msg_type' is the type name of the NotificationMessage

        'payload' is the raw data dictionary to send over the mobile clients

        'parse_channel_ids' is a list of Parse channel_ids, which are subscription lists,
        not to be confused with edx-notification's NotificationChannels - an unfortunate
        semantic collision.

        'send_at' is a datetime when this notification should be sent. Note that firing of notifications
        is approximate, so it will not fire BEFORE send_at, but there might be a lag, depending
        on how frequent timer polling is configured in a runtime instance.

        'timer_name' can be used in conjunction with 'send_at'. This is to allow for a fixed
        timer identifier in case the timed notification needs to be updated (or deleted)
        """

        try:
            msg_type = get_notification_type(msg_type_name)
        except ItemNotFoundError:
            msg_type = NotificationType(
                name=msg_type_name,
                renderer='edx_notifications.renderers.basic.JsonRenderer')
            register_notification_type(msg_type)

        msg = NotificationMessage(namespace=namespace,
                                  msg_type=msg_type,
                                  payload=payload)

        if not send_at:
            # send immediately
            publish_notification_to_user(
                user_id=_PARSE_SERVICE_USER_ID,
                msg=msg,
                # we want to make sure we always call this channel provider
                preferred_channel=_PARSE_CHANNEL_NAME,
                channel_context={
                    # tunnel through the parse_channel_id through the
                    # channel context
                    'parse_channel_ids': parse_channel_ids,
                })
        else:
            # time-based sending, use a TimedNotification
            publish_timed_notification(
                msg=msg,
                send_at=send_at,
                scope_name='user',
                scope_context={'user_id': _PARSE_SERVICE_USER_ID},
                timer_name=timer_name,
                timer_context={
                    # we want to make sure we always call this channel provider
                    'preferred_channel': _PARSE_CHANNEL_NAME,
                    'channel_context': {
                        # tunnel through the parse_channel_id through
                        # through the channel context
                        'parse_channel_ids': parse_channel_ids,
                    }
                })