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)
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 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 )
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_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)
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)
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 )
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
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)
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
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)
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)
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_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)
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 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
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)
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)
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_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
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
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
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