diff --git a/RELEASE.rst b/RELEASE.rst index bf439ed55..1b546c162 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,11 @@ Release Notes ============= +Version 0.160.6 +--------------- + +- feat: skip non usd emeritus courses (#3174) + Version 0.160.5 (Released October 07, 2024) --------------- diff --git a/courses/sync_external_courses/emeritus_api.py b/courses/sync_external_courses/emeritus_api.py index 5b9cb62d4..336694632 100644 --- a/courses/sync_external_courses/emeritus_api.py +++ b/courses/sync_external_courses/emeritus_api.py @@ -37,6 +37,12 @@ class EmeritusKeyMap(Enum): REPORT_NAMES = ["Batch"] PLATFORM_NAME = "Emeritus" DATE_FORMAT = "%Y-%m-%d" + REQUIRED_FIELDS = [ + "course_title", + "course_code", + "course_run_code", + "list_currency", + ] COURSE_PAGE_SUBHEAD = "Delivered in collaboration with Emeritus." WHO_SHOULD_ENROLL_PAGE_HEADING = "WHO SHOULD ENROLL" LEARNING_OUTCOMES_PAGE_HEADING = "WHAT YOU WILL LEARN" @@ -81,6 +87,7 @@ def __init__(self, emeritus_course_json): if emeritus_course_json.get("list_price") else None ) + self.list_currency = emeritus_course_json.get("list_currency") self.start_date = strip_datetime( emeritus_course_json.get("start_date"), EmeritusKeyMap.DATE_FORMAT.value @@ -123,6 +130,33 @@ def __init__(self, emeritus_course_json): else [] ) + def validate_required_fields(self): + """ + Validates the course data. + """ + for field in EmeritusKeyMap.REQUIRED_FIELDS.value: + if not getattr(self, field, None): + log.info(f"Missing required field {field}") # noqa: G004 + return False + return True + + def validate_list_currency(self): + """ + Validates that the price is in USD. + + We only support `USD`. To support any other currency, we will have to manage the conversion to `USD`. + """ + if self.list_currency != "USD": + log.info(f"Invalid currency: {self.list_currency}.") # noqa: G004 + return False + return True + + def validate_end_date(self): + """ + Validates that the course end date is in the future. + """ + return self.end_date and now_in_utc() < self.end_date + def fetch_emeritus_courses(): """ @@ -225,19 +259,17 @@ def update_emeritus_course_runs(emeritus_courses): # noqa: C901, PLR0915 emeritus_course.course_run_code, ) ) - # If course_title, course_code, or course_run_code is missing, skip. - if not ( - emeritus_course.course_title - and emeritus_course.course_code - and emeritus_course.course_run_code + if ( + not emeritus_course.validate_required_fields() + or not emeritus_course.validate_list_currency() ): log.info( - f"Missing required course data. Skipping... Course data: {json.dumps(emeritus_course_json)}" # noqa: G004 + f"Skipping due to bad data... Course data: {json.dumps(emeritus_course_json)}" # noqa: G004 ) stats["course_runs_skipped"].add(emeritus_course.course_run_code) continue - if now_in_utc() > emeritus_course.end_date: + if not emeritus_course.validate_end_date(): log.info( f"Course run is expired, Skipping... Course data: {json.dumps(emeritus_course_json)}" # noqa: G004 ) diff --git a/courses/sync_external_courses/emeritus_api_test.py b/courses/sync_external_courses/emeritus_api_test.py index 3608b451d..d03f803d4 100644 --- a/courses/sync_external_courses/emeritus_api_test.py +++ b/courses/sync_external_courses/emeritus_api_test.py @@ -38,7 +38,7 @@ ) from ecommerce.factories import ProductFactory, ProductVersionFactory from mitxpro.test_utils import MockResponse -from mitxpro.utils import clean_url +from mitxpro.utils import clean_url, now_in_utc @pytest.fixture @@ -92,6 +92,17 @@ def emeritus_course_data_with_null_price(emeritus_course_data): return emeritus_course_json +@pytest.fixture +def emeritus_course_data_with_non_usd_price(emeritus_course_data): + """ + Emeritus course JSON with non USD price. + """ + emeritus_course_json = emeritus_course_data.copy() + emeritus_course_json["list_currency"] = "INR" + emeritus_course_json["course_run_code"] = "MO-INRC-98-10#1" + return emeritus_course_json + + @pytest.mark.parametrize( ("emeritus_course_run_code", "expected_course_run_tag"), [ @@ -430,6 +441,7 @@ def test_update_emeritus_course_runs( # noqa: PLR0915 emeritus_expired_course_data, emeritus_course_with_bad_data, emeritus_course_data_with_null_price, + emeritus_course_data_with_non_usd_price, ): """ Tests that `update_emeritus_course_runs` creates new courses and updates existing. @@ -477,6 +489,7 @@ def test_update_emeritus_course_runs( # noqa: PLR0915 emeritus_course_runs.append(emeritus_expired_course_data) emeritus_course_runs.append(emeritus_course_with_bad_data) emeritus_course_runs.append(emeritus_course_data_with_null_price) + emeritus_course_runs.append(emeritus_course_data_with_non_usd_price) stats = update_emeritus_course_runs(emeritus_course_runs) courses = Course.objects.filter(platform=platform) @@ -489,7 +502,7 @@ def test_update_emeritus_course_runs( # noqa: PLR0915 num_products_created = 2 if create_existing_data else 4 num_product_versions_created = 2 if create_existing_data else 4 assert len(courses) == 4 - assert len(stats["course_runs_skipped"]) == 1 + assert len(stats["course_runs_skipped"]) == 2 assert len(stats["course_runs_expired"]) == 1 assert len(stats["courses_created"]) == num_courses_created assert len(stats["existing_courses"]) == num_existing_courses @@ -725,3 +738,82 @@ def test_save_page_revision(is_draft_page, has_unpublished_changes): if has_unpublished_changes: assert external_course_page.has_unpublished_changes + + +@pytest.mark.parametrize( + ("title", "course_code", "course_run_code", "is_valid"), + [ + ( + "Internet of Things (IoT): Design and Applications", + "MO-DBIP", + "MO-DBIP.ELE-99-07#1", + True, + ), + ("", "MO-DBIP", "MO-DBIP.ELE-99-07#1", False), + (None, "MO-DBIP", "MO-DBIP.ELE-99-07#1", False), + ( + "Internet of Things (IoT): Design and Applications", + "", + "MO-DBIP.ELE-99-07#1", + False, + ), + ( + "Internet of Things (IoT): Design and Applications", + None, + "MO-DBIP.ELE-99-07#1", + False, + ), + ("Internet of Things (IoT): Design and Applications", "MO-DBIP", "", False), + ("Internet of Things (IoT): Design and Applications", "MO-DBIP", None, False), + ("", "", "", False), + (None, None, None, False), + ], +) +def test_emeritus_course_validate_required_fields( + emeritus_course_data, title, course_code, course_run_code, is_valid +): + """ + Tests that EmeritusCourse.validate_required_fields validates required fields. + """ + emeritus_course = EmeritusCourse(emeritus_course_data) + emeritus_course.course_title = title + emeritus_course.course_code = course_code + emeritus_course.course_run_code = course_run_code + assert emeritus_course.validate_required_fields() == is_valid + + +@pytest.mark.parametrize( + ("list_currency", "is_valid"), + [ + ("USD", True), + ("INR", False), + ("EUR", False), + ("GBP", False), + ("PKR", False), + ], +) +def test_emeritus_course_validate_list_currency( + emeritus_course_data, list_currency, is_valid +): + """ + Tests that the `USD` is the only valid currency for the Emeritus courses. + """ + emeritus_course = EmeritusCourse(emeritus_course_data) + emeritus_course.list_currency = list_currency + assert emeritus_course.validate_list_currency() == is_valid + + +@pytest.mark.parametrize( + ("end_date", "is_valid"), + [ + (now_in_utc() + timedelta(days=1), True), + (now_in_utc() - timedelta(days=1), False), + ], +) +def test_emeritus_course_validate_end_date(emeritus_course_data, end_date, is_valid): + """ + Tests that the valid end date is in the future for Emeritus courses. + """ + emeritus_course = EmeritusCourse(emeritus_course_data) + emeritus_course.end_date = end_date + assert emeritus_course.validate_end_date() == is_valid diff --git a/courses/sync_external_courses/test_data/batch_test.json b/courses/sync_external_courses/test_data/batch_test.json index 47391e2c2..90b8eed24 100644 --- a/courses/sync_external_courses/test_data/batch_test.json +++ b/courses/sync_external_courses/test_data/batch_test.json @@ -128,7 +128,7 @@ "end_date": "2099-12-29", "Category": "Healthcare", "list_price": 7650, - "list_currency": null, + "list_currency": "USD", "total_weeks": 28, "product_family": "SEPO", "product_sub_type": "Online", @@ -151,7 +151,7 @@ "end_date": "2099-02-23", "Category": "Technology", "list_price": 7650, - "list_currency": null, + "list_currency": "USD", "total_weeks": 29, "product_family": "SEPO", "product_sub_type": "Online", diff --git a/mitxpro/settings.py b/mitxpro/settings.py index 168c925f8..f62891337 100644 --- a/mitxpro/settings.py +++ b/mitxpro/settings.py @@ -26,7 +26,7 @@ from mitxpro.celery_utils import OffsettingSchedule from mitxpro.sentry import init_sentry -VERSION = "0.160.5" +VERSION = "0.160.6" env.reset()