lib init
This commit is contained in:
91
tests/unit/test_models.py
Normal file
91
tests/unit/test_models.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from mail_intake.models.message import MailIntakeMessage
|
||||
from mail_intake.models.thread import MailIntakeThread
|
||||
|
||||
|
||||
def test_message_is_immutable():
|
||||
msg = MailIntakeMessage(
|
||||
message_id="m1",
|
||||
thread_id="t1",
|
||||
timestamp=datetime.utcnow(),
|
||||
from_email="alice@example.com",
|
||||
from_name="Alice",
|
||||
subject="Hello",
|
||||
body_text="Body",
|
||||
snippet="Snippet",
|
||||
raw_headers={"from": "Alice <alice@example.com>"},
|
||||
)
|
||||
|
||||
try:
|
||||
msg.subject = "Changed"
|
||||
assert False, "Message should be immutable"
|
||||
except Exception:
|
||||
assert True
|
||||
|
||||
|
||||
def test_thread_add_message_updates_participants_and_timestamp():
|
||||
t0 = datetime.utcnow()
|
||||
t1 = t0 + timedelta(minutes=5)
|
||||
|
||||
msg1 = MailIntakeMessage(
|
||||
message_id="m1",
|
||||
thread_id="t1",
|
||||
timestamp=t0,
|
||||
from_email="alice@example.com",
|
||||
from_name="Alice",
|
||||
subject="Hello",
|
||||
body_text="Body",
|
||||
snippet="Snippet",
|
||||
raw_headers={},
|
||||
)
|
||||
|
||||
msg2 = MailIntakeMessage(
|
||||
message_id="m2",
|
||||
thread_id="t1",
|
||||
timestamp=t1,
|
||||
from_email="bob@example.com",
|
||||
from_name="Bob",
|
||||
subject="Re: Hello",
|
||||
body_text="Reply",
|
||||
snippet="Reply",
|
||||
raw_headers={},
|
||||
)
|
||||
|
||||
thread = MailIntakeThread(
|
||||
thread_id="t1",
|
||||
normalized_subject="Hello",
|
||||
)
|
||||
|
||||
thread.add_message(msg1)
|
||||
assert thread.last_activity_at == t0
|
||||
assert "alice@example.com" in thread.participants
|
||||
|
||||
thread.add_message(msg2)
|
||||
assert thread.last_activity_at == t1
|
||||
assert "bob@example.com" in thread.participants
|
||||
assert len(thread.messages) == 2
|
||||
|
||||
|
||||
def test_thread_handles_messages_without_sender():
|
||||
msg = MailIntakeMessage(
|
||||
message_id="m1",
|
||||
thread_id="t1",
|
||||
timestamp=datetime.utcnow(),
|
||||
from_email="",
|
||||
from_name=None,
|
||||
subject="System Message",
|
||||
body_text="Body",
|
||||
snippet="Snippet",
|
||||
raw_headers={},
|
||||
)
|
||||
|
||||
thread = MailIntakeThread(
|
||||
thread_id="t1",
|
||||
normalized_subject="System Message",
|
||||
)
|
||||
|
||||
thread.add_message(msg)
|
||||
|
||||
assert len(thread.participants) == 0
|
||||
assert thread.last_activity_at is not None
|
||||
128
tests/unit/test_parsers.py
Normal file
128
tests/unit/test_parsers.py
Normal file
@@ -0,0 +1,128 @@
|
||||
import base64
|
||||
|
||||
from mail_intake.parsers.subject import normalize_subject
|
||||
from mail_intake.parsers.headers import parse_headers, extract_sender
|
||||
from mail_intake.parsers.body import extract_body
|
||||
|
||||
|
||||
def _b64(text: str) -> str:
|
||||
return base64.b64encode(text.encode("utf-8")).decode("utf-8")
|
||||
|
||||
|
||||
# --------------------
|
||||
# Subject parsing
|
||||
# --------------------
|
||||
|
||||
def test_normalize_subject_strips_common_prefixes():
|
||||
assert normalize_subject("Re: Interview Update") == "Interview Update"
|
||||
assert normalize_subject("Fwd: Re: Offer Letter") == "Offer Letter"
|
||||
assert normalize_subject("FW: Re: FW: Status") == "Status"
|
||||
|
||||
|
||||
def test_normalize_subject_preserves_content_and_case():
|
||||
subject = "Interview Update – Backend Role"
|
||||
assert normalize_subject(subject) == subject
|
||||
|
||||
|
||||
def test_normalize_subject_empty_and_none_safe():
|
||||
assert normalize_subject("") == ""
|
||||
|
||||
|
||||
# --------------------
|
||||
# Header parsing
|
||||
# --------------------
|
||||
|
||||
def test_parse_headers_lowercases_keys():
|
||||
raw_headers = [
|
||||
{"name": "From", "value": "Alice <alice@example.com>"},
|
||||
{"name": "Subject", "value": "Hello"},
|
||||
]
|
||||
|
||||
headers = parse_headers(raw_headers)
|
||||
|
||||
assert headers["from"] == "Alice <alice@example.com>"
|
||||
assert headers["subject"] == "Hello"
|
||||
|
||||
|
||||
def test_parse_headers_ignores_invalid_entries():
|
||||
raw_headers = [
|
||||
{"name": "From", "value": "Bob <bob@example.com>"},
|
||||
{"name": None, "value": "X"},
|
||||
{"name": "X-Test", "value": None},
|
||||
]
|
||||
|
||||
headers = parse_headers(raw_headers)
|
||||
assert "from" in headers
|
||||
assert "x-test" not in headers
|
||||
|
||||
|
||||
def test_extract_sender_with_name_and_email():
|
||||
headers = {"from": "Alice Smith <alice@example.com>"}
|
||||
email, name = extract_sender(headers)
|
||||
|
||||
assert email == "alice@example.com"
|
||||
assert name == "Alice Smith"
|
||||
|
||||
|
||||
def test_extract_sender_email_only():
|
||||
headers = {"from": "bob@example.com"}
|
||||
email, name = extract_sender(headers)
|
||||
|
||||
assert email == "bob@example.com"
|
||||
assert name is None
|
||||
|
||||
|
||||
def test_extract_sender_missing_from():
|
||||
email, name = extract_sender({})
|
||||
assert email == ""
|
||||
assert name is None
|
||||
|
||||
|
||||
# --------------------
|
||||
# Body parsing
|
||||
# --------------------
|
||||
|
||||
def test_extract_body_prefers_text_plain():
|
||||
payload = {
|
||||
"parts": [
|
||||
{
|
||||
"mimeType": "text/html",
|
||||
"body": {"data": _b64("<p>Hello <b>HTML</b></p>")},
|
||||
},
|
||||
{
|
||||
"mimeType": "text/plain",
|
||||
"body": {"data": _b64("Hello TEXT")},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
body = extract_body(payload)
|
||||
assert body == "Hello TEXT"
|
||||
|
||||
|
||||
def test_extract_body_falls_back_to_html():
|
||||
payload = {
|
||||
"parts": [
|
||||
{
|
||||
"mimeType": "text/html",
|
||||
"body": {"data": _b64("<p>Hello <b>World</b></p>")},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
body = extract_body(payload)
|
||||
assert "Hello" in body
|
||||
assert "World" in body
|
||||
|
||||
|
||||
def test_extract_body_single_part():
|
||||
payload = {
|
||||
"body": {"data": _b64("Single part body")}
|
||||
}
|
||||
|
||||
body = extract_body(payload)
|
||||
assert body == "Single part body"
|
||||
|
||||
|
||||
def test_extract_body_empty_payload():
|
||||
assert extract_body({}) == ""
|
||||
Reference in New Issue
Block a user