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") 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" 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