"""Regression tests for defects found while walking the live health journey. Each test below fails on the code as it was before the matching fix: 1. an unknown value on an enum-backed query filter (`sport_type`, `meal`, `status`) reached SQLAlchemy and raised `LookupError` -> HTTP 500 with no French error envelope; 2. `/health/weights/stats` drew `plan_line` from the goal start to the LAST DAY OF THE REQUESTED WINDOW, so the plan reference line of the weight chart moved with the chart period and was ~7x too steep for a `weekly_rate` goal (datamodel-health-vape.md §5.6b: `days = delta / (weekly_rate_kg / 7)`); 3. `/health/goals/active` reported `deficit_target_kcal` as `tdee_smoothed - budget`, i.e. the deficit left AFTER the calorie floor clamped the budget, instead of the deficit AIMED AT by the goal (§5.3 `BudgetResult.deficit_target = rate x 7700 / 7`). """ import datetime as dt from zoneinfo import ZoneInfo from fastapi.testclient import TestClient BASE = "/api/health" PARIS = ZoneInfo("Europe/Paris") PROFILE = { "height_cm": 178.0, "sex": "male", "birthdate": "1990-05-12", "activity_level": "moderate", "timezone": "Europe/Paris", } def _today() -> dt.date: return dt.datetime.now(dt.UTC).astimezone(PARIS).date() def _at(day: dt.date, hour: int) -> str: return ( dt.datetime(day.year, day.month, day.day, hour, tzinfo=PARIS) .astimezone(dt.UTC) .isoformat() .replace("+00:00", "Z") ) def _seed(client: TestClient, headers: dict[str, str]) -> None: assert ( client.put(f"{BASE}/profile", json=PROFILE, headers=headers).status_code == 200 ) today = _today() for offset, weight in ((6, 95.0), (4, 94.6), (2, 94.3), (0, 94.0)): response = client.post( f"{BASE}/weights", json={ "measured_at": _at(today - dt.timedelta(days=offset), 7), "weight_kg": weight, }, headers=headers, ) assert response.status_code == 201, response.text # --- 1. Unknown enum filter values must be rejected, never crash -------------- def test_unknown_sport_type_filter_is_rejected( client: TestClient, auth_headers: dict[str, str] ) -> None: response = client.get( f"{BASE}/workouts", params={"sport_type": "quidditch"}, headers=auth_headers ) assert response.status_code == 422, response.text assert response.json()["error"]["code"] == "validation_error" def test_unknown_meal_filter_is_rejected( client: TestClient, auth_headers: dict[str, str] ) -> None: response = client.get( f"{BASE}/nutrition/entries", params={"meal": "brunch"}, headers=auth_headers ) assert response.status_code == 422, response.text assert response.json()["error"]["code"] == "validation_error" def test_unknown_goal_status_filter_is_rejected( client: TestClient, auth_headers: dict[str, str] ) -> None: response = client.get( f"{BASE}/goals", params={"status": "zzz"}, headers=auth_headers ) assert response.status_code == 422, response.text assert response.json()["error"]["code"] == "validation_error" def test_known_enum_filters_still_work( client: TestClient, auth_headers: dict[str, str] ) -> None: assert ( client.get( f"{BASE}/workouts", params={"sport_type": "cycling"}, headers=auth_headers ).status_code == 200 ) assert ( client.get( f"{BASE}/nutrition/entries", params={"meal": "lunch"}, headers=auth_headers ).status_code == 200 ) assert ( client.get( f"{BASE}/goals", params={"status": "active"}, headers=auth_headers ).status_code == 200 ) # --- 2. plan_line must follow the goal rate, not the chart window ------------- def test_plan_line_follows_the_weekly_rate_not_the_window( client: TestClient, auth_headers: dict[str, str] ) -> None: _seed(client, auth_headers) today = _today() start = today - dt.timedelta(days=6) created = client.post( f"{BASE}/goals", json={ "mode": "weekly_rate", "start_date": start.isoformat(), "start_weight_kg": 95.0, "target_weight_kg": 85.0, "weekly_rate_kg": 0.5, }, headers=auth_headers, ) assert created.status_code == 201, created.text # 10 kg at 0.5 kg/week = 20 weeks = 140 days after the goal start date. expected_end = (start + dt.timedelta(days=140)).isoformat() def plan_line(params: dict) -> list: response = client.get( f"{BASE}/weights/stats", params=params, headers=auth_headers ) assert response.status_code == 200, response.text body = response.json() found = [s for s in body["series"] if s["name"] == "plan_line"] assert found, "plan_line series missing" assert body["meta"]["plan_end_date"] == expected_end return found[0]["points"] wide = plan_line({"from": start.isoformat(), "to": today.isoformat()}) narrow = plan_line( {"from": (today - dt.timedelta(days=2)).isoformat(), "to": today.isoformat()} ) assert wide == narrow, "plan_line must not depend on the requested window" assert wide[0] == [start.isoformat(), 95.0] assert wide[1] == [expected_end, 85.0] def test_plan_line_uses_target_date_when_the_goal_has_one( client: TestClient, auth_headers: dict[str, str] ) -> None: _seed(client, auth_headers) today = _today() start = today - dt.timedelta(days=6) target_date = today + dt.timedelta(days=200) assert ( client.post( f"{BASE}/goals", json={ "mode": "target_date", "start_date": start.isoformat(), "start_weight_kg": 95.0, "target_weight_kg": 85.0, "target_date": target_date.isoformat(), }, headers=auth_headers, ).status_code == 201 ) body = client.get( f"{BASE}/weights/stats", params={"from": start.isoformat(), "to": today.isoformat()}, headers=auth_headers, ).json() points = next(s for s in body["series"] if s["name"] == "plan_line")["points"] assert points[1] == [target_date.isoformat(), 85.0] def test_maintain_goal_has_no_plan_line( client: TestClient, auth_headers: dict[str, str] ) -> None: _seed(client, auth_headers) today = _today() assert ( client.post( f"{BASE}/goals", json={ "mode": "maintain", "start_date": (today - dt.timedelta(days=6)).isoformat(), "start_weight_kg": 95.0, "target_weight_kg": 95.0, }, headers=auth_headers, ).status_code == 201 ) body = client.get(f"{BASE}/weights/stats", headers=auth_headers).json() assert [s for s in body["series"] if s["name"] == "plan_line"] == [] assert body["meta"]["plan_end_date"] is None # --- 3. deficit_target_kcal is the AIMED deficit, floor or not ---------------- def test_deficit_target_is_the_goal_target_even_when_the_floor_applies( client: TestClient, auth_headers: dict[str, str] ) -> None: _seed(client, auth_headers) today = _today() assert ( client.post( f"{BASE}/goals", json={ "mode": "weekly_rate", "start_date": (today - dt.timedelta(days=6)).isoformat(), "start_weight_kg": 95.0, "target_weight_kg": 85.0, "weekly_rate_kg": 1.5, }, headers=auth_headers, ).status_code == 201 ) budget = client.get(f"{BASE}/goals/active", headers=auth_headers).json()["budget"] # 1.5 kg/week -> 1.5 * 7700 / 7 = 1650 kcal/day, whatever the floor does. assert budget["deficit_target_kcal"] == 1650.0 assert budget["floor_applied"] is True assert budget["kcal"] == 1500.0 def test_energy_balance_meta_exposes_the_target_deficit( client: TestClient, auth_headers: dict[str, str] ) -> None: _seed(client, auth_headers) today = _today() assert ( client.post( f"{BASE}/goals", json={ "mode": "weekly_rate", "start_date": (today - dt.timedelta(days=6)).isoformat(), "start_weight_kg": 95.0, "target_weight_kg": 85.0, "weekly_rate_kg": 0.5, }, headers=auth_headers, ).status_code == 201 ) meta = client.get( f"{BASE}/energy-balance", params={ "from": (today - dt.timedelta(days=6)).isoformat(), "to": today.isoformat(), }, headers=auth_headers, ).json()["meta"] assert meta["deficit_target_kcal"] == 550.0 # 0.5 * 7700 / 7