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_generate_course_expired_message(self, offsets): now = timezone.now() schedule_offset, course_offset = offsets if schedule_offset is not None: schedule_upgrade_deadline = now + timedelta(days=schedule_offset) else: schedule_upgrade_deadline = None if course_offset is not None: course_upgrade_deadline = now + timedelta(days=course_offset) else: course_upgrade_deadline = None def format_date(date): return strftime_localized(date, u'%b %-d, %Y') enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=course_upgrade_deadline, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=schedule_upgrade_deadline, ) duration_limit_upgrade_deadline = get_user_course_expiration_date(enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) message = generate_course_expired_message(enrollment.user, enrollment.course) self.assertIn(format_date(duration_limit_upgrade_deadline), message) soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline has_upgrade_deadline = course_upgrade_deadline is not None if upgradeable and soft_upgradeable: self.assertIn(format_date(schedule_upgrade_deadline), message) elif upgradeable and has_upgrade_deadline: self.assertIn(format_date(course_upgrade_deadline), message) else: self.assertNotIn("Upgrade by", message)
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_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_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 test_add_entitlement_and_upgrade_audit_enrollment_with_dynamic_deadline(self, mock_get_course_runs): """ Verify that if an entitlement is added for a user, if the user has one upgradeable enrollment that enrollment is upgraded to the mode of the entitlement and linked to the entitlement regardless of dynamic upgrade deadline being set. """ DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) course = CourseFactory.create(self_paced=True) course_uuid = uuid.uuid4() course_mode = CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=now() + timedelta(days=1) ) # Set up Entitlement entitlement_data = self._get_data_set(self.user, str(course_uuid)) mock_get_course_runs.return_value = [{'key': str(course.id)}] # Add an audit course enrollment for user. enrollment = CourseEnrollment.enroll(self.user, course.id, mode=CourseMode.AUDIT) # Set an upgrade schedule so that dynamic upgrade deadlines are used ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=course_mode.expiration_datetime + timedelta(days=-3) ) # The upgrade should complete and ignore the deadline response = self.client.post( self.entitlements_list_url, data=json.dumps(entitlement_data), content_type='application/json', ) assert response.status_code == 201 results = response.data course_entitlement = CourseEntitlement.objects.get( user=self.user, course_uuid=course_uuid ) # Assert that enrollment mode is now verified enrollment_mode = CourseEnrollment.enrollment_mode_for_user(self.user, course.id)[0] assert enrollment_mode == course_entitlement.mode assert course_entitlement.enrollment_course_run == enrollment assert results == CourseEntitlementSerializer(course_entitlement).data
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 setUp(self): ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 15, 44, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 17, 34, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(start=datetime.datetime(2017, 8, 2, 15, 34, 30, tzinfo=pytz.UTC)) site = SiteFactory.create() self.site_config = SiteConfigurationFactory.create(site=site) ScheduleConfigFactory.create(site=self.site_config.site)
def setUp(self): super(TestSendRecurringNudge, self).setUp() ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 15, 44, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 17, 34, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(start=datetime.datetime(2017, 8, 2, 15, 34, 30, tzinfo=pytz.UTC)) site = SiteFactory.create() self.site_config = SiteConfigurationFactory.create(site=site) ScheduleConfigFactory.create(site=self.site_config.site)
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 setUp(self): super(TestSendRecurringNudge, self).setUp() ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 15, 44, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(start=datetime.datetime(2017, 8, 1, 17, 34, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(start=datetime.datetime(2017, 8, 2, 15, 34, 30, tzinfo=pytz.UTC)) site = SiteFactory.create() self.site_config = SiteConfigurationFactory.create(site=site) ScheduleConfigFactory.create(site=self.site_config.site) DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
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_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_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 setUp(self): super(TestUpgradeReminder, self).setUp() ScheduleFactory.create(upgrade_deadline=datetime.datetime( 2017, 8, 1, 15, 44, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(upgrade_deadline=datetime.datetime( 2017, 8, 1, 17, 34, 30, tzinfo=pytz.UTC)) ScheduleFactory.create(upgrade_deadline=datetime.datetime( 2017, 8, 2, 15, 34, 30, tzinfo=pytz.UTC)) site = SiteFactory.create() self.site_config = SiteConfigurationFactory.create(site=site) ScheduleConfigFactory.create(site=self.site_config.site) DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
def test_no_banner_when_masquerading_as_staff(self, role_factory, mock_get_course_run_details): """ When masquerading as a specific expired user, if that user has a staff role the expired course banner will not show up. """ mock_get_course_run_details.return_value = {'weeks_to_complete': 1} if role_factory == GlobalStaffFactory: expired_staff = role_factory.create(password=TEST_PASSWORD) else: expired_staff = role_factory.create(password=TEST_PASSWORD, course_key=self.course.id) ScheduleFactory( start_date=self.THREE_YEARS_AGO, enrollment__mode=CourseMode.AUDIT, enrollment__course_id=self.course.id, enrollment__user=expired_staff ) CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(self.course.id), enabled_as_of=self.course.start, ) staff_user = StaffFactory.create(password=TEST_PASSWORD, course_key=self.course.id) CourseEnrollmentFactory.create( user=staff_user, course_id=self.course.id, mode='audit' ) self.client.login(username=staff_user.username, password='******') self.update_masquerade(username=expired_staff.username) course_home_url = reverse('openedx.course_experience.course_home', args=[six.text_type(self.course.id)]) response = self.client.get(course_home_url, follow=True) self.assertEqual(response.status_code, 200) six.assertCountEqual(self, response.redirect_chain, []) banner_text = 'This learner does not have access to this course. Their access expired on' self.assertNotContains(response, banner_text)
def create_enrollment(self, expired): """ Create an enrollment """ if expired: course = CourseFactory.create(start=self.THREE_YEARS_AGO, mobile_available=True) enrollment = CourseEnrollmentFactory.create(user=self.user, course_id=course.id) enrollment.created = self.THREE_YEARS_AGO + datetime.timedelta( days=1) enrollment.save() ScheduleFactory(enrollment=enrollment) else: course = CourseFactory.create(start=self.LAST_WEEK, mobile_available=True) self.enroll(course.id) add_course_mode(course, mode_slug=CourseMode.AUDIT) add_course_mode(course)
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_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.recurring_nudge_schedule_bin( self.site_config.site.id, target_day_str=test_time_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 _schedule_factory(self, offset=None, **factory_kwargs): _, _, target_day, upgrade_deadline = self._get_dates(offset=offset) factory_kwargs.setdefault('start', target_day) factory_kwargs.setdefault('upgrade_deadline', upgrade_deadline) factory_kwargs.setdefault('enrollment__course__self_paced', True) # Make all schedules in the same course factory_kwargs.setdefault('enrollment__course__run', '2012_Fall') if hasattr(self, 'experience_type'): factory_kwargs.setdefault('experience__experience_type', self.experience_type) schedule = ScheduleFactory(**factory_kwargs) course_id = schedule.enrollment.course_id if course_id not in self._courses_with_verified_modes: CourseModeFactory( course_id=course_id, mode_slug=CourseMode.VERIFIED, expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), ) self._courses_with_verified_modes.add(course_id) return schedule
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_upgrade_deadline_with_schedule(self): """ The property should use either the CourseMode or related Schedule to determine the deadline. """ course = CourseFactory(self_paced=True) CourseModeFactory( course_id=course.id, mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30), ) course_overview = CourseOverview.load_from_module_store(course.id) enrollment = CourseEnrollmentFactory( course_id=course.id, mode=CourseMode.AUDIT, course=course_overview, ) # The schedule's upgrade deadline should be used if a schedule exists DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) schedule = ScheduleFactory(enrollment=enrollment) assert enrollment.upgrade_deadline == schedule.upgrade_deadline
def create_enrollment(self, expired): """ Create an enrollment """ if expired: course = CourseFactory.create(start=self.THREE_YEARS_AGO, mobile_available=True) enrollment = CourseEnrollmentFactory.create( user=self.user, course_id=course.id ) ScheduleFactory( # TODO replace 'start' field with 'start_date' after data migration, # in removing writes from old field step in column renaming release start=self.THREE_YEARS_AGO + datetime.timedelta(days=1), enrollment=enrollment ) else: course = CourseFactory.create(start=self.LAST_WEEK, mobile_available=True) self.enroll(course.id) add_course_mode(course, upgrade_deadline_expired=False)
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() user2 = UserFactory.create() ScheduleFactory.create( start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=filtered_org, enrollment__user=user1, ) ScheduleFactory.create( start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, enrollment__user=user1, ) ScheduleFactory.create( start=datetime.datetime(2017, 8, 2, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, enrollment__user=user2, ) 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, day=3, target_hour_str=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_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() ScheduleFactory.create( upgrade_deadline=upgrade_deadline, start=target_day, enrollment__course__org=filtered_org, enrollment__course__self_paced=True, enrollment__user=user1, ) ScheduleFactory.create( upgrade_deadline=upgrade_deadline, start=target_day, enrollment__course__org=unfiltered_org, enrollment__course__self_paced=True, enrollment__user=user1, ) ScheduleFactory.create( upgrade_deadline=upgrade_deadline, start=target_day, enrollment__course__org=unfiltered_org, enrollment__course__self_paced=True, 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_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__course__id=CourseLocator('edX', 'toy', 'Bin')) for i in range(schedule_count) ] bins_in_use = frozenset( (s.enrollment.user.id % tasks.UPGRADE_REMINDER_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.UPGRADE_REMINDER_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.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_expired_course(self): """ Ensure that a user accessing an expired course sees a redirect to the student dashboard, not a 404. """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime( 2010, 1, 1)) course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]: CourseModeFactory.create(course_id=course.id, mode_slug=mode) # assert that an if an expired audit user tries to access the course they are redirected to the dashboard audit_user = UserFactory(password=self.TEST_PASSWORD) self.client.login(username=audit_user.username, password=self.TEST_PASSWORD) audit_enrollment = CourseEnrollment.enroll(audit_user, course.id, mode=CourseMode.AUDIT) audit_enrollment.created = THREE_YEARS_AGO + timedelta(days=1) audit_enrollment.save() ScheduleFactory(enrollment=audit_enrollment) response = self.client.get(url) expiration_date = strftime_localized( course.start + timedelta(weeks=4) + timedelta(days=1), u'%b %-d, %Y') expected_params = QueryDict(mutable=True) course_name = CourseOverview.get_from_id( course.id).display_name_with_default expected_params[ 'access_response_error'] = u'Access to {run} expired on {expiration_date}'.format( run=course_name, expiration_date=expiration_date) expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode()) self.assertRedirects(response, expected_url)
def test_reset_course_deadlines(self): course = self.courses[0] enrollment = CourseEnrollment.objects.get(course_id=course.id) enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=30) enrollment.schedule.save() post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})} course = self.courses[0] student_schedule = CourseEnrollment.objects.get(course_id=course.id, user=self.user).schedule student_schedule.start_date = timezone.now() - datetime.timedelta(days=30) student_schedule.save() staff = StaffFactory(course_key=course.id) staff_schedule = ScheduleFactory( start_date=timezone.now() - datetime.timedelta(days=30), enrollment__course__id=course.id, enrollment__user=staff, ) self.client.login(username=staff.username, password=TEST_PASSWORD) masquerade_url = reverse( 'masquerade_update', kwargs={ 'course_key_string': six.text_type(course.id), } ) response = self.client.post( masquerade_url, json.dumps({"role": 'student', "group_id": None, "user_name": self.user.username}), "application/json" ) assert response.status_code == 200 post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})} self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict) updated_schedule = Schedule.objects.get(id=student_schedule.id) self.assertEqual(updated_schedule.start_date.date(), datetime.datetime.today().date()) updated_staff_schedule = Schedule.objects.get(id=staff_schedule.id) self.assertEqual(updated_staff_schedule.start_date, staff_schedule.start_date)
def test_user_with_no_upgrade_deadline_is_not_upsold(self): 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, upgrade_deadline=None, enrollment__user=user, enrollment__course__id=course_id) schedule.enrollment.course.self_paced = True schedule.enrollment.course.save() verification_deadline = first_day_of_schedule + datetime.timedelta(days=21) CourseModeFactory( course_id=course_id, mode_slug=CourseMode.VERIFIED, expiration_datetime=verification_deadline ) schedule.upgrade_deadline = verification_deadline 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_user_with_no_upgrade_deadline_is_not_upsold(self): 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, upgrade_deadline=None, enrollment__user=user, enrollment__course__id=course_id) schedule.enrollment.course.self_paced = True schedule.enrollment.course.save() verification_deadline = first_day_of_schedule + datetime.timedelta(days=21) CourseModeFactory( course_id=course_id, mode_slug=CourseMode.VERIFIED, expiration_datetime=verification_deadline ) schedule.upgrade_deadline = verification_deadline 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_content_gating_course_card_changes(self): """ When a course is expired, the links on the course card should be removed. Links will be removed from the course title, course image and button (View Course/Resume Course). The course card should have an access expired message. """ self.override_waffle_switch(True) course = CourseFactory.create(start=self.THREE_YEARS_AGO) enrollment = CourseEnrollmentFactory.create(user=self.user, course_id=course.id) schedule = ScheduleFactory(start=self.THREE_YEARS_AGO, enrollment=enrollment) response = self.client.get(reverse('dashboard')) dashboard_html = self._remove_whitespace_from_html_string( response.content) access_expired_substring = 'Accessexpired' course_link_class = 'course-target-link' self.assertNotIn(course_link_class, dashboard_html) self.assertIn(access_expired_substring, dashboard_html)
def test_reset_course_deadlines_masquerade_generic_student(self): course = self.courses[0] student_schedule = CourseEnrollment.objects.get(course_id=course.id, user=self.user).schedule student_schedule.start_date = timezone.now() - datetime.timedelta(days=30) student_schedule.save() staff = StaffFactory(course_key=course.id) staff_schedule = ScheduleFactory( start_date=timezone.now() - datetime.timedelta(days=30), enrollment__course__id=course.id, enrollment__user=staff, ) self.client.login(username=staff.username, password=TEST_PASSWORD) self.update_masquerade(course=course) post_dict = {'course_id': str(course.id)} self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict) updated_student_schedule = Schedule.objects.get(id=student_schedule.id) self.assertEqual(updated_student_schedule.start_date, student_schedule.start_date) updated_staff_schedule = Schedule.objects.get(id=staff_schedule.id) self.assertEqual(updated_staff_schedule.start_date.date(), datetime.date.today())
def test_course_does_not_expire_for_global_users(self, role_factory): """ There are a number of different roles/users that should not lose access after the expiration date. Ensure that users who should not lose access get a 200 (ok) response when attempting to visit the course after their would be expiration date. """ course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) user = role_factory.create(password=self.TEST_PASSWORD) ScheduleFactory(start_date=THREE_YEARS_AGO, enrollment__mode=CourseMode.AUDIT, enrollment__course_id=course.id, enrollment__user=user) # ensure that the user who has indefinite access self.client.login(username=user.username, password=self.TEST_PASSWORD) response = self.client.get(url) self.assertEqual( response.status_code, 200, "Should not expire access for user", )
def test_no_course_overview(self, mock_schedule_send): schedule = ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), enrollment__user=UserFactory.create(), ) 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): with self.assertNumQueries(NUM_QUERIES_NO_MATCHING_SCHEDULES, 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=[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_content_gating_course_card_changes(self): """ When a course is expired, the links on the course card should be removed. Links will be removed from the course title, course image and button (View Course/Resume Course). The course card should have an access expired message. """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=self.THREE_YEARS_AGO - timedelta(days=30)) self.override_waffle_switch(True) course = CourseFactory.create(start=self.THREE_YEARS_AGO) add_course_mode(course, mode_slug=CourseMode.AUDIT) add_course_mode(course) enrollment = CourseEnrollmentFactory.create( user=self.user, course_id=course.id ) enrollment.created = self.THREE_YEARS_AGO + timedelta(days=1) enrollment.save() # pylint: disable=unused-variable schedule = ScheduleFactory(enrollment=enrollment) response = self.client.get(reverse('dashboard')) dashboard_html = self._remove_whitespace_from_response(response) access_expired_substring = 'Accessexpired' course_link_class = 'course-target-link' self.assertNotIn( course_link_class, dashboard_html ) self.assertIn( access_expired_substring, dashboard_html )
def test_no_course_overview(self, mock_schedule_send): schedule = ScheduleFactory.create( start=datetime.datetime(2017, 8, 3, 20, 34, 30, tzinfo=pytz.UTC), enrollment__user=UserFactory.create(), ) schedule.enrollment.course_id = CourseKey.from_string('edX/toy/Not_2012_Fall') schedule.enrollment.save() test_datetime = datetime.datetime(2017, 8, 3, 20, tzinfo=pytz.UTC) test_datetime_str = serialize(test_datetime) for b in range(tasks.RECURRING_NUDGE_NUM_BINS): with self.assertNumQueries(NUM_QUERIES_NO_MATCHING_SCHEDULES, 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=[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_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_time = datetime.datetime(2017, 8, 3, 17, tzinfo=pytz.UTC) test_time_str = serialize(test_time) with self.assertNumQueries(NUM_QUERIES_WITH_MATCHES, table_blacklist=WAFFLE_TABLES): tasks.recurring_nudge_schedule_bin( limited_config.site.id, target_day_str=test_time_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)
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)
def test_templates(self, message_count, day): DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) now = datetime.datetime.now(pytz.UTC) future_date = now + datetime.timedelta(days=21) user = UserFactory.create() schedules = [ ScheduleFactory.create(upgrade_deadline=future_date, enrollment__user=user, enrollment__course__id=CourseLocator( 'edX', 'toy', 'Course{}'.format(course_num))) for course_num in range(message_count) ] for schedule in schedules: schedule.enrollment.course.self_paced = True schedule.enrollment.course.save() CourseModeFactory(course_id=schedule.enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=future_date) test_time = future_date test_time_str = serialize(test_time) patch_policies(self, [StubPolicy([ChannelType.PUSH])]) mock_channel = Mock(name='test_channel', channel_type=ChannelType.EMAIL) patch_channels(self, [mock_channel]) sent_messages = [] templates_override = deepcopy(settings.TEMPLATES) templates_override[0]['OPTIONS'][ 'string_if_invalid'] = "TEMPLATE WARNING - MISSING VARIABLE [%s]" with self.settings(TEMPLATES=templates_override): with patch.object( tasks, '_upgrade_reminder_schedule_send') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append( args) # we execute one query per course to see if it's opted out of dynamic upgrade deadlines, however, # since we create a new course for each schedule in this test, we expect there to be one per message num_expected_queries = NUM_QUERIES_WITH_MATCHES + NUM_QUERIES_WITH_DEADLINE + message_count with self.assertNumQueries(num_expected_queries, table_blacklist=WAFFLE_TABLES): tasks.upgrade_reminder_schedule_bin( self.site_config.site.id, target_day_str=test_time_str, day_offset=day, bin_num=user.id % tasks.UPGRADE_REMINDER_NUM_BINS, org_list=[schedules[0].enrollment.course.org], ) self.assertEqual(len(sent_messages), message_count) for args in sent_messages: tasks._upgrade_reminder_schedule_send(*args) self.assertEqual(mock_channel.deliver.call_count, message_count) for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls: for template in attr.astuple(email): self.assertNotIn("TEMPLATE WARNING", template)
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.UPGRADE_REMINDER_NUM_BINS) user2 = UserFactory.create(id=tasks.UPGRADE_REMINDER_NUM_BINS * 2) ScheduleFactory.create( upgrade_deadline=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=filtered_org, enrollment__user=user1, ) ScheduleFactory.create( upgrade_deadline=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, enrollment__user=user1, ) ScheduleFactory.create( upgrade_deadline=datetime.datetime(2017, 8, 3, 17, 44, 30, tzinfo=pytz.UTC), enrollment__course__org=unfiltered_org, enrollment__user=user2, ) test_time = datetime.datetime(2017, 8, 3, 17, tzinfo=pytz.UTC) test_time_str = serialize(test_time) with self.assertNumQueries(3): tasks.upgrade_reminder_schedule_bin( limited_config.site.id, target_day_str=test_time_str, day_offset=2, 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)
def test_templates(self, message_count, day): now = datetime.datetime.now(pytz.UTC) future_datetime = now + datetime.timedelta(days=21) user = UserFactory.create() schedules = [ ScheduleFactory.create( upgrade_deadline=future_datetime, enrollment__user=user, enrollment__course__id=CourseLocator('edX', 'toy', 'Course{}'.format(course_num)) ) for course_num in range(message_count) ] for schedule in schedules: schedule.enrollment.course.self_paced = True schedule.enrollment.course.end = future_datetime + datetime.timedelta(days=30) schedule.enrollment.course.save() CourseModeFactory( course_id=schedule.enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=future_datetime ) test_datetime = future_datetime 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, '_upgrade_reminder_schedule_send') as mock_schedule_send: mock_schedule_send.apply_async = lambda args, *_a, **_kw: sent_messages.append(args) # we execute one query per course to see if it's opted out of dynamic upgrade deadlines, however, # since we create a new course for each schedule in this test, we expect there to be one per message num_expected_queries = NUM_QUERIES_WITH_MATCHES + NUM_QUERIES_WITH_DEADLINE + message_count with self.assertNumQueries(num_expected_queries, table_blacklist=WAFFLE_TABLES): tasks.upgrade_reminder_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), message_count) # Load the site (which we query per message sent) # Check the schedule config with self.assertNumQueries(1 + message_count): for args in sent_messages: tasks._upgrade_reminder_schedule_send(*args) self.assertEqual(mock_channel.deliver.call_count, message_count) for (_name, (_msg, email), _kwargs) in mock_channel.deliver.mock_calls: for template in attr.astuple(email): self.assertNotIn("TEMPLATE WARNING", template)
def test_register_course_expired_message(self, language, offsets, mock_messages): now = timezone.now() schedule_offset, course_offset = offsets if schedule_offset is not None: schedule_upgrade_deadline = now + timedelta(days=schedule_offset) else: schedule_upgrade_deadline = None if course_offset is not None: course_upgrade_deadline = now + timedelta(days=course_offset) else: course_upgrade_deadline = None def format_date(date): if language.startswith('es-'): return strftime_localized(date, '%-d de %b. de %Y').lower() else: return strftime_localized(date, '%b. %-d, %Y') patch_lang = patch( 'openedx.features.course_duration_limits.access.get_language', return_value=language) with patch_lang: enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=course_upgrade_deadline, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=schedule_upgrade_deadline, ) request = RequestFactory().get('/courseware') request.user = enrollment.user duration_limit_upgrade_deadline = get_user_course_expiration_date( enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) register_course_expired_message(request, enrollment.course) self.assertEqual(mock_messages.register_info_message.call_count, 1) message = str(mock_messages.register_info_message.call_args[0][1]) self.assertIn(format_date(duration_limit_upgrade_deadline), message) soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline has_upgrade_deadline = course_upgrade_deadline is not None if upgradeable and soft_upgradeable: self.assertIn(format_date(schedule_upgrade_deadline), message) elif upgradeable and has_upgrade_deadline: self.assertIn(format_date(course_upgrade_deadline), message) else: self.assertNotIn("Upgrade by", message)