# Backend test strategy (Anexo24) ## Test suites - `tests/e2e/`: full business flow (catalogs -> import -> export -> balances/discharges). - `tests/integration/`: process API behavior and edge cases. - `tests/unit/`: critical algorithms only (no trivial CRUD tests). --- ## Vista del flujo de tests (backend) ### Dónde vive todo | Capa | Ruta | Rol | |------|------|-----| | Infra | [conftest.py](conftest.py) | DB en transacción + rollback, Celery eager, app mínima con router de process, overrides de DB/usuario, tareas import/export **inline** en la misma sesión | | Datos | [fixtures/builders.py](fixtures/builders.py) | Catálogos, tenant/company, facturas import/export con líneas enlazadas | | E2E | [e2e/test_inventory_flow_anexo24.py](e2e/test_inventory_flow_anexo24.py) | Flujo negocio completo import → export → saldos/descargas | | Integración | [integration/test_import_process_api.py](integration/test_import_process_api.py), [integration/test_export_process_api.py](integration/test_export_process_api.py) | API HTTP + reglas por tipo de factura, saldo, doble proceso | | Unit | [unit/invoices/test_balance_algorithm.py](unit/invoices/test_balance_algorithm.py), [unit/invoices/test_currency_conversion_imports.py](unit/invoices/test_currency_conversion_imports.py) | Algoritmos puros / subprocesos sin HTTP | ### Infraestructura (flujo común) ```mermaid flowchart TB subgraph fixtures [Fixtures conftest] db[db_session: conexion + begin + Session] roll[teardown: rollback + close] celery[celery_eager autouse: memory broker] app[app: FastAPI + process router] client[TestClient] end db --> roll celery --> app db --> app app --> client subgraph inline [Monkeypatch rutas] imp[process_invoice_task -> main_process import en db_session] exp[process_export_invoice_task -> main_process export en db_session] end app --> inline ``` - **BD**: `TEST_DATABASE_URL` o `CORE_DATABASE_URL` o `settings.core_database_url`. Cada test corre dentro de una transacción; al terminar **no persiste** nada (rollback). - **Celery**: sin Redis; `task_always_eager` para que el endpoint dispare lógica síncrona. - **Seguridad en tests**: `validate_access_to_resource` se reemplaza por un stub que devuelve `tenant_id` del usuario fake. ### Builders: qué se construye antes de llamar a la API Orden típico en casi todos los tests que tocan proceso: 1. `ensure_reference_data`: tipos de factura (`TEM`, `DEF`, `MEX`, `DONAC`) y régimen `A1` si no existen. 2. `ensure_tenant_company(tenant_id=1, company_id=1)`. 3. `create_business_catalogs`: proveedor/cliente/destinatario (con dirección idempotente vía `_ensure_client_provider_address`), agente aduanal, UoM, part number FA, tipo cambio, etc. 4. `create_import_invoice_with_line` / `create_export_invoice_with_line` según el caso. ### Parches al pipeline (import / export) Los tests **no** ejecutan validadores pesados del proceso real; sustituyen funciones en `main_process` para aislar el comportamiento de saldos y API: **Import** (varios tests): `pre_validators` devuelve la línea creada; se anulan reviews de clase, tipo de cambio, pesos, SISIMP, `_validate_lines`. **Export**: análogo con `pre_validators` + reviews de clase, TC, asignación, cantidad vs peso, costo, series. **Caso especial** — `test_process_endpoint_prevents_double_processing_import` en [integration/test_export_process_api.py](integration/test_export_process_api.py): usa `_patch_import_pipeline_without_prevalidators` (sin parchear `pre_validators`) para que el **segundo** `POST /process` recorra el flujo real y dispare la validación de “ya procesado”. ### Mapa por archivo y edge cases #### E2E — [e2e/test_inventory_flow_anexo24.py](e2e/test_inventory_flow_anexo24.py) - **Flujo**: datos ref + tenant/company + catálogos → factura import `TEM` qty 10 → `POST .../process` → comprueba `BalanceMovement` tipo `ENTRY` → factura export ligada a import/línea qty 4 → `POST process` → comprueba `CONSUMPTION`, `DischargeHeader`, `DischargeDetail` con `movement_id`. - **Edge implícito**: saldo neto por línea de import ≥ 0 y < 10 tras consumo parcial (no agota todo el inventario del escenario). #### Integración import — [integration/test_import_process_api.py](integration/test_import_process_api.py) | Test | Qué valida | |------|------------| | `test_process_import_endpoint_creates_balance_entries` | `TEM` + process → al menos un `ENTRY` por factura/línea | | `test_process_import_def_does_not_create_balance_entries` | `DEF` + process → **ningún** movimiento de saldo (edge: tipo operación no genera entradas de balance) | #### Integración export + doble proceso — [integration/test_export_process_api.py](integration/test_export_process_api.py) | Test | Qué valida | |------|------------| | `_prepare_inventory` + `test_process_export_endpoint_consumes_existing_balances` | Tras import `TEM` qty 10, export qty 3 → existen `CONSUMPTION` | | `test_process_export_prevents_negative_balance` | Export qty 99 vs saldo 10 → `ValidationException` y **cero** consumos para esa factura export | | `test_process_endpoint_prevents_double_processing_import` | Segundo `POST` misma factura import → `ValidationException`; exactamente **un** `ENTRY` (no duplicar PEPS) | #### Unit saldos / FIFO — [unit/invoices/test_balance_algorithm.py](unit/invoices/test_balance_algorithm.py) - **`test_net_balance_accounts_for_returns_and_entry_void`**: `_net_balance_for_lot` con ENTRY, CONSUMPTION, RETURN, ENTRY_VOID (fórmula documentada en el docstring: 10 - 4 + 1 - 2 = 5). - **`test_fifo_consumption_algorithm_uses_oldest_lots_first`**: `compare_balances` con dos `AvailableLot` (órdenes 1 y 2), demanda 6; parche `_resolve_import_uom` → PZA; comprueba consumo 3+3 y `quantity_used == 6`. #### Unit IVA / moneda — [unit/invoices/test_currency_conversion_imports.py](unit/invoices/test_currency_conversion_imports.py) - Tres monedas en `assign_values_iva_lines`: **FOREIGN (ME)**, **LOCAL (MN)**, **MANUAL (MC)** con `SimpleNamespace` (sin BD). ### Diagrama de flujo de negocio (E2E + integración export) ```mermaid sequenceDiagram participant T as Test participant API as POST process participant DB as Session PostgreSQL T->>DB: seed ref + catalogs + import invoice T->>API: process import API->>DB: ENTRY movements T->>DB: export invoice linked T->>API: process export alt qty OK API->>DB: CONSUMPTION + discharges else qty over balance API-->>T: ValidationException end ``` ### Resumen de edge cases explícitos en la suite - **Tipo factura import**: `TEM` genera saldo; `DEF` no. - **Saldo insuficiente en export**: error y sin consumos. - **Doble procesamiento import**: error en segundo POST y una sola entrada. - **Neteo de movimientos**: retornos y anulaciones de entrada en el cálculo de saldo. - **FIFO**: reparto entre lotes por `order_peps`. - **Moneda / IVA**: tres modos de captura y conversión esperada. ### BD compartida / CI Si `TEST_DATABASE_URL` apunta a una base con datos previos, los builders reutilizan filas existentes donde aplica (por ejemplo direcciones de clientes vía `_ensure_client_provider_address`). Para máximo aislamiento, usar una BD o esquema dedicado a tests. --- ## Prerequisites - PostgreSQL test database available. - Environment variable: - `TEST_DATABASE_URL=postgresql://user:pass@host:5432/db_name` ## Run commands - Fast subset (CI gate): - `pytest -q tests/unit tests/integration` - Full suite: - `pytest -q tests` - `pytest tests -v -ra` - `pytest tests -v -ra -s` - E2E only: - `pytest -q tests/e2e` ## CI recommendations - Pull request gate: - Run unit + integration on every PR. - Nightly / main branch: - Run full suite including E2E. - Keep Celery eager mode for deterministic process endpoint tests: - `task_always_eager=True` - `task_eager_propagates=True`