diff --git a/tests/integration/_support/mail.py b/tests/integration/_support/mail.py index ef4f8f9a0ca..56c74193152 100644 --- a/tests/integration/_support/mail.py +++ b/tests/integration/_support/mail.py @@ -2,6 +2,7 @@ from __future__ import annotations import socketserver import threading +from collections import deque from collections.abc import Generator from contextlib import contextmanager from dataclasses import dataclass @@ -33,13 +34,10 @@ class Delivery: class Mailbox: host: str port: int - received: SimpleQueue[Delivery] + received: deque[Delivery] - def pending(self) -> int: - return self.received.qsize() - - def drain(self) -> tuple[Delivery, ...]: - return tuple(self.received.get_nowait() for _ in range(self.received.qsize())) + def with_subject(self, fragment: str) -> tuple[Delivery, ...]: + return tuple(delivery for delivery in tuple(self.received) if fragment in delivery.subject) def _address(argument: str) -> str: @@ -49,7 +47,7 @@ def _address(argument: str) -> str: @contextmanager def smtp_sink() -> Generator[Mailbox, None, None]: """Owned plaintext SMTP peer; deliveries traverse the proxy's real smtplib client.""" - received: Final[SimpleQueue[Delivery]] = SimpleQueue() + received: Final[deque[Delivery]] = deque() # mutable-ok: sink thread appends each delivery errors: Final[SimpleQueue[Exception]] = SimpleQueue() class Handler(socketserver.StreamRequestHandler): @@ -92,7 +90,7 @@ def smtp_sink() -> Generator[Mailbox, None, None]: if not chunk or chunk == b".\r\n": break body.extend(chunk[1:] if chunk.startswith(b"..") else chunk) - received.put(Delivery(sender, recipients, message_from_bytes(bytes(body)))) + received.append(Delivery(sender, recipients, message_from_bytes(bytes(body)))) sender, recipients = "", () self._reply("250 OK queued") elif verb == "RSET": diff --git a/tests/integration/spend/test_team_member_budget_alerts.py b/tests/integration/spend/test_team_member_budget_alerts.py index 49c3837aefa..5aedad3e360 100644 --- a/tests/integration/spend/test_team_member_budget_alerts.py +++ b/tests/integration/spend/test_team_member_budget_alerts.py @@ -56,7 +56,7 @@ def test_team_member_budget_thresholds_email_member_and_configured_recipients(ga eventually( lambda: _membership_spend(user_id, team_id), lambda spend: spend == pytest.approx(CALL_COST), seconds=70 ) - assert mailbox.drain() == (), "no threshold is reached before the first call is recorded" + assert tuple(mailbox.received) == (), "no threshold is reached before the first call is recorded" second: Final = candidate.request( "POST", @@ -65,10 +65,8 @@ def test_team_member_budget_thresholds_email_member_and_configured_recipients(ga key=key, ) assert second.status_code == 200, second.text - eventually(mailbox.pending, lambda count: count >= 1, seconds=30) - halfway: Final = mailbox.drain() + halfway: Final = eventually(lambda: mailbox.with_subject("50%"), lambda found: len(found) >= 1, seconds=30) assert [delivery.recipients for delivery in halfway] == [(member_email,)], halfway - assert "50%" in halfway[0].subject, halfway[0].subject assert f"${MEMBER_BUDGET}" in halfway[0].html, halfway[0].html eventually( lambda: _membership_spend(user_id, team_id), @@ -83,9 +81,7 @@ def test_team_member_budget_thresholds_email_member_and_configured_recipients(ga key=key, ) assert third.status_code == 422 and third.json()["error"]["type"] == "budget_exceeded", third.text - eventually(mailbox.pending, lambda count: count >= 2, seconds=30) - hundred: Final = mailbox.drain() - assert all("100%" in delivery.subject for delivery in hundred), hundred + hundred: Final = eventually(lambda: mailbox.with_subject("100%"), lambda found: len(found) >= 2, seconds=30) assert {recipient for delivery in hundred for recipient in delivery.recipients} == { member_email, finance_email,