def test_resolve_course_enrollments(self):
        """
        Test that the CourseEnrollmentsScopeResolver actually returns all enrollments
        """

        test_user_1 = UserFactory.create(password='******')
        CourseEnrollmentFactory(user=test_user_1, course_id=self.course.id)
        test_user_2 = UserFactory.create(password='******')
        CourseEnrollmentFactory(user=test_user_2, course_id=self.course.id)
        test_user_3 = UserFactory.create(password='******')
        enrollment = CourseEnrollmentFactory(user=test_user_3, course_id=self.course.id)

        # unenroll #3

        enrollment.is_active = False
        enrollment.save()

        resolver = CourseEnrollmentsScopeResolver()

        user_ids = resolver.resolve('course_enrollments', {'course_id': self.course.id}, None)

        # should have first two, but the third should not be present

        self.assertTrue(test_user_1.id in user_ids)
        self.assertTrue(test_user_2.id in user_ids)

        self.assertFalse(test_user_3.id in user_ids)
    def test_namespace_scope(self):
        """
        Make sure that we handle resolving namespaces correctly
        """

        test_user_1 = UserFactory.create(password='******',
                                         email='*****@*****.**',
                                         first_name='user',
                                         last_name='one')
        CourseEnrollmentFactory(user=test_user_1, course_id=self.course.id)

        test_user_2 = UserFactory.create(password='******',
                                         email='*****@*****.**',
                                         first_name='John',
                                         last_name='Smith')
        CourseEnrollmentFactory(user=test_user_2, course_id=self.course.id)

        test_user_3 = UserFactory.create(password='******')
        enrollment = CourseEnrollmentFactory(user=test_user_3,
                                             course_id=self.course.id)

        # unenroll #3

        enrollment.is_active = False
        enrollment.save()

        resolver = NamespaceEnrollmentsScopeResolver()

        users = resolver.resolve(
            'namespace_scope', {
                'namespace': self.course.id,
                'fields': {
                    'id': True,
                    'email': True,
                    'first_name': True,
                    'last_name': True,
                }
            }, None)

        _users = [user for user in users]

        self.assertEqual(len(_users), 2)

        self.assertIn('id', _users[0])
        self.assertIn('email', _users[0])
        self.assertIn('first_name', _users[0])
        self.assertIn('last_name', _users[0])
        self.assertEquals(_users[0]['id'], test_user_1.id)
        self.assertEquals(_users[0]['email'], test_user_1.email)
        self.assertEquals(_users[0]['first_name'], test_user_1.first_name)
        self.assertEquals(_users[0]['last_name'], test_user_1.last_name)

        self.assertIn('id', _users[1])
        self.assertIn('email', _users[1])
        self.assertIn('first_name', _users[1])
        self.assertIn('last_name', _users[1])
        self.assertEquals(_users[1]['id'], test_user_2.id)
        self.assertEquals(_users[1]['email'], test_user_2.email)
        self.assertEquals(_users[1]['first_name'], test_user_2.first_name)
        self.assertEquals(_users[1]['last_name'], test_user_2.last_name)
    def test_resolve_course_enrollments(self):
        """
        Test that the CourseEnrollmentsScopeResolver actually returns all enrollments
        """

        test_user_1 = UserFactory.create(password='******')
        CourseEnrollmentFactory(user=test_user_1, course_id=self.course.id)
        test_user_2 = UserFactory.create(password='******')
        CourseEnrollmentFactory(user=test_user_2, course_id=self.course.id)
        test_user_3 = UserFactory.create(password='******')
        enrollment = CourseEnrollmentFactory(user=test_user_3,
                                             course_id=self.course.id)

        # unenroll #3

        enrollment.is_active = False
        enrollment.save()

        resolver = CourseEnrollmentsScopeResolver()

        user_ids = resolver.resolve('course_enrollments',
                                    {'course_id': self.course.id}, None)

        # should have first two, but the third should not be present

        self.assertTrue(test_user_1.id in user_ids)
        self.assertTrue(test_user_2.id in user_ids)

        self.assertFalse(test_user_3.id in user_ids)
Example #4
0
 def test_enrollment_end(self, experiment_end, enrollment_created, expected_bucket):
     if enrollment_created:
         enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c')
         enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC)
         enrollment.save()
     if experiment_end:
         ExperimentKeyValueFactory(experiment_id=0, key='enrollment_end', value=experiment_end)
     self.assertEqual(self.get_bucket(), expected_bucket)
