FastAPI backend for personal health and skincare data with MCP export. Includes SQLModel models for products, inventory, medications, lab results, routines, and skin condition snapshots. Pytest suite with 111 tests running on SQLite in-memory (no PostgreSQL required). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
285 lines
8.8 KiB
Python
285 lines
8.8 KiB
Python
import uuid
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Medications
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_create_medication_minimal(client, medication_data):
|
|
r = client.post("/health/medications", json=medication_data)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert "record_id" in data
|
|
assert data["kind"] == "prescription"
|
|
assert data["product_name"] == "Epiduo"
|
|
|
|
|
|
def test_create_medication_invalid_kind(client):
|
|
r = client.post(
|
|
"/health/medications", json={"kind": "invalid_kind", "product_name": "X"}
|
|
)
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_list_medications_empty(client):
|
|
r = client.get("/health/medications")
|
|
assert r.status_code == 200
|
|
assert r.json() == []
|
|
|
|
|
|
def test_list_filter_kind(client):
|
|
client.post(
|
|
"/health/medications", json={"kind": "prescription", "product_name": "A"}
|
|
)
|
|
client.post(
|
|
"/health/medications", json={"kind": "supplement", "product_name": "B"}
|
|
)
|
|
r = client.get("/health/medications?kind=supplement")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
assert data[0]["kind"] == "supplement"
|
|
|
|
|
|
def test_list_filter_product_name(client):
|
|
client.post(
|
|
"/health/medications", json={"kind": "otc", "product_name": "Epiduo Forte"}
|
|
)
|
|
client.post(
|
|
"/health/medications", json={"kind": "otc", "product_name": "Panoxyl"}
|
|
)
|
|
r = client.get("/health/medications?product_name=epid")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
assert "Epiduo" in data[0]["product_name"]
|
|
|
|
|
|
def test_get_medication(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.get(f"/health/medications/{mid}")
|
|
assert r.status_code == 200
|
|
assert r.json()["record_id"] == mid
|
|
|
|
|
|
def test_get_medication_not_found(client):
|
|
r = client.get(f"/health/medications/{uuid.uuid4()}")
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_update_medication(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.patch(
|
|
f"/health/medications/{mid}", json={"notes": "Take in the evening"}
|
|
)
|
|
assert r.status_code == 200
|
|
assert r.json()["notes"] == "Take in the evening"
|
|
|
|
|
|
def test_update_medication_not_found(client):
|
|
r = client.patch(f"/health/medications/{uuid.uuid4()}", json={"notes": "x"})
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_delete_medication_no_usages(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.delete(f"/health/medications/{mid}")
|
|
assert r.status_code == 204
|
|
r2 = client.get(f"/health/medications/{mid}")
|
|
assert r2.status_code == 404
|
|
|
|
|
|
def test_delete_medication_with_usages(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
# Create a usage
|
|
client.post(
|
|
f"/health/medications/{mid}/usages",
|
|
json={"valid_from": "2026-01-01T00:00:00"},
|
|
)
|
|
# Delete medication (should also delete usages)
|
|
r = client.delete(f"/health/medications/{mid}")
|
|
assert r.status_code == 204
|
|
r2 = client.get(f"/health/medications/{mid}")
|
|
assert r2.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Usages
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_create_usage(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.post(
|
|
f"/health/medications/{mid}/usages",
|
|
json={"valid_from": "2026-01-01T08:00:00", "dose_value": 1.0, "dose_unit": "pea"},
|
|
)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert "record_id" in data
|
|
assert data["medication_record_id"] == mid
|
|
assert data["dose_unit"] == "pea"
|
|
|
|
|
|
def test_create_usage_medication_not_found(client):
|
|
r = client.post(
|
|
f"/health/medications/{uuid.uuid4()}/usages",
|
|
json={"valid_from": "2026-01-01T00:00:00"},
|
|
)
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_list_usages_empty(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.get(f"/health/medications/{mid}/usages")
|
|
assert r.status_code == 200
|
|
assert r.json() == []
|
|
|
|
|
|
def test_list_usages_returns_entries(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
client.post(
|
|
f"/health/medications/{mid}/usages",
|
|
json={"valid_from": "2026-01-01T00:00:00"},
|
|
)
|
|
r = client.get(f"/health/medications/{mid}/usages")
|
|
assert r.status_code == 200
|
|
assert len(r.json()) == 1
|
|
|
|
|
|
def test_update_usage(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.post(
|
|
f"/health/medications/{mid}/usages",
|
|
json={"valid_from": "2026-01-01T00:00:00"},
|
|
)
|
|
uid = r.json()["record_id"]
|
|
|
|
r2 = client.patch(f"/health/usages/{uid}", json={"dose_value": 2.5, "dose_unit": "mg"})
|
|
assert r2.status_code == 200
|
|
assert r2.json()["dose_value"] == 2.5
|
|
assert r2.json()["dose_unit"] == "mg"
|
|
|
|
|
|
def test_update_usage_not_found(client):
|
|
r = client.patch(f"/health/usages/{uuid.uuid4()}", json={"dose_value": 1.0})
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_delete_usage(client, created_medication):
|
|
mid = created_medication["record_id"]
|
|
r = client.post(
|
|
f"/health/medications/{mid}/usages",
|
|
json={"valid_from": "2026-01-01T00:00:00"},
|
|
)
|
|
uid = r.json()["record_id"]
|
|
|
|
r2 = client.delete(f"/health/usages/{uid}")
|
|
assert r2.status_code == 204
|
|
|
|
|
|
def test_delete_usage_not_found(client):
|
|
r = client.delete(f"/health/usages/{uuid.uuid4()}")
|
|
assert r.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lab results
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
LAB_RESULT_DATA = {
|
|
"collected_at": "2026-01-15T09:00:00",
|
|
"test_code": "718-7",
|
|
"test_name_original": "Hemoglobin",
|
|
"value_num": 14.5,
|
|
"unit_original": "g/dL",
|
|
"flag": "N",
|
|
}
|
|
|
|
|
|
def test_create_lab_result(client):
|
|
r = client.post("/health/lab-results", json=LAB_RESULT_DATA)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert "record_id" in data
|
|
assert data["test_code"] == "718-7"
|
|
assert data["flag"] == "N"
|
|
|
|
|
|
def test_create_lab_result_invalid_code(client):
|
|
bad = {**LAB_RESULT_DATA, "test_code": "not-a-loinc-code"}
|
|
r = client.post("/health/lab-results", json=bad)
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_create_lab_result_invalid_flag(client):
|
|
bad = {**LAB_RESULT_DATA, "flag": "INVALID"}
|
|
r = client.post("/health/lab-results", json=bad)
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_list_lab_results_empty(client):
|
|
r = client.get("/health/lab-results")
|
|
assert r.status_code == 200
|
|
assert r.json() == []
|
|
|
|
|
|
def test_list_filter_test_code(client):
|
|
client.post("/health/lab-results", json=LAB_RESULT_DATA)
|
|
client.post("/health/lab-results", json={**LAB_RESULT_DATA, "test_code": "2951-2"})
|
|
r = client.get("/health/lab-results?test_code=718-7")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
assert data[0]["test_code"] == "718-7"
|
|
|
|
|
|
def test_list_filter_flag(client):
|
|
client.post("/health/lab-results", json={**LAB_RESULT_DATA, "flag": "N"})
|
|
client.post("/health/lab-results", json={**LAB_RESULT_DATA, "flag": "H"})
|
|
r = client.get("/health/lab-results?flag=H")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
assert data[0]["flag"] == "H"
|
|
|
|
|
|
def test_list_filter_date_range(client):
|
|
client.post("/health/lab-results", json={**LAB_RESULT_DATA, "collected_at": "2026-01-01T00:00:00"})
|
|
client.post("/health/lab-results", json={**LAB_RESULT_DATA, "collected_at": "2026-06-01T00:00:00"})
|
|
r = client.get("/health/lab-results?from_date=2026-05-01T00:00:00")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
|
|
|
|
def test_get_lab_result(client):
|
|
r = client.post("/health/lab-results", json=LAB_RESULT_DATA)
|
|
rid = r.json()["record_id"]
|
|
r2 = client.get(f"/health/lab-results/{rid}")
|
|
assert r2.status_code == 200
|
|
assert r2.json()["record_id"] == rid
|
|
|
|
|
|
def test_get_lab_result_not_found(client):
|
|
r = client.get(f"/health/lab-results/{uuid.uuid4()}")
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_update_lab_result(client):
|
|
r = client.post("/health/lab-results", json=LAB_RESULT_DATA)
|
|
rid = r.json()["record_id"]
|
|
r2 = client.patch(f"/health/lab-results/{rid}", json={"notes": "Recheck in 3 months"})
|
|
assert r2.status_code == 200
|
|
assert r2.json()["notes"] == "Recheck in 3 months"
|
|
|
|
|
|
def test_delete_lab_result(client):
|
|
r = client.post("/health/lab-results", json=LAB_RESULT_DATA)
|
|
rid = r.json()["record_id"]
|
|
r2 = client.delete(f"/health/lab-results/{rid}")
|
|
assert r2.status_code == 204
|
|
r3 = client.get(f"/health/lab-results/{rid}")
|
|
assert r3.status_code == 404
|