Files
AIPortPilot/backend/tests/conftest.py
T
selfrelease fad458b2a7 docs(uiux): UIUX 设计方案大改 + 5 份作业指导书对齐 + 开发任务文档
- UIUX 文档:填充 19 个缺口(多主体画像/健康度/AI+看板/增长域/洞察域/创始人端/OODA/助推/商密)
- UIUX 文档:插入 6 个新章节(十四~十九),旧章节重编号为二十~三十一,更新目录和交叉引用
- 作业指导书 x5:导航改为 6 域分组,新增 Context Bar/工作模式/Insight Rail/决策线程/多工作区等 UI 概念
- 新建 docs/2-task-uiux.md:50 个代码落地开发任务,按 P0-P6 分优先级 + 8 Sprint 规划
- 后端/前端:大量新增模型、路由、组件(来自之前 Phase 开发)
2026-07-19 11:53:38 +08:00

150 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""共享测试夹具 — 所有测试文件共用同一文件级 SQLite 数据库。
使用同步引擎在模块级创建表,避免跨事件循环导致表缺失。
"""
import json
import os
from unittest.mock import AsyncMock, patch
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from app.core.database import Base, get_db
from app.main import app
TEST_DB_PATH = "./test.db"
TEST_DATABASE_URL = f"sqlite+aiosqlite:///{TEST_DB_PATH}"
SYNC_DATABASE_URL = f"sqlite:///{TEST_DB_PATH}"
# --- 同步引擎:模块级创建表 ---
_sync_engine = create_engine(SYNC_DATABASE_URL, echo=False)
def _create_tables() -> None:
"""用同步引擎创建全部表。"""
Base.metadata.create_all(_sync_engine)
def _drop_tables() -> None:
"""用同步引擎销毁全部表。"""
Base.metadata.drop_all(_sync_engine)
# 模块导入时立即创建表
if os.path.exists(TEST_DB_PATH):
os.remove(TEST_DB_PATH)
_create_tables()
# --- 异步引擎:测试运行时使用 ---
# StaticPool 保持单连接复用,connect_args 允许跨线程访问
test_engine = create_async_engine(
TEST_DATABASE_URL,
echo=False,
poolclass=StaticPool,
connect_args={"check_same_thread": False},
)
test_session_factory = async_sessionmaker(test_engine, class_=AsyncSession, expire_on_commit=False)
async def override_get_db():
"""覆盖数据库依赖,使用测试 SQLite。"""
async with test_session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
app.dependency_overrides[get_db] = override_get_db
@pytest.fixture(scope="session", autouse=True)
async def setup_db():
"""确保表已创建,测试结束后清理。"""
# 表已在模块级同步创建,此处仅做清理
yield
await test_engine.dispose()
_drop_tables()
_sync_engine.dispose()
if os.path.exists(TEST_DB_PATH):
os.remove(TEST_DB_PATH)
@pytest.fixture(autouse=True)
async def mock_llm():
"""Mock LLM 客户端 — 所有 AI 相关测试不依赖真实 API。
chat() 尝试解析 JSON 返回 Python 对象(dict/list),
使 service 层 isinstance(result, dict/list) 检查通过。
无法解析时返回原始字符串。
"""
mock_dict = {"summary": "AI 分析结果(测试 mock", "score": 75, "confidence": 0.8}
mock_list = [{"type": "tech", "title": "测试协同", "match_reason": "mock"}]
async def mock_chat_stream(self, messages, temperature=0.3, max_tokens=2000):
yield json.dumps(mock_dict)
async def mock_chat(self, messages, temperature=0.3, max_tokens=2000):
# service 层传的可能是 str 或 list[dict]
# 根据 prompt 内容判断期望返回类型
if isinstance(messages, str):
prompt_text = messages
elif isinstance(messages, list):
prompt_text = " ".join(str(m.get("content", "")) for m in messages)
else:
prompt_text = str(messages)
# 如果 prompt 中包含 "JSON 数组" 或 "数组格式",返回 list
if "数组" in prompt_text or "JSON 数组" in prompt_text:
return mock_list
# 默认返回 dict
return mock_dict
with patch("app.services.llm_client.LLMClient.chat", mock_chat), \
patch("app.services.llm_client.LLMClient.chat_stream", mock_chat_stream):
yield
@pytest.fixture
def client():
"""创建测试客户端。"""
return TestClient(app)
@pytest.fixture
def auth_headers(client: TestClient):
"""注册并登录投资人用户,返回认证头。"""
client.post(
"/api/v1/auth/register",
json={
"email": "smoke_test@example.com",
"password": "password123",
"name": "冒烟测试用户",
"tenant_name": "冒烟测试机构",
"role": "investor",
},
)
resp = client.post(
"/api/v1/auth/login",
json={"email": "smoke_test@example.com", "password": "password123"},
)
token = resp.json()["data"]["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
def company_id(client: TestClient, auth_headers: dict):
"""创建测试企业,返回企业 ID。"""
resp = client.post(
"/api/v1/companies",
json={"name": "冒烟测试公司", "industry": "AI"},
headers=auth_headers,
)
return resp.json()["data"]["id"]