class ResetSelfPacedScheduleTests(SharedModuleStoreTestCase):
    def create_schedule(self, offset=0):
        self.config = ScheduleConfigFactory(create_schedules=True)

        site_patch = patch('openedx.core.djangoapps.schedules.signals.get_current_site', return_value=self.config.site)
        self.addCleanup(site_patch.stop)
        site_patch.start()

        start = datetime.datetime.now(utc) - datetime.timedelta(days=100)
        self.course = CourseFactory.create(start=start, self_paced=True)

        self.enrollment = CourseEnrollmentFactory(
            course_id=self.course.id,
            mode=CourseMode.AUDIT,
        )
        self.enrollment.created = start + datetime.timedelta(days=offset)
        self.enrollment.save()

        self.schedule = self.enrollment.schedule
        self.schedule.start_date = self.enrollment.created
        self.schedule.save()

        self.user = self.enrollment.user

    def test_reset_to_now(self):
        self.create_schedule()
        original_start = self.schedule.start_date

        with self.assertNumQueries(3):
            reset_self_paced_schedule(self.user, self.course.id, use_availability_date=False)

        self.schedule.refresh_from_db()
        self.assertGreater(self.schedule.start_date, original_start)

    @ddt.data(
        (-1, 0),  # enrolled before course started (will reset to start date)
        (1, 1),   # enrolled after course started (will reset to enroll date)
    )
    @ddt.unpack
    def test_reset_to_start_date(self, offset, expected_offset):
        self.create_schedule(offset=offset)
        expected_start = self.course.start + datetime.timedelta(days=expected_offset)

        with self.assertNumQueries(3):
            reset_self_paced_schedule(self.user, self.course.id, use_availability_date=True)

        self.schedule.refresh_from_db()
        self.assertEqual(self.schedule.start_date.replace(microsecond=0), expected_start.replace(microsecond=0))

    def test_safe_without_schedule(self):
        """ Just ensure that we don't raise exceptions or create any schedules """
        self.create_schedule()
        self.schedule.delete()

        reset_self_paced_schedule(self.user, self.course.id, use_availability_date=False)
        reset_self_paced_schedule(self.user, self.course.id, use_availability_date=True)

        self.assertEqual(Schedule.objects.count(), 0)
Example #6
0
class CertificatesApiTestCase(TestCase):
    def setUp(self):
        super(CertificatesApiTestCase, self).setUp()
        self.course = CourseOverviewFactory.create(
            start=datetime(2017, 1, 1, tzinfo=pytz.UTC),
            end=datetime(2017, 1, 31, tzinfo=pytz.UTC),
            certificate_available_date=None)
        self.user = UserFactory.create()
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            is_active=True,
            mode='audit',
        )
        self.certificate = MockGeneratedCertificate(user=self.user,
                                                    course_id=self.course.id)

    @ddt.data(True, False)
    def test_auto_certificate_generation_enabled(self, feature_enabled):
        with configure_waffle_namespace(feature_enabled):
            self.assertEqual(feature_enabled,
                             api.auto_certificate_generation_enabled())

    @ddt.data(
        (True, True,
         False),  # feature enabled and self-paced should return False
        (True, False,
         True),  # feature enabled and instructor-paced should return True
        (False, True,
         False),  # feature not enabled and self-paced should return False
        (False, False, False
         ),  # feature not enabled and instructor-paced should return False
    )
    @ddt.unpack
    def test_can_show_certificate_available_date_field(self, feature_enabled,
                                                       is_self_paced,
                                                       expected_value):
        self.course.self_paced = is_self_paced
        with configure_waffle_namespace(feature_enabled):
            self.assertEqual(
                expected_value,
                api.can_show_certificate_available_date_field(self.course))

    @ddt.data((CourseMode.VERIFIED, CertificateStatuses.downloadable, True),
              (CourseMode.VERIFIED, CertificateStatuses.notpassing, False),
              (CourseMode.AUDIT, CertificateStatuses.downloadable, False))
    @ddt.unpack
    def test_is_certificate_valid(self, enrollment_mode, certificate_status,
                                  expected_value):
        self.enrollment.mode = enrollment_mode
        self.enrollment.save()

        self.certificate.mode = CourseMode.VERIFIED
        self.certificate.status = certificate_status

        self.assertEqual(expected_value,
                         api.is_certificate_valid(self.certificate))
