"""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__), "custom")) 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", )