Add ProductBase, ProductPublic, ProductWithInventory and SkinConditionSnapshotBase, SkinConditionSnapshotPublic. Table models now inherit from their Base counterpart and override JSON fields with sa_column. All field_serializer hacks removed; FastAPI response models use the non-table Public classes so Pydantic coerces raw DB dicts → typed models cleanly. ProductCreate and SnapshotCreate now simply inherit their respective Base classes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
218 lines
6.3 KiB
Python
218 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
|