Initial commit: webmail client (M0/M1)
Three-pane webmail client. - Backend: FastAPI + async SQLAlchemy + arq worker (Postgres/Redis), IMAP/SMTP sync, threading, search, drafts/outbox with undo, attachments, 2FA. - Frontend: React 18 + Vite + TypeScript + TanStack Query + Tailwind; three-pane UI with rich-text composer, light/dark themes, resizable panels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""Test helper: a user with two accounts/mailboxes + a message factory.
|
||||
|
||||
Kept out of ``conftest.py`` so tests can import :class:`ThreadingEnv` without
|
||||
re-importing the conftest module.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models import Account, Mailbox, Message, User
|
||||
from app.threading_.threader import thread_message
|
||||
|
||||
BASE = datetime(2026, 1, 1, 12, 0, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
class ThreadingEnv:
|
||||
"""A user with two accounts + one mailbox each, plus a message factory."""
|
||||
|
||||
def __init__(self, session: AsyncSession) -> None:
|
||||
self.session = session
|
||||
self.user_id: uuid.UUID
|
||||
self.account_a: uuid.UUID
|
||||
self.account_b: uuid.UUID
|
||||
self.mailbox_a: uuid.UUID
|
||||
self.mailbox_b: uuid.UUID
|
||||
self._uid = 0
|
||||
|
||||
async def setup(self) -> None:
|
||||
user = User(handle=f"t{uuid.uuid4().hex[:8]}@test.local", password_hash="x")
|
||||
self.session.add(user)
|
||||
await self.session.flush()
|
||||
self.user_id = user.id
|
||||
for label in ("a", "b"):
|
||||
account = Account(
|
||||
user_id=self.user_id,
|
||||
email=f"{label}@example.com",
|
||||
imap_config={},
|
||||
smtp_config={},
|
||||
secret_ct=b"",
|
||||
discovery_src="manual",
|
||||
)
|
||||
self.session.add(account)
|
||||
await self.session.flush()
|
||||
mailbox = Mailbox(account_id=account.id, user_id=self.user_id, path="INBOX")
|
||||
self.session.add(mailbox)
|
||||
await self.session.flush()
|
||||
setattr(self, f"account_{label}", account.id)
|
||||
setattr(self, f"mailbox_{label}", mailbox.id)
|
||||
|
||||
async def add(
|
||||
self,
|
||||
*,
|
||||
message_id: str | None,
|
||||
subject: str = "Subject",
|
||||
references: list[str] | None = None,
|
||||
in_reply_to: str | None = None,
|
||||
from_email: str = "alice@example.com",
|
||||
to_email: str = "bob@example.com",
|
||||
minutes: int = 0,
|
||||
account: str = "a",
|
||||
flags: list[str] | None = None,
|
||||
) -> Message:
|
||||
self._uid += 1
|
||||
msg = Message(
|
||||
user_id=self.user_id,
|
||||
account_id=getattr(self, f"account_{account}"),
|
||||
mailbox_id=getattr(self, f"mailbox_{account}"),
|
||||
uid=self._uid,
|
||||
message_id_hdr=message_id,
|
||||
in_reply_to=in_reply_to,
|
||||
references_hdr=references or [],
|
||||
subject=subject,
|
||||
from_addr=[{"name": "", "email": from_email}],
|
||||
to_addr=[{"name": "", "email": to_email}],
|
||||
internal_date=BASE + timedelta(minutes=minutes),
|
||||
flags=flags or [],
|
||||
blob_sha256="0" * 64,
|
||||
snippet=subject,
|
||||
)
|
||||
self.session.add(msg)
|
||||
await self.session.flush()
|
||||
await thread_message(self.session, msg)
|
||||
return msg
|
||||
Reference in New Issue
Block a user