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
62241b2b
Commit
62241b2b
authored
Feb 01, 2017
by
Clinton Blackburn
Committed by
GitHub
Feb 01, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14408 from edx/clintonb/automated-refunds
Automating Refund Approvals
parents
c0a64c2b
7c39978b
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
233 additions
and
187 deletions
+233
-187
common/djangoapps/student/models.py
+3
-1
common/djangoapps/student/tests/test_refunds.py
+2
-3
common/djangoapps/third_party_auth/tests/specs/base.py
+2
-2
lms/djangoapps/commerce/api/v0/tests/test_views.py
+3
-5
lms/djangoapps/commerce/api/v1/tests/test_views.py
+0
-2
lms/djangoapps/commerce/migrations/0005_commerceconfiguration_enable_automatic_refund_approval.py
+19
-0
lms/djangoapps/commerce/models.py
+4
-0
lms/djangoapps/commerce/signals.py
+45
-46
lms/djangoapps/commerce/tests/__init__.py
+12
-39
lms/djangoapps/commerce/tests/mocks.py
+51
-20
lms/djangoapps/commerce/tests/test_signals.py
+59
-30
lms/djangoapps/student_account/test/test_views.py
+1
-2
lms/djangoapps/verify_student/tests/test_views.py
+4
-5
lms/envs/aws.py
+0
-1
lms/envs/bok_choy.py
+0
-1
lms/envs/common.py
+0
-1
lms/envs/test.py
+2
-0
openedx/core/djangoapps/commerce/utils.py
+12
-19
openedx/core/djangoapps/credit/tests/test_api.py
+3
-5
openedx/core/lib/tests/test_edx_api_utils.py
+1
-3
openedx/core/lib/token_utils.py
+10
-2
No files found.
common/djangoapps/student/models.py
View file @
62241b2b
...
@@ -49,7 +49,6 @@ import request_cache
...
@@ -49,7 +49,6 @@ import request_cache
from
certificates.models
import
GeneratedCertificate
from
certificates.models
import
GeneratedCertificate
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
enrollment.api
import
_default_course_mode
from
enrollment.api
import
_default_course_mode
from
openedx.core.djangoapps.commerce.utils
import
ecommerce_api_client
,
ECOMMERCE_DATE_FORMAT
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
,
NoneToEmptyManager
from
openedx.core.djangoapps.xmodule_django.models
import
CourseKeyField
,
NoneToEmptyManager
...
@@ -1537,6 +1536,9 @@ class CourseEnrollment(models.Model):
...
@@ -1537,6 +1536,9 @@ class CourseEnrollment(models.Model):
def
refund_cutoff_date
(
self
):
def
refund_cutoff_date
(
self
):
""" Calculate and return the refund window end date. """
""" Calculate and return the refund window end date. """
# NOTE: This is here to avoid circular references
from
openedx.core.djangoapps.commerce.utils
import
ecommerce_api_client
,
ECOMMERCE_DATE_FORMAT
try
:
try
:
attribute
=
self
.
attributes
.
get
(
namespace
=
'order'
,
name
=
'order_number'
)
attribute
=
self
.
attributes
.
get
(
namespace
=
'order'
,
name
=
'order_number'
)
except
ObjectDoesNotExist
:
except
ObjectDoesNotExist
:
...
...
common/djangoapps/student/tests/test_refunds.py
View file @
62241b2b
...
@@ -30,7 +30,6 @@ from config_models.models import cache
...
@@ -30,7 +30,6 @@ from config_models.models import cache
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
TEST_API_URL
=
'http://www-internal.example.com/api'
TEST_API_URL
=
'http://www-internal.example.com/api'
TEST_API_SIGNING_KEY
=
'edx'
JSON
=
'application/json'
JSON
=
'application/json'
...
@@ -131,7 +130,7 @@ class RefundableTest(SharedModuleStoreTestCase):
...
@@ -131,7 +130,7 @@ class RefundableTest(SharedModuleStoreTestCase):
)
)
@ddt.unpack
@ddt.unpack
@httpretty.activate
@httpretty.activate
@override_settings
(
ECOMMERCE_API_
SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_API_
URL
=
TEST_API_URL
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
def
test_refund_cutoff_date
(
self
,
order_date_delta
,
course_start_delta
,
expected_date_delta
,
days
):
def
test_refund_cutoff_date
(
self
,
order_date_delta
,
course_start_delta
,
expected_date_delta
,
days
):
"""
"""
Assert that the later date is used with the configurable refund period in calculating the returned cutoff date.
Assert that the later date is used with the configurable refund period in calculating the returned cutoff date.
...
@@ -172,7 +171,7 @@ class RefundableTest(SharedModuleStoreTestCase):
...
@@ -172,7 +171,7 @@ class RefundableTest(SharedModuleStoreTestCase):
self
.
assertIsNone
(
self
.
enrollment
.
refund_cutoff_date
())
self
.
assertIsNone
(
self
.
enrollment
.
refund_cutoff_date
())
@httpretty.activate
@httpretty.activate
@override_settings
(
ECOMMERCE_API_
SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_API_
URL
=
TEST_API_URL
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
def
test_multiple_refunds_dashbaord_page_error
(
self
):
def
test_multiple_refunds_dashbaord_page_error
(
self
):
""" Order with mutiple refunds will not throw 500 error when dashboard page will access."""
""" Order with mutiple refunds will not throw 500 error when dashboard page will access."""
now
=
datetime
.
now
(
pytz
.
UTC
)
.
replace
(
microsecond
=
0
)
now
=
datetime
.
now
(
pytz
.
UTC
)
.
replace
(
microsecond
=
0
)
...
...
common/djangoapps/third_party_auth/tests/specs/base.py
View file @
62241b2b
...
@@ -18,7 +18,7 @@ from social import actions, exceptions
...
@@ -18,7 +18,7 @@ from social import actions, exceptions
from
social.apps.django_app
import
utils
as
social_utils
from
social.apps.django_app
import
utils
as
social_utils
from
social.apps.django_app
import
views
as
social_views
from
social.apps.django_app
import
views
as
social_views
from
lms.djangoapps.commerce.tests
import
TEST_API_URL
,
TEST_API_SIGNING_KEY
from
lms.djangoapps.commerce.tests
import
TEST_API_URL
from
student
import
models
as
student_models
from
student
import
models
as
student_models
from
student
import
views
as
student_views
from
student
import
views
as
student_views
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
...
@@ -911,7 +911,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase):
...
@@ -911,7 +911,7 @@ class IntegrationTest(testutil.TestCase, test.TestCase):
# pylint: disable=test-inherits-tests, abstract-method
# pylint: disable=test-inherits-tests, abstract-method
@django_utils.override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
@django_utils.override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
class
Oauth2IntegrationTest
(
IntegrationTest
):
class
Oauth2IntegrationTest
(
IntegrationTest
):
"""Base test case for integration tests of Oauth2 providers."""
"""Base test case for integration tests of Oauth2 providers."""
...
...
lms/djangoapps/commerce/api/v0/tests/test_views.py
View file @
62241b2b
...
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
commerce.constants
import
Messages
from
commerce.constants
import
Messages
from
commerce.tests
import
TEST_BASKET_ID
,
TEST_ORDER_NUMBER
,
TEST_PAYMENT_DATA
,
TEST_API_URL
,
TEST_API_SIGNING_KEY
from
commerce.tests
import
TEST_BASKET_ID
,
TEST_ORDER_NUMBER
,
TEST_PAYMENT_DATA
from
commerce.tests.mocks
import
mock_basket_order
,
mock_create_basket
from
commerce.tests.mocks
import
mock_basket_order
,
mock_create_basket
from
commerce.tests.test_views
import
UserMixin
from
commerce.tests.test_views
import
UserMixin
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
...
@@ -39,7 +39,6 @@ UTM_COOKIE_CONTENTS = {
...
@@ -39,7 +39,6 @@ UTM_COOKIE_CONTENTS = {
@attr
(
shard
=
1
)
@attr
(
shard
=
1
)
@ddt.ddt
@ddt.ddt
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
class
BasketsViewTests
(
EnrollmentEventTestMixin
,
UserMixin
,
ModuleStoreTestCase
):
class
BasketsViewTests
(
EnrollmentEventTestMixin
,
UserMixin
,
ModuleStoreTestCase
):
"""
"""
Tests for the commerce orders view.
Tests for the commerce orders view.
...
@@ -276,7 +275,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
...
@@ -276,7 +275,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
# We should be enrolled in honor mode
# We should be enrolled in honor mode
self
.
_test_course_without_sku
(
enrollment_mode
=
CourseMode
.
HONOR
)
self
.
_test_course_without_sku
(
enrollment_mode
=
CourseMode
.
HONOR
)
@override_settings
(
ECOMMERCE_API_URL
=
None
,
ECOMMERCE_API_SIGNING_KEY
=
None
)
@override_settings
(
ECOMMERCE_API_URL
=
None
)
def
test_ecommerce_service_not_configured
(
self
):
def
test_ecommerce_service_not_configured
(
self
):
"""
"""
If the E-Commerce Service is not configured, the view should enroll the user.
If the E-Commerce Service is not configured, the view should enroll the user.
...
@@ -313,7 +312,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
...
@@ -313,7 +312,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
""" Verifies that the view behaves appropriately when the course only has a professional mode. """
""" Verifies that the view behaves appropriately when the course only has a professional mode. """
self
.
assertProfessionalModeBypassed
()
self
.
assertProfessionalModeBypassed
()
@override_settings
(
ECOMMERCE_API_URL
=
None
,
ECOMMERCE_API_SIGNING_KEY
=
None
)
@override_settings
(
ECOMMERCE_API_URL
=
None
)
def
test_professional_mode_only_and_ecommerce_service_not_configured
(
self
):
def
test_professional_mode_only_and_ecommerce_service_not_configured
(
self
):
"""
"""
Verifies that the view behaves appropriately when the course only has a professional mode and
Verifies that the view behaves appropriately when the course only has a professional mode and
...
@@ -390,7 +389,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
...
@@ -390,7 +389,6 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
@attr
(
shard
=
1
)
@attr
(
shard
=
1
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
class
BasketOrderViewTests
(
UserMixin
,
TestCase
):
class
BasketOrderViewTests
(
UserMixin
,
TestCase
):
""" Tests for the basket order view. """
""" Tests for the basket order view. """
view_name
=
'commerce_api:v0:baskets:retrieve_order'
view_name
=
'commerce_api:v0:baskets:retrieve_order'
...
...
lms/djangoapps/commerce/api/v1/tests/test_views.py
View file @
62241b2b
...
@@ -17,7 +17,6 @@ from rest_framework.utils.encoders import JSONEncoder
...
@@ -17,7 +17,6 @@ from rest_framework.utils.encoders import JSONEncoder
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
commerce.tests
import
TEST_API_URL
,
TEST_API_SIGNING_KEY
from
commerce.tests.mocks
import
mock_order_endpoint
from
commerce.tests.mocks
import
mock_order_endpoint
from
commerce.tests.test_views
import
UserMixin
from
commerce.tests.test_views
import
UserMixin
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
...
@@ -391,7 +390,6 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
...
@@ -391,7 +390,6 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
@attr
(
shard
=
1
)
@attr
(
shard
=
1
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
class
OrderViewTests
(
UserMixin
,
TestCase
):
class
OrderViewTests
(
UserMixin
,
TestCase
):
""" Tests for the basket order view. """
""" Tests for the basket order view. """
view_name
=
'commerce_api:v1:orders:detail'
view_name
=
'commerce_api:v1:orders:detail'
...
...
lms/djangoapps/commerce/migrations/0005_commerceconfiguration_enable_automatic_refund_approval.py
0 → 100644
View file @
62241b2b
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'commerce'
,
'0004_auto_20160531_0950'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'commerceconfiguration'
,
name
=
'enable_automatic_refund_approval'
,
field
=
models
.
BooleanField
(
default
=
True
,
help_text
=
'Automatically approve valid refund requests, without manual processing'
),
),
]
lms/djangoapps/commerce/models.py
View file @
62241b2b
...
@@ -39,6 +39,10 @@ class CommerceConfiguration(ConfigurationModel):
...
@@ -39,6 +39,10 @@ class CommerceConfiguration(ConfigurationModel):
default
=
DEFAULT_RECEIPT_PAGE_URL
,
default
=
DEFAULT_RECEIPT_PAGE_URL
,
help_text
=
_
(
'Path to order receipt page.'
)
help_text
=
_
(
'Path to order receipt page.'
)
)
)
enable_automatic_refund_approval
=
models
.
BooleanField
(
default
=
True
,
help_text
=
_
(
'Automatically approve valid refund requests, without manual processing'
)
)
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
"Commerce configuration"
return
"Commerce configuration"
...
...
lms/djangoapps/commerce/signals.py
View file @
62241b2b
...
@@ -9,23 +9,24 @@ from urlparse import urljoin
...
@@ -9,23 +9,24 @@ from urlparse import urljoin
import
requests
import
requests
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth
import
get_user_model
from
django.contrib.auth.models
import
AnonymousUser
from
django.contrib.auth.models
import
AnonymousUser
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
edx_rest_api_client.exceptions
import
HttpClientError
from
request_cache.middleware
import
RequestCache
from
student.models
import
UNENROLL_DONE
from
commerce.models
import
CommerceConfiguration
from
openedx.core.djangoapps.commerce.utils
import
ecommerce_api_client
,
is_commerce_service_configured
from
openedx.core.djangoapps.commerce.utils
import
ecommerce_api_client
,
is_commerce_service_configured
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.theming
import
helpers
as
theming_helpers
from
openedx.core.djangoapps.theming
import
helpers
as
theming_helpers
from
request_cache.middleware
import
RequestCache
from
student.models
import
UNENROLL_DONE
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
# pylint: disable=unused-argument
@receiver
(
UNENROLL_DONE
)
@receiver
(
UNENROLL_DONE
)
def
handle_unenroll_done
(
sender
,
course_enrollment
=
None
,
skip_refund
=
False
,
def
handle_unenroll_done
(
sender
,
course_enrollment
=
None
,
skip_refund
=
False
,
**
kwargs
):
**
kwargs
):
# pylint: disable=unused-argument
"""
"""
Signal receiver for unenrollments, used to automatically initiate refunds
Signal receiver for unenrollments, used to automatically initiate refunds
when applicable.
when applicable.
...
@@ -40,12 +41,12 @@ def handle_unenroll_done(sender, course_enrollment=None, skip_refund=False,
...
@@ -40,12 +41,12 @@ def handle_unenroll_done(sender, course_enrollment=None, skip_refund=False,
request_user
=
get_request_user
()
or
course_enrollment
.
user
request_user
=
get_request_user
()
or
course_enrollment
.
user
if
isinstance
(
request_user
,
AnonymousUser
):
if
isinstance
(
request_user
,
AnonymousUser
):
# Assume the request was initiated via server-to-server
# Assume the request was initiated via server-to-server
#
api
call (presumably Otto). In this case we cannot
#
API
call (presumably Otto). In this case we cannot
# construct a client to call Otto back anyway, because
# construct a client to call Otto back anyway, because
# the client does not work anonymously, and furthermore,
# the client does not work anonymously, and furthermore,
# there's certainly no need to inform Otto about this request.
# there's certainly no need to inform Otto about this request.
return
return
refund_seat
(
course_enrollment
,
request_user
)
refund_seat
(
course_enrollment
)
except
:
# pylint: disable=bare-except
except
:
# pylint: disable=bare-except
# don't assume the signal was fired with `send_robust`.
# don't assume the signal was fired with `send_robust`.
# avoid blowing up other signal handlers by gracefully
# avoid blowing up other signal handlers by gracefully
...
@@ -69,57 +70,56 @@ def get_request_user():
...
@@ -69,57 +70,56 @@ def get_request_user():
return
getattr
(
request
,
'user'
,
None
)
return
getattr
(
request
,
'user'
,
None
)
def
refund_seat
(
course_enrollment
,
request_user
):
def
refund_seat
(
course_enrollment
):
"""
"""
Attempt to initiate a refund for any orders associated with the seat being
Attempt to initiate a refund for any orders associated with the seat being unenrolled, using the commerce service.
unenrolled, using the commerce service.
Arguments:
Arguments:
course_enrollment (CourseEnrollment): a student enrollment
course_enrollment (CourseEnrollment): a student enrollment
request_user: the user as whom to authenticate to the commerce service
when attempting to initiate the refund.
Returns:
Returns:
A list of the external service's IDs for any refunds that were initiated
A list of the external service's IDs for any refunds that were initiated
(may be empty).
(may be empty).
Raises:
Raises:
exceptions.SlumberBaseException: for any unhandled HTTP error during
exceptions.SlumberBaseException: for any unhandled HTTP error during communication with the E-Commerce Service.
communication with the commerce service.
exceptions.Timeout: if the attempt to reach the commerce service timed out.
exceptions.Timeout: if the attempt to reach the commerce service timed
out.
"""
"""
User
=
get_user_model
()
# pylint:disable=invalid-name
course_key_str
=
unicode
(
course_enrollment
.
course_id
)
course_key_str
=
unicode
(
course_enrollment
.
course_id
)
unenrolled_user
=
course_enrollment
.
user
enrollee
=
course_enrollment
.
user
try
:
service_user
=
User
.
objects
.
get
(
username
=
settings
.
ECOMMERCE_SERVICE_WORKER_USERNAME
)
refund_ids
=
ecommerce_api_client
(
request_user
or
unenrolled_user
)
.
refunds
.
post
(
api_client
=
ecommerce_api_client
(
service_user
)
{
'course_id'
:
course_key_str
,
'username'
:
unenrolled_user
.
username
}
)
log
.
info
(
'Attempting to create a refund for user [
%
s], course [
%
s]...'
,
enrollee
.
id
,
course_key_str
)
except
HttpClientError
,
exc
:
if
exc
.
response
.
status_code
==
403
and
request_user
!=
unenrolled_user
:
refund_ids
=
api_client
.
refunds
.
post
({
'course_id'
:
course_key_str
,
'username'
:
enrollee
.
username
})
# this is a known limitation; commerce service does not presently
# support the case of a non-superusers initiating a refund on
# behalf of another user.
log
.
warning
(
"User [
%
s] was not authorized to initiate a refund for user [
%
s] "
"upon unenrollment from course [
%
s]"
,
request_user
.
id
,
unenrolled_user
.
id
,
course_key_str
)
return
[]
else
:
# no other error is anticipated, so re-raise the Exception
raise
exc
if
refund_ids
:
if
refund_ids
:
# at least one refundable order was found.
log
.
info
(
'Refund successfully opened for user [
%
s], course [
%
s]:
%
r'
,
enrollee
.
id
,
course_key_str
,
refund_ids
)
log
.
info
(
"Refund successfully opened for user [
%
s], course [
%
s]:
%
r"
,
config
=
CommerceConfiguration
.
current
()
unenrolled_user
.
id
,
course_key_str
,
if
config
.
enable_automatic_refund_approval
:
refund_ids
,
refunds_requiring_approval
=
[]
)
for
refund_id
in
refund_ids
:
try
:
# NOTE: Approve payment only because the user has already been unenrolled. Additionally, this
# ensures we don't tie up an additional web worker when the E-Commerce Service tries to unenroll
# the learner
api_client
.
refunds
(
refund_id
)
.
process
.
put
({
'action'
:
'approve_payment_only'
})
log
.
info
(
'Refund [
%
d] successfully approved.'
,
refund_id
)
except
:
# pylint: disable=bare-except
log
.
exception
(
'Failed to automatically approve refund [
%
d]!'
,
refund_id
)
refunds_requiring_approval
.
append
(
refund_id
)
else
:
refunds_requiring_approval
=
refund_ids
if
refunds_requiring_approval
:
# XCOM-371: this is a temporary measure to suppress refund-related email
# XCOM-371: this is a temporary measure to suppress refund-related email
# notifications to students and support@)
for free enrollments. This
# notifications to students and support
for free enrollments. This
# condition should be removed when the CourseEnrollment.refundable() logic
# condition should be removed when the CourseEnrollment.refundable() logic
# is updated to be more correct, or when we implement better handling (and
# is updated to be more correct, or when we implement better handling (and
# notifications) in Otto for handling reversal of $0 transactions.
# notifications) in Otto for handling reversal of $0 transactions.
...
@@ -127,20 +127,19 @@ def refund_seat(course_enrollment, request_user):
...
@@ -127,20 +127,19 @@ def refund_seat(course_enrollment, request_user):
# 'verified' is the only enrollment mode that should presently
# 'verified' is the only enrollment mode that should presently
# result in opening a refund request.
# result in opening a refund request.
log
.
info
(
log
.
info
(
"Skipping refund email notification for non-verified mode for user [
%
s], course [
%
s], mode: [
%
s]"
,
'Skipping refund email notification for non-verified mode for user [
%
s], course [
%
s], mode: [
%
s]'
,
course_enrollment
.
user
.
id
,
course_enrollment
.
user
.
id
,
course_enrollment
.
course_id
,
course_enrollment
.
course_id
,
course_enrollment
.
mode
,
course_enrollment
.
mode
,
)
)
else
:
else
:
try
:
try
:
send_refund_notification
(
course_enrollment
,
refund_ids
)
send_refund_notification
(
course_enrollment
,
refunds_requiring_approval
)
except
:
# pylint: disable=bare-except
except
:
# pylint: disable=bare-except
# don't break, just log a warning
# don't break, just log a warning
log
.
warning
(
"Could not send email notification for refund."
,
exc_info
=
True
)
log
.
warning
(
'Could not send email notification for refund.'
,
exc_info
=
True
)
else
:
else
:
# no refundable orders were found.
log
.
info
(
'No refund opened for user [
%
s], course [
%
s]'
,
enrollee
.
id
,
course_key_str
)
log
.
debug
(
"No refund opened for user [
%
s], course [
%
s]"
,
unenrolled_user
.
id
,
course_key_str
)
return
refund_ids
return
refund_ids
...
...
lms/djangoapps/commerce/tests/__init__.py
View file @
62241b2b
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
""" Commerce app tests package. """
""" Commerce app tests package. """
import
datetime
import
json
import
httpretty
import
mock
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
freezegun
import
freeze_time
from
freezegun
import
freeze_time
import
httpretty
import
jwt
import
mock
from
edx_rest_api_client
import
auth
from
openedx.core.djangoapps.commerce.utils
import
ecommerce_api_client
from
openedx.core.djangoapps.commerce.utils
import
ecommerce_api_client
from
openedx.core.lib.token_utils
import
JwtBuilder
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
JSON
=
'application/json'
JSON
=
'application/json'
TEST_PUBLIC_URL_ROOT
=
'http://www.example.com'
TEST_PUBLIC_URL_ROOT
=
'http://www.example.com'
TEST_API_URL
=
'http://www-internal.example.com/api'
TEST_API_URL
=
'http://www-internal.example.com/api'
TEST_API_SIGNING_KEY
=
'edx'
TEST_BASKET_ID
=
7
TEST_BASKET_ID
=
7
TEST_ORDER_NUMBER
=
'100004'
TEST_ORDER_NUMBER
=
'100004'
TEST_PAYMENT_DATA
=
{
TEST_PAYMENT_DATA
=
{
...
@@ -29,33 +23,27 @@ TEST_PAYMENT_DATA = {
...
@@ -29,33 +23,27 @@ TEST_PAYMENT_DATA = {
}
}
@override_settings
(
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_API_URL
=
TEST_API_URL
)
class
EdxRestApiClientTest
(
TestCase
):
class
EdxRestApiClientTest
(
TestCase
):
""" Tests to ensure the client is initialized properly. """
""" Tests to ensure the client is initialized properly. """
TEST_USER_EMAIL
=
'test@example.com'
TEST_CLIENT_ID
=
'test-client-id'
TEST_CLIENT_ID
=
'test-client-id'
def
setUp
(
self
):
def
setUp
(
self
):
super
(
EdxRestApiClientTest
,
self
)
.
setUp
()
super
(
EdxRestApiClientTest
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
self
.
user
=
UserFactory
()
self
.
user
.
email
=
self
.
TEST_USER_EMAIL
self
.
user
.
save
()
# pylint: disable=no-member
@httpretty.activate
@httpretty.activate
@freeze_time
(
'2015-7-2'
)
@freeze_time
(
'2015-7-2'
)
@override_settings
(
JWT_AUTH
=
{
'JWT_ISSUER'
:
'http://example.com/oauth'
,
'JWT_EXPIRATION'
:
30
})
def
test_tracking_context
(
self
):
def
test_tracking_context
(
self
):
"""
"""
Ensure the tracking context is set up in the api client correctly and
Ensure the tracking context is set up in the api client correctly and
automatically.
automatically.
"""
"""
# fake an
ecommerce api
request.
# fake an
E-Commerce API
request.
httpretty
.
register_uri
(
httpretty
.
register_uri
(
httpretty
.
POST
,
httpretty
.
POST
,
'{}/baskets/1/'
.
format
(
TEST_API_URL
),
'{}/baskets/1/'
.
format
(
settings
.
ECOMMERCE_API_URL
.
strip
(
'/'
)
),
status
=
200
,
body
=
'{}'
,
status
=
200
,
body
=
'{}'
,
adding_headers
=
{
'Content-Type'
:
JSON
}
adding_headers
=
{
'Content-Type'
:
JSON
}
)
)
...
@@ -65,23 +53,18 @@ class EdxRestApiClientTest(TestCase):
...
@@ -65,23 +53,18 @@ class EdxRestApiClientTest(TestCase):
with
mock
.
patch
(
'openedx.core.djangoapps.commerce.utils.tracker.get_tracker'
,
return_value
=
mock_tracker
):
with
mock
.
patch
(
'openedx.core.djangoapps.commerce.utils.tracker.get_tracker'
,
return_value
=
mock_tracker
):
ecommerce_api_client
(
self
.
user
)
.
baskets
(
1
)
.
post
()
ecommerce_api_client
(
self
.
user
)
.
baskets
(
1
)
.
post
()
#
make sure the request's JWT token payload included correct tracking context values.
#
Verify the JWT includes the tracking context for the user
actual_header
=
httpretty
.
last_request
()
.
headers
[
'Authorization'
]
actual_header
=
httpretty
.
last_request
()
.
headers
[
'Authorization'
]
expected_payload
=
{
'username'
:
self
.
user
.
username
,
claims
=
{
'full_name'
:
self
.
user
.
profile
.
name
,
'email'
:
self
.
user
.
email
,
'iss'
:
settings
.
JWT_AUTH
[
'JWT_ISSUER'
],
'iat'
:
datetime
.
datetime
.
utcnow
(),
'exp'
:
datetime
.
datetime
.
utcnow
()
+
datetime
.
timedelta
(
seconds
=
settings
.
JWT_AUTH
[
'JWT_EXPIRATION'
]),
'tracking_context'
:
{
'tracking_context'
:
{
'lms_user_id'
:
self
.
user
.
id
,
# pylint: disable=no-member
'lms_user_id'
:
self
.
user
.
id
,
# pylint: disable=no-member
'lms_client_id'
:
self
.
TEST_CLIENT_ID
,
'lms_client_id'
:
self
.
TEST_CLIENT_ID
,
'lms_ip'
:
'127.0.0.1'
,
'lms_ip'
:
'127.0.0.1'
,
},
}
}
}
expected_header
=
'JWT {}'
.
format
(
jwt
.
encode
(
expected_payload
,
TEST_API_SIGNING_KEY
))
expected_jwt
=
JwtBuilder
(
self
.
user
)
.
build_token
([
'email'
,
'profile'
],
additional_claims
=
claims
)
expected_header
=
'JWT {}'
.
format
(
expected_jwt
)
self
.
assertEqual
(
actual_header
,
expected_header
)
self
.
assertEqual
(
actual_header
,
expected_header
)
@httpretty.activate
@httpretty.activate
...
@@ -95,19 +78,9 @@ class EdxRestApiClientTest(TestCase):
...
@@ -95,19 +78,9 @@ class EdxRestApiClientTest(TestCase):
expected_content
=
'{"result": "Préparatoire"}'
expected_content
=
'{"result": "Préparatoire"}'
httpretty
.
register_uri
(
httpretty
.
register_uri
(
httpretty
.
GET
,
httpretty
.
GET
,
'{}/baskets/1/order/'
.
format
(
TEST_API_URL
),
'{}/baskets/1/order/'
.
format
(
settings
.
ECOMMERCE_API_URL
.
strip
(
'/'
)
),
status
=
200
,
body
=
expected_content
,
status
=
200
,
body
=
expected_content
,
adding_headers
=
{
'Content-Type'
:
JSON
},
adding_headers
=
{
'Content-Type'
:
JSON
},
)
)
actual_object
=
ecommerce_api_client
(
self
.
user
)
.
baskets
(
1
)
.
order
.
get
()
actual_object
=
ecommerce_api_client
(
self
.
user
)
.
baskets
(
1
)
.
order
.
get
()
self
.
assertEqual
(
actual_object
,
{
u"result"
:
u"Préparatoire"
})
self
.
assertEqual
(
actual_object
,
{
u"result"
:
u"Préparatoire"
})
def
test_client_with_user_without_profile
(
self
):
"""
Verify client initialize successfully for users having no profile.
"""
worker
=
User
.
objects
.
create_user
(
username
=
'test_worker'
,
email
=
'test@example.com'
)
api_client
=
ecommerce_api_client
(
worker
)
self
.
assertEqual
(
api_client
.
_store
[
'session'
]
.
auth
.
__dict__
[
'username'
],
worker
.
username
)
# pylint: disable=protected-access
self
.
assertIsNone
(
api_client
.
_store
[
'session'
]
.
auth
.
__dict__
[
'full_name'
])
# pylint: disable=protected-access
lms/djangoapps/commerce/tests/mocks.py
View file @
62241b2b
...
@@ -2,11 +2,14 @@
...
@@ -2,11 +2,14 @@
import
json
import
json
import
httpretty
import
httpretty
from
django.conf
import
settings
from
commerce.tests
import
TEST_API_URL
,
factories
from
commerce.tests
import
factories
class
mock_ecommerce_api_endpoint
(
object
):
# pylint: disable=invalid-name
# pylint: disable=invalid-name
class
mock_ecommerce_api_endpoint
(
object
):
"""
"""
Base class for contextmanagers used to mock calls to api endpoints.
Base class for contextmanagers used to mock calls to api endpoints.
...
@@ -21,7 +24,9 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
...
@@ -21,7 +24,9 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
# override this in subclasses, using one of httpretty's method constants
# override this in subclasses, using one of httpretty's method constants
method
=
None
method
=
None
def
__init__
(
self
,
response
=
None
,
status
=
200
,
expect_called
=
True
,
exception
=
None
):
host
=
settings
.
ECOMMERCE_API_URL
.
strip
(
'/'
)
def
__init__
(
self
,
response
=
None
,
status
=
200
,
expect_called
=
True
,
exception
=
None
,
reset_on_exit
=
True
):
"""
"""
Keyword Arguments:
Keyword Arguments:
response: a JSON-serializable Python type representing the desired response body.
response: a JSON-serializable Python type representing the desired response body.
...
@@ -29,17 +34,28 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
...
@@ -29,17 +34,28 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
expect_called: a boolean indicating whether an API request was expected; set
expect_called: a boolean indicating whether an API request was expected; set
to False if we should ensure that no request arrived.
to False if we should ensure that no request arrived.
exception: raise this exception instead of returning an HTTP response when called.
exception: raise this exception instead of returning an HTTP response when called.
reset_on_exit (bool): Indicates if `httpretty` should be reset after the decorator exits.
"""
"""
self
.
response
=
response
or
self
.
default_response
self
.
response
=
response
or
self
.
default_response
self
.
status
=
status
self
.
status
=
status
self
.
expect_called
=
expect_called
self
.
expect_called
=
expect_called
self
.
exception
=
exception
self
.
exception
=
exception
self
.
reset_on_exit
=
reset_on_exit
def
get_uri
(
self
):
def
get_uri
(
self
):
"""
"""
Return the uri to register with httpretty for this contextmanager.
Returns the uri to register with httpretty for this contextmanager.
"""
return
self
.
host
+
'/'
+
self
.
get_path
()
.
lstrip
(
'/'
)
def
get_path
(
self
):
"""
Returns the path of the URI to register with httpretty for this contextmanager.
Subclasses must override this method.
Subclasses must override this method.
Returns:
str
"""
"""
raise
NotImplementedError
raise
NotImplementedError
...
@@ -48,7 +64,6 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
...
@@ -48,7 +64,6 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
raise
self
.
exception
# pylint: disable=raising-bad-type
raise
self
.
exception
# pylint: disable=raising-bad-type
def
__enter__
(
self
):
def
__enter__
(
self
):
httpretty
.
reset
()
httpretty
.
enable
()
httpretty
.
enable
()
httpretty
.
register_uri
(
httpretty
.
register_uri
(
self
.
method
,
self
.
method
,
...
@@ -61,9 +76,11 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
...
@@ -61,9 +76,11 @@ class mock_ecommerce_api_endpoint(object): # pylint: disable=invalid-name
def
__exit__
(
self
,
exc_type
,
exc_val
,
exc_tb
):
def
__exit__
(
self
,
exc_type
,
exc_val
,
exc_tb
):
assert
self
.
expect_called
==
(
httpretty
.
last_request
()
.
headers
!=
{})
assert
self
.
expect_called
==
(
httpretty
.
last_request
()
.
headers
!=
{})
httpretty
.
disable
()
httpretty
.
disable
()
if
self
.
reset_on_exit
:
httpretty
.
reset
()
class
mock_create_basket
(
mock_ecommerce_api_endpoint
):
# pylint: disable=invalid-name
class
mock_create_basket
(
mock_ecommerce_api_endpoint
):
""" Mocks calls to E-Commerce API client basket creation method. """
""" Mocks calls to E-Commerce API client basket creation method. """
default_response
=
{
default_response
=
{
...
@@ -77,11 +94,11 @@ class mock_create_basket(mock_ecommerce_api_endpoint): # pylint: disable=invali
...
@@ -77,11 +94,11 @@ class mock_create_basket(mock_ecommerce_api_endpoint): # pylint: disable=invali
}
}
method
=
httpretty
.
POST
method
=
httpretty
.
POST
def
get_
uri
(
self
):
def
get_
path
(
self
):
return
TEST_API_URL
+
'/baskets/'
return
'/baskets/'
class
mock_basket_order
(
mock_ecommerce_api_endpoint
):
# pylint: disable=invalid-name
class
mock_basket_order
(
mock_ecommerce_api_endpoint
):
""" Mocks calls to E-Commerce API client basket order method. """
""" Mocks calls to E-Commerce API client basket order method. """
default_response
=
{
'number'
:
1
}
default_response
=
{
'number'
:
1
}
...
@@ -91,21 +108,35 @@ class mock_basket_order(mock_ecommerce_api_endpoint): # pylint: disable=invalid
...
@@ -91,21 +108,35 @@ class mock_basket_order(mock_ecommerce_api_endpoint): # pylint: disable=invalid
super
(
mock_basket_order
,
self
)
.
__init__
(
**
kwargs
)
super
(
mock_basket_order
,
self
)
.
__init__
(
**
kwargs
)
self
.
basket_id
=
basket_id
self
.
basket_id
=
basket_id
def
get_
uri
(
self
):
def
get_
path
(
self
):
return
TEST_API_URL
+
'/baskets/{}/order/'
.
format
(
self
.
basket_id
)
return
'/baskets/{}/order/'
.
format
(
self
.
basket_id
)
class
mock_create_refund
(
mock_ecommerce_api_endpoint
):
# pylint: disable=invalid-name
class
mock_create_refund
(
mock_ecommerce_api_endpoint
):
""" Mocks calls to E-Commerce API client refund creation method. """
""" Mocks calls to E-Commerce API client refund creation method. """
default_response
=
[]
default_response
=
[]
method
=
httpretty
.
POST
method
=
httpretty
.
POST
def
get_uri
(
self
):
def
get_path
(
self
):
return
TEST_API_URL
+
'/refunds/'
return
'/refunds/'
class
mock_process_refund
(
mock_ecommerce_api_endpoint
):
""" Mocks calls to E-Commerce API client refund process method. """
default_response
=
[]
method
=
httpretty
.
PUT
def
__init__
(
self
,
refund_id
,
**
kwargs
):
super
(
mock_process_refund
,
self
)
.
__init__
(
**
kwargs
)
self
.
refund_id
=
refund_id
class
mock_order_endpoint
(
mock_ecommerce_api_endpoint
):
# pylint: disable=invalid-name
def
get_path
(
self
):
return
'/refunds/{}/process/'
.
format
(
self
.
refund_id
)
class
mock_order_endpoint
(
mock_ecommerce_api_endpoint
):
""" Mocks calls to E-Commerce API client basket order method. """
""" Mocks calls to E-Commerce API client basket order method. """
default_response
=
{
'number'
:
'EDX-100001'
}
default_response
=
{
'number'
:
'EDX-100001'
}
...
@@ -115,11 +146,11 @@ class mock_order_endpoint(mock_ecommerce_api_endpoint): # pylint: disable=inval
...
@@ -115,11 +146,11 @@ class mock_order_endpoint(mock_ecommerce_api_endpoint): # pylint: disable=inval
super
(
mock_order_endpoint
,
self
)
.
__init__
(
**
kwargs
)
super
(
mock_order_endpoint
,
self
)
.
__init__
(
**
kwargs
)
self
.
order_number
=
order_number
self
.
order_number
=
order_number
def
get_
uri
(
self
):
def
get_
path
(
self
):
return
TEST_API_URL
+
'/orders/{}/'
.
format
(
self
.
order_number
)
return
'/orders/{}/'
.
format
(
self
.
order_number
)
class
mock_get_orders
(
mock_ecommerce_api_endpoint
):
# pylint: disable=invalid-name
class
mock_get_orders
(
mock_ecommerce_api_endpoint
):
""" Mocks calls to E-Commerce API client order get method. """
""" Mocks calls to E-Commerce API client order get method. """
default_response
=
{
default_response
=
{
...
@@ -138,5 +169,5 @@ class mock_get_orders(mock_ecommerce_api_endpoint): # pylint: disable=invalid-n
...
@@ -138,5 +169,5 @@ class mock_get_orders(mock_ecommerce_api_endpoint): # pylint: disable=invalid-n
}
}
method
=
httpretty
.
GET
method
=
httpretty
.
GET
def
get_
uri
(
self
):
def
get_
path
(
self
):
return
TEST_API_URL
+
'/orders/'
return
'/orders/'
lms/djangoapps/commerce/tests/test_signals.py
View file @
62241b2b
...
@@ -9,21 +9,22 @@ import json
...
@@ -9,21 +9,22 @@ import json
from
urlparse
import
urljoin
from
urlparse
import
urljoin
import
ddt
import
ddt
import
httpretty
import
mock
from
django.conf
import
settings
from
django.contrib.auth.models
import
AnonymousUser
from
django.contrib.auth.models
import
AnonymousUser
from
django.test
import
TestCase
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
import
httpretty
import
mock
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
requests
import
Timeout
from
requests
import
Timeout
from
commerce.models
import
CommerceConfiguration
from
commerce.signals
import
send_refund_notification
,
generate_refund_notification_body
,
create_zendesk_ticket
from
commerce.tests
import
JSON
from
commerce.tests.mocks
import
mock_create_refund
,
mock_process_refund
from
course_modes.models
import
CourseMode
from
student.models
import
UNENROLL_DONE
from
student.models
import
UNENROLL_DONE
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
commerce.signals
import
(
refund_seat
,
send_refund_notification
,
generate_refund_notification_body
,
create_zendesk_ticket
)
from
commerce.tests
import
TEST_PUBLIC_URL_ROOT
,
TEST_API_URL
,
TEST_API_SIGNING_KEY
,
JSON
from
commerce.tests.mocks
import
mock_create_refund
from
course_modes.models
import
CourseMode
ZENDESK_URL
=
'http://zendesk.example.com/'
ZENDESK_URL
=
'http://zendesk.example.com/'
ZENDESK_USER
=
'test@example.com'
ZENDESK_USER
=
'test@example.com'
...
@@ -31,11 +32,7 @@ ZENDESK_API_KEY = 'abc123'
...
@@ -31,11 +32,7 @@ ZENDESK_API_KEY = 'abc123'
@ddt.ddt
@ddt.ddt
@override_settings
(
@override_settings
(
ZENDESK_URL
=
ZENDESK_URL
,
ZENDESK_USER
=
ZENDESK_USER
,
ZENDESK_API_KEY
=
ZENDESK_API_KEY
)
ECOMMERCE_PUBLIC_URL_ROOT
=
TEST_PUBLIC_URL_ROOT
,
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ZENDESK_URL
=
ZENDESK_URL
,
ZENDESK_USER
=
ZENDESK_USER
,
ZENDESK_API_KEY
=
ZENDESK_API_KEY
)
class
TestRefundSignal
(
TestCase
):
class
TestRefundSignal
(
TestCase
):
"""
"""
Exercises logic triggered by the UNENROLL_DONE signal.
Exercises logic triggered by the UNENROLL_DONE signal.
...
@@ -43,6 +40,10 @@ class TestRefundSignal(TestCase):
...
@@ -43,6 +40,10 @@ class TestRefundSignal(TestCase):
def
setUp
(
self
):
def
setUp
(
self
):
super
(
TestRefundSignal
,
self
)
.
setUp
()
super
(
TestRefundSignal
,
self
)
.
setUp
()
# Ensure the E-Commerce service user exists
UserFactory
(
username
=
settings
.
ECOMMERCE_SERVICE_WORKER_USERNAME
,
is_staff
=
True
)
self
.
requester
=
UserFactory
(
username
=
"test-requester"
)
self
.
requester
=
UserFactory
(
username
=
"test-requester"
)
self
.
student
=
UserFactory
(
self
.
student
=
UserFactory
(
username
=
"test-student"
,
username
=
"test-student"
,
...
@@ -55,6 +56,10 @@ class TestRefundSignal(TestCase):
...
@@ -55,6 +56,10 @@ class TestRefundSignal(TestCase):
)
)
self
.
course_enrollment
.
refundable
=
mock
.
Mock
(
return_value
=
True
)
self
.
course_enrollment
.
refundable
=
mock
.
Mock
(
return_value
=
True
)
self
.
config
=
CommerceConfiguration
.
current
()
self
.
config
.
enable_automatic_refund_approval
=
True
self
.
config
.
save
()
def
send_signal
(
self
,
skip_refund
=
False
):
def
send_signal
(
self
,
skip_refund
=
False
):
"""
"""
DRY helper: emit the UNENROLL_DONE signal, as is done in
DRY helper: emit the UNENROLL_DONE signal, as is done in
...
@@ -65,7 +70,6 @@ class TestRefundSignal(TestCase):
...
@@ -65,7 +70,6 @@ class TestRefundSignal(TestCase):
@override_settings
(
@override_settings
(
ECOMMERCE_PUBLIC_URL_ROOT
=
None
,
ECOMMERCE_PUBLIC_URL_ROOT
=
None
,
ECOMMERCE_API_URL
=
None
,
ECOMMERCE_API_URL
=
None
,
ECOMMERCE_API_SIGNING_KEY
=
None
,
)
)
def
test_no_service
(
self
):
def
test_no_service
(
self
):
"""
"""
...
@@ -88,7 +92,7 @@ class TestRefundSignal(TestCase):
...
@@ -88,7 +92,7 @@ class TestRefundSignal(TestCase):
"""
"""
self
.
send_signal
()
self
.
send_signal
()
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,
self
.
student
))
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,))
# if skip_refund is set to True in the signal, we should not try to initiate a refund.
# if skip_refund is set to True in the signal, we should not try to initiate a refund.
mock_refund_seat
.
reset_mock
()
mock_refund_seat
.
reset_mock
()
...
@@ -110,21 +114,21 @@ class TestRefundSignal(TestCase):
...
@@ -110,21 +114,21 @@ class TestRefundSignal(TestCase):
# no HTTP request/user: auth to commerce service as the unenrolled student.
# no HTTP request/user: auth to commerce service as the unenrolled student.
self
.
send_signal
()
self
.
send_signal
()
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,
self
.
student
))
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,))
# HTTP user is the student: auth to commerce service as the unenrolled student.
# HTTP user is the student: auth to commerce service as the unenrolled student.
mock_get_request_user
.
return_value
=
self
.
student
mock_get_request_user
.
return_value
=
self
.
student
mock_refund_seat
.
reset_mock
()
mock_refund_seat
.
reset_mock
()
self
.
send_signal
()
self
.
send_signal
()
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,
self
.
student
))
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,))
# HTTP user is another user: auth to commerce service as the requester.
# HTTP user is another user: auth to commerce service as the requester.
mock_get_request_user
.
return_value
=
self
.
requester
mock_get_request_user
.
return_value
=
self
.
requester
mock_refund_seat
.
reset_mock
()
mock_refund_seat
.
reset_mock
()
self
.
send_signal
()
self
.
send_signal
()
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertTrue
(
mock_refund_seat
.
called
)
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,
self
.
requester
))
self
.
assertEqual
(
mock_refund_seat
.
call_args
[
0
],
(
self
.
course_enrollment
,))
# HTTP user is another server (AnonymousUser): do not try to initiate a refund at all.
# HTTP user is another server (AnonymousUser): do not try to initiate a refund at all.
mock_get_request_user
.
return_value
=
AnonymousUser
()
mock_get_request_user
.
return_value
=
AnonymousUser
()
...
@@ -132,15 +136,6 @@ class TestRefundSignal(TestCase):
...
@@ -132,15 +136,6 @@ class TestRefundSignal(TestCase):
self
.
send_signal
()
self
.
send_signal
()
self
.
assertFalse
(
mock_refund_seat
.
called
)
self
.
assertFalse
(
mock_refund_seat
.
called
)
@mock.patch
(
'commerce.signals.log.warning'
)
def
test_not_authorized_warning
(
self
,
mock_log_warning
):
"""
Ensure that expected authorization issues are logged as warnings.
"""
with
mock_create_refund
(
status
=
403
):
refund_seat
(
self
.
course_enrollment
,
UserFactory
())
self
.
assertTrue
(
mock_log_warning
.
called
)
@mock.patch
(
'commerce.signals.log.exception'
)
@mock.patch
(
'commerce.signals.log.exception'
)
def
test_error_logging
(
self
,
mock_log_exception
):
def
test_error_logging
(
self
,
mock_log_exception
):
"""
"""
...
@@ -152,14 +147,48 @@ class TestRefundSignal(TestCase):
...
@@ -152,14 +147,48 @@ class TestRefundSignal(TestCase):
self
.
assertTrue
(
mock_log_exception
.
called
)
self
.
assertTrue
(
mock_log_exception
.
called
)
@mock.patch
(
'commerce.signals.send_refund_notification'
)
@mock.patch
(
'commerce.signals.send_refund_notification'
)
def
test_notification
(
self
,
mock_send_notification
):
def
test_notification
_when_approval_fails
(
self
,
mock_send_notification
):
"""
"""
Ensure the notification function is triggered when refunds are
Ensure the notification function is triggered when refunds are initiated, and cannot be automatically approved.
initiated
"""
"""
with
mock_create_refund
(
status
=
200
,
response
=
[
1
,
2
,
3
]):
refund_id
=
1
failed_refund_id
=
2
with
mock_create_refund
(
status
=
201
,
response
=
[
refund_id
,
failed_refund_id
]):
with
mock_process_refund
(
refund_id
,
reset_on_exit
=
False
):
with
mock_process_refund
(
failed_refund_id
,
status
=
500
,
reset_on_exit
=
False
):
self
.
send_signal
()
self
.
send_signal
()
self
.
assertTrue
(
mock_send_notification
.
called
)
self
.
assertTrue
(
mock_send_notification
.
called
)
mock_send_notification
.
assert_called_with
(
self
.
course_enrollment
,
[
failed_refund_id
])
@mock.patch
(
'commerce.signals.send_refund_notification'
)
def
test_notification_if_automatic_approval_disabled
(
self
,
mock_send_notification
):
"""
Ensure the notification is always sent if the automatic approval functionality is disabled.
"""
refund_id
=
1
self
.
config
.
enable_automatic_refund_approval
=
False
self
.
config
.
save
()
with
mock_create_refund
(
status
=
201
,
response
=
[
refund_id
]):
self
.
send_signal
()
self
.
assertTrue
(
mock_send_notification
.
called
)
mock_send_notification
.
assert_called_with
(
self
.
course_enrollment
,
[
refund_id
])
@mock.patch
(
'commerce.signals.send_refund_notification'
)
def
test_no_notification_after_approval
(
self
,
mock_send_notification
):
"""
Ensure the notification function is triggered when refunds are initiated, and cannot be automatically approved.
"""
refund_id
=
1
with
mock_create_refund
(
status
=
201
,
response
=
[
refund_id
]):
with
mock_process_refund
(
refund_id
,
reset_on_exit
=
False
):
self
.
send_signal
()
self
.
assertFalse
(
mock_send_notification
.
called
)
last_request
=
httpretty
.
last_request
()
self
.
assertDictEqual
(
json
.
loads
(
last_request
.
body
),
{
'action'
:
'approve_payment_only'
})
@mock.patch
(
'commerce.signals.send_refund_notification'
)
@mock.patch
(
'commerce.signals.send_refund_notification'
)
def
test_notification_no_refund
(
self
,
mock_send_notification
):
def
test_notification_no_refund
(
self
,
mock_send_notification
):
...
...
lms/djangoapps/student_account/test/test_views.py
View file @
62241b2b
...
@@ -32,7 +32,7 @@ from provider.oauth2.models import (
...
@@ -32,7 +32,7 @@ from provider.oauth2.models import (
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
commerce.models
import
CommerceConfiguration
from
commerce.models
import
CommerceConfiguration
from
commerce.tests
import
TEST_API_URL
,
TEST_API_SIGNING_KEY
,
factories
from
commerce.tests
import
factories
from
commerce.tests.mocks
import
mock_get_orders
from
commerce.tests.mocks
import
mock_get_orders
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
from
openedx.core.djangoapps.oauth_dispatch.tests
import
factories
as
dot_factories
from
openedx.core.djangoapps.oauth_dispatch.tests
import
factories
as
dot_factories
...
@@ -506,7 +506,6 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
...
@@ -506,7 +506,6 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
})
})
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
class
AccountSettingsViewTest
(
ThirdPartyAuthTestMixin
,
TestCase
,
ProgramsApiConfigMixin
):
class
AccountSettingsViewTest
(
ThirdPartyAuthTestMixin
,
TestCase
,
ProgramsApiConfigMixin
):
""" Tests for the account settings view. """
""" Tests for the account settings view. """
...
...
lms/djangoapps/verify_student/tests/test_views.py
View file @
62241b2b
...
@@ -36,7 +36,7 @@ from course_modes.tests.factories import CourseModeFactory
...
@@ -36,7 +36,7 @@ from course_modes.tests.factories import CourseModeFactory
from
courseware.url_helpers
import
get_redirect_url
from
courseware.url_helpers
import
get_redirect_url
from
common.test.utils
import
XssTestMixin
from
common.test.utils
import
XssTestMixin
from
commerce.models
import
CommerceConfiguration
from
commerce.models
import
CommerceConfiguration
from
commerce.tests
import
TEST_PAYMENT_DATA
,
TEST_API_URL
,
TEST_
API_SIGNING_KEY
,
TEST_
PUBLIC_URL_ROOT
from
commerce.tests
import
TEST_PAYMENT_DATA
,
TEST_API_URL
,
TEST_PUBLIC_URL_ROOT
from
openedx.core.djangoapps.embargo.test_utils
import
restrict_course
from
openedx.core.djangoapps.embargo.test_utils
import
restrict_course
from
openedx.core.djangoapps.user_api.accounts.api
import
get_account_settings
from
openedx.core.djangoapps.user_api.accounts.api
import
get_account_settings
from
openedx.core.djangoapps.theming.tests.test_util
import
with_comprehensive_theme
from
openedx.core.djangoapps.theming.tests.test_util
import
with_comprehensive_theme
...
@@ -140,7 +140,6 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
...
@@ -140,7 +140,6 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
@httpretty.activate
@httpretty.activate
@override_settings
(
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_PUBLIC_URL_ROOT
=
TEST_PUBLIC_URL_ROOT
ECOMMERCE_PUBLIC_URL_ROOT
=
TEST_PUBLIC_URL_ROOT
)
)
def
test_start_flow_with_ecommerce
(
self
):
def
test_start_flow_with_ecommerce
(
self
):
...
@@ -1053,7 +1052,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
...
@@ -1053,7 +1052,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
self
.
assertEqual
(
response_dict
[
'course_name'
],
mode_display_name
)
self
.
assertEqual
(
response_dict
[
'course_name'
],
mode_display_name
)
@httpretty.activate
@httpretty.activate
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
@ddt.data
(
"verify_student_start_flow"
,
"verify_student_begin_flow"
)
@ddt.data
(
"verify_student_start_flow"
,
"verify_student_begin_flow"
)
def
test_processors_api
(
self
,
payment_flow
):
def
test_processors_api
(
self
,
payment_flow
):
"""
"""
...
@@ -1223,7 +1222,7 @@ class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase):
...
@@ -1223,7 +1222,7 @@ class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase):
@attr
(
shard
=
2
)
@attr
(
shard
=
2
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
@patch
(
@patch
(
'lms.djangoapps.verify_student.views.checkout_with_ecommerce_service'
,
'lms.djangoapps.verify_student.views.checkout_with_ecommerce_service'
,
return_value
=
TEST_PAYMENT_DATA
,
return_value
=
TEST_PAYMENT_DATA
,
...
@@ -1248,7 +1247,7 @@ class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
...
@@ -1248,7 +1247,7 @@ class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
"""
"""
@httpretty.activate
@httpretty.activate
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
)
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
def
test_create_basket
(
self
):
def
test_create_basket
(
self
):
"""
"""
Check that when working with a product being processed by the
Check that when working with a product being processed by the
...
...
lms/envs/aws.py
View file @
62241b2b
...
@@ -776,7 +776,6 @@ ONLOAD_BEACON_SAMPLE_RATE = ENV_TOKENS.get('ONLOAD_BEACON_SAMPLE_RATE', ONLOAD_B
...
@@ -776,7 +776,6 @@ ONLOAD_BEACON_SAMPLE_RATE = ENV_TOKENS.get('ONLOAD_BEACON_SAMPLE_RATE', ONLOAD_B
##### ECOMMERCE API CONFIGURATION SETTINGS #####
##### ECOMMERCE API CONFIGURATION SETTINGS #####
ECOMMERCE_PUBLIC_URL_ROOT
=
ENV_TOKENS
.
get
(
'ECOMMERCE_PUBLIC_URL_ROOT'
,
ECOMMERCE_PUBLIC_URL_ROOT
)
ECOMMERCE_PUBLIC_URL_ROOT
=
ENV_TOKENS
.
get
(
'ECOMMERCE_PUBLIC_URL_ROOT'
,
ECOMMERCE_PUBLIC_URL_ROOT
)
ECOMMERCE_API_URL
=
ENV_TOKENS
.
get
(
'ECOMMERCE_API_URL'
,
ECOMMERCE_API_URL
)
ECOMMERCE_API_URL
=
ENV_TOKENS
.
get
(
'ECOMMERCE_API_URL'
,
ECOMMERCE_API_URL
)
ECOMMERCE_API_SIGNING_KEY
=
AUTH_TOKENS
.
get
(
'ECOMMERCE_API_SIGNING_KEY'
,
ECOMMERCE_API_SIGNING_KEY
)
ECOMMERCE_API_TIMEOUT
=
ENV_TOKENS
.
get
(
'ECOMMERCE_API_TIMEOUT'
,
ECOMMERCE_API_TIMEOUT
)
ECOMMERCE_API_TIMEOUT
=
ENV_TOKENS
.
get
(
'ECOMMERCE_API_TIMEOUT'
,
ECOMMERCE_API_TIMEOUT
)
COURSE_CATALOG_API_URL
=
ENV_TOKENS
.
get
(
'COURSE_CATALOG_API_URL'
,
COURSE_CATALOG_API_URL
)
COURSE_CATALOG_API_URL
=
ENV_TOKENS
.
get
(
'COURSE_CATALOG_API_URL'
,
COURSE_CATALOG_API_URL
)
...
...
lms/envs/bok_choy.py
View file @
62241b2b
...
@@ -218,7 +218,6 @@ BADGING_BACKEND = 'lms.djangoapps.badges.backends.tests.dummy_backend.DummyBacke
...
@@ -218,7 +218,6 @@ BADGING_BACKEND = 'lms.djangoapps.badges.backends.tests.dummy_backend.DummyBacke
# Configure the LMS to use our stub eCommerce implementation
# Configure the LMS to use our stub eCommerce implementation
ECOMMERCE_API_URL
=
'http://localhost:8043/api/v2/'
ECOMMERCE_API_URL
=
'http://localhost:8043/api/v2/'
ECOMMERCE_API_SIGNING_KEY
=
'ecommerce-key'
LMS_ROOT_URL
=
"http://localhost:8000"
LMS_ROOT_URL
=
"http://localhost:8000"
DOC_LINK_BASE_URL
=
'http://edx.readthedocs.io/projects/edx-guide-for-students'
DOC_LINK_BASE_URL
=
'http://edx.readthedocs.io/projects/edx-guide-for-students'
...
...
lms/envs/common.py
View file @
62241b2b
...
@@ -2831,7 +2831,6 @@ ACCOUNT_VISIBILITY_CONFIGURATION = {
...
@@ -2831,7 +2831,6 @@ ACCOUNT_VISIBILITY_CONFIGURATION = {
# E-Commerce API Configuration
# E-Commerce API Configuration
ECOMMERCE_PUBLIC_URL_ROOT
=
None
ECOMMERCE_PUBLIC_URL_ROOT
=
None
ECOMMERCE_API_URL
=
None
ECOMMERCE_API_URL
=
None
ECOMMERCE_API_SIGNING_KEY
=
None
ECOMMERCE_API_TIMEOUT
=
5
ECOMMERCE_API_TIMEOUT
=
5
ECOMMERCE_SERVICE_WORKER_USERNAME
=
'ecommerce_worker'
ECOMMERCE_SERVICE_WORKER_USERNAME
=
'ecommerce_worker'
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
ENTERPRISE_SERVICE_WORKER_USERNAME
=
'enterprise_worker'
...
...
lms/envs/test.py
View file @
62241b2b
...
@@ -588,3 +588,5 @@ COMPREHENSIVE_THEME_DIRS = [REPO_ROOT / "themes", REPO_ROOT / "common/test"]
...
@@ -588,3 +588,5 @@ COMPREHENSIVE_THEME_DIRS = [REPO_ROOT / "themes", REPO_ROOT / "common/test"]
COMPREHENSIVE_THEME_LOCALE_PATHS
=
[
REPO_ROOT
/
"themes/conf/locale"
,
]
COMPREHENSIVE_THEME_LOCALE_PATHS
=
[
REPO_ROOT
/
"themes/conf/locale"
,
]
LMS_ROOT_URL
=
"http://localhost:8000"
LMS_ROOT_URL
=
"http://localhost:8000"
ECOMMERCE_API_URL
=
'https://ecommerce.example.com/api/v2/'
openedx/core/djangoapps/commerce/utils.py
View file @
62241b2b
...
@@ -4,13 +4,14 @@ from edx_rest_api_client.client import EdxRestApiClient
...
@@ -4,13 +4,14 @@ from edx_rest_api_client.client import EdxRestApiClient
from
eventtracking
import
tracker
from
eventtracking
import
tracker
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.lib.token_utils
import
JwtBuilder
ECOMMERCE_DATE_FORMAT
=
"
%
Y-
%
m-
%
dT
%
H:
%
M:
%
SZ"
ECOMMERCE_DATE_FORMAT
=
'
%
Y-
%
m-
%
dT
%
H:
%
M:
%
SZ'
def
create_tracking_context
(
user
):
def
create_tracking_context
(
user
):
""" Assembles attributes from user and request objects to be sent along
""" Assembles attributes from user and request objects to be sent along
in
ecommerce api
calls for tracking purposes. """
in
E-Commerce API
calls for tracking purposes. """
context_tracker
=
tracker
.
get_tracker
()
.
resolve_context
()
context_tracker
=
tracker
.
get_tracker
()
.
resolve_context
()
return
{
return
{
...
@@ -22,27 +23,19 @@ def create_tracking_context(user):
...
@@ -22,27 +23,19 @@ def create_tracking_context(user):
def
is_commerce_service_configured
():
def
is_commerce_service_configured
():
"""
"""
Return a Boolean indicating whether or not configuration is present to use
Return a Boolean indicating whether or not configuration is present to use the external commerce service.
the external commerce service.
"""
"""
ecommerce_api_url
=
configuration_helpers
.
get_value
(
"ECOMMERCE_API_URL"
,
settings
.
ECOMMERCE_API_URL
)
ecommerce_api_url
=
configuration_helpers
.
get_value
(
'ECOMMERCE_API_URL'
,
settings
.
ECOMMERCE_API_URL
)
ecommerce_api_signing_key
=
configuration_helpers
.
get_value
(
return
bool
(
ecommerce_api_url
)
"ECOMMERCE_API_SIGNING_KEY"
,
settings
.
ECOMMERCE_API_SIGNING_KEY
,
)
return
bool
(
ecommerce_api_url
and
ecommerce_api_signing_key
)
def
ecommerce_api_client
(
user
,
session
=
None
):
def
ecommerce_api_client
(
user
,
session
=
None
,
token_expiration
=
None
):
""" Returns an E-Commerce API client setup with authentication for the specified user. """
""" Returns an E-Commerce API client setup with authentication for the specified user. """
jwt_auth
=
configuration_helpers
.
get_value
(
"JWT_AUTH"
,
settings
.
JWT_AUTH
)
claims
=
{
'tracking_context'
:
create_tracking_context
(
user
)}
jwt
=
JwtBuilder
(
user
)
.
build_token
([
'email'
,
'profile'
],
expires_in
=
token_expiration
,
additional_claims
=
claims
)
return
EdxRestApiClient
(
return
EdxRestApiClient
(
configuration_helpers
.
get_value
(
"ECOMMERCE_API_URL"
,
settings
.
ECOMMERCE_API_URL
),
configuration_helpers
.
get_value
(
'ECOMMERCE_API_URL'
,
settings
.
ECOMMERCE_API_URL
),
configuration_helpers
.
get_value
(
"ECOMMERCE_API_SIGNING_KEY"
,
settings
.
ECOMMERCE_API_SIGNING_KEY
),
jwt
=
jwt
,
user
.
username
,
user
.
profile
.
name
if
hasattr
(
user
,
'profile'
)
else
None
,
user
.
email
,
tracking_context
=
create_tracking_context
(
user
),
issuer
=
jwt_auth
[
'JWT_ISSUER'
],
expires_in
=
jwt_auth
[
'JWT_EXPIRATION'
],
session
=
session
session
=
session
)
)
openedx/core/djangoapps/credit/tests/test_api.py
View file @
62241b2b
...
@@ -11,7 +11,7 @@ from django.test.utils import override_settings
...
@@ -11,7 +11,7 @@ from django.test.utils import override_settings
from
django.db
import
connection
from
django.db
import
connection
from
nose.plugins.attrib
import
attr
from
nose.plugins.attrib
import
attr
import
httpretty
import
httpretty
from
lms.djangoapps.commerce.tests
import
TEST_API_
SIGNING_KEY
,
TEST_API_
URL
from
lms.djangoapps.commerce.tests
import
TEST_API_URL
import
mock
import
mock
import
pytz
import
pytz
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
...
@@ -575,7 +575,6 @@ class CreditRequirementApiTests(CreditApiTestBase):
...
@@ -575,7 +575,6 @@ class CreditRequirementApiTests(CreditApiTestBase):
@httpretty.activate
@httpretty.activate
@override_settings
(
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_SERVICE_WORKER_USERNAME
=
TEST_ECOMMERCE_WORKER
ECOMMERCE_SERVICE_WORKER_USERNAME
=
TEST_ECOMMERCE_WORKER
)
)
def
test_satisfy_all_requirements
(
self
):
def
test_satisfy_all_requirements
(
self
):
...
@@ -622,7 +621,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
...
@@ -622,7 +621,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
self
.
assertFalse
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
self
.
assertFalse
(
api
.
is_user_eligible_for_credit
(
user
.
username
,
self
.
course_key
))
# Satisfy the other requirement
# Satisfy the other requirement
with
self
.
assertNumQueries
(
2
1
):
with
self
.
assertNumQueries
(
2
5
):
api
.
set_credit_requirement_status
(
api
.
set_credit_requirement_status
(
user
,
user
,
self
.
course_key
,
self
.
course_key
,
...
@@ -676,7 +675,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
...
@@ -676,7 +675,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
# Delete the eligibility entries and satisfy the user's eligibility
# Delete the eligibility entries and satisfy the user's eligibility
# requirement again to trigger eligibility notification
# requirement again to trigger eligibility notification
CreditEligibility
.
objects
.
all
()
.
delete
()
CreditEligibility
.
objects
.
all
()
.
delete
()
with
self
.
assertNumQueries
(
1
6
):
with
self
.
assertNumQueries
(
1
7
):
api
.
set_credit_requirement_status
(
api
.
set_credit_requirement_status
(
user
,
user
,
self
.
course_key
,
self
.
course_key
,
...
@@ -1167,7 +1166,6 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
...
@@ -1167,7 +1166,6 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
@skip_unless_lms
@skip_unless_lms
@override_settings
(
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_URL
=
TEST_API_URL
,
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_SERVICE_WORKER_USERNAME
=
TEST_ECOMMERCE_WORKER
ECOMMERCE_SERVICE_WORKER_USERNAME
=
TEST_ECOMMERCE_WORKER
)
)
@ddt.ddt
@ddt.ddt
...
...
openedx/core/lib/tests/test_edx_api_utils.py
View file @
62241b2b
...
@@ -19,7 +19,6 @@ from student.tests.factories import UserFactory
...
@@ -19,7 +19,6 @@ from student.tests.factories import UserFactory
UTILITY_MODULE
=
'openedx.core.lib.edx_api_utils'
UTILITY_MODULE
=
'openedx.core.lib.edx_api_utils'
TEST_API_URL
=
'http://www-internal.example.com/api'
TEST_API_URL
=
'http://www-internal.example.com/api'
TEST_API_SIGNING_KEY
=
'edx'
@skip_unless_lms
@skip_unless_lms
...
@@ -200,8 +199,7 @@ class TestGetEdxApiData(ProgramsApiConfigMixin, CacheIsolationTestCase):
...
@@ -200,8 +199,7 @@ class TestGetEdxApiData(ProgramsApiConfigMixin, CacheIsolationTestCase):
self
.
assertTrue
(
mock_exception
.
called
)
self
.
assertTrue
(
mock_exception
.
called
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@override_settings
(
JWT_AUTH
=
{
'JWT_ISSUER'
:
'http://example.com/oauth'
,
'JWT_EXPIRATION'
:
30
},
@override_settings
(
ECOMMERCE_API_URL
=
TEST_API_URL
)
ECOMMERCE_API_SIGNING_KEY
=
TEST_API_SIGNING_KEY
,
ECOMMERCE_API_URL
=
TEST_API_URL
)
def
test_client_passed
(
self
):
def
test_client_passed
(
self
):
""" Verify that when API client is passed edx_rest_api_client is not
""" Verify that when API client is passed edx_rest_api_client is not
used.
used.
...
...
openedx/core/lib/token_utils.py
View file @
62241b2b
...
@@ -33,17 +33,22 @@ class JwtBuilder(object):
...
@@ -33,17 +33,22 @@ class JwtBuilder(object):
self
.
secret
=
secret
self
.
secret
=
secret
self
.
jwt_auth
=
configuration_helpers
.
get_value
(
'JWT_AUTH'
,
settings
.
JWT_AUTH
)
self
.
jwt_auth
=
configuration_helpers
.
get_value
(
'JWT_AUTH'
,
settings
.
JWT_AUTH
)
def
build_token
(
self
,
scopes
,
expires_in
,
aud
=
None
):
def
build_token
(
self
,
scopes
,
expires_in
=
None
,
aud
=
None
,
additional_claims
=
None
):
"""Returns a JWT access token.
"""Returns a JWT access token.
Arguments:
Arguments:
scopes (list): Scopes controlling which optional claims are included in the token.
scopes (list): Scopes controlling which optional claims are included in the token.
expires_in (int): Time to token expiry, specified in seconds.
Keyword Arguments:
Keyword Arguments:
expires_in (int): Time to token expiry, specified in seconds.
aud (string): Overrides configured JWT audience claim.
aud (string): Overrides configured JWT audience claim.
additional_claims (dict): Additional claims to include in the token.
Returns:
str: Encoded JWT
"""
"""
now
=
int
(
time
())
now
=
int
(
time
())
expires_in
=
expires_in
or
self
.
jwt_auth
[
'JWT_EXPIRATION'
]
payload
=
{
payload
=
{
'aud'
:
aud
if
aud
else
self
.
jwt_auth
[
'JWT_AUDIENCE'
],
'aud'
:
aud
if
aud
else
self
.
jwt_auth
[
'JWT_AUDIENCE'
],
'exp'
:
now
+
expires_in
,
'exp'
:
now
+
expires_in
,
...
@@ -54,6 +59,9 @@ class JwtBuilder(object):
...
@@ -54,6 +59,9 @@ class JwtBuilder(object):
'sub'
:
anonymous_id_for_user
(
self
.
user
,
None
),
'sub'
:
anonymous_id_for_user
(
self
.
user
,
None
),
}
}
if
additional_claims
:
payload
.
update
(
additional_claims
)
for
scope
in
scopes
:
for
scope
in
scopes
:
handler
=
self
.
claim_handlers
.
get
(
scope
)
handler
=
self
.
claim_handlers
.
get
(
scope
)
...
...
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