@@ -135,7 +135,9 @@ def setup_method(self):
135135
136136 def test_creates_new_instance_when_none (self ):
137137 mock_handler = MagicMock ()
138- with patch ("th_cli.test_run.prompt_manager.CameraStreamHandler" , return_value = mock_handler ):
138+ # CameraStreamHandler is imported lazily inside _get_video_handler
139+ # via `from .camera import CameraStreamHandler`, so patch the source
140+ with patch ("th_cli.test_run.camera.CameraStreamHandler" , return_value = mock_handler ):
139141 result = _get_video_handler ()
140142 assert result is mock_handler
141143
@@ -528,3 +530,124 @@ async def test_sends_ack_response(self):
528530 mock_send .assert_called_once ()
529531 call_kwargs = mock_send .call_args [1 ]
530532 assert call_kwargs .get ("response" ) == "ACK"
533+
534+
535+ # ---------------------------------------------------------------------------
536+ # __handle_stream_verification_prompt (lines 133-201)
537+ # Accessed via handle_prompt routing with STREAM_VERIFICATION_REQUEST type.
538+ # ---------------------------------------------------------------------------
539+
540+
541+ @pytest .mark .unit
542+ class TestHandleStreamVerificationPrompt :
543+ """Cover __handle_stream_verification_prompt body via handle_prompt routing."""
544+
545+ def _mock_video_handler (self , stream_ready = True , user_answer = 1 , init_error = None ):
546+ vh = MagicMock ()
547+ vh .http_server = MagicMock ()
548+ vh .http_server .port = 8999
549+ vh .set_prompt_data = MagicMock ()
550+ vh .start_video_capture_and_stream = AsyncMock (return_value = MagicMock ())
551+ vh .wait_for_stream_ready = AsyncMock (return_value = stream_ready )
552+ vh .initialization_error = init_error
553+ vh .wait_for_user_response = AsyncMock (return_value = user_answer )
554+ vh .stop_video_capture_and_stream = AsyncMock (return_value = None )
555+ return vh
556+
557+ def setup_method (self ):
558+ pm_module ._video_handler_instance = None
559+
560+ def teardown_method (self ):
561+ pm_module ._video_handler_instance = None
562+
563+ @pytest .mark .asyncio
564+ async def test_sends_response_when_stream_ready_and_user_answers (self ):
565+ prompt = _make_stream_prompt ()
566+ mock_socket = AsyncMock ()
567+ vh = self ._mock_video_handler (stream_ready = True , user_answer = 1 )
568+ pm_module ._video_handler_instance = vh
569+
570+ with patch ("th_cli.test_run.prompt_manager._send_prompt_response" , new_callable = AsyncMock ) as mock_send :
571+ with patch ("th_cli.test_run.prompt_manager._get_local_ip" , return_value = "10.0.0.1" ):
572+ with patch ("th_cli.test_run.prompt_manager.click.echo" ):
573+ await handle_prompt (
574+ socket = mock_socket ,
575+ request = prompt ,
576+ message_type = MessageTypeEnum .STREAM_VERIFICATION_REQUEST ,
577+ )
578+
579+ mock_send .assert_called_once ()
580+
581+ @pytest .mark .asyncio
582+ async def test_sends_cancelled_when_stream_not_ready (self ):
583+ prompt = _make_stream_prompt ()
584+ mock_socket = AsyncMock ()
585+ vh = self ._mock_video_handler (stream_ready = False , init_error = "FFmpeg not found" )
586+ pm_module ._video_handler_instance = vh
587+
588+ with patch ("th_cli.test_run.prompt_manager._send_prompt_response" , new_callable = AsyncMock ) as mock_send :
589+ with patch ("th_cli.test_run.prompt_manager._get_local_ip" , return_value = "10.0.0.1" ):
590+ with patch ("th_cli.test_run.prompt_manager.click.echo" ):
591+ await handle_prompt (
592+ socket = mock_socket ,
593+ request = prompt ,
594+ message_type = MessageTypeEnum .STREAM_VERIFICATION_REQUEST ,
595+ )
596+
597+ mock_send .assert_called_once ()
598+ call_kwargs = mock_send .call_args [1 ]
599+ from th_cli .test_run .socket_schemas import UserResponseStatusEnum
600+ assert call_kwargs .get ("status_code" ) == UserResponseStatusEnum .CANCELLED
601+
602+ @pytest .mark .asyncio
603+ async def test_sends_cancelled_when_stream_not_ready_no_init_error (self ):
604+ prompt = _make_stream_prompt ()
605+ mock_socket = AsyncMock ()
606+ vh = self ._mock_video_handler (stream_ready = False , init_error = None )
607+ pm_module ._video_handler_instance = vh
608+
609+ with patch ("th_cli.test_run.prompt_manager._send_prompt_response" , new_callable = AsyncMock ) as mock_send :
610+ with patch ("th_cli.test_run.prompt_manager._get_local_ip" , return_value = "10.0.0.1" ):
611+ with patch ("th_cli.test_run.prompt_manager.click.echo" ):
612+ await handle_prompt (
613+ socket = mock_socket ,
614+ request = prompt ,
615+ message_type = MessageTypeEnum .STREAM_VERIFICATION_REQUEST ,
616+ )
617+
618+ mock_send .assert_called_once ()
619+
620+ @pytest .mark .asyncio
621+ async def test_no_send_when_user_answer_is_none (self ):
622+ prompt = _make_stream_prompt ()
623+ mock_socket = AsyncMock ()
624+ vh = self ._mock_video_handler (stream_ready = True , user_answer = None )
625+ pm_module ._video_handler_instance = vh
626+
627+ with patch ("th_cli.test_run.prompt_manager._send_prompt_response" , new_callable = AsyncMock ) as mock_send :
628+ with patch ("th_cli.test_run.prompt_manager._get_local_ip" , return_value = "10.0.0.1" ):
629+ with patch ("th_cli.test_run.prompt_manager.click.echo" ):
630+ await handle_prompt (
631+ socket = mock_socket ,
632+ request = prompt ,
633+ message_type = MessageTypeEnum .STREAM_VERIFICATION_REQUEST ,
634+ )
635+
636+ mock_send .assert_not_called ()
637+
638+ @pytest .mark .asyncio
639+ async def test_missing_options_returns_early (self ):
640+ """Covers line 136 — options missing/empty."""
641+ prompt_no_opts = MagicMock (spec = StreamVerificationPromptRequest )
642+ prompt_no_opts .options = {}
643+ mock_socket = AsyncMock ()
644+
645+ with patch ("th_cli.test_run.prompt_manager._send_prompt_response" , new_callable = AsyncMock ) as mock_send :
646+ with patch ("th_cli.test_run.prompt_manager.click.echo" ):
647+ await handle_prompt (
648+ socket = mock_socket ,
649+ request = prompt_no_opts ,
650+ message_type = MessageTypeEnum .STREAM_VERIFICATION_REQUEST ,
651+ )
652+
653+ mock_send .assert_not_called ()
0 commit comments