feat(mailu): add ops.py + backup/restore tests + update PARITY.md (P4 now covered via PR#3)
This commit is contained in:
@ -28,13 +28,21 @@ email stack: nginx front + admin + postfix/smtp + dovecot/imap + rspamd/antispam
|
||||
network IMAP-auth test was dropped: under notls dovecot disallows plaintext network auth, so a
|
||||
host-side login is not a meaningful signal here.)
|
||||
|
||||
## Backup data-integrity (P4) — N/A (recipe ships no backup config)
|
||||
The upstream mailu recipe declares **no `backupbot.backup` label** on any service, so the cc-ci
|
||||
backup/restore tiers cleanly SKIP (`backup_capable=False`). There is no recipe backup mechanism to
|
||||
exercise — P4 is genuinely N/A for mailu as published, not a cut corner. The durable fix (if P4
|
||||
coverage is wanted) is a recipe-PR adding backupbot labels (mailu admin sqlite at /data + mail
|
||||
volume), filed as a deferral mirroring the immich Q3.5 / Q3.2b pattern — see DEFERRED.md. Pending
|
||||
Adversary §7.1 sign-off on the N/A.
|
||||
## Backup data-integrity (P4) — COVERED (phase-mailu, 2026-06-11)
|
||||
|
||||
P4 is now **earned** via recipe-mirror PR#3 (`add-backupbot-labels`) on
|
||||
`git.autonomic.zone/recipe-maintainers/mailu`. That PR adds backupbot v2 labels to the `admin`
|
||||
service (`/data` — sqlite DB) and `imap` service (`/mail` — Maildir), making `backup_capable=True`
|
||||
at the PR head.
|
||||
|
||||
cc-ci tests (all in `tests/mailu/`):
|
||||
- `ops.py` — `pre_backup`: seeds `citest@<domain>` via `flask mailu user`; `pre_restore`: deletes
|
||||
it via sqlite3 Python to simulate data loss.
|
||||
- `test_backup.py` — asserts `citest@<domain>` is in `config-export` at backup time.
|
||||
- `test_restore.py` — asserts `citest@<domain>` is back in `config-export` after restore.
|
||||
|
||||
Auto-detected: `generic.backup_capable()` scans the compose.yml for `backupbot.backup.*true` and
|
||||
returns `True` at the PR head — no `BACKUP_CAPABLE` override needed in `recipe_meta.py`.
|
||||
|
||||
## Browser flow (P6)
|
||||
Not added: mailu's user-facing UX (webmail/admin) is a standard web UI; the characteristic behaviour
|
||||
|
||||
36
tests/mailu/ops.py
Normal file
36
tests/mailu/ops.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""mailu — pre-op seed hooks. Creates / deletes a test mailbox in the admin sqlite DB to prove
|
||||
backup→restore data integrity on real mail data (P4 coverage, phase-mailu)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
|
||||
from harness import lifecycle # noqa: E402
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _mailu # noqa: E402
|
||||
|
||||
_CI_LOCALPART = "citest"
|
||||
_CI_PASSWORD = "CcCi-BackupTest1!Aa"
|
||||
|
||||
|
||||
def pre_backup(ctx):
|
||||
_mailu.ensure_domain(ctx.domain, ctx.domain)
|
||||
_mailu.create_user(ctx.domain, _CI_LOCALPART, ctx.domain, _CI_PASSWORD)
|
||||
|
||||
|
||||
def pre_restore(ctx):
|
||||
# Delete the seeded user directly from sqlite to simulate data loss before restore.
|
||||
# (flask mailu has no user-delete subcommand in 2024.06.52; sqlite3 module is always available.)
|
||||
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",
|
||||
)
|
||||
25
tests/mailu/test_backup.py
Normal file
25
tests/mailu/test_backup.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""mailu — BACKUP overlay: assert the seeded mailbox is present at backup time.
|
||||
|
||||
ops.pre_backup created citest@<domain> via the admin container; this overlay verifies the admin
|
||||
sqlite DB contains that user at the moment the backup is taken. The backup→restore divergence
|
||||
is in ops.pre_restore (which deletes the user before restore runs)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _mailu # noqa: E402
|
||||
|
||||
_CI_LOCALPART = "citest"
|
||||
|
||||
|
||||
def test_backup_captures_mailbox(live_app):
|
||||
email = f"{_CI_LOCALPART}@{live_app}"
|
||||
cfg = _mailu.config_export(live_app)
|
||||
emails = _mailu.user_emails(cfg)
|
||||
assert email in emails, (
|
||||
f"seeded mailbox {email!r} not found in config-export at backup time; "
|
||||
f"users present: {emails}"
|
||||
)
|
||||
26
tests/mailu/test_restore.py
Normal file
26
tests/mailu/test_restore.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""mailu — RESTORE overlay: assert the seeded mailbox is back after restore.
|
||||
|
||||
ops.pre_restore deleted citest@<domain> from the admin sqlite to simulate data loss; this
|
||||
overlay verifies that abra app restore brings the user back (the /data sqlite volume is
|
||||
restored from the backup taken by pre_backup, which contained the seeded user)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
import _mailu # noqa: E402
|
||||
|
||||
_CI_LOCALPART = "citest"
|
||||
|
||||
|
||||
def test_restore_returns_mailbox(live_app):
|
||||
email = f"{_CI_LOCALPART}@{live_app}"
|
||||
cfg = _mailu.config_export(live_app)
|
||||
emails = _mailu.user_emails(cfg)
|
||||
assert email in emails, (
|
||||
f"seeded mailbox {email!r} not found in config-export after restore; "
|
||||
f"restore did not return the pre-mutation mail data. "
|
||||
f"users present: {emails}"
|
||||
)
|
||||
Reference in New Issue
Block a user