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:
2026-07-17 23:20:34 +02:00
co-authored by Claude Opus 4.8
commit a5ec0d21cb
174 changed files with 22239 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
"""Handle validation rules (SPEC §4)."""
from __future__ import annotations
import pytest
from app.config import get_settings
from app.identity.handles import InvalidHandle, full_handle, validate_localpart
from app.identity.reserved import is_reserved
from app.identity.service import resolve_handle
_DOMAIN = get_settings().app_domain
@pytest.mark.parametrize(
"raw,expected",
[
("alice", "alice"),
("Alice", "alice"), # case-insensitive
(" bob ", "bob"), # trimmed
("a.b_c-d", "a.b_c-d"),
("user123", "user123"),
("abc", "abc"), # min length
("a" * 30, "a" * 30), # max length
],
)
def test_valid_localparts(raw: str, expected: str) -> None:
assert validate_localpart(raw) == expected
@pytest.mark.parametrize(
"raw,code",
[
("ab", "length"), # too short
("a" * 31, "length"), # too long
(".alice", "dot_edge"),
("alice.", "dot_edge"),
("al..ice", "double_dot"),
("al ice", "charset"), # space
("aliçe", "charset"), # non-ascii
("al@ice", "charset"), # '@'
("UPPER!", "charset"),
("postmaster", "reserved"),
("admin", "reserved"),
("root", "reserved"),
("NoReply", "reserved"), # reserved, case-insensitive
],
)
def test_invalid_localparts(raw: str, code: str) -> None:
with pytest.raises(InvalidHandle) as exc:
validate_localpart(raw)
assert exc.value.code == code
def test_full_handle_appends_domain() -> None:
assert full_handle("alice") == f"alice@{_DOMAIN}"
def test_reserved_is_case_insensitive() -> None:
assert is_reserved("ADMIN")
assert is_reserved("admin")
assert not is_reserved("alice")
def test_resolve_handle_accepts_localpart_or_full() -> None:
assert resolve_handle("Alice") == f"alice@{_DOMAIN}"
assert resolve_handle("alice@other.example") == "alice@other.example"