- Extraccion de helpers en backend: audit_helpers.py, helpers.py - Modularizacion de schemas en archivos individuales por dominio - Reduccion de audit.py en 953 lineas (74% del archivo) - Reduccion de tickets.py en 655 lineas (60% del archivo) - Expansion de auth.py con recuperacion de contrasenia y tokens - Nuevos modulos: core/email.py, core/cache.py - Reorganizacion de scripts a backend/scripts/ - Frontend: refactorizacion de audit page con array-driven components - Frontend: correccion de 11 errores ortograficos en tickets page - Frontend: proxy Docker corregido en vite.config.js - Frontend: nuevas rutas forgot-password, reset-password, organization, profile - Nuevas utilidades TS: colorUtils.ts, dateFormats.ts - 5 nuevos archivos de tests unitarios en backend/tests/unit/ - Eliminacion de 3 scripts temporales de prueba - Documentacion tecnica: CAMBIOS_v1.10.0.md, OPTIMIZACIONES_RENDIMIENTO.md
167 lines
4.9 KiB
Python
167 lines
4.9 KiB
Python
"""
|
|
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"
|