""" Тесты для get_next_lesson. Проверяют приоритеты: overdue → weak → test → next. """ import pytest from datetime import datetime, timedelta # ───────────────────────────────────────────── # ХЕЛПЕРЫ # ───────────────────────────────────────────── async def set_user_level(conn, user_id: int, level: str): await conn.execute("UPDATE users SET current_level = $1 WHERE telegram_id = $2", level, user_id) async def insert_mastery( conn, user_id: int, skill_tag: str, mastery: float, retention: str = "fresh", attempts: int = 3, days_since: int = 0, ): """Вставляет запись в user_skill_mastery.""" last_attempt = datetime.now() - timedelta(days=days_since) await conn.execute(""" INSERT INTO user_skill_mastery ( user_id, skill_tag, mastery_score, retention_status, attempts_count, last_attempt_at ) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (user_id, skill_tag) DO UPDATE SET mastery_score = $3, retention_status = $4, attempts_count = $5, last_attempt_at = $6 """, user_id, skill_tag, mastery, retention, attempts, last_attempt) # ───────────────────────────────────────────── # ТЕСТЫ # ───────────────────────────────────────────── async def test_orchestrator_prioritizes_overdue(conn, test_user, create_test_lesson, clean_mastery): """Если есть overdue навык — он выбирается первым.""" from routers.orchestrator import get_next_lesson await set_user_level(conn, test_user, "A2") await create_test_lesson("test_skill") await insert_mastery(conn, test_user, "test_skill", mastery=50.0, retention="overdue", days_since=20) result = await get_next_lesson(conn, test_user) assert result["type"] == "review" assert result["reason"] == "overdue" async def test_orchestrator_prioritizes_weak(conn, test_user, create_test_lesson, clean_mastery): """Если нет overdue, но есть weak (mastery < 75) — выбирается он.""" from routers.orchestrator import get_next_lesson await set_user_level(conn, test_user, "A2") await create_test_lesson("test_skill") await insert_mastery(conn, test_user, "test_skill", mastery=50.0, retention="fresh", days_since=1) result = await get_next_lesson(conn, test_user) assert result["type"] == "review" assert result["reason"] == "weak_mastery" async def test_orchestrator_prefers_overdue_over_weak(conn, test_user, create_test_lesson, clean_mastery): """Если есть и overdue, и weak — overdue важнее.""" from routers.orchestrator import get_next_lesson await set_user_level(conn, test_user, "A2") await create_test_lesson("weak_skill", order=1) await create_test_lesson("overdue_skill", order=2) await insert_mastery(conn, test_user, "weak_skill", mastery=40.0, retention="fresh", days_since=1) await insert_mastery(conn, test_user, "overdue_skill", mastery=60.0, retention="overdue", days_since=20) result = await get_next_lesson(conn, test_user) assert result["reason"] == "overdue" async def test_orchestrator_weak_requires_min_attempts(conn, test_user, create_test_lesson, clean_mastery): """Weak skill требует минимум 2 попытки — если меньше, он не выбирается.""" from routers.orchestrator import get_next_lesson await set_user_level(conn, test_user, "A2") await create_test_lesson("test_skill") await insert_mastery(conn, test_user, "test_skill", mastery=50.0, retention="fresh", attempts=1) result = await get_next_lesson(conn, test_user) assert result["reason"] != "weak_mastery"