Adds the tests/harness helper package: address(), available(), reset(),
users(), smtp_provider() and mailbox_config().
Tests address mailboxes as harness.address("<test name>"), which slugs the name
under the RFC 2606 reserved harness.email-connect.test domain, so distinct test
names cannot collide and a stray send cannot leave the host. No provisioning
call is needed -- the auth-disabled harness creates the mailbox on first login.
reset() drops every user and message via GreenMail's service reset; the next
login recreates the mailbox empty. It is documented to run at test start so a
crashed test cannot leak state forward, and it is global, so resetting tests
cannot run in parallel against one harness.
Suite: 64 passed with the harness up, 61 passed + 3 skipped with it down.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| __init__.py | ||
| docker-compose.yml | ||
| README.md | ||
Local mail test harness
A single GreenMail container serving SMTP and IMAP on loopback, so integration tests can drive the transactional send path and read the result back through the scanner without a live provider.
Part of EMAIL-WP-0005. Unit tests do not need this harness — they run against
fixture directories and temporary Maildir trees and stay offline.
Start and stop
docker compose -f tests/harness/docker-compose.yml up -d
docker compose -f tests/harness/docker-compose.yml down -v
Readiness (the container healthcheck probes both mail ports; the API also answers):
docker inspect --format '{{.State.Health.Status}}' email-connect-harness
curl -s http://127.0.0.1:8080/api/service/readiness
Ports
| Port | Protocol | Notes |
|---|---|---|
| 3025 | SMTP | plaintext, no STARTTLS |
| 3143 | IMAP | plaintext |
| 8080 | HTTP | GreenMail API — readiness, user and mail management |
All three bind 127.0.0.1 only. The harness accepts any credentials and must
never be exposed beyond the host.
Accounts
The harness runs with greenmail.auth.disabled, so any login is accepted and
the mailbox is created on first use. No provisioning step is needed: pick an
address under harness.email-connect.test and log in.
export EMAIL_CONNECT_IMAP_USER=t01@harness.email-connect.test
export EMAIL_CONNECT_IMAP_PASSWORD=harness # ignored, but must be set
These credentials are deliberately non-secret and belong in the repo. Real
provider material is routed through OpenBao — see
.claude/rules/credential-routing.md. Never point this harness at real
credentials or a real mailbox.
Addressing convention
Tests address mailboxes through the harness helper package rather than
hardcoding strings:
import harness
recipient = harness.address("invitation resend")
# -> invitation-resend@harness.email-connect.test
harness.address() slugs the name, so each test picks its own address and two
tests cannot collide. .test is reserved by RFC 2606, so a stray send can never
leave the host.
Reset contract
harness.reset() drops every user and message — GreenMail's reset clears
accounts along with their mail, and the next login recreates the mailbox empty.
Call it at the start of a test, not the end: a crashed or interrupted test then cannot leave state behind for the next one. It is global, so tests that reset cannot run in parallel against one harness.
def test_something():
harness.reset()
...
Skipping when the harness is down
Harness-dependent tests are gated so the default suite stays offline:
requires_harness = pytest.mark.skipif(
not harness.available(),
reason="mail harness not running",
)
harness.available() is evaluated at import time, so start the harness before
invoking pytest.
Scanning the harness
docker compose -f tests/harness/docker-compose.yml up -d
export EMAIL_CONNECT_IMAP_USER=t01@harness.email-connect.test
export EMAIL_CONNECT_IMAP_PASSWORD=harness
email-connect scan-mailbox --config config/harness-imap.yml --out reports/
Sending through the harness
GreenMail offers no STARTTLS, only plaintext or implicit TLS with a self-signed certificate. The transactional service therefore needs its transport mode set explicitly:
export EMAIL_CONNECT_SMTP_HOST=127.0.0.1
export EMAIL_CONNECT_SMTP_PORT=3025
export EMAIL_CONNECT_SMTP_SECURITY=plaintext
export EMAIL_CONNECT_SMTP_USERNAME=sender@harness.email-connect.test
export EMAIL_CONNECT_SMTP_PASSWORD=harness
export EMAIL_CONNECT_SENDER=noreply@harness.email-connect.test
EMAIL_CONNECT_SMTP_SECURITY defaults to starttls. plaintext is rejected
for any non-loopback host, so this setting cannot weaken a real deployment: it
fails at startup rather than sending credentials in the clear.
What harness mail proves
Nothing beyond provider_accepted. A message sitting in a GreenMail mailbox is
not evidence of inbox placement, recipient awareness, identity, or
authorization. Bounces, complaints, and deferrals are not reproducible here
— GreenMail accepts everything. Those classes stay on crafted .eml fixtures
and an optional provider-simulator tier (EMAIL-WP-0005-T05).