# `fake-smtp-client.ts` ## Purpose Deterministic test double for `SmtpClientLike`. Tests queue scripted responses, invoke the wrapped `SmtpTransport`, and inspect the captured calls — without `vi.mock`, without a real SMTP server, and without monkey-patching globals. Shipping the fake from the package itself means downstream consumers can test their integration against the same exact shape `SmtpTransport` produces, without re-implementing the protocol fake. ## Features - `push(response)` queues a scripted outcome (`info` for success, `reject` for failure). - `calls` captures every `sendMail` invocation in FIFO order so assertions can target individual recipients / headers / attachments. - `scheduleVerify(true | false | Error)` programs the next `verify()` call. - `verifyCalls` and `closeCalls` track lifecycle so tests can assert cleanup behaviour. ## Usage: Basic ```ts import { FakeSmtpClient } from "@suaveplan/emailer/fakes/fake-smtp-client"; import { SmtpTransport } from "@suaveplan/emailer/transports/smtp"; const adapter = new FakeSmtpClient(); adapter.push({ info: { messageId: "" } }); const transport = new SmtpTransport(); await transport.initialize({ client: adapter }); const r = await transport.send({ to: "u@x.y", subject: "hi", text: "hi" }); ``` ## Usage: Advanced ```ts import { FakeSmtpClient } from "@suaveplan/emailer/fakes/fake-smtp-client"; const adapter = new FakeSmtpClient(); adapter.push({ reject: new Error("connection refused") }); adapter.push({ info: { messageId: "<1@h>", accepted: ["a@b.c"] } }); adapter.scheduleVerify(true); // First send fails, second succeeds, verify returns true. ``` ## API Reference - `class FakeSmtpClient implements SmtpClientLike` - `interface FakeSmtpResponse { info?, reject? }` - `interface FakeSmtpCall { payload: SmtpSendPayload }` ## Implementation Notes - The fake is intentionally minimal — it carries no SMTP state, no auth, no connection pool. The point is that `SmtpTransport` only ever calls three methods (`sendMail`, `verify`, `close`), and the fake reproduces exactly those three semantics with FIFO scripting. - `scheduleVerify(Error)` is single-use — after the next `verify` throws, the reject queue clears. Subsequent calls return the most-recently scheduled boolean (default `true`). - `closeCalls` increments on every `close`, allowing tests to assert idempotent close behaviour without inspecting private state.