"""Mail account config — offline (no live IMAP/SMTP).""" from modules.mail import backend as mail def test_account_roundtrip_and_password_masking(tmp_path, monkeypatch): monkeypatch.setattr(mail, "_ACCOUNT_FILE", tmp_path / "acct.json") assert mail.load_accounts() == [] pub = mail.save_account(None, { "username": "me@icloud.com", "password": "app-specific-pw", "imap_host": "imap.mail.me.com", "from_addr": "nexus@enderofwings.com", }) account_id = pub["id"] # public view exposes a flag, never the secret assert pub["has_password"] is True assert "password" not in pub assert pub["configured"] is True assert mail.is_configured(account_id) is True # a blank password on update keeps the stored one (write-only field) mail.save_account(account_id, {"from_name": "Nexus"}) assert mail.get_account(account_id)["password"] == "app-specific-pw" assert mail.get_account(account_id)["from_name"] == "Nexus" # defaults target iCloud assert mail.get_account(account_id)["smtp_host"] == "smtp.mail.me.com" def test_multiple_accounts_are_independent(tmp_path, monkeypatch): monkeypatch.setattr(mail, "_ACCOUNT_FILE", tmp_path / "acct.json") a = mail.save_account(None, {"username": "a@icloud.com", "password": "pw-a", "label": "Personal"}) b = mail.save_account(None, {"username": "b@icloud.com", "password": "pw-b", "label": "Work"}) assert a["id"] != b["id"] accounts = mail.public_accounts() assert {x["id"] for x in accounts} == {a["id"], b["id"]} mail.delete_account(a["id"]) remaining = mail.public_accounts() assert len(remaining) == 1 assert remaining[0]["id"] == b["id"] assert mail.is_configured(a["id"]) is False def test_creds_file_lives_outside_the_db(monkeypatch, tmp_path): # Mail secrets must never ride in memory.db (which bin/sync.py dumps to git). monkeypatch.setattr(mail, "_ACCOUNT_FILE", tmp_path / "acct.json") mail.save_account(None, {"username": "u", "password": "p"}) assert (tmp_path / "acct.json").exists() from synapse.memory.store import PersistentMemoryStore assert "password" not in PersistentMemoryStore._SETTINGS_DEFAULTS def test_legacy_single_account_file_migrates(tmp_path, monkeypatch): import json f = tmp_path / "acct.json" f.write_text(json.dumps({ "imap_host": "imap.mail.me.com", "imap_port": 993, "smtp_host": "smtp.mail.me.com", "smtp_port": 587, "username": "legacy@icloud.com", "password": "old-pw", "from_addr": "legacy@enderofwings.com", "from_name": "", })) monkeypatch.setattr(mail, "_ACCOUNT_FILE", f) accounts = mail.load_accounts() assert len(accounts) == 1 assert accounts[0]["username"] == "legacy@icloud.com" assert "id" in accounts[0] # migration is persisted — the file is rewritten in list form assert json.loads(f.read_text())["accounts"][0]["username"] == "legacy@icloud.com"