Example #7
0
class CertificatesApiTestCase(TestCase):
    def setUp(self):
        super(CertificatesApiTestCase, self).setUp()
        self.course = CourseOverviewFactory.create(
            start=datetime(2017, 1, 1, tzinfo=pytz.UTC),
            end=datetime(2017, 1, 31, tzinfo=pytz.UTC),
            certificate_available_date=None
        )
        self.user = UserFactory.create()
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            is_active=True,
            mode='audit',
        )
        self.certificate = MockGeneratedCertificate(
            user=self.user,
            course_id=self.course.id
        )

    @ddt.data(True, False)
    def test_auto_certificate_generation_enabled(self, feature_enabled):
        with configure_waffle_namespace(feature_enabled):
            self.assertEqual(feature_enabled, api.auto_certificate_generation_enabled())

    @ddt.data(
        (True, True, False),  # feature enabled and self-paced should return False
        (True, False, True),  # feature enabled and instructor-paced should return True
        (False, True, False),  # feature not enabled and self-paced should return False
        (False, False, False),  # feature not enabled and instructor-paced should return False
    )
    @ddt.unpack
    def test_can_show_certificate_available_date_field(
            self, feature_enabled, is_self_paced, expected_value
    ):
        self.course.self_paced = is_self_paced
        with configure_waffle_namespace(feature_enabled):
            self.assertEqual(expected_value, api.can_show_certificate_available_date_field(self.course))

    @ddt.data(
        (CourseMode.VERIFIED, CertificateStatuses.downloadable, True),
        (CourseMode.VERIFIED, CertificateStatuses.notpassing, False),
        (CourseMode.AUDIT, CertificateStatuses.downloadable, False)
    )
    @ddt.unpack
    def test_is_certificate_valid(self, enrollment_mode, certificate_status, expected_value):
        self.enrollment.mode = enrollment_mode
        self.enrollment.save()

        self.certificate.mode = CourseMode.VERIFIED
        self.certificate.status = certificate_status

        self.assertEqual(expected_value, api.is_certificate_valid(self.certificate))
Example #8
0
    def test_celebration_created(self):
        """ Test that we make celebration objects when enrollments are created """
        self.assertEqual(CourseEnrollmentCelebration.objects.count(), 0)

        # Test initial creation upon an enrollment being made
        enrollment = CourseEnrollmentFactory()
        self.assertEqual(CourseEnrollmentCelebration.objects.count(), 1)
        celebration = CourseEnrollmentCelebration.objects.get(enrollment=enrollment, celebrate_first_section=True)

        # Test nothing changes if we update that enrollment
        celebration.celebrate_first_section = False
        celebration.save()
        enrollment.mode = 'test-mode'
        enrollment.save()
        self.assertEqual(CourseEnrollmentCelebration.objects.count(), 1)
        CourseEnrollmentCelebration.objects.get(enrollment=enrollment, celebrate_first_section=False)
    def test_namespace_scope(self):
        """
        Make sure that we handle resolving namespaces correctly
        """

        test_user_1 = UserFactory.create(
            password='******',
            email='*****@*****.**',
            first_name='user',
            last_name='one'
        )
        CourseEnrollmentFactory(user=test_user_1, course_id=self.course.id)

        test_user_2 = UserFactory.create(
            password='******',
            email='*****@*****.**',
            first_name='John',
            last_name='Smith'
        )
        CourseEnrollmentFactory(user=test_user_2, course_id=self.course.id)

        test_user_3 = UserFactory.create(password='******')
        enrollment = CourseEnrollmentFactory(user=test_user_3, course_id=self.course.id)

        # unenroll #3

        enrollment.is_active = False
        enrollment.save()

        resolver = NamespaceEnrollmentsScopeResolver()

        users = resolver.resolve(
            'namespace_scope',
            {
                'namespace': self.course.id,
                'fields': {
                    'id': True,
                    'email': True,
                    'first_name': True,
                    'last_name': True,
                }
            },
            None
        )

        _users = [user for user in users]

        self.assertEqual(len(_users), 2)

        self.assertIn('id', _users[0])
        self.assertIn('email', _users[0])
        self.assertIn('first_name', _users[0])
        self.assertIn('last_name', _users[0])
        self.assertEquals(_users[0]['id'], test_user_1.id)
        self.assertEquals(_users[0]['email'], test_user_1.email)
        self.assertEquals(_users[0]['first_name'], test_user_1.first_name)
        self.assertEquals(_users[0]['last_name'], test_user_1.last_name)

        self.assertIn('id', _users[1])
        self.assertIn('email', _users[1])
        self.assertIn('first_name', _users[1])
        self.assertIn('last_name', _users[1])
        self.assertEquals(_users[1]['id'], test_user_2.id)
        self.assertEquals(_users[1]['email'], test_user_2.email)
        self.assertEquals(_users[1]['first_name'], test_user_2.first_name)
        self.assertEquals(_users[1]['last_name'], test_user_2.last_name)
