78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
"""mailu — pre-op seed hooks. Creates a test mailbox AND injects a test message to prove
|
|
backup→restore data integrity on BOTH the admin sqlite /data volume and the imap mail /mail
|
|
volume (P4 coverage, phase-mailu, ADV-mailu-01 fix)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
|
from harness import lifecycle # noqa: E402
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "functional"))
|
|
import _mailu # noqa: E402
|
|
|
|
_CI_LOCALPART = "citest"
|
|
_CI_PASSWORD = "CcCi-BackupTest1!Aa"
|
|
_CI_MAIL_SUBJECT = "ccci-backup-probe"
|
|
|
|
|
|
def pre_backup(ctx):
|
|
mail_domain = ctx.domain
|
|
_mailu.ensure_domain(ctx.domain, mail_domain)
|
|
_mailu.create_user(ctx.domain, _CI_LOCALPART, mail_domain, _CI_PASSWORD)
|
|
|
|
# Inject a test message so /mail (Maildir) is also covered by the backup/restore cycle.
|
|
# Uses in-container sendmail (same path as test_mail_flow.py: no greylist, no TLS needed).
|
|
email = f"{_CI_LOCALPART}@{mail_domain}"
|
|
sender = f"admin@{mail_domain}"
|
|
msg = (
|
|
f"From: {sender}\\n"
|
|
f"To: {email}\\n"
|
|
f"Subject: {_CI_MAIL_SUBJECT}\\n"
|
|
f"\\nbody {_CI_MAIL_SUBJECT}\\n"
|
|
)
|
|
lifecycle.exec_in_app(
|
|
ctx.domain, ["sh", "-c", f"printf '{msg}' | sendmail -f {sender} {email}"], service="smtp"
|
|
)
|
|
|
|
# Wait for dovecot to deliver the message (poll doveadm, ≤60s)
|
|
deadline = time.time() + 60
|
|
while time.time() < deadline:
|
|
out = lifecycle.exec_in_app(
|
|
ctx.domain,
|
|
["sh", "-c", f"doveadm search -u '{email}' mailbox INBOX header subject '{_CI_MAIL_SUBJECT}'"],
|
|
service="imap",
|
|
)
|
|
if out.strip():
|
|
return
|
|
time.sleep(3)
|
|
raise RuntimeError(
|
|
f"pre_backup: test message {_CI_MAIL_SUBJECT!r} not delivered to {email} within 60s"
|
|
)
|
|
|
|
|
|
def pre_restore(ctx):
|
|
mail_domain = ctx.domain
|
|
|
|
# 1. Delete the user from sqlite (/data) — wipes the account record
|
|
lifecycle.exec_in_app(
|
|
ctx.domain,
|
|
[
|
|
"python3",
|
|
"-c",
|
|
f"import sqlite3; db=sqlite3.connect('/data/main.db'); "
|
|
f"db.execute(\"DELETE FROM user WHERE localpart='{_CI_LOCALPART}'\"); db.commit()",
|
|
],
|
|
service="admin",
|
|
)
|
|
|
|
# 2. Wipe the user's Maildir (/mail) — wipes the mail data so restore is observable
|
|
lifecycle.exec_in_app(
|
|
ctx.domain,
|
|
["sh", "-c", f"rm -rf /mail/{mail_domain}/{_CI_LOCALPART}"],
|
|
service="imap",
|
|
)
|