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_certificates_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

401 lines
13 KiB
Python

"""
Tests für die Certificates API.
Testet:
- CRUD-Operationen für Zeugnisse
- PDF-Export
- Workflow (Draft -> Review -> Approved -> Issued)
- Notenstatistiken
Note: Some tests require WeasyPrint which needs system libraries.
"""
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, AsyncMock
import sys
import os
# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Check if WeasyPrint is available (required for PDF endpoints)
try:
import weasyprint
WEASYPRINT_AVAILABLE = True
except (ImportError, OSError):
WEASYPRINT_AVAILABLE = False
class TestCertificatesAPIImport:
"""Tests für Certificates API Import."""
def test_import_certificates_api(self):
"""Test that certificates_api can be imported."""
from certificates_api import router
assert router is not None
def test_import_enums(self):
"""Test that enums can be imported."""
from certificates_api import CertificateType, CertificateStatus, BehaviorGrade
assert CertificateType is not None
assert CertificateStatus is not None
assert BehaviorGrade is not None
def test_import_models(self):
"""Test that Pydantic models can be imported."""
from certificates_api import (
CertificateCreateRequest,
CertificateUpdateRequest,
CertificateResponse,
SubjectGrade,
AttendanceInfo
)
assert CertificateCreateRequest is not None
assert SubjectGrade is not None
class TestCertificateTypes:
"""Tests für Zeugnistypen."""
def test_certificate_types_values(self):
"""Test that all certificate types have correct values."""
from certificates_api import CertificateType
expected_types = ["halbjahr", "jahres", "abschluss", "abgang", "uebergang"]
actual_types = [t.value for t in CertificateType]
for expected in expected_types:
assert expected in actual_types
def test_certificate_status_values(self):
"""Test that all statuses have correct values."""
from certificates_api import CertificateStatus
expected_statuses = ["draft", "review", "approved", "issued", "archived"]
actual_statuses = [s.value for s in CertificateStatus]
for expected in expected_statuses:
assert expected in actual_statuses
class TestSubjectGrade:
"""Tests für SubjectGrade Model."""
def test_create_subject_grade(self):
"""Test creating a subject grade."""
from certificates_api import SubjectGrade
grade = SubjectGrade(
name="Mathematik",
grade="2",
points=11,
note="Gute Mitarbeit"
)
assert grade.name == "Mathematik"
assert grade.grade == "2"
assert grade.points == 11
def test_create_subject_grade_minimal(self):
"""Test creating a minimal subject grade."""
from certificates_api import SubjectGrade
grade = SubjectGrade(name="Deutsch", grade="1")
assert grade.name == "Deutsch"
assert grade.grade == "1"
assert grade.points is None
class TestAttendanceInfo:
"""Tests für AttendanceInfo Model."""
def test_create_attendance_info(self):
"""Test creating attendance info."""
from certificates_api import AttendanceInfo
attendance = AttendanceInfo(
days_absent=10,
days_excused=8,
days_unexcused=2
)
assert attendance.days_absent == 10
assert attendance.days_excused == 8
assert attendance.days_unexcused == 2
def test_default_attendance_values(self):
"""Test default attendance values."""
from certificates_api import AttendanceInfo
attendance = AttendanceInfo()
assert attendance.days_absent == 0
assert attendance.days_excused == 0
assert attendance.days_unexcused == 0
class TestCertificateCreateRequest:
"""Tests für CertificateCreateRequest Model."""
def test_create_certificate_request(self):
"""Test creating a certificate request."""
from certificates_api import (
CertificateCreateRequest,
CertificateType,
SubjectGrade,
AttendanceInfo
)
request = CertificateCreateRequest(
student_id="student-123",
student_name="Max Mustermann",
student_birthdate="15.05.2010",
student_class="5a",
school_year="2024/2025",
certificate_type=CertificateType.HALBJAHR,
subjects=[
SubjectGrade(name="Deutsch", grade="2"),
SubjectGrade(name="Mathematik", grade="2"),
],
attendance=AttendanceInfo(days_absent=5, days_excused=5),
class_teacher="Frau Schmidt",
principal="Herr Direktor"
)
assert request.student_name == "Max Mustermann"
assert request.certificate_type == CertificateType.HALBJAHR
assert len(request.subjects) == 2
class TestHelperFunctions:
"""Tests für Helper-Funktionen."""
def test_calculate_average(self):
"""Test average calculation."""
from certificates_api import _calculate_average
subjects = [
{"name": "Deutsch", "grade": "2"},
{"name": "Mathe", "grade": "3"},
{"name": "Englisch", "grade": "1"}
]
avg = _calculate_average(subjects)
assert avg == 2.0
def test_calculate_average_empty(self):
"""Test average calculation with empty list."""
from certificates_api import _calculate_average
avg = _calculate_average([])
assert avg is None
def test_calculate_average_non_numeric(self):
"""Test average calculation with non-numeric grades."""
from certificates_api import _calculate_average
subjects = [
{"name": "Deutsch", "grade": "A"},
{"name": "Mathe", "grade": "B"}
]
avg = _calculate_average(subjects)
assert avg is None
def test_get_type_label(self):
"""Test type label function."""
from certificates_api import _get_type_label, CertificateType
assert "Halbjahres" in _get_type_label(CertificateType.HALBJAHR)
assert "Jahres" in _get_type_label(CertificateType.JAHRES)
assert "Abschluss" in _get_type_label(CertificateType.ABSCHLUSS)
@pytest.mark.skipif(
not WEASYPRINT_AVAILABLE,
reason="WeasyPrint not available (requires system libraries)"
)
class TestCertificatesAPIEndpoints:
"""Integration tests für Certificates API Endpoints."""
@pytest.fixture
def client(self):
"""Create test client."""
try:
from main import app
return TestClient(app)
except ImportError:
pytest.skip("main.py not available for testing")
@pytest.fixture
def sample_certificate_data(self):
"""Sample certificate data for tests."""
return {
"student_id": "student-test-123",
"student_name": "Test Schüler",
"student_birthdate": "01.01.2012",
"student_class": "5a",
"school_year": "2024/2025",
"certificate_type": "halbjahr",
"subjects": [
{"name": "Deutsch", "grade": "2"},
{"name": "Mathematik", "grade": "3"},
{"name": "Englisch", "grade": "2"}
],
"attendance": {
"days_absent": 5,
"days_excused": 4,
"days_unexcused": 1
},
"class_teacher": "Frau Test",
"principal": "Herr Direktor"
}
def test_create_certificate(self, client, sample_certificate_data):
"""Test creating a new certificate."""
if not client:
pytest.skip("Client not available")
response = client.post("/api/certificates/", json=sample_certificate_data)
assert response.status_code == 200
data = response.json()
assert data["student_name"] == sample_certificate_data["student_name"]
assert data["status"] == "draft"
assert "id" in data
assert data["average_grade"] is not None
def test_get_certificate(self, client, sample_certificate_data):
"""Test getting a certificate by ID."""
if not client:
pytest.skip("Client not available")
# First create a certificate
create_response = client.post("/api/certificates/", json=sample_certificate_data)
cert_id = create_response.json()["id"]
# Then get it
response = client.get(f"/api/certificates/{cert_id}")
assert response.status_code == 200
data = response.json()
assert data["id"] == cert_id
def test_update_certificate(self, client, sample_certificate_data):
"""Test updating a certificate."""
if not client:
pytest.skip("Client not available")
# Create certificate
create_response = client.post("/api/certificates/", json=sample_certificate_data)
cert_id = create_response.json()["id"]
# Update it
update_data = {"remarks": "Versetzung in Klasse 6a"}
response = client.put(f"/api/certificates/{cert_id}", json=update_data)
assert response.status_code == 200
data = response.json()
assert data["remarks"] == "Versetzung in Klasse 6a"
def test_delete_certificate(self, client, sample_certificate_data):
"""Test deleting a certificate."""
if not client:
pytest.skip("Client not available")
# Create certificate
create_response = client.post("/api/certificates/", json=sample_certificate_data)
cert_id = create_response.json()["id"]
# Delete it
response = client.delete(f"/api/certificates/{cert_id}")
assert response.status_code == 200
# Verify it's deleted
get_response = client.get(f"/api/certificates/{cert_id}")
assert get_response.status_code == 404
def test_export_pdf(self, client, sample_certificate_data):
"""Test PDF export."""
if not client:
pytest.skip("Client not available")
# Create certificate
create_response = client.post("/api/certificates/", json=sample_certificate_data)
cert_id = create_response.json()["id"]
# Export as PDF
response = client.post(f"/api/certificates/{cert_id}/export-pdf")
assert response.status_code == 200
assert response.headers["content-type"] == "application/pdf"
assert b"%PDF" in response.content[:10]
def test_certificate_workflow(self, client, sample_certificate_data):
"""Test complete certificate workflow."""
if not client:
pytest.skip("Client not available")
# 1. Create (draft)
create_response = client.post("/api/certificates/", json=sample_certificate_data)
cert_id = create_response.json()["id"]
assert create_response.json()["status"] == "draft"
# 2. Submit for review
review_response = client.post(f"/api/certificates/{cert_id}/submit-review")
assert review_response.status_code == 200
assert review_response.json()["status"] == "review"
# 3. Approve
approve_response = client.post(f"/api/certificates/{cert_id}/approve")
assert approve_response.status_code == 200
assert approve_response.json()["status"] == "approved"
# 4. Issue
issue_response = client.post(f"/api/certificates/{cert_id}/issue")
assert issue_response.status_code == 200
assert issue_response.json()["status"] == "issued"
# 5. Cannot update after issued
update_response = client.put(f"/api/certificates/{cert_id}", json={"remarks": "Test"})
assert update_response.status_code == 400
def test_get_certificate_types(self, client):
"""Test getting available certificate types."""
if not client:
pytest.skip("Client not available")
response = client.get("/api/certificates/types")
assert response.status_code == 200
data = response.json()
assert "types" in data
assert len(data["types"]) >= 5
def test_get_behavior_grades(self, client):
"""Test getting available behavior grades."""
if not client:
pytest.skip("Client not available")
response = client.get("/api/certificates/behavior-grades")
assert response.status_code == 200
data = response.json()
assert "grades" in data
assert len(data["grades"]) == 4
def test_get_nonexistent_certificate(self, client):
"""Test getting a certificate that doesn't exist."""
if not client:
pytest.skip("Client not available")
response = client.get("/api/certificates/nonexistent-id")
assert response.status_code == 404
# Run tests if executed directly
if __name__ == "__main__":
pytest.main([__file__, "-v"])