def test_is_cache_fresh(self):
     """Test for is_cache_fresh"""
     with self.assertRaises(ValueError):
         CachedEdxDataApi.is_cache_fresh(self.user, 'footype')
     # if there is no entry in the table, the cache is not fresh
     assert UserCacheRefreshTime.objects.filter(
         user=self.user).exists() is False
     for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
         assert CachedEdxDataApi.is_cache_fresh(self.user,
                                                cache_type) is False
     now = now_in_utc()
     user_cache = UserCacheRefreshTimeFactory.create(
         user=self.user,
         enrollment=now,
         certificate=now,
         current_grade=now,
     )
     for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
         assert CachedEdxDataApi.is_cache_fresh(self.user,
                                                cache_type) is True
     # moving back the timestamp of one day, makes the cache not fresh again
     yesterday = now - timedelta(days=1)
     user_cache.enrollment = yesterday
     user_cache.certificate = yesterday
     user_cache.current_grade = yesterday
     user_cache.save()
     for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
         assert CachedEdxDataApi.is_cache_fresh(self.user,
                                                cache_type) is False
    def test_update_cached_current_grades(self, mocked_index):
        """Test for update_cached_current_grades."""
        self.assert_cache_in_db()
        assert UserCacheRefreshTime.objects.filter(
            user=self.user).exists() is False
        CachedEdxDataApi.update_cached_current_grades(self.user,
                                                      self.edx_client)
        self.assert_cache_in_db(grades_keys=self.grades_ids)
        cache_time = UserCacheRefreshTime.objects.get(user=self.user)
        now = now_in_utc()
        assert cache_time.current_grade <= now
        assert mocked_index.delay.called is True
        mocked_index.reset_mock()

        # add another cached element for another course that will be removed by the refresh
        cached_grade = CachedCurrentGradeFactory.create(user=self.user)
        self.assert_cache_in_db(grades_keys=list(self.grades_ids) +
                                [cached_grade.course_run.edx_course_key])
        CachedEdxDataApi.update_cached_current_grades(self.user,
                                                      self.edx_client)
        self.assert_cache_in_db(grades_keys=self.grades_ids)
        cache_time.refresh_from_db()
        assert cache_time.current_grade >= now
        mocked_index.delay.assert_called_once_with([self.user.id],
                                                   check_if_changed=True)
Esempio n. 3
0
def get_user_program_info(user, edx_client):
    """
    Provides a detailed serialization all of a User's enrolled Programs with enrollment/grade info

    Args:
        user (User): A User
        edx_client (EdxApi): An EdxApi instance

    Returns:
        list: Enrolled Program information
    """
    # update cache
    # NOTE: this part can be moved to an asynchronous task
    if edx_client is not None:
        try:
            for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
                CachedEdxDataApi.update_cache_if_expired(
                    user, edx_client, cache_type)
        except InvalidCredentialStored:
            # this needs to raise in order to force the user re-login
            raise
        except:  # pylint: disable=bare-except
            log.exception('Impossible to refresh edX cache')

    response_data = {
        "programs": [],
        "is_edx_data_fresh": CachedEdxDataApi.are_all_caches_fresh(user)
    }
    all_programs = (Program.objects.filter(
        live=True, programenrollment__user=user).prefetch_related(
            'course_set__courserun_set'))
    for program in all_programs:
        mmtrack_info = get_mmtrack(user, program)
        response_data['programs'].append(get_info_for_program(mmtrack_info))
    return response_data
Esempio n. 4
0
    def post(self, request):
        """
        Audit enrolls the user in a course in edx
        """
        course_id = request.data.get('course_id')
        if course_id is None:
            raise ValidationError('course id missing in the request')
        # get the credentials for the current user for edX
        user_social = get_social_auth(request.user)
        try:
            utils.refresh_user_token(user_social)
        except utils.InvalidCredentialStored as exc:
            log.error(
                "Error while refreshing credentials for user %s",
                get_social_username(request.user),
            )
            return Response(
                status=exc.http_status_code,
                data={'error': str(exc)}
            )

        # create an instance of the client to query edX
        edx_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL)

        try:
            enrollment = edx_client.enrollments.create_audit_student_enrollment(course_id)
        except HTTPError as exc:
            if exc.response.status_code == status.HTTP_400_BAD_REQUEST:
                raise PossiblyImproperlyConfigured(
                    'Got a 400 status code from edX server while trying to create '
                    'audit enrollment. This might happen if the course is improperly '
                    'configured on MicroMasters. Course key '
                    '{course_key}, edX user "{edX_user}"'.format(
                        edX_user=get_social_username(request.user),
                        course_key=course_id,
                    )
                )
            log.error(
                "Http error from edX while creating audit enrollment for course key %s for edX user %s",
                course_id,
                get_social_username(request.user),
            )
            return Response(
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                data={'error': str(exc)}
            )
        except Exception as exc:  # pylint: disable=broad-except
            log.exception(
                "Error creating audit enrollment for course key %s for edX user %s",
                course_id,
                get_social_username(request.user),
            )
            return Response(
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                data={'error': str(exc)}
            )
        CachedEdxDataApi.update_cached_enrollment(request.user, enrollment, enrollment.course_id, index_user=True)
        return Response(
            data=enrollment.json
        )
