# services/external/tests/conftest.py """ Pytest configuration and fixtures for External Service tests """ import pytest import asyncio from datetime import datetime, timezone from typing import AsyncGenerator from uuid import uuid4, UUID from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.pool import StaticPool from fastapi.testclient import TestClient from app.main import app from app.core.config import settings from app.core.database import Base, get_db from app.models.weather import WeatherData, WeatherStation from app.models.traffic import TrafficData, TrafficMeasurementPoint # Test database configuration TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" @pytest.fixture(scope="session") def event_loop(): """Create event loop for the test session""" loop = asyncio.new_event_loop() yield loop loop.close() @pytest.fixture async def test_engine(): """Create test database engine""" engine = create_async_engine( TEST_DATABASE_URL, poolclass=StaticPool, connect_args={"check_same_thread": False} ) # Create tables async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose() @pytest.fixture async def test_db_session(test_engine) -> AsyncGenerator[AsyncSession, None]: """Create test database session""" async_session = async_sessionmaker( test_engine, class_=AsyncSession, expire_on_commit=False ) async with async_session() as session: yield session @pytest.fixture def test_client(): """Create test client""" return TestClient(app) @pytest.fixture async def override_get_db(test_db_session): """Override get_db dependency for testing""" async def _override_get_db(): yield test_db_session app.dependency_overrides[get_db] = _override_get_db yield app.dependency_overrides.clear() # Test data fixtures @pytest.fixture def sample_tenant_id() -> UUID: """Sample tenant ID for testing""" return uuid4() @pytest.fixture def sample_weather_data() -> dict: """Sample weather data for testing""" return { "city": "madrid", "location_id": "40.4168,-3.7038", "date": datetime.now(timezone.utc), "temperature": 18.5, "humidity": 65.0, "pressure": 1013.2, "wind_speed": 10.2, "condition": "partly_cloudy", "description": "Parcialmente nublado", "source": "aemet", "data_type": "current", "is_forecast": False, "data_quality_score": 95.0 } @pytest.fixture def sample_traffic_data() -> dict: """Sample traffic data for testing""" return { "city": "madrid", "location_id": "PM_M30_001", "date": datetime.now(timezone.utc), "measurement_point_id": "PM_M30_001", "measurement_point_name": "M-30 Norte - Nudo Norte", "measurement_point_type": "M30", "traffic_volume": 850, "average_speed": 65.2, "congestion_level": "medium", "occupation_percentage": 45.8, "latitude": 40.4501, "longitude": -3.6919, "district": "Chamartín", "source": "madrid_opendata", "data_quality_score": 92.0, "is_synthetic": False } @pytest.fixture def sample_weather_forecast() -> list[dict]: """Sample weather forecast data""" base_date = datetime.now(timezone.utc) return [ { "city": "madrid", "location_id": "40.4168,-3.7038", "date": base_date, "forecast_date": base_date, "temperature": 20.0, "temperature_min": 15.0, "temperature_max": 25.0, "precipitation": 0.0, "humidity": 60.0, "wind_speed": 12.0, "condition": "sunny", "description": "Soleado", "source": "aemet", "data_type": "forecast", "is_forecast": True, "data_quality_score": 85.0 } ] @pytest.fixture async def populated_weather_db(test_db_session: AsyncSession, sample_weather_data: dict): """Database populated with weather test data""" weather_record = WeatherData(**sample_weather_data) test_db_session.add(weather_record) await test_db_session.commit() yield test_db_session @pytest.fixture async def populated_traffic_db(test_db_session: AsyncSession, sample_traffic_data: dict): """Database populated with traffic test data""" traffic_record = TrafficData(**sample_traffic_data) test_db_session.add(traffic_record) await test_db_session.commit() yield test_db_session # Mock external API fixtures @pytest.fixture def mock_aemet_response(): """Mock AEMET API response""" return { "date": datetime.now(timezone.utc), "temperature": 18.5, "humidity": 65.0, "pressure": 1013.2, "wind_speed": 10.2, "description": "Parcialmente nublado", "source": "aemet" } @pytest.fixture def mock_madrid_traffic_xml(): """Mock Madrid Open Data traffic XML""" return """ 850 45 65 2024-01-15T10:30:00 320 78 25 2024-01-15T10:30:00 """ @pytest.fixture def mock_messaging(): """Mock messaging service""" class MockMessaging: def __init__(self): self.published_events = [] async def publish_weather_updated(self, data): self.published_events.append(("weather_updated", data)) return True async def publish_traffic_updated(self, data): self.published_events.append(("traffic_updated", data)) return True async def publish_collection_job_started(self, data): self.published_events.append(("job_started", data)) return True async def publish_collection_job_completed(self, data): self.published_events.append(("job_completed", data)) return True return MockMessaging() # Mock external clients @pytest.fixture def mock_aemet_client(): """Mock AEMET client""" class MockAEMETClient: async def get_current_weather(self, lat, lon): return { "date": datetime.now(timezone.utc), "temperature": 18.5, "humidity": 65.0, "pressure": 1013.2, "wind_speed": 10.2, "description": "Parcialmente nublado", "source": "aemet" } async def get_forecast(self, lat, lon, days): return [ { "forecast_date": datetime.now(timezone.utc), "temperature": 20.0, "temperature_min": 15.0, "temperature_max": 25.0, "precipitation": 0.0, "humidity": 60.0, "wind_speed": 12.0, "description": "Soleado", "source": "aemet" } ] return MockAEMETClient() @pytest.fixture def mock_madrid_client(): """Mock Madrid traffic client""" class MockMadridClient: async def fetch_current_traffic_xml(self): return """ 500 50 50 2024-01-15T10:30:00 """ return MockMadridClient() @pytest.fixture def mock_madrid_processor(): """Mock Madrid traffic processor""" class MockMadridProcessor: async def process_current_traffic_xml(self, xml_content): return [ { "city": "madrid", "location_id": "PM_TEST_001", "date": datetime.now(timezone.utc), "measurement_point_id": "PM_TEST_001", "measurement_point_name": "Test Point", "measurement_point_type": "TEST", "traffic_volume": 500, "average_speed": 50.0, "congestion_level": "medium", "occupation_percentage": 50.0, "latitude": 40.4168, "longitude": -3.7038, "district": "Centro", "source": "madrid_opendata", "data_quality_score": 90.0, "is_synthetic": False } ] return MockMadridProcessor()