modul-x / tests /test_smtp_client.py
sitcod3's picture
Upload folder using huggingface_hub
f5afa05 verified
Raw History Blame Contribute Delete
4.92 kB
import unittest
from app.validator.smtp_client import SmtpConnection, RcptOutcome
class FakeSock:
"""Scripted TCP socket: returns queued byte chunks on recv, records sends."""
def __init__(self, chunks):
self.chunks = list(chunks)
self.sent: list[str] = []
self.closed = False
self.timeout = None
def settimeout(self, t):
self.timeout = t
def sendall(self, data: bytes):
self.sent.append(data.decode("utf-8", "replace"))
def recv(self, _n: int) -> bytes:
return self.chunks.pop(0) if self.chunks else b""
def close(self):
self.closed = True
def make_conn(chunks, **kw):
sock = FakeSock(chunks)
conn = SmtpConnection(
"mx.example.test",
timeout=5,
helo_domain="validator.test",
connect=lambda *a, **k: sock,
**kw,
)
return conn, sock
BANNER = b"220 mx.example.test ESMTP ready\r\n"
EHLO_OK = b"250-mx.example.test\r\n250-PIPELINING\r\n250 OK\r\n"
class SmtpConnectionTests(unittest.TestCase):
def test_handshake_reads_banner_and_sends_ehlo(self):
conn, sock = make_conn([BANNER, EHLO_OK, b"250 OK\r\n"])
conn.open()
self.assertTrue(conn.ready)
self.assertTrue(any(c.startswith("EHLO ") for c in sock.sent))
self.assertIn("validator.test", "".join(sock.sent))
def test_multiline_ehlo_is_parsed_as_single_response(self):
conn, _ = make_conn([BANNER, EHLO_OK])
conn.open()
self.assertEqual(conn.server_code, 250)
def test_accepting_rcpt_returns_250(self):
conn, sock = make_conn(
[BANNER, EHLO_OK, b"250 OK\r\n", b"250 2.1.5 Ok\r\n", b"250 OK\r\n"]
)
conn.open()
out = conn.probe("real@example.test")
self.assertEqual(out.code, 250)
self.assertTrue(out.accepted)
self.assertFalse(out.transport_error)
self.assertTrue(any(c.startswith("RCPT TO:") for c in sock.sent))
def test_rejecting_rcpt_returns_550(self):
conn, _ = make_conn(
[
BANNER,
EHLO_OK,
b"250 OK\r\n",
b"550 5.1.1 The email account that you tried to reach does not exist\r\n",
b"250 OK\r\n",
]
)
conn.open()
out = conn.probe("ghost@example.test")
self.assertEqual(out.code, 550)
self.assertFalse(out.accepted)
self.assertIn("does not exist", out.message)
def test_multiple_probes_reuse_one_connection(self):
# The whole point of the engine: one TCP session, many RCPT probes.
conn, sock = make_conn(
[
BANNER,
EHLO_OK,
b"250 OK\r\n", # MAIL FROM #1
b"250 OK\r\n", # RCPT #1
b"250 OK\r\n", # RSET
b"250 OK\r\n", # MAIL FROM #2
b"550 5.1.1 nope\r\n", # RCPT #2
b"250 OK\r\n", # RSET
b"221 Bye\r\n",
]
)
conn.open()
first = conn.probe("a@example.test")
second = conn.probe("b@example.test")
conn.quit()
self.assertTrue(first.accepted)
self.assertFalse(second.accepted)
# Two MAIL FROM commands, one EHLO, one connection.
sends = "".join(sock.sent)
self.assertEqual(sends.count("MAIL FROM:"), 2)
self.assertEqual(sends.count("EHLO "), 1)
self.assertEqual(sends.count("RSET"), 2)
self.assertTrue(sock.closed)
def test_transport_failure_is_reported_not_raised(self):
conn, _ = make_conn([BANNER, EHLO_OK])
conn.open()
conn._sock.chunks = [] # server dies mid-session
out = conn.probe("a@example.test")
self.assertTrue(out.transport_error)
self.assertIsNone(out.code)
self.assertTrue(out.retryable)
def test_unexpected_banner_is_not_ready(self):
conn, _ = make_conn([b"554 Service unavailable\r\n"])
conn.open()
self.assertFalse(conn.ready)
def test_greylist_code_marked_retryable(self):
conn, _ = make_conn(
[BANNER, EHLO_OK, b"250 OK\r\n", b"451 4.7.1 Greylisted, try later\r\n", b"250 OK\r\n"]
)
conn.open()
out = conn.probe("a@example.test")
self.assertEqual(out.code, 451)
self.assertTrue(out.retryable)
self.assertFalse(out.permanent_reject)
class RcptOutcomeTests(unittest.TestCase):
def test_flags_for_5xx(self):
o = RcptOutcome(code=550, message="550 no such user")
self.assertTrue(o.permanent_reject)
self.assertFalse(o.retryable)
def test_flags_for_accept(self):
o = RcptOutcome(code=250, message="250 OK")
self.assertTrue(o.accepted)
self.assertFalse(o.retryable)
self.assertFalse(o.permanent_reject)
if __name__ == "__main__":
unittest.main()