Newer
Older
vmk-360-data_collector / tests / unit / test_ai_image_analyzer.py
@Eugene Sukhodolskiy Eugene Sukhodolskiy 1 day ago 3 KB fix: code review critical and high issues
"""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