Files
hunjing/backend/tests/conftest.py
T
chenxin 99985f5855 ✨ feat: WP-11 社区——发布、互动、关注与治理
- 迁移 12:community_content(帖子/角色 Release、publication 六态、可见性)、comment(父级树)、reaction((content,actor,kind) 唯一防重复)、follow(双 FK 显式命名修复 convention 冲突)、report + moderation_case(举报即建司议案)
- 发布:帖子直发;角色 Release 由服务端构造公开快照(name/道体/版本数)——私密记忆/关系/对话结构性缺席,测试以注入私密记忆后发布验证零泄露
- 互动:评论(父级校验同内容可见)、点赞/收藏(重复 409、取消后计数正确)
- 关注/取关与计数;举报自动生成司议案(治理队列入口,status=pending)
- 下架传播:removed 后列表/详情/评论统一 404 或过滤(require_content 单点);非作者下架 404 fail-closed
- 测试基建修复:login 返回自包含 headers(显式 Cookie 优先),多用户交替不再串会话
- 测试 79 项全绿(+7)
2026-09-12 09:02:36 +08:00

114 lines
3.9 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.
"""pytest 夹具:统一连接 PostgreSQL 测试库(ADR-024,不用 SQLite 替代)。"""
import os
import pytest
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app import models # noqa: F401 注册模型
from app.db.base import Base
TEST_DATABASE_URL = os.environ.get(
"TEST_DATABASE_URL",
"postgresql+asyncpg://hunjing:hunjing_dev_password@127.0.0.1:5432/hunjing_test",
)
@pytest.fixture
async def db() -> async_sessionmaker[AsyncSession]:
"""每个测试重建 schema:隔离且可重复。"""
engine = create_async_engine(TEST_DATABASE_URL)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
factory = async_sessionmaker(engine, expire_on_commit=False)
yield factory
await engine.dispose()
@pytest.fixture
async def client(db):
"""连接测试库的应用客户端(覆盖 get_db)。"""
from httpx import ASGITransport, AsyncClient
from app.db import session as db_session
from app.main import create_app
app = create_app()
async def override():
async with db() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
app.dependency_overrides[db_session.get_db] = override
# 后台 Run 任务使用测试库会话工厂与 mock 流(生产为独立工厂与真实传输)
app.state.session_factory = db
app.state.gw_transport = _mock_openai_stream()
import os as _os
_os.environ.setdefault("HJ_MOCK_KEY", "test-key")
from app.models import LogicalModel, ProviderEndpoint
async with db() as session:
session.add(LogicalModel(id="default-chat", capabilities=["chat"]))
session.add(
ProviderEndpoint(
logical_model_id="default-chat",
provider="openai_compatible",
name="mock-endpoint",
base_url="http://mock.internal",
model_id="mock-chat",
api_key_env="HJ_MOCK_KEY",
priority=1,
)
)
await session.commit()
transport = ASGITransport(app=app, raise_app_exceptions=False)
async with AsyncClient(transport=transport, base_url="https://test") as c:
yield c
async def login(client, email: str) -> dict[str, str]:
"""开发档登录:返回自包含请求头(显式 Cookie 支持多用户交替)。
同时保留 cookie jar 自动存储:不带 headers 的旧式请求仍走 jar。
显式 Cookie header 优先于 jar(httpx 行为),交替测试不串会话。
"""
resp = await client.post("/v1/auth/sessions", json={"email": email})
assert resp.status_code == 201, resp.text
from app.auth.service import SESSION_COOKIE
token = resp.cookies.get(SESSION_COOKIE)
assert token, "登录未签发会话 Cookie"
return {
"Hunjing-CSRF-Token": resp.json()["csrf_token"],
"Cookie": f"{SESSION_COOKIE}={token}",
}
def _mock_openai_stream():
"""OpenAI-compatible 流式 mock:真实 SSE 字节流(data: {...} 分块 + usage)。"""
import httpx
sse = (
b'data: {"choices":[{"delta":{"content":"\\u4f60\\u597d"}}]}\n\n'
b'data: {"choices":[{"delta":{"content":","}}]}\n\n'
b'data: {"choices":[{"delta":{"content":"\\u9b42\\u9559"}}]}\n\n'
b'data: {"choices":[{"delta":{},"finish_reason":"stop"}]}\n\n'
b'data: {"choices":[],"usage":{"prompt_tokens":12,'
b'"completion_tokens":6,"total_tokens":18}}\n\n'
b'data: [DONE]\n\n'
)
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(
200, headers={"content-type": "text/event-stream"}, content=sse
)
return httpx.MockTransport(handler)