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,55 @@
|
||||
"""Message-ID and subject normalization (SPEC §7.1, §7.3)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.threading_.normalize import (
|
||||
is_synthetic,
|
||||
normalize_message_id,
|
||||
normalize_reference_list,
|
||||
normalize_subject,
|
||||
synthesize_message_id,
|
||||
)
|
||||
|
||||
|
||||
def test_normalize_message_id_strips_brackets_and_lowercases_domain() -> None:
|
||||
assert normalize_message_id("<Abc.123@Example.COM>") == "Abc.123@example.com"
|
||||
assert normalize_message_id(" <x@Y.Z> ") == "x@y.z"
|
||||
# local part case is preserved, domain lowercased
|
||||
assert normalize_message_id("<CaseSensitive@Domain.Com>") == "CaseSensitive@domain.com"
|
||||
|
||||
|
||||
def test_normalize_message_id_rejects_invalid() -> None:
|
||||
assert normalize_message_id(None) is None
|
||||
assert normalize_message_id("no-at-sign") is None
|
||||
assert normalize_message_id("<>") is None
|
||||
assert normalize_message_id("@nolocal.com") is None
|
||||
|
||||
|
||||
def test_normalize_reference_list_order_and_dedupe() -> None:
|
||||
raw = "<a@x.com> <b@y.com> <a@X.com>"
|
||||
assert normalize_reference_list(raw) == ["a@x.com", "b@y.com"]
|
||||
assert normalize_reference_list("") == []
|
||||
assert normalize_reference_list(None) == []
|
||||
|
||||
|
||||
def test_normalize_reference_list_whitespace_separated_without_brackets() -> None:
|
||||
assert normalize_reference_list("a@x.com b@y.com") == ["a@x.com", "b@y.com"]
|
||||
|
||||
|
||||
def test_synthetic_id() -> None:
|
||||
sid = synthesize_message_id(b"raw bytes here")
|
||||
assert sid.endswith("@synthetic.local")
|
||||
assert is_synthetic(sid)
|
||||
assert not is_synthetic("real@example.com")
|
||||
assert not is_synthetic(None)
|
||||
# deterministic
|
||||
assert synthesize_message_id(b"raw bytes here") == sid
|
||||
|
||||
|
||||
def test_normalize_subject_strips_prefixes() -> None:
|
||||
assert normalize_subject("Re: Hello") == "Hello"
|
||||
assert normalize_subject("RE: FW: Fwd: Hello world") == "Hello world"
|
||||
assert normalize_subject("Odp: Cześć") == "Cześć" # Polish reply prefix
|
||||
assert normalize_subject("PD: X") == "X"
|
||||
assert normalize_subject("No prefix") == "No prefix"
|
||||
assert normalize_subject("") == ""
|
||||
Reference in New Issue
Block a user