Brings the public tree back in line with the development repo after several weeks of drift caused by a stale publish include list. New: - In-app update path: GET /update/check compares the checkout against origin/main and POST /update/apply runs `ncp upgrade` detached (pull, rebuild, restart). The sidebar shows the version, checks on click, and offers an "update available" pill. - Projects: a project workspace groups chats and RAG documents, with per-project instructions and document retrieval scoped to the active project. Replaces the standalone Documents page. - modules/: auto-discovered feature plugins (mail, network) with their frontend counterparts and tests. - Memory curation runs in-process (synapse/memory/curator.py) on the chat model when a conversation goes idle. The separate memory service on :8001 is gone, along with the launcher lines that started it. Also: the KDE theme, panel and Promethean terminal assets, the full test suite, and VERSION 1.2.0. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
76 lines
2.9 KiB
Python
76 lines
2.9 KiB
Python
"""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"
|