"""Unit tests for AiImageAnalyzer."""
import json
from typing import Any
from unittest.mock import AsyncMock
import pytest
from vmk_data_collector.core.exceptions import (
OllamaFatalError,
OllamaRetryableError,
)
from vmk_data_collector.schemas.ai_response import AiImageAnalysisResponse
from vmk_data_collector.services.ai_image_analyzer import AiImageAnalyzer
@pytest.fixture
def analyzer(mock_ollama_client: AsyncMock) -> AiImageAnalyzer:
return AiImageAnalyzer(client=mock_ollama_client)
class TestMockMode:
@pytest.mark.asyncio
async def test_returns_mock_when_enabled(
self,
analyzer: AiImageAnalyzer,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(
"vmk_data_collector.services.ai_image_analyzer.settings.ollama_mock",
True,
)
result = await analyzer.analyze("any_base64")
assert isinstance(result, AiImageAnalysisResponse)
assert result.overall_condition == "хорошее"
assert result.rooms_observed == 2
class TestHappyPath:
@pytest.mark.asyncio
async def test_parses_json_response(
self,
analyzer: AiImageAnalyzer,
mock_ollama_client: AsyncMock,
) -> None:
mock_ollama_client.chat_with_images.return_value = {
"message": {
"content": json.dumps(
{
"overall_condition": "отличное",
"rooms_observed": 3,
"issues_found": ["трещина"],
"positive_highlights": ["ремонт"],
"view_from_window": "парк",
"furniture_included": True,
"appliances_included": ["холодильник"],
}
)
}
}
result = await analyzer.analyze("base64data")
assert isinstance(result, AiImageAnalysisResponse)
assert result.overall_condition == "отличное"
assert result.rooms_observed == 3
assert result.issues_found == ["трещина"]
class TestErrorHandling:
@pytest.mark.asyncio
async def test_retryable_error_propagates(
self,
analyzer: AiImageAnalyzer,
mock_ollama_client: AsyncMock,
) -> None:
mock_ollama_client.chat_with_images.side_effect = OllamaRetryableError(
"transient"
)
with pytest.raises(OllamaRetryableError):
await analyzer.analyze("base64")
@pytest.mark.asyncio
async def test_fatal_error_propagates(
self,
analyzer: AiImageAnalyzer,
mock_ollama_client: AsyncMock,
) -> None:
mock_ollama_client.chat_with_images.side_effect = OllamaFatalError("fatal")
with pytest.raises(OllamaFatalError):
await analyzer.analyze("base64")
@pytest.mark.asyncio
async def test_unexpected_error_returns_empty_response(
self,
analyzer: AiImageAnalyzer,
mock_ollama_client: AsyncMock,
) -> None:
mock_ollama_client.chat_with_images.side_effect = RuntimeError("unexpected")
result = await analyzer.analyze("base64")
assert isinstance(result, AiImageAnalysisResponse)
assert result.overall_condition is None