Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
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
ecommerce
Commits
2383f489
Commit
2383f489
authored
May 15, 2018
by
Robert Raposa
Committed by
Jason Myatt
May 31, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reuse TieredCache.
LEARNER-5182
parent
da832628
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
455 additions
and
206 deletions
+455
-206
ecommerce/core/models.py
+29
-28
ecommerce/coupons/tests/mixins.py
+2
-2
ecommerce/coupons/tests/test_utils.py
+28
-3
ecommerce/coupons/utils.py
+26
-22
ecommerce/courses/tests/test_utils.py
+37
-13
ecommerce/courses/tests/test_views.py
+22
-0
ecommerce/courses/utils.py
+17
-12
ecommerce/courses/views.py
+17
-16
ecommerce/enterprise/api.py
+37
-31
ecommerce/enterprise/entitlements.py
+6
-5
ecommerce/enterprise/tests/test_api.py
+28
-6
ecommerce/enterprise/tests/test_entitlements.py
+64
-0
ecommerce/extensions/api/v2/tests/views/test_payments.py
+2
-2
ecommerce/extensions/api/v2/views/baskets.py
+5
-5
ecommerce/extensions/basket/tests/test_views.py
+5
-5
ecommerce/extensions/offer/models.py
+20
-18
ecommerce/extensions/offer/tests/test_models.py
+35
-0
ecommerce/extensions/order/tests/test_utils.py
+30
-0
ecommerce/extensions/order/utils.py
+6
-6
ecommerce/extensions/payment/signals.py
+2
-2
ecommerce/extensions/payment/tests/processors/test_signals.py
+3
-3
ecommerce/extensions/voucher/utils.py
+10
-7
ecommerce/programs/api.py
+10
-8
ecommerce/tests/mixins.py
+4
-2
ecommerce/tests/testcases.py
+10
-10
No files found.
ecommerce/core/models.py
View file @
2383f489
...
@@ -7,7 +7,6 @@ from dateutil.parser import parse
...
@@ -7,7 +7,6 @@ from dateutil.parser import parse
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth.models
import
AbstractUser
from
django.contrib.auth.models
import
AbstractUser
from
django.contrib.sites.models
import
Site
from
django.contrib.sites.models
import
Site
from
django.core.cache
import
cache
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
django.db
import
models
from
django.db
import
models
from
django.utils.functional
import
cached_property
from
django.utils.functional
import
cached_property
...
@@ -19,6 +18,7 @@ from requests.exceptions import ConnectionError, Timeout
...
@@ -19,6 +18,7 @@ from requests.exceptions import ConnectionError, Timeout
from
slumber.exceptions
import
HttpNotFoundError
,
SlumberBaseException
from
slumber.exceptions
import
HttpNotFoundError
,
SlumberBaseException
from
analytics
import
Client
as
SegmentClient
from
analytics
import
Client
as
SegmentClient
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.utils
import
log_message_and_raise_validation_error
from
ecommerce.core.utils
import
log_message_and_raise_validation_error
from
ecommerce.extensions.payment.exceptions
import
ProcessorNotFoundError
from
ecommerce.extensions.payment.exceptions
import
ProcessorNotFoundError
...
@@ -365,21 +365,20 @@ class SiteConfiguration(models.Model):
...
@@ -365,21 +365,20 @@ class SiteConfiguration(models.Model):
str: JWT access token
str: JWT access token
"""
"""
key
=
'siteconfiguration_access_token_{}'
.
format
(
self
.
id
)
key
=
'siteconfiguration_access_token_{}'
.
format
(
self
.
id
)
access_token
=
cache
.
get
(
key
)
access_token_cached_response
=
TieredCache
.
get_cached_response
(
key
)
if
access_token_cached_response
.
is_hit
:
# pylint: disable=unsubscriptable-object
return
access_token_cached_response
.
value
if
not
access_token
:
url
=
'{root}/access_token'
.
format
(
root
=
self
.
oauth2_provider_url
)
url
=
'{root}/access_token'
.
format
(
root
=
self
.
oauth2_provider_url
)
access_token
,
expiration_datetime
=
EdxRestApiClient
.
get_oauth_access_token
(
access_token
,
expiration_datetime
=
EdxRestApiClient
.
get_oauth_access_token
(
url
,
url
,
self
.
oauth_settings
[
'SOCIAL_AUTH_EDX_OIDC_KEY'
],
self
.
oauth_settings
[
'SOCIAL_AUTH_EDX_OIDC_KEY'
],
# pylint: disable=unsubscriptable-object
self
.
oauth_settings
[
'SOCIAL_AUTH_EDX_OIDC_SECRET'
],
self
.
oauth_settings
[
'SOCIAL_AUTH_EDX_OIDC_SECRET'
],
# pylint: disable=unsubscriptable-object
token_type
=
'jwt'
token_type
=
'jwt'
)
)
expires
=
(
expiration_datetime
-
datetime
.
datetime
.
utcnow
())
.
seconds
expires
=
(
expiration_datetime
-
datetime
.
datetime
.
utcnow
())
.
seconds
cache
.
set
(
key
,
access_token
,
expires
)
TieredCache
.
set_all_tiers
(
key
,
access_token
,
expires
)
return
access_token
return
access_token
@cached_property
@cached_property
...
@@ -542,18 +541,20 @@ class User(AbstractUser):
...
@@ -542,18 +541,20 @@ class User(AbstractUser):
try
:
try
:
cache_key
=
'verification_status_{username}'
.
format
(
username
=
self
.
username
)
cache_key
=
'verification_status_{username}'
.
format
(
username
=
self
.
username
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
verification
=
cache
.
get
(
cache_key
)
verification_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
verification
:
if
verification_cached_response
.
is_hit
:
api
=
EdxRestApiClient
(
return
verification_cached_response
.
value
site
.
siteconfiguration
.
build_lms_url
(
'api/user/v1/'
),
oauth_access_token
=
self
.
access_token
api
=
EdxRestApiClient
(
)
site
.
siteconfiguration
.
build_lms_url
(
'api/user/v1/'
),
response
=
api
.
accounts
(
self
.
username
)
.
verification_status
()
.
get
()
oauth_access_token
=
self
.
access_token
)
response
=
api
.
accounts
(
self
.
username
)
.
verification_status
()
.
get
()
verification
=
response
.
get
(
'is_verified'
,
False
)
verification
=
response
.
get
(
'is_verified'
,
False
)
if
verification
:
if
verification
:
cache_timeout
=
int
((
parse
(
response
.
get
(
'expiration_datetime'
))
-
now
())
.
total_seconds
())
cache_timeout
=
int
((
parse
(
response
.
get
(
'expiration_datetime'
))
-
now
())
.
total_seconds
())
cache
.
set
(
cache_key
,
verification
,
cache_timeout
)
TieredCache
.
set_all_tiers
(
cache_key
,
verification
,
cache_timeout
)
return
verification
return
verification
except
HttpNotFoundError
:
except
HttpNotFoundError
:
log
.
debug
(
'No verification data found for [
%
s]'
,
self
.
username
)
log
.
debug
(
'No verification data found for [
%
s]'
,
self
.
username
)
...
@@ -564,7 +565,7 @@ class User(AbstractUser):
...
@@ -564,7 +565,7 @@ class User(AbstractUser):
return
False
return
False
def
deactivate_account
(
self
,
site_configuration
):
def
deactivate_account
(
self
,
site_configuration
):
"""Deactive the user's account.
"""Deactiv
at
e the user's account.
Args:
Args:
site_configuration (SiteConfiguration): The site configuration
site_configuration (SiteConfiguration): The site configuration
...
...
ecommerce/coupons/tests/mixins.py
View file @
2383f489
...
@@ -2,11 +2,11 @@ import datetime
...
@@ -2,11 +2,11 @@ import datetime
import
json
import
json
import
httpretty
import
httpretty
from
django.core.cache
import
cache
from
django.test
import
RequestFactory
from
django.test
import
RequestFactory
from
oscar.core.utils
import
slugify
from
oscar.core.utils
import
slugify
from
oscar.test
import
factories
from
oscar.test
import
factories
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.constants
import
COUPON_PRODUCT_CLASS_NAME
from
ecommerce.core.constants
import
COUPON_PRODUCT_CLASS_NAME
from
ecommerce.core.models
import
BusinessClient
from
ecommerce.core.models
import
BusinessClient
from
ecommerce.extensions.api.v2.views.coupons
import
CouponViewSet
from
ecommerce.extensions.api.v2.views.coupons
import
CouponViewSet
...
@@ -20,7 +20,7 @@ class DiscoveryMockMixin(object):
...
@@ -20,7 +20,7 @@ class DiscoveryMockMixin(object):
""" Mocks for the Discovery service response. """
""" Mocks for the Discovery service response. """
def
setUp
(
self
):
def
setUp
(
self
):
super
(
DiscoveryMockMixin
,
self
)
.
setUp
()
super
(
DiscoveryMockMixin
,
self
)
.
setUp
()
cache
.
clear
()
TieredCache
.
clear_all_tiers
()
@staticmethod
@staticmethod
def
build_discovery_catalogs_url
(
discovery_api_url
,
catalog_id
=
''
):
def
build_discovery_catalogs_url
(
discovery_api_url
,
catalog_id
=
''
):
...
...
ecommerce/coupons/tests/test_utils.py
View file @
2383f489
import
ddt
import
ddt
import
httpretty
from
mock
import
patch
from
oscar.test.factories
import
ProductFactory
,
RangeFactory
,
VoucherFactory
from
oscar.test.factories
import
ProductFactory
,
RangeFactory
,
VoucherFactory
from
ecommerce.coupons.utils
import
is_voucher_applied
,
prepare_course_seat_types
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.coupons.tests.mixins
import
DiscoveryMockMixin
from
ecommerce.coupons.utils
import
fetch_course_catalog
,
is_voucher_applied
,
prepare_course_seat_types
from
ecommerce.extensions.basket.utils
import
prepare_basket
from
ecommerce.extensions.basket.utils
import
prepare_basket
from
ecommerce.extensions.test.factories
import
prepare_voucher
from
ecommerce.extensions.test.factories
import
prepare_voucher
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
@ddt.ddt
@ddt.ddt
class
CouponAppViewTests
(
TestCase
):
@httpretty.activate
class
CouponUtilsTests
(
TestCase
,
DiscoveryMockMixin
):
def
setUp
(
self
):
def
setUp
(
self
):
"""
"""
Setup variables for test cases.
Setup variables for test cases.
"""
"""
super
(
Coupon
AppView
Tests
,
self
)
.
setUp
()
super
(
Coupon
Utils
Tests
,
self
)
.
setUp
()
self
.
user
=
self
.
create_user
(
email
=
'test@tester.fake'
)
self
.
user
=
self
.
create_user
(
email
=
'test@tester.fake'
)
self
.
request
.
user
=
self
.
user
self
.
request
.
user
=
self
.
user
...
@@ -45,3 +50,23 @@ class CouponAppViewTests(TestCase):
...
@@ -45,3 +50,23 @@ class CouponAppViewTests(TestCase):
# Verify is_voucher_applied returns False when voucher can not be applied to the basket.
# Verify is_voucher_applied returns False when voucher can not be applied to the basket.
self
.
assertFalse
(
is_voucher_applied
(
basket
,
VoucherFactory
()))
self
.
assertFalse
(
is_voucher_applied
(
basket
,
VoucherFactory
()))
def
test_fetch_course_catalog
(
self
):
"""
Verify that fetch_course_catalog is cached
We expect 2 calls to set_all_tiers due to:
- the site_configuration api setup
- the result being cached
"""
self
.
mock_access_token_response
()
self
.
mock_catalog_detail_endpoint
(
self
.
site_configuration
.
discovery_api_url
)
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
_
=
fetch_course_catalog
(
self
.
site
,
1
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
_
=
fetch_course_catalog
(
self
.
site
,
1
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
ecommerce/coupons/utils.py
View file @
2383f489
...
@@ -3,10 +3,10 @@ import hashlib
...
@@ -3,10 +3,10 @@ import hashlib
import
logging
import
logging
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
slumber.exceptions
import
HttpNotFoundError
from
slumber.exceptions
import
HttpNotFoundError
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.core.utils
import
get_cache_key
Product
=
get_model
(
'catalogue'
,
'Product'
)
Product
=
get_model
(
'catalogue'
,
'Product'
)
...
@@ -66,18 +66,20 @@ def get_catalog_course_runs(site, query, limit=None, offset=None):
...
@@ -66,18 +66,20 @@ def get_catalog_course_runs(site, query, limit=None, offset=None):
)
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
response
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
response
:
if
cached_response
.
is_hit
:
api
=
site
.
siteconfiguration
.
discovery_api_client
return
cached_response
.
value
endpoint
=
getattr
(
api
,
api_resource_name
)
response
=
endpoint
()
.
get
(
partner
=
partner_code
,
q
=
query
,
limit
=
limit
,
offset
=
offset
)
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
api
=
site
.
siteconfiguration
.
discovery_api_client
endpoint
=
getattr
(
api
,
api_resource_name
)
response
=
endpoint
()
.
get
(
partner
=
partner_code
,
q
=
query
,
limit
=
limit
,
offset
=
offset
)
TieredCache
.
set_all_tiers
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
return
response
return
response
...
@@ -135,18 +137,20 @@ def fetch_course_catalog(site, catalog_id):
...
@@ -135,18 +137,20 @@ def fetch_course_catalog(site, catalog_id):
catalog_id
=
catalog_id
,
catalog_id
=
catalog_id
,
)
)
response
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
response
:
if
cached_response
.
is_hit
:
api
=
site
.
siteconfiguration
.
discovery_api_client
return
cached_response
.
value
endpoint
=
getattr
(
api
,
api_resource
)
api
=
site
.
siteconfiguration
.
discovery_api_client
endpoint
=
getattr
(
api
,
api_resource
)
try
:
try
:
response
=
endpoint
(
catalog_id
)
.
get
()
response
=
endpoint
(
catalog_id
)
.
get
()
except
HttpNotFoundError
:
except
HttpNotFoundError
:
logger
.
exception
(
"Catalog '
%
s' not found."
,
catalog_id
)
logger
.
exception
(
"Catalog '
%
s' not found."
,
catalog_id
)
raise
raise
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
TieredCache
.
set_all_tiers
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
return
response
return
response
...
...
ecommerce/courses/tests/test_utils.py
View file @
2383f489
...
@@ -2,10 +2,11 @@ import hashlib
...
@@ -2,10 +2,11 @@ import hashlib
import
ddt
import
ddt
import
httpretty
import
httpretty
from
django.core.cache
import
cache
from
mock
import
patch
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
requests.exceptions
import
ConnectionError
from
requests.exceptions
import
ConnectionError
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.coupons.tests.mixins
import
DiscoveryMockMixin
from
ecommerce.coupons.tests.mixins
import
DiscoveryMockMixin
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.utils
import
(
from
ecommerce.courses.utils
import
(
...
@@ -63,8 +64,8 @@ class UtilsTests(DiscoveryTestMixin, DiscoveryMockMixin, TestCase):
...
@@ -63,8 +64,8 @@ class UtilsTests(DiscoveryTestMixin, DiscoveryMockMixin, TestCase):
cache_key
=
'courses_api_detail_{}{}'
.
format
(
key
,
self
.
site
.
siteconfiguration
.
partner
.
short_code
)
cache_key
=
'courses_api_detail_{}{}'
.
format
(
key
,
self
.
site
.
siteconfiguration
.
partner
.
short_code
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
c
ached_course
=
cache
.
get
(
cache_key
)
c
ourse_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assert
IsNone
(
cached_course
)
self
.
assert
True
(
course_cached_response
.
is_miss
)
response
=
get_course_info_from_catalog
(
self
.
request
.
site
,
product
)
response
=
get_course_info_from_catalog
(
self
.
request
.
site
,
product
)
...
@@ -73,8 +74,31 @@ class UtilsTests(DiscoveryTestMixin, DiscoveryMockMixin, TestCase):
...
@@ -73,8 +74,31 @@ class UtilsTests(DiscoveryTestMixin, DiscoveryMockMixin, TestCase):
else
:
else
:
self
.
assertEqual
(
response
[
'title'
],
product
.
title
)
self
.
assertEqual
(
response
[
'title'
],
product
.
title
)
cached_course
=
cache
.
get
(
cache_key
)
course_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assertEqual
(
cached_course
,
response
)
self
.
assertEqual
(
course_cached_response
.
value
,
response
)
def
test_get_course_info_from_catalog_cached
(
self
):
"""
Verify that get_course_info_from_catalog is cached
We expect 2 calls to set_all_tiers in the get_course_info_from_catalog
method due to:
- the site_configuration api setup
- the result being cached
"""
self
.
mock_access_token_response
()
product
=
create_or_update_course_entitlement
(
'verified'
,
100
,
self
.
partner
,
'foo-bar'
,
'Foo Bar Entitlement'
)
self
.
mock_course_detail_endpoint
(
product
,
discovery_api_url
=
self
.
site_configuration
.
discovery_api_url
)
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
_
=
get_course_info_from_catalog
(
self
.
request
.
site
,
product
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
_
=
get_course_info_from_catalog
(
self
.
request
.
site
,
product
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
@ddt.data
(
@ddt.data
(
(
'honor'
,
'Honor'
),
(
'honor'
,
'Honor'
),
...
@@ -113,8 +137,8 @@ class GetCourseCatalogUtilTests(DiscoveryMockMixin, TestCase):
...
@@ -113,8 +137,8 @@ class GetCourseCatalogUtilTests(DiscoveryMockMixin, TestCase):
"""
"""
cache_key
=
'{}.catalog.api.data'
.
format
(
self
.
request
.
site
.
domain
)
cache_key
=
'{}.catalog.api.data'
.
format
(
self
.
request
.
site
.
domain
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
c
ached_course_catalogs
=
cache
.
get
(
cache_key
)
c
ourse_catalogs_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assert
IsNone
(
cached_course_catalog
s
)
self
.
assert
True
(
course_catalogs_cached_response
.
is_mis
s
)
response
=
get_course_catalogs
(
self
.
request
.
site
)
response
=
get_course_catalogs
(
self
.
request
.
site
)
...
@@ -122,8 +146,8 @@ class GetCourseCatalogUtilTests(DiscoveryMockMixin, TestCase):
...
@@ -122,8 +146,8 @@ class GetCourseCatalogUtilTests(DiscoveryMockMixin, TestCase):
for
catalog_index
,
catalog
in
enumerate
(
response
):
for
catalog_index
,
catalog
in
enumerate
(
response
):
self
.
assertEqual
(
catalog
[
'name'
],
catalog_name_list
[
catalog_index
])
self
.
assertEqual
(
catalog
[
'name'
],
catalog_name_list
[
catalog_index
])
c
ached_course
=
cache
.
get
(
cache_key
)
c
ourse_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assertEqual
(
c
ached_cours
e
,
response
)
self
.
assertEqual
(
c
ourse_cached_response
.
valu
e
,
response
)
def
test_get_course_catalogs_for_single_catalog_with_id
(
self
):
def
test_get_course_catalogs_for_single_catalog_with_id
(
self
):
"""
"""
...
@@ -136,14 +160,14 @@ class GetCourseCatalogUtilTests(DiscoveryMockMixin, TestCase):
...
@@ -136,14 +160,14 @@ class GetCourseCatalogUtilTests(DiscoveryMockMixin, TestCase):
catalog_id
=
1
catalog_id
=
1
cache_key
=
'{}.catalog.api.data.{}'
.
format
(
self
.
request
.
site
.
domain
,
catalog_id
)
cache_key
=
'{}.catalog.api.data.{}'
.
format
(
self
.
request
.
site
.
domain
,
catalog_id
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
c
ached_course_catalog
=
cache
.
get
(
cache_key
)
c
ourse_catalogs_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assert
IsNone
(
cached_course_catalog
)
self
.
assert
True
(
course_catalogs_cached_response
.
is_miss
)
response
=
get_course_catalogs
(
self
.
request
.
site
,
catalog_id
)
response
=
get_course_catalogs
(
self
.
request
.
site
,
catalog_id
)
self
.
assertEqual
(
response
[
'name'
],
'All Courses'
)
self
.
assertEqual
(
response
[
'name'
],
'All Courses'
)
c
ached_course
=
cache
.
get
(
cache_key
)
c
ourse_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assertEqual
(
c
ached_cours
e
,
response
)
self
.
assertEqual
(
c
ourse_cached_response
.
valu
e
,
response
)
# Verify the API was actually hit (not the cache)
# Verify the API was actually hit (not the cache)
self
.
_assert_num_requests
(
2
)
self
.
_assert_num_requests
(
2
)
...
...
ecommerce/courses/tests/test_views.py
View file @
2383f489
...
@@ -4,8 +4,10 @@ import ddt
...
@@ -4,8 +4,10 @@ import ddt
import
httpretty
import
httpretty
from
django.conf
import
settings
from
django.conf
import
settings
from
django.urls
import
reverse
from
django.urls
import
reverse
from
mock
import
patch
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
...
@@ -129,6 +131,26 @@ class CourseAppViewTests(TestCase):
...
@@ -129,6 +131,26 @@ class CourseAppViewTests(TestCase):
self
.
assertEqual
(
response
.
context
[
'credit_providers'
],
provider_json
)
self
.
assertEqual
(
response
.
context
[
'credit_providers'
],
provider_json
)
@httpretty.activate
@httpretty.activate
def
test_credit_providers_in_context_cached
(
self
):
""" Verify the cached context data includes a list of credit providers. """
self
.
_create_and_login_staff_user
()
__
,
provider_json
=
self
.
mock_credit_api_providers
()
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
response
=
self
.
client
.
get
(
self
.
path
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
context
[
'credit_providers'
],
provider_json
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
1
)
response
=
self
.
client
.
get
(
self
.
path
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
context
[
'credit_providers'
],
provider_json
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
1
)
@httpretty.activate
def
test_credit_api_failure
(
self
):
def
test_credit_api_failure
(
self
):
""" Verify the view logs an error if it fails to retrieve credit providers. """
""" Verify the view logs an error if it fails to retrieve credit providers. """
# Setup staff user with an OAuth 2 access token
# Setup staff user with an OAuth 2 access token
...
...
ecommerce/courses/utils.py
View file @
2383f489
import
hashlib
import
hashlib
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.utils
import
deprecated_traverse_pagination
from
ecommerce.core.utils
import
deprecated_traverse_pagination
...
@@ -32,15 +32,19 @@ def get_course_info_from_catalog(site, product):
...
@@ -32,15 +32,19 @@ def get_course_info_from_catalog(site, product):
api
=
site
.
siteconfiguration
.
discovery_api_client
api
=
site
.
siteconfiguration
.
discovery_api_client
partner_short_code
=
site
.
siteconfiguration
.
partner
.
short_code
partner_short_code
=
site
.
siteconfiguration
.
partner
.
short_code
cache_key
=
'courses_api_detail_{}{}'
.
format
(
key
,
partner_short_code
)
cache_key
=
'courses_api_detail_{}{}'
.
format
(
key
,
partner_short_code
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
course
=
cache
.
get
(
cache_key
)
course_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
course
:
# pragma: no cover
if
course_cached_response
.
is_hit
:
if
product
.
is_course_entitlement_product
:
return
course_cached_response
.
value
course
=
api
.
courses
(
key
)
.
get
()
else
:
if
product
.
is_course_entitlement_product
:
course
=
api
.
course_runs
(
key
)
.
get
(
partner
=
partner_short_code
)
course
=
api
.
courses
(
key
)
.
get
()
cache
.
set
(
cache_key
,
course
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
else
:
course
=
api
.
course_runs
(
key
)
.
get
(
partner
=
partner_short_code
)
TieredCache
.
set_all_tiers
(
cache_key
,
course
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
return
course
return
course
...
@@ -66,9 +70,10 @@ def get_course_catalogs(site, resource_id=None):
...
@@ -66,9 +70,10 @@ def get_course_catalogs(site, resource_id=None):
cache_key
=
'{}.{}'
.
format
(
base_cache_key
,
resource_id
)
if
resource_id
else
base_cache_key
cache_key
=
'{}.{}'
.
format
(
base_cache_key
,
resource_id
)
if
resource_id
else
base_cache_key
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cached
=
cache
.
get
(
cache_key
)
if
cached
:
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
return
cached
if
cached_response
.
is_hit
:
return
cached_response
.
value
api
=
site
.
siteconfiguration
.
discovery_api_client
api
=
site
.
siteconfiguration
.
discovery_api_client
endpoint
=
getattr
(
api
,
resource
)
endpoint
=
getattr
(
api
,
resource
)
...
@@ -79,7 +84,7 @@ def get_course_catalogs(site, resource_id=None):
...
@@ -79,7 +84,7 @@ def get_course_catalogs(site, resource_id=None):
else
:
else
:
results
=
deprecated_traverse_pagination
(
response
,
endpoint
)
results
=
deprecated_traverse_pagination
(
response
,
endpoint
)
cache
.
set
(
cache_key
,
results
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
TieredCache
.
set_all_tiers
(
cache_key
,
results
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
return
results
return
results
...
...
ecommerce/courses/views.py
View file @
2383f489
...
@@ -4,7 +4,6 @@ import os
...
@@ -4,7 +4,6 @@ import os
from
io
import
StringIO
from
io
import
StringIO
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.core.management
import
call_command
from
django.core.management
import
call_command
from
django.http
import
Http404
,
HttpResponse
from
django.http
import
Http404
,
HttpResponse
from
django.views.generic
import
TemplateView
,
View
from
django.views.generic
import
TemplateView
,
View
...
@@ -12,6 +11,7 @@ from edx_rest_api_client.client import EdxRestApiClient
...
@@ -12,6 +11,7 @@ from edx_rest_api_client.client import EdxRestApiClient
from
requests
import
Timeout
from
requests
import
Timeout
from
slumber.exceptions
import
SlumberBaseException
from
slumber.exceptions
import
SlumberBaseException
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.views
import
StaffOnlyMixin
from
ecommerce.core.views
import
StaffOnlyMixin
from
ecommerce.extensions.partner.shortcuts
import
get_partner_for_site
from
ecommerce.extensions.partner.shortcuts
import
get_partner_for_site
...
@@ -42,22 +42,23 @@ class CourseAppView(StaffOnlyMixin, TemplateView):
...
@@ -42,22 +42,23 @@ class CourseAppView(StaffOnlyMixin, TemplateView):
Results will be sorted alphabetically by display name.
Results will be sorted alphabetically by display name.
"""
"""
key
=
'credit_providers'
key
=
'credit_providers'
credit_providers
=
cache
.
get
(
key
,
[])
credit_providers_cache_response
=
TieredCache
.
get_cached_response
(
key
)
if
credit_providers_cache_response
.
is_hit
:
if
not
credit_providers
:
return
credit_providers_cache_response
.
value
try
:
credit_api
=
EdxRestApiClient
(
get_lms_url
(
'/api/credit/v1/'
),
oauth_access_token
=
self
.
request
.
user
.
access_token
)
credit_providers
=
credit_api
.
providers
.
get
()
credit_providers
.
sort
(
key
=
lambda
provider
:
provider
[
'display_name'
])
# Update the cache
cache
.
set
(
key
,
credit_providers
,
settings
.
CREDIT_PROVIDER_CACHE_TIMEOUT
)
except
(
SlumberBaseException
,
Timeout
):
logger
.
exception
(
'Failed to retrieve credit providers!'
)
try
:
credit_api
=
EdxRestApiClient
(
get_lms_url
(
'/api/credit/v1/'
),
oauth_access_token
=
self
.
request
.
user
.
access_token
)
credit_providers
=
credit_api
.
providers
.
get
()
credit_providers
.
sort
(
key
=
lambda
provider
:
provider
[
'display_name'
])
# Update the cache
TieredCache
.
set_all_tiers
(
key
,
credit_providers
,
settings
.
CREDIT_PROVIDER_CACHE_TIMEOUT
)
except
(
SlumberBaseException
,
Timeout
):
logger
.
exception
(
'Failed to retrieve credit providers!'
)
credit_providers
=
[]
return
credit_providers
return
credit_providers
...
...
ecommerce/enterprise/api.py
View file @
2383f489
...
@@ -5,10 +5,10 @@ import logging
...
@@ -5,10 +5,10 @@ import logging
from
urllib
import
urlencode
from
urllib
import
urlencode
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberHttpBaseException
from
slumber.exceptions
import
SlumberHttpBaseException
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.core.utils
import
get_cache_key
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -56,12 +56,14 @@ def fetch_enterprise_learner_entitlements(site, learner_id):
...
@@ -56,12 +56,14 @@ def fetch_enterprise_learner_entitlements(site, learner_id):
learner_id
=
learner_id
learner_id
=
learner_id
)
)
entitlements
=
cache
.
get
(
cache_key
)
entitlements_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
entitlements
:
if
entitlements_cached_response
.
is_hit
:
api
=
site
.
siteconfiguration
.
enterprise_api_client
return
entitlements_cached_response
.
value
entitlements
=
getattr
(
api
,
resource_url
)
.
get
()
cache
.
set
(
cache_key
,
entitlements
,
settings
.
ENTERPRISE_API_CACHE_TIMEOUT
)
api
=
site
.
siteconfiguration
.
enterprise_api_client
entitlements
=
getattr
(
api
,
resource_url
)
.
get
()
TieredCache
.
set_all_tiers
(
cache_key
,
entitlements
,
settings
.
ENTERPRISE_API_CACHE_TIMEOUT
)
return
entitlements
return
entitlements
...
@@ -153,14 +155,16 @@ def fetch_enterprise_learner_data(site, user):
...
@@ -153,14 +155,16 @@ def fetch_enterprise_learner_data(site, user):
username
=
user
.
username
username
=
user
.
username
)
)
response
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
response
:
if
cached_response
.
is_hit
:
api
=
site
.
siteconfiguration
.
enterprise_api_client
return
cached_response
.
value
endpoint
=
getattr
(
api
,
api_resource_name
)
querystring
=
{
'username'
:
user
.
username
}
api
=
site
.
siteconfiguration
.
enterprise_api_client
response
=
endpoint
()
.
get
(
**
querystring
)
endpoint
=
getattr
(
api
,
api_resource_name
)
cache
.
set
(
cache_key
,
response
,
settings
.
ENTERPRISE_API_CACHE_TIMEOUT
)
querystring
=
{
'username'
:
user
.
username
}
response
=
endpoint
()
.
get
(
**
querystring
)
TieredCache
.
set_all_tiers
(
cache_key
,
response
,
settings
.
ENTERPRISE_API_CACHE_TIMEOUT
)
return
response
return
response
...
@@ -184,22 +188,24 @@ def catalog_contains_course_runs(site, course_run_ids, enterprise_customer_uuid,
...
@@ -184,22 +188,24 @@ def catalog_contains_course_runs(site, course_run_ids, enterprise_customer_uuid,
query_params
=
urlencode
(
query_params
,
True
)
query_params
=
urlencode
(
query_params
,
True
)
)
)
contains_content
=
cache
.
get
(
cache_key
)
contains_content_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
contains_content
is
None
:
if
contains_content_cached_response
.
is_hit
:
api
=
site
.
siteconfiguration
.
enterprise_api_client
return
contains_content_cached_response
.
value
endpoint
=
getattr
(
api
,
api_resource_name
)(
api_resource_id
)
try
:
api
=
site
.
siteconfiguration
.
enterprise_api_client
contains_content
=
endpoint
.
contains_content_items
.
get
(
**
query_params
)[
'contains_content_items'
]
endpoint
=
getattr
(
api
,
api_resource_name
)(
api_resource_id
)
cache
.
set
(
cache_key
,
contains_content
,
settings
.
ENTERPRISE_API_CACHE_TIMEOUT
)
try
:
except
(
ConnectionError
,
KeyError
,
SlumberHttpBaseException
,
Timeout
):
contains_content
=
endpoint
.
contains_content_items
.
get
(
**
query_params
)[
'contains_content_items'
]
logger
.
exception
(
'Failed to check if course_runs [
%
s] exist in '
TieredCache
.
set_all_tiers
(
cache_key
,
contains_content
,
settings
.
ENTERPRISE_API_CACHE_TIMEOUT
)
'EnterpriseCustomerCatalog [
%
s]'
except
(
ConnectionError
,
KeyError
,
SlumberHttpBaseException
,
Timeout
):
'for EnterpriseCustomer [
%
s].'
,
logger
.
exception
(
course_run_ids
,
'Failed to check if course_runs [
%
s] exist in '
enterprise_customer_catalog_uuid
,
'EnterpriseCustomerCatalog [
%
s]'
enterprise_customer_uuid
,
'for EnterpriseCustomer [
%
s].'
,
)
course_run_ids
,
contains_content
=
False
enterprise_customer_catalog_uuid
,
enterprise_customer_uuid
,
)
contains_content
=
False
return
contains_content
return
contains_content
ecommerce/enterprise/entitlements.py
View file @
2383f489
...
@@ -12,13 +12,13 @@ from collections import OrderedDict
...
@@ -12,13 +12,13 @@ from collections import OrderedDict
from
urllib
import
urlencode
from
urllib
import
urlencode
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.http
import
HttpResponseRedirect
from
django.http
import
HttpResponseRedirect
from
django.urls
import
reverse
from
django.urls
import
reverse
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberBaseException
from
slumber.exceptions
import
SlumberBaseException
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.constants
import
COUPON_PRODUCT_CLASS_NAME
from
ecommerce.core.constants
import
COUPON_PRODUCT_CLASS_NAME
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.coupons.views
import
voucher_is_valid
from
ecommerce.coupons.views
import
voucher_is_valid
...
@@ -168,14 +168,15 @@ def is_course_in_enterprise_catalog(site, course_id, enterprise_catalog_id):
...
@@ -168,14 +168,15 @@ def is_course_in_enterprise_catalog(site, course_id, enterprise_catalog_id):
course_id
=
course_id
,
course_id
=
course_id
,
catalog_id
=
enterprise_catalog_id
catalog_id
=
enterprise_catalog_id
)
)
response
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
response
:
if
cached_response
.
is_hit
:
response
=
cached_response
.
value
else
:
try
:
try
:
# GET: /api/v1/catalogs/{catalog_id}/contains?course_run_id={course_run_ids}
response
=
site
.
siteconfiguration
.
discovery_api_client
.
catalogs
(
enterprise_catalog_id
)
.
contains
.
get
(
response
=
site
.
siteconfiguration
.
discovery_api_client
.
catalogs
(
enterprise_catalog_id
)
.
contains
.
get
(
course_run_id
=
course_id
course_run_id
=
course_id
)
)
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
TieredCache
.
set_all_tiers
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
logger
.
exception
(
'Unable to connect to Discovery Service for catalog contains endpoint.'
)
logger
.
exception
(
'Unable to connect to Discovery Service for catalog contains endpoint.'
)
return
False
return
False
...
...
ecommerce/enterprise/tests/test_api.py
View file @
2383f489
import
ddt
import
ddt
import
httpretty
import
httpretty
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
mock
import
patch
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.enterprise
import
api
as
enterprise_api
from
ecommerce.enterprise
import
api
as
enterprise_api
from
ecommerce.enterprise.tests.mixins
import
EnterpriseServiceMockMixin
from
ecommerce.enterprise.tests.mixins
import
EnterpriseServiceMockMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
DiscoveryTestMixin
from
ecommerce.extensions.partner.strategy
import
DefaultStrategy
from
ecommerce.extensions.partner.strategy
import
DefaultStrategy
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
...
@@ -18,7 +20,7 @@ StockRecord = get_model('partner', 'StockRecord')
...
@@ -18,7 +20,7 @@ StockRecord = get_model('partner', 'StockRecord')
@ddt.ddt
@ddt.ddt
@httpretty.activate
@httpretty.activate
class
EnterpriseAPITests
(
EnterpriseServiceMockMixin
,
TestCase
):
class
EnterpriseAPITests
(
EnterpriseServiceMockMixin
,
DiscoveryTestMixin
,
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
super
(
EnterpriseAPITests
,
self
)
.
setUp
()
super
(
EnterpriseAPITests
,
self
)
.
setUp
()
self
.
course_run
=
CourseFactory
()
self
.
course_run
=
CourseFactory
()
...
@@ -54,14 +56,14 @@ class EnterpriseAPITests(EnterpriseServiceMockMixin, TestCase):
...
@@ -54,14 +56,14 @@ class EnterpriseAPITests(EnterpriseServiceMockMixin, TestCase):
username
=
self
.
learner
.
username
username
=
self
.
learner
.
username
)
)
cached_enterprise_learner_response
=
cache
.
get
(
cache_key
)
enterprise_learner_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assert
IsNone
(
cached_enterprise_learner_response
)
self
.
assert
True
(
enterprise_learner_cached_response
.
is_miss
)
response
=
enterprise_api
.
fetch_enterprise_learner_data
(
self
.
request
.
site
,
self
.
learner
)
response
=
enterprise_api
.
fetch_enterprise_learner_data
(
self
.
request
.
site
,
self
.
learner
)
self
.
assertEqual
(
len
(
response
[
'results'
]),
1
)
self
.
assertEqual
(
len
(
response
[
'results'
]),
1
)
c
ached_course
=
cache
.
get
(
cache_key
)
c
ourse_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assertEqual
(
c
ached_cours
e
,
response
)
self
.
assertEqual
(
c
ourse_cached_response
.
valu
e
,
response
)
def
_assert_contains_course_runs
(
self
,
expected
,
course_run_ids
,
enterprise_customer_uuid
,
def
_assert_contains_course_runs
(
self
,
expected
,
course_run_ids
,
enterprise_customer_uuid
,
enterprise_customer_catalog_uuid
):
enterprise_customer_catalog_uuid
):
...
@@ -145,6 +147,26 @@ class EnterpriseAPITests(EnterpriseServiceMockMixin, TestCase):
...
@@ -145,6 +147,26 @@ class EnterpriseAPITests(EnterpriseServiceMockMixin, TestCase):
self
.
_assert_contains_course_runs
(
expected
,
[
self
.
course_run
.
id
],
'fake-uuid'
,
enterprise_customer_catalog_uuid
)
self
.
_assert_contains_course_runs
(
expected
,
[
self
.
course_run
.
id
],
'fake-uuid'
,
enterprise_customer_catalog_uuid
)
def
test_catalog_contains_course_runs_cache_hit
(
self
):
"""
Verify `catalog_contains_course_runs` returns a cached response
"""
self
.
mock_catalog_contains_course_runs
(
[
self
.
course_run
.
id
],
'fake-uuid'
,
enterprise_customer_catalog_uuid
=
None
,
contains_content
=
True
,
)
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
self
.
_assert_contains_course_runs
(
True
,
[
self
.
course_run
.
id
],
'fake-uuid'
,
None
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
self
.
_assert_contains_course_runs
(
True
,
[
self
.
course_run
.
id
],
'fake-uuid'
,
None
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
def
test_catalog_contains_course_runs_with_api_exception
(
self
):
def
test_catalog_contains_course_runs_with_api_exception
(
self
):
"""
"""
Verify that method `catalog_contains_course_runs` returns the appropriate response
Verify that method `catalog_contains_course_runs` returns the appropriate response
...
...
ecommerce/enterprise/tests/test_entitlements.py
View file @
2383f489
...
@@ -2,11 +2,13 @@
...
@@ -2,11 +2,13 @@
import
ddt
import
ddt
import
httpretty
import
httpretty
from
django.conf
import
settings
from
django.conf
import
settings
from
mock
import
patch
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberBaseException
from
slumber.exceptions
import
SlumberBaseException
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.coupons.tests.mixins
import
CouponMixin
,
DiscoveryMockMixin
from
ecommerce.coupons.tests.mixins
import
CouponMixin
,
DiscoveryMockMixin
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.tests.factories
import
CourseFactory
...
@@ -371,6 +373,36 @@ class EntitlementsTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, Discover
...
@@ -371,6 +373,36 @@ class EntitlementsTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, Discover
self
.
_assert_num_requests
(
2
)
self
.
_assert_num_requests
(
2
)
self
.
assertTrue
(
is_course_available
)
self
.
assertTrue
(
is_course_available
)
def
test_is_course_in_enterprise_catalog_for_available_course_cached
(
self
):
"""
Verify that the response from the discovery API call made in method
"is_course_in_enterprise_catalog" is cached for cases where the
course is available in the enterprise course catalog.
We expect 2 calls to set_all_tiers in the
is_course_in_enterprise_catalog method due to:
- the site_configuration api setup
- the result being cached
"""
enterprise_catalog_id
=
1
self
.
mock_access_token_response
()
self
.
mock_catalog_contains_endpoint
(
discovery_api_url
=
self
.
site_configuration
.
discovery_api_url
,
catalog_id
=
enterprise_catalog_id
,
course_run_ids
=
[
self
.
course
.
id
]
)
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
is_course_available
=
is_course_in_enterprise_catalog
(
self
.
request
.
site
,
self
.
course
.
id
,
enterprise_catalog_id
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
self
.
assertTrue
(
is_course_available
)
is_course_available
=
is_course_in_enterprise_catalog
(
self
.
request
.
site
,
self
.
course
.
id
,
enterprise_catalog_id
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
self
.
assertTrue
(
is_course_available
)
def
test_is_course_in_enterprise_catalog_for_unavailable_course
(
self
):
def
test_is_course_in_enterprise_catalog_for_unavailable_course
(
self
):
"""
"""
Verify that method "is_course_in_enterprise_catalog" returns False if
Verify that method "is_course_in_enterprise_catalog" returns False if
...
@@ -391,6 +423,38 @@ class EntitlementsTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, Discover
...
@@ -391,6 +423,38 @@ class EntitlementsTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, Discover
self
.
_assert_num_requests
(
2
)
self
.
_assert_num_requests
(
2
)
self
.
assertFalse
(
is_course_available
)
self
.
assertFalse
(
is_course_available
)
def
test_is_course_in_enterprise_catalog_for_unavailable_course_cached
(
self
):
"""
Verify that the response from the discovery API call made in method
"is_course_in_enterprise_catalog" is cached for cases where the
course is not available in the enterprise course catalog.
We expect 2 calls to set_all_tiers due to:
- the site_configuration api setup
- the result being cached
"""
enterprise_catalog_id
=
1
self
.
mock_access_token_response
()
self
.
mock_catalog_contains_endpoint
(
discovery_api_url
=
self
.
site_configuration
.
discovery_api_url
,
catalog_id
=
enterprise_catalog_id
,
course_run_ids
=
[
self
.
course
.
id
]
)
test_course
=
CourseFactory
(
id
=
'edx/Non_Enterprise_Course/DemoX'
)
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
is_course_available
=
is_course_in_enterprise_catalog
(
self
.
request
.
site
,
test_course
.
id
,
enterprise_catalog_id
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
self
.
assertFalse
(
is_course_available
)
is_course_available
=
is_course_in_enterprise_catalog
(
self
.
request
.
site
,
test_course
.
id
,
enterprise_catalog_id
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
self
.
assertFalse
(
is_course_available
)
@ddt.data
(
ConnectionError
,
SlumberBaseException
,
Timeout
)
@ddt.data
(
ConnectionError
,
SlumberBaseException
,
Timeout
)
def
test_is_course_in_enterprise_catalog_for_error_in_get_course_catalogs
(
self
,
error
):
def
test_is_course_in_enterprise_catalog_for_error_in_get_course_catalogs
(
self
,
error
):
"""
"""
...
...
ecommerce/extensions/api/v2/tests/views/test_payments.py
View file @
2383f489
import
json
import
json
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.test
import
override_settings
from
django.test
import
override_settings
from
django.urls
import
reverse
from
django.urls
import
reverse
from
waffle.models
import
Switch
from
waffle.models
import
Switch
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.models
import
SiteConfiguration
from
ecommerce.core.models
import
SiteConfiguration
from
ecommerce.extensions.payment.tests.processors
import
AnotherDummyProcessor
,
DummyProcessor
from
ecommerce.extensions.payment.tests.processors
import
AnotherDummyProcessor
,
DummyProcessor
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
...
@@ -34,7 +34,7 @@ class PaymentProcessorListViewTests(TestCase):
...
@@ -34,7 +34,7 @@ class PaymentProcessorListViewTests(TestCase):
self
.
addCleanup
(
reset_site_config
)
self
.
addCleanup
(
reset_site_config
)
# Clear the view cache
# Clear the view cache
cache
.
clear
()
TieredCache
.
clear_all_tiers
()
def
toggle_payment_processor
(
self
,
processor
,
active
):
def
toggle_payment_processor
(
self
,
processor
,
active
):
"""Set the given payment processor's Waffle switch."""
"""Set the given payment processor's Waffle switch."""
...
...
ecommerce/extensions/api/v2/views/baskets.py
View file @
2383f489
...
@@ -7,7 +7,6 @@ import warnings
...
@@ -7,7 +7,6 @@ import warnings
import
waffle
import
waffle
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth
import
get_user_model
from
django.contrib.auth
import
get_user_model
from
django.core.cache
import
cache
from
django.db
import
transaction
from
django.db
import
transaction
from
django.http
import
HttpResponseBadRequest
,
HttpResponseForbidden
from
django.http
import
HttpResponseBadRequest
,
HttpResponseForbidden
from
django.utils.decorators
import
method_decorator
from
django.utils.decorators
import
method_decorator
...
@@ -18,6 +17,7 @@ from rest_framework import generics, status
...
@@ -18,6 +17,7 @@ from rest_framework import generics, status
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.enterprise.entitlements
import
get_entitlement_voucher
from
ecommerce.enterprise.entitlements
import
get_entitlement_voucher
from
ecommerce.extensions.analytics.utils
import
audit_log
from
ecommerce.extensions.analytics.utils
import
audit_log
...
@@ -514,9 +514,9 @@ class BasketCalculateView(generics.GenericAPIView):
...
@@ -514,9 +514,9 @@ class BasketCalculateView(generics.GenericAPIView):
resource_name
=
'calculate'
,
resource_name
=
'calculate'
,
skus
=
skus
skus
=
skus
)
)
basket_calculate_results
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
basket_calculate_results
:
if
cached_response
.
is_hit
:
return
Response
(
basket_calculate_results
)
return
Response
(
cached_response
.
value
)
if
waffle
.
flag_is_active
(
request
,
"disable_calculate_temporary_basket_atomic_transaction"
):
if
waffle
.
flag_is_active
(
request
,
"disable_calculate_temporary_basket_atomic_transaction"
):
response
=
self
.
_calculate_temporary_basket
(
basket_owner
,
request
,
products
,
voucher
,
skus
,
code
)
response
=
self
.
_calculate_temporary_basket
(
basket_owner
,
request
,
products
,
voucher
,
skus
,
code
)
...
@@ -524,6 +524,6 @@ class BasketCalculateView(generics.GenericAPIView):
...
@@ -524,6 +524,6 @@ class BasketCalculateView(generics.GenericAPIView):
response
=
self
.
_calculate_temporary_basket_atomic
(
basket_owner
,
request
,
products
,
voucher
,
skus
,
code
)
response
=
self
.
_calculate_temporary_basket_atomic
(
basket_owner
,
request
,
products
,
voucher
,
skus
,
code
)
if
response
and
use_default_basket
:
if
response
and
use_default_basket
:
cache
.
set
(
cache_key
,
response
,
settings
.
ANONYMOUS_BASKET_CALCULATE_CACHE_TIMEOUT
)
TieredCache
.
set_all_tiers
(
cache_key
,
response
,
settings
.
ANONYMOUS_BASKET_CALCULATE_CACHE_TIMEOUT
)
return
Response
(
response
)
return
Response
(
response
)
ecommerce/extensions/basket/tests/test_views.py
View file @
2383f489
...
@@ -9,7 +9,6 @@ import mock
...
@@ -9,7 +9,6 @@ import mock
import
pytz
import
pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.messages
import
get_messages
from
django.contrib.messages
import
get_messages
from
django.core.cache
import
cache
from
django.http
import
HttpResponseRedirect
from
django.http
import
HttpResponseRedirect
from
django.test
import
override_settings
from
django.test
import
override_settings
from
django.urls
import
reverse
from
django.urls
import
reverse
...
@@ -23,6 +22,7 @@ from slumber.exceptions import SlumberBaseException
...
@@ -23,6 +22,7 @@ from slumber.exceptions import SlumberBaseException
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
waffle.testutils
import
override_flag
from
waffle.testutils
import
override_flag
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.constants
import
ENROLLMENT_CODE_PRODUCT_CLASS_NAME
from
ecommerce.core.constants
import
ENROLLMENT_CODE_PRODUCT_CLASS_NAME
from
ecommerce.core.exceptions
import
SiteConfigurationError
from
ecommerce.core.exceptions
import
SiteConfigurationError
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
...
@@ -465,13 +465,13 @@ class BasketSummaryViewTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, Dis
...
@@ -465,13 +465,13 @@ class BasketSummaryViewTests(EnterpriseServiceMockMixin, DiscoveryTestMixin, Dis
cache_key
=
'courses_api_detail_{}{}'
.
format
(
self
.
course
.
id
,
self
.
site
.
siteconfiguration
.
partner
.
short_code
)
cache_key
=
'courses_api_detail_{}{}'
.
format
(
self
.
course
.
id
,
self
.
site
.
siteconfiguration
.
partner
.
short_code
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
c
ached_course_before
=
cache
.
get
(
cache_key
)
c
ourse_before_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assert
IsNone
(
cached_course_before
)
self
.
assert
True
(
course_before_cached_response
.
is_miss
)
response
=
self
.
client
.
get
(
self
.
path
)
response
=
self
.
client
.
get
(
self
.
path
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
c
ached_course_after
=
cache
.
get
(
cache_key
)
c
ourse_after_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
self
.
assertEqual
(
c
ached_course_after
[
'title'
],
self
.
course
.
name
)
self
.
assertEqual
(
c
ourse_after_cached_response
.
value
[
'title'
],
self
.
course
.
name
)
@ddt.data
({
@ddt.data
({
'course'
:
'edX+DemoX'
,
'course'
:
'edX+DemoX'
,
...
...
ecommerce/extensions/offer/models.py
View file @
2383f489
...
@@ -4,7 +4,6 @@ import logging
...
@@ -4,7 +4,6 @@ import logging
import
re
import
re
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
oscar.apps.offer.abstract_models
import
(
from
oscar.apps.offer.abstract_models
import
(
...
@@ -18,6 +17,7 @@ from requests.exceptions import ConnectionError, Timeout
...
@@ -18,6 +17,7 @@ from requests.exceptions import ConnectionError, Timeout
from
slumber.exceptions
import
SlumberBaseException
from
slumber.exceptions
import
SlumberBaseException
from
threadlocals.threadlocals
import
get_current_request
from
threadlocals.threadlocals
import
get_current_request
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.utils
import
get_cache_key
,
log_message_and_raise_validation_error
from
ecommerce.core.utils
import
get_cache_key
,
log_message_and_raise_validation_error
OFFER_PRIORITY_ENTERPRISE
=
10
OFFER_PRIORITY_ENTERPRISE
=
10
...
@@ -78,14 +78,14 @@ class Benefit(AbstractBenefit):
...
@@ -78,14 +78,14 @@ class Benefit(AbstractBenefit):
course_id
=
product_id
,
course_id
=
product_id
,
query
=
query
query
=
query
)
)
response
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
response
is
False
:
if
cached_response
.
is_miss
:
applicable_lines
.
remove
(
line
)
elif
response
is
None
:
if
line
.
product
.
is_seat_product
:
if
line
.
product
.
is_seat_product
:
course_run_ids
.
append
({
'id'
:
product_id
,
'cache_key'
:
cache_key
,
'line'
:
line
})
course_run_ids
.
append
({
'id'
:
product_id
,
'cache_key'
:
cache_key
,
'line'
:
line
})
else
:
else
:
course_uuids
.
append
({
'id'
:
product_id
,
'cache_key'
:
cache_key
,
'line'
:
line
})
course_uuids
.
append
({
'id'
:
product_id
,
'cache_key'
:
cache_key
,
'line'
:
line
})
elif
cached_response
.
value
is
False
:
applicable_lines
.
remove
(
line
)
return
course_run_ids
,
course_uuids
,
applicable_lines
return
course_run_ids
,
course_uuids
,
applicable_lines
def
get_applicable_lines
(
self
,
offer
,
basket
,
range
=
None
):
# pylint: disable=redefined-builtin
def
get_applicable_lines
(
self
,
offer
,
basket
,
range
=
None
):
# pylint: disable=redefined-builtin
...
@@ -116,7 +116,7 @@ class Benefit(AbstractBenefit):
...
@@ -116,7 +116,7 @@ class Benefit(AbstractBenefit):
# Cache range-state individually for each course or run identifier and remove lines not in the range.
# Cache range-state individually for each course or run identifier and remove lines not in the range.
for
metadata
in
course_run_ids
+
course_uuids
:
for
metadata
in
course_run_ids
+
course_uuids
:
in_range
=
response
[
str
(
metadata
[
'id'
])]
in_range
=
response
[
str
(
metadata
[
'id'
])]
cache
.
set
(
metadata
[
'cache_key'
],
in_range
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
TieredCache
.
set_all_tiers
(
metadata
[
'cache_key'
],
in_range
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
if
not
in_range
:
if
not
in_range
:
applicable_lines
.
remove
(
metadata
[
'line'
])
applicable_lines
.
remove
(
metadata
[
'line'
])
return
[(
line
.
product
.
stockrecords
.
first
()
.
price_excl_tax
,
line
)
for
line
in
applicable_lines
]
return
[(
line
.
product
.
stockrecords
.
first
()
.
price_excl_tax
,
line
)
for
line
in
applicable_lines
]
...
@@ -334,19 +334,21 @@ class Range(AbstractRange):
...
@@ -334,19 +334,21 @@ class Range(AbstractRange):
course_id
=
product
.
course_id
,
course_id
=
product
.
course_id
,
catalog_id
=
self
.
course_catalog
catalog_id
=
self
.
course_catalog
)
)
response
=
cache
.
get
(
cache_key
)
cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
response
:
if
cached_response
.
is_hit
:
discovery_api_client
=
request
.
site
.
siteconfiguration
.
discovery_api_client
return
cached_response
.
value
try
:
# GET: /api/v1/catalogs/{catalog_id}/contains?course_run_id={course_run_ids}
discovery_api_client
=
request
.
site
.
siteconfiguration
.
discovery_api_client
response
=
discovery_api_client
.
catalogs
(
self
.
course_catalog
)
.
contains
.
get
(
try
:
course_run_id
=
product
.
course_id
# GET: /api/v1/catalogs/{catalog_id}/contains?course_run_id={course_run_ids}
)
response
=
discovery_api_client
.
catalogs
(
self
.
course_catalog
)
.
contains
.
get
(
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
course_run_id
=
product
.
course_id
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
)
raise
Exception
(
'Unable to connect to Discovery Service for catalog contains endpoint.'
)
return
response
TieredCache
.
set_all_tiers
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
return
response
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
raise
Exception
(
'Unable to connect to Discovery Service for catalog contains endpoint.'
)
def
contains_product
(
self
,
product
):
def
contains_product
(
self
,
product
):
"""
"""
...
...
ecommerce/extensions/offer/tests/test_models.py
View file @
2383f489
...
@@ -4,11 +4,13 @@ from __future__ import unicode_literals
...
@@ -4,11 +4,13 @@ from __future__ import unicode_literals
import
ddt
import
ddt
import
httpretty
import
httpretty
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
mock
import
patch
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
oscar.test
import
factories
from
oscar.test
import
factories
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberBaseException
from
slumber.exceptions
import
SlumberBaseException
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.coupons.tests.mixins
import
CouponMixin
,
DiscoveryMockMixin
from
ecommerce.coupons.tests.mixins
import
CouponMixin
,
DiscoveryMockMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
DiscoveryTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
DiscoveryTestMixin
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
...
@@ -294,6 +296,39 @@ class RangeTests(CouponMixin, DiscoveryTestMixin, DiscoveryMockMixin, TestCase):
...
@@ -294,6 +296,39 @@ class RangeTests(CouponMixin, DiscoveryTestMixin, DiscoveryMockMixin, TestCase):
_range
=
Range
.
objects
.
create
(
**
data
)
_range
=
Range
.
objects
.
create
(
**
data
)
self
.
assertEqual
(
_range
.
course_seat_types
,
course_seat_types
)
self
.
assertEqual
(
_range
.
course_seat_types
,
course_seat_types
)
def
test_catalog_contains_product
(
self
):
"""
Verify that catalog_contains_product is cached
We expect 2 calls to set_all_tiers due to:
- the site_configuration api setup
- the result being cached
"""
self
.
mock_access_token_response
()
course
,
__
=
self
.
create_course_and_seat
()
course_catalog_id
=
1
self
.
range
.
catalog_query
=
None
self
.
range
.
course_seat_types
=
'verified'
self
.
range
.
course_catalog
=
course_catalog_id
self
.
range
.
save
()
self
.
product
.
course
=
course
self
.
product
.
save
()
self
.
mock_catalog_contains_endpoint
(
discovery_api_url
=
self
.
site_configuration
.
discovery_api_url
,
catalog_id
=
course_catalog_id
,
course_run_ids
=
[
course
.
id
]
)
with
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
_
=
self
.
range
.
catalog_contains_product
(
self
.
product
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
_
=
self
.
range
.
catalog_contains_product
(
self
.
product
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
@ddt.ddt
@ddt.ddt
@httpretty.activate
@httpretty.activate
...
...
ecommerce/extensions/order/tests/test_utils.py
View file @
2383f489
"""Test Order Utility classes """
"""Test Order Utility classes """
import
datetime
import
json
import
json
import
logging
import
logging
import
ddt
import
ddt
import
httpretty
import
httpretty
import
mock
import
mock
import
pytz
from
django.test.client
import
RequestFactory
from
django.test.client
import
RequestFactory
from
oscar.core.loading
import
get_class
,
get_model
from
oscar.core.loading
import
get_class
,
get_model
from
oscar.test.factories
import
BasketFactory
from
oscar.test.factories
import
BasketFactory
from
requests
import
Timeout
from
requests
import
Timeout
from
testfixtures
import
LogCapture
from
testfixtures
import
LogCapture
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_lms_entitlement_api_url
from
ecommerce.core.url_utils
import
get_lms_entitlement_api_url
from
ecommerce.extensions.fulfillment.status
import
ORDER
from
ecommerce.extensions.fulfillment.status
import
ORDER
from
ecommerce.extensions.order.utils
import
UserAlreadyPlacedOrder
from
ecommerce.extensions.order.utils
import
UserAlreadyPlacedOrder
...
@@ -22,6 +25,7 @@ from ecommerce.tests.factories import PartnerFactory, SiteConfigurationFactory
...
@@ -22,6 +25,7 @@ from ecommerce.tests.factories import PartnerFactory, SiteConfigurationFactory
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
LOGGER_NAME
=
'ecommerce.extensions.order.utils'
LOGGER_NAME
=
'ecommerce.extensions.order.utils'
EXPIRED_DATE
=
datetime
.
datetime
(
year
=
1985
,
month
=
10
,
day
=
26
,
hour
=
1
,
minute
=
20
,
tzinfo
=
pytz
.
utc
)
Country
=
get_class
(
'address.models'
,
'Country'
)
Country
=
get_class
(
'address.models'
,
'Country'
)
NoShippingRequired
=
get_class
(
'shipping.methods'
,
'NoShippingRequired'
)
NoShippingRequired
=
get_class
(
'shipping.methods'
,
'NoShippingRequired'
)
...
@@ -310,3 +314,29 @@ class UserAlreadyPlacedOrderTests(RefundTestMixin, TestCase):
...
@@ -310,3 +314,29 @@ class UserAlreadyPlacedOrderTests(RefundTestMixin, TestCase):
refund_line
.
status
=
refund_line_status
refund_line
.
status
=
refund_line_status
refund_line
.
save
()
refund_line
.
save
()
self
.
assertEqual
(
UserAlreadyPlacedOrder
.
is_order_line_refunded
(
refund_line
.
order_line
),
is_refunded
)
self
.
assertEqual
(
UserAlreadyPlacedOrder
.
is_order_line_refunded
(
refund_line
.
order_line
),
is_refunded
)
@httpretty.activate
def
test_is_entitlement_expired_cached
(
self
):
"""
Test that entitlement's expired status gets cached
We expect 2 calls to set_all_tiers in the is_entitlement_expired
method due to:
- the site_configuration api setup
- the result being cached
"""
self
.
mock_access_token_response
()
self
.
course_entitlement
.
expires
=
EXPIRED_DATE
httpretty
.
register_uri
(
httpretty
.
GET
,
get_lms_entitlement_api_url
()
+
'entitlements/'
+
self
.
course_entitlement_uuid
+
'/'
,
status
=
200
,
body
=
json
.
dumps
({}),
content_type
=
'application/json'
)
with
mock
.
patch
.
object
(
TieredCache
,
'set_all_tiers'
,
wraps
=
TieredCache
.
set_all_tiers
)
as
mocked_set_all_tiers
:
mocked_set_all_tiers
.
assert_not_called
()
_
=
UserAlreadyPlacedOrder
.
is_entitlement_expired
(
self
.
course_entitlement_uuid
,
site
=
self
.
site
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
_
=
UserAlreadyPlacedOrder
.
is_entitlement_expired
(
self
.
course_entitlement_uuid
,
site
=
self
.
site
)
self
.
assertEqual
(
mocked_set_all_tiers
.
call_count
,
2
)
ecommerce/extensions/order/utils.py
View file @
2383f489
...
@@ -5,7 +5,6 @@ import logging
...
@@ -5,7 +5,6 @@ import logging
import
waffle
import
waffle
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
edx_rest_api_client.client
import
EdxRestApiClient
from
edx_rest_api_client.client
import
EdxRestApiClient
from
edx_rest_api_client.exceptions
import
HttpNotFoundError
from
edx_rest_api_client.exceptions
import
HttpNotFoundError
from
oscar.apps.order.utils
import
OrderCreator
as
OscarOrderCreator
from
oscar.apps.order.utils
import
OrderCreator
as
OscarOrderCreator
...
@@ -13,6 +12,7 @@ from oscar.core.loading import get_model
...
@@ -13,6 +12,7 @@ from oscar.core.loading import get_model
from
requests.exceptions
import
ConnectionError
,
ConnectTimeout
# pylint: disable=ungrouped-imports
from
requests.exceptions
import
ConnectionError
,
ConnectTimeout
# pylint: disable=ungrouped-imports
from
threadlocals.threadlocals
import
get_current_request
from
threadlocals.threadlocals
import
get_current_request
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_lms_entitlement_api_url
from
ecommerce.core.url_utils
import
get_lms_entitlement_api_url
from
ecommerce.extensions.order.constants
import
DISABLE_REPEAT_ORDER_CHECK_SWITCH_NAME
from
ecommerce.extensions.order.constants
import
DISABLE_REPEAT_ORDER_CHECK_SWITCH_NAME
from
ecommerce.extensions.refund.status
import
REFUND_LINE
from
ecommerce.extensions.refund.status
import
REFUND_LINE
...
@@ -149,15 +149,15 @@ class UserAlreadyPlacedOrder(object):
...
@@ -149,15 +149,15 @@ class UserAlreadyPlacedOrder(object):
jwt
=
site
.
siteconfiguration
.
access_token
)
jwt
=
site
.
siteconfiguration
.
access_token
)
partner_short_code
=
site
.
siteconfiguration
.
partner
.
short_code
partner_short_code
=
site
.
siteconfiguration
.
partner
.
short_code
key
=
'course_entitlement_detail_{}{}'
.
format
(
entitlement_uuid
,
partner_short_code
)
key
=
'course_entitlement_detail_{}{}'
.
format
(
entitlement_uuid
,
partner_short_code
)
entitlement
=
cache
.
get
(
key
)
entitlement_cached_response
=
TieredCache
.
get_cached_response
(
key
)
if
entitlement_cached_response
.
is_hit
:
if
not
entitlement
:
entitlement
=
entitlement_cached_response
.
value
else
:
logger
.
debug
(
'Trying to get entitlement {
%
s}'
,
entitlement_uuid
)
logger
.
debug
(
'Trying to get entitlement {
%
s}'
,
entitlement_uuid
)
entitlement
=
entitlement_api_client
.
entitlements
(
entitlement_uuid
)
.
get
()
entitlement
=
entitlement_api_client
.
entitlements
(
entitlement_uuid
)
.
get
()
cache
.
set
(
key
,
entitlement
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
TieredCache
.
set_all_tiers
(
key
,
entitlement
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
expired
=
entitlement
.
get
(
'expired_at'
)
expired
=
entitlement
.
get
(
'expired_at'
)
logger
.
debug
(
'Entitlement {
%
s} expired = {
%
s}'
,
entitlement_uuid
,
expired
)
logger
.
debug
(
'Entitlement {
%
s} expired = {
%
s}'
,
entitlement_uuid
,
expired
)
return
expired
return
expired
...
...
ecommerce/extensions/payment/signals.py
View file @
2383f489
import
logging
import
logging
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.db.models.signals
import
post_save
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
waffle.models
import
Switch
from
waffle.models
import
Switch
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.extensions.api.v2.views.payments
import
PAYMENT_PROCESSOR_CACHE_KEY
from
ecommerce.extensions.api.v2.views.payments
import
PAYMENT_PROCESSOR_CACHE_KEY
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -22,5 +22,5 @@ def invalidate_processor_cache(*_args, **kwargs):
...
@@ -22,5 +22,5 @@ def invalidate_processor_cache(*_args, **kwargs):
if
len
(
parts
)
==
2
:
if
len
(
parts
)
==
2
:
processor
=
parts
[
1
]
processor
=
parts
[
1
]
logger
.
info
(
'Switched payment processor [
%
s]
%
s.'
,
processor
,
'on'
if
switch
.
active
else
'off'
)
logger
.
info
(
'Switched payment processor [
%
s]
%
s.'
,
processor
,
'on'
if
switch
.
active
else
'off'
)
cache
.
delete
(
PAYMENT_PROCESSOR_CACHE_KEY
)
TieredCache
.
delete_all_tiers
(
PAYMENT_PROCESSOR_CACHE_KEY
)
logger
.
info
(
'Invalidated payment processor cache after toggling [
%
s].'
,
switch
.
name
)
logger
.
info
(
'Invalidated payment processor cache after toggling [
%
s].'
,
switch
.
name
)
ecommerce/extensions/payment/tests/processors/test_signals.py
View file @
2383f489
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.urls
import
reverse
from
django.urls
import
reverse
from
waffle.models
import
Switch
from
waffle.models
import
Switch
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.extensions.api.v2.views.payments
import
PAYMENT_PROCESSOR_CACHE_KEY
from
ecommerce.extensions.api.v2.views.payments
import
PAYMENT_PROCESSOR_CACHE_KEY
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
...
@@ -16,8 +16,8 @@ class SignalTests(TestCase):
...
@@ -16,8 +16,8 @@ class SignalTests(TestCase):
# Make a call that triggers cache creation
# Make a call that triggers cache creation
response
=
self
.
client
.
get
(
reverse
(
'api:v2:payment:list_processors'
))
response
=
self
.
client
.
get
(
reverse
(
'api:v2:payment:list_processors'
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assert
IsNotNone
(
cache
.
get
(
PAYMENT_PROCESSOR_CACHE_KEY
)
)
self
.
assert
True
(
TieredCache
.
get_cached_response
(
PAYMENT_PROCESSOR_CACHE_KEY
)
.
is_hit
)
# Toggle a switch to trigger cache deletion
# Toggle a switch to trigger cache deletion
Switch
.
objects
.
get_or_create
(
name
=
settings
.
PAYMENT_PROCESSOR_SWITCH_PREFIX
+
'dummy'
)
Switch
.
objects
.
get_or_create
(
name
=
settings
.
PAYMENT_PROCESSOR_SWITCH_PREFIX
+
'dummy'
)
self
.
assert
IsNone
(
cache
.
get
(
PAYMENT_PROCESSOR_CACHE_KEY
)
)
self
.
assert
True
(
TieredCache
.
get_cached_response
(
PAYMENT_PROCESSOR_CACHE_KEY
)
.
is_miss
)
ecommerce/extensions/voucher/utils.py
View file @
2383f489
...
@@ -11,13 +11,13 @@ from decimal import Decimal, DecimalException
...
@@ -11,13 +11,13 @@ from decimal import Decimal, DecimalException
import
dateutil.parser
import
dateutil.parser
import
pytz
import
pytz
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.urls
import
reverse
from
django.urls
import
reverse
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
oscar.templatetags.currency_filters
import
currency
from
oscar.templatetags.currency_filters
import
currency
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_ecommerce_url
from
ecommerce.core.url_utils
import
get_ecommerce_url
from
ecommerce.core.utils
import
log_message_and_raise_validation_error
from
ecommerce.core.utils
import
log_message_and_raise_validation_error
from
ecommerce.extensions.api
import
exceptions
from
ecommerce.extensions.api
import
exceptions
...
@@ -674,12 +674,15 @@ def get_cached_voucher(code):
...
@@ -674,12 +674,15 @@ def get_cached_voucher(code):
Raises:
Raises:
Voucher.DoesNotExist: When no vouchers with provided code exist.
Voucher.DoesNotExist: When no vouchers with provided code exist.
"""
"""
cache_key
=
'voucher_{code}'
.
format
(
code
=
code
)
voucher_code
=
'voucher_{code}'
.
format
(
code
=
code
)
cache_key
=
hashlib
.
md5
(
cache_key
)
.
hexdigest
()
# pylint: disable=redefined-variable-type
cache_key
=
hashlib
.
md5
(
voucher_code
)
.
hexdigest
()
voucher
=
cache
.
get
(
cache_key
)
voucher_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
not
voucher
:
if
voucher_cached_response
.
is_hit
:
voucher
=
Voucher
.
objects
.
get
(
code
=
code
)
return
voucher_cached_response
.
value
cache
.
set
(
cache_key
,
voucher
,
settings
.
VOUCHER_CACHE_TIMEOUT
)
voucher
=
Voucher
.
objects
.
get
(
code
=
code
)
TieredCache
.
set_all_tiers
(
cache_key
,
voucher
,
settings
.
VOUCHER_CACHE_TIMEOUT
)
return
voucher
return
voucher
...
...
ecommerce/programs/api.py
View file @
2383f489
import
logging
import
logging
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
ecommerce.cache_utils.utils
import
TieredCache
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -31,14 +32,15 @@ class ProgramsApiClient(object):
...
@@ -31,14 +32,15 @@ class ProgramsApiClient(object):
program_uuid
=
str
(
uuid
)
program_uuid
=
str
(
uuid
)
cache_key
=
'{site_domain}-program-{uuid}'
.
format
(
site_domain
=
self
.
site_domain
,
uuid
=
program_uuid
)
cache_key
=
'{site_domain}-program-{uuid}'
.
format
(
site_domain
=
self
.
site_domain
,
uuid
=
program_uuid
)
program
=
cache
.
get
(
cache_key
)
program
_cached_response
=
TieredCache
.
get_cached_response
(
cache_key
)
if
program
:
# pragma: no cover
if
program
_cached_response
.
is_hit
:
# pragma: no cover
logger
.
debug
(
'Program [
%
s] was found in the cache.'
,
program_uuid
)
logger
.
debug
(
'Program [
%
s] was found in the cache.'
,
program_uuid
)
else
:
return
program_cached_response
.
value
logging
.
info
(
'Retrieving details of of program [
%
s]...'
,
program_uuid
)
program
=
self
.
client
.
programs
(
program_uuid
)
.
get
()
logging
.
info
(
'Retrieving details of of program [
%
s]...'
,
program_uuid
)
cache
.
set
(
cache_key
,
program
,
self
.
cache_ttl
)
program
=
self
.
client
.
programs
(
program_uuid
)
.
get
()
logging
.
info
(
'Program [
%
s] was successfully retrieved and cached.'
,
program_uuid
)
TieredCache
.
set_all_tiers
(
cache_key
,
program
,
self
.
cache_ttl
)
logging
.
info
(
'Program [
%
s] was successfully retrieved and cached.'
,
program_uuid
)
return
program
return
program
ecommerce/tests/mixins.py
View file @
2383f489
...
@@ -10,7 +10,6 @@ import jwt
...
@@ -10,7 +10,6 @@ import jwt
from
django.conf
import
settings
from
django.conf
import
settings
from
django.contrib.auth
import
get_user_model
from
django.contrib.auth
import
get_user_model
from
django.contrib.sites.models
import
Site
from
django.contrib.sites.models
import
Site
from
django.core.cache
import
cache
from
django.urls
import
reverse
from
django.urls
import
reverse
from
django.utils.timezone
import
now
from
django.utils.timezone
import
now
from
mock
import
patch
from
mock
import
patch
...
@@ -20,7 +19,9 @@ from oscar.test.utils import RequestFactory
...
@@ -20,7 +19,9 @@ from oscar.test.utils import RequestFactory
from
social_django.models
import
UserSocialAuth
from
social_django.models
import
UserSocialAuth
from
threadlocals.threadlocals
import
set_thread_variable
from
threadlocals.threadlocals
import
set_thread_variable
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.utils
import
mode_for_product
from
ecommerce.courses.utils
import
mode_for_product
from
ecommerce.extensions.fulfillment.signals
import
SHIPPING_EVENT_NAME
from
ecommerce.extensions.fulfillment.signals
import
SHIPPING_EVENT_NAME
from
ecommerce.tests.factories
import
SiteConfigurationFactory
from
ecommerce.tests.factories
import
SiteConfigurationFactory
...
@@ -80,7 +81,7 @@ class ThrottlingMixin(object):
...
@@ -80,7 +81,7 @@ class ThrottlingMixin(object):
super
(
ThrottlingMixin
,
self
)
.
setUp
()
super
(
ThrottlingMixin
,
self
)
.
setUp
()
# Throttling for tests relies on the cache. To get around throttling, simply clear the cache.
# Throttling for tests relies on the cache. To get around throttling, simply clear the cache.
self
.
addCleanup
(
cache
.
clear
)
self
.
addCleanup
(
TieredCache
.
clear_all_tiers
)
class
JwtMixin
(
object
):
class
JwtMixin
(
object
):
...
@@ -255,6 +256,7 @@ class SiteMixin(object):
...
@@ -255,6 +256,7 @@ class SiteMixin(object):
domain
=
'testserver.fake'
domain
=
'testserver.fake'
self
.
client
=
self
.
client_class
(
SERVER_NAME
=
domain
)
self
.
client
=
self
.
client_class
(
SERVER_NAME
=
domain
)
Course
.
objects
.
all
()
.
delete
()
Partner
.
objects
.
all
()
.
delete
()
Partner
.
objects
.
all
()
.
delete
()
Site
.
objects
.
all
()
.
delete
()
Site
.
objects
.
all
()
.
delete
()
self
.
site_configuration
=
SiteConfigurationFactory
(
self
.
site_configuration
=
SiteConfigurationFactory
(
...
...
ecommerce/tests/testcases.py
View file @
2383f489
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.cache
import
cache
from
django.test
import
LiveServerTestCase
as
DjangoLiveServerTestCase
from
django.test
import
LiveServerTestCase
as
DjangoLiveServerTestCase
from
django.test
import
TestCase
as
DjangoTestCase
from
django.test
import
TestCase
as
DjangoTestCase
from
django.test
import
TransactionTestCase
as
DjangoTransactionTestCase
from
django.test
import
TransactionTestCase
as
DjangoTransactionTestCase
from
ecommerce.cache_utils.utils
import
TieredCache
from
ecommerce.tests.mixins
import
SiteMixin
,
TestServerUrlMixin
,
UserMixin
from
ecommerce.tests.mixins
import
SiteMixin
,
TestServerUrlMixin
,
UserMixin
class
CacheMixin
(
object
):
class
Tiered
CacheMixin
(
object
):
def
setUp
(
self
):
def
setUp
(
self
):
cache
.
clear
()
TieredCache
.
clear_all_tiers
()
super
(
CacheMixin
,
self
)
.
setUp
()
super
(
Tiered
CacheMixin
,
self
)
.
setUp
()
def
tearDown
(
self
):
def
tearDown
(
self
):
cache
.
clear
()
TieredCache
.
clear_all_tiers
()
super
(
CacheMixin
,
self
)
.
tearDown
()
super
(
Tiered
CacheMixin
,
self
)
.
tearDown
()
class
ViewTestMixin
(
CacheMixin
):
class
ViewTestMixin
(
Tiered
CacheMixin
):
path
=
None
path
=
None
def
setUp
(
self
):
def
setUp
(
self
):
...
@@ -50,7 +50,7 @@ class ViewTestMixin(CacheMixin):
...
@@ -50,7 +50,7 @@ class ViewTestMixin(CacheMixin):
self
.
assert_get_response_status
(
200
)
self
.
assert_get_response_status
(
200
)
class
TestCase
(
TestServerUrlMixin
,
UserMixin
,
SiteMixin
,
CacheMixin
,
DjangoTestCase
):
class
TestCase
(
TestServerUrlMixin
,
UserMixin
,
SiteMixin
,
Tiered
CacheMixin
,
DjangoTestCase
):
"""
"""
Base test case for ecommerce tests.
Base test case for ecommerce tests.
...
@@ -58,7 +58,7 @@ class TestCase(TestServerUrlMixin, UserMixin, SiteMixin, CacheMixin, DjangoTestC
...
@@ -58,7 +58,7 @@ class TestCase(TestServerUrlMixin, UserMixin, SiteMixin, CacheMixin, DjangoTestC
"""
"""
class
LiveServerTestCase
(
TestServerUrlMixin
,
UserMixin
,
SiteMixin
,
CacheMixin
,
DjangoLiveServerTestCase
):
class
LiveServerTestCase
(
TestServerUrlMixin
,
UserMixin
,
SiteMixin
,
Tiered
CacheMixin
,
DjangoLiveServerTestCase
):
"""
"""
Base test case for ecommerce tests.
Base test case for ecommerce tests.
...
@@ -67,7 +67,7 @@ class LiveServerTestCase(TestServerUrlMixin, UserMixin, SiteMixin, CacheMixin, D
...
@@ -67,7 +67,7 @@ class LiveServerTestCase(TestServerUrlMixin, UserMixin, SiteMixin, CacheMixin, D
pass
pass
class
TransactionTestCase
(
TestServerUrlMixin
,
UserMixin
,
SiteMixin
,
CacheMixin
,
DjangoTransactionTestCase
):
class
TransactionTestCase
(
TestServerUrlMixin
,
UserMixin
,
SiteMixin
,
Tiered
CacheMixin
,
DjangoTransactionTestCase
):
"""
"""
Base test case for ecommerce tests.
Base test case for ecommerce tests.
...
...
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