first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
# assertMail
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`assertMail()` is a test assertion helper that polls the Mailpit REST API and fails tests that do not receive an expected email within a configurable timeout. It bridges the gap between sending an email via SMTP and verifying delivery in integration tests.
The helper is designed for use with `MailpitFixture` but works with any Mailpit instance accessible at a known HTTP URL. Its injectable `fetchFn` option makes it fully unit-testable without a live Mailpit server.
## Features
- **Polling-based**: retries at a configurable interval until the deadline, tolerating normal async email delivery latency.
- **Exact address matching**: verifies `To[].Address` exactly matches the provided `to` address.
- **Substring subject matching**: checks that the message `Subject` includes the provided `subject` string, allowing partial matches.
- **Configurable timeout and interval**: `timeoutMs` and `pollIntervalMs` options allow fast-failing in unit tests (e.g. 50ms timeout) and longer waits in real environments.
- **Injectable fetch**: the `fetchFn` option replaces `globalThis.fetch` for deterministic unit tests with no live network dependency.
- **Structured error message**: the timeout error includes the `to` and `subject` criteria and timeout value for easy debugging.
## Usage: Basic Example
```typescript
import { assertMail } from "@suaveplan/testing-integration-mailpit";
// After sending an email via SMTP...
await assertMail("http://localhost:8025", {
to: "user@example.com",
subject: "Welcome",
});
// Resolves when a matching message is found; throws after 10s otherwise.
```
## Usage: Advanced Example
```typescript
import {
MailpitFixture,
assertMail,
} from "@suaveplan/testing-integration-mailpit";
import nodemailer from "nodemailer";
const fixture = new MailpitFixture();
const config = await fixture.start();
const transporter = nodemailer.createTransport({ url: config.smtpUrl });
await transporter.sendMail({
from: "noreply@example.com",
to: "alice@example.com",
subject: "Password Reset Request",
text: "Click here to reset your password.",
});
// Custom timeout: wait up to 5 seconds, polling every 200ms
await assertMail(config.apiUrl, {
to: "alice@example.com",
subject: "Password Reset",
}, {
timeoutMs: 5000,
pollIntervalMs: 200,
});
await fixture.stop();
```
## API Reference
### `assertMail(apiUrl, criteria, options?): Promise<void>`
Polls the Mailpit API until a matching message is found or the timeout elapses.
#### Parameters
| Name | Type | Description |
|------|------|-------------|
| `apiUrl` | `string` | Mailpit API base URL (e.g. `http://localhost:8025`). |
| `criteria` | `AssertMailCriteria` | Matching criteria. |
| `options?` | `AssertMailOptions` | Optional configuration. |
#### Throws
`Error` when no matching message is found within `timeoutMs`. The message includes the `to` and `subject` values and elapsed timeout.
### `interface AssertMailCriteria`
| Field | Type | Description |
|-------|------|-------------|
| `to` | `string` | Exact recipient address to match in `To[].Address`. |
| `subject` | `string` | Subject substring to match via `String.includes()`. |
### `interface AssertMailOptions`
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `timeoutMs?` | `number` | `10_000` | Total wait time in ms. |
| `pollIntervalMs?` | `number` | `500` | Polling interval in ms. |
| `fetchFn?` | `typeof fetch` | `globalThis.fetch` | Injectable fetch for unit tests. |
## Implementation Notes
- **Polling strategy**: the helper uses a `while (Date.now() < deadline)` loop with `setTimeout` between iterations. This provides predictable behavior without relying on `setInterval` side effects.
- **Mailpit API shape**: the `/api/v1/messages` endpoint returns `{ messages: Array<{ To: [{Address: string}], Subject: string, ... }> }`. The `messages` key may be absent on an empty inbox; the helper defaults to `[]`.
- **Partial subject matching rationale**: email subjects often contain dynamic content (timestamps, user names) appended to a fixed prefix. Substring matching lets tests assert on the stable prefix without hardcoding dynamic parts.
- **Exact address matching rationale**: recipient addresses are authoritative identifiers. Substring matching on addresses would be error-prone (e.g. `user@example.com` matching `user@example.company`).