You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
buffer_platform/tests/test_main_lifespan.py

148 lines
5.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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_triggers_migration_when_mysql_ok(patch_lifespan_deps):
""" MySQL 可用时lifespan 应触发 SQLite 到 MySQL 的迁移。 """
from app.main import lifespan, app
patch_lifespan_deps["init_redis"].return_value = None
patch_lifespan_deps["init_mysql"].return_value = MagicMock()
with patch("app.migration.migrate_sqlite_to_mysql") as mock_migrate:
async with lifespan(app):
pass
mock_migrate.assert_called_once()
@pytest.mark.asyncio
async def test_lifespan_skips_migration_when_mysql_unavailable(patch_lifespan_deps):
""" MySQL 不可用时lifespan 不应调用迁移函数。 """
from app.main import lifespan, app
patch_lifespan_deps["init_redis"].return_value = None
patch_lifespan_deps["init_mysql"].return_value = None
with patch("app.migration.migrate_sqlite_to_mysql") as mock_migrate:
async with lifespan(app):
pass
mock_migrate.assert_not_called()
@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