Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add expiration date field in credits_available serializer #238

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ class SubsidyAccessPolicyCreditsAvailableResponseSerializer(SubsidyAccessPolicyR
"""
remaining_balance_per_user = serializers.SerializerMethodField()
remaining_balance = serializers.SerializerMethodField()
subsidy_end_date = serializers.SerializerMethodField()

def get_remaining_balance_per_user(self, obj):
lms_user_id = self.context.get('lms_user_id')
Expand All @@ -423,6 +424,9 @@ def get_remaining_balance_per_user(self, obj):
def get_remaining_balance(self, obj):
return obj.remaining_balance()

def get_subsidy_end_date(self, obj):
return obj.subsidy_expiration_datetime


class SubsidyAccessPolicyCanRedeemReasonResponseSerializer(serializers.Serializer):
"""
Expand Down
53 changes: 52 additions & 1 deletion enterprise_access/apps/api/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"""
Tests for the serializers in the API.
"""
from unittest import mock
from uuid import uuid4

from django.conf import settings
from django.test import TestCase
from django.urls import reverse

from enterprise_access.apps.api.serializers.subsidy_access_policy import SubsidyAccessPolicyRedeemableResponseSerializer
from enterprise_access.apps.api.serializers.subsidy_access_policy import (
SubsidyAccessPolicyCreditsAvailableResponseSerializer,
SubsidyAccessPolicyRedeemableResponseSerializer
)
from enterprise_access.apps.subsidy_access_policy.tests.factories import (
PerLearnerEnrollmentCapLearnerCreditAccessPolicyFactory
)
Expand Down Expand Up @@ -35,3 +41,48 @@ def test_get_policy_redemption_url(self):
expected_url = f"{settings.ENTERPRISE_ACCESS_URL}/api/v1/policy-redemption/" \
f"{self.non_redeemable_policy.uuid}/redeem/"
self.assertEqual(data["policy_redemption_url"], expected_url)


class TestSubsidyAccessPolicyCreditsAvailableResponseSerializer(TestCase):
"""
Tests for the SubsidyAccessPolicyCreditsAvailableResponseSerializer.
"""
def setUp(self):
self.user_id = 24
self.enterprise_uuid = uuid4()
self.redeemable_policy = PerLearnerEnrollmentCapLearnerCreditAccessPolicyFactory(
enterprise_customer_uuid=self.enterprise_uuid,
spend_limit=300,
active=True
)

@mock.patch('enterprise_access.apps.subsidy_access_policy.models.SubsidyAccessPolicy.transactions_for_learner')
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.get_and_cache_subsidy_record')
def test_get_subsidy_end_date(self, mock_subsidy_record, mock_transactions_for_learner):
"""
Test that the get_subsidy_end_date method returns the correct
subsidy expiration date.
"""
mock_transactions_for_learner.return_value = {
'transactions': [],
'aggregates': {
'total_quantity': 0,
},
}
subsidy_exp_date = '2030-01-01 12:00:00Z'
mock_subsidy_record.return_value = {
'uuid': str(uuid4()),
'title': 'Test Subsidy',
'enterprise_customer_uuid': str(self.enterprise_uuid),
'expiration_datetime': subsidy_exp_date,
'active_datetime': '2020-01-01 12:00:00Z',
'current_balance': '1000',
}
serializer = SubsidyAccessPolicyCreditsAvailableResponseSerializer(
[self.redeemable_policy],
many=True,
context={'lms_user_id': self.user_id}
)
data = serializer.data
self.assertIn('subsidy_end_date', data[0])
self.assertEqual(data[0].get('subsidy_end_date'), subsidy_exp_date)
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ def setup_subsidy_mocks(self):
'expiration_datetime': self.tomorrow,
'is_active': True,
}
subsidy_client_patcher = patch.object(
SubsidyAccessPolicy, 'subsidy_client'
subsidy_record_patcher = patch.object(
SubsidyAccessPolicy, 'subsidy_record'
)
self.mock_subsidy_client = subsidy_client_patcher.start()
self.mock_subsidy_client.retrieve_subsidy.return_value = mock_subsidy
self.mock_subsidy_record = subsidy_record_patcher.start()
self.mock_subsidy_record.return_value = mock_subsidy

self.addCleanup(subsidy_client_patcher.stop)
self.addCleanup(subsidy_record_patcher.stop)


@ddt.ddt
Expand Down Expand Up @@ -1067,10 +1067,20 @@ def test_redeem_policy_redemption_idempotency_key_versions(
assert new_idempotency_key_sent == baseline_idempotency_key

@mock.patch('enterprise_access.apps.subsidy_access_policy.models.get_and_cache_transactions_for_learner')
def test_credits_available_endpoint(self, mock_transactions_cache_for_learner):
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.get_and_cache_subsidy_record')
def test_credits_available_endpoint(self, mock_subsidy_record, mock_transactions_cache_for_learner):
"""
Verify that SubsidyAccessPolicyViewset credits_available returns credit based policies with redeemable credit.
"""
mock_subsidy_record.return_value = {
'uuid': str(uuid4()),
'title': 'Test Subsidy',
'enterprise_customer_uuid': str(self.enterprise_uuid),
'expiration_datetime': '2030-01-01 12:00:00Z',
'active_datetime': '2020-01-01 12:00:00Z',
'current_balance': '1000',
}

mock_transaction_record = {
'uuid': str(uuid4()),
'state': TransactionStateChoices.COMMITTED,
Expand Down
4 changes: 2 additions & 2 deletions enterprise_access/apps/subsidy_access_policy/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
)
from .content_metadata_api import get_and_cache_catalog_contains_content, get_and_cache_content_metadata
from .exceptions import ContentPriceNullException, SubsidyAccessPolicyLockAttemptFailed, SubsidyAPIHTTPError
from .subsidy_api import get_and_cache_transactions_for_learner
from .subsidy_api import get_and_cache_subsidy_record, get_and_cache_transactions_for_learner
from .utils import ProxyAwareHistoricalRecords, create_idempotency_key_for_transaction, get_versioned_subsidy_client

POLICY_LOCK_RESOURCE_NAME = "subsidy_access_policy"
Expand Down Expand Up @@ -220,7 +220,7 @@ def __new__(cls, *args, **kwargs):
return super().__new__(proxy_class) # pylint: disable=lost-exception

def subsidy_record(self):
return self.subsidy_client.retrieve_subsidy(subsidy_uuid=self.subsidy_uuid)
return get_and_cache_subsidy_record(subsidy_uuid=self.subsidy_uuid)

def subsidy_balance(self):
"""
Expand Down
19 changes: 19 additions & 0 deletions enterprise_access/apps/subsidy_access_policy/subsidy_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ def learner_transaction_cache_key(subsidy_uuid, lms_user_id):
return versioned_cache_key('get_transactions_for_learner', subsidy_uuid, lms_user_id)


def subsidy_record_cache_key(subsidy_uuid):
return versioned_cache_key('get_subsidy_record', subsidy_uuid)


def get_and_cache_subsidy_record(subsidy_uuid):
"""
Get a subsidy record associated with a policy.
"""
cache_key = subsidy_record_cache_key(subsidy_uuid)
cached_response = request_cache().get_cached_response(cache_key)
if cached_response.is_found:
return cached_response.value

client = get_versioned_subsidy_client()
response = client.retrieve_subsidy(subsidy_uuid=subsidy_uuid)
request_cache().set(cache_key, response)
return response


def get_and_cache_transactions_for_learner(subsidy_uuid, lms_user_id):
"""
Get all transactions for a learner in a given subsidy. This can
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Tests for subsidy_access_policy models.
"""
from datetime import datetime, timedelta
from unittest import mock
from unittest.mock import patch
from uuid import uuid4

Expand Down Expand Up @@ -541,7 +542,8 @@ def test_content_would_exceed_limit_positive_spent_amount(self):
with self.assertRaisesRegex(Exception, 'Expected a sum of transaction quantities <= 0'):
self.per_learner_enroll_policy.content_would_exceed_limit(10, 100, 15)

def test_mock_subsidy_datetimes(self):
@mock.patch('enterprise_access.apps.subsidy_access_policy.models.get_and_cache_subsidy_record')
def test_mock_subsidy_datetimes(self, mock_subsidy_record):
yesterday = datetime.utcnow() - timedelta(days=1)
tomorrow = datetime.utcnow() + timedelta(days=1)
mock_subsidy = {
Expand All @@ -550,7 +552,7 @@ def test_mock_subsidy_datetimes(self):
'expiration_datetime': tomorrow,
'is_active': True,
}
self.mock_subsidy_client.retrieve_subsidy.return_value = mock_subsidy
mock_subsidy_record.return_value = mock_subsidy
policy = PerLearnerEnrollmentCapLearnerCreditAccessPolicyFactory.create()
assert policy.subsidy_record() == mock_subsidy

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@

from django.test import TestCase

from ..subsidy_api import get_and_cache_transactions_for_learner, get_redemptions_by_content_and_policy_for_learner
from ..subsidy_api import (
get_and_cache_subsidy_record,
get_and_cache_transactions_for_learner,
get_redemptions_by_content_and_policy_for_learner
)
from .factories import PerLearnerSpendCapLearnerCreditAccessPolicyFactory


Expand Down Expand Up @@ -149,3 +153,37 @@ def test_redemptions_by_content_and_policy(self, mock_transaction_cache):
},
result,
)


class SubsidyRecordForPolicyTests(TestCase):
"""
Tests the ``get_and_cache_subsidy_record`` function.
"""
@mock.patch('enterprise_access.apps.subsidy_access_policy.subsidy_api.get_versioned_subsidy_client')
def test_request_caching_works(self, mock_client_getter):
"""
Test that we utilize the request cache.
"""
subsidy_uuid = str(uuid.uuid4())
expected_response_payload = {
'uuid': subsidy_uuid,
'title': 'Test Subsidy',
'enterprise_customer_uuid': str(subsidy_uuid),
'expiration_datetime': '2030-01-01 12:00:00Z',
'active_datetime': '2020-01-01 12:00:00Z',
'current_balance': '1000',
}
mock_client = mock_client_getter.return_value
mock_client.retrieve_subsidy.return_value = expected_response_payload

result = get_and_cache_subsidy_record(subsidy_uuid)

self.assertEqual(result, expected_response_payload)

# call it again, should be using the cache this time
next_result = get_and_cache_subsidy_record(subsidy_uuid)

self.assertEqual(next_result, expected_response_payload)

# we should only have used the client in the first call
mock_client.retrieve_subsidy.assert_called_once_with(subsidy_uuid=subsidy_uuid)
Loading