Refactor (Welle D): Lektions-Auflösung in ProgressService zentralisieren
- Neue ProgressService.resolve_current_lesson_index(): kapselt Resume- Fallback (UserSettings) + Index-Validierung + Sync des globalen Progress an einer benannten, getesteten Stelle. Rollen der drei Zustandsfelder dokumentiert (kein Schema-Bruch). - get_training_context() nutzt die Methode; die verstreute Heuristik und die Inline-'from models import db'-Importe entfallen. - 5 Unit-Tests für die Auflösungslogik (Resume, Zeiger-Vorrang, Normalisierung ungültiger/out-of-range Indizes). Gesamt: 96 Tests grün, ruff clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6502b7f826
commit
8eb7984468
3 changed files with 91 additions and 17 deletions
|
|
@ -57,6 +57,44 @@ class ProgressService:
|
|||
db.session.commit()
|
||||
logger.info(f"Globalen Lektionsindex auf {lesson_index} gesetzt")
|
||||
|
||||
@staticmethod
|
||||
def resolve_current_lesson_index(persisted_index: int, lesson_count: int) -> int:
|
||||
"""Ermittelt die aktuelle Lektion und synchronisiert den globalen Progress.
|
||||
|
||||
Rollen der drei Zustandsquellen:
|
||||
- Progress.current_text_index: Laufzeit-Zeiger (Quelle der Wahrheit)
|
||||
- UserSettings.current_lesson_index: persistenter Fallback (Resume)
|
||||
- LessonProgress: pro-Lektion-Fortschritt (orthogonal, kein Konflikt)
|
||||
|
||||
Logik: Ist der Laufzeit-Zeiger im Anfangszustand (0), wird die zuletzt
|
||||
persistierte Lektion als Resume-Fallback übernommen. Ein ungültiger
|
||||
Index wird auf 0 normalisiert. Das Ergebnis wird in den globalen
|
||||
Progress zurückgeschrieben.
|
||||
|
||||
Args:
|
||||
persisted_index: Zuletzt persistierte Lektion (aus UserSettings)
|
||||
lesson_count: Anzahl verfügbarer Lektionen
|
||||
|
||||
Returns:
|
||||
Der ermittelte, gültige Lektionsindex
|
||||
"""
|
||||
progress = ProgressService.get_or_create_global_progress()
|
||||
index = progress.current_text_index
|
||||
|
||||
# Resume: persistierte Lektion übernehmen, wenn Zeiger initial ist
|
||||
if index == 0 and persisted_index > 0:
|
||||
index = persisted_index
|
||||
|
||||
# Ungültigen Index normalisieren
|
||||
if not (0 <= index < lesson_count):
|
||||
index = 0
|
||||
|
||||
if index != progress.current_text_index:
|
||||
progress.current_text_index = index
|
||||
db.session.commit()
|
||||
|
||||
return index
|
||||
|
||||
@staticmethod
|
||||
def get_or_create_lesson_progress(lesson_index: int) -> LessonProgress:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -36,26 +36,15 @@ class TrainingService:
|
|||
Returns:
|
||||
Dictionary with all training data
|
||||
"""
|
||||
# Get global progress
|
||||
progress = self.progress_service.get_or_create_global_progress()
|
||||
current_lesson_index = progress.current_text_index
|
||||
|
||||
# Get user settings
|
||||
user_settings_dict = self.settings_service.get_settings_dict()
|
||||
|
||||
# Restore saved lesson if initial
|
||||
if current_lesson_index == 0 and user_settings_dict['current_lesson_index'] > 0:
|
||||
current_lesson_index = user_settings_dict['current_lesson_index']
|
||||
progress.current_text_index = current_lesson_index
|
||||
from models import db
|
||||
db.session.commit()
|
||||
|
||||
# Validate lesson index
|
||||
if not self.lesson_service.validate_lesson_index(current_lesson_index):
|
||||
current_lesson_index = 0
|
||||
progress.current_text_index = 0
|
||||
from models import db
|
||||
db.session.commit()
|
||||
# Aktuelle Lektion ermitteln und globalen Progress synchronisieren.
|
||||
# Kapselt Resume-Fallback + Validierung an einer Stelle.
|
||||
current_lesson_index = self.progress_service.resolve_current_lesson_index(
|
||||
persisted_index=user_settings_dict['current_lesson_index'],
|
||||
lesson_count=self.lesson_service.get_lesson_count(),
|
||||
)
|
||||
|
||||
# Get lesson info
|
||||
lesson_info = self.lesson_service.get_lesson_info(current_lesson_index)
|
||||
|
|
|
|||
|
|
@ -255,5 +255,52 @@ class TestProgressServiceIntegration(unittest.TestCase):
|
|||
self.assertEqual(lesson3.current_position, 30) # Unverändert
|
||||
|
||||
|
||||
class TestResolveCurrentLessonIndex(unittest.TestCase):
|
||||
"""Tests für die zentrale Lektions-Auflösung (Resume + Validierung)."""
|
||||
|
||||
def setUp(self):
|
||||
app.config['TESTING'] = True
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
|
||||
app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.app_context = app.app_context()
|
||||
self.app_context.push()
|
||||
db.create_all()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.remove()
|
||||
db.drop_all()
|
||||
self.app_context.pop()
|
||||
|
||||
def test_initial_pointer_no_persisted(self):
|
||||
# Zeiger 0, nichts persistiert -> 0
|
||||
self.assertEqual(
|
||||
ProgressService.resolve_current_lesson_index(persisted_index=0, lesson_count=5), 0
|
||||
)
|
||||
|
||||
def test_resume_from_persisted_when_initial(self):
|
||||
# Zeiger 0, persistiert 3 -> Resume auf 3 und in Progress geschrieben
|
||||
result = ProgressService.resolve_current_lesson_index(persisted_index=3, lesson_count=5)
|
||||
self.assertEqual(result, 3)
|
||||
self.assertEqual(Progress.query.first().current_text_index, 3)
|
||||
|
||||
def test_runtime_pointer_wins_over_persisted(self):
|
||||
# Laufender Zeiger 2 gewinnt gegenüber persistiert 4 (kein Resume)
|
||||
ProgressService.set_current_lesson_index(2)
|
||||
result = ProgressService.resolve_current_lesson_index(persisted_index=4, lesson_count=5)
|
||||
self.assertEqual(result, 2)
|
||||
|
||||
def test_invalid_index_normalized_to_zero(self):
|
||||
# Out-of-range Zeiger wird auf 0 normalisiert
|
||||
ProgressService.set_current_lesson_index(10)
|
||||
result = ProgressService.resolve_current_lesson_index(persisted_index=0, lesson_count=5)
|
||||
self.assertEqual(result, 0)
|
||||
self.assertEqual(Progress.query.first().current_text_index, 0)
|
||||
|
||||
def test_out_of_range_persisted_normalized(self):
|
||||
# Resume nimmt persistiert 99, Validierung normalisiert auf 0
|
||||
result = ProgressService.resolve_current_lesson_index(persisted_index=99, lesson_count=5)
|
||||
self.assertEqual(result, 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue