298 lines
8.9 KiB
Python
298 lines
8.9 KiB
Python
import uuid
|
|
from unittest.mock import patch
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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
|
|
|
|
|
|
def test_suggest_routine(client, session):
|
|
with patch(
|
|
"innercontext.api.routines.call_gemini_with_function_tools"
|
|
) as mock_gemini:
|
|
# Mock the Gemini response
|
|
mock_response = type(
|
|
"Response",
|
|
(),
|
|
{
|
|
"text": '{"steps": [{"product_id": null, "action_type": "shaving_razor"}], "reasoning": "because"}'
|
|
},
|
|
)
|
|
mock_gemini.return_value = mock_response
|
|
|
|
r = client.post(
|
|
"/routines/suggest",
|
|
json={
|
|
"routine_date": "2026-03-03",
|
|
"part_of_day": "am",
|
|
"notes": "Testing",
|
|
"include_minoxidil_beard": True,
|
|
},
|
|
)
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data["steps"]) == 1
|
|
assert data["steps"][0]["action_type"] == "shaving_razor"
|
|
assert data["reasoning"] == "because"
|
|
kwargs = mock_gemini.call_args.kwargs
|
|
assert "USER PROFILE:" in kwargs["contents"]
|
|
assert "function_handlers" in kwargs
|
|
assert "get_product_details" in kwargs["function_handlers"]
|
|
|
|
|
|
def test_suggest_batch(client, session):
|
|
with patch("innercontext.api.routines.call_gemini") as mock_gemini:
|
|
# Mock the Gemini response
|
|
mock_response = type(
|
|
"Response",
|
|
(),
|
|
{
|
|
"text": '{"days": [{"date": "2026-03-03", "am_steps": [], "pm_steps": [], "reasoning": "none"}], "overall_reasoning": "batch test"}'
|
|
},
|
|
)
|
|
mock_gemini.return_value = mock_response
|
|
|
|
r = client.post(
|
|
"/routines/suggest-batch",
|
|
json={
|
|
"from_date": "2026-03-03",
|
|
"to_date": "2026-03-04",
|
|
"minimize_products": True,
|
|
},
|
|
)
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert len(data["days"]) == 1
|
|
assert data["days"][0]["date"] == "2026-03-03"
|
|
assert data["overall_reasoning"] == "batch test"
|
|
kwargs = mock_gemini.call_args.kwargs
|
|
assert "USER PROFILE:" in kwargs["contents"]
|
|
|
|
|
|
def test_suggest_batch_invalid_date_range(client):
|
|
r = client.post(
|
|
"/routines/suggest-batch",
|
|
json={"from_date": "2026-03-04", "to_date": "2026-03-03"},
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_suggest_batch_too_long(client):
|
|
r = client.post(
|
|
"/routines/suggest-batch",
|
|
json={"from_date": "2026-03-01", "to_date": "2026-03-20"},
|
|
)
|
|
assert r.status_code == 400
|