11import datetime
22from collections import ChainMap
3+ from datetime import timedelta
4+ from unittest .mock import PropertyMock , patch
35
46import ddt
57import factory
68import pytest
79from django .conf import settings
810from django .contrib .sites .models import Site
911from django .test import TestCase , override_settings
12+ from django .utils import timezone
1013from pytz import UTC
1114
1215from conftest import TEST_DOMAIN
1619from course_discovery .apps .course_metadata .choices import ExternalProductStatus , ProgramStatus
1720from course_discovery .apps .course_metadata .models import CourseRunStatus , CourseType , ProductValue , ProgramType
1821from course_discovery .apps .course_metadata .tests .factories import (
19- AdditionalMetadataFactory , CourseFactory , CourseRunFactory , CourseTypeFactory , DegreeAdditionalMetadataFactory ,
20- DegreeFactory , GeoLocationFactory , LevelTypeFactory , OrganizationFactory , ProductMetaFactory , ProgramFactory ,
21- ProgramSubscriptionFactory , ProgramSubscriptionPriceFactory , ProgramTypeFactory , RestrictedCourseRunFactory ,
22- SeatFactory , SeatTypeFactory , SourceFactory , SubjectFactory , VideoFactory
22+ AdditionalMetadataFactory , CourseFactory , CourseRunFactory , CourseRunTypeFactory , CourseTypeFactory ,
23+ DegreeAdditionalMetadataFactory , DegreeFactory , GeoLocationFactory , LevelTypeFactory , OrganizationFactory ,
24+ ProductMetaFactory , ProgramFactory , ProgramSubscriptionFactory , ProgramSubscriptionPriceFactory , ProgramTypeFactory ,
25+ RestrictedCourseRunFactory , SeatFactory , SeatTypeFactory , SourceFactory , SubjectFactory , VideoFactory
2326)
2427from course_discovery .apps .ietf_language_tags .models import LanguageTag
2528
@@ -312,6 +315,8 @@ def test_earliest_upcoming_wins(self):
312315 def test_active_course_run_beats_no_active_course_run (self ):
313316 course_1 = self .create_course_with_basic_active_course_run ()
314317 course_2 = AlgoliaProxyCourseFactory (partner = self .__class__ .edxPartner )
318+ course_2 .advertised_course_run = None
319+ course_2 .save ()
315320 CourseRunFactory (
316321 course = course_2 ,
317322 start = self .YESTERDAY ,
@@ -320,7 +325,7 @@ def test_active_course_run_beats_no_active_course_run(self):
320325 status = CourseRunStatus .Published
321326 )
322327 assert course_1 .availability_rank
323- assert not course_2 .availability_rank
328+ assert course_2 .availability_rank is None
324329
325330 def test_course_availability_reflects_all_course_runs (self ):
326331 course = AlgoliaProxyCourseFactory (partner = self .__class__ .edxPartner )
@@ -593,13 +598,73 @@ def test_course_ai_languages(self):
593598 }
594599
595600 def test_course_ai_languages__no_advertised_run (self ):
596- course = self .create_blocked_course (status = CourseRunStatus .Unpublished )
597- assert course .product_ai_languages == {
598- 'translation_languages' : [],
599- 'transcription_languages' : []
601+ course = CourseFactory ()
602+ with patch .object (AlgoliaProxyCourse , 'advertised_course_run' , new_callable = PropertyMock ) as mock_run :
603+ mock_run .return_value = None
604+ proxy_course = AlgoliaProxyCourse (course )
605+ assert proxy_course .product_ai_languages == {
606+ 'translation_languages' : [],
607+ 'transcription_languages' : []
600608 }
601609
610+ def test_product_weeks_to_complete_from_advertised_run (self ):
611+ """
612+ Verify that AlgoliaProxyCourse correctly exposes weeks_to_complete
613+ from the advertised_course_run.
614+ """
615+ course = self .create_course_with_basic_active_course_run ()
616+ course .authoring_organizations .add (OrganizationFactory ())
617+ advertised_run = course .advertised_course_run
618+ advertised_run .weeks_to_complete = 7
619+ advertised_run .save ()
620+
621+ proxy_course = AlgoliaProxyCourse .objects .get (pk = course .pk )
622+ assert proxy_course .product_weeks_to_complete == 7
623+
624+ def test_product_weeks_to_complete_returns_none_if_no_run (self ):
625+ """
626+ Should return None if there are no course runs at all.
627+ """
628+ course = AlgoliaProxyCourseFactory (partner = self .__class__ .edxPartner )
629+ course .authoring_organizations .add (OrganizationFactory ())
630+
631+ proxy_course = AlgoliaProxyCourse .objects .get (pk = course .pk )
632+ assert proxy_course .product_weeks_to_complete is None
633+
634+ def test_product_weeks_to_complete_with_multiple_runs (self ):
635+ """
636+ If multiple runs exist, ensure the advertised one’s weeks_to_complete is picked.
637+ """
638+ course = self .create_course_with_basic_active_course_run ()
639+ course .authoring_organizations .add (OrganizationFactory ())
640+ advertised_run = course .advertised_course_run
641+ advertised_run .weeks_to_complete = 6
642+ advertised_run .save ()
643+
644+ proxy_course = AlgoliaProxyCourse .objects .get (pk = course .pk )
645+ assert proxy_course .product_weeks_to_complete == 6
646+
647+ def test_product_weeks_to_complete_ignores_invalid_or_none_values (self ):
648+ """
649+ Ensure that product_weeks_to_complete returns None
650+ when the advertised course run has an invalid or None weeks_to_complete value.
651+ """
652+ course = self .create_course_with_basic_active_course_run ()
653+ course .authoring_organizations .add (OrganizationFactory ())
654+
655+ advertised_run = course .advertised_course_run
656+ advertised_run .weeks_to_complete = None
657+ advertised_run .save ()
658+
659+ proxy_course = AlgoliaProxyCourse .objects .get (pk = course .pk )
660+ assert proxy_course .product_weeks_to_complete is None
661+
662+ # Now test with invalid value (e.g., 0 weeks)
663+ advertised_run .weeks_to_complete = 0
664+ advertised_run .save ()
602665
666+ proxy_course = AlgoliaProxyCourse .objects .get (pk = course .pk )
667+ assert proxy_course .product_weeks_to_complete is None
603668@ddt .ddt
604669@pytest .mark .django_db
605670class TestAlgoliaProxyProgram (TestAlgoliaProxyWithEdxPartner ):
0 commit comments