"""MIME parsing (SPEC §6.4).""" from __future__ import annotations from email.message import EmailMessage from app.indexer.mime import parse_message def _build( *, subject: str = "Hello", from_addr: str = "Alice ", to_addr: str = "bob@example.com", message_id: str = "", references: str | None = None, in_reply_to: str | None = None, text: str | None = "plain body", html: str | None = None, ) -> bytes: msg = EmailMessage() msg["Subject"] = subject msg["From"] = from_addr msg["To"] = to_addr msg["Message-ID"] = message_id msg["Date"] = "Mon, 09 Mar 2026 08:36:37 +0100" if references: msg["References"] = references if in_reply_to: msg["In-Reply-To"] = in_reply_to if text is not None: msg.set_content(text) if html is not None: if text is not None: msg.add_alternative(html, subtype="html") else: msg.set_content(html, subtype="html") return msg.as_bytes() def test_parse_basic_headers_and_body() -> None: parsed = parse_message(_build()) assert parsed.subject == "Hello" assert parsed.message_id == "m1@example.com" assert parsed.from_addr == [{"name": "Alice", "email": "alice@example.com"}] assert parsed.to_addr == [{"name": "", "email": "bob@example.com"}] assert "plain body" in parsed.text_body assert parsed.date is not None assert parsed.snippet == "plain body" def test_undecodable_header_bytes_scrubbed_to_valid_utf8() -> None: # A raw 8-bit byte the parser can't decode surfaces as a lone surrogate; it must # be scrubbed so the value is valid UTF-8 for Postgres text/JSONB storage. raw = _build(subject="Fakt\udce9ra", from_addr="B\udce9ta ") parsed = parse_message(raw) parsed.subject.encode("utf-8") # would raise on a lone surrogate parsed.from_addr[0]["name"].encode("utf-8") assert "\udce9" not in parsed.subject assert "�" in parsed.subject def test_references_include_in_reply_to_in_order() -> None: parsed = parse_message(_build(references=" ", in_reply_to="")) # References first, then In-Reply-To, deduped assert parsed.references == ["a@x.com", "b@x.com"] assert parsed.in_reply_to == "b@x.com" def test_html_only_derives_text() -> None: parsed = parse_message(_build(text=None, html="

Hello world

")) assert parsed.html_body is not None assert "Hello" in parsed.text_body assert "world" in parsed.text_body assert "" not in parsed.text_body # tags stripped for indexing text def test_multipart_alternative_prefers_html_for_display() -> None: parsed = parse_message(_build(text="text version", html="

html version

")) assert parsed.html_body is not None assert "html version" in parsed.html_body assert "text version" in parsed.text_body def test_rfc2047_encoded_subject_is_decoded() -> None: # "Próba" base64-encoded UTF-8 parsed = parse_message(_build(subject="=?UTF-8?B?UHLDs2Jh?=")) assert parsed.subject == "Próba" def test_attachment_extracted() -> None: msg = EmailMessage() msg["Subject"] = "with attachment" msg["From"] = "a@b.com" msg["To"] = "c@d.com" msg["Message-ID"] = "" msg.set_content("see attached") msg.add_attachment( b"\x00\x01\x02data", maintype="application", subtype="octet-stream", filename="file.bin" ) parsed = parse_message(msg.as_bytes()) assert parsed.has_attachments assert parsed.attachments[0].filename == "file.bin" assert parsed.attachments[0].payload == b"\x00\x01\x02data" def test_inline_image_nested_in_related_is_extracted() -> None: # Real-world structure: mixed > related > alternative(text, html) + inline image. # iter_attachments() misses the nested image; a full walk must find it so the # cid: reference resolves (regression: broken inline image). from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText root = MIMEMultipart("mixed") root["Subject"] = "newsletter" root["From"] = "biuro@kipa.pl" root["To"] = "me@example.com" root["Message-ID"] = "" related = MIMEMultipart("related") alt = MIMEMultipart("alternative") alt.attach(MIMEText("text version", "plain")) alt.attach(MIMEText('

Hi

', "html")) related.attach(alt) img = MIMEImage(b"\x89PNG\r\n\x1a\ndata", "png") img.add_header("Content-ID", "") img.add_header("Content-Disposition", "inline", filename="image001.png") related.attach(img) root.attach(related) parsed = parse_message(root.as_bytes()) assert len(parsed.attachments) == 1 att = parsed.attachments[0] assert att.content_id == "img001" assert att.mime_type == "image/png" # Inline-only: no attachment chip / paperclip. assert parsed.has_attachments is False def test_attachment_with_content_id_is_a_real_attachment() -> None: # iMIP invites (e.g. Fastmail) send invite.ics as Content-Disposition: attachment # yet also give it a Content-ID. It must show as a downloadable chip, not be # hidden as an inline resource (regression: "no attachments" shown). from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText root = MIMEMultipart("mixed") root["Subject"] = "invite" root["From"] = "org@example.com" root["To"] = "me@example.com" root["Message-ID"] = "" root.attach(MIMEText("body", "plain")) ics = MIMEText("BEGIN:VCALENDAR\nEND:VCALENDAR", "calendar") ics.add_header("Content-Disposition", "attachment", filename="invite.ics") ics.add_header("Content-ID", "") root.attach(ics) parsed = parse_message(root.as_bytes()) assert len(parsed.attachments) == 1 att = parsed.attachments[0] assert att.filename == "invite.ics" assert att.content_id is None # not treated as inline despite the Content-ID assert parsed.has_attachments is True def test_missing_message_id_returns_none() -> None: msg = EmailMessage() msg["Subject"] = "no id" msg["From"] = "a@b.com" msg["To"] = "c@d.com" msg.set_content("body") parsed = parse_message(msg.as_bytes()) assert parsed.message_id is None