def test_resolver_send(self, mock_ace): current_day, offset, target_day, _ = self._get_dates() with patch.object(self.task, 'apply_async') as mock_apply_async: self.task.enqueue(self.site_config.site, current_day, offset) mock_apply_async.assert_any_call( (self.site_config.site.id, serialize(target_day), offset, 0, None), retry=False, ) mock_apply_async.assert_any_call( (self.site_config.site.id, serialize(target_day), offset, self.task.num_bins - 1, None), retry=False, ) self.assertFalse(mock_ace.send.called)
def test_resolver_send(self, mock_schedule_bin, mock_ace): current_day = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) nudge.ScheduleStartResolver(self.site_config.site, current_day).send(-3) test_day = current_day + datetime.timedelta(days=-3) self.assertFalse(mock_schedule_bin.called) mock_schedule_bin.apply_async.assert_any_call( (self.site_config.site.id, serialize(test_day), -3, 0, [], True, None), retry=False, ) mock_schedule_bin.apply_async.assert_any_call( (self.site_config.site.id, serialize(test_day), -3, tasks.RECURRING_NUDGE_NUM_BINS - 1, [], True, None), retry=False, ) self.assertFalse(mock_ace.send.called)
def test_resolver_send(self, mock_schedule_bin, mock_ace): current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) nudge.ScheduleStartResolver(self.site_config.site, current_time).send(-3) test_time = current_time + datetime.timedelta(days=-3) self.assertFalse(mock_schedule_bin.called) mock_schedule_bin.apply_async.assert_any_call( (self.site_config.site.id, serialize(test_time), -3, 0, [], True, None), retry=False, ) mock_schedule_bin.apply_async.assert_any_call( (self.site_config.site.id, serialize(test_time), -3, tasks.RECURRING_NUDGE_NUM_BINS - 1, [], True, None), retry=False, ) self.assertFalse(mock_ace.send.called)
def test_resolver_send(self, mock_schedule_hour, mock_ace): current_time = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) nudge.ScheduleStartResolver(self.site_config.site, current_time).send(3) test_time = current_time - datetime.timedelta(days=3) self.assertFalse(mock_schedule_hour.called) mock_schedule_hour.apply_async.assert_any_call( (self.site_config.site.id, 3, serialize(test_time), [], True, None), retry=False, ) mock_schedule_hour.apply_async.assert_any_call( (self.site_config.site.id, 3, serialize(test_time + datetime.timedelta(hours=23)), [], True, None), retry=False, ) self.assertFalse(mock_ace.send.called)
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs): """ Updates all course schedules if course hasn't started yet and the updated start date is still in the future. """ upgrade_deadline = _calculate_upgrade_deadline( updated_course_overview.id, content_availability_date=updated_course_overview.start, ) update_course_schedules.apply_async(kwargs=dict( course_id=unicode(updated_course_overview.id), new_start_date_str=date.serialize(updated_course_overview.start), new_upgrade_deadline_str=date.serialize(upgrade_deadline), ), )
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs): # pylint: disable=unused-argument """ Updates all course schedules start and upgrade_deadline dates based off of the new course overview start date. """ upgrade_deadline = _calculate_upgrade_deadline( updated_course_overview.id, content_availability_date=updated_course_overview.start, ) update_course_schedules.apply_async(kwargs=dict( course_id=six.text_type(updated_course_overview.id), new_start_date_str=date.serialize(updated_course_overview.start), new_upgrade_deadline_str=date.serialize(upgrade_deadline), ), )
def create_thread_and_comments(cls): cls.thread = { 'id': cls.discussion_id, 'course_id': six.text_type(cls.course.id), 'created_at': date.serialize(TWO_HOURS_AGO), 'title': 'thread-title', 'user_id': cls.thread_author.id, 'username': cls.thread_author.username, 'commentable_id': 'thread-commentable-id', } cls.comment = { 'id': 'comment', 'body': 'comment-body', 'created_at': date.serialize(ONE_HOUR_AGO), 'thread_id': cls.thread['id'], 'parent_id': None, 'user_id': cls.comment_author.id, 'username': cls.comment_author.username, } cls.comment2 = { 'id': 'comment2', 'body': 'comment2-body', 'created_at': date.serialize(NOW), 'thread_id': cls.thread['id'], 'parent_id': None, 'user_id': cls.comment_author.id, 'username': cls.comment_author.username } cls.subcomment = { 'id': 'subcomment', 'body': 'subcomment-body', 'created_at': date.serialize(NOW), 'thread_id': cls.thread['id'], 'parent_id': cls.comment['id'], 'user_id': cls.comment_author.id, 'username': cls.comment_author.username, } cls.thread['children'] = [cls.comment, cls.comment2] cls.comment['child_count'] = 1 cls.thread2 = { 'id': cls.discussion_id, 'course_id': six.text_type(cls.course.id), 'created_at': date.serialize(TWO_HOURS_AGO), 'title': 'thread-title', 'user_id': cls.thread_author.id, 'username': cls.thread_author.username, 'commentable_id': 'thread-commentable-id-2', }
def create_thread_and_comments(cls): # lint-amnesty, pylint: disable=missing-function-docstring cls.thread = { 'id': cls.discussion_id, 'course_id': str(cls.course.id), 'created_at': date.serialize(TWO_HOURS_AGO), 'title': 'thread-title', 'user_id': cls.thread_author.id, 'username': cls.thread_author.username, 'commentable_id': 'thread-commentable-id', } cls.comment = { 'id': 'comment', 'body': 'comment-body', 'created_at': date.serialize(ONE_HOUR_AGO), 'thread_id': cls.thread['id'], 'parent_id': None, 'user_id': cls.comment_author.id, 'username': cls.comment_author.username, } cls.comment2 = { 'id': 'comment2', 'body': 'comment2-body', 'created_at': date.serialize(NOW), 'thread_id': cls.thread['id'], 'parent_id': None, 'user_id': cls.comment_author.id, 'username': cls.comment_author.username } cls.subcomment = { 'id': 'subcomment', 'body': 'subcomment-body', 'created_at': date.serialize(NOW), 'thread_id': cls.thread['id'], 'parent_id': cls.comment['id'], 'user_id': cls.comment_author.id, 'username': cls.comment_author.username, } cls.thread['children'] = [cls.comment, cls.comment2] cls.comment['child_count'] = 1 cls.thread2 = { 'id': cls.discussion_id, 'course_id': str(cls.course.id), 'created_at': date.serialize(TWO_HOURS_AGO), 'title': 'thread-title', 'user_id': cls.thread_author.id, 'username': cls.thread_author.username, 'commentable_id': 'thread-commentable-id-2', }
def create_thread_and_comments(cls): cls.thread = { 'id': cls.discussion_id, 'course_id': unicode(cls.course.id), 'created_at': date.serialize(TWO_HOURS_AGO), 'title': 'thread-title', 'user_id': cls.thread_author.id, 'username': cls.thread_author.username, 'commentable_id': 'thread-commentable-id', } cls.comment = { 'id': 'comment', 'body': 'comment-body', 'created_at': date.serialize(ONE_HOUR_AGO), 'thread_id': cls.thread['id'], 'parent_id': None, 'user_id': cls.comment_author.id, 'username': cls.comment_author.username, } cls.comment2 = { 'id': 'comment2', 'body': 'comment2-body', 'created_at': date.serialize(NOW), 'thread_id': cls.thread['id'], 'parent_id': None, 'user_id': cls.comment_author.id, 'username': cls.comment_author.username } cls.subcomment = { 'id': 'subcomment', 'body': 'subcomment-body', 'created_at': date.serialize(NOW), 'thread_id': cls.thread['id'], 'parent_id': cls.comment['id'], 'user_id': cls.comment_author.id, 'username': cls.comment_author.username, } cls.thread['children'] = [cls.comment, cls.comment2] cls.comment['child_count'] = 1 cls.thread2 = { 'id': cls.discussion_id, 'course_id': unicode(cls.course.id), 'created_at': date.serialize(TWO_HOURS_AGO), 'title': 'thread-title', 'user_id': cls.thread_author.id, 'username': cls.thread_author.username, 'commentable_id': 'thread-commentable-id-2', }
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): current_date = _get_datetime_beginning_of_day(current_date) for course_key, config in CourseDurationLimitConfig.all_current_course_configs().items(): if not config['enabled'][0]: cls.log_info(u'Course duration limits disabled for course_key %s, skipping', course_key) continue # enqueue_enabled, _ = config[cls.enqueue_config_var] # TODO: Switch over to a model where enqueing is based in CourseDurationLimitConfig enqueue_enabled = waffle.switch_is_active('course_duration_limits.enqueue_enabled') if not enqueue_enabled: cls.log_info(u'Message queuing disabled for course_key %s', course_key) continue target_date = current_date + datetime.timedelta(days=day_offset) for bin_num in range(cls.num_bins): task_args = ( site.id, unicode(course_key), serialize(target_date), day_offset, bin_num, override_recipient_email, ) cls().apply_async( task_args, retry=False, )
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs): """ Updates all course schedules if course hasn't started yet and the updated start date is still in the future. """ upgrade_deadline = _calculate_upgrade_deadline( updated_course_overview.id, content_availability_date=updated_course_overview.start, ) update_course_schedules.apply_async( kwargs=dict( course_id=unicode(updated_course_overview.id), new_start_date_str=date.serialize(updated_course_overview.start), new_upgrade_deadline_str=date.serialize(upgrade_deadline), ), )
def update_schedules_on_course_start_changed(sender, updated_course_overview, previous_start_date, **kwargs): """ Updates all course schedules start and upgrade_deadline dates based off of the new course overview start date. """ upgrade_deadline = _calculate_upgrade_deadline( updated_course_overview.id, content_availability_date=updated_course_overview.start, ) update_course_schedules.apply_async( kwargs=dict( course_id=unicode(updated_course_overview.id), new_start_date_str=date.serialize(updated_course_overview.start), new_upgrade_deadline_str=date.serialize(upgrade_deadline), ), )
def test_resolver_send(self, mock_schedule_bin, mock_ace): current_day = datetime.datetime(2017, 8, 1, tzinfo=pytz.UTC) test_day = current_day + datetime.timedelta(days=2) ScheduleFactory.create(upgrade_deadline=datetime.datetime(2017, 8, 3, 15, 34, 30, tzinfo=pytz.UTC)) reminder.UpgradeReminderResolver(self.site_config.site, current_day).send(2) self.assertFalse(mock_schedule_bin.called) mock_schedule_bin.apply_async.assert_any_call( (self.site_config.site.id, serialize(test_day), 2, 0, [], True, None), retry=False, ) mock_schedule_bin.apply_async.assert_any_call( (self.site_config.site.id, serialize(test_day), 2, tasks.UPGRADE_REMINDER_NUM_BINS - 1, [], True, None), retry=False, ) self.assertFalse(mock_ace.send.called)
def send(self, day, override_recipient_email=None): """ Send a message to all users whose schedule started at ``self.current_date`` - ``day``. """ if not ScheduleConfig.current(self.site).enqueue_recurring_nudge: return try: site_config = SiteConfiguration.objects.get(site_id=self.site.id) org_list = site_config.values.get('course_org_filter', None) exclude_orgs = False if not org_list: not_orgs = set() for other_site_config in SiteConfiguration.objects.all(): not_orgs.update( other_site_config.values.get('course_org_filter', [])) org_list = list(not_orgs) exclude_orgs = True elif not isinstance(org_list, list): org_list = [org_list] except SiteConfiguration.DoesNotExist: org_list = None exclude_orgs = False target_date = self.current_date - datetime.timedelta(days=day) for hour in range(24): target_hour = target_date + datetime.timedelta(hours=hour) recurring_nudge_schedule_hour.apply_async( (self.site.id, day, serialize(target_hour), org_list, exclude_orgs, override_recipient_email), retry=False, )
def test_filter_out_verified_schedules(self): current_day, offset, target_day, upgrade_deadline = self._get_dates() user = UserFactory.create() schedules = [ self._schedule_factory( enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(i)), enrollment__mode=CourseMode.VERIFIED if i in (0, 3) else CourseMode.AUDIT, ) for i in range(5) ] sent_messages = [] with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args[1]) self.task.apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(user), )) messages = [Message.from_string(m) for m in sent_messages] self.assertEqual(len(messages), 1) message = messages[0] self.assertItemsEqual( message.context['course_ids'], [str(schedules[i].enrollment.course.id) for i in (1, 2, 4)] )
def send(self, day_offset, override_recipient_email=None): if not self.is_enqueue_enabled(): self.log_debug('Message queuing disabled for site %s', self.site.domain) return exclude_orgs, org_list = self.get_course_org_filter() target_date = self.current_date + datetime.timedelta(days=day_offset) self.log_debug('Target date = %s', target_date.isoformat()) for bin in range(self.num_bins): task_args = ( self.site.id, serialize(target_date), day_offset, bin, org_list, exclude_orgs, override_recipient_email, ) self.log_debug('Launching task with args = %r', task_args) self.async_send_task.apply_async( task_args, retry=False, )
def test_with_course_data(self, mock_get_current_site): self.highlights_patcher.stop() self.highlights_patcher = None mock_get_current_site.return_value = self.site_config.site course = CourseFactory(highlights_enabled_for_messaging=True, self_paced=True) with self.store.bulk_operations(course.id): ItemFactory.create(parent=course, category='chapter', highlights=[u'highlights']) enrollment = CourseEnrollmentFactory(course_id=course.id, user=self.user, mode=u'audit') self.assertEqual(enrollment.schedule.get_experience_type(), ScheduleExperience.EXPERIENCES.course_updates) _, offset, target_day, _ = self._get_dates( offset=self.expected_offsets[0]) enrollment.schedule.start = target_day enrollment.schedule.save() with patch.object(tasks, 'ace') as mock_ace: self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(enrollment.user), )) self.assertTrue(mock_ace.send.called)
def test_no_course_overview(self, mock_schedule_send): schedule = ScheduleFactory.create(upgrade_deadline=datetime.datetime( 2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), ) schedule.enrollment.course_id = CourseKey.from_string( 'edX/toy/Not_2012_Fall') schedule.enrollment.save() test_time = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC) test_time_str = serialize(test_time) for b in range(tasks.UPGRADE_REMINDER_NUM_BINS): with self.assertNumQueries(NUM_QUERIES_NO_MATCHING_SCHEDULES, table_blacklist=WAFFLE_TABLES): tasks.upgrade_reminder_schedule_bin( self.site_config.site.id, target_day_str=test_time_str, day_offset=2, bin_num=b, org_list=[schedule.enrollment.course.org], ) # There is no database constraint that enforces that enrollment.course_id points # to a valid CourseOverview object. However, in that case, schedules isn't going # to attempt to address it, and will instead simply skip those users. # This happens 'transparently' because django generates an inner-join between # enrollment and course_overview, and thus will skip any rows where course_overview # is null. self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): current_date = _get_datetime_beginning_of_day(current_date) for course_key, config in CourseDurationLimitConfig.all_current_course_configs().items(): if not config['enabled'][0]: cls.log_info(u'Course duration limits disabled for course_key %s, skipping', course_key) continue # enqueue_enabled, _ = config[cls.enqueue_config_var] # TODO: Switch over to a model where enqueing is based in CourseDurationLimitConfig enqueue_enabled = waffle.switch_is_active('course_duration_limits.enqueue_enabled') if not enqueue_enabled: cls.log_info(u'Message queuing disabled for course_key %s', course_key) continue target_date = current_date + datetime.timedelta(days=day_offset) task_args = ( site.id, six.text_type(course_key), serialize(target_date), day_offset, override_recipient_email, ) cls().apply_async( task_args, retry=False, )
def test_upsell(self, enable_config, testcase): DynamicUpgradeDeadlineConfiguration.objects.create( enabled=enable_config) current_day, offset, target_day, _ = self._get_dates() upgrade_deadline = None if testcase.set_deadline: upgrade_deadline = current_day + datetime.timedelta( days=testcase.deadline_offset) schedule = self._schedule_factory(upgrade_deadline=upgrade_deadline) sent_messages = [] with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append( args[1]) self.task.apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(len(sent_messages), 1) found_upsell = self._contains_upsell(sent_messages[0]) expect_upsell = enable_config and testcase.expect_upsell self.assertEqual(found_upsell, expect_upsell)
def test_site_config(self, org_list, exclude_orgs, expected_message_count, mock_schedule_send, mock_ace): filtered_org = 'filtered_org' unfiltered_org = 'unfiltered_org' site1 = SiteFactory.create(domain='foo1.bar', name='foo1.bar') limited_config = SiteConfigurationFactory.create(values={'course_org_filter': [filtered_org]}, site=site1) site2 = SiteFactory.create(domain='foo2.bar', name='foo2.bar') unlimited_config = SiteConfigurationFactory.create(values={'course_org_filter': []}, site=site2) for config in (limited_config, unlimited_config): ScheduleConfigFactory.create(site=config.site) ScheduleFactory.create( start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=filtered_org, ) for _ in range(2): ScheduleFactory.create( start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, ) test_time_str = serialize(datetime.datetime(2017, 8, 2, 17, tzinfo=pytz.UTC)) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( limited_config.site.id, 3, test_time_str, org_list=org_list, exclude_orgs=exclude_orgs, ) self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count) self.assertFalse(mock_ace.send.called)
def test_multiple_enrollments(self, mock_schedule_send, mock_ace): user = UserFactory.create() schedules = [ ScheduleFactory.create( upgrade_deadline=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC), enrollment__user=user, enrollment__course__id=CourseLocator( 'edX', 'toy', 'Course{}'.format(course_num))) for course_num in (1, 2, 3) ] test_time = datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC) test_time_str = serialize(test_time) with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES): tasks.upgrade_reminder_schedule_bin( self.site_config.site.id, target_day_str=test_time_str, day_offset=2, bin_num=user.id % tasks.UPGRADE_REMINDER_NUM_BINS, org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, 3) self.assertFalse(mock_ace.send.called)
def test_filter_out_verified_schedules(self): current_day, offset, target_day, upgrade_deadline = self._get_dates() user = UserFactory.create() schedules = [ self._schedule_factory( enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(i)), enrollment__mode=CourseMode.VERIFIED if i in (0, 3) else CourseMode.AUDIT, ) for i in range(5) ] sent_messages = [] with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append( args[1]) self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(user), )) messages = [Message.from_string(m) for m in sent_messages] self.assertEqual(len(messages), 1) message = messages[0] six.assertCountEqual( self, message.context['course_ids'], [str(schedules[i].enrollment.course.id) for i in (1, 2, 4)])
def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace): schedules = [ ScheduleFactory.create( upgrade_deadline=datetime.datetime(2017, 8, 3, 18, 44, 30, tzinfo=pytz.UTC), enrollment__user=UserFactory.create(), enrollment__course__id=CourseLocator('edX', 'toy', 'Bin')) for _ in range(schedule_count) ] test_time = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC) test_time_str = serialize(test_time) for b in range(tasks.UPGRADE_REMINDER_NUM_BINS): # waffle flag takes an extra query before it is cached with self.assertNumQueries(3 if b == 0 else 2): tasks.upgrade_reminder_schedule_bin( self.site_config.site.id, target_day_str=test_time_str, day_offset=2, bin_num=b, org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count) self.assertFalse(mock_ace.send.called)
def test_no_upsell_button_when_DUDConfiguration_is_off(self): DynamicUpgradeDeadlineConfiguration.objects.create(enabled=False) user = UserFactory.create() course_id = CourseLocator('edX', 'toy', 'Course1') first_day_of_schedule = datetime.datetime.now(pytz.UTC) target_day = first_day_of_schedule target_hour_as_string = serialize(target_day) nudge_day = 3 schedule = ScheduleFactory.create(start=first_day_of_schedule, enrollment__user=user, enrollment__course__id=course_id) schedule.enrollment.course.self_paced = True schedule.enrollment.course.save() bin_task_parameters = [ target_hour_as_string, nudge_day, user, schedule.enrollment.course.org ] sent_messages = self._stub_sender_and_collect_sent_messages(bin_task=tasks.recurring_nudge_schedule_bin, stubbed_send_task=patch.object(tasks, '_recurring_nudge_schedule_send'), bin_task_params=bin_task_parameters) self.assertEqual(len(sent_messages), 1) message_attributes = sent_messages[0][1] self.assertFalse(self._contains_upsell_attribute(message_attributes))
def test_site_config(self, this_org_list, other_org_list, expected_message_count, mock_ace): filtered_org = 'filtered_org' unfiltered_org = 'unfiltered_org' this_config = SiteConfigurationFactory.create(values={'course_org_filter': this_org_list}) other_config = SiteConfigurationFactory.create(values={'course_org_filter': other_org_list}) for config in (this_config, other_config): ScheduleConfigFactory.create(site=config.site) user1 = UserFactory.create(id=self.task.num_bins) user2 = UserFactory.create(id=self.task.num_bins * 2) current_day, offset, target_day, upgrade_deadline = self._get_dates() self._schedule_factory( enrollment__course__org=filtered_org, enrollment__user=user1, ) self._schedule_factory( enrollment__course__org=unfiltered_org, enrollment__user=user1, ) self._schedule_factory( enrollment__course__org=unfiltered_org, enrollment__user=user2, ) with patch.object(self.task, 'async_send_task') as mock_schedule_send: self.task().apply(kwargs=dict( site_id=this_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=0 )) self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count) self.assertFalse(mock_ace.send.called)
def test_multiple_target_schedules(self, mock_ace): user = UserFactory.create() current_day, offset, target_day, upgrade_deadline = self._get_dates() num_courses = 3 for course_index in range(num_courses): self._schedule_factory( enrollment__user=user, enrollment__course__id=CourseKey.from_string( 'edX/toy/course{}'.format(course_index))) # 2 queries per course, one for the course opt out and one for the course modes # one query for course modes for the first schedule if we aren't checking the deadline for each course additional_course_queries = ( num_courses * 2) - 1 if self.queries_deadline_for_each_course else 1 expected_query_count = NUM_QUERIES_FIRST_MATCH + additional_course_queries with self.assertNumQueries(expected_query_count, table_blacklist=WAFFLE_TABLES): with patch.object(self.task, 'async_send_task') as mock_schedule_send: self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(user), )) expected_call_count = 1 if self.consolidates_emails_for_learner else num_courses self.assertEqual(mock_schedule_send.apply_async.call_count, expected_call_count) self.assertFalse(mock_ace.send.called)
def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace): schedules = [ ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 18, 44, 30, tzinfo=pytz.UTC), enrollment__course__id=CourseLocator('edX', 'toy', 'Bin') ) for i in range(schedule_count) ] bins_in_use = frozenset((s.enrollment.user.id % tasks.RECURRING_NUDGE_NUM_BINS) for s in schedules) test_time = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC) test_time_str = serialize(test_time) for b in range(tasks.RECURRING_NUDGE_NUM_BINS): expected_queries = NUM_QUERIES_NO_MATCHING_SCHEDULES if b in bins_in_use: # to fetch course modes for valid schedules expected_queries += NUM_COURSE_MODES_QUERIES with self.assertNumQueries(expected_queries, table_blacklist=WAFFLE_TABLES): tasks.recurring_nudge_schedule_bin( self.site_config.site.id, target_day_str=test_time_str, day_offset=-3, bin_num=b, org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count) self.assertFalse(mock_ace.send.called)
def send(self, day, override_recipient_email=None): """ Send a message to all users whose schedule started at ``self.current_date`` - ``day``. """ if not ScheduleConfig.current(self.site).enqueue_recurring_nudge: return try: site_config = SiteConfiguration.objects.get(site_id=self.site.id) org_list = site_config.values.get('course_org_filter', None) exclude_orgs = False if not org_list: not_orgs = set() for other_site_config in SiteConfiguration.objects.all(): not_orgs.update(other_site_config.values.get('course_org_filter', [])) org_list = list(not_orgs) exclude_orgs = True elif not isinstance(org_list, list): org_list = [org_list] except SiteConfiguration.DoesNotExist: org_list = None exclude_orgs = False target_date = self.current_date - datetime.timedelta(days=day) for hour in range(24): target_hour = target_date + datetime.timedelta(hours=hour) recurring_nudge_schedule_hour.apply_async( (self.site.id, day, serialize(target_hour), org_list, exclude_orgs, override_recipient_email), retry=False, )
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): current_date = resolvers._get_datetime_beginning_of_day(current_date) if not cls.is_enqueue_enabled(site): cls.log_debug('Message queuing disabled for site %s', site.domain) return target_date = current_date + datetime.timedelta(days=day_offset) cls.log_debug('Target date = %s', target_date.isoformat()) for bin in range(cls.num_bins): task_args = ( site.id, serialize(target_date), day_offset, bin, override_recipient_email, ) cls.log_debug('Launching task with args = %r', task_args) cls.apply_async( task_args, retry=False, )
def test_no_course_overview(self): current_day, offset, target_day, upgrade_deadline = self._get_dates() # Don't use CourseEnrollmentFactory since it creates a course overview enrollment = CourseEnrollment.objects.create( course_id=CourseKey.from_string('edX/toy/Not_2012_Fall'), user=UserFactory.create(), ) self._schedule_factory(enrollment=enrollment) with patch.object(self.task, 'async_send_task') as mock_schedule_send: for bin_num in range(self.task().num_bins): self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=bin_num, )) # There is no database constraint that enforces that enrollment.course_id points # to a valid CourseOverview object. However, in that case, schedules isn't going # to attempt to address it, and will instead simply skip those users. # This happens 'transparently' because django generates an inner-join between # enrollment and course_overview, and thus will skip any rows where course_overview # is null. self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): # lint-amnesty, pylint: disable=missing-function-docstring set_code_owner_attribute_from_module(__name__) target_datetime = (current_date - datetime.timedelta(days=day_offset)) if not cls.is_enqueue_enabled(site): cls.log_info(u'Message queuing disabled for site %s', site.domain) return cls.log_info(u'Target date = %s', target_datetime.date().isoformat()) for course_key in CourseOverview.get_all_course_keys(): task_args = ( site.id, serialize( target_datetime ), # Need to leave as a datetime for serialization purposes here str(course_key ), # Needs to be a string for celery to properly process override_recipient_email, ) cls.log_info(u'Launching task with args = %r', task_args) cls.task_instance.apply_async( task_args, retry=False, )
def enqueue(cls, site, current_date, day_offset, override_recipient_email=None): # lint-amnesty, pylint: disable=missing-function-docstring set_code_owner_attribute_from_module(__name__) current_date = resolvers._get_datetime_beginning_of_day(current_date) # lint-amnesty, pylint: disable=protected-access if not cls.is_enqueue_enabled(site): cls.log_info(u'Message queuing disabled for site %s', site.domain) return target_date = current_date + datetime.timedelta(days=day_offset) cls.log_info(u'Target date = %s', target_date.isoformat()) for bin in range(cls.num_bins): # lint-amnesty, pylint: disable=redefined-builtin task_args = ( site.id, serialize(target_date), day_offset, bin, override_recipient_email, ) cls.log_info(u'Launching task with args = %r', task_args) cls.task_instance.apply_async( task_args, retry=False, )
def test_schedule_bin(self, schedule_count, mock_schedule_send, mock_ace): schedules = [ ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 18, 44, 30, tzinfo=pytz.UTC), enrollment__course__id=CourseLocator('edX', 'toy', 'Bin') ) for i in range(schedule_count) ] bins_in_use = frozenset((s.enrollment.user.id % tasks.RECURRING_NUDGE_NUM_BINS) for s in schedules) test_datetime = datetime.datetime(2017, 8, 3, 18, tzinfo=pytz.UTC) test_datetime_str = serialize(test_datetime) for b in range(tasks.RECURRING_NUDGE_NUM_BINS): expected_queries = NUM_QUERIES_NO_MATCHING_SCHEDULES if b in bins_in_use: # to fetch course modes for valid schedules expected_queries += NUM_COURSE_MODES_QUERIES with self.assertNumQueries(expected_queries, table_blacklist=WAFFLE_TABLES): tasks.recurring_nudge_schedule_bin( self.site_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=b, org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count) self.assertFalse(mock_ace.send.called)
def test_no_course_overview(self, mock_schedule_send): schedule = ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), ) schedule.enrollment.course_id = CourseKey.from_string('edX/toy/Not_2012_Fall') schedule.enrollment.save() test_time = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC) test_time_str = serialize(test_time) for b in range(tasks.RECURRING_NUDGE_NUM_BINS): # waffle flag takes an extra query before it is cached with self.assertNumQueries(3 if b == 0 else 2): tasks.recurring_nudge_schedule_bin( self.site_config.site.id, target_day_str=test_time_str, day_offset=-3, bin_num=b, org_list=[schedule.enrollment.course.org], ) # There is no database constraint that enforces that enrollment.course_id points # to a valid CourseOverview object. However, in that case, schedules isn't going # to attempt to address it, and will instead simply skip those users. # This happens 'transparently' because django generates an inner-join between # enrollment and course_overview, and thus will skip any rows where course_overview # is null. self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
def test_multiple_enrollments(self, test_hour, messages_sent, mock_schedule_send, mock_ace): user = UserFactory.create() schedules = [ ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, hour, 44, 30, tzinfo=pytz.UTC), enrollment__user=user, enrollment__course__id=CourseLocator( 'edX', 'toy', 'Hour{}'.format(hour))) for hour in (19, 20, 21) ] test_time_str = serialize( datetime.datetime(2017, 8, 1, test_hour, tzinfo=pytz.UTC)) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( self.site_config.site.id, 3, test_time_str, [schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, messages_sent) self.assertFalse(mock_ace.send.called)
def test_no_course_overview(self, mock_schedule_send): schedule = ScheduleFactory.create(start=datetime.datetime( 2017, 8, 1, 20, 34, 30, tzinfo=pytz.UTC), ) schedule.enrollment.course_id = CourseKey.from_string( 'edX/toy/Not_2012_Fall') schedule.enrollment.save() test_time_str = serialize( datetime.datetime(2017, 8, 1, 20, tzinfo=pytz.UTC)) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( self.site_config.site.id, 3, test_time_str, [schedule.enrollment.course.org], ) # There is no database constraint that enforces that enrollment.course_id points # to a valid CourseOverview object. However, in that case, schedules isn't going # to attempt to address it, and will instead simply skip those users. # This happens 'transparently' because django generates an inner-join between # enrollment and course_overview, and thus will skip any rows where course_overview # is null. self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
def test_course_end(self, has_course_ended): user1 = UserFactory.create(id=self.tested_task.num_bins) current_day, offset, target_day = self._get_dates() schedule = ScheduleFactory.create( start=target_day, upgrade_deadline=target_day, enrollment__course__self_paced=True, enrollment__user=user1, ) schedule.enrollment.course.start = current_day - datetime.timedelta( days=30) end_date_offset = -2 if has_course_ended else 2 schedule.enrollment.course.end = current_day + datetime.timedelta( days=end_date_offset) schedule.enrollment.course.save() with patch.object(self.tested_task, 'async_send_task') as mock_schedule_send: self.tested_task.apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=0, )) if has_course_ended: self.assertFalse(mock_schedule_send.apply_async.called) else: self.assertTrue(mock_schedule_send.apply_async.called)
def test_multiple_enrollments(self, mock_ace): user = UserFactory.create() current_day, offset, target_day = self._get_dates() num_courses = 3 for course_index in range(num_courses): ScheduleFactory.create( start=target_day, upgrade_deadline=target_day, enrollment__course__self_paced=True, enrollment__user=user, enrollment__course__id=CourseKey.from_string( 'edX/toy/course{}'.format(course_index))) course_queries = num_courses if self.has_course_queries else 0 expected_query_count = NUM_QUERIES_FIRST_MATCH + course_queries + NUM_QUERIES_NO_ORG_LIST with self.assertNumQueries(expected_query_count, table_blacklist=WAFFLE_TABLES): with patch.object(self.tested_task, 'async_send_task') as mock_schedule_send: self.tested_task.apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(user), )) self.assertEqual(mock_schedule_send.apply_async.call_count, 1) self.assertFalse(mock_ace.send.called)
def _assert_template_for_offset(self, offset, message_count): current_day, offset, target_day, upgrade_deadline = self._get_dates(offset) user = UserFactory.create() for course_index in range(message_count): self._schedule_factory( offset=offset, enrollment__user=user, enrollment__course__id=CourseKey.from_string('edX/toy/course{}'.format(course_index)) ) patch_policies(self, [StubPolicy([ChannelType.PUSH])]) mock_channel = Mock( channel_type=ChannelType.EMAIL, action_links=[], tracker_image_sources=[], ) channel_map = ChannelMap([ ['sailthru', mock_channel], ]) sent_messages = [] with self.settings(TEMPLATES=self._get_template_overrides()): with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args) num_expected_queries = NUM_QUERIES_FIRST_MATCH if self.queries_deadline_for_each_course: # one query per course for opt-out and one for course modes num_expected_queries += (message_count * 2) - 1 else: num_expected_queries += 1 with self.assertNumQueries(num_expected_queries, table_blacklist=WAFFLE_TABLES): self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(user), )) num_expected_messages = 1 if self.consolidates_emails_for_learner else message_count self.assertEqual(len(sent_messages), num_expected_messages) with self.assertNumQueries(NUM_QUERIES_PER_MESSAGE_DELIVERY): with patch('openedx.core.djangoapps.schedules.tasks.segment.track') as mock_segment_track: with patch('edx_ace.channel.channels', return_value=channel_map): self.deliver_task(*sent_messages[0]) self.assertEqual(mock_segment_track.call_count, 1) self.assertEqual(mock_channel.deliver.call_count, 1) for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls: for template in attr.astuple(email): self.assertNotIn("TEMPLATE WARNING", template) self.assertNotIn("{{", template) self.assertNotIn("}}", template) return mock_channel.deliver.mock_calls
def default(self, o): # pylint: disable=method-hidden if isinstance(o, UUID): return six.text_type(o) elif isinstance(o, date.datetime): return date.serialize(o) elif hasattr(o, u'to_json'): return o.to_json() else: return super(MessageEncoder, self).default(o)
def test_course_without_verified_mode(self, mock_ace): current_day, offset, target_day, upgrade_deadline = self._get_dates() schedule = self._schedule_factory() schedule.enrollment.course.modes.filter(mode_slug=CourseMode.VERIFIED).delete() self.task.apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(mock_ace.send.called, False)
def test_verified_learner(self, is_verified, mock_ace): current_day, offset, target_day, upgrade_deadline = self._get_dates() schedule = self._schedule_factory( enrollment__mode=CourseMode.VERIFIED if is_verified else CourseMode.AUDIT, ) self.task.apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(mock_ace.send.called, not is_verified)
def test_schedule_hour(self, schedule_count, mock_schedule_send, mock_ace): schedules = [ ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 18, 34, 30, tzinfo=pytz.UTC)) for _ in range(schedule_count) ] test_time_str = serialize(datetime.datetime(2017, 8, 1, 18, tzinfo=pytz.UTC)) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( self.site_config.site.id, 3, test_time_str, [schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, schedule_count) self.assertFalse(mock_ace.send.called)
def _send_message_task(self, schedule, offset, target_day): """ Calls the task for sending a message to the given schedule and for the given offset and target_day. Returns the message that would have been sent. """ sent_messages = [] with patch.object(self.task, 'async_send_task') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args[1]) self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(len(sent_messages), 1) return Message.from_string(sent_messages[0])
def test_templates(self, message_count, day): user = UserFactory.create() schedules = [ ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC), enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num)) ) for course_num in range(message_count) ] test_datetime = datetime.datetime(2017, 8, 3, 19, tzinfo=pytz.UTC) test_datetime_str = serialize(test_datetime) patch_policies(self, [StubPolicy([ChannelType.PUSH])]) mock_channel = Mock( name='test_channel', channel_type=ChannelType.EMAIL ) patch_channels(self, [mock_channel]) sent_messages = [] with self.settings(TEMPLATES=self._get_template_overrides()): with patch.object(tasks, '_recurring_nudge_schedule_send') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args) with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES): tasks.recurring_nudge_schedule_bin( self.site_config.site.id, target_day_str=test_datetime_str, day_offset=day, bin_num=self._calculate_bin_for_user(user), org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(len(sent_messages), 1) # Load the site # Check the schedule config with self.assertNumQueries(2): for args in sent_messages: tasks._recurring_nudge_schedule_send(*args) self.assertEqual(mock_channel.deliver.call_count, 1) for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls: for template in attr.astuple(email): self.assertNotIn("TEMPLATE WARNING", template)
def test_multiple_enrollments(self, test_hour, messages_sent, mock_schedule_send, mock_ace): user = UserFactory.create() schedules = [ ScheduleFactory.create( start=datetime.datetime(2017, 8, 1, hour, 44, 30, tzinfo=pytz.UTC), enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Hour{}'.format(hour)) ) for hour in (19, 20, 21) ] test_time_str = serialize(datetime.datetime(2017, 8, 1, test_hour, tzinfo=pytz.UTC)) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( self.site_config.site.id, 3, test_time_str, [schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, messages_sent) self.assertFalse(mock_ace.send.called)
def _check_if_email_sent_for_experience(self, test_config): current_day, offset, target_day, _ = self._get_dates(offset=test_config.offset) kwargs = { 'offset': offset } if test_config.experience is None: kwargs['experience'] = None else: kwargs['experience__experience_type'] = test_config.experience schedule = self._schedule_factory(**kwargs) with patch.object(tasks, 'ace') as mock_ace: self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=self._calculate_bin_for_user(schedule.enrollment.user), )) self.assertEqual(mock_ace.send.called, test_config.email_sent)
def test_send_after_course_end(self, mock_schedule_send): user1 = UserFactory.create(id=tasks.RECURRING_NUDGE_NUM_BINS) schedule = ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), enrollment__user=user1, ) schedule.enrollment.course = CourseOverviewFactory() schedule.enrollment.course.end = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=1) test_datetime = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC) test_datetime_str = serialize(test_datetime) tasks.recurring_nudge_schedule_bin.apply_async( self.site_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=0, org_list=[schedule.enrollment.course.org], ) self.assertFalse(mock_schedule_send.apply_async.called)
def test_course_end(self, has_course_ended): user1 = UserFactory.create(id=self.task.num_bins) current_day, offset, target_day, upgrade_deadline = self._get_dates() end_date_offset = -2 if has_course_ended else 2 self._schedule_factory( enrollment__user=user1, enrollment__course__start=current_day - datetime.timedelta(days=30), enrollment__course__end=current_day + datetime.timedelta(days=end_date_offset) ) with patch.object(self.task, 'async_send_task') as mock_schedule_send: self.task().apply(kwargs=dict( site_id=self.site_config.site.id, target_day_str=serialize(target_day), day_offset=offset, bin_num=0, )) if has_course_ended: self.assertFalse(mock_schedule_send.apply_async.called) else: self.assertTrue(mock_schedule_send.apply_async.called)
def test_templates(self, message_count, day): settings.TEMPLATES[0]['OPTIONS']['string_if_invalid'] = "TEMPLATE WARNING - MISSING VARIABLE [%s]" user = UserFactory.create() schedules = [ ScheduleFactory.create( start=datetime.datetime(2017, 8, 1, 19, 44, 30, tzinfo=pytz.UTC), enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Hour{}'.format(idx)) ) for idx in range(message_count) ] test_time_str = serialize(datetime.datetime(2017, 8, 1, 19, tzinfo=pytz.UTC)) patch_policies(self, [StubPolicy([ChannelType.PUSH])]) mock_channel = Mock( name='test_channel', channel_type=ChannelType.EMAIL ) patch_channels(self, [mock_channel]) sent_messages = [] with patch.object(tasks, '_recurring_nudge_schedule_send') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( self.site_config.site.id, day, test_time_str, [schedules[0].enrollment.course.org], ) self.assertEqual(len(sent_messages), 1) for args in sent_messages: tasks._recurring_nudge_schedule_send(*args) self.assertEqual(mock_channel.deliver.call_count, 1) for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls: for template in attr.astuple(email): self.assertNotIn("TEMPLATE WARNING", template)
def test_no_course_overview(self, mock_schedule_send): schedule = ScheduleFactory.create( start=datetime.datetime(2017, 8, 1, 20, 34, 30, tzinfo=pytz.UTC), ) schedule.enrollment.course_id = CourseKey.from_string('edX/toy/Not_2012_Fall') schedule.enrollment.save() test_time_str = serialize(datetime.datetime(2017, 8, 1, 20, tzinfo=pytz.UTC)) with self.assertNumQueries(2): tasks.recurring_nudge_schedule_hour( self.site_config.site.id, 3, test_time_str, [schedule.enrollment.course.org], ) # There is no database constraint that enforces that enrollment.course_id points # to a valid CourseOverview object. However, in that case, schedules isn't going # to attempt to address it, and will instead simply skip those users. # This happens 'transparently' because django generates an inner-join between # enrollment and course_overview, and thus will skip any rows where course_overview # is null. self.assertEqual(mock_schedule_send.apply_async.call_count, 0)
def test_multiple_enrollments(self, mock_schedule_send, mock_ace): user = UserFactory.create() schedules = [ ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC), enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num)) ) for course_num in (1, 2, 3) ] test_datetime = datetime.datetime(2017, 8, 3, 19, 44, 30, tzinfo=pytz.UTC) test_datetime_str = serialize(test_datetime) with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES): tasks.recurring_nudge_schedule_bin( self.site_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=user.id % tasks.RECURRING_NUDGE_NUM_BINS, org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(mock_schedule_send.apply_async.call_count, 1) self.assertFalse(mock_ace.send.called)
def test_site_config(self, org_list, exclude_orgs, expected_message_count, mock_schedule_send, mock_ace): filtered_org = 'filtered_org' unfiltered_org = 'unfiltered_org' site1 = SiteFactory.create(domain='foo1.bar', name='foo1.bar') limited_config = SiteConfigurationFactory.create(values={'course_org_filter': [filtered_org]}, site=site1) site2 = SiteFactory.create(domain='foo2.bar', name='foo2.bar') unlimited_config = SiteConfigurationFactory.create(values={'course_org_filter': []}, site=site2) for config in (limited_config, unlimited_config): ScheduleConfigFactory.create(site=config.site) user1 = UserFactory.create(id=tasks.RECURRING_NUDGE_NUM_BINS) user2 = UserFactory.create(id=tasks.RECURRING_NUDGE_NUM_BINS * 2) ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=filtered_org, enrollment__user=user1, ) ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, enrollment__user=user1, ) ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, enrollment__user=user2, ) test_datetime = datetime.datetime(2017, 8, 3, 17, tzinfo=pytz.UTC) test_datetime_str = serialize(test_datetime) with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES): tasks.recurring_nudge_schedule_bin( limited_config.site.id, target_day_str=test_datetime_str, day_offset=-3, bin_num=0, org_list=org_list, exclude_orgs=exclude_orgs, ) self.assertEqual(mock_schedule_send.apply_async.call_count, expected_message_count) self.assertFalse(mock_ace.send.called)