Esempio n. 5
0
 def test_is_cache_fresh(self):
     """Test for is_cache_fresh"""
     with self.assertRaises(ValueError):
         CachedEdxDataApi.is_cache_fresh(self.user, 'footype')
     # if there is no entry in the table, the cache is not fresh
     assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False
     for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
         assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is False
     now = now_in_utc()
     user_cache = UserCacheRefreshTimeFactory.create(
         user=self.user,
         enrollment=now,
         certificate=now,
         current_grade=now,
     )
     for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
         assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is True
     # moving back the timestamp of one day, makes the cache not fresh again
     yesterday = now - timedelta(days=1)
     user_cache.enrollment = yesterday
     user_cache.certificate = yesterday
     user_cache.current_grade = yesterday
     user_cache.save()
     for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
         assert CachedEdxDataApi.is_cache_fresh(self.user, cache_type) is False
Esempio n. 6
0
    def test_update_cached_enrollment(self, mocked_index):
        """Test for update_cached_enrollment"""
        course_id = list(self.enrollment_ids)[0]
        enrollment = self.enrollments.get_enrollment_for_course(course_id)
        self.assert_cache_in_db()

        # normal update that creates also the entry
        CachedEdxDataApi.update_cached_enrollment(self.user, enrollment, course_id, False)
        self.assert_cache_in_db(enrollment_keys=[course_id])
        cached_enr = CachedEnrollment.objects.get(user=self.user, course_run__edx_course_key=course_id)
        assert cached_enr.data == enrollment.json
        assert mocked_index.delay.called is False
        # update of different data with indexing
        enr_json = {
            "course_details": {
                "course_id": course_id,
            },
            "is_active": True,
            "mode": "verified",
            "user": self.user.username
        }
        enrollment_new = Enrollment(enr_json)
        CachedEdxDataApi.update_cached_enrollment(self.user, enrollment_new, course_id, True)
        self.assert_cache_in_db(enrollment_keys=[course_id])
        cached_enr.refresh_from_db()
        assert cached_enr.data == enr_json
        mocked_index.delay.assert_any_call([self.user.id], check_if_changed=True)
Esempio n. 7
0
def get_user_program_info(user, edx_client):
    """
    Provides a detailed serialization all of a User's enrolled Programs with enrollment/grade info

    Args:
        user (User): A User
        edx_client (EdxApi): An EdxApi instance

    Returns:
        list: Enrolled Program information
    """
    # update cache
    # NOTE: this part can be moved to an asynchronous task
    if edx_client is not None:
        try:
            for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
                CachedEdxDataApi.update_cache_if_expired(user, edx_client, cache_type)
        except InvalidCredentialStored:
            # this needs to raise in order to force the user re-login
            raise
        except:  # pylint: disable=bare-except
            log.exception('Impossible to refresh edX cache')

    response_data = {
        "programs": [],
        "is_edx_data_fresh": CachedEdxDataApi.are_all_caches_fresh(user)
    }
    all_programs = (
        Program.objects.filter(live=True, programenrollment__user=user).prefetch_related('course_set__courserun_set')
    )
    for program in all_programs:
        mmtrack_info = get_mmtrack(user, program)
        response_data['programs'].append(get_info_for_program(mmtrack_info))
    return response_data
    def test_update_cached_enrollment(self, mocked_index):
        """Test for update_cached_enrollment"""
        course_id = list(self.enrollment_ids)[0]
        enrollment = self.enrollments.get_enrollment_for_course(course_id)
        self.assert_cache_in_db()

        # normal update that creates also the entry
        CachedEdxDataApi.update_cached_enrollment(self.user, enrollment,
                                                  course_id, False)
        self.assert_cache_in_db(enrollment_keys=[course_id])
        cached_enr = CachedEnrollment.objects.get(
            user=self.user, course_run__edx_course_key=course_id)
        assert cached_enr.data == enrollment.json
        assert mocked_index.delay.called is False
        # update of different data with indexing
        enr_json = {
            "course_details": {
                "course_id": course_id,
            },
            "is_active": True,
            "mode": "verified",
            "user": self.user.username
        }
        enrollment_new = Enrollment(enr_json)
        CachedEdxDataApi.update_cached_enrollment(self.user, enrollment_new,
                                                  course_id, True)
        self.assert_cache_in_db(enrollment_keys=[course_id])
        cached_enr.refresh_from_db()
        assert cached_enr.data == enr_json
        mocked_index.delay.assert_any_call([self.user.id],
                                           check_if_changed=True)
