feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks. - Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence. - Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config. - Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency. - Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT. - Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled. Made-with: Cursor
This commit is contained in:
117
tests/test_surgery_repository.py
Normal file
117
tests/test_surgery_repository.py
Normal file
@@ -0,0 +1,117 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
||||
|
||||
import app.db.models # noqa: F401
|
||||
from app.db.base import Base
|
||||
from app.repositories.surgery_results import SurgeryResultRepository
|
||||
from app.schemas import SurgeryConsumptionDetail
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def db_session() -> AsyncSession:
|
||||
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
session = factory()
|
||||
yield session
|
||||
await session.close()
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_empty_then_load(db_session: AsyncSession) -> None:
|
||||
repo = SurgeryResultRepository()
|
||||
async with db_session.begin():
|
||||
await repo.save_final_result(db_session, surgery_id="123456", details=[])
|
||||
async with db_session.begin():
|
||||
loaded = await repo.load_final_details(db_session, "123456")
|
||||
assert loaded == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_roundtrip(db_session: AsyncSession) -> None:
|
||||
repo = SurgeryResultRepository()
|
||||
ts = datetime(2026, 4, 21, 10, 0, tzinfo=timezone.utc)
|
||||
details = [
|
||||
SurgeryConsumptionDetail(
|
||||
item_id="纱布",
|
||||
item_name="纱布",
|
||||
quantity=1,
|
||||
doctor_id="D1",
|
||||
timestamp=ts,
|
||||
source="vision",
|
||||
),
|
||||
SurgeryConsumptionDetail(
|
||||
item_id="纱布",
|
||||
item_name="纱布",
|
||||
quantity=1,
|
||||
doctor_id="voice",
|
||||
timestamp=ts,
|
||||
source="voice",
|
||||
),
|
||||
]
|
||||
async with db_session.begin():
|
||||
await repo.save_final_result(db_session, surgery_id="654321", details=details)
|
||||
async with db_session.begin():
|
||||
loaded = await repo.load_final_details(db_session, "654321")
|
||||
assert loaded is not None
|
||||
assert len(loaded) == 2
|
||||
assert loaded[0].source == "vision"
|
||||
assert loaded[1].source == "voice"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_missing_surgery_returns_none(db_session: AsyncSession) -> None:
|
||||
repo = SurgeryResultRepository()
|
||||
async with db_session.begin():
|
||||
missing = await repo.load_final_details(db_session, "000000")
|
||||
assert missing is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_save_overwrites_previous_final_result(db_session: AsyncSession) -> None:
|
||||
repo = SurgeryResultRepository()
|
||||
ts1 = datetime(2026, 4, 21, 9, 0, tzinfo=timezone.utc)
|
||||
ts2 = datetime(2026, 4, 21, 10, 0, tzinfo=timezone.utc)
|
||||
async with db_session.begin():
|
||||
await repo.save_final_result(
|
||||
db_session,
|
||||
surgery_id="888888",
|
||||
details=[
|
||||
SurgeryConsumptionDetail(
|
||||
item_id="旧",
|
||||
item_name="旧",
|
||||
quantity=1,
|
||||
doctor_id="D1",
|
||||
timestamp=ts1,
|
||||
source="vision",
|
||||
),
|
||||
],
|
||||
)
|
||||
async with db_session.begin():
|
||||
await repo.save_final_result(
|
||||
db_session,
|
||||
surgery_id="888888",
|
||||
details=[
|
||||
SurgeryConsumptionDetail(
|
||||
item_id="新",
|
||||
item_name="新",
|
||||
quantity=2,
|
||||
doctor_id="D2",
|
||||
timestamp=ts2,
|
||||
source="voice",
|
||||
),
|
||||
],
|
||||
)
|
||||
async with db_session.begin():
|
||||
loaded = await repo.load_final_details(db_session, "888888")
|
||||
assert loaded is not None
|
||||
assert len(loaded) == 1
|
||||
assert loaded[0].item_id == "新"
|
||||
assert loaded[0].quantity == 2
|
||||
assert loaded[0].source == "voice"
|
||||
Reference in New Issue
Block a user