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_correction_api.py
Benjamin Admin 21a844cb8a 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

544 lines
17 KiB
Python

"""
Tests für Correction API.
Testet den Korrektur-Workflow:
- Upload
- OCR
- Analyse
- Export
"""
import pytest
import io
from unittest.mock import patch, MagicMock
from fastapi.testclient import TestClient
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from main import app
client = TestClient(app)
class TestCorrectionCreate:
"""Tests für Korrektur-Erstellung."""
def test_create_correction_success(self):
"""Testet erfolgreiche Erstellung."""
response = client.post(
"/api/corrections/",
json={
"student_id": "student-001",
"student_name": "Max Mustermann",
"class_name": "7a",
"exam_title": "Mathematik Test 1",
"subject": "Mathematik",
"max_points": 50.0
}
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["correction"]["student_name"] == "Max Mustermann"
assert data["correction"]["status"] == "uploaded"
assert data["correction"]["max_points"] == 50.0
def test_create_correction_default_points(self):
"""Testet Erstellung mit Standard-Punktzahl."""
response = client.post(
"/api/corrections/",
json={
"student_id": "student-002",
"student_name": "Anna Schmidt",
"class_name": "7a",
"exam_title": "Deutsch Aufsatz",
"subject": "Deutsch"
}
)
assert response.status_code == 200
data = response.json()
assert data["correction"]["max_points"] == 100.0
class TestCorrectionUpload:
"""Tests für Datei-Upload."""
def _create_correction(self):
"""Hilfsmethode zum Erstellen einer Korrektur."""
response = client.post(
"/api/corrections/",
json={
"student_id": "student-test",
"student_name": "Test Student",
"class_name": "Test",
"exam_title": "Test Exam",
"subject": "Test"
}
)
return response.json()["correction"]["id"]
def test_upload_pdf_success(self):
"""Testet PDF-Upload."""
correction_id = self._create_correction()
# Erstelle Mock-PDF
pdf_content = b"%PDF-1.4 test content"
files = {"file": ("test.pdf", io.BytesIO(pdf_content), "application/pdf")}
with patch("correction_api._process_ocr"):
response = client.post(
f"/api/corrections/{correction_id}/upload",
files=files
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["correction"]["file_path"] is not None
def test_upload_image_success(self):
"""Testet Bild-Upload."""
correction_id = self._create_correction()
# Erstelle Mock-PNG
png_content = b"\x89PNG\r\n\x1a\n test content"
files = {"file": ("test.png", io.BytesIO(png_content), "image/png")}
with patch("correction_api._process_ocr"):
response = client.post(
f"/api/corrections/{correction_id}/upload",
files=files
)
assert response.status_code == 200
def test_upload_invalid_format(self):
"""Testet Ablehnung ungültiger Formate."""
correction_id = self._create_correction()
files = {"file": ("test.txt", io.BytesIO(b"text"), "text/plain")}
response = client.post(
f"/api/corrections/{correction_id}/upload",
files=files
)
assert response.status_code == 400
assert "Ungültiges Dateiformat" in response.json()["detail"]
def test_upload_not_found(self):
"""Testet Upload für nicht existierende Korrektur."""
files = {"file": ("test.pdf", io.BytesIO(b"content"), "application/pdf")}
response = client.post(
"/api/corrections/nonexistent/upload",
files=files
)
assert response.status_code == 404
class TestCorrectionRetrieval:
"""Tests für Korrektur-Abruf."""
def test_get_correction(self):
"""Testet Abrufen einer Korrektur."""
# Erstelle Korrektur
create_response = client.post(
"/api/corrections/",
json={
"student_id": "get-test",
"student_name": "Get Test",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = create_response.json()["correction"]["id"]
# Rufe ab
response = client.get(f"/api/corrections/{correction_id}")
assert response.status_code == 200
data = response.json()
assert data["correction"]["id"] == correction_id
def test_get_correction_not_found(self):
"""Testet Fehler bei nicht vorhandener Korrektur."""
response = client.get("/api/corrections/nonexistent")
assert response.status_code == 404
def test_list_corrections(self):
"""Testet Auflisten von Korrekturen."""
# Erstelle einige Korrekturen
for i in range(3):
client.post(
"/api/corrections/",
json={
"student_id": f"list-{i}",
"student_name": f"Student {i}",
"class_name": "ListTest",
"exam_title": "Test",
"subject": "Test"
}
)
response = client.get("/api/corrections/?class_name=ListTest")
assert response.status_code == 200
data = response.json()
assert data["total"] >= 3
def test_list_corrections_filter_status(self):
"""Testet Filterung nach Status."""
response = client.get("/api/corrections/?status=completed")
assert response.status_code == 200
data = response.json()
# Alle zurückgegebenen Korrekturen sollten "completed" Status haben
for c in data["corrections"]:
if c.get("status"): # Falls vorhanden
assert c["status"] == "completed"
class TestCorrectionAnalysis:
"""Tests für Korrektur-Analyse."""
def _create_correction_with_text(self):
"""Erstellt Korrektur mit OCR-Text."""
response = client.post(
"/api/corrections/",
json={
"student_id": "analyze-test",
"student_name": "Analyze Test",
"class_name": "Test",
"exam_title": "Test",
"subject": "Mathematik",
"max_points": 100.0
}
)
correction_id = response.json()["correction"]["id"]
# Simuliere OCR-Ergebnis durch direktes Setzen
from correction_api import _corrections, CorrectionStatus
correction = _corrections[correction_id]
correction.extracted_text = """
Aufgabe 1: Die Antwort ist 42.
Aufgabe 2: Hier ist meine Lösung für die Gleichung.
Aufgabe 3: Das Ergebnis beträgt 15.
"""
correction.status = CorrectionStatus.OCR_COMPLETE
_corrections[correction_id] = correction
return correction_id
def test_analyze_correction(self):
"""Testet Analyse einer Korrektur."""
correction_id = self._create_correction_with_text()
response = client.post(
f"/api/corrections/{correction_id}/analyze"
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["evaluations"]) > 0
assert "suggested_grade" in data
assert "ai_feedback" in data
def test_analyze_with_expected_answers(self):
"""Testet Analyse mit Musterlösung."""
correction_id = self._create_correction_with_text()
expected = {
"1": "42",
"2": "Gleichung",
"3": "15"
}
response = client.post(
f"/api/corrections/{correction_id}/analyze",
json=expected
)
assert response.status_code == 200
data = response.json()
# Mit passender Musterlösung sollten einige Antworten korrekt sein
correct_count = sum(1 for e in data["evaluations"] if e["is_correct"])
assert correct_count > 0
def test_analyze_wrong_status(self):
"""Testet Analyse bei falschem Status."""
# Neue Korrektur ohne OCR
response = client.post(
"/api/corrections/",
json={
"student_id": "wrong-status",
"student_name": "Wrong Status",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = response.json()["correction"]["id"]
# Analyse ohne vorherige OCR
response = client.post(f"/api/corrections/{correction_id}/analyze")
assert response.status_code == 400
class TestCorrectionUpdate:
"""Tests für Korrektur-Aktualisierung."""
def test_update_correction(self):
"""Testet Aktualisierung einer Korrektur."""
# Erstelle Korrektur
response = client.post(
"/api/corrections/",
json={
"student_id": "update-test",
"student_name": "Update Test",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = response.json()["correction"]["id"]
# Aktualisiere
response = client.put(
f"/api/corrections/{correction_id}",
json={
"grade": "2",
"total_points": 85.0,
"teacher_notes": "Gute Arbeit!"
}
)
assert response.status_code == 200
data = response.json()
assert data["correction"]["grade"] == "2"
assert data["correction"]["total_points"] == 85.0
assert data["correction"]["teacher_notes"] == "Gute Arbeit!"
def test_complete_correction(self):
"""Testet Abschluss einer Korrektur."""
# Erstelle Korrektur
response = client.post(
"/api/corrections/",
json={
"student_id": "complete-test",
"student_name": "Complete Test",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = response.json()["correction"]["id"]
# Schließe ab
response = client.post(f"/api/corrections/{correction_id}/complete")
assert response.status_code == 200
data = response.json()
assert data["correction"]["status"] == "completed"
class TestCorrectionDelete:
"""Tests für Korrektur-Löschung."""
def test_delete_correction(self):
"""Testet Löschen einer Korrektur."""
# Erstelle Korrektur
response = client.post(
"/api/corrections/",
json={
"student_id": "delete-test",
"student_name": "Delete Test",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = response.json()["correction"]["id"]
# Lösche
response = client.delete(f"/api/corrections/{correction_id}")
assert response.status_code == 200
assert response.json()["status"] == "deleted"
# Prüfe dass gelöscht
response = client.get(f"/api/corrections/{correction_id}")
assert response.status_code == 404
def test_delete_not_found(self):
"""Testet Fehler beim Löschen nicht existierender Korrektur."""
response = client.delete("/api/corrections/nonexistent")
assert response.status_code == 404
class TestClassSummary:
"""Tests für Klassen-Zusammenfassung."""
def _create_completed_corrections(self, class_name: str, count: int):
"""Erstellt abgeschlossene Korrekturen für eine Klasse."""
from correction_api import _corrections, CorrectionStatus
import uuid
from datetime import datetime
for i in range(count):
response = client.post(
"/api/corrections/",
json={
"student_id": f"summary-{i}",
"student_name": f"Student {i}",
"class_name": class_name,
"exam_title": "Summary Test",
"subject": "Test",
"max_points": 100.0
}
)
correction_id = response.json()["correction"]["id"]
# Setze als completed mit Punkten
correction = _corrections[correction_id]
correction.status = CorrectionStatus.COMPLETED
correction.total_points = 70 + i * 5 # 70, 75, 80, ...
correction.percentage = correction.total_points
correction.grade = str(3 - i // 2) # Verschiedene Noten
_corrections[correction_id] = correction
def test_class_summary(self):
"""Testet Klassen-Zusammenfassung."""
class_name = "SummaryTestClass"
self._create_completed_corrections(class_name, 3)
response = client.get(f"/api/corrections/class/{class_name}/summary")
assert response.status_code == 200
data = response.json()
assert data["class_name"] == class_name
assert data["total_students"] == 3
assert "average_percentage" in data
assert "grade_distribution" in data
assert len(data["corrections"]) == 3
def test_class_summary_empty(self):
"""Testet Zusammenfassung für leere Klasse."""
response = client.get("/api/corrections/class/EmptyClass/summary")
assert response.status_code == 200
data = response.json()
assert data["total_students"] == 0
assert data["average_percentage"] == 0
class TestGradeCalculation:
"""Tests für Notenberechnung."""
def test_grade_1(self):
"""Testet Note 1 (>=92%)."""
from correction_api import _calculate_grade
assert _calculate_grade(92) == "1"
assert _calculate_grade(100) == "1"
def test_grade_2(self):
"""Testet Note 2 (81-91%)."""
from correction_api import _calculate_grade
assert _calculate_grade(81) == "2"
assert _calculate_grade(91) == "2"
def test_grade_3(self):
"""Testet Note 3 (67-80%)."""
from correction_api import _calculate_grade
assert _calculate_grade(67) == "3"
assert _calculate_grade(80) == "3"
def test_grade_4(self):
"""Testet Note 4 (50-66%)."""
from correction_api import _calculate_grade
assert _calculate_grade(50) == "4"
assert _calculate_grade(66) == "4"
def test_grade_5(self):
"""Testet Note 5 (30-49%)."""
from correction_api import _calculate_grade
assert _calculate_grade(30) == "5"
assert _calculate_grade(49) == "5"
def test_grade_6(self):
"""Testet Note 6 (<30%)."""
from correction_api import _calculate_grade
assert _calculate_grade(29) == "6"
assert _calculate_grade(0) == "6"
class TestOCRRetry:
"""Tests für OCR-Wiederholung."""
def test_retry_ocr(self):
"""Testet OCR-Wiederholung."""
# Erstelle Korrektur mit Datei
response = client.post(
"/api/corrections/",
json={
"student_id": "retry-test",
"student_name": "Retry Test",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = response.json()["correction"]["id"]
# Setze file_path manuell (simuliert Upload)
from correction_api import _corrections
import tempfile
import os
# Erstelle temp file
fd, path = tempfile.mkstemp(suffix=".pdf")
os.write(fd, b"%PDF-1.4 test")
os.close(fd)
correction = _corrections[correction_id]
correction.file_path = path
_corrections[correction_id] = correction
# Retry OCR
with patch("correction_api._process_ocr"):
response = client.post(f"/api/corrections/{correction_id}/ocr/retry")
assert response.status_code == 200
# Cleanup
os.remove(path)
def test_retry_ocr_no_file(self):
"""Testet Fehler bei OCR-Retry ohne Datei."""
response = client.post(
"/api/corrections/",
json={
"student_id": "retry-no-file",
"student_name": "No File",
"class_name": "Test",
"exam_title": "Test",
"subject": "Test"
}
)
correction_id = response.json()["correction"]["id"]
response = client.post(f"/api/corrections/{correction_id}/ocr/retry")
assert response.status_code == 400
assert "Keine Datei" in response.json()["detail"]