dataloader/tests/unit/test_context.py

207 lines
6.4 KiB
Python

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