Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce-worker
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-worker
Commits
17716208
Commit
17716208
authored
Jan 20, 2017
by
Ahsan Ulhaq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use Course Title with SailThru Purchase Call
ECOM-6873
parent
816a6c53
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
139 additions
and
21 deletions
+139
-21
ecommerce_worker/fulfillment/v1/tasks.py
+2
-8
ecommerce_worker/sailthru/v1/tasks.py
+44
-7
ecommerce_worker/sailthru/v1/tests/sailthru_tests.py
+77
-6
ecommerce_worker/utils.py
+16
-0
No files found.
ecommerce_worker/fulfillment/v1/tasks.py
View file @
17716208
...
@@ -3,9 +3,8 @@ from celery import shared_task
...
@@ -3,9 +3,8 @@ from celery import shared_task
from
celery.exceptions
import
Ignore
from
celery.exceptions
import
Ignore
from
celery.utils.log
import
get_task_logger
from
celery.utils.log
import
get_task_logger
from
edx_rest_api_client
import
exceptions
from
edx_rest_api_client
import
exceptions
from
edx_rest_api_client.client
import
EdxRestApiClient
from
ecommerce_worker.utils
import
get_configuration
from
ecommerce_worker.utils
import
get_configuration
,
get_ecommerce_client
logger
=
get_task_logger
(
__name__
)
# pylint: disable=invalid-name
logger
=
get_task_logger
(
__name__
)
# pylint: disable=invalid-name
...
@@ -36,13 +35,8 @@ def fulfill_order(self, order_number, site_code=None):
...
@@ -36,13 +35,8 @@ def fulfill_order(self, order_number, site_code=None):
Returns:
Returns:
None
None
"""
"""
ecommerce_api_root
=
get_configuration
(
'ECOMMERCE_API_ROOT'
,
site_code
=
site_code
)
max_fulfillment_retries
=
get_configuration
(
'MAX_FULFILLMENT_RETRIES'
,
site_code
=
site_code
)
max_fulfillment_retries
=
get_configuration
(
'MAX_FULFILLMENT_RETRIES'
,
site_code
=
site_code
)
signing_key
=
get_configuration
(
'JWT_SECRET_KEY'
,
site_code
=
site_code
)
api
=
get_ecommerce_client
(
site_code
=
site_code
)
issuer
=
get_configuration
(
'JWT_ISSUER'
,
site_code
=
site_code
)
service_username
=
get_configuration
(
'ECOMMERCE_SERVICE_USERNAME'
,
site_code
=
site_code
)
api
=
EdxRestApiClient
(
ecommerce_api_root
,
signing_key
=
signing_key
,
issuer
=
issuer
,
username
=
service_username
)
try
:
try
:
logger
.
info
(
'Requesting fulfillment of order [
%
s].'
,
order_number
)
logger
.
info
(
'Requesting fulfillment of order [
%
s].'
,
order_number
)
api
.
orders
(
order_number
)
.
fulfill
.
put
()
api
.
orders
(
order_number
)
.
fulfill
.
put
()
...
...
ecommerce_worker/sailthru/v1/tasks.py
View file @
17716208
...
@@ -8,7 +8,7 @@ from sailthru.sailthru_client import SailthruClient
...
@@ -8,7 +8,7 @@ from sailthru.sailthru_client import SailthruClient
from
sailthru.sailthru_error
import
SailthruClientError
from
sailthru.sailthru_error
import
SailthruClientError
from
ecommerce_worker.cache
import
Cache
from
ecommerce_worker.cache
import
Cache
from
ecommerce_worker.utils
import
get_configuration
from
ecommerce_worker.utils
import
get_configuration
,
get_ecommerce_client
logger
=
get_task_logger
(
__name__
)
# pylint: disable=invalid-name
logger
=
get_task_logger
(
__name__
)
# pylint: disable=invalid-name
cache
=
Cache
()
# pylint: disable=invalid-name
cache
=
Cache
()
# pylint: disable=invalid-name
...
@@ -83,7 +83,7 @@ def update_course_enrollment(self, email, course_url, purchase_incomplete, mode,
...
@@ -83,7 +83,7 @@ def update_course_enrollment(self, email, course_url, purchase_incomplete, mode,
_schedule_retry
(
self
,
config
)
_schedule_retry
(
self
,
config
)
# Get course data from Sailthru content library or cache
# Get course data from Sailthru content library or cache
course_data
=
_get_course_content
(
course_url
,
sailthru_client
,
site_code
,
config
)
course_data
=
_get_course_content
(
course_
id
,
course_
url
,
sailthru_client
,
site_code
,
config
)
# build item description
# build item description
item
=
_build_purchase_item
(
course_id
,
course_url
,
cost_in_cents
,
mode
,
course_data
)
item
=
_build_purchase_item
(
course_id
,
course_url
,
cost_in_cents
,
mode
,
course_data
)
...
@@ -167,12 +167,13 @@ def _record_purchase(sailthru_client, email, item, purchase_incomplete, message_
...
@@ -167,12 +167,13 @@ def _record_purchase(sailthru_client, email, item, purchase_incomplete, message_
return
True
return
True
def
_get_course_content
(
course_url
,
sailthru_client
,
site_code
,
config
):
def
_get_course_content
(
course_
id
,
course_
url
,
sailthru_client
,
site_code
,
config
):
"""Get course information using the Sailthru content api or from cache.
"""Get course information using the Sailthru content api or from cache.
If there is an error, just return with an empty response.
If there is an error, just return with an empty response.
Arguments:
Arguments:
course_id (str): course key of the course
course_url (str): LMS url for course info page.
course_url (str): LMS url for course info page.
sailthru_client (object): SailthruClient
sailthru_client (object): SailthruClient
site_code (str): site code
site_code (str): site code
...
@@ -188,17 +189,53 @@ def _get_course_content(course_url, sailthru_client, site_code, config):
...
@@ -188,17 +189,53 @@ def _get_course_content(course_url, sailthru_client, site_code, config):
try
:
try
:
sailthru_response
=
sailthru_client
.
api_get
(
"content"
,
{
"id"
:
course_url
})
sailthru_response
=
sailthru_client
.
api_get
(
"content"
,
{
"id"
:
course_url
})
if
not
sailthru_response
.
is_ok
():
if
not
sailthru_response
.
is_ok
():
re
turn
{}
re
sponse
=
{}
else
:
response
=
sailthru_response
.
json
response
=
sailthru_response
.
json
cache
.
set
(
cache_key
,
response
,
config
.
get
(
'SAILTHRU_CACHE_TTL_SECONDS'
))
cache
.
set
(
cache_key
,
response
,
config
.
get
(
'SAILTHRU_CACHE_TTL_SECONDS'
))
except
SailthruClientError
:
except
SailthruClientError
:
response
=
{}
response
=
{}
if
not
response
:
logger
.
error
(
'Could not get course data from Sailthru on enroll/purchase event. '
'Calling Ecommerce Course API to get course info for enrollment confirmation email'
)
response
=
_get_course_content_from_ecommerce
(
course_id
,
site_code
=
site_code
)
if
response
:
cache
.
set
(
cache_key
,
response
,
config
.
get
(
'SAILTHRU_CACHE_TTL_SECONDS'
))
return
response
return
response
def
_get_course_content_from_ecommerce
(
course_id
,
site_code
=
None
):
"""
Get course information using the Ecommerce course api.
In case of error returns empty response.
Arguments:
course_id (str): course key of the course
site_code (str): site code
Returns:
course information from Ecommerce
"""
api
=
get_ecommerce_client
(
site_code
=
site_code
)
try
:
api_response
=
api
.
courses
(
course_id
)
.
get
()
except
Exception
:
# pylint: disable=broad-except
logger
.
exception
(
'An error occurred while retrieving data for course run [
%
s] from the Catalog API.'
,
course_id
,
exc_info
=
True
)
return
{}
return
{
'title'
:
api_response
.
get
(
'name'
),
'verification_deadline'
:
api_response
.
get
(
'verification_deadline'
)
}
def
_update_unenrolled_list
(
sailthru_client
,
email
,
course_url
,
unenroll
):
def
_update_unenrolled_list
(
sailthru_client
,
email
,
course_url
,
unenroll
):
"""Maintain a list of courses the user has unenrolled from in the Sailthru user record
"""Maintain a list of courses the user has unenrolled from in the Sailthru user record
...
...
ecommerce_worker/sailthru/v1/tests/sailthru_tests.py
View file @
17716208
"""Tests of sailthru worker code."""
"""Tests of sailthru worker code."""
import
json
import
logging
import
logging
from
decimal
import
Decimal
from
decimal
import
Decimal
from
unittest
import
TestCase
from
unittest
import
TestCase
import
httpretty
from
mock
import
patch
from
mock
import
patch
from
sailthru.sailthru_error
import
SailthruClientError
from
sailthru.sailthru_error
import
SailthruClientError
from
ecommerce_worker.sailthru.v1.tasks
import
update_course_enrollment
,
_update_unenrolled_list
,
_get_course_content
from
ecommerce_worker.sailthru.v1.tasks
import
(
update_course_enrollment
,
_update_unenrolled_list
,
_get_course_content
,
_get_course_content_from_ecommerce
)
from
ecommerce_worker.utils
import
get_configuration
from
ecommerce_worker.utils
import
get_configuration
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -26,6 +30,29 @@ class SailthruTests(TestCase):
...
@@ -26,6 +30,29 @@ class SailthruTests(TestCase):
self
.
course_id2
=
'edX/toy/2016_Fall'
self
.
course_id2
=
'edX/toy/2016_Fall'
self
.
course_url2
=
'http://lms.testserver.fake/courses/edX/toy/2016_Fall/info'
self
.
course_url2
=
'http://lms.testserver.fake/courses/edX/toy/2016_Fall/info'
def
mock_ecommerce_api
(
self
,
body
,
course_id
,
status
=
200
):
""" Mock GET requests to the ecommerce course API endpoint. """
httpretty
.
reset
()
httpretty
.
register_uri
(
httpretty
.
GET
,
'{}/courses/{}/'
.
format
(
get_configuration
(
'ECOMMERCE_API_ROOT'
)
.
strip
(
'/'
),
unicode
(
course_id
)
),
status
=
status
,
body
=
json
.
dumps
(
body
),
content_type
=
'application/json'
,
)
def
ecom_course_data
(
self
,
course_id
):
""" Returns dummy course data. """
return
{
"id"
:
course_id
,
"url"
:
"https://test-ecommerce.edx.org/api/v2/courses/{}/"
.
format
(
course_id
),
"name"
:
"test course"
,
"verification_deadline"
:
"2016-12-01T23:59:00Z"
,
"type"
:
"verified"
,
"products_url"
:
"https://test-ecommerce.edx.org/api/v2/courses/{}/products/"
.
format
(
course_id
),
"last_edited"
:
"2016-12-28T15:20:58Z"
}
@patch
(
'ecommerce_worker.sailthru.v1.tasks.get_configuration'
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.get_configuration'
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.logger.error'
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.logger.error'
)
def
test_sailthru_disabled
(
self
,
mock_log_error
,
mock_get_configuration
):
def
test_sailthru_disabled
(
self
,
mock_log_error
,
mock_get_configuration
):
...
@@ -318,6 +345,7 @@ class SailthruTests(TestCase):
...
@@ -318,6 +345,7 @@ class SailthruTests(TestCase):
unit_cost
=
Decimal
(
99
))
unit_cost
=
Decimal
(
99
))
self
.
assertTrue
(
mock_log_error
.
called
)
self
.
assertTrue
(
mock_log_error
.
called
)
@httpretty.activate
@patch
(
'ecommerce_worker.sailthru.v1.tasks.SailthruClient'
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.SailthruClient'
)
def
test_get_course_content
(
self
,
mock_sailthru_client
):
def
test_get_course_content
(
self
,
mock_sailthru_client
):
"""
"""
...
@@ -325,23 +353,66 @@ class SailthruTests(TestCase):
...
@@ -325,23 +353,66 @@ class SailthruTests(TestCase):
"""
"""
config
=
{
'SAILTHRU_CACHE_TTL_SECONDS'
:
100
}
config
=
{
'SAILTHRU_CACHE_TTL_SECONDS'
:
100
}
mock_sailthru_client
.
api_get
.
return_value
=
MockSailthruResponse
({
"title"
:
"The title"
})
mock_sailthru_client
.
api_get
.
return_value
=
MockSailthruResponse
({
"title"
:
"The title"
})
response_json
=
_get_course_content
(
'course:123'
,
mock_sailthru_client
,
None
,
config
)
response_json
=
_get_course_content
(
self
.
course_id
,
'course:123'
,
mock_sailthru_client
,
None
,
config
)
self
.
assertEquals
(
response_json
,
{
"title"
:
"The title"
})
self
.
assertEquals
(
response_json
,
{
"title"
:
"The title"
})
mock_sailthru_client
.
api_get
.
assert_called_with
(
'content'
,
{
'id'
:
'course:123'
})
mock_sailthru_client
.
api_get
.
assert_called_with
(
'content'
,
{
'id'
:
'course:123'
})
# test second call uses cache
# test second call uses cache
mock_sailthru_client
.
reset_mock
()
mock_sailthru_client
.
reset_mock
()
response_json
=
_get_course_content
(
'course:123'
,
mock_sailthru_client
,
None
,
config
)
response_json
=
_get_course_content
(
self
.
course_id
,
'course:123'
,
mock_sailthru_client
,
None
,
config
)
self
.
assertEquals
(
response_json
,
{
"title"
:
"The title"
})
self
.
assertEquals
(
response_json
,
{
"title"
:
"The title"
})
mock_sailthru_client
.
api_get
.
assert_not_called
()
mock_sailthru_client
.
api_get
.
assert_not_called
()
# test error from Sailthru
# test error from Sailthru
mock_sailthru_client
.
api_get
.
return_value
=
MockSailthruResponse
({},
error
=
'Got an error'
)
mock_sailthru_client
.
api_get
.
return_value
=
MockSailthruResponse
({},
error
=
'Got an error'
)
self
.
assertEquals
(
_get_course_content
(
'course:124'
,
mock_sailthru_client
,
None
,
config
),
{})
data
=
self
.
ecom_course_data
(
self
.
course_id
)
expected_response
=
{
'title'
:
'test course'
,
'verification_deadline'
:
'2016-12-01T23:59:00Z'
}
self
.
mock_ecommerce_api
(
data
,
self
.
course_id
)
self
.
assertEquals
(
_get_course_content
(
self
.
course_id
,
'course:124'
,
mock_sailthru_client
,
None
,
config
),
expected_response
)
# test Sailthru exception
data
=
self
.
ecom_course_data
(
self
.
course_id
)
expected_response
=
{
'title'
:
'test course'
,
'verification_deadline'
:
'2016-12-01T23:59:00Z'
}
mock_sailthru_client
.
api_get
.
side_effect
=
SailthruClientError
self
.
mock_ecommerce_api
(
data
,
self
.
course_id
)
self
.
assertEquals
(
_get_course_content
(
self
.
course_id
,
'course:125'
,
mock_sailthru_client
,
None
,
config
),
expected_response
)
# test exception
# test
Sailthru and Ecommerce
exception
mock_sailthru_client
.
api_get
.
side_effect
=
SailthruClientError
mock_sailthru_client
.
api_get
.
side_effect
=
SailthruClientError
self
.
assertEquals
(
_get_course_content
(
'course:125'
,
mock_sailthru_client
,
None
,
config
),
{})
self
.
mock_ecommerce_api
({},
self
.
course_id2
,
status
=
500
)
self
.
assertEquals
(
_get_course_content
(
self
.
course_id2
,
'course:126'
,
mock_sailthru_client
,
None
,
config
),
{}
)
@httpretty.activate
def
test_get_course_content_from_ecommerce
(
self
):
"""
Test routine that fetches data from ecommerce.
"""
data
=
self
.
ecom_course_data
(
self
.
course_id
)
expected_response
=
{
'title'
:
'test course'
,
'verification_deadline'
:
'2016-12-01T23:59:00Z'
}
self
.
mock_ecommerce_api
(
data
,
self
.
course_id
)
response_json
=
_get_course_content_from_ecommerce
(
self
.
course_id
,
None
)
self
.
assertEquals
(
response_json
,
expected_response
)
# test error getting data
self
.
mock_ecommerce_api
({},
self
.
course_id2
,
status
=
500
)
response_json
=
_get_course_content_from_ecommerce
(
self
.
course_id2
,
None
)
self
.
assertEquals
(
response_json
,
{})
@patch
(
'ecommerce_worker.sailthru.v1.tasks.SailthruClient'
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.SailthruClient'
)
def
test_update_unenrolled_list_new
(
self
,
mock_sailthru_client
):
def
test_update_unenrolled_list_new
(
self
,
mock_sailthru_client
):
...
...
ecommerce_worker/utils.py
View file @
17716208
...
@@ -2,6 +2,8 @@
...
@@ -2,6 +2,8 @@
import
os
import
os
import
sys
import
sys
from
edx_rest_api_client.client
import
EdxRestApiClient
from
ecommerce_worker.configuration
import
CONFIGURATION_MODULE
from
ecommerce_worker.configuration
import
CONFIGURATION_MODULE
...
@@ -42,3 +44,17 @@ def get_configuration(variable, site_code=None):
...
@@ -42,3 +44,17 @@ def get_configuration(variable, site_code=None):
if
setting_value
is
None
:
if
setting_value
is
None
:
raise
RuntimeError
(
'Worker is improperly configured: {} is unset in {}.'
.
format
(
variable
,
module
))
raise
RuntimeError
(
'Worker is improperly configured: {} is unset in {}.'
.
format
(
variable
,
module
))
return
setting_value
return
setting_value
def
get_ecommerce_client
(
site_code
=
None
):
"""
Get client for fetching data from ecommerce API
Returns:
EdxRestApiClient object
"""
ecommerce_api_root
=
get_configuration
(
'ECOMMERCE_API_ROOT'
,
site_code
=
site_code
)
signing_key
=
get_configuration
(
'JWT_SECRET_KEY'
,
site_code
=
site_code
)
issuer
=
get_configuration
(
'JWT_ISSUER'
,
site_code
=
site_code
)
service_username
=
get_configuration
(
'ECOMMERCE_SERVICE_USERNAME'
,
site_code
=
site_code
)
return
EdxRestApiClient
(
ecommerce_api_root
,
signing_key
=
signing_key
,
issuer
=
issuer
,
username
=
service_username
)
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