Backend: los 8 endpoints de catálogo responden 200 con las semillas exactas y filtran por búsqueda; tax-regimes acota por tipo de persona; ninguna ruta de catálogo acepta escritura (405); sync_catalogs es idempotente. CRUD de conceptos, conflicto 409 por clave ProdServ repetida en la misma empresa, la misma clave permitida en otra empresa, la baja lógica liberándola, aislamiento multi-tenant, upsert del emisor sin duplicar filas y RFC inválido rechazado. También que una partida con concept_id hereda la descripción y que las facturas sin claves del SAT siguen listándose y generando PDF. El fixture de pruebas siembra los catálogos con la misma función que usa la migración, sobre el schema sat mapeado a SQLite. Frontend: prueba del cacheo del cliente de catálogos. RFC dummy XAXX010101000 en todas las pruebas: sin datos reales. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
91 lines
3.4 KiB
Python
91 lines
3.4 KiB
Python
"""Fixtures de pruebas del módulo CRM.
|
|
|
|
Las pruebas de servicios corren contra SQLite en memoria usando
|
|
``schema_translate_map`` para mapear los schemas ``crm``/``core`` al schema
|
|
principal de SQLite. Esto permite ejercitar la lógica de negocio sin depender
|
|
de PostgreSQL/Docker en el entorno de desarrollo local.
|
|
|
|
Nota: la validación estructural del esquema (FKs cross-schema, DDL) se hace
|
|
con la migración Alembic contra PostgreSQL en CI (``TEST_DATABASE_URL``).
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
from sqlalchemy import Column, Integer, String, Table, create_engine, event
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
BACKEND_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
if BACKEND_DIR not in sys.path:
|
|
sys.path.insert(0, BACKEND_DIR)
|
|
|
|
from core.database import Base # noqa: E402
|
|
|
|
# Importar los modelos registra sus tablas en Base.metadata
|
|
import api.v1.modules.crm.accounts.models # noqa: E402,F401
|
|
import api.v1.modules.crm.activities.models # noqa: E402,F401
|
|
import api.v1.modules.crm.addresses.models # noqa: E402,F401
|
|
import api.v1.modules.crm.contacts.models # noqa: E402,F401
|
|
import api.v1.modules.crm.documents.models # noqa: E402,F401
|
|
import api.v1.modules.crm.leads.models # noqa: E402,F401
|
|
import api.v1.modules.crm.opportunities.models # noqa: E402,F401
|
|
import api.v1.modules.crm.pipelines.models # noqa: E402,F401
|
|
import api.v1.modules.crm.quotes.models # noqa: E402,F401
|
|
import api.v1.modules.crm.service_requests.models # noqa: E402,F401
|
|
import api.v1.modules.crm.suppliers.models # noqa: E402,F401
|
|
import api.v1.modules.ops.shipments.models # noqa: E402,F401
|
|
import api.v1.modules.fin.catalogs.models # noqa: E402,F401
|
|
import api.v1.modules.fin.concepts.models # noqa: E402,F401
|
|
import api.v1.modules.fin.issuer.models # noqa: E402,F401
|
|
import api.v1.modules.fin.invoices.models # noqa: E402,F401
|
|
from api.v1.modules.fin.catalogs.seed_data import sync_catalogs # noqa: E402
|
|
|
|
_SCHEMA_MAP = {"crm": None, "core": None, "ops": None, "fin": None, "sat": None}
|
|
|
|
# Tabla mínima core.tenants para resolver la FK tenant_id de las tablas crm.
|
|
# En CI (PostgreSQL) la tabla real la crea la migración inicial del core.
|
|
if "core.tenants" not in Base.metadata.tables:
|
|
Table(
|
|
"tenants",
|
|
Base.metadata,
|
|
Column("id", Integer, primary_key=True),
|
|
Column("name", String(255)),
|
|
schema="core",
|
|
)
|
|
|
|
TENANT_ID = 1
|
|
COMPANY_ID = 1
|
|
|
|
|
|
@pytest.fixture()
|
|
def db():
|
|
engine = create_engine(
|
|
"sqlite://",
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
future=True,
|
|
).execution_options(schema_translate_map=_SCHEMA_MAP)
|
|
|
|
@event.listens_for(engine, "connect")
|
|
def _register_now(dbapi_conn, _record):
|
|
# Soporta server_default text("now()") de los mixins de timestamp
|
|
dbapi_conn.create_function(
|
|
"now", 0, lambda: datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S.%f")
|
|
)
|
|
|
|
Base.metadata.create_all(engine)
|
|
session_factory = sessionmaker(bind=engine, future=True)
|
|
session = session_factory()
|
|
# Los catálogos del SAT los siembra la migración en PostgreSQL; aquí se replica
|
|
# con la misma función para que conceptos y emisor tengan claves que referenciar.
|
|
sync_catalogs(session.connection())
|
|
session.commit()
|
|
try:
|
|
yield session
|
|
finally:
|
|
session.close()
|
|
engine.dispose()
|