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>
219 lines
6.3 KiB
Python
219 lines
6.3 KiB
Python
import uuid
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Routines
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_create_routine_minimal(client):
|
|
r = client.post(
|
|
"/routines/", json={"routine_date": "2026-02-26", "part_of_day": "am"}
|
|
)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert "id" in data
|
|
assert data["routine_date"] == "2026-02-26"
|
|
assert data["part_of_day"] == "am"
|
|
|
|
|
|
def test_create_routine_invalid_part_of_day(client):
|
|
r = client.post(
|
|
"/routines/", json={"routine_date": "2026-02-26", "part_of_day": "noon"}
|
|
)
|
|
assert r.status_code == 422
|
|
|
|
|
|
def test_list_routines_empty(client):
|
|
r = client.get("/routines/")
|
|
assert r.status_code == 200
|
|
assert r.json() == []
|
|
|
|
|
|
def test_list_filter_date_range(client):
|
|
client.post("/routines/", json={"routine_date": "2026-01-01", "part_of_day": "am"})
|
|
client.post("/routines/", json={"routine_date": "2026-06-01", "part_of_day": "am"})
|
|
r = client.get("/routines/?from_date=2026-05-01&to_date=2026-12-31")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
assert data[0]["routine_date"] == "2026-06-01"
|
|
|
|
|
|
def test_list_filter_part_of_day(client):
|
|
client.post("/routines/", json={"routine_date": "2026-02-01", "part_of_day": "am"})
|
|
client.post("/routines/", json={"routine_date": "2026-02-02", "part_of_day": "pm"})
|
|
r = client.get("/routines/?part_of_day=pm")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data) == 1
|
|
assert data[0]["part_of_day"] == "pm"
|
|
|
|
|
|
def test_get_routine(client, created_routine):
|
|
rid = created_routine["id"]
|
|
r = client.get(f"/routines/{rid}")
|
|
assert r.status_code == 200
|
|
assert r.json()["id"] == rid
|
|
|
|
|
|
def test_get_routine_not_found(client):
|
|
r = client.get(f"/routines/{uuid.uuid4()}")
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_update_routine_notes(client, created_routine):
|
|
rid = created_routine["id"]
|
|
r = client.patch(f"/routines/{rid}", json={"notes": "Felt great today"})
|
|
assert r.status_code == 200
|
|
assert r.json()["notes"] == "Felt great today"
|
|
|
|
|
|
def test_update_routine_not_found(client):
|
|
r = client.patch(f"/routines/{uuid.uuid4()}", json={"notes": "x"})
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_delete_routine(client, created_routine):
|
|
rid = created_routine["id"]
|
|
r = client.delete(f"/routines/{rid}")
|
|
assert r.status_code == 204
|
|
r2 = client.get(f"/routines/{rid}")
|
|
assert r2.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# RoutineStep
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_add_step_action_only(client, created_routine):
|
|
rid = created_routine["id"]
|
|
r = client.post(
|
|
f"/routines/{rid}/steps",
|
|
json={"order_index": 0, "action_notes": "Cleanse face gently"},
|
|
)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert data["order_index"] == 0
|
|
assert data["action_notes"] == "Cleanse face gently"
|
|
assert data["product_id"] is None
|
|
|
|
|
|
def test_add_step_with_product(client, created_routine, created_product):
|
|
rid = created_routine["id"]
|
|
pid = created_product["id"]
|
|
r = client.post(
|
|
f"/routines/{rid}/steps",
|
|
json={"order_index": 1, "product_id": pid},
|
|
)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert data["product_id"] == pid
|
|
assert data["order_index"] == 1
|
|
|
|
|
|
def test_add_step_routine_not_found(client):
|
|
r = client.post(
|
|
f"/routines/{uuid.uuid4()}/steps",
|
|
json={"order_index": 0},
|
|
)
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_update_step(client, created_routine):
|
|
rid = created_routine["id"]
|
|
r = client.post(
|
|
f"/routines/{rid}/steps",
|
|
json={"order_index": 0, "action_notes": "Initial"},
|
|
)
|
|
step_id = r.json()["id"]
|
|
|
|
r2 = client.patch(f"/routines/steps/{step_id}", json={"action_notes": "Updated"})
|
|
assert r2.status_code == 200
|
|
assert r2.json()["action_notes"] == "Updated"
|
|
|
|
|
|
def test_update_step_not_found(client):
|
|
r = client.patch(f"/routines/steps/{uuid.uuid4()}", json={"action_notes": "x"})
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_delete_step(client, created_routine):
|
|
rid = created_routine["id"]
|
|
r = client.post(
|
|
f"/routines/{rid}/steps",
|
|
json={"order_index": 0},
|
|
)
|
|
step_id = r.json()["id"]
|
|
|
|
r2 = client.delete(f"/routines/steps/{step_id}")
|
|
assert r2.status_code == 204
|
|
|
|
|
|
def test_delete_step_not_found(client):
|
|
r = client.delete(f"/routines/steps/{uuid.uuid4()}")
|
|
assert r.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GroomingSchedule
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_list_grooming_schedule_empty(client):
|
|
r = client.get("/routines/grooming-schedule")
|
|
assert r.status_code == 200
|
|
assert r.json() == []
|
|
|
|
|
|
def test_create_grooming_schedule(client):
|
|
r = client.post(
|
|
"/routines/grooming-schedule",
|
|
json={"day_of_week": 1, "action": "shaving_razor"},
|
|
)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert data["day_of_week"] == 1
|
|
assert data["action"] == "shaving_razor"
|
|
|
|
|
|
def test_list_grooming_schedule_returns_entry(client):
|
|
client.post(
|
|
"/routines/grooming-schedule",
|
|
json={"day_of_week": 3, "action": "dermarolling"},
|
|
)
|
|
r = client.get("/routines/grooming-schedule")
|
|
assert r.status_code == 200
|
|
assert len(r.json()) >= 1
|
|
|
|
|
|
def test_update_grooming_schedule(client):
|
|
r = client.post(
|
|
"/routines/grooming-schedule",
|
|
json={"day_of_week": 0, "action": "shaving_oneblade"},
|
|
)
|
|
entry_id = r.json()["id"]
|
|
|
|
r2 = client.patch(
|
|
f"/routines/grooming-schedule/{entry_id}",
|
|
json={"notes": "Use cold water"},
|
|
)
|
|
assert r2.status_code == 200
|
|
assert r2.json()["notes"] == "Use cold water"
|
|
|
|
|
|
def test_delete_grooming_schedule(client):
|
|
r = client.post(
|
|
"/routines/grooming-schedule",
|
|
json={"day_of_week": 5, "action": "shaving_razor"},
|
|
)
|
|
entry_id = r.json()["id"]
|
|
|
|
r2 = client.delete(f"/routines/grooming-schedule/{entry_id}")
|
|
assert r2.status_code == 204
|
|
|
|
|
|
def test_delete_grooming_schedule_not_found(client):
|
|
r = client.delete(f"/routines/grooming-schedule/{uuid.uuid4()}")
|
|
assert r.status_code == 404
|