import pytest from unittest.mock import MagicMock, patch, ANY from pathlib import Path import sys # 1. Mock the dependencies BEFORE import mock_llama = MagicMock() mock_chat_handler = MagicMock() sys.modules["llama_cpp"] = mock_llama sys.modules["llama_cpp.llama_chat_format"] = MagicMock() sys.modules["llama_cpp.llama_chat_format"].Llava15ChatHandler = mock_chat_handler from src.perception.engine import Qwen2PerceptionEngine from src.config.settings import settings class TestPerceptionEngine: @patch("src.perception.engine.settings") def test_load_model_calls_llama_correctly(self, mock_settings): """Test that load_model initializes the Llama class with GPU settings.""" # Setup engine = Qwen2PerceptionEngine() fake_path = Path("/tmp/fake_model.gguf") fake_projector = Path("/tmp/mmproj.gguf") # Mock the glob search for the projector mock_settings.paths.models_dir.glob.return_value = [fake_projector] # Act engine.load_model(fake_path) # Assert # 1. Check if it looked for the projector mock_settings.paths.models_dir.glob.assert_called() # 2. Check if ChatHandler was initialized mock_chat_handler.assert_called_with(clip_model_path=str(fake_projector)) # 3. Check if Llama was instantiated with GPU layers mock_llama.Llama.assert_called_with( model_path=str(fake_path), chat_handler=ANY, # The instance of chat handler n_ctx=2048, n_gpu_layers=-1, # Important: Must be -1 for full GPU n_batch=512, verbose=False ) def test_analyze_frame_structure(self): """Test that analyze_frame constructs the correct message format for Qwen.""" engine = Qwen2PerceptionEngine() # Mock the internal Llama model engine._model = MagicMock() engine._model.create_chat_completion.return_value = { "choices": [{"message": {"content": "A dog on a bike"}}] } # Mock image loader to avoid file IO with patch("src.perception.engine.open", create=True) as mock_open: mock_open.return_value.__enter__.return_value.read.return_value = b"fake_image_bytes" result = engine.analyze_frame("test.jpg", "Describe this") # Assert assert result == "A dog on a bike" # Verify the prompt structure calls = engine._model.create_chat_completion.call_args messages = calls.kwargs['messages'] assert messages[0]['role'] == 'system' assert messages[1]['role'] == 'user' # Check content list (Image + Text) content = messages[1]['content'] assert content[0]['type'] == 'image_url' assert content[1]['type'] == 'text' assert content[1]['text'] == 'Describe this'