class VerifiedUpgradeToolTest(SharedModuleStoreTestCase):
    @classmethod
    def setUpClass(cls):
        super(VerifiedUpgradeToolTest, cls).setUpClass()
        cls.now = datetime.datetime.now(pytz.UTC)

        cls.course = CourseFactory.create(
            org='edX',
            number='test',
            display_name='Test Course',
            self_paced=True,
            start=cls.now - datetime.timedelta(days=30),
        )
        cls.course_overview = CourseOverview.get_from_id(cls.course.id)

    @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True)
    def setUp(self):
        super(VerifiedUpgradeToolTest, self).setUp()

        self.course_verified_mode = CourseModeFactory(
            course_id=self.course.id,
            mode_slug=CourseMode.VERIFIED,
            expiration_datetime=self.now + datetime.timedelta(days=30),
        )

        patcher = patch(
            'openedx.core.djangoapps.schedules.signals.get_current_site')
        mock_get_current_site = patcher.start()
        self.addCleanup(patcher.stop)
        mock_get_current_site.return_value = SiteFactory.create()

        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)

        self.request = RequestFactory().request()
        crum.set_current_request(self.request)
        self.addCleanup(crum.set_current_request, None)
        self.enrollment = CourseEnrollmentFactory(
            course_id=self.course.id,
            mode=CourseMode.AUDIT,
            course=self.course_overview,
        )
        self.request.user = self.enrollment.user

    def test_tool_visible(self):
        self.assertTrue(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_no_enrollment_exists(self):
        self.enrollment.delete()

        request = RequestFactory().request()
        request.user = UserFactory()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_using_deadline_from_course_mode(self):
        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=False)
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_enrollment_is_inactive(self):
        self.enrollment.is_active = False
        self.enrollment.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_already_verified(self):
        self.enrollment.mode = CourseMode.VERIFIED
        self.enrollment.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_no_verified_track(self):
        self.course_verified_mode.delete()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_course_deadline_has_passed(self):
        self.course_verified_mode.expiration_datetime = self.now - datetime.timedelta(
            days=1)
        self.course_verified_mode.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))

    def test_not_visible_when_course_mode_has_no_deadline(self):
        self.course_verified_mode.expiration_datetime = None
        self.course_verified_mode.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(
            self.request, self.course.id))
Example #11
0
class CreditCourseDashboardTest(ModuleStoreTestCase):
    """
    Tests for credit courses on the student dashboard.
    """

    USERNAME = "******"
    PASSWORD = "******"

    PROVIDER_ID = "hogwarts"
    PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
    PROVIDER_STATUS_URL = "http://credit.example.com/status"

    def setUp(self):
        """Create a course and an enrollment. """
        super(CreditCourseDashboardTest, self).setUp()

        # Create a user and log in
        self.user = UserFactory.create(username=self.USERNAME,
                                       password=self.PASSWORD)
        result = self.client.login(username=self.USERNAME,
                                   password=self.PASSWORD)
        self.assertTrue(result, msg="Could not log in")

        # Create a course and configure it as a credit course
        self.course = CourseFactory()
        CreditCourse.objects.create(course_key=self.course.id, enabled=True)  # pylint: disable=no-member

        # Configure a credit provider
        CreditProvider.objects.create(
            provider_id=self.PROVIDER_ID,
            display_name=self.PROVIDER_NAME,
            provider_status_url=self.PROVIDER_STATUS_URL,
            enable_integration=True,
        )

        # Configure a single credit requirement (minimum passing grade)
        credit_api.set_credit_requirements(
            self.course.id,  # pylint: disable=no-member
            [{
                "namespace": "grade",
                "name": "grade",
                "display_name": "Final Grade",
                "criteria": {
                    "min_grade": 0.8
                }
            }])

        # Enroll the user in the course as "verified"
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,  # pylint: disable=no-member
            mode="verified")

    def test_not_eligible_for_credit(self):
        # The user is not yet eligible for credit, so no additional information should be displayed on the dashboard.
        response = self._load_dashboard()
        self.assertNotContains(response, "credit-eligibility-msg")
        self.assertNotContains(response, "purchase-credit-btn")

    def test_eligible_for_credit(self):
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        response = self._load_dashboard()
        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")

        # Move the eligibility deadline so it's within 30 days
        eligibility = CreditEligibility.objects.get(username=self.USERNAME)
        eligibility.deadline = datetime.datetime.now(
            pytz.UTC) + datetime.timedelta(days=29)
        eligibility.save()

        # The user should still have the option to purchase credit,
        # but there should also be a message urging the user to purchase soon.

        response = self._load_dashboard()

        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
        self.assertContains(response,
                            "You have completed this course and are eligible")

    def test_purchased_credit(self):
        # Simulate that the user has purchased credit, but has not
        # yet initiated a request to the credit provider
        self._make_eligible()
        self._purchase_credit()

        response = self._load_dashboard()
        self.assertContains(response, "credit-request-not-started-msg")

    def test_purchased_credit_and_request_pending(self):
        # Simulate that the user has purchased credit and initiated a request,
        # but we haven't yet heard back from the credit provider.
        self._make_eligible()
        self._purchase_credit()
        self._initiate_request()

        # Expect that the user's status is "pending"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-pending-msg")

    def test_purchased_credit_and_request_approved(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request approved by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "approved")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-approved-msg")

    def test_purchased_credit_and_request_rejected(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request rejected by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "rejected")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-rejected-msg")

    def test_credit_status_error(self):
        # Simulate an error condition: the user has a credit enrollment
        # but no enrollment attribute indicating which provider the user
        # purchased credit from.
        self._make_eligible()
        self._purchase_credit()
        CourseEnrollmentAttribute.objects.all().delete()

        # Expect an error message
        response = self._load_dashboard()
        self.assertContains(response, "credit-error-msg")

    def _load_dashboard(self):
        """Load the student dashboard and return the HttpResponse. """
        return self.client.get(reverse("dashboard"))

    def _make_eligible(self):
        """Make the user eligible for credit in the course. """
        credit_api.set_credit_requirement_status(
            self.user,
            self.course.id,  # pylint: disable=no-member
            "grade",
            "grade",
            status="satisfied",
            reason={"final_grade": 0.95})

    def _purchase_credit(self):
        """Purchase credit from a provider in the course. """
        self.enrollment.mode = "credit"
        self.enrollment.save()  # pylint: disable=no-member

        CourseEnrollmentAttribute.objects.create(
            enrollment=self.enrollment,
            namespace="credit",
            name="provider_id",
            value=self.PROVIDER_ID,
        )

    def _initiate_request(self):
        """Initiate a request for credit from a provider. """
        request = credit_api.create_credit_request(
            self.course.id,  # pylint: disable=no-member
            self.PROVIDER_ID,
            self.USERNAME)
        return request["parameters"]["request_uuid"]

    def _set_request_status(self, uuid, status):
        """Set the status of a request for credit, simulating the notification from the provider. """
        credit_api.update_credit_request_status(uuid, self.PROVIDER_ID, status)

    @ddt.data(([
        u'Arizona State University'
    ], 'You are now eligible for credit from Arizona State University'), (
        [u'Arizona State University', u'Hogwarts School of Witchcraft'],
        'You are now eligible for credit from Arizona State University and Hogwarts School of Witchcraft'
    ), ([
        u'Arizona State University',
        u'Hogwarts School of Witchcraft and Wizardry', u'Charter Oak'
    ], 'You are now eligible for credit from Arizona State University, Hogwarts School'
        ' of Witchcraft and Wizardry, and Charter Oak'),
              ([], 'You have completed this course and are eligible'),
              (None, 'You have completed this course and are eligible'))
    @ddt.unpack
    def test_eligible_for_credit_with_providers_names(self, providers_list,
                                                      credit_string):
        """Verify the message on dashboard with different number of providers."""
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        with patch('student.views.get_credit_provider_display_names'
                   ) as mock_method:
            mock_method.return_value = providers_list
            response = self._load_dashboard()

        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
        self.assertContains(response, credit_string)
