"""Secondary (non-INBOX) folder polling — multi-folder CONDSTORE sync (SPEC §6.3).""" from __future__ import annotations import uuid from datetime import UTC, datetime from email.message import EmailMessage from sqlalchemy import delete, func, select from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from sqlalchemy.pool import NullPool from app.config import get_settings from app.db import session_scope from app.identity.passwords import hash_password from app.models import Account, Mailbox, Message, User from app.providers.base import MailboxDelta, RemoteMessage from app.sync import engine as sync_engine def _raw(subject: str, mid: str) -> bytes: m = EmailMessage() m["Subject"] = subject m["From"] = "me@example.com" m["To"] = "bob@example.com" m["Message-ID"] = mid m["Date"] = "Mon, 09 Mar 2026 08:36:37 +0100" m.set_content("body") return m.as_bytes() async def test_secondary_poll_syncs_special_folders_not_inbox(monkeypatch) -> None: # type: ignore[no-untyped-def] engine = create_async_engine(get_settings().database_url, poolclass=NullPool) sm = async_sessionmaker(engine, expire_on_commit=False) session = sm() user = User(handle=f"sec{uuid.uuid4().hex[:8]}@test.local", password_hash=hash_password("pw")) session.add(user) await session.flush() account = Account( user_id=user.id, email="me@example.com", imap_config={}, smtp_config={}, secret_ct=b"", discovery_src="manual", ) session.add(account) await session.flush() inbox = Mailbox(account_id=account.id, user_id=user.id, path="INBOX", special_use="inbox") sent = Mailbox(account_id=account.id, user_id=user.id, path="SENT", special_use="sent") custom = Mailbox(account_id=account.id, user_id=user.id, path="Notes", special_use=None) session.add_all([inbox, sent, custom]) await session.commit() polled: list[str] = [] class FakeProvider: async def poll(self, mailbox: str, *, last_seen_uid: int, highestmodseq: int | None): # type: ignore[no-untyped-def] polled.append(mailbox) if mailbox == "SENT": return MailboxDelta( [ RemoteMessage( uid=5, raw=_raw("Sent one", ""), flags=frozenset({"\\Seen"}), internal_date=datetime.now(UTC), ) ], [], [], None, None, ) return MailboxDelta([], [], [], None, None) async def close(self) -> None: return None monkeypatch.setattr(sync_engine, "build_provider", lambda _a: FakeProvider()) try: await sync_engine._poll_secondary_once(account.id) # Only the non-INBOX special-use folder is polled (SENT); INBOX and the # custom folder (no special_use) are skipped. assert polled == ["SENT"] # The delta was applied: the sent message is now in the DB under SENT. session.expunge_all() n = await session.scalar( select(func.count()).select_from(Message).where(Message.mailbox_id == sent.id) ) assert n == 1 finally: async with session_scope() as cleanup: await cleanup.execute(delete(User).where(User.id == user.id)) await session.close() await engine.dispose()