Esempio n. 9
0
    def post(self, request):
        """
        Audit enrolls the user in a course in edx
        """
        course_id = request.data.get('course_id')
        if course_id is None:
            raise ValidationError('course id missing in the request')
        # get the credentials for the current user for edX
        user_social = get_social_auth(request.user)
        try:
            utils.refresh_user_token(user_social)
        except utils.InvalidCredentialStored as exc:
            log.error(
                "Error while refreshing credentials for user %s",
                get_social_username(request.user),
            )
            return Response(
                status=exc.http_status_code,
                data={'error': str(exc)}
            )

        # create an instance of the client to query edX
        edx_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL)

        try:
            enrollment = edx_client.enrollments.create_audit_student_enrollment(course_id)
        except HTTPError as exc:
            if exc.response.status_code == status.HTTP_400_BAD_REQUEST:
                raise PossiblyImproperlyConfigured(
                    'Got a 400 status code from edX server while trying to create '
                    'audit enrollment. This might happen if the course is improperly '
                    'configured on MicroMasters. Course key '
                    '{course_key}, edX user "{edX_user}"'.format(
                        edX_user=get_social_username(request.user),
                        course_key=course_id,
                    )
                )
            log.error(
                "Http error from edX while creating audit enrollment for course key %s for edX user %s",
                course_id,
                get_social_username(request.user),
            )
            return Response(
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                data={'error': str(exc)}
            )
        except Exception as exc:  # pylint: disable=broad-except
            log.exception(
                "Error creating audit enrollment for course key %s for edX user %s",
                course_id,
                get_social_username(request.user),
            )
            return Response(
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
                data={'error': str(exc)}
            )
        CachedEdxDataApi.update_cached_enrollment(request.user, enrollment, enrollment.course_id, index_user=True)
        return Response(
            data=enrollment.json
        )
Esempio n. 10
0
 def set_or_create(self, course_run, **kwargs):
     """Ensures that a cached edX object exists, then properly sets values on the data property"""
     data = self.build_data_property(course_run, **kwargs)
     obj, created = self.get_or_create(course_run, data=data)
     if not created:
         obj.data = data
         obj.save()
     CachedEdxDataApi.update_cache_last_access(self.user, self.cache_type, timestamp=now_in_utc())
     return obj
Esempio n. 11
0
 def test_update_all_cached_grade_data(self, mock_refr, mock_enr, mock_cert, mock_grade):
     """Test for update_all_cached_grade_data"""
     for mock_func in (mock_refr, mock_enr, mock_cert, mock_grade, ):
         assert mock_func.called is False
     CachedEdxDataApi.update_all_cached_grade_data(self.user)
     assert mock_enr.called is False
     mock_refr.assert_called_once_with(self.user.social_auth.get(provider=EdxOrgOAuth2.name))
     for mock_func in (mock_cert, mock_grade, ):
         mock_func.assert_called_once_with(self.user, ANY)
Esempio n. 12
0
 def set_or_create(self, course_run, **kwargs):
     """Ensures that a cached edX object exists, then properly sets values on the data property"""
     data = self.build_data_property(course_run, **kwargs)
     obj, created = self.get_or_create(course_run, data=data)
     if not created:
         obj.data = data
         obj.save()
     CachedEdxDataApi.update_cache_last_access(self.user,
                                               self.cache_type,
                                               timestamp=now_in_utc())
     return obj
Esempio n. 13
0
 def assert_cache_in_db(self, enrollment_keys=None, certificate_keys=None, grades_keys=None):
     """
     Helper function to assert the course keys in the database cache
     """
     enrollment_keys = enrollment_keys or []
     certificate_keys = certificate_keys or []
     grades_keys = grades_keys or []
     enrollments = CachedEdxDataApi.get_cached_edx_data(self.user, CachedEdxDataApi.ENROLLMENT)
     certificates = CachedEdxDataApi.get_cached_edx_data(self.user, CachedEdxDataApi.CERTIFICATE)
     grades = CachedEdxDataApi.get_cached_edx_data(self.user, CachedEdxDataApi.CURRENT_GRADE)
     assert sorted(list(enrollments.enrollments.keys())) == sorted(enrollment_keys)
     assert sorted(list(certificates.certificates.keys())) == sorted(certificate_keys)
     assert sorted(list(grades.current_grades.keys())) == sorted(grades_keys)
