feat(lessons): implement comprehensive lesson configurator, session runner and AI debrief (Beta)
This commit is contained in:
1 parent
31e105b2c2
commit
718e9797eb
12 files changed
+4093
-1
No files matched your search
@@ -2359,6 +2359,145 @@ def test_listening_exercise_and_check():
|
||||
assert "/api/audio/" in card_ex["story_audio_url"]
|
||||
|
||||
|
||||
def test_lessons_presets_crud():
|
||||
# 1. Login user
|
||||
login_resp = client.post(
|
||||
"/api/login",
|
||||
json={"username": config.ADMIN_USERNAME, "password": config.ADMIN_PASSWORD},
|
||||
)
|
||||
assert login_resp.status_code == 200
|
||||
token = login_resp.json()["token"]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 2. Get presets
|
||||
resp = client.get("/api/lessons/presets", headers=headers)
|
||||
assert resp.status_code == 200
|
||||
presets = resp.json()["presets"]
|
||||
assert len(presets) >= 4
|
||||
preset_ids = [p["id"] for p in presets]
|
||||
assert "express" in preset_ids
|
||||
assert "balanced" in preset_ids
|
||||
|
||||
# 3. Create custom preset
|
||||
new_preset_payload = {
|
||||
"name": "Тестовый спринт",
|
||||
"icon": "⚡",
|
||||
"description": "Быстрый тест",
|
||||
"estimated_min": 12,
|
||||
"steps": [
|
||||
{"id": "step_vocab", "type": "vocab", "enabled": True, "count": 5},
|
||||
{"id": "step_ai_debrief", "type": "ai_debrief", "enabled": True},
|
||||
],
|
||||
}
|
||||
save_resp = client.post("/api/lessons/presets", json=new_preset_payload, headers=headers)
|
||||
assert save_resp.status_code == 200
|
||||
created = save_resp.json()["preset"]
|
||||
assert created["name"] == "Тестовый спринт"
|
||||
assert created["is_custom"] is True
|
||||
custom_id = created["custom_id"]
|
||||
|
||||
# Verify custom preset appears in list
|
||||
resp2 = client.get("/api/lessons/presets", headers=headers)
|
||||
assert resp2.status_code == 200
|
||||
presets2 = resp2.json()["presets"]
|
||||
assert any(p.get("custom_id") == custom_id for p in presets2)
|
||||
|
||||
# 4. Delete custom preset
|
||||
del_resp = client.delete(f"/api/lessons/presets/{custom_id}", headers=headers)
|
||||
assert del_resp.status_code == 200
|
||||
assert del_resp.json()["deleted"] is True
|
||||
|
||||
|
||||
def test_lessons_grammar_recommendations_and_articles():
|
||||
login_resp = client.post(
|
||||
"/api/login",
|
||||
json={"username": config.ADMIN_USERNAME, "password": config.ADMIN_PASSWORD},
|
||||
)
|
||||
token = login_resp.json()["token"]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# 1. Grammar recommendations
|
||||
rec_resp = client.get("/api/lessons/grammar-recommendations", headers=headers)
|
||||
assert rec_resp.status_code == 200
|
||||
recs = rec_resp.json()
|
||||
assert "all_units" in recs
|
||||
assert len(recs["all_units"]) >= 15
|
||||
assert "weakest_unit" in recs
|
||||
assert "previous_unit" in recs
|
||||
assert "next_unit" in recs
|
||||
|
||||
# 2. Articles
|
||||
art_resp = client.get("/api/lessons/articles", headers=headers)
|
||||
assert art_resp.status_code == 200
|
||||
articles = art_resp.json()["articles"]
|
||||
assert len(articles) >= 8
|
||||
first_art = articles[0]
|
||||
assert "title" in first_art
|
||||
assert "paragraphs" in first_art
|
||||
assert "paragraphs_ru" in first_art
|
||||
assert "vocabulary_highlights" in first_art
|
||||
|
||||
|
||||
def test_lessons_debrief_and_history():
|
||||
login_resp = client.post(
|
||||
"/api/login",
|
||||
json={"username": config.ADMIN_USERNAME, "password": config.ADMIN_PASSWORD},
|
||||
)
|
||||
token = login_resp.json()["token"]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# Post session debrief
|
||||
debrief_payload = {
|
||||
"preset_name": "⚡ Утренний экспресс (10 мин)",
|
||||
"duration_seconds": 620,
|
||||
"total_score": 85,
|
||||
"steps": [
|
||||
{"type": "vocab", "title": "Вспоминаем слова", "score": 90, "correct": 9, "total": 10},
|
||||
{"type": "grammar_review", "title": "Закрепление грамматики", "score": 80, "correct": 4, "total": 5},
|
||||
{"type": "listening", "title": "Аудирование", "score": 100, "correct": 1, "total": 1},
|
||||
],
|
||||
"errors": [
|
||||
{
|
||||
"type": "grammar",
|
||||
"unit_num": 1,
|
||||
"unit_title": "Present Continuous",
|
||||
"formula": "S + am/is/are + V-ing",
|
||||
"user_answer": "sing",
|
||||
"correct_answer": "are singing",
|
||||
"explanation": "Слово-маркер Listen указывает на момент речи",
|
||||
},
|
||||
{
|
||||
"type": "vocab",
|
||||
"term": "reluctant",
|
||||
"translation": "неохотный",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
debrief_resp = client.post("/api/lessons/debrief", json=debrief_payload, headers=headers)
|
||||
assert debrief_resp.status_code == 200
|
||||
data = debrief_resp.json()
|
||||
assert "session_id" in data
|
||||
assert "ai_report" in data
|
||||
report = data["ai_report"]
|
||||
assert report["grade"] in ["A+", "A", "B", "C"]
|
||||
assert "headline" in report
|
||||
assert "strengths" in report
|
||||
assert "error_patterns" in report
|
||||
assert "tomorrow_focus" in report
|
||||
|
||||
# Check history
|
||||
hist_resp = client.get("/api/lessons/history", headers=headers)
|
||||
assert hist_resp.status_code == 200
|
||||
hist = hist_resp.json()
|
||||
assert hist["total_lessons"] >= 1
|
||||
assert len(hist["history"]) >= 1
|
||||
last_session = hist["history"][0]
|
||||
assert last_session["preset_name"] == "⚡ Утренний экспресс (10 мин)"
|
||||
assert last_session["errors_count"] == 2
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user