Files
stack/tests/bib/test_email_ingest.py
kert 0ce15f80a5
Some checks failed
CI / skinny-install (aco) (push) Successful in 1m7s
CI / skinny-install (api) (push) Successful in 33s
CI / skinny-install (bcda) (push) Successful in 39s
CI / skinny-install (bib) (push) Successful in 38s
CI / skinny-install (bls) (push) Successful in 34s
CI / skinny-install (ccw) (push) Successful in 38s
CI / skinny-install (cli) (push) Successful in 34s
CI / skinny-install (cms) (push) Successful in 38s
CI / skinny-install (conf) (push) Successful in 35s
CI / skinny-install (opps) (push) Successful in 33s
CI / skinny-install (perf) (push) Successful in 35s
CI / skinny-install (pfs) (push) Successful in 42s
CI / skinny-install (rex) (push) Successful in 40s
CI / lint-test (push) Failing after 11m44s
Deploy / build-scan-report (push) Failing after 6m13s
test: mocked integration tests for bib, prisma, zot — tracks #353
80 new tests covering federalregister, email_ingest, regulations_gov,
prisma/fetch (incl. deterministic altcha PoW vector), and zot/ops.

All external deps mocked (httpx, imaplib, subprocess, pydo). No
network calls in CI.
2026-04-17 08:31:10 -04:00

167 lines
5.0 KiB
Python
Raw Blame History

"""Tests for bib.email_ingest — IMAP → bib ingest."""
from __future__ import annotations
import email.message
from pathlib import Path
from unittest.mock import MagicMock, patch
from bib.email_ingest import (
Mailbox,
_date_iso,
_extract_body,
_safe_filename,
_sender_domain,
_slug,
ingest,
)
# ── Pure helpers ────────────────────<E29480><E29480>─────────────────────────────
class TestSlug:
def test_basic(self):
assert _slug("Hello World!") == "hello-world"
def test_truncate(self):
assert len(_slug("x" * 100)) <= 60
def test_empty(self):
assert _slug("") == ""
class TestSafeFilename:
def test_special_chars(self):
assert _safe_filename("file (1).pdf") == "file_1_.pdf"
def test_empty(self):
assert _safe_filename("") == "attachment"
class TestSenderDomain:
def test_angle_bracket(self):
assert _sender_domain("John <john@example.com>") == "example.com"
def test_bare(self):
assert _sender_domain("user@domain.org") == "domain.org"
def test_no_domain(self):
assert _sender_domain("no email here") == ""
class TestDateIso:
def test_rfc2822(self):
assert _date_iso("Wed, 15 Apr 2026 22:00:35 +0000") == "2026-04-15"
def test_empty(self):
assert _date_iso("") == ""
def test_garbage(self):
assert _date_iso("not a date") == ""
class TestExtractBody:
def test_plain_text(self):
msg = email.message.EmailMessage()
msg.set_content("Hello world")
assert _extract_body(msg) == "Hello world\n"
def test_html_fallback(self):
msg = email.message.EmailMessage()
msg["Content-Type"] = "multipart/alternative"
msg.add_alternative("<p>Hello</p>", subtype="html")
body = _extract_body(msg)
assert "Hello" in body
# ── ingest (mocked IMAP + Store) ─────────────────────────────────
def _make_raw_email(
subject="Test", sender="cms@example.gov", body="Hello", msg_id="<abc@test>"
):
msg = email.message.EmailMessage()
msg["Subject"] = subject
msg["From"] = sender
msg["Date"] = "Wed, 15 Apr 2026 10:00:00 +0000"
msg["Message-ID"] = msg_id
msg.set_content(body)
return msg.as_bytes()
class TestIngest:
@patch("bib.email_ingest.imaplib.IMAP4_SSL")
def test_processes_unseen(self, MockIMAP):
conn = MockIMAP.return_value
conn.login.return_value = ("OK", [])
conn.select.return_value = ("OK", [])
conn.uid.side_effect = [
("OK", [b"1 2"]), # SEARCH
(
"OK",
[(b"1 (RFC822 {100}", _make_raw_email(msg_id="<m1@test>"))],
), # FETCH 1
("OK", []), # STORE 1
(
"OK",
[
(
b"2 (RFC822 {100}",
_make_raw_email(subject="Second", msg_id="<m2@test>"),
)
],
), # FETCH 2
("OK", []), # STORE 2
]
conn.close.return_value = ("OK", [])
conn.logout.return_value = ("OK", [])
store = MagicMock()
store.upsert.return_value = "KEY1"
mb = Mailbox(
host="mail.test", port=993, username="test@test.com", password="pw"
)
stats = ingest(store, mb, scratch_root=Path("/tmp/test-ingest"))
assert stats["seen"] == 2
assert stats["ingested"] == 2
assert stats["errors"] == 0
assert store.upsert.call_count == 2
@patch("bib.email_ingest.imaplib.IMAP4_SSL")
def test_empty_inbox(self, MockIMAP):
conn = MockIMAP.return_value
conn.login.return_value = ("OK", [])
conn.select.return_value = ("OK", [])
conn.uid.return_value = ("OK", [b""])
conn.close.return_value = ("OK", [])
conn.logout.return_value = ("OK", [])
store = MagicMock()
mb = Mailbox(
host="mail.test", port=993, username="test@test.com", password="pw"
)
stats = ingest(store, mb)
assert stats["seen"] == 0
assert stats["ingested"] == 0
@patch("bib.email_ingest.imaplib.IMAP4_SSL")
def test_limit(self, MockIMAP):
conn = MockIMAP.return_value
conn.login.return_value = ("OK", [])
conn.select.return_value = ("OK", [])
conn.uid.side_effect = [
("OK", [b"1 2 3 4 5"]), # SEARCH returns 5
("OK", [(b"1 (RFC822 {100}", _make_raw_email(msg_id="<l1@test>"))]),
("OK", []),
]
conn.close.return_value = ("OK", [])
conn.logout.return_value = ("OK", [])
store = MagicMock()
store.upsert.return_value = "K"
mb = Mailbox(host="h", port=993, username="u@d", password="p")
stats = ingest(store, mb, limit=1)
assert stats["seen"] == 1
assert stats["ingested"] == 1