Esempio n. 14
0
    def test_get_cached_edx_data(self):
        """
        Test for get_cached_edx_data
        """
        with self.assertRaises(ValueError):
            CachedEdxDataApi.get_cached_edx_data(self.user, 'footype')

        self.assert_cache_in_db()
        for run in self.all_runs:
            CachedEnrollmentFactory.create(user=self.user, course_run=run)
            CachedCertificateFactory.create(user=self.user, course_run=run)
            CachedCurrentGradeFactory.create(user=self.user, course_run=run)
        self.assert_cache_in_db(self.all_course_run_ids, self.all_course_run_ids, self.all_course_run_ids)
    def test_get_cached_edx_data(self):
        """
        Test for get_cached_edx_data
        """
        with self.assertRaises(ValueError):
            CachedEdxDataApi.get_cached_edx_data(self.user, 'footype')

        self.assert_cache_in_db()
        for run in self.all_runs:
            CachedEnrollmentFactory.create(user=self.user, course_run=run)
            CachedCertificateFactory.create(user=self.user, course_run=run)
            CachedCurrentGradeFactory.create(user=self.user, course_run=run)
        self.assert_cache_in_db(self.all_course_run_ids,
                                self.all_course_run_ids,
                                self.all_course_run_ids)
Esempio n. 16
0
def refresh_user_data(user_id):
    """
    Refresh the edx cache data for a user.

    Note that this function will not raise an exception on error, instead the errors are logged.

    Args:
        user_id (int): The user id
    """
    # pylint: disable=bare-except
    try:
        user = User.objects.get(pk=user_id)
    except:
        log.exception('edX data refresh task: unable to get user "%s"',
                      user_id)
        return

    # get the credentials for the current user for edX
    try:
        user_social = get_social_auth(user)
    except:
        log.exception('user "%s" does not have edX credentials', user.username)
        return

    try:
        utils.refresh_user_token(user_social)
    except:
        save_cache_update_failure(user_id)
        log.exception("Unable to refresh token for student %s", user.username)
        return

    try:
        edx_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL)
    except:
        log.exception("Unable to create an edX client object for student %s",
                      user.username)
        return

    for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
        try:
            CachedEdxDataApi.update_cache_if_expired(user, edx_client,
                                                     cache_type)
        except:
            save_cache_update_failure(user_id)
            log.exception("Unable to refresh cache %s for student %s",
                          cache_type, user.username)
            continue
Esempio n. 17
0
 def test_update_cache_if_expired_http_errors(self, status_code, mock_enr):
     """
     Test for update_cache_if_expired in case a backend function raises an HTTPError
     """
     def raise_http_error(*args, **kwargs):  # pylint: disable=unused-argument
         """Mock function to raise an exception"""
         error = HTTPError()
         error.response = MagicMock()
         error.response.status_code = status_code
         raise error
     mock_enr.side_effect = raise_http_error
     if status_code in (400, 401):
         with self.assertRaises(InvalidCredentialStored):
             CachedEdxDataApi.update_cache_if_expired(self.user, self.edx_client, CachedEdxDataApi.ENROLLMENT)
     else:
         with self.assertRaises(HTTPError):
             CachedEdxDataApi.update_cache_if_expired(self.user, self.edx_client, CachedEdxDataApi.ENROLLMENT)
 def test_update_all_cached_grade_data(self, mock_refr, mock_enr, mock_cert,
                                       mock_grade):
     """Test for update_all_cached_grade_data"""
     for mock_func in (
             mock_refr,
             mock_enr,
             mock_cert,
             mock_grade,
     ):
         assert mock_func.called is False
     CachedEdxDataApi.update_all_cached_grade_data(self.user)
     assert mock_enr.called is False
     mock_refr.assert_called_once_with(
         self.user.social_auth.get(provider=EdxOrgOAuth2.name))
     for mock_func in (
             mock_cert,
             mock_grade,
     ):
         mock_func.assert_called_once_with(self.user, ANY)
