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})
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} )
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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, } } )
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, } })