Add an end-to-end integration harness that runs the webmail against a real Stalwart mail server in Docker and drives it with Playwright, focused on the mail/folder synchronisation behaviour (unread/total counters, folder-list sync, account-scoped Unified Mailbox) that the unified-mailbox work touches. - integration/ stack: Stalwart (declarative bootstrap: alice/bob/carol, submission + IMAP listeners, permissive CORS) + webmail (dev mode, so the browser's plaintext cross-origin JMAP calls aren't blocked by the prod CSP). - Helpers: dependency-free SMTP submit client, JMAP client for seeding / inspecting server state, and page helpers (login, add/switch account, locale-independent folder-counter reads). - Specs: login, single-account sync (receive/read/move/delete/folder-create/ burst) and multi-account (per-account isolation + cross-account Unified Inbox aggregation + background-account delivery). 12 tests, all green. - Add focused data-testid hooks to the mail UI (folder rows + counters, email list items, account switcher, composer) for stable selectors. - Exclude examples/ and integration/ from tsconfig/eslint/.dockerignore.
130 lines
4.5 KiB
Bash
Executable File
130 lines
4.5 KiB
Bash
Executable File
#!/bin/sh
|
|
# Declarative bootstrap for the integration-test Stalwart Mail Server.
|
|
#
|
|
# Phase 1 (first start only, marker absent):
|
|
# - Stalwart starts in bootstrap mode (no config.json -> HTTP on :8080).
|
|
# - plan-bootstrap.ndjson is applied via stalwart-cli. This writes
|
|
# config.json, initialises RocksDB and creates the default domain +
|
|
# admin account.
|
|
# - Stalwart is restarted in normal mode (config.json now exists).
|
|
# - plan-accounts.ndjson.tpl is materialised with the resolved DOMAIN_ID
|
|
# and the shared TEST_ACCOUNT_PASSWORD, then applied (test accounts +
|
|
# submission/IMAP listeners + cleartext auth for the dev lanes).
|
|
# - Stalwart is stopped and the marker is written.
|
|
#
|
|
# Phase 2 (regular start, marker present):
|
|
# - exec stalwart as PID 1.
|
|
#
|
|
# Adapted from examples/docker/stalwart for webmail<->Stalwart integration
|
|
# testing: no ticket/service accounts, no Sieve, a single shared password for
|
|
# the alice/bob/carol test mailboxes.
|
|
|
|
set -eu
|
|
|
|
# stalwart-cli caches its schema under $HOME/.cache/stalwart-cli. The stalwart
|
|
# user has no home, so redirect to /tmp.
|
|
export HOME=/tmp
|
|
|
|
DATA_DIR=/var/lib/stalwart
|
|
MARKER="${DATA_DIR}/.bootstrap-applied"
|
|
PLAN_DIR=/etc/stalwart-bootstrap
|
|
STALWART_BIN=/usr/local/bin/stalwart
|
|
STALWART_CLI=/usr/local/bin/stalwart-cli
|
|
STALWART_CFG=/etc/stalwart/config.json
|
|
LOCAL_URL=http://127.0.0.1:8080
|
|
|
|
log() { printf '[stalwart-bootstrap] %s\n' "$*" >&2; }
|
|
|
|
wait_for_http() {
|
|
for _ in $(seq 1 60); do
|
|
if curl -fsS -u "admin:${ADMIN_PASS}" "${LOCAL_URL}/jmap/session" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
log "Stalwart HTTP on :8080 did not come up in time"
|
|
return 1
|
|
}
|
|
|
|
run_stalwart_bg() {
|
|
"${STALWART_BIN}" --config "${STALWART_CFG}" &
|
|
STALWART_PID=$!
|
|
}
|
|
|
|
stop_stalwart_bg() {
|
|
if [ -n "${STALWART_PID:-}" ]; then
|
|
kill -TERM "${STALWART_PID}" 2>/dev/null || true
|
|
wait "${STALWART_PID}" 2>/dev/null || true
|
|
STALWART_PID=
|
|
fi
|
|
}
|
|
|
|
if [ ! -f "${MARKER}" ]; then
|
|
: "${STALWART_RECOVERY_ADMIN:?must be set for first-run bootstrap}"
|
|
: "${TEST_ACCOUNT_PASSWORD:?must be set for first-run bootstrap}"
|
|
|
|
ADMIN_PASS=${STALWART_RECOVERY_ADMIN#*:}
|
|
|
|
log "Phase 1: starting Stalwart in bootstrap mode"
|
|
run_stalwart_bg
|
|
wait_for_http
|
|
|
|
log "Applying plan-bootstrap.ndjson"
|
|
STALWART_URL=${LOCAL_URL} \
|
|
STALWART_USER=admin \
|
|
STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" apply --file "${PLAN_DIR}/plan-bootstrap.ndjson" --quiet
|
|
|
|
log "Restarting Stalwart to leave bootstrap mode"
|
|
stop_stalwart_bg
|
|
run_stalwart_bg
|
|
wait_for_http
|
|
|
|
log "Resolving DOMAIN_ID for example.org"
|
|
DOMAIN_ID=$(STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" query Domain --json 2>/dev/null \
|
|
| head -1 \
|
|
| sed -E 's/.*"id":"([^"]+)".*/\1/')
|
|
if [ -z "${DOMAIN_ID}" ]; then
|
|
log "Could not resolve DOMAIN_ID after bootstrap"
|
|
stop_stalwart_bg
|
|
exit 1
|
|
fi
|
|
log "DOMAIN_ID=${DOMAIN_ID}"
|
|
|
|
# Materialise the account plan. gettext/envsubst is not in the base image,
|
|
# so substitute the two placeholders with sed. Passwords are escaped for the
|
|
# sed replacement (& and / are the only metacharacters that matter here).
|
|
PLAN_ACCOUNTS=/tmp/plan-accounts.ndjson
|
|
esc_pw=$(printf '%s' "${TEST_ACCOUNT_PASSWORD}" | sed -e 's/[&/\\]/\\&/g')
|
|
sed -e "s/\${DOMAIN_ID}/${DOMAIN_ID}/g" \
|
|
-e "s/\${TEST_ACCOUNT_PASSWORD}/${esc_pw}/g" \
|
|
"${PLAN_DIR}/plan-accounts.ndjson.tpl" > "${PLAN_ACCOUNTS}"
|
|
|
|
log "Applying plan-accounts.ndjson"
|
|
STALWART_URL=${LOCAL_URL} \
|
|
STALWART_USER=admin \
|
|
STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" apply --file "${PLAN_ACCOUNTS}" --quiet
|
|
rm -f "${PLAN_ACCOUNTS}"
|
|
|
|
# Default inbound throttles (sender->recipient + sender-IP) otherwise trip
|
|
# 452 4.4.5 when a test blasts many messages. Stalwart re-seeds the defaults
|
|
# on every start when absent, so deleting is useless; disable them instead,
|
|
# which survives restarts.
|
|
for tid in $(STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" query MtaInboundThrottle --json 2>/dev/null \
|
|
| sed -E 's/.*"id":"([^"]+)".*/\1/'); do
|
|
log "Disabling MtaInboundThrottle ${tid}"
|
|
STALWART_URL=${LOCAL_URL} STALWART_USER=admin STALWART_PASSWORD=${ADMIN_PASS} \
|
|
"${STALWART_CLI}" update MtaInboundThrottle "${tid}" --field enable=false >/dev/null
|
|
done
|
|
|
|
log "Stopping bootstrap instance, marking complete"
|
|
stop_stalwart_bg
|
|
touch "${MARKER}"
|
|
fi
|
|
|
|
log "Starting Stalwart (final, foreground)"
|
|
exec "${STALWART_BIN}" --config "${STALWART_CFG}"
|