class ProgressPageCreditRequirementsTest(ModuleStoreTestCase):
    """
    Tests for credit requirement display on the progress page.
    """

    USERNAME = "******"
    PASSWORD = "******"
    USER_FULL_NAME = "Bob"

    MIN_GRADE_REQ_DISPLAY = "Final Grade Credit Requirement"
    VERIFICATION_REQ_DISPLAY = "Midterm Exam Credit Requirement"

    def setUp(self):
        super(ProgressPageCreditRequirementsTest, self).setUp()

        # Create a course and configure it as a credit course
        self.course = CourseFactory.create()
        CreditCourse.objects.create(course_key=self.course.id, enabled=True)

        # Configure credit requirements (passing grade and in-course reverification)
        credit_api.set_credit_requirements(
            self.course.id,
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": self.MIN_GRADE_REQ_DISPLAY,
                    "criteria": {
                        "min_grade": 0.8
                    }
                },
                {
                    "namespace": "reverification",
                    "name": "midterm",
                    "display_name": self.VERIFICATION_REQ_DISPLAY,
                    "criteria": {}
                }
            ]
        )

        # Create a user and log in
        self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
        self.user.profile.name = self.USER_FULL_NAME
        self.user.profile.save()

        result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
        self.assertTrue(result, msg="Could not log in")

        # Enroll the user in the course as "verified"
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,
            mode="verified"
        )

    def test_credit_requirements_maybe_eligible(self):
        # The user hasn't satisfied any of the credit requirements yet, but she
        # also hasn't failed any.
        response = self._get_progress_page()

        # Expect that the requirements are displayed
        self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY)
        self.assertContains(response, self.VERIFICATION_REQ_DISPLAY)
        self.assertContains(response, "Upcoming")
        self.assertContains(
            response,
            "{}, you have not yet met the requirements for credit".format(self.USER_FULL_NAME)
        )

    def test_credit_requirements_eligible(self):
        # Mark the user as eligible for all requirements
        credit_api.set_credit_requirement_status(
            self.user.username, self.course.id,
            "grade", "grade",
            status="satisfied",
            reason={"final_grade": 0.95}
        )

        credit_api.set_credit_requirement_status(
            self.user.username, self.course.id,
            "reverification", "midterm",
            status="satisfied", reason={}
        )

        # Check the progress page display
        response = self._get_progress_page()
        self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY)
        self.assertContains(response, self.VERIFICATION_REQ_DISPLAY)
        self.assertContains(
            response,
            "{}, you have met the requirements for credit in this course.".format(self.USER_FULL_NAME)
        )
        self.assertContains(response, "Completed {date}".format(date=self._now_formatted_date()))
        self.assertContains(response, "95%")

    def test_credit_requirements_not_eligible(self):
        # Mark the user as having failed both requirements
        credit_api.set_credit_requirement_status(
            self.user.username, self.course.id,
            "reverification", "midterm",
            status="failed", reason={}
        )

        # Check the progress page display
        response = self._get_progress_page()
        self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY)
        self.assertContains(response, self.VERIFICATION_REQ_DISPLAY)
        self.assertContains(
            response,
            "{}, you are no longer eligible for credit in this course.".format(self.USER_FULL_NAME)
        )
        self.assertContains(response, "Verification Failed")

    @ddt.data(
        (CourseMode.VERIFIED, True),
        (CourseMode.CREDIT_MODE, True),
        (CourseMode.HONOR, False),
        (CourseMode.AUDIT, False),
        (CourseMode.PROFESSIONAL, False),
        (CourseMode.NO_ID_PROFESSIONAL_MODE, False)
    )
    @ddt.unpack
    def test_credit_requirements_on_progress_page(self, enrollment_mode, is_requirement_displayed):
        """Test the progress table is only displayed to the verified and credit students."""
        self.enrollment.mode = enrollment_mode
        self.enrollment.save()  # pylint: disable=no-member

        response = self._get_progress_page()
        # Verify the requirements are shown only if the user is in a credit-eligible mode.
        classes = ('credit-eligibility', 'eligibility-heading')
        method = self.assertContains if is_requirement_displayed else self.assertNotContains

        for _class in classes:
            method(response, _class)

    def _get_progress_page(self):
        """Load the progress page for the course the user is enrolled in. """
        url = reverse("progress", kwargs={"course_id": unicode(self.course.id)})
        return self.client.get(url)

    def _now_formatted_date(self):
        """Retrieve the formatted current date. """
        return get_time_display(
            datetime.datetime.now(UTC),
            DEFAULT_SHORT_DATE_FORMAT,
            settings.TIME_ZONE
        )
