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
7a31441e
Commit
7a31441e
authored
Jul 27, 2017
by
Jesse Shapiro
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move to new consent API
parent
b649d5a2
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
241 additions
and
269 deletions
+241
-269
cms/envs/common.py
+2
-0
common/djangoapps/course_modes/tests/test_views.py
+2
-129
common/djangoapps/course_modes/views.py
+0
-31
common/djangoapps/enrollment/tests/test_views.py
+44
-0
common/djangoapps/enrollment/views.py
+42
-22
lms/djangoapps/courseware/tests/test_course_info.py
+5
-14
lms/djangoapps/courseware/tests/test_view_authentication.py
+4
-13
lms/djangoapps/courseware/tests/test_views.py
+2
-2
lms/djangoapps/student_account/test/test_views.py
+12
-23
lms/djangoapps/student_account/views.py
+8
-14
lms/envs/aws.py
+5
-0
lms/envs/common.py
+1
-0
lms/envs/test.py
+2
-0
openedx/features/course_experience/tests/views/test_course_home.py
+1
-1
openedx/features/course_experience/tests/views/test_course_updates.py
+1
-1
openedx/features/enterprise_support/api.py
+0
-0
openedx/features/enterprise_support/tests/mixins/enterprise.py
+109
-18
openedx/features/enterprise_support/tests/test_api.py
+0
-0
requirements/edx/base.txt
+1
-1
No files found.
cms/envs/common.py
View file @
7a31441e
...
...
@@ -355,6 +355,7 @@ AUTHENTICATION_BACKENDS = (
LMS_BASE
=
None
LMS_ROOT_URL
=
"http://localhost:8000"
ENTERPRISE_API_URL
=
LMS_ROOT_URL
+
'/enterprise/api/v1/'
ENTERPRISE_CONSENT_API_URL
=
LMS_ROOT_URL
+
'/consent/api/v1/'
# These are standard regexes for pulling out info like course_ids, usage_ids, etc.
# They are used so that URLs with deprecated-format strings still work.
...
...
@@ -1177,6 +1178,7 @@ OPTIONAL_APPS = (
# Enterprise App (http://github.com/edx/edx-enterprise)
(
'enterprise'
,
None
),
(
'consent'
,
None
),
)
...
...
common/djangoapps/course_modes/tests/test_views.py
View file @
7a31441e
...
...
@@ -21,7 +21,6 @@ from lms.djangoapps.commerce.tests import test_utils as ecomm_test_utils
from
openedx.core.djangoapps.catalog.tests.mixins
import
CatalogIntegrationMixin
from
openedx.core.djangoapps.embargo.test_utils
import
restrict_course
from
openedx.core.djangoapps.theming.tests.test_util
import
with_comprehensive_theme
from
openedx.features.enterprise_support.tests.mixins.enterprise
import
EnterpriseServiceMockMixin
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
util
import
organizations_helpers
as
organizations_api
...
...
@@ -35,7 +34,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
@attr
(
shard
=
3
)
@ddt.ddt
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
CourseModeViewTest
(
CatalogIntegrationMixin
,
UrlResetMixin
,
ModuleStoreTestCase
,
EnterpriseServiceMockMixin
,
CourseCatalogServiceMockMixin
):
class
CourseModeViewTest
(
CatalogIntegrationMixin
,
UrlResetMixin
,
ModuleStoreTestCase
,
CourseCatalogServiceMockMixin
):
"""
Course Mode View tests
"""
...
...
@@ -48,13 +47,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
self
.
user
=
UserFactory
.
create
(
username
=
"Bob"
,
email
=
"bob@example.com"
,
password
=
"edx"
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
"edx"
)
# Create a service user, because the track selection page depends on it
UserFactory
.
create
(
username
=
'enterprise_worker'
,
email
=
"enterprise_worker@example.com"
,
password
=
"edx"
,
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
@httpretty.activate
@ddt.data
(
...
...
@@ -82,8 +74,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
user
=
self
.
user
)
self
.
mock_enterprise_learner_api
()
# Configure whether we're upgrading or not
url
=
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)])
response
=
self
.
client
.
get
(
url
)
...
...
@@ -133,109 +123,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
self
.
assertRedirects
(
response
,
'http://testserver/basket/add/?sku=TEST'
,
fetch_redirect_response
=
False
)
ecomm_test_utils
.
update_commerce_config
(
enabled
=
False
)
def
_generate_enterprise_learner_context
(
self
,
enable_audit_enrollment
=
False
):
"""
Internal helper to support common pieces of test case variations
"""
# Create the course modes
for
mode
in
(
'audit'
,
'honor'
,
'verified'
):
CourseModeFactory
.
create
(
mode_slug
=
mode
,
course_id
=
self
.
course
.
id
)
catalog_integration
=
self
.
create_catalog_integration
()
UserFactory
(
username
=
catalog_integration
.
service_username
)
self
.
mock_course_discovery_api_for_catalog_contains
(
catalog_id
=
1
,
course_run_ids
=
[
str
(
self
.
course
.
id
)]
)
self
.
mock_enterprise_learner_api
(
enable_audit_enrollment
=
enable_audit_enrollment
)
return
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)])
@httpretty.activate
def
test_no_enrollment
(
self
):
url
=
self
.
_generate_enterprise_learner_context
()
response
=
self
.
client
.
get
(
url
)
self
.
assertEquals
(
response
.
status_code
,
200
)
@httpretty.activate
@waffle.testutils.override_switch
(
"populate-multitenant-programs"
,
True
)
def
test_enterprise_learner_context
(
self
):
"""
Test: Track selection page should show the enterprise context message if user belongs to the Enterprise.
"""
url
=
self
.
_generate_enterprise_learner_context
()
# User visits the track selection page directly without ever enrolling
response
=
self
.
client
.
get
(
url
)
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertContains
(
response
,
'Welcome, {username}! You are about to enroll in {course_name}, from {partner_names}, '
'sponsored by TestShib. Please select your enrollment information below.'
.
format
(
username
=
self
.
user
.
username
,
course_name
=
self
.
course
.
display_name_with_default_escaped
,
partner_names
=
self
.
course
.
org
)
)
@httpretty.activate
@waffle.testutils.override_switch
(
"populate-multitenant-programs"
,
True
)
def
test_enterprise_learner_context_with_multiple_organizations
(
self
):
"""
Test: Track selection page should show the enterprise context message with multiple organization names
if user belongs to the Enterprise.
"""
url
=
self
.
_generate_enterprise_learner_context
()
# Creating organization
for
i
in
xrange
(
2
):
test_organization_data
=
{
'name'
:
'test organization '
+
str
(
i
),
'short_name'
:
'test_organization_'
+
str
(
i
),
'description'
:
'Test Organization Description'
,
'active'
:
True
,
'logo'
:
'/logo_test1.png/'
}
test_org
=
organizations_api
.
add_organization
(
organization_data
=
test_organization_data
)
organizations_api
.
add_organization_course
(
organization_data
=
test_org
,
course_id
=
unicode
(
self
.
course
.
id
))
# User visits the track selection page directly without ever enrolling
response
=
self
.
client
.
get
(
url
)
self
.
assertEquals
(
response
.
status_code
,
200
)
self
.
assertContains
(
response
,
'Welcome, {username}! You are about to enroll in {course_name}, from test organization 0 and '
'test organization 1, sponsored by TestShib. Please select your enrollment information below.'
.
format
(
username
=
self
.
user
.
username
,
course_name
=
self
.
course
.
display_name_with_default_escaped
)
)
@httpretty.activate
@waffle.testutils.override_switch
(
"populate-multitenant-programs"
,
True
)
def
test_enterprise_learner_context_audit_disabled
(
self
):
"""
Track selection page should hide the audit choice by default in an Enterprise Customer/Learner context
"""
# User visits the track selection page directly without ever enrolling, sees only Verified track choice
url
=
self
.
_generate_enterprise_learner_context
()
response
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
response
,
'Pursue a Verified Certificate'
)
self
.
assertNotContains
(
response
,
'Audit This Course'
)
@httpretty.activate
def
test_enterprise_learner_context_audit_enabled
(
self
):
"""
Track selection page should display Audit choice when specified for an Enterprise Customer
"""
# User visits the track selection page directly without ever enrolling, sees both Verified and Audit choices
url
=
self
.
_generate_enterprise_learner_context
(
enable_audit_enrollment
=
True
)
response
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
response
,
'Pursue a Verified Certificate'
)
self
.
assertContains
(
response
,
'Audit This Course'
)
@httpretty.activate
@ddt.data
(
''
,
...
...
@@ -263,8 +150,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
user
=
self
.
user
)
self
.
mock_enterprise_learner_api
()
# Verify that the prices render correctly
response
=
self
.
client
.
get
(
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)]),
...
...
@@ -286,8 +171,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
for
mode
in
available_modes
:
CourseModeFactory
.
create
(
mode_slug
=
mode
,
course_id
=
self
.
course
.
id
)
self
.
mock_enterprise_learner_api
()
# Check whether credit upsell is shown on the page
# This should *only* be shown when a credit mode is available
url
=
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)])
...
...
@@ -530,8 +413,6 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
for
mode
in
[
"honor"
,
"verified"
]:
CourseModeFactory
.
create
(
mode_slug
=
mode
,
course_id
=
self
.
course
.
id
)
self
.
mock_enterprise_learner_api
()
# Load the track selection page
url
=
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)])
response
=
self
.
client
.
get
(
url
)
...
...
@@ -558,7 +439,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
TrackSelectionEmbargoTest
(
UrlResetMixin
,
ModuleStoreTestCase
,
EnterpriseServiceMockMixin
):
class
TrackSelectionEmbargoTest
(
UrlResetMixin
,
ModuleStoreTestCase
):
"""Test embargo restrictions on the track selection page. """
URLCONF_MODULES
=
[
'openedx.core.djangoapps.embargo'
]
...
...
@@ -576,13 +457,6 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseSe
self
.
user
=
UserFactory
.
create
(
username
=
"Bob"
,
email
=
"bob@example.com"
,
password
=
"edx"
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
"edx"
)
# Create a service user
UserFactory
.
create
(
username
=
'enterprise_worker'
,
email
=
"enterprise_worker@example.com"
,
password
=
"edx"
,
)
# Construct the URL for the track selection page
self
.
url
=
reverse
(
'course_modes_choose'
,
args
=
[
unicode
(
self
.
course
.
id
)])
...
...
@@ -595,6 +469,5 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseSe
@httpretty.activate
def
test_embargo_allow
(
self
):
self
.
mock_enterprise_learner_api
()
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
common/djangoapps/course_modes/views.py
View file @
7a31441e
...
...
@@ -24,7 +24,6 @@ from edxmako.shortcuts import render_to_response
from
lms.djangoapps.commerce.utils
import
EcommerceService
from
lms.djangoapps.experiments.utils
import
get_experiment_user_metadata_context
from
openedx.core.djangoapps.embargo
import
api
as
embargo_api
from
openedx.features.enterprise_support
import
api
as
enterprise_api
from
student.models
import
CourseEnrollment
from
third_party_auth.decorators
import
tpa_hint_ends_existing_session
from
util
import
organizations_helpers
as
organization_api
...
...
@@ -162,36 +161,6 @@ class ChooseModeView(View):
title_content
=
_
(
"Congratulations! You are now enrolled in {course_name}"
)
.
format
(
course_name
=
course
.
display_name_with_default_escaped
)
enterprise_learner_data
=
enterprise_api
.
get_enterprise_learner_data
(
site
=
request
.
site
,
user
=
request
.
user
)
if
enterprise_learner_data
:
enterprise_learner
=
enterprise_learner_data
[
0
]
is_course_in_enterprise_catalog
=
enterprise_api
.
is_course_in_enterprise_catalog
(
site
=
request
.
site
,
course_id
=
course_id
,
enterprise_catalog_id
=
enterprise_learner
[
'enterprise_customer'
][
'catalog'
]
)
if
is_course_in_enterprise_catalog
:
partner_names
=
partner_name
=
course
.
display_organization
\
if
course
.
display_organization
else
course
.
org
enterprise_name
=
enterprise_learner
[
'enterprise_customer'
][
'name'
]
organizations
=
organization_api
.
get_course_organizations
(
course_id
=
course
.
id
)
if
organizations
:
partner_names
=
' and '
.
join
([
org
.
get
(
'name'
,
partner_name
)
for
org
in
organizations
])
title_content
=
_
(
"Welcome, {username}! You are about to enroll in {course_name},"
" from {partner_names}, sponsored by {enterprise_name}. Please select your enrollment"
" information below."
)
.
format
(
username
=
request
.
user
.
username
,
course_name
=
course
.
display_name_with_default_escaped
,
partner_names
=
partner_names
,
enterprise_name
=
enterprise_name
)
# Hide the audit modes for this enterprise customer, if necessary
if
not
enterprise_learner
[
'enterprise_customer'
]
.
get
(
'enable_audit_enrollment'
):
for
audit_mode
in
CourseMode
.
AUDIT_MODES
:
modes
.
pop
(
audit_mode
,
None
)
context
[
"title_content"
]
=
title_content
...
...
common/djangoapps/enrollment/tests/test_views.py
View file @
7a31441e
...
...
@@ -56,6 +56,7 @@ class EnrollmentTestMixin(object):
min_mongo_calls
=
0
,
max_mongo_calls
=
0
,
enterprise_course_consent
=
None
,
linked_enterprise_customer
=
None
,
):
"""
Enroll in the course and verify the response's status code. If the expected status is 200, also validates
...
...
@@ -85,6 +86,9 @@ class EnrollmentTestMixin(object):
if
enterprise_course_consent
is
not
None
:
data
[
'enterprise_course_consent'
]
=
enterprise_course_consent
if
linked_enterprise_customer
is
not
None
:
data
[
'linked_enterprise_customer'
]
=
linked_enterprise_customer
extra
=
{}
if
as_server
:
extra
[
'HTTP_X_EDX_API_KEY'
]
=
self
.
API_KEY
...
...
@@ -961,6 +965,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
updated_mode
)
@override_settings
(
ENABLE_ENTERPRISE_INTEGRATION
=
True
)
def
test_enterprise_course_enrollment_invalid_consent
(
self
):
"""Verify that the enterprise_course_consent must be a boolean. """
CourseModeFactory
.
create
(
...
...
@@ -976,6 +981,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
@httpretty.activate
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
@override_settings
(
ENABLE_ENTERPRISE_INTEGRATION
=
True
)
def
test_enterprise_course_enrollment_api_error
(
self
):
"""Verify that enterprise service errors are handled properly. """
UserFactory
.
create
(
...
...
@@ -1003,6 +1009,7 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
@httpretty.activate
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
@override_settings
(
ENABLE_ENTERPRISE_INTEGRATION
=
True
)
def
test_enterprise_course_enrollment_successful
(
self
):
"""Verify that the enrollment completes when the EnterpriseCourseEnrollment creation succeeds. """
UserFactory
.
create
(
...
...
@@ -1028,6 +1035,43 @@ class EnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase, Ente
'No request was made to the mocked enterprise-course-enrollment API'
)
@httpretty.activate
@override_settings
(
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
)
@override_settings
(
ENABLE_ENTERPRISE_INTEGRATION
=
True
)
def
test_enterprise_course_enrollment_with_ec_uuid
(
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
,
)
consent_kwargs
=
{
'username'
:
self
.
user
.
username
,
'course_id'
:
unicode
(
self
.
course
.
id
),
'ec_uuid'
:
'this-is-a-real-uuid'
}
self
.
mock_consent_missing
(
**
consent_kwargs
)
self
.
mock_consent_post
(
**
consent_kwargs
)
self
.
assert_enrollment_status
(
expected_status
=
status
.
HTTP_200_OK
,
as_server
=
True
,
username
=
'enterprise_worker'
,
linked_enterprise_customer
=
'this-is-a-real-uuid'
,
)
self
.
assertEqual
(
httpretty
.
last_request
()
.
path
,
'/consent/api/v1/data_sharing_consent'
,
)
self
.
assertEqual
(
httpretty
.
last_request
()
.
method
,
httpretty
.
POST
)
def
test_enrollment_attributes_always_written
(
self
):
""" Enrollment attributes should always be written, regardless of whether
the enrollment is being created or updated.
...
...
common/djangoapps/enrollment/views.py
View file @
7a31441e
...
...
@@ -29,7 +29,12 @@ 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
openedx.features.enterprise_support.api
import
EnterpriseApiClient
,
EnterpriseApiException
,
enterprise_enabled
from
openedx.features.enterprise_support.api
import
(
ConsentApiClient
,
EnterpriseApiClient
,
EnterpriseApiException
,
enterprise_enabled
)
from
student.auth
import
user_has_role
from
student.models
import
User
from
student.roles
import
CourseStaffRole
,
GlobalStaff
...
...
@@ -591,27 +596,42 @@ 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
)
explicit_linked_enterprise
=
request
.
data
.
get
(
'linked_enterprise_customer'
)
if
has_api_key_permissions
and
enterprise_enabled
():
# We received an explicitly-linked EnterpriseCustomer for the enrollment
if
explicit_linked_enterprise
is
not
None
:
kwargs
=
{
'username'
:
username
,
'course_id'
:
unicode
(
course_id
),
'enterprise_customer_uuid'
:
explicit_linked_enterprise
,
}
consent_client
=
ConsentApiClient
()
consent_client
.
provide_consent
(
**
kwargs
)
# We received an implicit "consent granted" parameter from ecommerce
# TODO: Once ecommerce has been deployed with explicit enterprise support, remove this
# entire chunk of logic, related tests, and any supporting methods no longer required.
elif
enterprise_course_consent
is
not
None
:
# Check if the enterprise_course_enrollment is a boolean
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
))
...
...
lms/djangoapps/courseware/tests/test_course_info.py
View file @
7a31441e
...
...
@@ -12,6 +12,7 @@ from lms.djangoapps.ccx.tests.factories import CcxFactory
from
nose.plugins.attrib
import
attr
from
openedx.core.djangoapps.self_paced.models
import
SelfPacedConfiguration
from
openedx.core.djangoapps.waffle_utils.testutils
import
WAFFLE_TABLES
from
openedx.features.enterprise_support.tests.mixins.enterprise
import
EnterpriseTestConsentRequired
from
pyquery
import
PyQuery
as
pq
from
student.models
import
CourseEnrollment
from
student.tests.factories
import
AdminFactory
...
...
@@ -32,7 +33,7 @@ QUERY_COUNT_TABLE_BLACKLIST = WAFFLE_TABLES
@attr
(
shard
=
1
)
class
CourseInfoTestCase
(
LoginEnrollmentTestCase
,
SharedModuleStoreTestCase
):
class
CourseInfoTestCase
(
EnterpriseTestConsentRequired
,
LoginEnrollmentTestCase
,
SharedModuleStoreTestCase
):
"""
Tests for the Course Info page
"""
...
...
@@ -61,8 +62,7 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
self
.
assertNotIn
(
"You are not currently enrolled in this course"
,
resp
.
content
)
# TODO: LEARNER-611: If this is only tested under Course Info, does this need to move?
@mock.patch
(
'openedx.features.enterprise_support.api.get_enterprise_consent_url'
)
def
test_redirection_missing_enterprise_consent
(
self
,
mock_get_url
):
def
test_redirection_missing_enterprise_consent
(
self
):
"""
Verify that users viewing the course info who are enrolled, but have not provided
data sharing consent, are first redirected to a consent page, and then, once they've
...
...
@@ -70,19 +70,10 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
"""
self
.
setup_user
()
self
.
enroll
(
self
.
course
)
mock_get_url
.
return_value
=
reverse
(
'dashboard'
)
url
=
reverse
(
'info'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
()])
response
=
self
.
client
.
get
(
url
)
url
=
reverse
(
'info'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
()]
)
self
.
assertRedirects
(
response
,
reverse
(
'dashboard'
)
)
mock_get_url
.
assert_called_once
()
mock_get_url
.
return_value
=
None
response
=
self
.
client
.
get
(
url
)
self
.
assertNotIn
(
"You are not currently enrolled in this course"
,
response
.
content
)
self
.
verify_consent_required
(
self
.
client
,
url
)
def
test_anonymous_user
(
self
):
url
=
reverse
(
'info'
,
args
=
[
self
.
course
.
id
.
to_deprecated_string
()])
...
...
lms/djangoapps/courseware/tests/test_view_authentication.py
View file @
7a31441e
...
...
@@ -15,6 +15,7 @@ from courseware.tests.factories import (
StaffFactory
)
from
courseware.tests.helpers
import
CourseAccessTestMixin
,
LoginEnrollmentTestCase
from
openedx.features.enterprise_support.tests.mixins.enterprise
import
EnterpriseTestConsentRequired
from
student.tests.factories
import
CourseEnrollmentFactory
,
UserFactory
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -22,7 +23,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@attr
(
shard
=
1
)
class
TestViewAuth
(
ModuleStoreTestCase
,
LoginEnrollmentTestCase
):
class
TestViewAuth
(
EnterpriseTestConsentRequired
,
ModuleStoreTestCase
,
LoginEnrollmentTestCase
):
"""
Check that view authentication works properly.
"""
...
...
@@ -201,28 +202,18 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
)
)
@patch
(
'openedx.features.enterprise_support.api.get_enterprise_consent_url'
)
def
test_redirection_missing_enterprise_consent
(
self
,
mock_get_url
):
def
test_redirection_missing_enterprise_consent
(
self
):
"""
Verify that enrolled students are redirected to the Enterprise consent
URL if a linked Enterprise Customer requires data sharing consent
and it has not yet been provided.
"""
mock_get_url
.
return_value
=
reverse
(
'dashboard'
)
self
.
login
(
self
.
enrolled_user
)
url
=
reverse
(
'courseware'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()}
)
response
=
self
.
client
.
get
(
url
)
self
.
assertRedirects
(
response
,
reverse
(
'dashboard'
)
)
mock_get_url
.
assert_called_once
()
mock_get_url
.
return_value
=
None
response
=
self
.
client
.
get
(
url
)
self
.
assertNotIn
(
"You are not currently enrolled in this course"
,
response
.
content
)
self
.
verify_consent_required
(
self
.
client
,
url
,
status_code
=
302
)
def
test_instructor_page_access_nonstaff
(
self
):
"""
...
...
lms/djangoapps/courseware/tests/test_views.py
View file @
7a31441e
...
...
@@ -212,8 +212,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS
=
20
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
6
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
6
),
(
ModuleStoreEnum
.
Type
.
mongo
,
10
,
14
5
),
(
ModuleStoreEnum
.
Type
.
split
,
4
,
14
5
),
)
@ddt.unpack
def
test_index_query_counts
(
self
,
store_type
,
expected_mongo_query_count
,
expected_mysql_query_count
):
...
...
lms/djangoapps/student_account/test/test_views.py
View file @
7a31441e
...
...
@@ -472,34 +472,24 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
@mock.patch
(
'student_account.views.enterprise_customer_for_request'
)
@ddt.data
(
(
'signin_user'
,
False
,
None
,
None
,
None
),
(
'register_user'
,
False
,
None
,
None
,
None
),
(
'signin_user'
,
True
,
'Fake EC'
,
'http://logo.com/logo.jpg'
,
u'{enterprise_name} - {platform_name}'
),
(
'register_user'
,
True
,
'Fake EC'
,
'http://logo.com/logo.jpg'
,
u'{enterprise_name} - {platform_name}'
),
(
'signin_user'
,
True
,
'Fake EC'
,
None
,
u'{enterprise_name} - {platform_name}'
),
(
'register_user'
,
True
,
'Fake EC'
,
None
,
u'{enterprise_name} - {platform_name}'
),
(
'signin_user'
,
True
,
'Fake EC'
,
'http://logo.com/logo.jpg'
,
None
),
(
'register_user'
,
True
,
'Fake EC'
,
'http://logo.com/logo.jpg'
,
None
),
(
'signin_user'
,
True
,
'Fake EC'
,
None
,
None
),
(
'register_user'
,
True
,
'Fake EC'
,
None
,
None
),
(
'signin_user'
,
False
,
None
,
None
),
(
'register_user'
,
False
,
None
,
None
),
(
'signin_user'
,
True
,
'Fake EC'
,
'http://logo.com/logo.jpg'
),
(
'register_user'
,
True
,
'Fake EC'
,
'http://logo.com/logo.jpg'
),
(
'signin_user'
,
True
,
'Fake EC'
,
None
),
(
'register_user'
,
True
,
'Fake EC'
,
None
),
)
@ddt.unpack
def
test_enterprise_register
(
self
,
url_name
,
ec_present
,
ec_name
,
logo_url
,
welcome_message
,
mock_get_ec
):
def
test_enterprise_register
(
self
,
url_name
,
ec_present
,
ec_name
,
logo_url
,
mock_get_ec
):
"""
Verify that when an EnterpriseCustomer is received on the login and register views,
the appropriate sidebar is rendered.
"""
if
ec_present
:
mock_ec
=
mock_get_ec
.
return_value
mock_ec
.
name
=
ec_name
if
logo_url
:
mock_ec
.
branding_configuration
.
logo
.
url
=
logo_url
else
:
mock_ec
.
branding_configuration
.
logo
=
None
if
welcome_message
:
mock_ec
.
branding_configuration
.
welcome_message
=
welcome_message
else
:
del
mock_ec
.
branding_configuration
.
welcome_message
mock_get_ec
.
return_value
=
{
'name'
:
ec_name
,
'branding_configuration'
:
{
'logo'
:
logo_url
}
}
else
:
mock_get_ec
.
return_value
=
None
...
...
@@ -511,8 +501,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
self
.
assertNotContains
(
response
,
text
=
enterprise_sidebar_div_id
)
else
:
self
.
assertContains
(
response
,
text
=
enterprise_sidebar_div_id
)
if
not
welcome_message
:
welcome_message
=
settings
.
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
welcome_message
=
settings
.
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
expected_message
=
welcome_message
.
format
(
start_bold
=
u'<b>'
,
end_bold
=
u'</b>'
,
...
...
lms/djangoapps/student_account/views.py
View file @
7a31441e
...
...
@@ -263,23 +263,17 @@ def enterprise_sidebar_context(request):
platform_name
=
configuration_helpers
.
get_value
(
'PLATFORM_NAME'
,
settings
.
PLATFORM_NAME
)
if
enterprise_customer
.
branding_configuration
.
logo
:
enterprise_logo_url
=
enterprise_customer
.
branding_configuration
.
logo
.
url
else
:
enterprise_logo_url
=
''
logo_url
=
enterprise_customer
.
get
(
'branding_configuration'
,
{})
.
get
(
'logo'
,
''
)
if
getattr
(
enterprise_customer
.
branding_configuration
,
'welcome_message'
,
None
):
branded_welcome_template
=
enterprise_customer
.
branding_configuration
.
welcome_message
else
:
branded_welcome_template
=
configuration_helpers
.
get_value
(
'ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE'
,
settings
.
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
)
branded_welcome_template
=
configuration_helpers
.
get_value
(
'ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE'
,
settings
.
ENTERPRISE_SPECIFIC_BRANDED_WELCOME_TEMPLATE
)
branded_welcome_string
=
branded_welcome_template
.
format
(
start_bold
=
u'<b>'
,
end_bold
=
u'</b>'
,
enterprise_name
=
enterprise_customer
.
name
,
enterprise_name
=
enterprise_customer
[
'name'
]
,
platform_name
=
platform_name
)
...
...
@@ -290,8 +284,8 @@ def enterprise_sidebar_context(request):
platform_welcome_string
=
platform_welcome_template
.
format
(
platform_name
=
platform_name
)
context
=
{
'enterprise_name'
:
enterprise_customer
.
name
,
'enterprise_logo_url'
:
enterprise_
logo_url
,
'enterprise_name'
:
enterprise_customer
[
'name'
]
,
'enterprise_logo_url'
:
logo_url
,
'enterprise_branded_welcome_string'
:
branded_welcome_string
,
'platform_welcome_string'
:
platform_welcome_string
,
}
...
...
lms/envs/aws.py
View file @
7a31441e
...
...
@@ -969,6 +969,11 @@ if LMS_ROOT_URL is not None:
DEFAULT_ENTERPRISE_API_URL
=
LMS_ROOT_URL
+
'/enterprise/api/v1/'
ENTERPRISE_API_URL
=
ENV_TOKENS
.
get
(
'ENTERPRISE_API_URL'
,
DEFAULT_ENTERPRISE_API_URL
)
DEFAULT_ENTERPRISE_CONSENT_API_URL
=
None
if
LMS_ROOT_URL
is
not
None
:
DEFAULT_ENTERPRISE_CONSENT_API_URL
=
LMS_ROOT_URL
+
'/consent/api/v1/'
ENTERPRISE_CONSENT_API_URL
=
ENV_TOKENS
.
get
(
'ENTERPRISE_CONSENT_API_URL'
,
DEFAULT_ENTERPRISE_CONSENT_API_URL
)
ENTERPRISE_SERVICE_WORKER_USERNAME
=
ENV_TOKENS
.
get
(
'ENTERPRISE_SERVICE_WORKER_USERNAME'
,
ENTERPRISE_SERVICE_WORKER_USERNAME
...
...
lms/envs/common.py
View file @
7a31441e
...
...
@@ -3216,6 +3216,7 @@ ENTERPRISE_COURSE_ENROLLMENT_AUDIT_MODES = ['audit', 'honor']
# and are overridden by the configuration parameter accessors defined in aws.py
ENTERPRISE_API_URL
=
LMS_ROOT_URL
+
'/enterprise/api/v1/'
ENTERPRISE_CONSENT_API_URL
=
LMS_ROOT_URL
+
'/consent/api/v1/'
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
ENTERPRISE_API_CACHE_TIMEOUT
=
3600
# Value is in seconds
ENTERPRISE_CUSTOMER_LOGO_IMAGE_SIZE
=
512
# Enterprise logo image size limit in KB's
...
...
lms/envs/test.py
View file @
7a31441e
...
...
@@ -605,7 +605,9 @@ COMPREHENSIVE_THEME_LOCALE_PATHS = [REPO_ROOT / "themes/conf/locale", ]
LMS_ROOT_URL
=
"http://localhost:8000"
ENABLE_ENTERPRISE_INTEGRATION
=
False
ECOMMERCE_API_URL
=
'https://ecommerce.example.com/api/v2/'
ENTERPRISE_API_URL
=
'http://enterprise.example.com/enterprise/api/v1/'
ENTERPRISE_CONSENT_API_URL
=
'http://enterprise.example.com/consent/api/v1/'
ACTIVATION_EMAIL_FROM_ADDRESS
=
'test_activate@edx.org'
openedx/features/course_experience/tests/views/test_course_home.py
View file @
7a31441e
...
...
@@ -160,7 +160,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
course_home_url
(
self
.
course
)
# Fetch the view and verify the query counts
with
self
.
assertNumQueries
(
4
2
,
table_blacklist
=
QUERY_COUNT_TABLE_BLACKLIST
):
with
self
.
assertNumQueries
(
4
1
,
table_blacklist
=
QUERY_COUNT_TABLE_BLACKLIST
):
with
check_mongo_calls
(
4
):
url
=
course_home_url
(
self
.
course
)
self
.
client
.
get
(
url
)
...
...
openedx/features/course_experience/tests/views/test_course_updates.py
View file @
7a31441e
...
...
@@ -127,7 +127,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase):
course_updates_url
(
self
.
course
)
# Fetch the view and verify that the query counts haven't changed
with
self
.
assertNumQueries
(
3
3
,
table_blacklist
=
QUERY_COUNT_TABLE_BLACKLIST
):
with
self
.
assertNumQueries
(
3
2
,
table_blacklist
=
QUERY_COUNT_TABLE_BLACKLIST
):
with
check_mongo_calls
(
4
):
url
=
course_updates_url
(
self
.
course
)
self
.
client
.
get
(
url
)
openedx/features/enterprise_support/api.py
View file @
7a31441e
This diff is collapsed.
Click to expand it.
openedx/features/enterprise_support/tests/mixins/enterprise.py
View file @
7a31441e
...
...
@@ -7,6 +7,8 @@ import mock
import
httpretty
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.core.urlresolvers
import
reverse
from
django.test
import
SimpleTestCase
class
EnterpriseServiceMockMixin
(
object
):
...
...
@@ -14,6 +16,8 @@ class EnterpriseServiceMockMixin(object):
Mocks for the Enterprise service responses.
"""
consent_url
=
'{}{}'
.
format
(
settings
.
ENTERPRISE_CONSENT_API_URL
,
'data_sharing_consent'
)
def
setUp
(
self
):
super
(
EnterpriseServiceMockMixin
,
self
)
.
setUp
()
cache
.
clear
()
...
...
@@ -23,6 +27,19 @@ class EnterpriseServiceMockMixin(object):
"""Return a URL to the configured Enterprise API. """
return
'{}{}/'
.
format
(
settings
.
ENTERPRISE_API_URL
,
path
)
def
mock_get_enterprise_customer
(
self
,
uuid
,
response
,
status
):
"""
Helper to mock the HTTP call to the /enterprise-customer/uuid endpoint
"""
body
=
json
.
dumps
(
response
)
httpretty
.
register_uri
(
method
=
httpretty
.
GET
,
uri
=
(
self
.
get_enterprise_url
(
'enterprise-customer'
)
+
uuid
+
'/'
),
body
=
body
,
content_type
=
'application/json'
,
status
=
status
,
)
def
mock_enterprise_course_enrollment_post_api
(
# pylint: disable=invalid-name
self
,
username
=
'test_user'
,
...
...
@@ -57,6 +74,70 @@ class EnterpriseServiceMockMixin(object):
status
=
500
)
def
mock_consent_response
(
self
,
username
,
course_id
,
ec_uuid
,
method
=
httpretty
.
GET
,
granted
=
True
,
required
=
False
,
exists
=
True
,
response_code
=
None
):
response_body
=
{
'username'
:
username
,
'course_id'
:
course_id
,
'enterprise_customer_uuid'
:
ec_uuid
,
'consent_provided'
:
granted
,
'consent_required'
:
required
,
'exists'
:
exists
,
}
httpretty
.
register_uri
(
method
=
method
,
uri
=
self
.
consent_url
,
content_type
=
'application/json'
,
body
=
json
.
dumps
(
response_body
),
status
=
response_code
or
200
,
)
def
mock_consent_post
(
self
,
username
,
course_id
,
ec_uuid
):
self
.
mock_consent_response
(
username
,
course_id
,
ec_uuid
,
method
=
httpretty
.
POST
,
granted
=
True
,
exists
=
True
,
)
def
mock_consent_get
(
self
,
username
,
course_id
,
ec_uuid
):
self
.
mock_consent_response
(
username
,
course_id
,
ec_uuid
)
def
mock_consent_missing
(
self
,
username
,
course_id
,
ec_uuid
):
self
.
mock_consent_response
(
username
,
course_id
,
ec_uuid
,
exists
=
False
,
granted
=
False
,
required
=
True
,
)
def
mock_consent_not_required
(
self
,
username
,
course_id
,
ec_uuid
):
self
.
mock_consent_response
(
username
,
course_id
,
ec_uuid
,
exists
=
False
,
granted
=
False
,
required
=
False
,
)
def
mock_enterprise_learner_api
(
self
,
catalog_id
=
1
,
...
...
@@ -134,7 +215,7 @@ class EnterpriseServiceMockMixin(object):
)
class
EnterpriseTestConsentRequired
(
object
):
class
EnterpriseTestConsentRequired
(
SimpleTestCase
):
"""
Mixin to help test the data_sharing_consent_required decorator.
"""
...
...
@@ -149,20 +230,30 @@ class EnterpriseTestConsentRequired(object):
* url: URL to test
* status_code: expected status code of URL when no data sharing consent is required.
"""
with
mock
.
patch
(
'openedx.features.enterprise_support.api.enterprise_enabled'
,
return_value
=
True
):
with
mock
.
patch
(
'openedx.features.enterprise_support.api.consent_necessary_for_course'
)
as
mock_consent_necessary
:
# pylint: disable=line-too-long
# Ensure that when consent is necessary, the user is redirected to the consent page.
mock_consent_necessary
.
return_value
=
True
response
=
client
.
get
(
url
)
assert
response
.
status_code
==
302
assert
'grant_data_sharing_permissions'
in
response
.
url
# pylint: disable=no-member
# Ensure that when consent is not necessary, the user continues through to the requested page.
mock_consent_necessary
.
return_value
=
False
response
=
client
.
get
(
url
)
assert
response
.
status_code
==
status_code
# If we were expecting a redirect, ensure it's not to the data sharing permission page
if
status_code
==
302
:
assert
'grant_data_sharing_permissions'
not
in
response
.
url
# pylint: disable=no-member
return
response
def
mock_consent_reverse
(
*
args
,
**
kwargs
):
if
args
[
0
]
==
'grant_data_sharing_permissions'
:
return
'/enterprise/grant_data_sharing_permissions'
return
reverse
(
*
args
,
**
kwargs
)
with
mock
.
patch
(
'openedx.features.enterprise_support.api.reverse'
,
side_effect
=
mock_consent_reverse
):
with
mock
.
patch
(
'openedx.features.enterprise_support.api.enterprise_enabled'
,
return_value
=
True
):
with
mock
.
patch
(
'openedx.features.enterprise_support.api.consent_needed_for_course'
)
as
mock_consent_necessary
:
# Ensure that when consent is necessary, the user is redirected to the consent page.
mock_consent_necessary
.
return_value
=
True
response
=
client
.
get
(
url
)
while
(
response
.
status_code
==
302
and
'grant_data_sharing_permissions'
not
in
response
.
url
):
response
=
client
.
get
(
response
.
url
)
self
.
assertEqual
(
response
.
status_code
,
302
)
self
.
assertIn
(
'grant_data_sharing_permissions'
,
response
.
url
)
# pylint: disable=no-member
# Ensure that when consent is not necessary, the user continues through to the requested page.
mock_consent_necessary
.
return_value
=
False
response
=
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
status_code
)
# If we were expecting a redirect, ensure it's not to the data sharing permission page
if
status_code
==
302
:
self
.
assertNotIn
(
'grant_data_sharing_permissions'
,
response
.
url
)
# pylint: disable=no-member
return
response
openedx/features/enterprise_support/tests/test_api.py
View file @
7a31441e
This diff is collapsed.
Click to expand it.
requirements/edx/base.txt
View file @
7a31441e
...
...
@@ -48,7 +48,7 @@ edx-lint==0.4.3
astroid==1.3.8
edx-django-oauth2-provider==1.1.4
edx-django-sites-extensions==2.3.0
edx-enterprise==0.40.
1
edx-enterprise==0.40.
2
edx-oauth2-provider==1.2.0
edx-opaque-keys==0.4.0
edx-organizations==0.4.5
...
...
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