BioPatch / backend /tests /test_api.py
Jerry
BioPatch backend (clean state, history rewritten)
10795db
Raw History Blame Contribute Delete
5.72 kB
"""End-to-end smoke tests for the BioPatch API.
Uses an in-memory SQLite shared across connections so the TestClient and the
WebSocket use the same data. The simulator and auto-seeder are disabled here
so we can drive the system deterministically.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
os.environ["DATABASE_URL"] = "sqlite+pysqlite:///file:biopatch_test?mode=memory&cache=shared&uri=true"
os.environ["ENABLE_SIMULATOR"] = "false"
os.environ["AUTO_SEED"] = "false"
os.environ["JWT_SECRET"] = "test-secret-for-pytest-only-not-for-prod-use"
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import pytest
from fastapi.testclient import TestClient
from core.database import init_db # noqa: E402
from main import app # noqa: E402
@pytest.fixture(scope="module")
def client():
init_db()
with TestClient(app) as c:
yield c
def _register_and_login(client: TestClient, role: str = "admin") -> str:
email = f"{role}@biopatch.io"
client.post(
"/api/auth/register",
json={
"email": email,
"full_name": f"Test {role.title()}",
"password": "TestPassword!1",
"role": role,
},
)
resp = client.post(
"/api/auth/login/json",
json={"email": email, "password": "TestPassword!1"},
)
assert resp.status_code == 200, resp.text
return resp.json()["access_token"]
def test_health(client: TestClient) -> None:
r = client.get("/api/health")
assert r.status_code == 200
assert r.json()["status"] == "ok"
def test_auth_required_on_patients(client: TestClient) -> None:
r = client.get("/api/patients")
assert r.status_code == 401
def test_full_patient_flow(client: TestClient) -> None:
token = _register_and_login(client, role="admin")
h = {"Authorization": f"Bearer {token}"}
# Create a patient
r = client.post(
"/api/patients",
json={
"mrn": "MRN-TEST-1",
"full_name": "Test Patient",
"age": 60,
"sex": "M",
"ward": "TestWard",
"bed": "1",
"surgery_type": "Appendectomy",
"surgery_date": "2025-05-10T08:00:00Z",
},
headers=h,
)
assert r.status_code == 201, r.text
patient_id = r.json()["id"]
# Attach a sensor
r = client.post(
f"/api/patients/{patient_id}/sensors",
json={"device_id": "BP-TEST-001", "location": "incision"},
headers=h,
)
assert r.status_code == 201, r.text
sensor_id = r.json()["id"]
# Ingest a vital that triggers critical
r = client.post(
f"/api/patients/{patient_id}/vitals",
json={"temperature_c": 38.6, "ph": 8.1, "humidity": 88.0, "sensor_id": sensor_id},
headers=h,
)
assert r.status_code == 201, r.text
vital = r.json()
assert vital["severity"] == "critical"
assert vital["sepsis_risk"] >= 75
assert vital["bacterial_signature"] is not None
# List shows the latest score
r = client.get("/api/patients", headers=h)
assert r.status_code == 200
items = r.json()
target = next(p for p in items if p["id"] == patient_id)
assert target["latest_severity"] == "critical"
assert target["online_sensor_count"] == 1
# Vitals history
r = client.get(f"/api/patients/{patient_id}/vitals?hours=1", headers=h)
assert r.status_code == 200
assert len(r.json()) == 1
# Clinical note
r = client.post(
f"/api/patients/{patient_id}/notes",
json={"body": "Started broad-spectrum antibiotics.", "category": "intervention", "severity": "warning"},
headers=h,
)
assert r.status_code == 201, r.text
assert r.json()["author_name"] == "Test Admin"
r = client.get(f"/api/patients/{patient_id}/notes", headers=h)
assert r.status_code == 200
assert len(r.json()) == 1
def test_role_enforcement_for_patient_create(client: TestClient) -> None:
token = _register_and_login(client, role="nurse")
h = {"Authorization": f"Bearer {token}"}
r = client.post(
"/api/patients",
json={
"mrn": "MRN-TEST-NURSE",
"full_name": "Should Fail",
"age": 50,
"sex": "F",
"ward": "X",
"bed": "1",
"surgery_type": "X",
"surgery_date": "2025-05-10T08:00:00Z",
},
headers=h,
)
assert r.status_code == 403
def test_websocket_snapshot_and_broadcast(client: TestClient) -> None:
token = _register_and_login(client, role="admin")
# Unauthenticated handshake closes immediately
with client.websocket_connect("/ws/stream") as ws:
msg = ws.receive_json()
assert msg["type"] == "error"
with client.websocket_connect(f"/ws/stream?token={token}") as ws:
snap = ws.receive_json()
assert snap["type"] == "snapshot"
assert "patients" in snap["data"]
# Drive a new reading -> the WS must broadcast it
h = {"Authorization": f"Bearer {token}"}
patients = client.get("/api/patients", headers=h).json()
assert patients, "expected at least one patient from previous test"
pid = patients[0]["id"]
r = client.post(
f"/api/patients/{pid}/vitals",
json={"temperature_c": 34.0, "ph": 6.0, "humidity": 75.0},
headers=h,
)
assert r.status_code == 201
seen_types: set[str] = set()
for _ in range(5):
event = ws.receive_json()
seen_types.add(event["type"])
if "vitals" in seen_types:
break
assert "vitals" in seen_types