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