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