DEV Community

Olivia Cheng
Olivia Cheng

Posted on

FastAPI: contratos para correos temporales en CI

Una bandeja de correo temporal puede hacer que una prueba de integración sea rápida y repetible, pero solo si cada ejecución tiene límites claros. En esta guía veremos un contrato pequeño para FastAPI, pytest y CI.

Por qué un correo temporal necesita un contrato

En un flujo de registro, la aplicación crea un usuario, envía un código y espera que el test lo lea. El problema aparece cuando varias ejecuciones comparten el mismo buzón: un mensaje viejo parece válido, dos jobs leen el mismo código o una prueba falla solo cuando hay carga.

La solución no es añadir sleep(10) a todo. Es tratar el buzón como un recurso temporal, con un dueño, un identificador de ejecución y un momento de expiración. El contrato debería responder cuatro preguntas:

  • ¿Qué job creó este buzón?
  • ¿Qué mensajes puede consumir?
  • ¿Cuánto tiempo puede esperar?
  • ¿Quién lo limpia si el test falla?

Este enfoque también ayuda a separar la aplicación de la infraestructura de correo. Una opción de mantener el layout estable en React sirve para la interfaz; en backend, el equivalente es no mezclar la lógica del registro con los detalles del buzón.

El contrato mínimo para cada ejecución

Para cada suite genero un alias o buzón con un nombre derivado del run_id. No debe contener datos personales ni quedar reutilizable por otro job. El objeto que circula entre los tests puede ser tan pequeño como este:

from dataclasses import dataclass
from datetime import datetime


@dataclass(frozen=True)
class MailboxLease:
    address: str
    run_id: str
    expires_at: datetime
Enter fullscreen mode Exit fullscreen mode

El run_id es más importante de lo que parece. Si el pipeline reintenta una tarea, el segundo intento puede recibir un identificador nuevo y no confundir los mensajes del primero. Para borrar un lease, conviene usar una operación idempotente: si ya no existe, la limpieza sigue siendo correcta.

También defino un estado explícito para la espera: created, message_found, consumed, expired o failed. Los estados hacen que el log sea util, especialmente cuando el fallo ocurre en un runner remoto y no tenemos la bandeja abierta delante.

Un fixture pequeño con FastAPI y pytest

El fixture puede crear el buzón antes de cada test y revocarlo al terminar. La API de correo queda detrás de un cliente, así el test no sabe si usa un servicio real, un servidor local o un double en desarrollo.

import os
import uuid
import pytest


@pytest.fixture
def mailbox(mail_client):
    run_id = os.getenv("CI_JOB_ID", str(uuid.uuid4()))
    lease = mail_client.create_lease(run_id=run_id, ttl_seconds=300)
    yield lease
    mail_client.revoke_lease(lease.address)


def test_verificacion_de_usuario(client, mailbox, mail_client):
    response = client.post(
        "/signup",
        json={"email": mailbox.address, "password": "solo-para-test"},
    )
    assert response.status_code == 201

    message = mail_client.wait_for_message(
        mailbox.address,
        timeout_seconds=30,
        subject="Verifica tu cuenta",
    )
    assert message is not None
Enter fullscreen mode Exit fullscreen mode

En producción de tests, wait_for_message debería filtrar por destinatario, asunto y un token de correlación. Leer “el último email” es una atajo peligroso: otro test puede terminar justo antes y dejar un resultado falso positivo.

Si el equipo usa medir la activación por email en un SaaS, el mismo run_id puede conectar la entrega con el evento de activación sin guardar el contenido completo del mensaje. Menos datos retenidos significa menos ruido y menos riesgo.

Limpieza, expiración y diagnóstico

La limpieza debe ocurrir en dos capas. Primero, el fixture revoca el buzón en el camino normal. Segundo, un proceso periódico elimina leases expirados. Así una cancelación del runner no deja recursos abiertos para siempre.

En CI suelo guardar solo metadatos: run_id, estado, latencia de entrega y causa de fallo. El cuerpo del correo se puede ocultar o borrar después de extraer el código. Para depurar, una traza como esta es suficiente:

run=1842 state=created mailbox=ci-1842 expires_in=300s
run=1842 state=message_found wait=2.4s subject=Verifica tu cuenta
run=1842 state=consumed cleanup=ok
Enter fullscreen mode Exit fullscreen mode

Si necesitas un servicio externo para pruebas manuales, un temp mail so puede ser útil para aislar registros exploratorios. Para una suite automatizada, verifica primero su política de retención, límites y estabilidad; no conviene convertir un servicio público en dependencia invisible del pipeline.

Hay un detalle que suele colarse en tickets de QA: fake e mail com. Lo trato como una búsqueda o etiqueta de diagnóstico, nunca como un criterio para aceptar un mensaje. El criterio real debe ser el run_id y el destinatario exacto.

Checklist para CI

Antes de subir el flujo, reviso lo siguiente:

  1. Cada ejecución obtiene un buzón o alias único.
  2. El lease tiene TTL y propietario (run_id).
  3. La espera tiene timeout y no usa pausas fijas como estrategia principal.
  4. El mensaje se filtra por destinatario, asunto y correlación.
  5. La revocación se ejecuta incluso cuando el test falla.
  6. Existe una limpieza de respaldo para leases expirados.
  7. Los logs no imprimen códigos, tokens ni el cuerpo completo.
  8. Un reintento no consume mensajes de la ejecución anterior.

Este checklist parece pequeño, pero evita bastante trabajo repetido. Cuando el contrato está definido, cambiar de proveedor de email o ejecutar los tests en paralelo deja de ser una aventura.

Preguntas rápidas

¿Debo crear un buzón por test?

No siempre. Un buzón por suite puede ser suficiente si cada mensaje lleva un identificador único y el consumo es estricto. Para pruebas paralelas o flujos sensibles, un lease por test reduce interferencias.

¿Qué timeout elegir?

Empieza con el tiempo normal de entrega más un margen corto y mide. Un timeout de 30 segundos suele ser razonable para un entorno controlado, pero el valor correcto depende del proveedor y del runner.

¿Puedo guardar el email para investigar?

Solo durante el tiempo imprescindible y con datos minimizados. Enmascara direcciones y elimina tokens después de la ejecución. Un buen diagnóstico no necesita convertir cada fallo en un archivo permanente.

La idea central es simple: el correo de prueba también es infraestructura. Si tiene identidad, límites, estados y limpieza, FastAPI y CI pueden usarlo de forma predecible, incluso cuando los jobs corren al mismo tiempo.

Top comments (0)