Skip to content

Commit

Permalink
feat: add lms_user_email to redemptions (#162)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnagro authored Nov 2, 2023
1 parent e1c0631 commit b0f03c9
Show file tree
Hide file tree
Showing 15 changed files with 319 additions and 17 deletions.
2 changes: 2 additions & 0 deletions enterprise_subsidy/apps/api/v1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,9 @@ class Meta:
"state",
"idempotency_key",
"lms_user_id",
"lms_user_email",
"content_key",
"content_title",
"quantity",
"unit", # Manually fetch from parent ledger via get_unit().
"fulfillment_identifier",
Expand Down
85 changes: 75 additions & 10 deletions enterprise_subsidy/apps/api/v1/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ class APITestBase(APITestMixin):
subsidy_access_policy_1_uuid = str(uuid.uuid4())
subsidy_access_policy_2_uuid = str(uuid.uuid4())
content_key_1 = "course-v1:edX+test+course.1"
content_title_1 = "edx: Test Course 1"
content_key_2 = "course-v1:edX+test+course.2"
content_title_2 = "edx: Test Course 2"
lms_user_email = '[email protected]'

def setUp(self):
super().setUp()
Expand All @@ -69,17 +72,21 @@ def setUp(self):
quantity=-1000,
ledger=self.subsidy_1.ledger,
lms_user_id=STATIC_LMS_USER_ID, # This is the only transaction belonging to the requester.
lms_user_email=self.lms_user_email,
subsidy_access_policy_uuid=self.subsidy_access_policy_1_uuid,
content_key=self.content_key_1,
content_title=self.content_title_1,
)
self.subsidy_1_transaction_2 = TransactionFactory(
uuid=self.subsidy_1_transaction_2_uuid,
state=TransactionStateChoices.COMMITTED,
quantity=-1000,
ledger=self.subsidy_1.ledger,
lms_user_id=STATIC_LMS_USER_ID+1000,
lms_user_email=self.lms_user_email,
subsidy_access_policy_uuid=self.subsidy_access_policy_2_uuid,
content_key=self.content_key_2,
content_title=self.content_title_2,
)

# Create an extra subsidy with the same enterprise_customer_uuid, but the learner does not have any transactions
Expand All @@ -96,13 +103,15 @@ def setUp(self):
quantity=-1000,
ledger=self.subsidy_2.ledger,
lms_user_id=STATIC_LMS_USER_ID+1000,
lms_user_email=self.lms_user_email,
)
TransactionFactory(
uuid=self.subsidy_2_transaction_2_uuid,
state=TransactionStateChoices.COMMITTED,
quantity=-1000,
ledger=self.subsidy_2.ledger,
lms_user_id=STATIC_LMS_USER_ID+1000,
lms_user_email=self.lms_user_email,
)

# Create third subsidy with a different enterprise_customer_uuid. Neither test learner nor the test admin
Expand All @@ -119,13 +128,15 @@ def setUp(self):
quantity=-1000,
ledger=self.subsidy_3.ledger,
lms_user_id=STATIC_LMS_USER_ID+1000,
lms_user_email=self.lms_user_email,
)
TransactionFactory(
uuid=self.subsidy_3_transaction_2_uuid,
state=TransactionStateChoices.COMMITTED,
quantity=-1000,
ledger=self.subsidy_3.ledger,
lms_user_id=STATIC_LMS_USER_ID+1000,
lms_user_email=self.lms_user_email,
)

