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>
438 lines
15 KiB
Python
438 lines
15 KiB
Python
"""
|
|
E2E-Tests für Frontend-Module Integration.
|
|
|
|
Testet die Verbindung zwischen Frontend-Modulen und ihren APIs:
|
|
- Worksheets Frontend → Worksheets API
|
|
- Correction Frontend → Corrections API
|
|
- Letters Frontend → Letters API
|
|
- Companion Frontend → State Engine API
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
import sys
|
|
import json
|
|
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from main import app
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
class TestWorksheetsIntegration:
|
|
"""E2E-Tests für Worksheets Frontend → API Integration."""
|
|
|
|
def test_generate_mc_endpoint(self):
|
|
"""Testet MC-Generierung wie Frontend sie aufruft."""
|
|
response = client.post(
|
|
"/api/worksheets/generate/multiple-choice",
|
|
json={
|
|
"source_text": "Die Fotosynthese ist ein Prozess, bei dem Pflanzen Lichtenergie nutzen, um aus Kohlendioxid und Wasser Zucker und Sauerstoff herzustellen. Dieser Prozess findet in den Chloroplasten statt.",
|
|
"num_questions": 3,
|
|
"difficulty": "medium",
|
|
"topic": "Fotosynthese",
|
|
"subject": "Biologie"
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "content" in data
|
|
assert data["content"]["content_type"] == "multiple_choice"
|
|
assert "questions" in data["content"]["data"]
|
|
assert len(data["content"]["data"]["questions"]) >= 1
|
|
|
|
def test_generate_cloze_endpoint(self):
|
|
"""Testet Lückentext-Generierung."""
|
|
response = client.post(
|
|
"/api/worksheets/generate/cloze",
|
|
json={
|
|
"source_text": "Berlin ist die Hauptstadt von Deutschland. Die Stadt hat etwa 3,6 Millionen Einwohner und liegt an der Spree.",
|
|
"num_gaps": 3,
|
|
"gap_type": "word",
|
|
"hint_type": "first_letter"
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["content"]["content_type"] == "cloze"
|
|
|
|
def test_generate_mindmap_endpoint(self):
|
|
"""Testet Mindmap-Generierung."""
|
|
response = client.post(
|
|
"/api/worksheets/generate/mindmap",
|
|
json={
|
|
"source_text": "Das Mittelalter war eine Epoche der europäischen Geschichte. Es begann etwa 500 n. Chr. und endete um 1500. Wichtige Aspekte waren das Lehnswesen, die Kirche und das Rittertum.",
|
|
"max_branches": 4,
|
|
"depth": 2
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["content"]["content_type"] == "mindmap"
|
|
# API returns 'mermaid' key (not 'mermaid_code')
|
|
assert "mermaid" in data["content"]["data"] or "mermaid_code" in data["content"]["data"]
|
|
|
|
def test_generate_quiz_endpoint(self):
|
|
"""Testet Quiz-Generierung."""
|
|
response = client.post(
|
|
"/api/worksheets/generate/quiz",
|
|
json={
|
|
"source_text": "Der Wasserkreislauf beschreibt die kontinuierliche Bewegung des Wassers auf der Erde. Wasser verdunstet, bildet Wolken und fällt als Niederschlag.",
|
|
"num_items": 3,
|
|
"quiz_types": ["true_false"]
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert data["content"]["content_type"] == "quiz"
|
|
|
|
def test_generate_batch_endpoint(self):
|
|
"""Testet Batch-Generierung mehrerer Typen."""
|
|
response = client.post(
|
|
"/api/worksheets/generate/batch",
|
|
json={
|
|
"source_text": "Python ist eine Programmiersprache. Sie wurde 1991 von Guido van Rossum entwickelt. Python nutzt dynamische Typisierung.",
|
|
"content_types": ["multiple_choice", "cloze"]
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert len(data["contents"]) == 2
|
|
|
|
|
|
class TestCorrectionsIntegration:
|
|
"""E2E-Tests für Correction Frontend → API Integration."""
|
|
|
|
def test_create_correction_workflow(self):
|
|
"""Testet kompletten Korrektur-Workflow."""
|
|
# Step 1: Korrektur erstellen
|
|
create_response = client.post(
|
|
"/api/corrections/",
|
|
json={
|
|
"student_id": "test-student-e2e",
|
|
"student_name": "Test Schueler",
|
|
"class_name": "10a",
|
|
"exam_title": "E2E-Testklausur",
|
|
"subject": "Mathematik",
|
|
"max_points": 100
|
|
}
|
|
)
|
|
|
|
assert create_response.status_code == 200
|
|
create_data = create_response.json()
|
|
assert create_data["success"] is True
|
|
correction_id = create_data["correction"]["id"]
|
|
|
|
# Step 2: Status abrufen
|
|
get_response = client.get(f"/api/corrections/{correction_id}")
|
|
assert get_response.status_code == 200
|
|
get_data = get_response.json()
|
|
assert get_data["correction"]["status"] == "uploaded"
|
|
|
|
# Step 3: Korrektur aktualisieren (simuliert Review)
|
|
update_response = client.put(
|
|
f"/api/corrections/{correction_id}",
|
|
json={
|
|
"total_points": 85,
|
|
"grade": "2",
|
|
"teacher_notes": "Gute Arbeit!",
|
|
"status": "reviewing"
|
|
}
|
|
)
|
|
|
|
assert update_response.status_code == 200
|
|
update_data = update_response.json()
|
|
assert update_data["correction"]["total_points"] == 85
|
|
assert update_data["correction"]["grade"] == "2"
|
|
|
|
# Step 4: Abschließen
|
|
complete_response = client.post(f"/api/corrections/{correction_id}/complete")
|
|
assert complete_response.status_code == 200
|
|
|
|
# Step 5: Finalen Status prüfen
|
|
final_response = client.get(f"/api/corrections/{correction_id}")
|
|
assert final_response.status_code == 200
|
|
assert final_response.json()["correction"]["status"] == "completed"
|
|
|
|
# Cleanup
|
|
client.delete(f"/api/corrections/{correction_id}")
|
|
|
|
def test_list_corrections(self):
|
|
"""Testet Korrektur-Liste."""
|
|
response = client.get("/api/corrections/")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "corrections" in data
|
|
assert "total" in data
|
|
|
|
|
|
class TestLettersIntegration:
|
|
"""E2E-Tests für Letters Frontend → API Integration."""
|
|
|
|
def test_create_letter_workflow(self):
|
|
"""Testet kompletten Brief-Workflow."""
|
|
# Step 1: Brief erstellen
|
|
create_response = client.post(
|
|
"/api/letters/",
|
|
json={
|
|
"recipient_name": "Familie Testmann",
|
|
"recipient_address": "Teststr. 1",
|
|
"student_name": "Max Testmann",
|
|
"student_class": "7a",
|
|
"subject": "E2E-Testbrief",
|
|
"content": "Sehr geehrte Eltern, dies ist ein Testbrief.",
|
|
"letter_type": "general",
|
|
"tone": "professional",
|
|
"teacher_name": "Frau Test",
|
|
"teacher_title": "Klassenlehrerin"
|
|
}
|
|
)
|
|
|
|
assert create_response.status_code == 200
|
|
letter_data = create_response.json()
|
|
letter_id = letter_data["id"]
|
|
assert letter_data["status"] == "draft"
|
|
|
|
# Step 2: Brief abrufen
|
|
get_response = client.get(f"/api/letters/{letter_id}")
|
|
assert get_response.status_code == 200
|
|
assert get_response.json()["student_name"] == "Max Testmann"
|
|
|
|
# Step 3: Brief aktualisieren
|
|
update_response = client.put(
|
|
f"/api/letters/{letter_id}",
|
|
json={
|
|
"content": "Sehr geehrte Eltern, dies ist ein aktualisierter Testbrief."
|
|
}
|
|
)
|
|
assert update_response.status_code == 200
|
|
|
|
# Cleanup
|
|
client.delete(f"/api/letters/{letter_id}")
|
|
|
|
def test_improve_letter_content(self):
|
|
"""Testet GFK-Verbesserung."""
|
|
response = client.post(
|
|
"/api/letters/improve",
|
|
json={
|
|
"content": "Ihr Kind muss sich verbessern. Es ist immer zu spät.",
|
|
"communication_type": "general",
|
|
"tone": "professional"
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "improved_content" in data
|
|
assert "gfk_score" in data
|
|
assert "changes" in data
|
|
|
|
def test_list_letter_types(self):
|
|
"""Testet Abruf der Brieftypen."""
|
|
response = client.get("/api/letters/types")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "types" in data
|
|
assert len(data["types"]) >= 5
|
|
|
|
def test_list_letter_tones(self):
|
|
"""Testet Abruf der Tonalitäten."""
|
|
response = client.get("/api/letters/tones")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "tones" in data
|
|
assert len(data["tones"]) >= 4
|
|
|
|
|
|
class TestStateEngineIntegration:
|
|
"""E2E-Tests für Companion Frontend → State Engine API Integration."""
|
|
|
|
def test_get_dashboard(self):
|
|
"""Testet Dashboard-Abruf wie Companion-Mode."""
|
|
response = client.get("/api/state/dashboard?teacher_id=e2e-test-teacher")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "context" in data
|
|
assert "suggestions" in data
|
|
assert "stats" in data
|
|
assert "progress" in data
|
|
assert "phases" in data
|
|
|
|
def test_get_suggestions(self):
|
|
"""Testet Vorschläge-Abruf."""
|
|
response = client.get("/api/state/suggestions?teacher_id=e2e-test-teacher")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "suggestions" in data
|
|
assert "current_phase" in data
|
|
assert "priority_counts" in data
|
|
|
|
def test_complete_milestone_workflow(self):
|
|
"""Testet Meilenstein-Abschluss-Workflow."""
|
|
teacher_id = "e2e-milestone-test"
|
|
|
|
# Meilenstein abschließen
|
|
response = client.post(
|
|
f"/api/state/milestone?teacher_id={teacher_id}",
|
|
json={"milestone": "e2e_test_milestone"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "e2e_test_milestone" in data["completed_milestones"]
|
|
|
|
# Prüfen ob Meilenstein gespeichert
|
|
context_response = client.get(f"/api/state/context?teacher_id={teacher_id}")
|
|
assert context_response.status_code == 200
|
|
context = context_response.json()["context"]
|
|
assert "e2e_test_milestone" in context["completed_milestones"]
|
|
|
|
def test_phase_transition(self):
|
|
"""Testet Phasenübergang."""
|
|
teacher_id = "e2e-transition-test"
|
|
|
|
# Erst alle Onboarding-Meilensteine abschließen
|
|
for milestone in ["school_select", "consent_accept", "profile_complete"]:
|
|
resp = client.post(
|
|
f"/api/state/milestone?teacher_id={teacher_id}",
|
|
json={"milestone": milestone}
|
|
)
|
|
# Verify milestones are being accepted
|
|
assert resp.status_code == 200
|
|
|
|
# Transition zu school_year_start sollte möglich sein
|
|
response = client.post(
|
|
f"/api/state/transition?teacher_id={teacher_id}",
|
|
json={"target_phase": "school_year_start"}
|
|
)
|
|
|
|
# Accept both 200 (success) and 400 (if conditions not met due to test isolation)
|
|
# In production, milestones persist; in tests, context may be reset between requests
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
else:
|
|
# Document test environment limitation
|
|
assert response.status_code == 400
|
|
# Verify it's a condition-related rejection, not a server error
|
|
data = response.json()
|
|
assert "detail" in data
|
|
|
|
def test_invalid_phase_transition(self):
|
|
"""Testet ungültigen Phasenübergang."""
|
|
response = client.post(
|
|
"/api/state/transition?teacher_id=e2e-invalid-test",
|
|
json={"target_phase": "archived"}
|
|
)
|
|
|
|
# Sollte 400 zurückgeben da direkter Sprung zu archived nicht erlaubt
|
|
assert response.status_code == 400
|
|
|
|
|
|
class TestCrossModuleIntegration:
|
|
"""Tests für modul-übergreifende Funktionalität."""
|
|
|
|
def test_studio_html_renders(self):
|
|
"""Testet dass Studio HTML alle Module enthält."""
|
|
response = client.get("/studio")
|
|
|
|
assert response.status_code == 200
|
|
html = response.text
|
|
|
|
# Prüfe ob alle Panel-IDs vorhanden
|
|
assert "panel-worksheets" in html
|
|
assert "panel-correction" in html
|
|
assert "panel-letters" in html
|
|
|
|
def test_all_apis_accessible(self):
|
|
"""Testet dass alle APIs erreichbar sind."""
|
|
endpoints = [
|
|
("/api/worksheets/generate/multiple-choice", "POST"),
|
|
("/api/corrections/", "GET"),
|
|
("/api/letters/", "GET"),
|
|
("/api/state/phases", "GET"),
|
|
]
|
|
|
|
for endpoint, method in endpoints:
|
|
if method == "GET":
|
|
response = client.get(endpoint)
|
|
else:
|
|
response = client.post(endpoint, json={
|
|
"source_text": "Test",
|
|
"num_questions": 1
|
|
})
|
|
|
|
# Sollte nicht 404 oder 500 sein
|
|
assert response.status_code in [200, 400, 422], f"{endpoint} returned {response.status_code}"
|
|
|
|
|
|
class TestExportFunctionality:
|
|
"""Tests für Export-Funktionen aller Module."""
|
|
|
|
def test_corrections_pdf_export_endpoint_exists(self):
|
|
"""Testet dass PDF-Export Endpoint existiert."""
|
|
# Erst Korrektur erstellen
|
|
create_response = client.post(
|
|
"/api/corrections/",
|
|
json={
|
|
"student_id": "pdf-test",
|
|
"student_name": "PDF Test",
|
|
"class_name": "10a",
|
|
"exam_title": "PDF-Test",
|
|
"subject": "Test",
|
|
"max_points": 100
|
|
}
|
|
)
|
|
correction_id = create_response.json()["correction"]["id"]
|
|
|
|
# PDF-Export versuchen
|
|
response = client.get(f"/api/corrections/{correction_id}/export-pdf")
|
|
|
|
# 500 ist ok wenn PDF-Service nicht verfügbar, aber Endpoint existiert
|
|
assert response.status_code in [200, 500]
|
|
|
|
# Cleanup
|
|
client.delete(f"/api/corrections/{correction_id}")
|
|
|
|
def test_letters_pdf_export_endpoint_exists(self):
|
|
"""Testet dass Letters PDF-Export existiert."""
|
|
response = client.post(
|
|
"/api/letters/export-pdf",
|
|
json={
|
|
"letter_data": {
|
|
"recipient_name": "Test",
|
|
"recipient_address": "Test",
|
|
"student_name": "Test",
|
|
"student_class": "Test",
|
|
"subject": "Test",
|
|
"content": "Test",
|
|
"letter_type": "general",
|
|
"tone": "professional",
|
|
"teacher_name": "Test",
|
|
"teacher_title": "Test"
|
|
}
|
|
}
|
|
)
|
|
|
|
# 500 ist ok wenn PDF-Service nicht verfügbar
|
|
assert response.status_code in [200, 500]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|