Esempio n. 19
0
 def test_are_all_caches_fresh(self, cache_type):
     """Test for are_all_caches_fresh"""
     assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is False
     now = now_in_utc()
     yesterday = now - timedelta(days=1)
     user_cache = UserCacheRefreshTimeFactory.create(
         user=self.user,
         enrollment=now,
         certificate=now,
         current_grade=now,
     )
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is True
     setattr(user_cache, cache_type, yesterday)
     user_cache.save()
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is False
     setattr(user_cache, cache_type, now)
     user_cache.save()
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is True
Esempio n. 20
0
def refresh_user_data(user_id):
    """
    Refresh the edx cache data for a user.

    Note that this function will not raise an exception on error, instead the errors are logged.

    Args:
        user_id (int): The user id
    """
    # pylint: disable=bare-except
    try:
        user = User.objects.get(pk=user_id)
    except:
        log.exception('edX data refresh task: unable to get user "%s"', user_id)
        return

    # get the credentials for the current user for edX
    try:
        user_social = get_social_auth(user)
    except:
        log.exception('user "%s" does not have edX credentials', user.username)
        return

    try:
        utils.refresh_user_token(user_social)
    except:
        save_cache_update_failure(user_id)
        log.exception("Unable to refresh token for student %s", user.username)
        return

    try:
        edx_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL)
    except:
        log.exception("Unable to create an edX client object for student %s", user.username)
        return

    for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
        try:
            CachedEdxDataApi.update_cache_if_expired(user, edx_client, cache_type)
        except:
            save_cache_update_failure(user_id)
            log.exception("Unable to refresh cache %s for student %s", cache_type, user.username)
            continue
    def test_update_cache_if_expired(self, mock_enr, mock_cert, mock_grade):
        """Test for update_cache_if_expired"""
        all_mocks = (
            mock_enr,
            mock_cert,
            mock_grade,
        )

        with self.assertRaises(ValueError):
            CachedEdxDataApi.update_cache_if_expired(self.user,
                                                     self.edx_client,
                                                     'footype')

        # if there is no entry in the UserCacheRefreshTime the cache is not fresh and needs to be refreshed
        for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
            # the following is possible only because a mocked function is called
            assert UserCacheRefreshTime.objects.filter(
                user=self.user).exists() is False
            CachedEdxDataApi.update_cache_if_expired(self.user,
                                                     self.edx_client,
                                                     cache_type)
        for mock_func in all_mocks:
            assert mock_func.called is True
            mock_func.reset_mock()

        # if we create a fresh entry in the UserCacheRefreshTime, no update is called
        now = now_in_utc()
        user_cache = UserCacheRefreshTimeFactory.create(
            user=self.user,
            enrollment=now,
            certificate=now,
            current_grade=now,
        )
        for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
            CachedEdxDataApi.update_cache_if_expired(self.user,
                                                     self.edx_client,
                                                     cache_type)
        for mock_func in all_mocks:
            assert mock_func.called is False
            mock_func.reset_mock()

        # moving back the last access time, the functions are called again
        yesterday = now - timedelta(days=1)
        user_cache.enrollment = yesterday
        user_cache.certificate = yesterday
        user_cache.current_grade = yesterday
        user_cache.save()
        for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
            CachedEdxDataApi.update_cache_if_expired(self.user,
                                                     self.edx_client,
                                                     cache_type)
        for mock_func in all_mocks:
            assert mock_func.called is True
