Initial commit: backend API, data models, and test suite
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>
This commit is contained in:
commit
8f7d893a63
32 changed files with 6282 additions and 0 deletions
219
backend/tests/test_routines.py
Normal file
219
backend/tests/test_routines.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue