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)
Exemplo n.º 2
0
    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)
Exemplo n.º 6
0
    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)
Exemplo n.º 16
0
    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)
Exemplo n.º 17
0
    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)
Exemplo n.º 18
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_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)
Exemplo n.º 19
0
 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)
Exemplo n.º 22
0
    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
Exemplo n.º 23
0
    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)
Exemplo n.º 25
0
    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)
Exemplo n.º 26
0
    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)
Exemplo n.º 27
0
    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)
Exemplo n.º 28
0
    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)
Exemplo n.º 29
0
    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))
Exemplo n.º 31
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.
        """
        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)
Exemplo n.º 32
0
    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())
Exemplo n.º 33
0
    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",
        )
Exemplo n.º 34
0
    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)
Exemplo n.º 35
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)
Exemplo n.º 37
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)
Exemplo n.º 39
0
    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)
Exemplo n.º 40
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.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)
Exemplo n.º 42
0
    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)