feat: Version 1.10.0 - Refactorizacion, optimizacion UI y mejoras de seguridad
- 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
This commit is contained in:
@@ -1,28 +1,166 @@
|
||||
"""
|
||||
Test Configuration - ServiceManagerWeb
|
||||
|
||||
Configuración básica para testing con pytest
|
||||
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")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_user_data():
|
||||
"""Sample user data for testing."""
|
||||
# ============================================================
|
||||
# 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!"
|
||||
"last_name": "User",
|
||||
"password": "TestPassword123!",
|
||||
"role": "AGENT",
|
||||
"language": "es",
|
||||
"timezone": "UTC",
|
||||
"notifications_email": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_tenant_data():
|
||||
"""Sample tenant data for testing."""
|
||||
@pytest.fixture
|
||||
def test_tenant_data() -> dict:
|
||||
"""Datos de tenant válidos para pruebas."""
|
||||
return {
|
||||
"name": "Test Tenant",
|
||||
"slug": "test-tenant",
|
||||
"description": "Test tenant for testing"
|
||||
}
|
||||
"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"
|
||||
|
||||
Reference in New Issue
Block a user