This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/tests/test_klausur_korrektur_api.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

347 lines
11 KiB
Python

"""
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"])