class CreditCourseDashboardTest(ModuleStoreTestCase):
    """
    Tests for credit courses on the student dashboard.
    """

    USERNAME = "******"
    PASSWORD = "******"

    PROVIDER_ID = "hogwarts"
    PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
    PROVIDER_STATUS_URL = "http://credit.example.com/status"

    def setUp(self):
        """Create a course and an enrollment. """
        super(CreditCourseDashboardTest, self).setUp()

        # Create a user and log in
        self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
        result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
        self.assertTrue(result, msg="Could not log in")

        # Create a course and configure it as a credit course
        self.course = CourseFactory()
        CreditCourse.objects.create(course_key=self.course.id, enabled=True)  # pylint: disable=no-member

        # Configure a credit provider
        CreditProvider.objects.create(
            provider_id=self.PROVIDER_ID,
            display_name=self.PROVIDER_NAME,
            provider_status_url=self.PROVIDER_STATUS_URL,
            enable_integration=True,
        )

        # Configure a single credit requirement (minimum passing grade)
        credit_api.set_credit_requirements(
            self.course.id,  # pylint: disable=no-member
            [
                {
                    "namespace": "grade",
                    "name": "grade",
                    "display_name": "Final Grade",
                    "criteria": {
                        "min_grade": 0.8
                    }
                }
            ]
        )

        # Enroll the user in the course as "verified"
        self.enrollment = CourseEnrollmentFactory(
            user=self.user,
            course_id=self.course.id,  # pylint: disable=no-member
            mode="verified"
        )

    def test_not_eligible_for_credit(self):
        # The user is not yet eligible for credit, so no additional information should be displayed on the dashboard.
        response = self._load_dashboard()
        self.assertNotContains(response, "credit")

    def test_eligible_for_credit(self):
        # Simulate that the user has completed the only requirement in the course
        # so the user is eligible for credit.
        self._make_eligible()

        # The user should have the option to purchase credit
        response = self._load_dashboard()
        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")

        # Move the eligibility deadline so it's within 30 days
        eligibility = CreditEligibility.objects.get(username=self.USERNAME)
        eligibility.deadline = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=29)
        eligibility.save()

        # The user should still have the option to purchase credit,
        # but there should also be a message urging the user to purchase soon.
        response = self._load_dashboard()
        self.assertContains(response, "credit-eligibility-msg")
        self.assertContains(response, "purchase-credit-btn")
        self.assertContains(response, "purchase credit for this course expires")

    def test_purchased_credit(self):
        # Simulate that the user has purchased credit, but has not
        # yet initiated a request to the credit provider
        self._make_eligible()
        self._purchase_credit()

        # Expect that the user's status is "pending"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-pending-msg")

    def test_purchased_credit_and_request_pending(self):
        # Simulate that the user has purchased credit and initiated a request,
        # but we haven't yet heard back from the credit provider.
        self._make_eligible()
        self._purchase_credit()
        self._initiate_request()

        # Expect that the user's status is "pending"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-pending-msg")

    def test_purchased_credit_and_request_approved(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request approved by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "approved")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-approved-msg")

    def test_purchased_credit_and_request_rejected(self):
        # Simulate that the user has purchased credit and initiated a request,
        # and had that request rejected by the credit provider
        self._make_eligible()
        self._purchase_credit()
        request_uuid = self._initiate_request()
        self._set_request_status(request_uuid, "rejected")

        # Expect that the user's status is "approved"
        response = self._load_dashboard()
        self.assertContains(response, "credit-request-rejected-msg")

    def test_credit_status_error(self):
        # Simulate an error condition: the user has a credit enrollment
        # but no enrollment attribute indicating which provider the user
        # purchased credit from.
        self._make_eligible()
        self._purchase_credit()
        CourseEnrollmentAttribute.objects.all().delete()

        # Expect an error message
        response = self._load_dashboard()
        self.assertContains(response, "credit-error-msg")

    def _load_dashboard(self):
        """Load the student dashboard and return the HttpResponse. """
        return self.client.get(reverse("dashboard"))

    def _make_eligible(self):
        """Make the user eligible for credit in the course. """
        credit_api.set_credit_requirement_status(
            self.USERNAME,
            self.course.id,  # pylint: disable=no-member
            "grade", "grade",
            status="satisfied",
            reason={
                "final_grade": 0.95
            }
        )

    def _purchase_credit(self):
        """Purchase credit from a provider in the course. """
        self.enrollment.mode = "credit"
        self.enrollment.save()  # pylint: disable=no-member

        CourseEnrollmentAttribute.objects.create(
            enrollment=self.enrollment,
            namespace="credit",
            name="provider_id",
            value=self.PROVIDER_ID,
        )

    def _initiate_request(self):
        """Initiate a request for credit from a provider. """
        request = credit_api.create_credit_request(
            self.course.id,  # pylint: disable=no-member
            self.PROVIDER_ID,
            self.USERNAME
        )
        return request["parameters"]["request_uuid"]

    def _set_request_status(self, uuid, status):
        """Set the status of a request for credit, simulating the notification from the provider. """
        credit_api.update_credit_request_status(uuid, self.PROVIDER_ID, status)
