Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
cb85ef1f
Unverified
Commit
cb85ef1f
authored
Feb 18, 2017
by
Brandon DeRosier
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ENT-162 Create an enterprise enrollment during the enrollment flow
parent
78f235a5
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
226 additions
and
17 deletions
+226
-17
common/djangoapps/enrollment/tests/test_views.py
+74
-1
common/djangoapps/enrollment/views.py
+34
-2
common/djangoapps/util/enterprise_helpers.py
+56
-12
common/djangoapps/util/tests/mixins/__init__.py
+0
-0
common/djangoapps/util/tests/mixins/enterprise.py
+57
-0
lms/envs/aws.py
+1
-0
lms/envs/common.py
+1
-0
lms/envs/devstack_docker.py
+1
-1
lms/envs/test.py
+1
-0
requirements/edx/base.txt
+1
-1
No files found.
common/djangoapps/enrollment/tests/test_views.py
View file @
cb85ef1f
...
...
@@ -20,11 +20,13 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
check_mongo_calls_range
from
django.test.utils
import
override_settings
import
pytz
import
httpretty
from
course_modes.models
import
CourseMode
from
enrollment.views
import
EnrollmentUserThrottle
from
util.models
import
RateLimitConfiguration
from
util.testing
import
UrlResetMixin
from
util.tests.mixins.enterprise
import
EnterpriseServiceMockMixin
from
enrollment
import
api
from
enrollment.errors
import
CourseEnrollmentError
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
...
...
@@ -53,6 +55,7 @@ class EnrollmentTestMixin(object):
enrollment_attributes
=
None
,
min_mongo_calls
=
0
,
max_mongo_calls
=
0
,
enterprise_course_consent
=
None
,
):
"""
Enroll in the course and verify the response's status code. If the expected status is 200, also validates
...
...
@@ -79,6 +82,9 @@ class EnrollmentTestMixin(object):
if
email_opt_in
is
not
None
:
data
[
'email_opt_in'
]
=
email_opt_in
if
enterprise_course_consent
is
not
None
:
data
[
'enterprise_course_consent'
]
=
enterprise_course_consent
extra
=
{}
if
as_server
:
extra
[
'HTTP_X_EDX_API_KEY'
]
=
self
.
API_KEY
...
...
@@ -130,7 +136,7 @@ class EnrollmentTestMixin(object):
@override_settings
(
EDX_API_KEY
=
"i am a key"
)
@ddt.ddt
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentTest
(
EnrollmentTestMixin
,
ModuleStoreTestCase
,
APITestCase
):
class
EnrollmentTest
(
EnrollmentTestMixin
,
ModuleStoreTestCase
,
APITestCase
,
EnterpriseServiceMockMixin
):
"""
Test user enrollment, especially with different course modes.
"""
...
...
@@ -924,6 +930,73 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
CourseMode
.
DEFAULT_MODE_SLUG
)
def
test_enterprise_course_enrollment_invalid_consent
(
self
):
"""Verify that the enterprise_course_consent must be a boolean. """
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
DEFAULT_MODE_SLUG
,
mode_display_name
=
CourseMode
.
DEFAULT_MODE_SLUG
,
)
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_400_BAD_REQUEST
,
enterprise_course_consent
=
'invalid'
,
as_server
=
True
,
)
@httpretty.activate
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
def
test_enterprise_course_enrollment_api_error
(
self
):
"""Verify that enterprise service errors are handled properly. """
UserFactory
.
create
(
username
=
'enterprise_worker'
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
,
)
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
DEFAULT_MODE_SLUG
,
mode_display_name
=
CourseMode
.
DEFAULT_MODE_SLUG
,
)
self
.
mock_enterprise_course_enrollment_post_api_failure
()
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_400_BAD_REQUEST
,
enterprise_course_consent
=
True
,
as_server
=
True
,
username
=
'enterprise_worker'
)
self
.
assertEqual
(
httpretty
.
last_request
()
.
path
,
'/enterprise/api/v1/enterprise-course-enrollment/'
,
'No request was made to the mocked enterprise-course-enrollment API'
)
@httpretty.activate
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
def
test_enterprise_course_enrollment_successful
(
self
):
"""Verify that the enrollment completes when the EnterpriseCourseEnrollment creation succeeds. """
UserFactory
.
create
(
username
=
'enterprise_worker'
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
,
)
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
mode_slug
=
CourseMode
.
DEFAULT_MODE_SLUG
,
mode_display_name
=
CourseMode
.
DEFAULT_MODE_SLUG
,
)
self
.
mock_enterprise_course_enrollment_post_api
(
username
=
self
.
user
.
username
,
course_id
=
unicode
(
self
.
course
.
id
))
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_200_OK
,
enterprise_course_consent
=
True
,
as_server
=
True
,
username
=
'enterprise_worker'
)
self
.
assertEqual
(
httpretty
.
last_request
()
.
path
,
'/enterprise/api/v1/enterprise-course-enrollment/'
,
'No request was made to the mocked enterprise-course-enrollment API'
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
EnrollmentEmbargoTest
(
EnrollmentTestMixin
,
UrlResetMixin
,
ModuleStoreTestCase
):
...
...
common/djangoapps/enrollment/views.py
View file @
cb85ef1f
...
...
@@ -16,8 +16,6 @@ from rest_framework.throttling import UserRateThrottle
from
rest_framework.views
import
APIView
from
course_modes.models
import
CourseMode
from
enrollment
import
api
from
enrollment.errors
import
CourseEnrollmentError
,
CourseModeNotFoundError
,
CourseEnrollmentExistsError
from
openedx.core.djangoapps.cors_csrf.authentication
import
SessionAuthenticationCrossDomainCsrf
from
openedx.core.djangoapps.cors_csrf.decorators
import
ensure_csrf_cookie_cross_domain
from
openedx.core.djangoapps.embargo
import
api
as
embargo_api
...
...
@@ -28,6 +26,13 @@ from openedx.core.lib.api.authentication import (
from
openedx.core.lib.api.permissions
import
ApiKeyHeaderPermission
,
ApiKeyHeaderPermissionIsAuthenticated
from
openedx.core.lib.exceptions
import
CourseNotFoundError
from
openedx.core.lib.log_utils
import
audit_log
from
util.enterprise_helpers
import
enterprise_enabled
,
EnterpriseApiClient
,
EnterpriseApiException
from
enrollment
import
api
from
enrollment.errors
import
(
CourseEnrollmentError
,
CourseModeNotFoundError
,
CourseEnrollmentExistsError
)
from
student.auth
import
user_has_role
from
student.models
import
User
from
student.roles
import
CourseStaffRole
,
GlobalStaff
...
...
@@ -362,6 +367,10 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
* user: Optional. The user ID of the currently logged in user. You
cannot use the command to enroll a different user.
* enterprise_course_consent: Optional. A Boolean value that
indicates the consent status for an EnterpriseCourseEnrollment
to be posted to the Enterprise service.
**GET Response Values**
If an unspecified error occurs when the user tries to obtain a
...
...
@@ -574,6 +583,29 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
}
)
enterprise_course_consent
=
request
.
data
.
get
(
'enterprise_course_consent'
)
# Check if the enterprise_course_enrollment is a boolean
if
has_api_key_permissions
and
enterprise_enabled
()
and
enterprise_course_consent
is
not
None
:
if
not
isinstance
(
enterprise_course_consent
,
bool
):
return
Response
(
status
=
status
.
HTTP_400_BAD_REQUEST
,
data
=
{
'message'
:
(
u"'{value}' is an invalid enterprise course consent value."
)
.
format
(
value
=
enterprise_course_consent
)
}
)
try
:
EnterpriseApiClient
()
.
post_enterprise_course_enrollment
(
username
,
unicode
(
course_id
),
enterprise_course_consent
)
except
EnterpriseApiException
as
error
:
log
.
exception
(
"An unexpected error occurred while creating the new EnterpriseCourseEnrollment "
"for user [
%
s] in course run [
%
s]"
,
username
,
course_id
)
raise
CourseEnrollmentError
(
error
.
message
)
enrollment_attributes
=
request
.
data
.
get
(
'enrollment_attributes'
)
enrollment
=
api
.
get_enrollment
(
username
,
unicode
(
course_id
))
mode_changed
=
enrollment
and
mode
is
not
None
and
enrollment
[
'mode'
]
!=
mode
...
...
common/djangoapps/util/enterprise_helpers.py
View file @
cb85ef1f
"""
Helpers to access the enterprise app
"""
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.utils.translation
import
ugettext
as
_
import
logging
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.core.urlresolvers
import
reverse
from
django.utils.http
import
urlencode
from
edx_rest_api_client.client
import
EdxRestApiClient
try
:
from
enterprise.models
import
EnterpriseCustomer
from
enterprise
import
utils
as
enterprise_utils
from
enterprise.tpa_pipeline
import
(
active_provider_requests_data_sharing
,
active_provider_enforces_data_sharing
,
get_enterprise_customer_for_request
,
)
from
enterprise.utils
import
consent_necessary_for_course
except
ImportError
:
pass
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.lib.token_utils
import
JwtBuilder
from
slumber.exceptions
import
HttpClientError
,
HttpServerError
ENTERPRISE_CUSTOMER_BRANDING_OVERRIDE_DETAILS
=
'enterprise_customer_branding_override_details'
LOGGER
=
logging
.
getLogger
(
"edx.enterprise_helpers"
)
class
EnterpriseApiException
(
Exception
):
"""
Exception for errors while communicating with the Enterprise service API.
"""
pass
class
EnterpriseApiClient
(
object
):
"""
Class for producing an Enterprise service API client.
"""
def
__init__
(
self
):
"""
Initialize an Enterprise service API client, authenticated using the Enterprise worker username.
"""
self
.
user
=
User
.
objects
.
get
(
username
=
settings
.
ENTERPRISE_SERVICE_WORKER_USERNAME
)
jwt
=
JwtBuilder
(
self
.
user
)
.
build_token
([])
self
.
client
=
EdxRestApiClient
(
configuration_helpers
.
get_value
(
'ENTERPRISE_API_URL'
,
settings
.
ENTERPRISE_API_URL
),
jwt
=
jwt
)
def
post_enterprise_course_enrollment
(
self
,
username
,
course_id
,
consent_granted
):
"""
Create an EnterpriseCourseEnrollment by using the corresponding serializer (for validation).
"""
data
=
{
'username'
:
username
,
'course_id'
:
course_id
,
'consent_granted'
:
consent_granted
,
}
endpoint
=
getattr
(
self
.
client
,
'enterprise-course-enrollment'
)
# pylint: disable=literal-used-as-attribute
try
:
endpoint
.
post
(
data
=
data
)
except
(
HttpClientError
,
HttpServerError
):
message
=
(
"An error occured while posting EnterpriseCourseEnrollment for user {username} and "
"course run {course_id} (consent_granted value: {consent_granted})"
)
.
format
(
username
=
username
,
course_id
=
course_id
,
consent_granted
=
consent_granted
,
)
LOGGER
.
exception
(
message
)
raise
EnterpriseApiException
(
message
)
def
enterprise_enabled
():
"""
Determines whether the Enterprise app is installed
...
...
common/djangoapps/util/tests/mixins/__init__.py
0 → 100644
View file @
cb85ef1f
common/djangoapps/util/tests/mixins/enterprise.py
0 → 100644
View file @
cb85ef1f
"""
Mixins for the EnterpriseApiClient.
"""
import
json
import
httpretty
from
django.conf
import
settings
from
django.core.cache
import
cache
class
EnterpriseServiceMockMixin
(
object
):
"""
Mocks for the Enterprise service responses.
"""
def
setUp
(
self
):
super
(
EnterpriseServiceMockMixin
,
self
)
.
setUp
()
cache
.
clear
()
@staticmethod
def
get_enterprise_url
(
path
):
"""Return a URL to the configured Enterprise API. """
return
'{}{}/'
.
format
(
settings
.
ENTERPRISE_API_URL
,
path
)
def
mock_enterprise_course_enrollment_post_api
(
# pylint: disable=invalid-name
self
,
username
=
'test_user'
,
course_id
=
'course-v1:edX+DemoX+Demo_Course'
,
consent_granted
=
True
):
"""
Helper method to register the enterprise course enrollment API POST endpoint.
"""
api_response
=
{
username
:
username
,
course_id
:
course_id
,
consent_granted
:
consent_granted
,
}
api_response_json
=
json
.
dumps
(
api_response
)
httpretty
.
register_uri
(
method
=
httpretty
.
POST
,
uri
=
self
.
get_enterprise_url
(
'enterprise-course-enrollment'
),
body
=
api_response_json
,
content_type
=
'application/json'
)
def
mock_enterprise_course_enrollment_post_api_failure
(
self
):
# pylint: disable=invalid-name
"""
Helper method to register the enterprise course enrollment API endpoint for a failure.
"""
httpretty
.
register_uri
(
method
=
httpretty
.
POST
,
uri
=
self
.
get_enterprise_url
(
'enterprise-course-enrollment'
),
body
=
'{}'
,
content_type
=
'application/json'
,
status
=
500
)
lms/envs/aws.py
View file @
cb85ef1f
...
...
@@ -176,6 +176,7 @@ EDXMKTG_LOGGED_IN_COOKIE_NAME = ENV_TOKENS.get('EDXMKTG_LOGGED_IN_COOKIE_NAME',
EDXMKTG_USER_INFO_COOKIE_NAME
=
ENV_TOKENS
.
get
(
'EDXMKTG_USER_INFO_COOKIE_NAME'
,
EDXMKTG_USER_INFO_COOKIE_NAME
)
LMS_ROOT_URL
=
ENV_TOKENS
.
get
(
'LMS_ROOT_URL'
)
ENTERPRISE_API_URL
=
ENV_TOKENS
.
get
(
'ENTERPRISE_API_URL'
,
LMS_ROOT_URL
+
'/enterprise/api/v1/'
)
ENV_FEATURES
=
ENV_TOKENS
.
get
(
'FEATURES'
,
{})
for
feature
,
value
in
ENV_FEATURES
.
items
():
...
...
lms/envs/common.py
View file @
cb85ef1f
...
...
@@ -61,6 +61,7 @@ DISCUSSION_SETTINGS = {
}
LMS_ROOT_URL
=
"http://localhost:8000"
ENTERPRISE_API_URL
=
LMS_ROOT_URL
+
'/enterprise/api/v1/'
# Features
FEATURES
=
{
...
...
lms/envs/devstack_docker.py
View file @
cb85ef1f
...
...
@@ -15,7 +15,7 @@ LMS_ROOT_URL = 'http://{}'.format(HOST)
ECOMMERCE_PUBLIC_URL_ROOT
=
'http://localhost:18130'
ECOMMERCE_API_URL
=
'http://edx.devstack.ecommerce:18130/api/v2'
ENTERPRISE_API_URL
=
'http://enterprise.example.com/enterprise/api/v1/'
OAUTH_OIDC_ISSUER
=
'{}/oauth2'
.
format
(
LMS_ROOT_URL
)
...
...
lms/envs/test.py
View file @
cb85ef1f
...
...
@@ -590,3 +590,4 @@ COMPREHENSIVE_THEME_LOCALE_PATHS = [REPO_ROOT / "themes/conf/locale", ]
LMS_ROOT_URL
=
"http://localhost:8000"
ECOMMERCE_API_URL
=
'https://ecommerce.example.com/api/v2/'
ENTERPRISE_API_URL
=
'http://enterprise.example.com/enterprise/api/v1/'
requirements/edx/base.txt
View file @
cb85ef1f
...
...
@@ -51,7 +51,7 @@ edx-lint==0.4.3
astroid==1.3.8
edx-django-oauth2-provider==1.1.4
edx-django-sites-extensions==2.1.1
edx-enterprise==0.2
2
.0
edx-enterprise==0.2
3
.0
edx-oauth2-provider==1.2.0
edx-opaque-keys==0.4.0
edx-organizations==0.4.3
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment