# 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 (alineado con el fixture `test_tenant`).

### 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. Fixture `test_tenant`: asigna IDs efímeros altos y crea tenant + company; el `app` usa ese `tenant_id` en el usuario fake.
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 &lt; 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@127.0.0.1:5432/db_name` (sustituye por el host real alcanzable desde el runner; no uses el literal `host` de los ejemplos genéricos)

## 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`
