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>
284 lines
9.7 KiB
Python
284 lines
9.7 KiB
Python
"""
|
|
Tests for Tile Server API
|
|
"""
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
# Import app after mocking to avoid import errors
|
|
import sys
|
|
sys.path.insert(0, '/app')
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
"""Create test client."""
|
|
from main import app
|
|
return TestClient(app)
|
|
|
|
|
|
class TestTileEndpoints:
|
|
"""Tests for tile server endpoints."""
|
|
|
|
def test_health_check(self, client):
|
|
"""Test health check endpoint returns healthy status."""
|
|
response = client.get("/health")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["status"] == "healthy"
|
|
assert data["service"] == "geo-service"
|
|
assert "data_status" in data
|
|
|
|
def test_root_endpoint(self, client):
|
|
"""Test root endpoint returns service info."""
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["service"] == "GeoEdu Service"
|
|
assert "endpoints" in data
|
|
assert "attribution" in data
|
|
|
|
def test_tile_metadata(self, client):
|
|
"""Test tile metadata endpoint."""
|
|
response = client.get("/api/v1/tiles/metadata")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "name" in data
|
|
assert "minzoom" in data
|
|
assert "maxzoom" in data
|
|
assert "bounds" in data
|
|
assert data["attribution"] == "© OpenStreetMap contributors (ODbL)"
|
|
|
|
def test_tile_bounds(self, client):
|
|
"""Test tile bounds endpoint returns Germany bounds."""
|
|
response = client.get("/api/v1/tiles/bounds")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "bounds" in data
|
|
# Germany approximate bounds
|
|
bounds = data["bounds"]
|
|
assert bounds[0] < 6 # west
|
|
assert bounds[1] > 47 # south
|
|
assert bounds[2] > 14 # east
|
|
assert bounds[3] < 56 # north
|
|
|
|
def test_style_json(self, client):
|
|
"""Test MapLibre style endpoint."""
|
|
response = client.get("/api/v1/tiles/style.json")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["version"] == 8
|
|
assert "sources" in data
|
|
assert "layers" in data
|
|
assert "osm" in data["sources"]
|
|
|
|
def test_tile_request_without_data(self, client):
|
|
"""Test tile request returns 503 when data not available."""
|
|
response = client.get("/api/v1/tiles/0/0/0.pbf")
|
|
# Should return 503 (data not available) or 204 (empty tile)
|
|
assert response.status_code in [204, 503]
|
|
|
|
def test_tile_request_invalid_zoom(self, client):
|
|
"""Test tile request with invalid zoom level."""
|
|
response = client.get("/api/v1/tiles/25/0/0.pbf")
|
|
assert response.status_code == 422 # Validation error
|
|
|
|
|
|
class TestTerrainEndpoints:
|
|
"""Tests for terrain/DEM endpoints."""
|
|
|
|
def test_terrain_metadata(self, client):
|
|
"""Test terrain metadata endpoint."""
|
|
response = client.get("/api/v1/terrain/metadata")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "Copernicus DEM GLO-30"
|
|
assert data["resolution_m"] == 30
|
|
assert "bounds" in data
|
|
|
|
def test_heightmap_request_without_data(self, client):
|
|
"""Test heightmap request returns appropriate status when no data."""
|
|
response = client.get("/api/v1/terrain/10/500/500.png")
|
|
# Should return 204 (no content) or 503 (data not available)
|
|
assert response.status_code in [204, 503]
|
|
|
|
def test_elevation_at_point_within_germany(self, client):
|
|
"""Test elevation endpoint with valid Germany coordinates."""
|
|
# Berlin coordinates
|
|
response = client.get("/api/v1/terrain/elevation?lat=52.52&lon=13.405")
|
|
# Should return 404 (no data) or 200 (with elevation) or 503
|
|
assert response.status_code in [200, 404, 503]
|
|
|
|
def test_elevation_at_point_outside_germany(self, client):
|
|
"""Test elevation endpoint with coordinates outside Germany."""
|
|
# Paris coordinates (outside Germany)
|
|
response = client.get("/api/v1/terrain/elevation?lat=48.8566&lon=2.3522")
|
|
# Should return 422 (validation error - outside bounds)
|
|
assert response.status_code == 422
|
|
|
|
|
|
class TestAOIEndpoints:
|
|
"""Tests for AOI (Area of Interest) endpoints."""
|
|
|
|
def test_validate_polygon_valid(self, client):
|
|
"""Test polygon validation with valid polygon."""
|
|
polygon = {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[
|
|
[9.19, 47.71],
|
|
[9.20, 47.71],
|
|
[9.20, 47.70],
|
|
[9.19, 47.70],
|
|
[9.19, 47.71],
|
|
]
|
|
],
|
|
}
|
|
response = client.post("/api/v1/aoi/validate", json=polygon)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "valid" in data
|
|
assert "area_km2" in data
|
|
assert "within_germany" in data
|
|
|
|
def test_validate_polygon_too_large(self, client):
|
|
"""Test polygon validation with too large area."""
|
|
# Large polygon (> 4 km²)
|
|
polygon = {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[
|
|
[9.0, 47.5],
|
|
[9.5, 47.5],
|
|
[9.5, 48.0],
|
|
[9.0, 48.0],
|
|
[9.0, 47.5],
|
|
]
|
|
],
|
|
}
|
|
response = client.post("/api/v1/aoi/validate", json=polygon)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["within_size_limit"] == False
|
|
|
|
def test_validate_polygon_outside_germany(self, client):
|
|
"""Test polygon validation outside Germany."""
|
|
# Paris area
|
|
polygon = {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[
|
|
[2.3, 48.8],
|
|
[2.4, 48.8],
|
|
[2.4, 48.9],
|
|
[2.3, 48.9],
|
|
[2.3, 48.8],
|
|
]
|
|
],
|
|
}
|
|
response = client.post("/api/v1/aoi/validate", json=polygon)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["within_germany"] == False
|
|
|
|
def test_mainau_template(self, client):
|
|
"""Test Mainau demo template endpoint."""
|
|
response = client.get("/api/v1/aoi/templates/mainau")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "Insel Mainau"
|
|
assert "polygon" in data
|
|
assert "center" in data
|
|
assert "suggested_themes" in data
|
|
|
|
def test_create_aoi(self, client):
|
|
"""Test AOI creation with valid polygon."""
|
|
request = {
|
|
"polygon": {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[
|
|
[9.1875, 47.7055],
|
|
[9.1975, 47.7055],
|
|
[9.1975, 47.7115],
|
|
[9.1875, 47.7115],
|
|
[9.1875, 47.7055],
|
|
]
|
|
],
|
|
},
|
|
"theme": "topographie",
|
|
"quality": "medium",
|
|
}
|
|
response = client.post("/api/v1/aoi", json=request)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "aoi_id" in data
|
|
assert data["status"] == "queued"
|
|
assert "area_km2" in data
|
|
|
|
def test_create_aoi_too_large(self, client):
|
|
"""Test AOI creation fails with too large area."""
|
|
request = {
|
|
"polygon": {
|
|
"type": "Polygon",
|
|
"coordinates": [
|
|
[
|
|
[9.0, 47.5],
|
|
[9.5, 47.5],
|
|
[9.5, 48.0],
|
|
[9.0, 48.0],
|
|
[9.0, 47.5],
|
|
]
|
|
],
|
|
},
|
|
"theme": "topographie",
|
|
"quality": "medium",
|
|
}
|
|
response = client.post("/api/v1/aoi", json=request)
|
|
assert response.status_code == 400
|
|
|
|
def test_get_nonexistent_aoi(self, client):
|
|
"""Test getting non-existent AOI returns 404."""
|
|
response = client.get("/api/v1/aoi/nonexistent-id")
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestLearningEndpoints:
|
|
"""Tests for Learning Nodes endpoints."""
|
|
|
|
def test_learning_templates(self, client):
|
|
"""Test learning templates endpoint."""
|
|
response = client.get("/api/v1/learning/templates")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "themes" in data
|
|
assert "difficulties" in data
|
|
assert len(data["themes"]) == 6 # 6 themes
|
|
|
|
# Check theme structure
|
|
theme = data["themes"][0]
|
|
assert "id" in theme
|
|
assert "name" in theme
|
|
assert "description" in theme
|
|
assert "example_questions" in theme
|
|
|
|
def test_learning_statistics(self, client):
|
|
"""Test learning statistics endpoint."""
|
|
response = client.get("/api/v1/learning/statistics")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "total_nodes_generated" in data
|
|
assert "nodes_by_theme" in data
|
|
|
|
def test_generate_nodes_without_aoi(self, client):
|
|
"""Test node generation fails without valid AOI."""
|
|
request = {
|
|
"aoi_id": "nonexistent-aoi",
|
|
"theme": "topographie",
|
|
"difficulty": "mittel",
|
|
"node_count": 5,
|
|
}
|
|
response = client.post("/api/v1/learning/generate", json=request)
|
|
# Should return 404 (AOI not found) or 503 (Ollama not available)
|
|
assert response.status_code in [404, 503]
|