Example #14
0
class ProgressPageCreditRequirementsTest(ModuleStoreTestCase):
    """
    Tests for credit requirement display on the progress page.
    """

    USERNAME = "******"
    PASSWORD = "******"
    USER_FULL_NAME = "Bob"

    MIN_GRADE_REQ_DISPLAY = "Final Grade Credit Requirement"
    VERIFICATION_REQ_DISPLAY = "Midterm Exam Credit Requirement"

    def setUp(self):
        super(ProgressPageCreditRequirementsTest, self).setUp()

        # Create a course and configure it as a credit course
        self.course = CourseFactory.create()
        CreditCourse.objects.create(course_key=self.course.id, enabled=True)

        # Configure credit requirements (passing grade and in-course reverification)
        credit_api.set_credit_requirements(
            self.course.id, [{
                "namespace": "grade",
                "name": "grade",
                "display_name": self.MIN_GRADE_REQ_DISPLAY,
                "criteria": {
                    "min_grade": 0.8
                }
            }, {
                "namespace": "reverification",
                "name": "midterm",
                "display_name": self.VERIFICATION_REQ_DISPLAY,
                "criteria": {}
            }])

        # Create a user and log in
        self.user = UserFactory.create(username=self.USERNAME,
                                       password=self.PASSWORD)
        self.user.profile.name = self.USER_FULL_NAME
        self.user.profile.save()

        result = self.client.login(username=self.USERNAME,
                                   password=self.PASSWORD)
        self.assertTrue(result, msg="Could not log in")

        # Enroll the user in the course as "verified"
        self.enrollment = CourseEnrollmentFactory(user=self.user,
                                                  course_id=self.course.id,
                                                  mode="verified")

    def test_credit_requirements_maybe_eligible(self):
        # The user hasn't satisfied any of the credit requirements yet, but she
        # also hasn't failed any.
        response = self._get_progress_page()

        # Expect that the requirements are displayed
        self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY)
        self.assertContains(response, self.VERIFICATION_REQ_DISPLAY)
        self.assertContains(response, "Upcoming")
        self.assertContains(
            response,
            "{}, you have not yet met the requirements for credit".format(
                self.USER_FULL_NAME))

    def test_credit_requirements_eligible(self):
        # Mark the user as eligible for all requirements
        credit_api.set_credit_requirement_status(self.user.username,
                                                 self.course.id,
                                                 "grade",
                                                 "grade",
                                                 status="satisfied",
                                                 reason={"final_grade": 0.95})

        credit_api.set_credit_requirement_status(self.user.username,
                                                 self.course.id,
                                                 "reverification",
                                                 "midterm",
                                                 status="satisfied",
                                                 reason={})

        # Check the progress page display
        response = self._get_progress_page()
        self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY)
        self.assertContains(response, self.VERIFICATION_REQ_DISPLAY)
        self.assertContains(
            response,
            "{}, you have met the requirements for credit in this course.".
            format(self.USER_FULL_NAME))
        self.assertContains(
            response,
            "Completed {date}".format(date=self._now_formatted_date()))
        self.assertContains(response, "95%")

    def test_credit_requirements_not_eligible(self):
        # Mark the user as having failed both requirements
        credit_api.set_credit_requirement_status(self.user.username,
                                                 self.course.id,
                                                 "reverification",
                                                 "midterm",
                                                 status="failed",
                                                 reason={})

        # Check the progress page display
        response = self._get_progress_page()
        self.assertContains(response, self.MIN_GRADE_REQ_DISPLAY)
        self.assertContains(response, self.VERIFICATION_REQ_DISPLAY)
        self.assertContains(
            response,
            "{}, you are no longer eligible for credit in this course.".format(
                self.USER_FULL_NAME))
        self.assertContains(response, "Verification Failed")

    @ddt.data((CourseMode.VERIFIED, True), (CourseMode.CREDIT_MODE, True),
              (CourseMode.HONOR, False), (CourseMode.AUDIT, False),
              (CourseMode.PROFESSIONAL, False),
              (CourseMode.NO_ID_PROFESSIONAL_MODE, False))
    @ddt.unpack
    def test_credit_requirements_on_progress_page(self, enrollment_mode,
                                                  is_requirement_displayed):
        """Test the progress table is only displayed to the verified and credit students."""
        self.enrollment.mode = enrollment_mode
        self.enrollment.save()  # pylint: disable=no-member

        response = self._get_progress_page()
        # Verify the requirements are shown only if the user is in a credit-eligible mode.
        classes = ('credit-eligibility', 'eligibility-heading')
        method = self.assertContains if is_requirement_displayed else self.assertNotContains

        for _class in classes:
            method(response, _class)

    def _get_progress_page(self):
        """Load the progress page for the course the user is enrolled in. """
        url = reverse("progress",
                      kwargs={"course_id": unicode(self.course.id)})
        return self.client.get(url)

    def _now_formatted_date(self):
        """Retrieve the formatted current date. """
        return get_time_display(datetime.datetime.now(UTC),
                                DEFAULT_SHORT_DATE_FORMAT, settings.TIME_ZONE)
