201 lines
6.3 KiB
Python
201 lines
6.3 KiB
Python
# 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
|