test: 平台测试套件适配异步四层,66 全绿

- conftest 改为 monkeypatch DATABASE_URL + lifespan 建库(解决异步引擎跨事件循环)
- 修复 seed_data 辅助函数 await、_to_dict 链式 await、get_current_user await
- 修复 Database.initialize 建表于本实例引擎
- 平台 19 路由全部经异步接口回归通过
This commit is contained in:
2026-08-24 00:34:28 +08:00
parent 4ac8b81755
commit a869b2934a
4 changed files with 52 additions and 47 deletions
+1 -1
View File
@@ -215,7 +215,7 @@ async def verify(request: Request, db: Database = Depends(get_db)):
"""校验调用方 Bearer 令牌;无效令牌由依赖层直接返回 401。"""
if not config.AUTH_ENABLED:
return VerifyResponse(valid=True, username="")
user = get_current_user(request, db)
user = await get_current_user(request, db)
return VerifyResponse(valid=True, username=user["username"])
+15 -15
View File
@@ -1111,7 +1111,7 @@ class IdentityRepository:
async def get(self, identity_id: str) -> dict | None:
row = await self.session.get(UserIdentity, identity_id)
return self._to_dict(row) if row else None
return await self._to_dict(row) if row else None
async def get_for_user(self, identity_id: str, user_id: str) -> dict | None:
row = await self.session.scalar(
@@ -1120,14 +1120,14 @@ class IdentityRepository:
UserIdentity.user_id == user_id,
)
)
return self._to_dict(row) if row else None
return await self._to_dict(row) if row else None
async def list_for_user(self, user_id: str, active_only: bool = True) -> list[dict]:
stmt = select(UserIdentity).where(UserIdentity.user_id == user_id)
if active_only:
stmt = stmt.where(UserIdentity.status == "active")
rows = (await self.session.scalars(stmt.order_by(UserIdentity.port))).all()
return [self._to_dict(i) for i in rows]
return [await self._to_dict(i) for i in rows]
async def create(
self,
@@ -1156,7 +1156,7 @@ class IdentityRepository:
)
self.session.add(row)
await self.session.commit()
return self._to_dict(row)
return await self._to_dict(row)
async def set_status(self, identity_id: str, status: str) -> dict | None:
row = await self.session.get(UserIdentity, identity_id)
@@ -1165,7 +1165,7 @@ class IdentityRepository:
row.status = status
row.updated_at = utcnow_iso()
await self.session.commit()
return self._to_dict(row)
return await self._to_dict(row)
# ---------------------------------------------------------------------------
@@ -1293,11 +1293,11 @@ class RoadshowRepository:
stmt = select(Roadshow).order_by(Roadshow.created_at.desc())
if status:
stmt = stmt.where(Roadshow.status == status)
return [self._to_dict(r) for r in await self.session.scalars(stmt)]
return [await self._to_dict(r) for r in await self.session.scalars(stmt)]
async def get(self, roadshow_id: str) -> dict | None:
row = await self.session.get(Roadshow, roadshow_id)
return self._to_dict(row) if row else None
return await self._to_dict(row) if row else None
async def create(self, fields: dict) -> dict:
now = utcnow_iso()
@@ -1318,7 +1318,7 @@ class RoadshowRepository:
)
self.session.add(row)
await self.session.commit()
return self._to_dict(row)
return await self._to_dict(row)
async def set_status(self, roadshow_id: str, status: str, review_comment: str = "") -> dict | None:
row = await self.session.get(Roadshow, roadshow_id)
@@ -1329,7 +1329,7 @@ class RoadshowRepository:
row.review_comment = review_comment
row.updated_at = utcnow_iso()
await self.session.commit()
return self._to_dict(row)
return await self._to_dict(row)
class RoadshowRegistrationRepository:
@@ -1472,7 +1472,7 @@ class ServiceReferralRepository:
stmt = select(ServiceReferral).order_by(ServiceReferral.created_at.desc())
if carrier_id:
stmt = stmt.where(ServiceReferral.carrier_id == carrier_id)
return [self._to_dict(r) for r in await self.session.scalars(stmt)]
return [await self._to_dict(r) for r in await self.session.scalars(stmt)]
async def create(self, fields: dict) -> dict:
row = ServiceReferral(
@@ -1526,7 +1526,7 @@ class EscrowRepository:
stmt = select(Escrow).order_by(Escrow.created_at.desc())
if status:
stmt = stmt.where(Escrow.status == status)
return [self._to_dict(r) for r in await self.session.scalars(stmt)]
return [await self._to_dict(r) for r in await self.session.scalars(stmt)]
async def get(self, escrow_id: str) -> dict | None:
row = await self.session.get(Escrow, escrow_id)
@@ -1587,7 +1587,7 @@ class DisputeRepository:
stmt = select(Dispute).order_by(Dispute.created_at.desc())
if status:
stmt = stmt.where(Dispute.status == status)
return [self._to_dict(r) for r in await self.session.scalars(stmt)]
return [await self._to_dict(r) for r in await self.session.scalars(stmt)]
async def create(self, task_id: str, task_title: str, initiator: str, reason: str) -> dict:
row = Dispute(id=new_id("disp"), task_id=task_id, task_title=task_title,
@@ -1791,12 +1791,12 @@ class Database:
async def initialize(self) -> None:
"""建表(本实例的 engine)+ 幂等种子(roles 空时才写)。"""
from .db import Base
from .seed import seed_data
if self._engine is not None:
from .db import init_db
await init_db()
async with self._engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
await seed_data(self.session)
async def close(self) -> None:
+21 -21
View File
@@ -261,17 +261,17 @@ async def seed_data(session: AsyncSession) -> None:
existing = await session.scalar(select(Role.id).limit(1))
if existing is not None:
# 已初始化的库:仍补种 OPC 业务演示数据、账号身份与端口工作台(各自幂等)。
_sync_permissions(session, now)
_ensure_extra_demo_users(session, now)
_seed_opc_business(session, now)
_migrate_identities(session, now)
_seed_port_dashboards(session, now)
_seed_market(session, now)
_seed_port_pages(session, now)
_seed_investor(session, now)
_seed_ecosystem(session, now)
_ensure_port_agents(session, now)
_seed_org_members(session, now)
await _sync_permissions(session, now)
await _ensure_extra_demo_users(session, now)
await _seed_opc_business(session, now)
await _migrate_identities(session, now)
await _seed_port_dashboards(session, now)
await _seed_market(session, now)
await _seed_port_pages(session, now)
await _seed_investor(session, now)
await _seed_ecosystem(session, now)
await _ensure_port_agents(session, now)
await _seed_org_members(session, now)
await session.commit()
return
@@ -331,16 +331,16 @@ async def seed_data(session: AsyncSession) -> None:
)
)
_seed_operator_business(session, now)
_seed_opc_business(session, now)
_migrate_identities(session, now)
_seed_port_dashboards(session, now)
_seed_market(session, now)
_seed_port_pages(session, now)
_seed_investor(session, now)
_seed_ecosystem(session, now)
_ensure_port_agents(session, now)
_seed_org_members(session, now)
await _seed_operator_business(session, now)
await _seed_opc_business(session, now)
await _migrate_identities(session, now)
await _seed_port_dashboards(session, now)
await _seed_market(session, now)
await _seed_port_pages(session, now)
await _seed_investor(session, now)
await _seed_ecosystem(session, now)
await _ensure_port_agents(session, now)
await _seed_org_members(session, now)
await session.commit()
+15 -10
View File
@@ -1,22 +1,27 @@
# -*- coding: utf-8 -*-
"""共享夹具:临时 SQLite 数据库 + TestClientauth 与 rbac 测试共用)。"""
from __future__ import annotations
"""共享夹具:临时 SQLite 数据库 + TestClientauth 与 rbac 测试共用)。
import asyncio
方案:monkeypatch config.DATABASE_URL 为临时异步 SQLite,让应用 lifespan 在
TestClient 自身的事件循环内建库/种子,避免异步引擎跨事件循环问题。测试函数
保持同步(TestClient 内部驱动 async 应用)。
"""
from __future__ import annotations
import pytest
from fastapi.testclient import TestClient
from app import config as app_config
from app.main import app
from app.infrastructure.repositories import Database
@pytest.fixture()
def client(tmp_path):
""" app.state.db 换成指向临时 SQLite 的 Database,保证测试隔离。"""
db = Database(db_url=f"sqlite+aiosqlite:///{tmp_path}/test.db")
asyncio.run(db.initialize())
app.state.db = db
def client(tmp_path, monkeypatch):
"""数据库指向临时 SQLite,保证测试隔离。"""
monkeypatch.setattr(
app_config, "DATABASE_URL", f"sqlite+aiosqlite:///{tmp_path}/test.db",
)
# 清掉上一测试残留的 app.state.db,强制 lifespan 用当前 tmp URL 新建库
if hasattr(app.state, "db"):
app.state.db = None
with TestClient(app) as c:
yield c
asyncio.run(db.close())