Files
breakpilot-lehrer/klausur-service/backend/tests/test_worksheet_editor.py
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

540 lines
16 KiB
Python

"""
Unit Tests for Worksheet Editor API
Tests cover:
- Worksheet CRUD (create, read, list, delete)
- AI Image generation (mocked)
- PDF Export
- Health check
DSGVO Note: All tests run locally without external API calls.
BACKLOG: Feature not yet integrated into main.py
See: https://macmini:3002/infrastructure/tests
"""
import pytest
import sys
# Skip entire module if worksheet_editor_api is not available
pytest.importorskip("worksheet_editor_api", reason="worksheet_editor_api not yet integrated - Backlog item")
# Mark all tests in this module as expected failures (backlog item)
pytestmark = pytest.mark.xfail(
reason="worksheet_editor_api not yet integrated into main.py - Backlog item",
strict=False # Don't fail if test unexpectedly passes
)
import json
import uuid
import io
import os
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, patch
from fastapi.testclient import TestClient
# Import the main app and worksheet-editor components
sys.path.insert(0, '..')
from main import app
from worksheet_editor_api import (
worksheets_db,
AIImageStyle,
WorksheetStatus,
WORKSHEET_STORAGE_DIR,
)
# =============================================
# FIXTURES
# =============================================
@pytest.fixture
def client():
"""Test client for FastAPI app."""
return TestClient(app)
@pytest.fixture(autouse=True)
def clear_storage():
"""Clear in-memory storage before each test."""
worksheets_db.clear()
# Clean up test files
if os.path.exists(WORKSHEET_STORAGE_DIR):
for f in os.listdir(WORKSHEET_STORAGE_DIR):
if f.startswith('test_') or f.startswith('ws_test_'):
os.remove(os.path.join(WORKSHEET_STORAGE_DIR, f))
yield
worksheets_db.clear()
@pytest.fixture
def sample_worksheet_data():
"""Sample worksheet data."""
return {
"title": "Test Arbeitsblatt",
"description": "Testbeschreibung",
"pages": [
{
"id": "page_1",
"index": 0,
"canvasJSON": json.dumps({
"objects": [
{
"type": "text",
"text": "Überschrift",
"left": 100,
"top": 50,
"fontSize": 24
},
{
"type": "rect",
"left": 100,
"top": 100,
"width": 200,
"height": 100
}
]
})
}
],
"pageFormat": {
"width": 210,
"height": 297,
"orientation": "portrait",
"margins": {"top": 15, "right": 15, "bottom": 15, "left": 15}
}
}
@pytest.fixture
def sample_multipage_worksheet():
"""Sample worksheet with multiple pages."""
return {
"title": "Mehrseitiges Arbeitsblatt",
"pages": [
{"id": "page_1", "index": 0, "canvasJSON": "{}"},
{"id": "page_2", "index": 1, "canvasJSON": "{}"},
{"id": "page_3", "index": 2, "canvasJSON": "{}"},
]
}
# =============================================
# WORKSHEET CRUD TESTS
# =============================================
class TestWorksheetCRUD:
"""Tests for Worksheet CRUD operations."""
def test_create_worksheet(self, client, sample_worksheet_data):
"""Test creating a new worksheet."""
response = client.post(
"/api/v1/worksheet/save",
json=sample_worksheet_data
)
assert response.status_code == 200
data = response.json()
assert "id" in data
assert data["id"].startswith("ws_")
assert data["title"] == sample_worksheet_data["title"]
assert data["description"] == sample_worksheet_data["description"]
assert len(data["pages"]) == 1
assert "createdAt" in data
assert "updatedAt" in data
def test_update_worksheet(self, client, sample_worksheet_data):
"""Test updating an existing worksheet."""
# First create
create_response = client.post(
"/api/v1/worksheet/save",
json=sample_worksheet_data
)
worksheet_id = create_response.json()["id"]
# Then update
sample_worksheet_data["id"] = worksheet_id
sample_worksheet_data["title"] = "Aktualisiertes Arbeitsblatt"
update_response = client.post(
"/api/v1/worksheet/save",
json=sample_worksheet_data
)
assert update_response.status_code == 200
data = update_response.json()
assert data["id"] == worksheet_id
assert data["title"] == "Aktualisiertes Arbeitsblatt"
def test_get_worksheet(self, client, sample_worksheet_data):
"""Test retrieving a worksheet by ID."""
# Create first
create_response = client.post(
"/api/v1/worksheet/save",
json=sample_worksheet_data
)
worksheet_id = create_response.json()["id"]
# Then get
get_response = client.get(f"/api/v1/worksheet/{worksheet_id}")
assert get_response.status_code == 200
data = get_response.json()
assert data["id"] == worksheet_id
assert data["title"] == sample_worksheet_data["title"]
def test_get_nonexistent_worksheet(self, client):
"""Test retrieving a non-existent worksheet."""
response = client.get("/api/v1/worksheet/ws_nonexistent123")
assert response.status_code == 404
assert "not found" in response.json()["detail"].lower()
def test_list_worksheets(self, client, sample_worksheet_data):
"""Test listing all worksheets."""
# Create multiple worksheets
for i in range(3):
data = sample_worksheet_data.copy()
data["title"] = f"Arbeitsblatt {i+1}"
client.post("/api/v1/worksheet/save", json=data)
# List all
response = client.get("/api/v1/worksheet/list/all")
assert response.status_code == 200
data = response.json()
assert "worksheets" in data
assert "total" in data
assert data["total"] >= 3
def test_delete_worksheet(self, client, sample_worksheet_data):
"""Test deleting a worksheet."""
# Create first
create_response = client.post(
"/api/v1/worksheet/save",
json=sample_worksheet_data
)
worksheet_id = create_response.json()["id"]
# Delete
delete_response = client.delete(f"/api/v1/worksheet/{worksheet_id}")
assert delete_response.status_code == 200
assert delete_response.json()["status"] == "deleted"
# Verify it's gone
get_response = client.get(f"/api/v1/worksheet/{worksheet_id}")
assert get_response.status_code == 404
def test_delete_nonexistent_worksheet(self, client):
"""Test deleting a non-existent worksheet."""
response = client.delete("/api/v1/worksheet/ws_nonexistent123")
assert response.status_code == 404
# =============================================
# MULTIPAGE TESTS
# =============================================
class TestMultipageWorksheet:
"""Tests for multi-page worksheet functionality."""
def test_create_multipage_worksheet(self, client, sample_multipage_worksheet):
"""Test creating a worksheet with multiple pages."""
response = client.post(
"/api/v1/worksheet/save",
json=sample_multipage_worksheet
)
assert response.status_code == 200
data = response.json()
assert len(data["pages"]) == 3
def test_page_indices(self, client, sample_multipage_worksheet):
"""Test that page indices are preserved."""
response = client.post(
"/api/v1/worksheet/save",
json=sample_multipage_worksheet
)
data = response.json()
for i, page in enumerate(data["pages"]):
assert page["index"] == i
# =============================================
# AI IMAGE TESTS
# =============================================
class TestAIImageGeneration:
"""Tests for AI image generation."""
def test_ai_image_with_valid_prompt(self, client):
"""Test AI image generation with valid prompt."""
response = client.post(
"/api/v1/worksheet/ai-image",
json={
"prompt": "A friendly cartoon dog reading a book",
"style": "cartoon",
"width": 256,
"height": 256
}
)
# Should return 200 (with placeholder) since Ollama may not be running
assert response.status_code == 200
data = response.json()
assert "image_base64" in data
assert data["image_base64"].startswith("data:image/png;base64,")
assert "prompt_used" in data
def test_ai_image_styles(self, client):
"""Test different AI image styles."""
styles = ["realistic", "cartoon", "sketch", "clipart", "educational"]
for style in styles:
response = client.post(
"/api/v1/worksheet/ai-image",
json={
"prompt": "Test image",
"style": style,
"width": 256,
"height": 256
}
)
assert response.status_code == 200
data = response.json()
assert style in data["prompt_used"].lower() or "test image" in data["prompt_used"].lower()
def test_ai_image_empty_prompt(self, client):
"""Test AI image generation with empty prompt."""
response = client.post(
"/api/v1/worksheet/ai-image",
json={
"prompt": "",
"style": "educational",
"width": 256,
"height": 256
}
)
assert response.status_code == 422 # Validation error
def test_ai_image_invalid_dimensions(self, client):
"""Test AI image generation with invalid dimensions."""
response = client.post(
"/api/v1/worksheet/ai-image",
json={
"prompt": "Test",
"style": "educational",
"width": 50, # Too small
"height": 256
}
)
assert response.status_code == 422 # Validation error
# =============================================
# CANVAS JSON TESTS
# =============================================
class TestCanvasJSON:
"""Tests for canvas JSON handling."""
def test_save_and_load_canvas_json(self, client):
"""Test that canvas JSON is preserved correctly."""
canvas_data = {
"objects": [
{"type": "text", "text": "Hello", "left": 10, "top": 10},
{"type": "rect", "left": 50, "top": 50, "width": 100, "height": 50}
],
"background": "#ffffff"
}
worksheet_data = {
"title": "Canvas Test",
"pages": [{
"id": "page_1",
"index": 0,
"canvasJSON": json.dumps(canvas_data)
}]
}
# Save
create_response = client.post(
"/api/v1/worksheet/save",
json=worksheet_data
)
worksheet_id = create_response.json()["id"]
# Load
get_response = client.get(f"/api/v1/worksheet/{worksheet_id}")
loaded_data = get_response.json()
# Compare
loaded_canvas = json.loads(loaded_data["pages"][0]["canvasJSON"])
assert loaded_canvas["objects"] == canvas_data["objects"]
def test_empty_canvas_json(self, client):
"""Test worksheet with empty canvas JSON."""
worksheet_data = {
"title": "Empty Canvas",
"pages": [{
"id": "page_1",
"index": 0,
"canvasJSON": "{}"
}]
}
response = client.post(
"/api/v1/worksheet/save",
json=worksheet_data
)
assert response.status_code == 200
# =============================================
# PAGE FORMAT TESTS
# =============================================
class TestPageFormat:
"""Tests for page format handling."""
def test_default_page_format(self, client):
"""Test that default page format is applied."""
worksheet_data = {
"title": "Default Format",
"pages": [{"id": "p1", "index": 0, "canvasJSON": "{}"}]
}
response = client.post(
"/api/v1/worksheet/save",
json=worksheet_data
)
data = response.json()
assert data["pageFormat"]["width"] == 210
assert data["pageFormat"]["height"] == 297
assert data["pageFormat"]["orientation"] == "portrait"
def test_custom_page_format(self, client):
"""Test custom page format."""
worksheet_data = {
"title": "Custom Format",
"pages": [{"id": "p1", "index": 0, "canvasJSON": "{}"}],
"pageFormat": {
"width": 297,
"height": 210,
"orientation": "landscape",
"margins": {"top": 20, "right": 20, "bottom": 20, "left": 20}
}
}
response = client.post(
"/api/v1/worksheet/save",
json=worksheet_data
)
data = response.json()
assert data["pageFormat"]["width"] == 297
assert data["pageFormat"]["height"] == 210
assert data["pageFormat"]["orientation"] == "landscape"
# =============================================
# HEALTH CHECK TESTS
# =============================================
class TestHealthCheck:
"""Tests for health check endpoint."""
def test_health_check(self, client):
"""Test health check endpoint."""
response = client.get("/api/v1/worksheet/health/check")
assert response.status_code == 200
data = response.json()
assert "status" in data
assert data["status"] == "healthy"
assert "storage" in data
assert "reportlab" in data
# =============================================
# PDF EXPORT TESTS
# =============================================
class TestPDFExport:
"""Tests for PDF export functionality."""
def test_export_pdf(self, client, sample_worksheet_data):
"""Test PDF export of a worksheet."""
# Create worksheet
create_response = client.post(
"/api/v1/worksheet/save",
json=sample_worksheet_data
)
worksheet_id = create_response.json()["id"]
# Export
export_response = client.post(
f"/api/v1/worksheet/{worksheet_id}/export-pdf"
)
# Check response (may be 501 if reportlab not installed)
assert export_response.status_code in [200, 501]
if export_response.status_code == 200:
assert export_response.headers["content-type"] == "application/pdf"
assert "attachment" in export_response.headers.get("content-disposition", "")
def test_export_nonexistent_worksheet_pdf(self, client):
"""Test PDF export of non-existent worksheet."""
response = client.post("/api/v1/worksheet/ws_nonexistent/export-pdf")
assert response.status_code in [404, 501]
# =============================================
# ERROR HANDLING TESTS
# =============================================
class TestErrorHandling:
"""Tests for error handling."""
def test_invalid_json(self, client):
"""Test handling of invalid JSON."""
response = client.post(
"/api/v1/worksheet/save",
content="not valid json",
headers={"Content-Type": "application/json"}
)
assert response.status_code == 422
def test_missing_required_fields(self, client):
"""Test handling of missing required fields."""
response = client.post(
"/api/v1/worksheet/save",
json={"pages": []} # Missing title
)
assert response.status_code == 422
def test_empty_pages_array(self, client):
"""Test worksheet with empty pages array."""
response = client.post(
"/api/v1/worksheet/save",
json={
"title": "No Pages",
"pages": []
}
)
# Should still work - empty worksheets are allowed
assert response.status_code == 200