"""Pytest fixtures and helpers."""
from collections.abc import AsyncGenerator
from typing import Any
from unittest.mock import AsyncMock, MagicMock
import httpx
import pytest
from fastapi import FastAPI
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from sqlalchemy.ext.asyncio import AsyncSession
from vmk_data_collector.api.deps import get_db
from vmk_data_collector.api.v1.router_health import router as health_router
from vmk_data_collector.api.v1.router_properties import router as properties_router
from vmk_data_collector.core.exceptions import (
AIProcessingError,
AppError,
NotRealEstateError,
ValidationError,
)
from vmk_data_collector.core.limiter import limiter
from vmk_data_collector.main import (
ai_processing_error_handler,
app_error_handler,
not_real_estate_handler,
validation_error_handler,
)
@pytest.fixture
def mock_async_session() -> AsyncMock:
"""Mock SQLAlchemy AsyncSession with common methods."""
session = AsyncMock(spec=AsyncSession)
session.__aenter__ = AsyncMock(return_value=session)
session.__aexit__ = AsyncMock(return_value=None)
result_mock = MagicMock()
session.execute.return_value = result_mock
return session
@pytest.fixture
def mock_session_factory(mock_async_session: AsyncMock) -> MagicMock:
"""Mock async_sessionmaker that yields mock_async_session."""
factory = MagicMock()
factory.return_value = mock_async_session
return factory
@pytest.fixture
def mock_ollama_client() -> AsyncMock:
"""Mock OllamaClient with a default successful chat response."""
client = AsyncMock()
client.chat.return_value = {
"message": {
"content": (
'{"is_real_estate": true, "reason": null, '
'"normalized": {"property_type": "apartment", '
'"deal_type": "sale", "title": "Test", '
'"description": "Desc", "price": 100000, '
'"currency": "UAH", "total_area": 50, '
'"rooms_count": 2, "floor": 3, '
'"floors_total": 9, "city": "Kyiv", '
'"address_raw": "Kyiv", "images": [], '
'"custom_fields": {}}}'
)
}
}
return client
@pytest.fixture
def fastapi_app(mock_async_session: AsyncMock) -> FastAPI:
"""FastAPI app for integration tests (no lifespan, no worker)."""
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(
RateLimitExceeded, _rate_limit_exceeded_handler
)
app.add_exception_handler(AppError, app_error_handler)
app.add_exception_handler(ValidationError, validation_error_handler)
app.add_exception_handler(NotRealEstateError, not_real_estate_handler)
app.add_exception_handler(AIProcessingError, ai_processing_error_handler)
app.include_router(health_router, prefix="/api/v1")
app.include_router(properties_router, prefix="/api/v1")
app.dependency_overrides[get_db] = lambda: mock_async_session
return app
@pytest.fixture
async def async_client(
fastapi_app: FastAPI,
) -> AsyncGenerator[httpx.AsyncClient, None]:
"""Async HTTP client wired to the test FastAPI app."""
transport = httpx.ASGITransport(app=fastapi_app)
async with httpx.AsyncClient(
transport=transport, base_url="http://test"
) as client:
yield client