parent
b7ae8973a8
commit
a4c3af6c78
@ -0,0 +1,117 @@
|
|||||||
|
import logging
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def patch_lifespan_deps():
|
||||||
|
"""Patch all dependencies around lifespan so only storage init is exercised."""
|
||||||
|
mock_db = MagicMock()
|
||||||
|
mock_session_local = MagicMock(return_value=mock_db)
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
patch("app.main.Base.metadata.create_all"),
|
||||||
|
patch("app.main.UserBase.metadata.create_all"),
|
||||||
|
patch("app.analysis_db.init_analysis_db"),
|
||||||
|
patch("app.database.SessionLocal", mock_session_local),
|
||||||
|
patch("app.auth_service.create_default_admin"),
|
||||||
|
patch("app.main.start_scheduler"),
|
||||||
|
patch("app.services.cache.list_tasks", return_value=[]),
|
||||||
|
patch("app.services.scheduler.add_job"),
|
||||||
|
patch("app.main.stop_scheduler"),
|
||||||
|
patch("app.redis_client.init_redis"),
|
||||||
|
patch("app.mysql_database.init_mysql"),
|
||||||
|
]
|
||||||
|
started = [p.start() for p in patches]
|
||||||
|
yield {
|
||||||
|
"Base_create_all": started[0],
|
||||||
|
"UserBase_create_all": started[1],
|
||||||
|
"init_analysis_db": started[2],
|
||||||
|
"SessionLocal": started[3],
|
||||||
|
"create_default_admin": started[4],
|
||||||
|
"start_scheduler": started[5],
|
||||||
|
"list_tasks": started[6],
|
||||||
|
"add_job": started[7],
|
||||||
|
"stop_scheduler": started[8],
|
||||||
|
"init_redis": started[9],
|
||||||
|
"init_mysql": started[10],
|
||||||
|
}
|
||||||
|
for p in patches:
|
||||||
|
p.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_lifespan_calls_redis_and_mysql_init(patch_lifespan_deps, caplog):
|
||||||
|
""" lifespan 应调用 Redis 与 MySQL 初始化函数。 """
|
||||||
|
from app.main import lifespan, app
|
||||||
|
|
||||||
|
patch_lifespan_deps["init_redis"].return_value = MagicMock()
|
||||||
|
patch_lifespan_deps["init_mysql"].return_value = MagicMock()
|
||||||
|
|
||||||
|
with caplog.at_level(logging.INFO, logger="app.main"):
|
||||||
|
async with lifespan(app):
|
||||||
|
pass
|
||||||
|
|
||||||
|
patch_lifespan_deps["init_redis"].assert_called_once()
|
||||||
|
patch_lifespan_deps["init_mysql"].assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_lifespan_storage_mode_redis_and_mysql(patch_lifespan_deps, caplog):
|
||||||
|
""" Redis 与 MySQL 均可用时,日志显示 Redis + MySQL 模式。 """
|
||||||
|
from app.main import lifespan, app
|
||||||
|
|
||||||
|
patch_lifespan_deps["init_redis"].return_value = MagicMock()
|
||||||
|
patch_lifespan_deps["init_mysql"].return_value = MagicMock()
|
||||||
|
|
||||||
|
with caplog.at_level(logging.INFO, logger="app.main"):
|
||||||
|
async with lifespan(app):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert "存储模式: Redis + MySQL" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_lifespan_storage_mode_mysql_only(patch_lifespan_deps, caplog):
|
||||||
|
""" 仅 MySQL 可用时,日志显示 MySQL 降级模式。 """
|
||||||
|
from app.main import lifespan, app
|
||||||
|
|
||||||
|
patch_lifespan_deps["init_redis"].return_value = None
|
||||||
|
patch_lifespan_deps["init_mysql"].return_value = MagicMock()
|
||||||
|
|
||||||
|
with caplog.at_level(logging.WARNING, logger="app.main"):
|
||||||
|
async with lifespan(app):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert "存储模式: MySQL (Redis 不可用)" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_lifespan_storage_mode_sqlite_fallback(patch_lifespan_deps, caplog):
|
||||||
|
""" Redis 与 MySQL 均不可用时,日志显示 SQLite 降级模式。 """
|
||||||
|
from app.main import lifespan, app
|
||||||
|
|
||||||
|
patch_lifespan_deps["init_redis"].return_value = None
|
||||||
|
patch_lifespan_deps["init_mysql"].return_value = None
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR, logger="app.main"):
|
||||||
|
async with lifespan(app):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert "存储模式: SQLite (Redis 和 MySQL 均不可用)" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_lifespan_storage_init_failure_does_not_raise(patch_lifespan_deps, caplog):
|
||||||
|
""" 初始化 Redis/MySQL 失败抛出异常时,应用仍可正常启动。 """
|
||||||
|
from app.main import lifespan, app
|
||||||
|
|
||||||
|
patch_lifespan_deps["init_redis"].side_effect = Exception("redis down")
|
||||||
|
patch_lifespan_deps["init_mysql"].side_effect = Exception("mysql down")
|
||||||
|
|
||||||
|
with caplog.at_level(logging.ERROR, logger="app.main"):
|
||||||
|
async with lifespan(app):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert "存储模式: SQLite (Redis 和 MySQL 均不可用)" in caplog.text
|
||||||
Loading…
Reference in new issue