def test_enrollments(): """ Enrolls the user in a course and then pulls down the enrollments for the user. This assumes that the course in the edX instance is available for enrollment. """ api = EdxApi({ 'access_token': ACCESS_TOKEN, 'api_key': API_KEY }, base_url=BASE_URL) # the enrollment will be done manually until # a client to enroll the student is implemented requester = api.get_requester() requester.post(urljoin(BASE_URL, '/api/enrollment/v1/enrollment'), json={ "course_details": { "course_id": 'course-v1:edX+DemoX+Demo_Course' } }) enrollments = api.enrollments.get_student_enrollments() enrolled_courses = [ enrolled_course.course_details.course_id for enrolled_course in enrollments.enrolled_courses ] assert 'course-v1:edX+DemoX+Demo_Course' in enrolled_courses
def test_get_certificate_404_error(): """ Asserts that a 404 returned from EDX will be silenced for get_student_certificates """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) username = '******' course_key = 'course-v1:edX+DemoX+Demo_Course' certificates = api.certificates old_requester_get = certificates.requester.get def mocked_get(url, *args, **kwargs): """ Return an error for specific URLs """ if '/api/certificates/v0/certificates/' in url: return FakeErroredResponse(status_code=404) return old_requester_get(url, *args, **kwargs) with patch.object(certificates.requester, 'get', autospec=True) as get: get.side_effect = mocked_get with pytest.raises(HTTPError): certificates.get_student_certificate(username, course_key) # Note no error here, just empty list certs = certificates.get_student_certificates(username) assert not certs.all_courses_certs
def test_course_structure(): """ Pull down the course structure and validate it has the correct entries. This test assumes that the used user can access the course. """ api = EdxApi({ 'access_token': ACCESS_TOKEN, 'api_key': API_KEY }, base_url=BASE_URL) structure = api.course_structure.course_blocks( 'course-v1:edX+DemoX+Demo_Course', 'staff') titles = [ block.title for block in structure.root.children if block.visible ] assert titles == [ 'Introduction', 'Example Week 1: Getting Started', 'Example Week 2: Get Interactive', 'Example Week 3: Be Social', 'About Exams and Certificates', 'holding section', ]
def setUpClass(cls): edx_base_url = "http://edx.example.com" cls.api_url = urljoin(edx_base_url, EmailSettings.api_url) cls.client = EdxApi({"access_token": "foobar"}, cls.api_url) cls.email_settings = cls.client.email_settings cls.json = {"success": "true"} cls.course_id = "course_id"
def get(self, request, username, *args, **kargs): # pylint: disable=unused-argument """ Returns information needed to display the user dashboard for all the programs the user is enrolled in. """ user = get_object_or_404(User, social_auth__uid=username, social_auth__provider=EdxOrgOAuth2.name) # get the credentials for the current user for edX edx_client = None if user == request.user: user_social = get_social_auth(request.user) try: utils.refresh_user_token(user_social) except utils.InvalidCredentialStored as exc: return Response(status=exc.http_status_code, data={'error': str(exc)}) except: # pylint: disable=bare-except log.exception( 'Impossible to refresh user credentials in dashboard view') # create an instance of the client to query edX edx_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL) try: program_dashboard = get_user_program_info(user, edx_client) except utils.InvalidCredentialStored as exc: log.exception( 'Access token for user %s is fresh but invalid; forcing login.', user.username) return Response(status=exc.http_status_code, data={'error': str(exc)}) return Response(status=status.HTTP_200_OK, data=program_dashboard)
def get(self, request, *args, **kargs): # pylint: disable=unused-argument, no-self-use """ Returns information needed to display the user dashboard for a program. """ # get the credentials for the current user for edX user_social = request.user.social_auth.get(provider=EdxOrgOAuth2.name) try: utils.refresh_user_token(user_social) except utils.InvalidCredentialStored as exc: 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) # get an enrollments client for the student enrollments = get_student_enrollments(request.user, edx_client) # get a certificates client for the student certificates = get_student_certificates(request.user, edx_client) response_data = [] for program in Program.objects.filter(live=True): response_data.append(get_info_for_program(program, enrollments, certificates)) return Response(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 setUpClass(cls): with open( os.path.join(os.path.dirname(__file__), 'fixtures/user_enrollments.json')) as file_obj: cls.enrollments_json = json.loads(file_obj.read()) with open( os.path.join(os.path.dirname(__file__), 'fixtures/enrollments_list.json')) as file_obj: cls.enrollments_list_json = json.loads(file_obj.read()) cls.enrollment_responses = [ { 'json': cls.enrollments_json[0], 'status_code': 200 }, { 'json': cls.enrollments_json[1], 'status_code': 200 }, ] base_edx_url = 'http://edx.example.com' cls.enrollment_url = urljoin(base_edx_url, CourseEnrollments.enrollment_url) cls.client = EdxApi({'access_token': 'foobar'}, cls.enrollment_url) cls.enrollment_client = cls.client.enrollments
def get_edx_api_client(user, ttl_in_seconds=OPENEDX_AUTH_DEFAULT_TTL_IN_SECONDS): """ Gets an edx api client instance for the user Args: user (users.models.User): A user object ttl_in_seconds (int): number of seconds the auth credentials for this client should still be valid Returns: EdxApi: edx api client instance """ try: auth = get_valid_edx_api_auth(user, ttl_in_seconds=ttl_in_seconds) except OpenEdxApiAuth.DoesNotExist: raise NoEdxApiAuthError( "{} does not have an associated OpenEdxApiAuth".format(str(user))) return EdxApi( { "access_token": auth.access_token, "api_key": settings.OPENEDX_API_KEY }, settings.OPENEDX_API_BASE_URL, timeout=settings.EDX_API_CLIENT_TIMEOUT, )
def test_user_name_update(): """ Asserts that update user's name api updates the full name of the user correctly. """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) user_name = 'Test Name' updated_user = api.user_info.update_user_name('staff', user_name) assert updated_user.username == 'staff' assert updated_user.name == user_name
def test_user_info_timeout(): """ assert timeout exception on user_info """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) user_info = api.user_info with patch.object(user_info.requester, 'get', autospec=True) as get: get.side_effect = mocked_timeout with pytest.raises(Timeout): user_info.get_user_info()
def test_course_details(): """ Pull down the course details. This test assumes that the used user is not anonymous. """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) details = api.course_detail.get_detail('course-v1:edX+DemoX+Demo_Course') assert details.course_id == "course-v1:edX+DemoX+Demo_Course" assert details.name == "edX Demonstration Course"
def test_user_info(): """ Gets information about the logged in user """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) info = api.user_info.get_user_info() assert info.username == 'staff' assert info.name == '' assert info.email == '*****@*****.**' assert isinstance(info.user_id, int)
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_get_current_grade_timeout(): """ assert timeout exception on get current grade. """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) current_grades = api.current_grades with patch.object(current_grades.requester, 'get', autospec=True) as get: get.side_effect = mocked_timeout with pytest.raises(Timeout): current_grades.get_student_current_grade( 'staff', 'course-v1:edX+DemoX+Demo_Course')
def test_course_details_timeout(): """ assert timeout exception on get course_detail """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) course_detail = api.course_detail with patch.object(course_detail._requester, 'get', autospec=True) as get: # pylint: disable=protected-access get.side_effect = mocked_timeout with pytest.raises(Timeout): course_detail.get_detail('course-v1:edX+DemoX+Demo_Course')
def test_create_ccx(): """ Creates a CCX for the demo course. This course *MUST* have ccx enabled in the advanced settings. """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) ccx_id = api.ccx.create( 'course-v1:edX+DemoX+Demo_Course', '*****@*****.**', 100, 'My CCX from test_create_ccx integration test via edx-api-client.') assert ccx_id is not None assert '@' in ccx_id # follows ccx format
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 setUp(self): super(GradesApiTestCase, self).setUp() with open( os.path.join( os.path.dirname(__file__), "../enrollments/fixtures/user_enrollments.json", )) as file: # pylint: disable=redefined-builtin self.enrollment_data = json.load(file) self.enrollment_url = urljoin( "https://edx.example.com", enrollments.CourseEnrollments.enrollment_url) self.client = EdxApi({"access_token": "opensesame"}, "https://edx.example.com")
def test_create_enrollment_timeout(): """ Asserts request timeout error on enrollment api """ api = EdxApi({ 'access_token': ACCESS_TOKEN, 'api_key': API_KEY }, base_url=BASE_URL) enrollments = api.enrollments with patch.object(enrollments.requester, 'post', autospec=True) as post: post.side_effect = mocked_timeout with pytest.raises(Timeout): enrollments.create_student_enrollment( ENROLLMENT_CREATION_COURSE_ID)
def test_create_audit_enrollment(): """ Integration test to enroll the user in a course with `audit` mode """ api = EdxApi({ 'access_token': ACCESS_TOKEN, 'api_key': API_KEY }, base_url=BASE_URL) enrollment = api.enrollments.create_student_enrollment( course_id=ENROLLMENT_CREATION_COURSE_ID, mode=ENROLLMENT_MODE_AUDIT, username="******") assert enrollment.course_id == ENROLLMENT_CREATION_COURSE_ID assert enrollment.mode == ENROLLMENT_MODE_AUDIT
def test_get_certificate(): """ Gets the certificate for the demo course. See this module docstring for the code to run to create one """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) certificate = api.certificates.get_student_certificate( 'staff', 'course-v1:edX+DemoX+Demo_Course') assert certificate.username == 'staff' assert certificate.is_verified is True certificates = api.certificates.get_student_certificates('staff') assert len(certificates.all_courses_certs) >= 1 assert 'course-v1:edX+DemoX+Demo_Course' in certificates.all_courses_certs assert 'course-v1:edX+DemoX+Demo_Course' in certificates.all_courses_verified_certs
def test_deactivate_enrollment(): """ Integration test to enroll then deactivate a user in a course """ api = EdxApi({ 'access_token': ACCESS_TOKEN, 'api_key': API_KEY }, base_url=BASE_URL) api.enrollments.create_student_enrollment( course_id=ENROLLMENT_CREATION_COURSE_ID, mode=ENROLLMENT_MODE_AUDIT) deactivated_enrollment = api.enrollments.deactivate_enrollment( course_id=ENROLLMENT_CREATION_COURSE_ID) assert deactivated_enrollment.course_id == ENROLLMENT_CREATION_COURSE_ID assert deactivated_enrollment.is_active is False
def test_get_timeout_error_ccx(): """ Asserts request timeout error on ccx """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) ccx = api.ccx with patch.object(ccx.requester, 'post', autospec=True) as post: post.side_effect = mocked_timeout with pytest.raises(Timeout): ccx.create( 'course-v1:edX+DemoX+Demo_Course', '*****@*****.**', 100, 'My CCX from test_create_ccx integration test via edx-api-client.' )
def user_data(self, access_token, *args, **kwargs): """ Loads user data from service. This is the function that has to pull the data from edx Args: access_token (str): the OAUTH access token Returns: dict: a dictionary containing user information coming from the remote service. """ edx_client = EdxApi({'access_token': access_token}, self.EDXORG_BASE_URL) info = edx_client.user_info.get_user_info() return {'name': info.name, 'username': info.username, 'email': info.email}
def test_get_certificate_timeout_error(): """ Asserts request timeout error on certificate apis """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) username = '******' course_key = 'course-v1:edX+DemoX+Demo_Course' certificates = api.certificates with patch.object(certificates.requester, 'get', autospec=True) as get: get.side_effect = mocked_timeout with pytest.raises(Timeout): certificates.get_student_certificate(username, course_key) with pytest.raises(Timeout): certificates.get_student_certificates(username)
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 update_all_cached_grade_data(cls, user): """ Updates only certificates and Current grade. Used before a final grade freeze. Args: user (django.contrib.auth.models.User): A user Returns: None """ # get the credentials for the current user for edX user_social = get_social_auth(user) utils.refresh_user_token(user_social) # create an instance of the client to query edX edx_client = EdxApi(user_social.extra_data, settings.EDXORG_BASE_URL) cls.update_cached_certificates(user, edx_client) cls.update_cached_current_grades(user, edx_client)
def test_get_current_grade(): """ Gets the user current grade. If an user is enrolled in a course she has a current grade (probably with percent == 0.0) """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) course_grade = api.current_grades.get_student_current_grade( 'staff', 'course-v1:edX+DemoX+Demo_Course') assert course_grade.username == 'staff' student_grades = api.current_grades.get_student_current_grades('staff') assert len(student_grades.all_course_ids) >= 1 assert 'course-v1:edX+DemoX+Demo_Course' in student_grades.all_course_ids course_grades = api.current_grades.get_course_current_grades( 'course-v1:edX+DemoX+Demo_Course') assert len(course_grades.all_usernames) >= 1 assert 'honor' in course_grades.all_usernames
def get_edx_api_service_client(): """ Gets an edx api client instance for the service worker user Returns: EdxApi: edx api service worker client instance """ if settings.OPENEDX_SERVICE_WORKER_API_TOKEN is None: raise ImproperlyConfigured( "OPENEDX_SERVICE_WORKER_API_TOKEN is not set") edx_client = EdxApi( { "access_token": settings.OPENEDX_SERVICE_WORKER_API_TOKEN, "api_key": settings.OPENEDX_API_KEY, }, settings.OPENEDX_API_BASE_URL, timeout=settings.EDX_API_CLIENT_TIMEOUT, ) return edx_client
def test_enrollments_list(): """ Enrolls the user in a course and then pulls down the enrollments for the user. This assumes that the course in the edX instance is available for enrollment. """ api = EdxApi({'access_token': ACCESS_TOKEN}, base_url=BASE_URL) enrollments = api.enrollments.get_enrollments() cnt = 0 for enrollment in enrollments: assert enrollment.course_id assert enrollment.created assert enrollment.mode assert enrollment.is_active assert enrollment.user if enrollment.mode != ENROLLMENT_MODE_VERIFIED: assert not enrollment.is_verified else: assert enrollment.is_verified cnt += 1 assert cnt >= 2