""" Test Configuration - ServiceManagerWeb Configuración global para todos los tests (unit + integration). Carga variables de entorno de prueba antes de cualquier import de la app, y provee fixtures compartidos sin dependencia de Docker/PostgreSQL. """ import os import pytest import asyncio from typing import AsyncGenerator, Generator from unittest.mock import AsyncMock, MagicMock import uuid # ============================================================ # CARGAR VARIABLES DE ENTORNO DE TEST ANTES DE IMPORTAR LA APP # Esto evita que pydantic-settings falle por SECRET_KEY faltante # ============================================================ os.environ.setdefault("ENVIRONMENT", "testing") os.environ.setdefault("DEBUG", "true") os.environ.setdefault("SECRET_KEY", "test-secret-key-for-unit-tests-only-32chars!") os.environ.setdefault("JWT_SECRET_KEY", "test-jwt-secret-key-for-unit-tests-only!") os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_unit.db") os.environ.setdefault("REDIS_URL", "redis://localhost:6379/15") os.environ.setdefault("CELERY_BROKER_URL", "redis://localhost:6379/15") os.environ.setdefault("CELERY_RESULT_BACKEND", "redis://localhost:6379/15") os.environ.setdefault("CORS_ORIGINS", "http://localhost:3000") os.environ.setdefault("ALLOWED_FILE_EXTENSIONS", "pdf,jpg,jpeg,png,doc,docx,txt") # ============================================================ # IN-MEMORY SQLite DB PARA UNIT TESTS (sin Docker) # ============================================================ @pytest.fixture(scope="session") def event_loop() -> Generator: """Event loop compartido para toda la sesión de tests.""" policy = asyncio.get_event_loop_policy() loop = policy.new_event_loop() yield loop loop.close() @pytest.fixture(scope="session") async def sqlite_engine(): """ Engine SQLite en memoria para unit tests. No requiere Docker ni PostgreSQL. """ from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.pool import StaticPool from app.core.database import Base # Importar todos los modelos para registrarlos en Base.metadata import app.models # noqa: F401 engine = create_async_engine( "sqlite+aiosqlite:///:memory:", echo=False, connect_args={"check_same_thread": False}, poolclass=StaticPool, ) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine async with engine.begin() as conn: await conn.run_sync(Base.metadata.drop_all) await engine.dispose() @pytest.fixture async def db_session(sqlite_engine) -> AsyncGenerator: """ Sesión de BD SQLite en memoria para cada test. Hace rollback al finalizar para mantener tests aislados. """ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker async_session = async_sessionmaker( sqlite_engine, class_=AsyncSession, expire_on_commit=False, ) async with async_session() as session: async with session.begin(): yield session await session.rollback() # ============================================================ # FIXTURES DE DATOS COMUNES # ============================================================ @pytest.fixture def test_user_data() -> dict: """Datos de usuario válidos para pruebas.""" return { "email": "test@example.com", "first_name": "Test", "last_name": "User", "password": "TestPassword123!", "role": "AGENT", "language": "es", "timezone": "UTC", "notifications_email": True, } @pytest.fixture def test_tenant_data() -> dict: """Datos de tenant válidos para pruebas.""" return { "name": "Test Company", "slug": "test-company", "contact_email": "admin@testcompany.com", } @pytest.fixture def test_ticket_data() -> dict: """Datos de ticket válidos para pruebas.""" return { "subject": "Test ticket subject", "description": "Detailed description of the test ticket", "priority": "MEDIUM", } @pytest.fixture def mock_db_session(): """Sesión de BD completamente mockeada (sin SQLite, sin red).""" session = AsyncMock() session.execute = AsyncMock() session.add = MagicMock() session.commit = AsyncMock() session.refresh = AsyncMock() session.rollback = AsyncMock() return session @pytest.fixture def mock_request(): """Request HTTP mockeado para tests de middleware y endpoints.""" request = MagicMock() request.url.path = "/v1/tickets/" request.method = "GET" request.headers = {} request.state = MagicMock() return request @pytest.fixture def sample_tenant_id() -> str: """UUID de tenant fijo para pruebas.""" return "12345678-1234-5678-1234-567812345678" @pytest.fixture def sample_user_id() -> str: """UUID de usuario fijo para pruebas.""" return "87654321-4321-8765-4321-876543218765"