self.all_initial_transactions = set([
Expand Down Expand Up @@ -254,14 +265,18 @@ def test_can_redeem_bad_request(self, query_params):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

@mock.patch('enterprise_subsidy.apps.api.v1.views.subsidy.can_redeem')
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.lms_user_client")
@ddt.data(False, True)
def test_can_redeem_happy_path(self, has_existing_transaction, mock_can_redeem):
def test_can_redeem_happy_path(self, has_existing_transaction, mock_lms_user_client, mock_can_redeem):
"""
Tests that the result of ``api.can_redeem()`` is returned as the response
payload for a POST to the can_redeem action, including any relevant
existing transaction.
"""
self.set_up_admin(enterprise_uuids=[self.subsidy_1.enterprise_customer_uuid])
mock_lms_user_client.return_value.best_effort_user_data.return_value = {
'email': self.lms_user_email,
}
expected_redeemable = True
expected_active = True
expected_price = 350
Expand Down Expand Up @@ -292,8 +307,10 @@ def test_can_redeem_happy_path(self, has_existing_transaction, mock_can_redeem):
'state': TransactionStateChoices.COMMITTED,
'quantity': -1000,
'lms_user_id': STATIC_LMS_USER_ID,
'lms_user_email': self.lms_user_email,
'subsidy_access_policy_uuid': str(self.subsidy_access_policy_1_uuid),
'content_key': self.content_key_1,
'content_title': self.content_title_1,
'external_reference': [],
'transaction_status_api_url': f"{self.transaction_status_api_url}/{self.subsidy_1_transaction_1_uuid}/",
'courseware_url': f"http://localhost:2000/course/{self.content_key_1}/home",
Expand Down Expand Up @@ -959,17 +976,26 @@ def test_retrieve_invalid_uuid(self):
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.price_for_content")
@mock.patch("enterprise_subsidy.apps.content_metadata.api.ContentMetadataApi.get_content_summary")
def test_create(self, mock_get_content_summary, mock_price_for_content, mock_enterprise_client):
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.lms_user_client")
def test_create(
self,
mock_lms_user_client,
mock_get_content_summary,
mock_price_for_content,
mock_enterprise_client,
):
"""
Test create Transaction, happy case.
"""
mock_lms_user_client.return_value.best_effort_user_data.return_value = {'email': '[email protected]'}
url = reverse("api:v1:transaction-list")
test_enroll_enterprise_fulfillment_uuid = "test-enroll-reference-id"
mock_enterprise_client.enroll.return_value = test_enroll_enterprise_fulfillment_uuid
mock_price_for_content.return_value = 10000
mock_get_content_summary.return_value = {
'content_uuid': 'course-v1:edX-test-course',
'content_key': 'course-v1:edX-test-course',
'content_title': 'edX: Test Course',
'source': 'edX',
'mode': 'verified',
'content_price': 10000,
Expand All @@ -994,7 +1020,9 @@ def test_create(self, mock_get_content_summary, mock_price_for_content, mock_ent
f"{self.transaction_status_api_url}/{create_response_data['uuid']}/"
)
assert create_response_data["content_key"] == post_data["content_key"]
assert create_response_data["content_title"] == 'edX: Test Course'
assert create_response_data["lms_user_id"] == post_data["lms_user_id"]
assert create_response_data["lms_user_email"] == '[email protected]'
assert create_response_data["subsidy_access_policy_uuid"] == post_data["subsidy_access_policy_uuid"]
assert create_response_data["courseware_url"] == f"http://localhost:2000/course/{post_data['content_key']}/home"
self.assertDictEqual(create_response_data["metadata"], {})
Expand All @@ -1011,6 +1039,7 @@ def test_create(self, mock_get_content_summary, mock_price_for_content, mock_ent
retrieve_response_data = retrieve_response.json()
assert retrieve_response_data["uuid"] == create_response_data["uuid"]
assert retrieve_response_data["idempotency_key"] == create_response_data["idempotency_key"]
# assert retrieve_response_data["content_title"] == 'edX: Test Course'

# Uncomment after Segment events are setup:
#
Expand All @@ -1030,10 +1059,18 @@ def test_create(self, mock_get_content_summary, mock_price_for_content, mock_ent
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.price_for_content")
@mock.patch("enterprise_subsidy.apps.content_metadata.api.ContentMetadataApi.get_content_summary")
def test_create_with_metadata(self, mock_get_content_summary, mock_price_for_content, mock_enterprise_client):
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.lms_user_client")
def test_create_with_metadata(
self,
mock_lms_user_client,
mock_get_content_summary,
mock_price_for_content,
mock_enterprise_client
):
"""
Test create Transaction, happy case.
"""
mock_lms_user_client.return_value.best_effort_user_data.return_value = {'email': '[email protected]'}
url = reverse("api:v1:transaction-list")
test_enroll_enterprise_fulfillment_uuid = "test-enroll-reference-id"
mock_enterprise_client.enroll.return_value = test_enroll_enterprise_fulfillment_uuid
Expand Down Expand Up @@ -1085,10 +1122,27 @@ def test_create_with_metadata(self, mock_get_content_summary, mock_price_for_con

@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.price_for_content")
def test_create_ledger_locked(self, mock_price_for_content, mock_enterprise_client):
@mock.patch("enterprise_subsidy.apps.content_metadata.api.ContentMetadataApi.get_content_summary")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.lms_user_client")
def test_create_ledger_locked(
self,
mock_lms_user_client,
mock_get_content_summary,
mock_price_for_content,
mock_enterprise_client,
):
"""
Test create Transaction, 429 response due to the ledger being locked.
"""
mock_get_content_summary.return_value = {
'content_uuid': 'course-v1:edX-test-course',
'content_key': 'course-v1:edX-test-course',
'source': 'edX',
'mode': 'verified',
'content_price': 10000,
'geag_variant_id': None,
}
mock_lms_user_client.return_value.best_effort_user_data.return_value = {'email': '[email protected]'}
url = reverse("api:v1:transaction-list")
test_enroll_enterprise_fulfillment_uuid = "test-enroll-reference-id"
mock_enterprise_client.enroll.return_value = test_enroll_enterprise_fulfillment_uuid
Expand Down Expand Up @@ -1171,29 +1225,40 @@ def test_create_content_not_in_catalog(self, mock_content_metadata_api):
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.content_metadata_api")
@mock.patch("enterprise_subsidy.apps.content_metadata.api.ContentMetadataApi.get_content_summary")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.lms_user_client")
def test_create_external_enroll_failed(
self,
mock_lms_user_client,
mock_get_content_summary,
mock_content_metadata_api,
mock_enterprise_client
mock_subsidy_content_metadata_api,
mock_enterprise_client,
):
"""
Test create Transaction, 5xx response due to the external enrollment failing. Check that a transaction is
created, then rolled back to "failed" state.
"""
mock_get_content_summary.return_value = {
'content_uuid': 'course-v1:edX+test+course.enroll.failed',
'content_key': 'course-v1:edX+test+course.enroll.failed',
'content_uuid': 'course-v1:edX-test-course',
'content_key': 'course-v1:edX-test-course',
'source': 'edX',
'mode': 'verified',
'content_price': 10000,
'content_price': 100,
'geag_variant_id': None,
}
mock_subsidy_content_metadata_api().get_content_summary.return_value = {
'content_uuid': 'course-v1:edX-test-course',
'content_key': 'course-v1:edX-test-course',
'source': 'edX',
'mode': 'verified',
'content_price': 100,
'geag_variant_id': None,
}
mock_subsidy_content_metadata_api().get_course_price.return_value = 100
mock_lms_user_client.return_value.best_effort_user_data.return_value = {'email': '[email protected]'}
# Create privileged staff user that should be able to create Transactions.
self.set_up_operator()
url = reverse("api:v1:transaction-list")
mock_enterprise_client.enroll.side_effect = HTTPError()
mock_content_metadata_api().get_course_price.return_value = 100
test_content_key = "course-v1:edX+test+course.enroll.failed"
test_lms_user_id = 1234
post_data = {
Expand Down
15 changes: 15 additions & 0 deletions enterprise_subsidy/apps/api/v2/tests/test_transaction_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class APITestBase(APITestMixin):
Contains boilerplate to create a couple of subsidies with related ledgers and starting transactions.
"""

lms_user_email = '[email protected]'
enterprise_1_uuid = STATIC_ENTERPRISE_UUID
enterprise_2_uuid = str(uuid.uuid4())

Expand All @@ -56,6 +57,8 @@ class APITestBase(APITestMixin):

content_key_1 = "course-v1:edX+test+course.1"
content_key_2 = "course-v1:edX+test+course.2"
content_title_1 = "edX: Test Course 1"
content_title_2 = "edx: Test Course 2"

@classmethod
def setUpClass(cls):
Expand Down Expand Up @@ -108,8 +111,10 @@ def _setup_transactions(cls):
quantity=-1000,
ledger=cls.subsidy_1.ledger,
lms_user_id=STATIC_LMS_USER_ID+1000,
lms_user_email=cls.lms_user_email,
subsidy_access_policy_uuid=cls.subsidy_access_policy_2_uuid,
content_key=cls.content_key_2,
content_title=cls.content_title_2,
)
# Also create a reversed transaction, and also include metadata on both the transaction and reversal.
cls.subsidy_1_transaction_3 = TransactionFactory(
Expand All @@ -118,8 +123,10 @@ def _setup_transactions(cls):
quantity=-1000,
ledger=cls.subsidy_1.ledger,
lms_user_id=STATIC_LMS_USER_ID,
lms_user_email=cls.lms_user_email,
subsidy_access_policy_uuid=cls.subsidy_access_policy_2_uuid,
content_key=cls.content_key_2,
content_title=cls.content_title_2,
metadata={"bin": "baz"},
)
cls.subsidy_1_transaction_3_reversal = ReversalFactory(
Expand Down Expand Up @@ -256,8 +263,10 @@ def setUpClass(cls):
quantity=0,
ledger=cls.subsidy_1.ledger,
lms_user_id=STATIC_LMS_USER_ID, # This is the only transaction belonging to the requester.
lms_user_email=cls.lms_user_email,
subsidy_access_policy_uuid=cls.subsidy_access_policy_1_uuid,
content_key=cls.content_key_1,
content_title=cls.content_title_1,
)

def test_list_transactions_metadata_format(self):
Expand Down Expand Up @@ -705,8 +714,10 @@ def test_operator_creation_happy_path_transaction_exists(self, mock_price_for_co
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.price_for_content")
@mock.patch("enterprise_subsidy.apps.content_metadata.api.ContentMetadataApi.get_content_summary")
@mock.patch("enterprise_subsidy.apps.subsidy.models.Subsidy.lms_user_client")
def test_operator_creation_happy_path_201(
self,
mock_lms_user_client,
mock_get_content_summary,
mock_price_for_content,
mock_enterprise_client
Expand All @@ -717,11 +728,15 @@ def test_operator_creation_happy_path_201(
"""
self.set_up_operator()

mock_lms_user_client.return_value.best_effort_user_data.return_value = {
'email': self.lms_user_email,
}
mock_enterprise_client.enroll.return_value = 'my-fulfillment-id'
mock_price_for_content.return_value = 1000
mock_get_content_summary.return_value = {
'content_uuid': self.content_key_1,
'content_key': self.content_key_1,
'content_title': self.content_title_1,
'source': 'edX',
'mode': 'verified',
'content_price': 10000,
Expand Down
Loading

0 comments on commit b0f03c9

Please sign in to comment.