- 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
265 lines
9.5 KiB
Python
265 lines
9.5 KiB
Python
"""
|
|
Unit Tests - Pydantic Schemas - ServiceManagerWeb
|
|
|
|
Tests para validación de schemas en app.api.schemas.
|
|
No requieren base de datos ni red.
|
|
"""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
import uuid
|
|
|
|
|
|
# ============================================================
|
|
# AUTH SCHEMAS
|
|
# ============================================================
|
|
|
|
class TestAuthSchemas:
|
|
"""Tests para schemas de autenticación."""
|
|
|
|
def test_login_request_valid(self):
|
|
"""LoginRequest acepta datos válidos."""
|
|
from app.api.schemas.auth import LoginRequest
|
|
schema = LoginRequest(
|
|
email="user@example.com",
|
|
password="Pass123!",
|
|
tenant_slug="my-tenant",
|
|
)
|
|
assert schema.email == "user@example.com"
|
|
assert schema.tenant_slug == "my-tenant"
|
|
assert schema.totp_code is None
|
|
|
|
def test_login_request_invalid_email(self):
|
|
"""LoginRequest rechaza email inválido."""
|
|
from app.api.schemas.auth import LoginRequest
|
|
with pytest.raises(ValidationError):
|
|
LoginRequest(email="not-an-email", password="Pass123!", tenant_slug="t")
|
|
|
|
def test_login_request_with_totp(self):
|
|
"""LoginRequest acepta código TOTP opcional."""
|
|
from app.api.schemas.auth import LoginRequest
|
|
schema = LoginRequest(
|
|
email="user@example.com",
|
|
password="Pass123!",
|
|
tenant_slug="my-tenant",
|
|
totp_code="123456",
|
|
)
|
|
assert schema.totp_code == "123456"
|
|
|
|
def test_token_response_default_type(self):
|
|
"""TokenResponse tiene token_type=bearer por defecto."""
|
|
from app.api.schemas.auth import TokenResponse
|
|
schema = TokenResponse(access_token="abc123", expires_in=3600)
|
|
assert schema.token_type == "bearer"
|
|
|
|
|
|
# ============================================================
|
|
# TENANT SCHEMAS
|
|
# ============================================================
|
|
|
|
class TestTenantSchemas:
|
|
"""Tests para schemas de tenants."""
|
|
|
|
def test_tenant_create_valid(self):
|
|
"""TenantCreate acepta datos mínimos válidos."""
|
|
from app.api.schemas.tenant import TenantCreate
|
|
schema = TenantCreate(name="ACME Corp", slug="acme-corp")
|
|
assert schema.name == "ACME Corp"
|
|
assert schema.slug == "acme-corp"
|
|
assert schema.domain is None
|
|
|
|
def test_tenant_create_with_all_fields(self):
|
|
"""TenantCreate acepta todos los campos opcionales."""
|
|
from app.api.schemas.tenant import TenantCreate
|
|
schema = TenantCreate(
|
|
name="ACME Corp",
|
|
slug="acme-corp",
|
|
domain="acme.com",
|
|
contact_email="admin@acme.com",
|
|
contact_phone="+1234567890",
|
|
)
|
|
assert schema.contact_email == "admin@acme.com"
|
|
|
|
def test_tenant_create_invalid_email(self):
|
|
"""TenantCreate rechaza email de contacto inválido."""
|
|
from app.api.schemas.tenant import TenantCreate
|
|
with pytest.raises(ValidationError):
|
|
TenantCreate(name="Corp", slug="corp", contact_email="bad-email")
|
|
|
|
def test_tenant_update_all_optional(self):
|
|
"""TenantUpdate permite actualización parcial (todos opcionales)."""
|
|
from app.api.schemas.tenant import TenantUpdate
|
|
schema = TenantUpdate()
|
|
assert schema.name is None
|
|
assert schema.slug is None
|
|
assert schema.status is None
|
|
|
|
def test_tenant_update_only_name(self):
|
|
"""TenantUpdate permite actualizar solo el nombre."""
|
|
from app.api.schemas.tenant import TenantUpdate
|
|
schema = TenantUpdate(name="New Name")
|
|
assert schema.name == "New Name"
|
|
assert schema.slug is None
|
|
|
|
|
|
# ============================================================
|
|
# USER SCHEMAS
|
|
# ============================================================
|
|
|
|
class TestUserSchemas:
|
|
"""Tests para schemas de usuarios."""
|
|
|
|
def test_user_create_valid(self):
|
|
"""UserCreate acepta datos válidos con defaults."""
|
|
from app.api.schemas.user import UserCreate
|
|
from app.models.user import UserRole
|
|
schema = UserCreate(
|
|
email="agent@company.com",
|
|
first_name="John",
|
|
last_name="Doe",
|
|
role=UserRole.AGENT,
|
|
password="SecurePass123!",
|
|
)
|
|
assert schema.email == "agent@company.com"
|
|
assert schema.language == "es"
|
|
assert schema.timezone == "UTC"
|
|
assert schema.notifications_email is True
|
|
|
|
def test_user_create_invalid_email(self):
|
|
"""UserCreate rechaza email inválido."""
|
|
from app.api.schemas.user import UserCreate
|
|
from app.models.user import UserRole
|
|
with pytest.raises(ValidationError):
|
|
UserCreate(
|
|
email="not-valid",
|
|
first_name="John",
|
|
last_name="Doe",
|
|
role=UserRole.AGENT,
|
|
password="Pass123!",
|
|
)
|
|
|
|
def test_user_create_invalid_role(self):
|
|
"""UserCreate rechaza rol inválido."""
|
|
from app.api.schemas.user import UserCreate
|
|
with pytest.raises(ValidationError):
|
|
UserCreate(
|
|
email="user@test.com",
|
|
first_name="John",
|
|
last_name="Doe",
|
|
role="SUPER_VILLAIN",
|
|
password="Pass123!",
|
|
)
|
|
|
|
def test_user_update_all_optional(self):
|
|
"""UserUpdate permite actualización parcial."""
|
|
from app.api.schemas.user import UserUpdate
|
|
schema = UserUpdate()
|
|
assert schema.email is None
|
|
assert schema.first_name is None
|
|
assert schema.is_active is None
|
|
|
|
|
|
# ============================================================
|
|
# TICKET SCHEMAS
|
|
# ============================================================
|
|
|
|
class TestTicketSchemas:
|
|
"""Tests para schemas de tickets."""
|
|
|
|
def test_ticket_create_valid_minimal(self):
|
|
"""TicketCreate acepta datos mínimos con priority por defecto."""
|
|
from app.api.schemas.ticket import TicketCreate
|
|
schema = TicketCreate(
|
|
subject="Mi impresora no funciona",
|
|
description="La impresora del piso 3 no enciende desde esta mañana.",
|
|
)
|
|
assert schema.subject == "Mi impresora no funciona"
|
|
assert schema.priority == "MEDIUM"
|
|
assert schema.category_id is None
|
|
assert schema.affected_system_id is None
|
|
|
|
def test_ticket_create_with_priority(self):
|
|
"""TicketCreate acepta prioridad personalizada."""
|
|
from app.api.schemas.ticket import TicketCreate
|
|
schema = TicketCreate(
|
|
subject="Sistema caído",
|
|
description="El sistema principal no responde.",
|
|
priority="URGENT",
|
|
)
|
|
assert schema.priority == "URGENT"
|
|
|
|
def test_ticket_update_all_optional(self):
|
|
"""TicketUpdate permite actualización parcial."""
|
|
from app.api.schemas.ticket import TicketUpdate
|
|
schema = TicketUpdate()
|
|
assert schema.subject is None
|
|
assert schema.status is None
|
|
assert schema.assigned_to is None
|
|
|
|
def test_ticket_close_request_optional_resolution(self):
|
|
"""TicketCloseRequest acepta resolución vacía."""
|
|
from app.api.schemas.ticket import TicketCloseRequest
|
|
schema = TicketCloseRequest()
|
|
assert schema.resolution is None
|
|
|
|
def test_comment_create_defaults(self):
|
|
"""CommentCreate tiene is_internal=False por defecto."""
|
|
from app.api.schemas.ticket import CommentCreate
|
|
schema = CommentCreate(content="Este es un comentario de prueba.")
|
|
assert schema.is_internal is False
|
|
|
|
def test_comment_create_internal(self):
|
|
"""CommentCreate acepta comentario interno."""
|
|
from app.api.schemas.ticket import CommentCreate
|
|
schema = CommentCreate(content="Nota interna.", is_internal=True)
|
|
assert schema.is_internal is True
|
|
|
|
|
|
# ============================================================
|
|
# CATEGORY SCHEMAS
|
|
# ============================================================
|
|
|
|
class TestCategorySchemas:
|
|
"""Tests para schemas de categorías."""
|
|
|
|
def test_category_create_defaults(self):
|
|
"""CategoryCreate tiene SLAs por defecto correctos."""
|
|
from app.api.schemas.category import CategoryCreate
|
|
schema = CategoryCreate(name="Hardware")
|
|
assert schema.sla_response_hours == 24
|
|
assert schema.sla_resolution_hours == 72
|
|
assert schema.is_active if hasattr(schema, "is_active") else True
|
|
|
|
def test_category_create_custom_sla(self):
|
|
"""CategoryCreate acepta SLAs personalizados."""
|
|
from app.api.schemas.category import CategoryCreate
|
|
schema = CategoryCreate(
|
|
name="Urgente",
|
|
sla_response_hours=1,
|
|
sla_resolution_hours=4,
|
|
)
|
|
assert schema.sla_response_hours == 1
|
|
assert schema.sla_resolution_hours == 4
|
|
|
|
|
|
# ============================================================
|
|
# SYSTEM SCHEMAS
|
|
# ============================================================
|
|
|
|
class TestSystemSchemas:
|
|
"""Tests para schemas de sistemas."""
|
|
|
|
def test_system_create_valid(self):
|
|
"""SystemCreate acepta datos válidos."""
|
|
from app.api.schemas.system import SystemCreate
|
|
schema = SystemCreate(name="ERP Principal")
|
|
assert schema.name == "ERP Principal"
|
|
assert schema.description is None
|
|
|
|
def test_system_update_all_optional(self):
|
|
"""SystemUpdate permite actualización parcial."""
|
|
from app.api.schemas.system import SystemUpdate
|
|
schema = SystemUpdate(is_active=False)
|
|
assert schema.is_active is False
|
|
assert schema.name is None
|