102 lines
3.9 KiB
Python
102 lines
3.9 KiB
Python
from __future__ import annotations
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
return FastAPI()
|
|
|
|
|
|
@pytest.fixture
|
|
def lifespan_mocks(monkeypatch):
|
|
"""为 lifespan 测试准备最小 mock 集,使其能进入/退出上下文。"""
|
|
mocks = {
|
|
"channel_bootstrap": AsyncMock(),
|
|
}
|
|
|
|
# 渠道启动器
|
|
channel_class_mock = MagicMock(return_value=mocks["channel_bootstrap"])
|
|
monkeypatch.setattr("server.utils.lifespan.ChannelGatewayBootstrap", channel_class_mock)
|
|
|
|
# 数据库与基础服务
|
|
mock_pg_manager = MagicMock()
|
|
mock_pg_manager.initialize = MagicMock()
|
|
mock_pg_manager.create_tables = AsyncMock()
|
|
mock_pg_manager.ensure_business_schema = AsyncMock()
|
|
mock_pg_manager.ensure_knowledge_schema = AsyncMock()
|
|
mock_pg_manager.close = AsyncMock()
|
|
mock_pg_manager.langgraph_pool = MagicMock()
|
|
mock_pg_manager.get_async_session_context = MagicMock()
|
|
monkeypatch.setattr("server.utils.lifespan.pg_manager", mock_pg_manager)
|
|
|
|
monkeypatch.setattr("server.utils.lifespan.ensure_builtin_mcp_servers_in_db", AsyncMock())
|
|
monkeypatch.setattr("server.utils.lifespan.ensure_builtin_model_providers_in_db", AsyncMock())
|
|
monkeypatch.setattr("server.utils.lifespan.get_redis_client", AsyncMock())
|
|
monkeypatch.setattr("server.utils.lifespan.close_queue_clients", AsyncMock())
|
|
monkeypatch.setattr("server.utils.lifespan.init_sandbox_provider", MagicMock())
|
|
monkeypatch.setattr("server.utils.lifespan.shutdown_sandbox_provider", MagicMock())
|
|
|
|
mock_tasker = MagicMock()
|
|
mock_tasker.start = AsyncMock()
|
|
mock_tasker.shutdown = AsyncMock()
|
|
monkeypatch.setattr("server.utils.lifespan.tasker", mock_tasker)
|
|
|
|
# 知识库、模型缓存、AgentRepository、内置技能
|
|
mock_kb = MagicMock()
|
|
mock_kb.initialize = AsyncMock()
|
|
monkeypatch.setattr("server.utils.lifespan.knowledge_base", mock_kb)
|
|
monkeypatch.setattr("server.utils.lifespan.model_cache", MagicMock())
|
|
|
|
async def _noop(*args, **kwargs):
|
|
pass
|
|
|
|
mock_agent_repo = MagicMock()
|
|
mock_agent_repo.ensure_default_agent = AsyncMock()
|
|
mock_agent_repo.ensure_general_purpose_subagent = AsyncMock()
|
|
mock_agent_repo.ensure_web_search_subagent = AsyncMock()
|
|
mock_agent_repo.ensure_deep_research_agents = AsyncMock()
|
|
monkeypatch.setattr("server.utils.lifespan.AgentRepository", lambda session: mock_agent_repo)
|
|
monkeypatch.setattr("server.utils.lifespan.get_all_model_providers", AsyncMock())
|
|
monkeypatch.setattr("server.utils.lifespan.init_builtin_skills", _noop)
|
|
|
|
# LangGraph checkpointer
|
|
checkpointer_mock = MagicMock()
|
|
checkpointer_mock.setup = AsyncMock()
|
|
monkeypatch.setattr("server.utils.lifespan.AsyncPostgresSaver", lambda pool: checkpointer_mock)
|
|
|
|
monkeypatch.setattr("server.utils.lifespan.get_version", lambda: "0.0.0-test")
|
|
|
|
return mocks
|
|
|
|
|
|
async def test_lifespan_starts_and_stops_channel_gateway(app, lifespan_mocks):
|
|
"""验证 lifespan 启动时会启动多渠道网关,关闭时会停止。"""
|
|
from server.utils.lifespan import lifespan
|
|
|
|
async with lifespan(app):
|
|
pass
|
|
|
|
bootstrap_mock = lifespan_mocks["channel_bootstrap"]
|
|
bootstrap_mock.start.assert_awaited_once_with(app)
|
|
bootstrap_mock.stop.assert_awaited_once()
|
|
|
|
|
|
async def test_lifespan_logs_but_continues_when_channel_start_fails(app, lifespan_mocks, caplog):
|
|
"""验证渠道网关启动失败时应用仍可启动,并在关闭时安全停止。"""
|
|
from server.utils.lifespan import lifespan
|
|
|
|
lifespan_mocks["channel_bootstrap"].start.side_effect = RuntimeError("channel boom")
|
|
|
|
with caplog.at_level("ERROR"):
|
|
async with lifespan(app):
|
|
pass
|
|
|
|
bootstrap_mock = lifespan_mocks["channel_bootstrap"]
|
|
bootstrap_mock.start.assert_awaited_once_with(app)
|
|
bootstrap_mock.stop.assert_awaited_once()
|
|
assert "Failed to start channel gateway during startup" in caplog.text
|