test(email): cover smtp_username / imap_username / case-insensitive self-address match
The original regression only exercised a from_address match with all three identity fields set to the same value, so it couldn't distinguish whether _self_addresses actually picks up smtp_username and imap_username or just collapses on from_address. Add a parametrized test covering: - smtp_username-only match (from_address empty, imap_username different) — simulates SMTP relays that rewrite outbound From to the login identity. - imap_username-only match — simulates mailbox-identity setups. - Case-insensitive match — inbound From arriving upper-cased must still hit. No production code changes. Made-with: Cursor
This commit is contained in:
@@ -132,6 +132,69 @@ def test_fetch_new_messages_skips_self_sent_email_and_marks_seen(monkeypatch) ->
|
|||||||
assert items_again == []
|
assert items_again == []
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"config_override,from_header",
|
||||||
|
[
|
||||||
|
# Only smtp_username matches — simulates an SMTP relay where
|
||||||
|
# outbound From gets rewritten to the SMTP login identity.
|
||||||
|
(
|
||||||
|
{"from_address": "", "smtp_username": "bot@example.com", "imap_username": "other@imap.com"},
|
||||||
|
"bot@example.com",
|
||||||
|
),
|
||||||
|
# Only imap_username matches — simulates mailbox-based identity
|
||||||
|
# with no explicit from_address set.
|
||||||
|
(
|
||||||
|
{"from_address": "", "smtp_username": "other@smtp.com", "imap_username": "bot@example.com"},
|
||||||
|
"bot@example.com",
|
||||||
|
),
|
||||||
|
# Case-insensitive: inbound From arrives upper-cased.
|
||||||
|
(
|
||||||
|
{"from_address": "bot@example.com", "smtp_username": "other@smtp.com", "imap_username": "other@imap.com"},
|
||||||
|
"BOT@EXAMPLE.COM",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ids=["smtp_username_only", "imap_username_only", "case_insensitive"],
|
||||||
|
)
|
||||||
|
def test_fetch_new_messages_skips_self_sent_across_identity_sources(
|
||||||
|
monkeypatch, config_override, from_header
|
||||||
|
) -> None:
|
||||||
|
"""Self-address detection must fire when any of from_address / smtp_username /
|
||||||
|
imap_username matches, and must be case-insensitive."""
|
||||||
|
raw = _make_raw_email(from_addr=from_header, subject="Loop test")
|
||||||
|
|
||||||
|
class FakeIMAP:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.store_calls: list[tuple[bytes, str, str]] = []
|
||||||
|
|
||||||
|
def login(self, _user: str, _pw: str):
|
||||||
|
return "OK", [b"logged in"]
|
||||||
|
|
||||||
|
def select(self, _mailbox: str):
|
||||||
|
return "OK", [b"1"]
|
||||||
|
|
||||||
|
def search(self, *_args):
|
||||||
|
return "OK", [b"1"]
|
||||||
|
|
||||||
|
def fetch(self, _imap_id: bytes, _parts: str):
|
||||||
|
return "OK", [(b"1 (UID 123 BODY[] {200})", raw), b")"]
|
||||||
|
|
||||||
|
def store(self, imap_id: bytes, op: str, flags: str):
|
||||||
|
self.store_calls.append((imap_id, op, flags))
|
||||||
|
return "OK", [b""]
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
|
return "BYE", [b""]
|
||||||
|
|
||||||
|
fake = FakeIMAP()
|
||||||
|
monkeypatch.setattr("nanobot.channels.email.imaplib.IMAP4_SSL", lambda _h, _p: fake)
|
||||||
|
|
||||||
|
channel = EmailChannel(_make_config(**config_override), MessageBus())
|
||||||
|
items = channel._fetch_new_messages()
|
||||||
|
|
||||||
|
assert items == []
|
||||||
|
assert fake.store_calls == [(b"1", "+FLAGS", "\\Seen")]
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_new_messages_retries_once_when_imap_connection_goes_stale(monkeypatch) -> None:
|
def test_fetch_new_messages_retries_once_when_imap_connection_goes_stale(monkeypatch) -> None:
|
||||||
raw = _make_raw_email(subject="Invoice", body="Please pay")
|
raw = _make_raw_email(subject="Invoice", body="Please pay")
|
||||||
fail_once = {"pending": True}
|
fail_once = {"pending": True}
|
||||||
|
|||||||
Reference in New Issue
Block a user