/
pa1ch
/
FitAssistant
Обзор
Документация
Войти
/
pa1ch
/
FitAssistant
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
tests/integration/api/test_workout_replace_exercise.py
93 строки
3 KB
Pavel Chugaev
feat: AI-генерация — метка снаряда, промпт против дублей, честная ошибка вместо тихого фолбэка
16 июл 2026, 16:21
16 июл 2026, 16:21
72a9039
Код
Авторство
О чём код?
"""Integration tests for POST /ai/exercise-replacement. No real LLM calls: ``stub_plan_ai`` swaps the model for pydantic-ai's TestModel, whose junk pick resolves to the deterministic (favorite-first) candidate. Unit coverage for the LLM-pick path lives in tests/unit/services/test_workout_plan_agent.py. """ from app.services.coach import accept_invite, create_invite async def _link(session, coach_id: int, athlete_id: int) -> None: code = await create_invite(session, coach_id) await accept_invite(session, athlete_id, code) await session.flush() async def _first_chest_exercise(client): resp = await client.get("/api/v1/exercises", params={"group": "Грудь"}) return resp.json()[0] async def test_replace_exercise_basic(client, seeded_exercises, stub_plan_ai): ex = await _first_chest_exercise(client) resp = await client.post("/api/v1/ai/exercise-replacement", json={"exercise_id": ex["id"]}) assert resp.status_code == 200 replacement = resp.json() assert replacement["id"] != ex["id"] assert replacement["muscle_group"] == "Грудь" async def test_replace_exercise_respects_exclude_list(client, seeded_exercises, stub_plan_ai): all_chest = (await client.get("/api/v1/exercises", params={"group": "Грудь"})).json() original = all_chest[0] exclude_ids = [e["id"] for e in all_chest[1:-1]] resp = await client.post( "/api/v1/ai/exercise-replacement", json={"exercise_id": original["id"], "exclude_exercise_ids": exclude_ids}, ) assert resp.status_code == 200 assert resp.json()["id"] == all_chest[-1]["id"] async def test_replace_exercise_no_alternatives_422(client, seeded_exercises, stub_plan_ai): all_calves = (await client.get("/api/v1/exercises", params={"group": "Икры"})).json() original = all_calves[0] exclude_ids = [e["id"] for e in all_calves[1:]] resp = await client.post( "/api/v1/ai/exercise-replacement", json={"exercise_id": original["id"], "exclude_exercise_ids": exclude_ids}, ) assert resp.status_code == 422 async def test_replace_exercise_unknown_id_422(client, seeded_exercises, stub_plan_ai): resp = await client.post("/api/v1/ai/exercise-replacement", json={"exercise_id": 999999}) assert resp.status_code == 422 async def test_replace_exercise_ai_unavailable_503(client, seeded_exercises, monkeypatch): monkeypatch.setattr("app.config.settings.ai_enabled", False) ex = await _first_chest_exercise(client) resp = await client.post("/api/v1/ai/exercise-replacement", json={"exercise_id": ex["id"]}) assert resp.status_code == 503 assert "недоступна" in resp.json()["detail"] async def test_replace_exercise_for_linked_athlete( client, session, user, other_user, seeded_exercises, stub_plan_ai ): user.is_coach = True await session.flush() await _link(session, user.id, other_user.id) ex = await _first_chest_exercise(client) resp = await client.post( "/api/v1/ai/exercise-replacement", json={"exercise_id": ex["id"], "athlete_id": other_user.id}, ) assert resp.status_code == 200 async def test_replace_exercise_for_unlinked_athlete_404( client, session, user, other_user, seeded_exercises, stub_plan_ai ): ex = await _first_chest_exercise(client) resp = await client.post( "/api/v1/ai/exercise-replacement", json={"exercise_id": ex["id"], "athlete_id": other_user.id}, ) assert resp.status_code == 404