import pytest import numpy as np from unittest.mock import MagicMock, patch, AsyncMock from services.video_processor import VideoProcessor, DemoScenarioRunner @pytest.fixture def mock_frame(): return np.zeros((720, 1280, 3), dtype=np.uint8) @pytest.mark.asyncio async def test_video_processor_full_loop(mock_frame): processor = VideoProcessor(source=0) # Use AsyncMock for the agents since they are awaited processor.vision_agent._safe_process = AsyncMock(return_value={ "incident": True, "type": "theft", "severity": "high", "confidence": 95 }) processor.planner_agent._safe_process = AsyncMock(return_value=[{"action": "test"}]) # We use patch.multiple or patch specifically for the awaited executor with patch("services.video_processor.cv2") as mock_cv2, \ patch("services.video_processor.db_service") as mock_db, \ patch("services.video_processor.action_executor", new_callable=AsyncMock) as mock_executor: mock_cap = MagicMock() mock_cap.isOpened.return_value = True mock_cap.read.return_value = (True, mock_frame) mock_cv2.VideoCapture.return_value = mock_cap # Stop the loop after one iteration async def stop_after_one(*args, **kwargs): processor.running = False return {"incident": True} processor.vision_agent._safe_process.side_effect = stop_after_one await processor.process_stream(display=True) assert mock_cv2.imshow.called assert mock_db.save_incident.called # Now this will pass because mock_executor is an AsyncMock assert mock_executor.execute_plan.called @pytest.mark.asyncio async def test_demo_scenario_runner(): runner = DemoScenarioRunner() runner.vision_agent._safe_process = AsyncMock(return_value={"incident": False}) # Properly close the agents in the runner to clear warnings with patch("builtins.print"), \ patch.object(runner.vision_agent, 'close', new_callable=AsyncMock), \ patch.object(runner.planner_agent, 'close', new_callable=AsyncMock): await runner._run_scenario("Test", "Test Desc", (255, 0, 0))