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