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>
540 lines
16 KiB
Python
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
|