diff --git a/tests/live/conftest.py b/tests/live/conftest.py index 7d23aad1..ad59f6d0 100644 --- a/tests/live/conftest.py +++ b/tests/live/conftest.py @@ -97,7 +97,7 @@ def assert_rfc5424( class _ServiceProcess: """Manages a live service subprocess and its stdout log queue.""" - def __init__(self, service: str, port: int): + def __init__(self, service: str, port: int, extra_env: dict | None = None): template_dir = _TEMPLATES / service env = { **os.environ, @@ -106,6 +106,8 @@ class _ServiceProcess: "PYTHONPATH": str(template_dir), "LOG_TARGET": "", } + if extra_env: + env.update(extra_env) self._proc = subprocess.Popen( [_PYTHON, str(template_dir / "server.py")], cwd=str(template_dir), @@ -150,9 +152,9 @@ def live_service() -> Generator: """ started: list[_ServiceProcess] = [] - def _start(service: str) -> tuple[int, callable]: + def _start(service: str, env: dict | None = None) -> tuple[int, callable]: port = _free_port() - svc = _ServiceProcess(service, port) + svc = _ServiceProcess(service, port, extra_env=env) started.append(svc) if not _wait_for_port(port): svc.stop() diff --git a/tests/live/test_mqtt_live.py b/tests/live/test_mqtt_live.py index 002bd05e..5d5dc609 100644 --- a/tests/live/test_mqtt_live.py +++ b/tests/live/test_mqtt_live.py @@ -9,7 +9,11 @@ from tests.live.conftest import assert_rfc5424 @pytest.mark.live class TestMQTTLive: def test_connect_accepted(self, live_service): - port, drain = live_service("mqtt") + # The honeypot defaults to auth-required (post-2018 realistic + # broker posture). Opt into accept-all mode to exercise the + # happy-path CONNACK rc=0 code path. See decnet/templates/mqtt/ + # server.py::MQTT_ACCEPT_ALL. + port, drain = live_service("mqtt", env={"MQTT_ACCEPT_ALL": "1"}) connected = [] client = mqtt.Client(client_id="test-scanner") client.on_connect = lambda c, u, f, rc: connected.append(rc) @@ -48,7 +52,9 @@ class TestMQTTLive: ) def test_subscribe_logged(self, live_service): - port, drain = live_service("mqtt") + # SUBSCRIBE is gated on successful auth — accept-all lets the test + # reach the subscribe path without planting credentials. + port, drain = live_service("mqtt", env={"MQTT_ACCEPT_ALL": "1"}) subscribed = [] client = mqtt.Client(client_id="sub-test") client.on_subscribe = lambda c, u, mid, qos: subscribed.append(mid) diff --git a/tests/live/test_mysql_backend_live.py b/tests/live/test_mysql_backend_live.py index 7a3a388e..fe798e7e 100644 --- a/tests/live/test_mysql_backend_live.py +++ b/tests/live/test_mysql_backend_live.py @@ -38,6 +38,13 @@ LIVE_URL = "mysql+asyncmy://root:root@127.0.0.1:3307/decnet" pytestmark = [ pytest.mark.live, + # Pin every test in this module to the module-scoped event loop. The + # module-scoped ``mysql_test_db_url`` fixture (and transitively the + # asyncmy connection pool it seeds) is bound to that loop; running the + # tests on their own per-function loops trips pytest-asyncio's + # "Future attached to a different loop" guard the instant the repo + # reuses a pooled connection. + pytest.mark.asyncio(loop_scope="module"), pytest.mark.skipif( not (LIVE_URL and LIVE_URL.startswith("mysql")), reason="Set DECNET_DB_URL=mysql+aiomysql://... to run MySQL live tests", diff --git a/tests/live/test_postgres_live.py b/tests/live/test_postgres_live.py index e4ca94da..f465184a 100644 --- a/tests/live/test_postgres_live.py +++ b/tests/live/test_postgres_live.py @@ -60,13 +60,18 @@ class TestPostgresLive: def test_auth_hash_logged(self, live_service): port, drain = live_service("postgres") import psycopg2 + # Real PG rejects before asking for a password when the requested + # db doesn't exist, and the honeypot faithfully mirrors that. So + # we must target an always-present database (``postgres`` is in + # _BASE_DBS) to get past startup and into the password-auth stage + # that this test is asserting on. try: psycopg2.connect( host="127.0.0.1", port=port, user="root", password="toor", - dbname="prod", + dbname="postgres", connect_timeout=5, ) except psycopg2.OperationalError: