Ejemplos
Prueba fallos SMTP transitorios, entregas en pausa y recuperación de conexión con Vitest.Inyección de fallos SMTP + Vitest
Prueba las rutas de reintento, concurrencia y recuperación de conexiones en el límite SMTP real. Este proyecto independiente de Vitest usa el fixture de InboxTap para el entorno de pruebas, un transporte local dinámico de Nodemailer y un buzón nuevo en cada prueba.
Requisitos previos
- Bun 1.3 o posterior
Configuración
bun install --frozen-lockfileInboxTap está fijado en la versión 1.2.0, la primera que incluye server.faults.
Ejecutar las pruebas
bun run testEl conjunto de pruebas inicia InboxTap en puertos dinámicos de la interfaz de bucle local y cierra el transporte y los procesos de escucha cuando termina el archivo.
Qué demuestra el ejemplo
Las tres pruebas ejercitan el comportamiento SMTP en el nivel de entrega:
- una respuesta
451que el código de la aplicación reintenta una vez; - una pausa dirigida a un destinatario mientras se completa una transacción no relacionada; y
- una interrupción de la conexión seguida de una entrega correcta mediante una conexión nueva.
Las entregas fallidas o desconectadas nunca llegan al buzón. Un mensaje pausado solo aparece después de liberar su compuerta.
Reintento transitorio
Registra el fallo antes de activar la aplicación. La siguiente transacción que coincida consume la
regla cuando alcanza DATA:
inboxTap.server.faults.failNext({
code: 451,
message: "Temporary local failure",
to: inbox.address,
});
const attempts = await sendWithOneRetry(() =>
inboxTap.transport.sendMail({
from: "app@local.test",
to: inbox.address,
subject: "Retry receipt",
text: "The application retries this delivery once.",
}),
);sendWithOneRetry() pertenece a la aplicación de este ejemplo. InboxTap devuelve la respuesta
SMTP configurada; no reintenta, conserva ni elimina duplicados del trabajo de la aplicación.
Pausar y liberar
pauseNext() devuelve una compuerta aislada. Esperar hasta que la transacción esté en pausa hace
que el punto de concurrencia sea determinista:
const gate = inboxTap.server.faults.pauseNext({ to: inbox.address });
const pausedDelivery = inboxTap.transport.sendMail({
from: "app@local.test",
to: inbox.address,
subject: "Paused receipt",
text: "This message is not captured until release.",
});
await gate.waitUntilPaused();
expect(await inbox.messages()).toHaveLength(0);
await sendUnrelatedMessage();
gate.release();
await pausedDelivery;La prueba libera la compuerta en finally, de modo que un fallo de aserción no pueda dejar la
transacción SMTP en espera.
Recuperación tras una desconexión
Los umbrales de desconexión se aplican por fragmentos de datos. Un umbral de cero intenta cerrar
la conexión en cuanto empieza el procesamiento de DATA:
inboxTap.server.faults.disconnectNext({
afterBytes: 0,
to: inbox.address,
});
await expect(sendInterruptedMessage()).rejects.toBeInstanceOf(Error);
expect(await inbox.messages()).toHaveLength(0);
await sendRecoveredMessage();
expect(await inbox.messages()).toHaveLength(1);El segundo envío demuestra la recuperación del transporte: es una transacción SMTP nueva y no hereda el fallo ya consumido.
Límites de responsabilidad
InboxTap se ocupa de capturar SMTP en local y de aplicar fallos de entrega deterministas. La aplicación sigue siendo responsable de la política de reintentos, los trabajos duraderos, la idempotencia y la eliminación de duplicados según la lógica de negocio. La función auxiliar de reintento de este ejemplo es deliberadamente pequeña para que esas responsabilidades sean visibles.
Solución de problemas
- La regla de fallo afecta a otra prueba: dirígela siempre a la dirección
inbox.addressinyectada; cada prueba recibe un buzón nuevo. - Una prueba en pausa agota el tiempo de espera: llama a
waitUntilPaused()antes de inspeccionar el estado y libera la compuerta enfinally. - Una aserción de desconexión espera un estado SMTP: las interrupciones de conexión no incluyen una respuesta SMTP; comprueba que la promesa se rechace y verifica después la recuperación.
- Una dependencia resuelve una API que aún no se ha publicado: instala con el archivo de
bloqueo incluido y conserva
inboxtapfijado exactamente en la versión1.2.0.