Esempio n. 22
0
def enroll_user_on_success(order):
    """
    Enroll user after they made a successful purchase.

    Args:
        order (Order): An order to be fulfilled

    Returns:
         None
    """
    user_social = get_social_auth(order.user)
    enrollments_client = EdxApi(user_social.extra_data,
                                settings.EDXORG_BASE_URL).enrollments
    existing_enrollments = enrollments_client.get_student_enrollments()

    exceptions = []
    enrollments = []
    for line in order.line_set.all():
        course_key = line.course_key
        try:
            if not existing_enrollments.is_enrolled_in(course_key):
                enrollments.append(
                    enrollments_client.create_audit_student_enrollment(
                        course_key))
        except Exception as ex:  # pylint: disable=broad-except
            log.exception(
                "Error creating audit enrollment for course key %s for user %s",
                course_key,
                get_social_username(order.user),
            )
            exceptions.append(ex)

    for enrollment in enrollments:
        CachedEdxDataApi.update_cached_enrollment(
            order.user,
            enrollment,
            enrollment.course_id,
            index_user=True,
        )

    if exceptions:
        raise EcommerceEdxApiException(exceptions)
 def test_are_all_caches_fresh(self, cache_type):
     """Test for are_all_caches_fresh"""
     assert UserCacheRefreshTime.objects.filter(
         user=self.user).exists() is False
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is False
     now = now_in_utc()
     yesterday = now - timedelta(days=1)
     user_cache = UserCacheRefreshTimeFactory.create(
         user=self.user,
         enrollment=now,
         certificate=now,
         current_grade=now,
     )
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is True
     setattr(user_cache, cache_type, yesterday)
     user_cache.save()
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is False
     setattr(user_cache, cache_type, now)
     user_cache.save()
     assert CachedEdxDataApi.are_all_caches_fresh(self.user) is True
    def test_update_cache_if_expired_http_errors(self, status_code, mock_enr):
        """
        Test for update_cache_if_expired in case a backend function raises an HTTPError
        """
        def raise_http_error(*args, **kwargs):  # pylint: disable=unused-argument
            """Mock function to raise an exception"""
            error = HTTPError()
            error.response = MagicMock()
            error.response.status_code = status_code
            raise error

        mock_enr.side_effect = raise_http_error
        if status_code in (400, 401):
            with self.assertRaises(InvalidCredentialStored):
                CachedEdxDataApi.update_cache_if_expired(
                    self.user, self.edx_client, CachedEdxDataApi.ENROLLMENT)
        else:
            with self.assertRaises(HTTPError):
                CachedEdxDataApi.update_cache_if_expired(
                    self.user, self.edx_client, CachedEdxDataApi.ENROLLMENT)
Esempio n. 25
0
    def test_update_cached_current_grades(self, mocked_index):
        """Test for update_cached_current_grades."""
        self.assert_cache_in_db()
        assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False
        CachedEdxDataApi.update_cached_current_grades(self.user, self.edx_client)
        self.assert_cache_in_db(grades_keys=self.grades_ids)
        cache_time = UserCacheRefreshTime.objects.get(user=self.user)
        now = now_in_utc()
        assert cache_time.current_grade <= now
        assert mocked_index.delay.called is True
        mocked_index.reset_mock()

        # add another cached element for another course that will be removed by the refresh
        cached_grade = CachedCurrentGradeFactory.create(user=self.user)
        self.assert_cache_in_db(grades_keys=list(self.grades_ids) + [cached_grade.course_run.edx_course_key])
        CachedEdxDataApi.update_cached_current_grades(self.user, self.edx_client)
        self.assert_cache_in_db(grades_keys=self.grades_ids)
        cache_time.refresh_from_db()
        assert cache_time.current_grade >= now
        mocked_index.delay.assert_called_once_with([self.user.id], check_if_changed=True)
 def assert_cache_in_db(self,
                        enrollment_keys=None,
                        certificate_keys=None,
                        grades_keys=None):
     """
     Helper function to assert the course keys in the database cache
     """
     enrollment_keys = enrollment_keys or []
     certificate_keys = certificate_keys or []
     grades_keys = grades_keys or []
     enrollments = CachedEdxDataApi.get_cached_edx_data(
         self.user, CachedEdxDataApi.ENROLLMENT)
     certificates = CachedEdxDataApi.get_cached_edx_data(
         self.user, CachedEdxDataApi.CERTIFICATE)
     grades = CachedEdxDataApi.get_cached_edx_data(
         self.user, CachedEdxDataApi.CURRENT_GRADE)
     assert sorted(list(
         enrollments.enrollments.keys())) == sorted(enrollment_keys)
     assert sorted(list(
         certificates.certificates.keys())) == sorted(certificate_keys)
     assert sorted(list(
         grades.current_grades.keys())) == sorted(grades_keys)
Esempio n. 27
0
def enroll_user_on_success(order):
    """
    Enroll user after they made a successful purchase.

    Args:
        order (Order): An order to be fulfilled

    Returns:
         None
    """
    user_social = get_social_auth(order.user)
    enrollments_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL).enrollments
    existing_enrollments = enrollments_client.get_student_enrollments()

    exceptions = []
    enrollments = []
    for line in order.line_set.all():
        course_key = line.course_key
        try:
            if not existing_enrollments.is_enrolled_in(course_key):
                enrollments.append(enrollments_client.create_audit_student_enrollment(course_key))
        except Exception as ex:  # pylint: disable=broad-except
            log.exception(
                "Error creating audit enrollment for course key %s for user %s",
                course_key,
                get_social_username(order.user),
            )
            exceptions.append(ex)

    for enrollment in enrollments:
        CachedEdxDataApi.update_cached_enrollment(
            order.user,
            enrollment,
            enrollment.course_id,
            index_user=True,
        )

    if exceptions:
        raise EcommerceEdxApiException(exceptions)
