diff --git a/enterprise_subsidy/apps/api/v1/tests/test_views.py b/enterprise_subsidy/apps/api/v1/tests/test_views.py index d1f534d7..a33015c9 100644 --- a/enterprise_subsidy/apps/api/v1/tests/test_views.py +++ b/enterprise_subsidy/apps/api/v1/tests/test_views.py @@ -1803,6 +1803,7 @@ def test_successful_get( 'content_price': expected_content_price, 'mode': expected_mode, 'geag_variant_id': expected_geag_variant_id, + 'enroll_by_date': None, } # Now make a second call to validate that the view-level cache is utilized. @@ -1824,6 +1825,7 @@ def test_successful_get( 'content_price': expected_content_price, 'mode': expected_mode, 'geag_variant_id': expected_geag_variant_id, + 'enroll_by_date': None, } # Validate that, in the first, non-cached request, we call # the enterprise catalog endpoint via the client, and that diff --git a/enterprise_subsidy/apps/content_metadata/api.py b/enterprise_subsidy/apps/content_metadata/api.py index 04a7b897..bc283680 100644 --- a/enterprise_subsidy/apps/content_metadata/api.py +++ b/enterprise_subsidy/apps/content_metadata/api.py @@ -124,12 +124,47 @@ def get_geag_variant_id_for_content(self, content_identifier, content_data): variant_id = additional_metadata.get('variant_id') return variant_id + def enroll_by_date_for_content(self, content_data, content_mode): + """ + Determines the enrollment deadline for the given ``content_data``, + which could be either a course or course run, hence the branching + on content type below. + + Args: + content_data: A dictionary of metadata pertaining to a course or course run. + content_mode: The (already computed) course mode for the content. + + Returns: + A datetime string, or possibly null. + """ + if content_data.get('content_type') == 'course': + return content_data.get('normalized_metadata', {}).get('enroll_by_date') + + # For edx-verified mode course runs, first try to extract + # the verified upgrade deadline from the course run's seat + # associated with the given content_mode + upgrade_deadline = None + if content_mode == CourseModes.EDX_VERIFIED.value: + seats_for_mode = [ + seat for seat in content_data.get('seats', []) + if seat.get('type') == content_mode + ] + if seats_for_mode: + seat = seats_for_mode[0] + upgrade_deadline = seat.get('upgrade_deadline_override') or seat.get('upgrade_deadline') + + # Return the upgrade deadline. If no such deadline exists, + # or if we're dealing with another course mode (e.g. exec ed), + # use the `enrollment_end` of the course run. + return upgrade_deadline or content_data.get('enrollment_end') + def summary_data_for_content(self, content_identifier, content_data): """ Returns a summary dict specifying the content_uuid, content_key, source, and content_price for a dict of content metadata. """ course_run_content = self.get_course_run(content_identifier, content_data) + content_mode = self.mode_for_content(content_data) return { 'content_title': content_data.get('title'), 'content_uuid': content_data.get('uuid'), @@ -137,8 +172,9 @@ def summary_data_for_content(self, content_identifier, content_data): 'course_run_uuid': course_run_content.get('uuid'), 'course_run_key': course_run_content.get('key'), 'source': self.product_source_for_content(content_data), - 'mode': self.mode_for_content(content_data), + 'mode': content_mode, 'content_price': self.price_for_content(content_data, course_run_content), + 'enroll_by_date': self.enroll_by_date_for_content(content_data, content_mode), 'geag_variant_id': self.get_geag_variant_id_for_content(content_identifier, content_data), } diff --git a/enterprise_subsidy/apps/content_metadata/tests/test_api.py b/enterprise_subsidy/apps/content_metadata/tests/test_api.py index 60e6d516..cb7140d0 100644 --- a/enterprise_subsidy/apps/content_metadata/tests/test_api.py +++ b/enterprise_subsidy/apps/content_metadata/tests/test_api.py @@ -92,7 +92,10 @@ def setUpTestData(cls): 'course_run_keys': [cls.courserun_key_1], 'content_last_modified': '2023-03-06T20:56:46.003840Z', 'enrollment_url': 'https://foobar.com', - 'active': False + 'active': False, + 'normalized_metadata': { + 'enroll_by_date': '2023-05-26T15:45:32.494051Z', + }, } cls.executive_education_course_metadata = { @@ -136,6 +139,9 @@ def setUpTestData(cls): "additional_metadata": { "variant_id": cls.variant_id_2, }, + "normalized_metadata": { + "enroll_by_date": "2024-01-01T00:00:00Z", + }, } @ddt.data( @@ -216,6 +222,7 @@ def test_summary_data_for_content(self): assert summary.get('content_key') == self.course_key assert summary.get('course_run_key') == self.courserun_key_1 assert summary.get('content_price') == 14900 + assert summary.get('enroll_by_date') == '2023-05-26T15:45:32.494051Z' def test_summary_data_for_exec_ed_content(self): mode = self.content_metadata_api.mode_for_content(self.executive_education_course_metadata) @@ -251,6 +258,7 @@ def test_summary_data_for_exec_ed_content(self): assert summary.get('course_run_key') is self.courserun_key_2 assert summary.get('content_price') == 59949 assert summary.get('geag_variant_id') == self.variant_id_2 + assert summary.get('enroll_by_date') == '2024-01-01T00:00:00Z' @ddt.data( { @@ -428,3 +436,38 @@ def test_tiered_caching_works(self, mock_catalog_client): ) assert client_instance.get_content_metadata.call_count == 1 TieredCache.delete_all_tiers(cache_key) + + @ddt.data(True, False) + def test_enroll_by_date_for_verified_course_run_content(self, has_override): + upgrade_deadline_key = 'upgrade_deadline_override' if has_override else 'upgrade_deadline' + content_data = { + 'content_type': 'courserun', + 'enrollment_end': '2024-12-01T00:00:00Z', + 'seats': [ + { + 'type': 'verified', + upgrade_deadline_key: '2025-01-01T00:00:00Z', + } + ] + } + self.assertEqual( + ContentMetadataApi().enroll_by_date_for_content(content_data, 'verified'), + '2025-01-01T00:00:00Z', + ) + + @ddt.data('verified', 'paid-executive-education') + def test_enroll_by_date_for_content_fallback(self, mode): + content_data = { + 'content_type': 'courserun', + 'enrollment_end': '2024-12-01T00:00:00Z', + } + self.assertEqual( + ContentMetadataApi().enroll_by_date_for_content(content_data, mode), + '2024-12-01T00:00:00Z', + ) + + def test_enroll_by_date_for_content_handles_null(self): + content_data = { + 'content_type': 'courserun', + } + self.assertIsNone(ContentMetadataApi().enroll_by_date_for_content(content_data, 'verified'))