class VerifiedUpgradeToolTest(SharedModuleStoreTestCase):

    @classmethod
    def setUpClass(cls):
        super(VerifiedUpgradeToolTest, cls).setUpClass()
        cls.now = datetime.datetime.now(pytz.UTC)

        cls.course = CourseFactory.create(
            org='edX',
            number='test',
            display_name='Test Course',
            self_paced=True,
            start=cls.now - datetime.timedelta(days=30),
        )
        cls.course_overview = CourseOverview.get_from_id(cls.course.id)

    @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True)
    def setUp(self):
        super(VerifiedUpgradeToolTest, self).setUp()

        self.course_verified_mode = CourseModeFactory(
            course_id=self.course.id,
            mode_slug=CourseMode.VERIFIED,
            expiration_datetime=self.now + datetime.timedelta(days=30),
        )

        patcher = patch('openedx.core.djangoapps.schedules.signals.get_current_site')
        mock_get_current_site = patcher.start()
        self.addCleanup(patcher.stop)
        mock_get_current_site.return_value = SiteFactory.create()

        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)

        self.enrollment = CourseEnrollmentFactory(
            course_id=self.course.id,
            mode=CourseMode.AUDIT,
            course=self.course_overview,
        )
        self.request = RequestFactory().request()
        self.request.user = self.enrollment.user
        crum.set_current_request(self.request)

    def test_tool_visible(self):
        self.assertTrue(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_no_enrollment_exists(self):
        self.enrollment.delete()

        request = RequestFactory().request()
        request.user = UserFactory()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_using_deadline_from_course_mode(self):
        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=False)
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_enrollment_is_inactive(self):
        self.enrollment.is_active = False
        self.enrollment.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_already_verified(self):
        self.enrollment.mode = CourseMode.VERIFIED
        self.enrollment.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_no_verified_track(self):
        self.course_verified_mode.delete()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_course_deadline_has_passed(self):
        self.course_verified_mode.expiration_datetime = self.now - datetime.timedelta(days=1)
        self.course_verified_mode.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))

    def test_not_visible_when_course_mode_has_no_deadline(self):
        self.course_verified_mode.expiration_datetime = None
        self.course_verified_mode.save()
        self.assertFalse(VerifiedUpgradeTool().is_enabled(self.request, self.course.id))