@@ -445,28 +445,23 @@ async def test_install_progress_rounding_does_not_cause_misses(
445445 ]
446446 coresys .docker .images .pull .return_value = AsyncIterator (logs )
447447
448- with (
449- patch .object (
450- type (coresys .supervisor ), "arch" , PropertyMock (return_value = "i386" )
451- ),
452- ):
453- # Schedule job so we can listen for the end. Then we can assert against the WS mock
454- event = asyncio .Event ()
455- job , install_task = coresys .jobs .schedule_job (
456- test_docker_interface .install ,
457- JobSchedulerOptions (),
458- AwesomeVersion ("1.2.3" ),
459- "test" ,
460- )
448+ # Schedule job so we can listen for the end. Then we can assert against the WS mock
449+ event = asyncio .Event ()
450+ job , install_task = coresys .jobs .schedule_job (
451+ test_docker_interface .install ,
452+ JobSchedulerOptions (),
453+ AwesomeVersion ("1.2.3" ),
454+ "test" ,
455+ )
461456
462- async def listen_for_job_end (reference : SupervisorJob ):
463- if reference .uuid != job .uuid :
464- return
465- event .set ()
457+ async def listen_for_job_end (reference : SupervisorJob ):
458+ if reference .uuid != job .uuid :
459+ return
460+ event .set ()
466461
467- coresys .bus .register_event (BusEvent .SUPERVISOR_JOB_END , listen_for_job_end )
468- await install_task
469- await event .wait ()
462+ coresys .bus .register_event (BusEvent .SUPERVISOR_JOB_END , listen_for_job_end )
463+ await install_task
464+ await event .wait ()
470465
471466 capture_exception .assert_not_called ()
472467
@@ -664,3 +659,64 @@ async def listen_for_job_end(reference: SupervisorJob):
664659 assert job .done is True
665660 assert job .progress == 100
666661 capture_exception .assert_not_called ()
662+
663+
664+ async def test_missing_total_handled_gracefully (
665+ coresys : CoreSys ,
666+ test_docker_interface : DockerInterface ,
667+ ha_ws_client : AsyncMock ,
668+ capture_exception : Mock ,
669+ ):
670+ """Test missing 'total' fields in progress details handled gracefully."""
671+ coresys .core .set_state (CoreState .RUNNING )
672+
673+ # Progress details with missing 'total' fields observed in real-world pulls
674+ logs = [
675+ {
676+ "status" : "Pulling from home-assistant/odroid-n2-homeassistant" ,
677+ "id" : "2025.7.1" ,
678+ },
679+ {"status" : "Pulling fs layer" , "progressDetail" : {}, "id" : "1e214cd6d7d0" },
680+ {
681+ "status" : "Downloading" ,
682+ "progressDetail" : {"current" : 436480882 },
683+ "progress" : "[===================================================] 436.5MB/436.5MB" ,
684+ "id" : "1e214cd6d7d0" ,
685+ },
686+ {"status" : "Verifying Checksum" , "progressDetail" : {}, "id" : "1e214cd6d7d0" },
687+ {"status" : "Download complete" , "progressDetail" : {}, "id" : "1e214cd6d7d0" },
688+ {
689+ "status" : "Extracting" ,
690+ "progressDetail" : {"current" : 436480882 },
691+ "progress" : "[===================================================] 436.5MB/436.5MB" ,
692+ "id" : "1e214cd6d7d0" ,
693+ },
694+ {"status" : "Pull complete" , "progressDetail" : {}, "id" : "1e214cd6d7d0" },
695+ {
696+ "status" : "Digest: sha256:7d97da645f232f82a768d0a537e452536719d56d484d419836e53dbe3e4ec736"
697+ },
698+ {
699+ "status" : "Status: Downloaded newer image for ghcr.io/home-assistant/odroid-n2-homeassistant:2025.7.1"
700+ },
701+ ]
702+ coresys .docker .images .pull .return_value = AsyncIterator (logs )
703+
704+ # Schedule job so we can listen for the end. Then we can assert against the WS mock
705+ event = asyncio .Event ()
706+ job , install_task = coresys .jobs .schedule_job (
707+ test_docker_interface .install ,
708+ JobSchedulerOptions (),
709+ AwesomeVersion ("1.2.3" ),
710+ "test" ,
711+ )
712+
713+ async def listen_for_job_end (reference : SupervisorJob ):
714+ if reference .uuid != job .uuid :
715+ return
716+ event .set ()
717+
718+ coresys .bus .register_event (BusEvent .SUPERVISOR_JOB_END , listen_for_job_end )
719+ await install_task
720+ await event .wait ()
721+
722+ capture_exception .assert_not_called ()
0 commit comments