Skip to content

Commit c7af1ef

Browse files
committed
fix: validate late enrollment course run metadata
1 parent 634d52a commit c7af1ef

4 files changed

Lines changed: 46 additions & 1 deletion

File tree

enterprise_subsidy/apps/api_client/enterprise.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from enterprise_subsidy.apps.api_client.base_oauth import BaseOAuthClient
1212
from enterprise_subsidy.apps.core.utils import localized_utcnow
13+
from enterprise_subsidy.apps.subsidy.constants import ALLOW_LATE_ENROLLMENT_KEY
1314

1415
logger = logging.getLogger(__name__)
1516

@@ -109,7 +110,7 @@ def enroll(self, lms_user_id, course_run_key, ledger_transaction):
109110
}
110111
# If late enrollment has been enabled for this transaction, inform the enterprise bulk enroll endpoint to bypass
111112
# any enrollment deadline validation.
112-
if ledger_transaction.metadata and ledger_transaction.metadata.get('allow_late_enrollment', False):
113+
if ledger_transaction.metadata and ledger_transaction.metadata.get(ALLOW_LATE_ENROLLMENT_KEY, False):
113114
enrollment_info['force_enrollment'] = True
114115
customer_uuid = ledger_transaction.ledger.subsidy.enterprise_customer_uuid
115116
response = self.bulk_enroll_enterprise_learners(customer_uuid, [enrollment_info])

enterprise_subsidy/apps/subsidy/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
# Numeric constants
66
CENTS_PER_DOLLAR = 100
77

8+
# Transaction metadata keys
9+
ALLOW_LATE_ENROLLMENT_KEY = 'allow_late_enrollment'
10+
811
# System-wide roles defined by edx-enterprise, used across the entire openedx instance, and can be found in JWT tokens.
912
SYSTEM_ENTERPRISE_LEARNER_ROLE = 'enterprise_learner'
1013
SYSTEM_ENTERPRISE_ADMIN_ROLE = 'enterprise_admin'

enterprise_subsidy/apps/subsidy/models.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from enterprise_subsidy.apps.core.utils import localized_utcnow
3535
from enterprise_subsidy.apps.fulfillment.api import GEAGFulfillmentHandler, is_geag_fulfillment
3636
from enterprise_subsidy.apps.fulfillment.exceptions import IncompleteContentMetadataException
37+
from enterprise_subsidy.apps.subsidy.constants import ALLOW_LATE_ENROLLMENT_KEY
3738

3839
MOCK_CATALOG_CLIENT = mock.MagicMock()
3940
MOCK_ENROLLMENT_CLIENT = mock.MagicMock()
@@ -519,6 +520,12 @@ def redeem(
519520

520521
# Fetch one or more metadata keys from catalog service, with overall metadata request locally cached.
521522
content_metadata_summary = self.metadata_summary_for_content(content_key)
523+
is_late_enrollment = bool(metadata and metadata.get(ALLOW_LATE_ENROLLMENT_KEY))
524+
if is_late_enrollment and not content_metadata_summary.get('course_run_key'):
525+
raise ContentNotFoundForCustomerException(
526+
f'Late enrollment for {content_key} must resolve to a concrete course run. '
527+
'Use the exact course run key for custom or one-off presentations.'
528+
)
522529
content_title = content_metadata_summary.get('content_title')
523530
parent_content_key = content_metadata_summary.get('content_key')
524531
course_run_start_date = content_metadata_summary.get('course_run_start_date')

enterprise_subsidy/apps/subsidy/tests/test_models.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from enterprise_subsidy.apps.content_metadata.constants import ProductSources
2323
from enterprise_subsidy.apps.fulfillment.api import InvalidFulfillmentMetadataException
2424
from enterprise_subsidy.apps.fulfillment.exceptions import IncompleteContentMetadataException
25+
from enterprise_subsidy.apps.subsidy.constants import ALLOW_LATE_ENROLLMENT_KEY
2526
from test_utils.utils import MockResponse
2627

2728
from ..models import ContentNotFoundForCustomerException, PriceValidationError, Subsidy
@@ -472,6 +473,39 @@ def test_redeem_with_metadata(
472473
assert new_transaction.quantity == -mock_content_price
473474
assert new_transaction.metadata == tx_metadata
474475

476+
@mock.patch('enterprise_subsidy.apps.subsidy.models.Subsidy.price_for_content')
477+
@mock.patch('enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client')
478+
@mock.patch("enterprise_subsidy.apps.content_metadata.api.ContentMetadataApi.get_content_summary")
479+
def test_redeem_late_enrollment_requires_resolved_course_run(
480+
self, mock_get_content_summary, mock_enterprise_client, mock_price_for_content
481+
):
482+
"""
483+
Late enrollment should not attempt LMS enrollment when catalog metadata has no concrete run.
484+
"""
485+
lms_user_id = 1
486+
content_key = "edX+test"
487+
subsidy_access_policy_uuid = str(uuid4())
488+
mock_get_content_summary.return_value = {
489+
'content_uuid': 'edX+test',
490+
'content_key': 'edX+test',
491+
'content_title': 'edx: Test Course',
492+
'source': 'edX',
493+
'mode': 'verified',
494+
'content_price': 10000,
495+
'geag_variant_id': None,
496+
}
497+
mock_price_for_content.return_value = 1000
498+
499+
with self.assertRaisesRegex(ContentNotFoundForCustomerException, 'exact course run key'):
500+
self.subsidy.redeem(
501+
lms_user_id,
502+
content_key,
503+
subsidy_access_policy_uuid,
504+
metadata={ALLOW_LATE_ENROLLMENT_KEY: True},
505+
)
506+
507+
mock_enterprise_client.enroll.assert_not_called()
508+
475509
@mock.patch('enterprise_subsidy.apps.subsidy.models.is_geag_fulfillment', return_value=True)
476510
@mock.patch('enterprise_subsidy.apps.subsidy.models.Subsidy.price_for_content')
477511
@mock.patch('enterprise_subsidy.apps.subsidy.models.Subsidy.enterprise_client')

0 commit comments

Comments
 (0)