diff --git a/enterprise_catalog/apps/api/tests/test_tasks.py b/enterprise_catalog/apps/api/tests/test_tasks.py index e8194b13..d13c0ffc 100644 --- a/enterprise_catalog/apps/api/tests/test_tasks.py +++ b/enterprise_catalog/apps/api/tests/test_tasks.py @@ -959,6 +959,27 @@ def test_get_algolia_objects_from_course_metadata(self): test_course.catalog_queries.set(catalog_queries[0:3]) algolia_objects = tasks.get_algolia_objects_from_course_content_metadata(test_course) + + expected_transformed_advertised_course_run = { + 'key': 'course-v1:edX+DemoX+2T2024', + 'pacing_type': None, + 'availability': 'current', + 'start': '2024-02-12T11:00:00Z', + 'end': '2026-02-05T11:00:00Z', + 'min_effort': None, + 'max_effort': None, + 'weeks_to_complete': None, + 'upgrade_deadline': 1769471999.0, + 'enroll_by': 1769471999.0, + 'has_enroll_by': True, + 'enroll_start': None, + 'has_enroll_start': False, + 'content_price': 50.0, + 'is_active': True, + 'is_late_enrollment_eligible': False, + 'restriction_type': None + } + # Should look something like- # [{'advertised_course_run': {'availability': 'current', # 'end': None, @@ -1004,6 +1025,7 @@ def test_get_algolia_objects_from_course_metadata(self): for algo_object in algolia_objects: assert algo_object.get('key') == test_course.content_key assert algo_object.get('uuid') == test_course.json_metadata.get('uuid') + assert algo_object.get('advertised_course_run') == expected_transformed_advertised_course_run if object_catalogs := algo_object.get('enterprise_catalog_uuids'): assert set(object_catalogs) == {str(catalog.uuid) for catalog in catalogs} diff --git a/enterprise_catalog/apps/catalog/algolia_utils.py b/enterprise_catalog/apps/catalog/algolia_utils.py index 80e9cc23..77278708 100644 --- a/enterprise_catalog/apps/catalog/algolia_utils.py +++ b/enterprise_catalog/apps/catalog/algolia_utils.py @@ -31,8 +31,8 @@ VIDEO, ) from enterprise_catalog.apps.catalog.content_metadata_utils import ( + get_advertised_course_run, get_course_first_paid_enrollable_seat_price, - get_course_run_by_uuid, is_course_run_active, ) from enterprise_catalog.apps.catalog.models import ContentMetadata @@ -229,11 +229,7 @@ def _should_index_course(course_metadata): bool: Whether or not the course should be indexed by algolia. """ course_json_metadata = course_metadata.json_metadata - advertised_course_run_uuid = course_json_metadata.get('advertised_course_run_uuid') - advertised_course_run = get_course_run_by_uuid( - course_json_metadata, - advertised_course_run_uuid, - ) + advertised_course_run = get_advertised_course_run(course_json_metadata) # We define a series of no-arg functions, each of which has the property that, # if it returns true, means we should *not* index this record. @@ -477,8 +473,7 @@ def get_course_language(course): Returns: string: human-readable language name parsed from a language code, or None if language name is not present. """ - advertised_course_run = get_course_run_by_uuid(course, course.get('advertised_course_run_uuid')) - if not advertised_course_run: + if not (advertised_course_run := get_advertised_course_run(course)): return None content_language_name = advertised_course_run.get('content_language_search_facet_name') @@ -496,8 +491,7 @@ def get_course_transcript_languages(course): Returns: list: a list of available human-readable video transcript languages parsed from a language code. """ - advertised_course_run = get_course_run_by_uuid(course, course.get('advertised_course_run_uuid')) - if not advertised_course_run: + if not (advertised_course_run := get_advertised_course_run(course)): return None transcript_languages = advertised_course_run.get('transcript_languages_search_facet_names') @@ -1168,23 +1162,6 @@ def _get_course_run(course, course_run): return course_run -def get_advertised_course_run(course): - """ - Get part of the advertised course_run as per advertised_course_run_uuid - - Argument: - course (dict) - - Returns: - dict: containing key, pacing_type, start, end, and upgrade deadline - for the course_run, or None - """ - full_course_run = get_course_run_by_uuid(course, course.get('advertised_course_run_uuid')) - if full_course_run is None: - return None - return _get_course_run(course, full_course_run) - - def get_course_runs(course): """ Extract and transform a list of course runs into what we'll index. @@ -1514,6 +1491,8 @@ def _algolia_object_from_product(product, algolia_fields): """ searchable_product = copy.deepcopy(product) if searchable_product.get('content_type') == COURSE: + advertised_course_run = get_advertised_course_run(searchable_product) + transformed_advertised_course_run = _get_course_run(searchable_product, advertised_course_run) searchable_product.update({ 'language': get_course_language(searchable_product), 'availability': get_course_availability(searchable_product), @@ -1522,7 +1501,7 @@ def _algolia_object_from_product(product, algolia_fields): 'program_titles': get_course_program_titles(searchable_product), 'subjects': get_course_subjects(searchable_product), 'card_image_url': get_course_card_image_url(searchable_product), - 'advertised_course_run': get_advertised_course_run(searchable_product), + 'advertised_course_run': transformed_advertised_course_run, 'course_runs': get_course_runs(searchable_product), 'upcoming_course_runs': get_upcoming_course_runs(searchable_product), 'skill_names': get_course_skill_names(searchable_product), diff --git a/enterprise_catalog/apps/catalog/content_metadata_utils.py b/enterprise_catalog/apps/catalog/content_metadata_utils.py index da894034..60739e27 100644 --- a/enterprise_catalog/apps/catalog/content_metadata_utils.py +++ b/enterprise_catalog/apps/catalog/content_metadata_utils.py @@ -89,7 +89,7 @@ def get_course_first_paid_enrollable_seat_price(course): # Use advertised course run. # If that fails use one of the other active course runs. # (The latter is what Discovery does) - advertised_course_run = get_course_run_by_uuid(course, course.get('advertised_course_run_uuid')) + advertised_course_run = get_advertised_course_run(course) if advertised_course_run and advertised_course_run.get('first_enrollable_paid_seat_price'): return advertised_course_run.get('first_enrollable_paid_seat_price') @@ -102,3 +102,20 @@ def get_course_first_paid_enrollable_seat_price(course): if 'first_enrollable_paid_seat_price' in course_run: return course_run['first_enrollable_paid_seat_price'] return None + + +def get_advertised_course_run(course): + """ + Get part of the advertised course_run as per advertised_course_run_uuid + + Argument: + course (dict) + + Returns: + dict: containing key, pacing_type, start, end, and upgrade deadline + for the course_run, or None + """ + full_course_run = get_course_run_by_uuid(course, course.get('advertised_course_run_uuid')) + if full_course_run is None: + return None + return full_course_run diff --git a/enterprise_catalog/apps/catalog/models.py b/enterprise_catalog/apps/catalog/models.py index aaaf7999..b397150c 100644 --- a/enterprise_catalog/apps/catalog/models.py +++ b/enterprise_catalog/apps/catalog/models.py @@ -45,6 +45,7 @@ json_serialized_course_modes, ) from enterprise_catalog.apps.catalog.content_metadata_utils import ( + get_advertised_course_run, get_course_first_paid_enrollable_seat_price, ) from enterprise_catalog.apps.catalog.utils import ( @@ -555,10 +556,15 @@ def get_content_enrollment_url(self, content_metadata): else: # Catalog param only needed for legacy (non-learner-portal) enrollment URLs params['catalog'] = self.uuid + + course_run_key = content_key + if not parent_content_key: + if advertised_course_run := get_advertised_course_run(content_metadata.json_metadata): + course_run_key = advertised_course_run['key'] url = '{}/enterprise/{}/course/{}/enroll/'.format( settings.LMS_BASE_URL, self.enterprise_uuid, - content_key, + course_run_key, ) return update_query_parameters(url, params) diff --git a/enterprise_catalog/apps/catalog/serializers.py b/enterprise_catalog/apps/catalog/serializers.py index e9d7e8d4..a3e082ca 100644 --- a/enterprise_catalog/apps/catalog/serializers.py +++ b/enterprise_catalog/apps/catalog/serializers.py @@ -11,7 +11,7 @@ from enterprise_catalog.apps.api.constants import CourseMode from enterprise_catalog.apps.catalog.constants import EXEC_ED_2U_COURSE_TYPE from enterprise_catalog.apps.catalog.content_metadata_utils import ( - get_course_run_by_uuid, + get_advertised_course_run, ) @@ -113,8 +113,7 @@ def is_exec_ed_2u_course(self): def course_run_metadata(self): if run_metadata := self.instance.get('course_run_metadata'): return run_metadata - advertised_course_run_uuid = self.course_metadata.get('advertised_course_run_uuid') - return get_course_run_by_uuid(self.course_metadata, advertised_course_run_uuid) + return get_advertised_course_run(self.course_metadata) @extend_schema_field(serializers.DateTimeField) def get_start_date(self, obj) -> str: # pylint: disable=unused-argument diff --git a/enterprise_catalog/apps/catalog/tests/factories.py b/enterprise_catalog/apps/catalog/tests/factories.py index 45fab4b4..93ef6394 100644 --- a/enterprise_catalog/apps/catalog/tests/factories.py +++ b/enterprise_catalog/apps/catalog/tests/factories.py @@ -99,7 +99,7 @@ def _json_metadata(self): 'logo_image_url': fake.image_url() + '.jpg', }] course_runs = [{ - 'key': 'course-v1:edX+DemoX', + 'key': 'course-v1:edX+DemoX+2T2024', 'uuid': str(FAKE_ADVERTISED_COURSE_RUN_UUID), 'content_language': 'en-us', 'status': 'published', diff --git a/enterprise_catalog/apps/catalog/tests/test_algolia_utils.py b/enterprise_catalog/apps/catalog/tests/test_algolia_utils.py index 5309f5c5..469c07fb 100644 --- a/enterprise_catalog/apps/catalog/tests/test_algolia_utils.py +++ b/enterprise_catalog/apps/catalog/tests/test_algolia_utils.py @@ -6,6 +6,7 @@ from django.test import TestCase from enterprise_catalog.apps.catalog import algolia_utils as utils +from enterprise_catalog.apps.catalog.algolia_utils import _get_course_run from enterprise_catalog.apps.catalog.constants import ( ALGOLIA_DEFAULT_TIMESTAMP, COURSE, @@ -16,6 +17,9 @@ PROGRAM, RESTRICTION_FOR_B2B, ) +from enterprise_catalog.apps.catalog.content_metadata_utils import ( + get_advertised_course_run, +) from enterprise_catalog.apps.catalog.tests.factories import ( ContentMetadataFactory, ) @@ -353,163 +357,6 @@ def test_get_course_subjects(self, course_metadata, expected_subjects): course_subjects = utils.get_course_subjects(course_metadata) assert sorted(course_subjects) == sorted(expected_subjects) - @ddt.data( - ( - { - 'course_runs': [{ - 'key': 'course-v1:org+course+1T2021', - 'uuid': ADVERTISED_COURSE_RUN_UUID, - 'pacing_type': 'instructor_paced', - 'start': '2013-10-16T14:00:00Z', - 'end': '2014-10-16T14:00:00Z', - 'enrollment_end': '2013-10-17T14:00:00Z', - 'availability': 'Current', - 'min_effort': 10, - 'max_effort': 14, - 'weeks_to_complete': 13, - 'status': 'published', - 'is_enrollable': True, - 'is_marketable': True, - 'enrollment_start': '2013-10-01T14:00:00Z', - }], - 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID - }, - { - 'key': 'course-v1:org+course+1T2021', - 'pacing_type': 'instructor_paced', - 'start': '2013-10-16T14:00:00Z', - 'end': '2014-10-16T14:00:00Z', - 'availability': 'Current', - 'min_effort': 10, - 'max_effort': 14, - 'weeks_to_complete': 13, - 'upgrade_deadline': 32503680000.0, - 'enroll_start': 1380636000, - 'has_enroll_start': True, - 'has_enroll_by': True, - 'enroll_by': 1382018400.0, - 'is_active': True, - 'is_late_enrollment_eligible': False, - 'content_price': 0.0, - 'restriction_type': None, - }, - ), - ( - { - 'course_runs': [{ - 'uuid': uuid4(), - }], - 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID - }, - None, - ), - ( - { - 'course_runs': [{ - 'key': 'course-v1:org+course+1T2021', - 'uuid': ADVERTISED_COURSE_RUN_UUID, - 'pacing_type': 'instructor_paced', - 'start': '2013-10-16T14:00:00Z', - 'end': '2014-10-16T14:00:00Z', - 'enrollment_end': '2013-10-17T14:00:00Z', - 'enrollment_start_date': '2013-10-01T14:00:00Z', - 'availability': 'Current', - 'min_effort': 10, - 'max_effort': 14, - 'weeks_to_complete': 13, - 'status': 'published', - 'is_enrollable': True, - 'is_marketable': True, - 'seats': [ - { - 'type': 'audit', - 'upgrade_deadline': None, - }, - { - 'type': 'verified', - 'upgrade_deadline': '2015-01-04T15:52:00Z', - 'price': '50.00', - } - ], - 'first_enrollable_paid_seat_price': 50, - 'enrollment_start': '2013-10-01T14:00:00Z', - }], - 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID - }, - { - 'key': 'course-v1:org+course+1T2021', - 'pacing_type': 'instructor_paced', - 'start': '2013-10-16T14:00:00Z', - 'end': '2014-10-16T14:00:00Z', - 'availability': 'Current', - 'min_effort': 10, - 'max_effort': 14, - 'weeks_to_complete': 13, - 'upgrade_deadline': 1420386720.0, - 'enroll_by': 1382018400.0, - 'enroll_start': 1380636000.0, - 'has_enroll_by': True, - 'has_enroll_start': True, - 'content_price': 50, - 'is_active': True, - 'is_late_enrollment_eligible': False, - 'restriction_type': None, - } - ), - ( - { - 'course_runs': [{ - 'key': 'course-v1:org+course+1T2021', - 'uuid': ADVERTISED_COURSE_RUN_UUID, - 'pacing_type': 'instructor_paced', - 'start': '2013-10-16T14:00:00Z', - 'end': '2014-10-16T14:00:00Z', - 'enrollment_end': '2013-10-17T14:00:00Z', - 'availability': 'Current', - 'min_effort': 10, - 'max_effort': 14, - 'weeks_to_complete': 13, - 'status': 'published', - 'is_enrollable': True, - 'is_marketable': True, - 'seats': [{ - 'type': 'verified', - 'upgrade_deadline': None, - 'price': '50.00', - }], - 'first_enrollable_paid_seat_price': 50, - }], - 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID - }, - { - 'key': 'course-v1:org+course+1T2021', - 'pacing_type': 'instructor_paced', - 'start': '2013-10-16T14:00:00Z', - 'end': '2014-10-16T14:00:00Z', - 'availability': 'Current', - 'min_effort': 10, - 'max_effort': 14, - 'weeks_to_complete': 13, - 'upgrade_deadline': 32503680000.0, - 'enroll_by': 1382018400.0, - 'enroll_start': None, - 'has_enroll_by': True, - 'has_enroll_start': False, - 'content_price': 50, - 'is_active': True, - 'is_late_enrollment_eligible': False, - 'restriction_type': None, - } - ) - ) - @ddt.unpack - def test_get_advertised_course_run(self, searchable_course, expected_course_run): - """ - Assert get_advertised_course_runs fetches just enough info about advertised course run - """ - advertised_course_run = utils.get_advertised_course_run(searchable_course) - assert advertised_course_run == expected_course_run - @ddt.data( ( { @@ -1788,3 +1635,161 @@ def test_get_course_transcript_languages(self, course_metadata, expected_transcr """ transcript_languages = utils.get_course_transcript_languages(course_metadata) assert transcript_languages == expected_transcript_languages + + @ddt.data( + ( + { + 'course_runs': [{ + 'key': 'course-v1:org+course+1T2021', + 'uuid': ADVERTISED_COURSE_RUN_UUID, + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'enrollment_end': '2013-10-17T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'enrollment_start': '2013-10-01T14:00:00Z', + }], + 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID + }, + { + 'key': 'course-v1:org+course+1T2021', + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'upgrade_deadline': 32503680000.0, + 'enroll_start': 1380636000, + 'has_enroll_start': True, + 'has_enroll_by': True, + 'enroll_by': 1382018400.0, + 'is_active': True, + 'is_late_enrollment_eligible': False, + 'content_price': 0.0, + 'restriction_type': None, + }, + ), + ( + { + 'course_runs': [{ + 'uuid': uuid4(), + }], + 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID + }, + None, + ), + ( + { + 'course_runs': [{ + 'key': 'course-v1:org+course+1T2021', + 'uuid': ADVERTISED_COURSE_RUN_UUID, + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'enrollment_end': '2013-10-17T14:00:00Z', + 'enrollment_start_date': '2013-10-01T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'seats': [ + { + 'type': 'audit', + 'upgrade_deadline': None, + }, + { + 'type': 'verified', + 'upgrade_deadline': '2015-01-04T15:52:00Z', + 'price': '50.00', + } + ], + 'first_enrollable_paid_seat_price': 50, + 'enrollment_start': '2013-10-01T14:00:00Z', + }], + 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID + }, + { + 'key': 'course-v1:org+course+1T2021', + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'upgrade_deadline': 1420386720.0, + 'enroll_by': 1382018400.0, + 'enroll_start': 1380636000.0, + 'has_enroll_by': True, + 'has_enroll_start': True, + 'content_price': 50, + 'is_active': True, + 'is_late_enrollment_eligible': False, + 'restriction_type': None, + } + ), + ( + { + 'course_runs': [{ + 'key': 'course-v1:org+course+1T2021', + 'uuid': ADVERTISED_COURSE_RUN_UUID, + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'enrollment_end': '2013-10-17T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'seats': [{ + 'type': 'verified', + 'upgrade_deadline': None, + 'price': '50.00', + }], + 'first_enrollable_paid_seat_price': 50, + }], + 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID + }, + { + 'key': 'course-v1:org+course+1T2021', + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'upgrade_deadline': 32503680000.0, + 'enroll_by': 1382018400.0, + 'enroll_start': None, + 'has_enroll_by': True, + 'has_enroll_start': False, + 'content_price': 50, + 'is_active': True, + 'is_late_enrollment_eligible': False, + 'restriction_type': None, + } + ) + ) + @ddt.unpack + def test_get_course_run(self, searchable_course, expected_course_run): + """ + Assert get_advertised_course_run fetches just enough info about advertised course run + """ + advertised_course_run = get_advertised_course_run(searchable_course) + transformed_advertised_course_run = _get_course_run(searchable_course, advertised_course_run) + assert transformed_advertised_course_run == expected_course_run diff --git a/enterprise_catalog/apps/catalog/tests/test_content_metadata_utils.py b/enterprise_catalog/apps/catalog/tests/test_content_metadata_utils.py index f3cb9e5a..d5f86bc6 100644 --- a/enterprise_catalog/apps/catalog/tests/test_content_metadata_utils.py +++ b/enterprise_catalog/apps/catalog/tests/test_content_metadata_utils.py @@ -1,13 +1,19 @@ from uuid import uuid4 +import ddt from django.test import TestCase from enterprise_catalog.apps.catalog.content_metadata_utils import ( + get_advertised_course_run, tansform_force_included_courses, transform_course_metadata_to_visible, ) +ADVERTISED_COURSE_RUN_UUID = uuid4() + + +@ddt.ddt class ContentMetadataUtilsTests(TestCase): """ Tests for content metadata utils. @@ -51,3 +57,128 @@ def test_tansform_force_included_courses(self): courses = [content_metadata] tansform_force_included_courses(courses) assert courses[0]['course_runs'][0]['status'] == 'published' + + @ddt.data( + # Happy path: Multiple runs including advertised_course_run in course_runs, available advertised_course_run_uuid + ( + { + 'course_runs': [ + { + 'key': 'course-v1:org+course+1T2021', + 'uuid': ADVERTISED_COURSE_RUN_UUID, + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'enrollment_end': '2013-10-17T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'enrollment_start': '2013-10-01T14:00:00Z', + }, + { + 'key': 'course-v1:org+course+1T2021', + 'uuid': uuid4(), + 'pacing_type': 'instructor_paced', + 'start': '2016-10-16T14:00:00Z', + 'end': '2019-10-16T14:00:00Z', + 'enrollment_end': '2016-10-17T14:00:00Z', + 'availability': 'Upcoming', + 'min_effort': 11, + 'max_effort': 15, + 'weeks_to_complete': 15, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'enrollment_start': '2013-10-01T14:00:00Z', + } + ], + 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID + }, + { + 'key': 'course-v1:org+course+1T2021', + 'uuid': ADVERTISED_COURSE_RUN_UUID, + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'enrollment_end': '2013-10-17T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'enrollment_start': '2013-10-01T14:00:00Z', + }, + ), + # Edge case: course_runs does not include advertised_course_run with available advertised_course_run_uuid + ( + { + 'course_runs': [{ + 'uuid': uuid4(), + }], + 'advertised_course_run_uuid': ADVERTISED_COURSE_RUN_UUID + }, + None, + ), + # Edge case: No available course_runs and no advertised_course_run_uuid + ( + { + 'course_runs': [], + 'advertised_course_run_uuid': None + }, + None + ), + # Edge case: Available advertised_course_run within course_runs, and no advertised_course_run_uuid + ( + { + 'course_runs': [ + { + 'key': 'course-v1:org+course+1T2021', + 'uuid': ADVERTISED_COURSE_RUN_UUID, + 'pacing_type': 'instructor_paced', + 'start': '2013-10-16T14:00:00Z', + 'end': '2014-10-16T14:00:00Z', + 'enrollment_end': '2013-10-17T14:00:00Z', + 'availability': 'Current', + 'min_effort': 10, + 'max_effort': 14, + 'weeks_to_complete': 13, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'enrollment_start': '2013-10-01T14:00:00Z', + }, + { + 'key': 'course-v1:org+course+1T2021', + 'uuid': uuid4(), + 'pacing_type': 'instructor_paced', + 'start': '2016-10-16T14:00:00Z', + 'end': '2019-10-16T14:00:00Z', + 'enrollment_end': '2016-10-17T14:00:00Z', + 'availability': 'Upcoming', + 'min_effort': 11, + 'max_effort': 15, + 'weeks_to_complete': 15, + 'status': 'published', + 'is_enrollable': True, + 'is_marketable': True, + 'enrollment_start': '2013-10-01T14:00:00Z', + } + ], + 'advertised_course_run_uuid': None + }, + None + ), + ) + @ddt.unpack + def test_get_advertised_course(self, searchable_course, expected_course_run): + """ + Assert get_advertised_course_run fetches the expected_course_run + """ + advertised_course_run = get_advertised_course_run(searchable_course) + assert advertised_course_run == expected_course_run diff --git a/enterprise_catalog/apps/catalog/tests/test_models.py b/enterprise_catalog/apps/catalog/tests/test_models.py index 03b7367f..3c6d1f53 100644 --- a/enterprise_catalog/apps/catalog/tests/test_models.py +++ b/enterprise_catalog/apps/catalog/tests/test_models.py @@ -623,6 +623,49 @@ def test_get_content_enrollment_url( else: assert settings.LMS_BASE_URL in content_enrollment_url + @ddt.data( + { + 'content_key': 'course-v1:edX+DemoX+2T2024', + 'parent_content_key': 'edX+DemoX', + 'content_type': COURSE_RUN, + }, + { + 'content_key': 'edX+DemoX', + 'parent_content_key': None, + 'content_type': COURSE, + }, + ) + @ddt.unpack + def test_get_content_enrollment_url_disabled_learner_portal( + self, + content_key, + parent_content_key, + content_type + ): + enterprise_uuid = uuid4() + enterprise_slug = 'sluggy' + + enterprise_catalog = factories.EnterpriseCatalogFactory(enterprise_uuid=enterprise_uuid) + content_metadata = factories.ContentMetadataFactory( + content_key=content_key, + parent_content_key=parent_content_key, + content_type=content_type, + ) + + enterprise_catalog.catalog_query.contentmetadata_set.add(*[content_metadata]) + + mock_enterprise_customer_return_value = { + 'slug': enterprise_slug, + 'enable_learner_portal': False, + } + + with self._mock_enterprise_customer_cache( + mock_enterprise_customer_return_value, + ): + content_enrollment_url = enterprise_catalog.get_content_enrollment_url(content_metadata) + + assert "/course/course-v1:edX+DemoX+2T2024/enroll/" in content_enrollment_url + @mock.patch('enterprise_catalog.apps.api_client.enterprise_cache.EnterpriseApiClient') @ddt.data( { diff --git a/enterprise_catalog/apps/catalog/tests/test_serializers.py b/enterprise_catalog/apps/catalog/tests/test_serializers.py index 78ada547..2fb6b234 100644 --- a/enterprise_catalog/apps/catalog/tests/test_serializers.py +++ b/enterprise_catalog/apps/catalog/tests/test_serializers.py @@ -8,11 +8,13 @@ COURSE, EXEC_ED_2U_COURSE_TYPE, ) +from enterprise_catalog.apps.catalog.content_metadata_utils import ( + get_advertised_course_run, +) from enterprise_catalog.apps.catalog.serializers import ( NormalizedContentMetadataSerializer, ) from enterprise_catalog.apps.catalog.tests import factories -from enterprise_catalog.apps.catalog.utils import get_course_run_by_uuid @ddt.ddt @@ -224,8 +226,7 @@ def test_content_price(self, course_content.json_metadata['entitlements'] = entitlements course_content.json_metadata['course_type'] = course_type - advertised_course_run_uuid = course_content.json_metadata['advertised_course_run_uuid'] - advertised_course_run = get_course_run_by_uuid(course_content.json_metadata, advertised_course_run_uuid) + advertised_course_run = get_advertised_course_run(course_content.json_metadata) advertised_course_run['fixed_price_usd'] = fixed_price_usd advertised_course_run['first_enrollable_paid_seat_price'] = first_enrollable_paid_seat_price diff --git a/enterprise_catalog/apps/catalog/utils.py b/enterprise_catalog/apps/catalog/utils.py index 7a046424..ada81ea9 100644 --- a/enterprise_catalog/apps/catalog/utils.py +++ b/enterprise_catalog/apps/catalog/utils.py @@ -158,21 +158,3 @@ def to_timestamp(datetime_str): except (ValueError, TypeError) as exc: LOGGER.error(f"[to_timestamp][{exc}] Could not parse date string: {datetime_str}") return None - - -def get_course_run_by_uuid(course, course_run_uuid): - """ - Find a course_run based on uuid - - Arguments: - course (dict): course dict - course_run_uuid (str): uuid to lookup - - Returns: - dict: a course_run or None - """ - try: - course_run = [run for run in course.get('course_runs', []) if run.get('uuid') == course_run_uuid][0] - except IndexError: - return None - return course_run