""" Tests fuer die Klausur-Korrektur API Tests fuer: - Klausuren erstellen, abrufen, aktualisieren, loeschen - Text-Quellen hinzufuegen und verwalten - Schuelerarbeiten hochladen - Bewertung und Gutachten - 15-Punkte-Notensystem """ import pytest from unittest.mock import MagicMock, patch from datetime import datetime # Import des zu testenden Moduls import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from klausur_korrektur_api import ( router, AbiturKlausur, KlausurModus, KlausurStatus, TextSource, TextSourceType, TextSourceStatus, StudentKlausur, StudentKlausurStatus, Erwartungshorizont, Aufgabe, CriterionScore, Gutachten, ExaminerResult, DEFAULT_CRITERIA, GRADE_THRESHOLDS, calculate_15_point_grade, klausuren_db, ) class TestGradeCalculation: """Tests fuer die Notenberechnung im 15-Punkte-System.""" def test_calculate_15_point_grade_perfect(self): """100% sollte 15 Punkte ergeben.""" assert calculate_15_point_grade(100.0) == 15 def test_calculate_15_point_grade_95(self): """95% sollte 15 Punkte ergeben (1+).""" assert calculate_15_point_grade(95.0) == 15 def test_calculate_15_point_grade_90(self): """90% sollte 14 Punkte ergeben (1).""" assert calculate_15_point_grade(90.0) == 14 def test_calculate_15_point_grade_85(self): """85% sollte 13 Punkte ergeben (1-).""" assert calculate_15_point_grade(85.0) == 13 def test_calculate_15_point_grade_50(self): """50% sollte 6 Punkte ergeben (4+).""" assert calculate_15_point_grade(50.0) == 6 def test_calculate_15_point_grade_45(self): """45% sollte 5 Punkte ergeben (4).""" assert calculate_15_point_grade(45.0) == 5 def test_calculate_15_point_grade_below_threshold(self): """19% sollte 0 Punkte ergeben (6).""" assert calculate_15_point_grade(19.0) == 0 def test_calculate_15_point_grade_zero(self): """0% sollte 0 Punkte ergeben.""" assert calculate_15_point_grade(0.0) == 0 def test_calculate_15_point_grade_boundary_values(self): """Test aller Grenzwerte.""" expected_results = [ (95, 15), (94.9, 14), (90, 14), (89.9, 13), (85, 13), (84.9, 12), (80, 12), (79.9, 11), (75, 11), (74.9, 10), (70, 10), (69.9, 9), (65, 9), (64.9, 8), (60, 8), (59.9, 7), (55, 7), (54.9, 6), (50, 6), (49.9, 5), (45, 5), (44.9, 4), (40, 4), (39.9, 3), (33, 3), (32.9, 2), (27, 2), (26.9, 1), (20, 1), (19.9, 0), ] for percentage, expected_points in expected_results: result = calculate_15_point_grade(percentage) assert result == expected_points, f"Expected {expected_points} for {percentage}%, got {result}" class TestGradeThresholds: """Tests fuer die Notenschwellen.""" def test_all_thresholds_present(self): """Alle 16 Notenpunkte (0-15) sollten definiert sein.""" assert len(GRADE_THRESHOLDS) == 16 for i in range(16): assert i in GRADE_THRESHOLDS def test_thresholds_descending(self): """Schwellen sollten von 15 nach 0 absteigend sein.""" prev_threshold = 100 for points in range(15, -1, -1): threshold = GRADE_THRESHOLDS[points] assert threshold < prev_threshold or (points == 15 and threshold <= prev_threshold) prev_threshold = threshold class TestDefaultCriteria: """Tests fuer die Standard-Bewertungskriterien.""" def test_criteria_weights_sum_to_one(self): """Gewichte aller Kriterien sollten 1.0 ergeben.""" total_weight = sum(c["weight"] for c in DEFAULT_CRITERIA.values()) assert abs(total_weight - 1.0) < 0.001 def test_required_criteria_present(self): """Alle erforderlichen Kriterien sollten vorhanden sein.""" required = ["rechtschreibung", "grammatik", "inhalt", "struktur", "stil"] for criterion in required: assert criterion in DEFAULT_CRITERIA def test_inhalt_has_highest_weight(self): """Inhalt sollte das hoechste Gewicht haben.""" inhalt_weight = DEFAULT_CRITERIA["inhalt"]["weight"] for name, criterion in DEFAULT_CRITERIA.items(): if name != "inhalt": assert criterion["weight"] <= inhalt_weight class TestKlausurModels: """Tests fuer die Datenmodelle.""" def test_create_abitur_klausur(self): """Eine neue Klausur sollte erstellt werden koennen.""" now = datetime.now() klausur = AbiturKlausur( id="test-123", title="Deutsch LK Q4", subject="deutsch", modus=KlausurModus.LANDES_ABITUR, year=2025, semester="Q4", kurs="LK", class_id=None, status=KlausurStatus.DRAFT, text_sources=[], erwartungshorizont=None, students=[], created_at=now, updated_at=now ) assert klausur.id == "test-123" assert klausur.modus == KlausurModus.LANDES_ABITUR def test_create_text_source(self): """Eine Textquelle sollte erstellt werden koennen.""" source = TextSource( id="src-1", source_type=TextSourceType.NIBIS, title="Kafka - Die Verwandlung", author="Franz Kafka", content="Als Gregor Samsa eines Morgens...", nibis_id=None, license_status=TextSourceStatus.VERIFIED, license_info={"license": "PD"}, created_at=datetime.now() ) assert source.license_status == TextSourceStatus.VERIFIED def test_student_klausur_status_workflow(self): """Der Status-Workflow einer Schuelerarbeit sollte korrekt sein.""" statuses = list(StudentKlausurStatus) expected_order = [ StudentKlausurStatus.UPLOADED, StudentKlausurStatus.OCR_PROCESSING, StudentKlausurStatus.OCR_COMPLETE, StudentKlausurStatus.ANALYZING, StudentKlausurStatus.FIRST_EXAMINER, StudentKlausurStatus.SECOND_EXAMINER, StudentKlausurStatus.COMPLETED, StudentKlausurStatus.ERROR, # Error state can occur at any point ] assert statuses == expected_order class TestCriterionScore: """Tests fuer die Bewertungskriterien-Punkte.""" def test_create_criterion_score(self): """Ein Kriterium-Score sollte erstellt werden koennen.""" score = CriterionScore( score=85, weight=0.4, annotations=["Gute Argumentation"], comment="Insgesamt gut", ai_suggestions=["Mehr Beispiele hinzufuegen"] ) assert score.score == 85 assert score.weight == 0.4 def test_weighted_score_calculation(self): """Der gewichtete Score sollte korrekt berechnet werden.""" score = CriterionScore( score=80, weight=0.4, annotations=[], comment="", ai_suggestions=[] ) weighted = score.score * score.weight assert weighted == 32.0 class TestExpectationHorizon: """Tests fuer den Erwartungshorizont.""" def test_create_aufgabe(self): """Eine Aufgabe sollte erstellt werden koennen.""" aufgabe = Aufgabe( id="aufg-1", nummer="1a", text="Analysieren Sie das Gedicht.", operator="analysieren", anforderungsbereich=2, erwartete_leistungen=["Epoche erkennen", "Stilmittel benennen"], punkte=20 ) assert aufgabe.anforderungsbereich == 2 assert aufgabe.punkte == 20 def test_create_erwartungshorizont(self): """Ein Erwartungshorizont sollte erstellt werden koennen.""" aufgaben = [ Aufgabe(id="a1", nummer="1", text="Aufgabe 1", operator="analysieren", anforderungsbereich=2, erwartete_leistungen=["Test"], punkte=30), Aufgabe(id="a2", nummer="2", text="Aufgabe 2", operator="erlaeutern", anforderungsbereich=2, erwartete_leistungen=["Test2"], punkte=30), Aufgabe(id="a3", nummer="3", text="Aufgabe 3", operator="beurteilen", anforderungsbereich=3, erwartete_leistungen=["Test3"], punkte=40), ] ewh = Erwartungshorizont( id="ewh-1", aufgaben=aufgaben, max_points=100, hinweise="Allgemeine Hinweise", generated=False, created_at=datetime.now() ) assert ewh.max_points == 100 assert len(ewh.aufgaben) == 3 total_points = sum(a.punkte for a in ewh.aufgaben) assert total_points == 100 class TestKlausurDB: """Tests fuer die In-Memory Datenbank.""" def setup_method(self): """Setup vor jedem Test - leere die DB.""" klausuren_db.clear() def test_empty_db(self): """Eine leere DB sollte leer sein.""" assert len(klausuren_db) == 0 def test_add_klausur_to_db(self): """Eine Klausur sollte zur DB hinzugefuegt werden koennen.""" now = datetime.now() klausur = AbiturKlausur( id="test-1", title="Test Klausur", subject="deutsch", modus=KlausurModus.VORABITUR, year=2025, semester="Q3", kurs="GK", class_id=None, status=KlausurStatus.DRAFT, text_sources=[], erwartungshorizont=None, students=[], created_at=now, updated_at=now ) klausuren_db["test-1"] = klausur assert "test-1" in klausuren_db assert klausuren_db["test-1"].title == "Test Klausur" class TestKlausurModus: """Tests fuer die Klausur-Modi.""" def test_landes_abitur_mode(self): """Landes-Abitur Modus sollte existieren.""" assert KlausurModus.LANDES_ABITUR.value == "landes_abitur" def test_vorabitur_mode(self): """Vorabitur Modus sollte existieren.""" assert KlausurModus.VORABITUR.value == "vorabitur" class TestTextSourceStatus: """Tests fuer den TextSource-Status.""" def test_pending_status(self): """Pending Status sollte existieren.""" assert TextSourceStatus.PENDING.value == "pending" def test_verified_status(self): """Verified Status sollte existieren.""" assert TextSourceStatus.VERIFIED.value == "verified" def test_rejected_status(self): """Rejected Status sollte existieren.""" assert TextSourceStatus.REJECTED.value == "rejected" if __name__ == "__main__": pytest.main([__file__, "-v"])