Esempio n. 28
0
    def test_update_cache_if_expired(self, mock_enr, mock_cert, mock_grade):
        """Test for update_cache_if_expired"""
        all_mocks = (mock_enr, mock_cert, mock_grade, )

        with self.assertRaises(ValueError):
            CachedEdxDataApi.update_cache_if_expired(self.user, self.edx_client, 'footype')

        # if there is no entry in the UserCacheRefreshTime the cache is not fresh and needs to be refreshed
        for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
            # the following is possible only because a mocked function is called
            assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False
            CachedEdxDataApi.update_cache_if_expired(self.user, self.edx_client, cache_type)
        for mock_func in all_mocks:
            assert mock_func.called is True
            mock_func.reset_mock()

        # if we create a fresh entry in the UserCacheRefreshTime, no update is called
        now = now_in_utc()
        user_cache = UserCacheRefreshTimeFactory.create(
            user=self.user,
            enrollment=now,
            certificate=now,
            current_grade=now,
        )
        for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
            CachedEdxDataApi.update_cache_if_expired(self.user, self.edx_client, cache_type)
        for mock_func in all_mocks:
            assert mock_func.called is False
            mock_func.reset_mock()

        # moving back the last access time, the functions are called again
        yesterday = now - timedelta(days=1)
        user_cache.enrollment = yesterday
        user_cache.certificate = yesterday
        user_cache.current_grade = yesterday
        user_cache.save()
        for cache_type in CachedEdxDataApi.SUPPORTED_CACHES:
            CachedEdxDataApi.update_cache_if_expired(self.user, self.edx_client, cache_type)
        for mock_func in all_mocks:
            assert mock_func.called is True
Esempio n. 29
0
    def test_update_cache_last_access(self):
        """Test for update_cache_last_access"""
        with self.assertRaises(ValueError):
            CachedEdxDataApi.update_cache_last_access(self.user, 'footype')
        assert UserCacheRefreshTime.objects.filter(user=self.user).exists() is False

        CachedEdxDataApi.update_cache_last_access(self.user, CachedEdxDataApi.ENROLLMENT)
        cache_time = UserCacheRefreshTime.objects.get(user=self.user)
        assert cache_time.enrollment <= now_in_utc()
        assert cache_time.certificate is None
        assert cache_time.current_grade is None

        old_timestamp = now_in_utc() - timedelta(days=1)
        CachedEdxDataApi.update_cache_last_access(self.user, CachedEdxDataApi.ENROLLMENT, old_timestamp)
        cache_time.refresh_from_db()
        assert cache_time.enrollment == old_timestamp
    def test_update_cache_last_access(self):
        """Test for update_cache_last_access"""
        with self.assertRaises(ValueError):
            CachedEdxDataApi.update_cache_last_access(self.user, 'footype')
        assert UserCacheRefreshTime.objects.filter(
            user=self.user).exists() is False

        CachedEdxDataApi.update_cache_last_access(self.user,
                                                  CachedEdxDataApi.ENROLLMENT)
        cache_time = UserCacheRefreshTime.objects.get(user=self.user)
        assert cache_time.enrollment <= now_in_utc()
        assert cache_time.certificate is None
        assert cache_time.current_grade is None

        old_timestamp = now_in_utc() - timedelta(days=1)
        CachedEdxDataApi.update_cache_last_access(self.user,
                                                  CachedEdxDataApi.ENROLLMENT,
                                                  old_timestamp)
        cache_time.refresh_from_db()
        assert cache_time.enrollment == old_timestamp
Esempio n. 31
0
def freeze_user_final_grade(user, course_run, raise_on_exception=False):
    """
    Public function to freeze final grades for the a user in a course run.

    Args:
        user (User): a django User
        course_run (CourseRun): a course run model object
        raise_on_exception (bool): If true, raise an exception on error. Else create a warning and exit

    Returns:
        FinalGrade: The final grade created, or None if there was an exception but raise_on_exception was False
    """
    # no need to do anything if the course run is not ready
    if not course_run.can_freeze_grades:
        if not raise_on_exception:
            log.info(
                'The grade for user "%s" course "%s" cannot be frozen yet',
                user.username, course_run.edx_course_key
            )
            return None
        else:
            raise FreezeGradeFailedException(
                'The grade for user "{0}" course "{1}" cannot be frozen yet'.format(
                    user.username,
                    course_run.edx_course_key,
                )
            )
    # update one last time the user's certificates and current grades
    try:
        CachedEdxDataApi.update_all_cached_grade_data(user)
    except Exception as ex:  # pylint: disable=broad-except
        con = get_redis_connection("redis")
        con.lpush(CACHE_KEY_FAILED_USERS_BASE_STR.format(course_run.edx_course_key), user.id)
        if not raise_on_exception:
            log.exception(
                'Impossible to refresh the edX cache for user "%s" in course %s',
                user.username,
                course_run.edx_course_key
            )
            return None
        else:
            raise FreezeGradeFailedException(
                'Impossible to refresh the edX cache for user "{0}" in course {1}'.format(
                    user.username,
                    course_run.edx_course_key
                )
            ) from ex
    # get the final grade for the user in the program
    try:
        final_grade = get_final_grade(user, course_run)
    except Exception as ex:  # pylint: disable=broad-except
        # If user doesn't have a grade no need to freeze
        con = get_redis_connection("redis")
        con.lpush(CACHE_KEY_FAILED_USERS_BASE_STR.format(course_run.edx_course_key), user.id)
        if not raise_on_exception:
            log.exception(
                'Impossible to get final grade for user "%s" in course %s', user.username, course_run.edx_course_key)
            return None
        else:
            raise FreezeGradeFailedException(
                'Impossible to get final grade for user "{0}" in course {1}'.format(
                    user.username,
                    course_run.edx_course_key
                )
            ) from ex
    # the final grade at this point should not exists, but putting a `get_or_create`
    # should solve the problem when the function is called synchronously from the dashboard REST API multiple times
    final_grade_obj, _ = FinalGrade.objects.get_or_create(
        user=user,
        course_run=course_run,
        grade=final_grade.grade,
        passed=final_grade.passed,
        status=FinalGradeStatus.COMPLETE,
        course_run_paid_on_edx=final_grade.payed_on_edx
    )
    return final_grade_obj
Esempio n. 32
0
def freeze_user_final_grade(user, course_run, raise_on_exception=False):
    """
    Public function to freeze final grades for the a user in a course run.

    Args:
        user (User): a django User
        course_run (CourseRun): a course run model object
        raise_on_exception (bool): If true, raise an exception on error. Else create a warning and exit

    Returns:
        FinalGrade: The final grade created, or None if there was an exception but raise_on_exception was False
    """
    # no need to do anything if the course run is not ready
    if not course_run.can_freeze_grades:
        if not raise_on_exception:
            log.info(
                'The grade for user "%s" course "%s" cannot be frozen yet',
                user.username, course_run.edx_course_key
            )
            return None
        else:
            raise FreezeGradeFailedException(
                'The grade for user "{0}" course "{1}" cannot be frozen yet'.format(
                    user.username,
                    course_run.edx_course_key,
                )
            )
    # update one last time the user's certificates and current grades
    try:
        CachedEdxDataApi.update_all_cached_grade_data(user)
    except Exception as ex:  # pylint: disable=broad-except
        con = get_redis_connection("redis")
        con.lpush(CACHE_KEY_FAILED_USERS_BASE_STR.format(course_run.edx_course_key), user.id)
        if not raise_on_exception:
            log.exception(
                'Impossible to refresh the edX cache for user "%s" in course %s',
                user.username,
                course_run.edx_course_key
            )
            return None
        else:
            raise FreezeGradeFailedException(
                'Impossible to refresh the edX cache for user "{0}" in course {1}'.format(
                    user.username,
                    course_run.edx_course_key
                )
            ) from ex
    # get the final grade for the user in the program
    try:
        final_grade = get_final_grade(user, course_run)
    except Exception as ex:  # pylint: disable=broad-except
        # If user doesn't have a grade no need to freeze
        con = get_redis_connection("redis")
        con.lpush(CACHE_KEY_FAILED_USERS_BASE_STR.format(course_run.edx_course_key), user.id)
        if not raise_on_exception:
            log.exception(
                'Impossible to get final grade for user "%s" in course %s', user.username, course_run.edx_course_key)
            return None
        else:
            raise FreezeGradeFailedException(
                'Impossible to get final grade for user "{0}" in course {1}'.format(
                    user.username,
                    course_run.edx_course_key
                )
            ) from ex
    # the final grade at this point should not exists, but putting a `get_or_create`
    # should solve the problem when the function is called synchronously from the dashboard REST API multiple times
    final_grade_obj, _ = FinalGrade.objects.get_or_create(
        user=user,
        course_run=course_run,
        grade=final_grade.grade,
        passed=final_grade.passed,
        status=FinalGradeStatus.COMPLETE,
        course_run_paid_on_edx=final_grade.payed_on_edx
    )
    return final_grade_obj