# tests/unit/test_context.py from __future__ import annotations from unittest.mock import AsyncMock, Mock, patch import pytest from dataloader.context import AppContext, get_session @pytest.mark.unit class TestAppContext: """ Unit тесты для AppContext. """ def test_init_creates_empty_context(self): """ Тест создания пустого контекста. """ ctx = AppContext() assert ctx._engine is None assert ctx._sessionmaker is None assert ctx._context_vars_container is not None def test_engine_property_raises_when_not_initialized(self): """ Тест, что engine выбрасывает RuntimeError, если не инициализирован. """ ctx = AppContext() with pytest.raises(RuntimeError, match="Database engine is not initialized"): _ = ctx.engine def test_engine_property_returns_engine_when_initialized(self): """ Тест, что engine возвращает движок после инициализации. """ ctx = AppContext() mock_engine = Mock() ctx._engine = mock_engine assert ctx.engine == mock_engine def test_sessionmaker_property_raises_when_not_initialized(self): """ Тест, что sessionmaker выбрасывает RuntimeError, если не инициализирован. """ ctx = AppContext() with pytest.raises(RuntimeError, match="Sessionmaker is not initialized"): _ = ctx.sessionmaker def test_sessionmaker_property_returns_sessionmaker_when_initialized(self): """ Тест, что sessionmaker возвращает фабрику после инициализации. """ ctx = AppContext() mock_sm = Mock() ctx._sessionmaker = mock_sm assert ctx.sessionmaker == mock_sm @pytest.mark.asyncio async def test_on_startup_initializes_engine_and_sessionmaker(self): """ Тест, что on_startup инициализирует engine и sessionmaker. """ ctx = AppContext() mock_engine = Mock() mock_sm = Mock() with patch("dataloader.logger.logger.setup_logging") as mock_setup_logging, \ patch("dataloader.storage.engine.create_engine", return_value=mock_engine) as mock_create_engine, \ patch("dataloader.storage.engine.create_sessionmaker", return_value=mock_sm) as mock_create_sm, \ patch("dataloader.context.APP_CONFIG") as mock_config: mock_config.pg.url = "postgresql://test" await ctx.on_startup() mock_setup_logging.assert_called_once() mock_create_engine.assert_called_once_with("postgresql://test") mock_create_sm.assert_called_once_with(mock_engine) assert ctx._engine == mock_engine assert ctx._sessionmaker == mock_sm @pytest.mark.asyncio async def test_on_shutdown_disposes_engine(self): """ Тест, что on_shutdown закрывает движок БД. """ ctx = AppContext() mock_engine = AsyncMock() ctx._engine = mock_engine await ctx.on_shutdown() mock_engine.dispose.assert_called_once() @pytest.mark.asyncio async def test_on_shutdown_does_nothing_when_no_engine(self): """ Тест, что on_shutdown безопасно работает без движка. """ ctx = AppContext() await ctx.on_shutdown() assert ctx._engine is None def test_get_logger_returns_logger(self): """ Тест получения логгера. """ ctx = AppContext() with patch("dataloader.logger.logger.get_logger") as mock_get_logger: mock_logger = Mock() mock_get_logger.return_value = mock_logger logger = ctx.get_logger("test_module") mock_get_logger.assert_called_once_with("test_module") assert logger == mock_logger def test_get_logger_without_name(self): """ Тест получения логгера без указания имени. """ ctx = AppContext() with patch("dataloader.logger.logger.get_logger") as mock_get_logger: mock_logger = Mock() mock_get_logger.return_value = mock_logger logger = ctx.get_logger() mock_get_logger.assert_called_once_with(None) assert logger == mock_logger def test_get_context_vars_container_returns_container(self): """ Тест получения контейнера контекстных переменных. """ ctx = AppContext() container = ctx.get_context_vars_container() assert container == ctx._context_vars_container @pytest.mark.unit class TestGetSession: """ Unit тесты для get_session dependency. """ @pytest.mark.asyncio async def test_get_session_yields_session(self): """ Тест, что get_session возвращает сессию. """ mock_session = AsyncMock() mock_context_manager = AsyncMock() mock_context_manager.__aenter__.return_value = mock_session mock_context_manager.__aexit__.return_value = None mock_sm = Mock(return_value=mock_context_manager) with patch("dataloader.context.APP_CTX") as mock_ctx: mock_ctx.sessionmaker = mock_sm async for session in get_session(): assert session == mock_session @pytest.mark.asyncio async def test_get_session_closes_session_after_use(self): """ Тест, что get_session закрывает сессию после использования. """ mock_session = AsyncMock() mock_exit = AsyncMock(return_value=None) mock_context_manager = AsyncMock() mock_context_manager.__aenter__.return_value = mock_session mock_context_manager.__aexit__ = mock_exit mock_sm = Mock(return_value=mock_context_manager) with patch("dataloader.context.APP_CTX") as mock_ctx: mock_ctx.sessionmaker = mock_sm async for session in get_session(): pass assert mock_exit.call_count == 1