innercontext/backend/tests/test_routines.py
Piotr Oleszczyk 558708653c feat(api): expand routines tool-calling to reduce prompt load
Keep the /routines/suggest base context lean by sending only active names and fetching detailed safety, actives, usage notes, and INCI on demand. Add a conservative fallback when tool roundtrip limits are hit to preserve safe outputs instead of failing the request.
2026-03-04 11:52:07 +01:00

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 "function_handlers" in kwargs
assert "get_product_inci" in kwargs["function_handlers"]
assert "get_product_safety_rules" in kwargs["function_handlers"]
assert "get_product_actives" in kwargs["function_handlers"]
assert "get_product_usage_notes" 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"
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