diff --git a/ingestor/adapters/nats_consumer.py b/ingestor/adapters/nats_consumer.py index bc4bd87..b985142 100644 --- a/ingestor/adapters/nats_consumer.py +++ b/ingestor/adapters/nats_consumer.py @@ -94,6 +94,8 @@ class NatsWriteConsumer: f'{traceback.format_exc()}' ) + # These stay async with nothing to await: nats-py rejects callbacks that are not + # coroutine functions (InvalidCallbackTypeError on connect). async def _on_error(self, e: Exception) -> None: self.logger.warning(f'NATS error: {type(e).__name__} {e}') diff --git a/tests/unit/adapters/test_nats_consumer.py b/tests/unit/adapters/test_nats_consumer.py index eddca14..1485d1a 100644 --- a/tests/unit/adapters/test_nats_consumer.py +++ b/tests/unit/adapters/test_nats_consumer.py @@ -1,4 +1,5 @@ import asyncio +import inspect import json from unittest.mock import AsyncMock, MagicMock, patch @@ -76,9 +77,12 @@ async def test_start_gives_up_after_the_connect_timeout(): """start() runs on the main loop: an unreachable NATS must not block it.""" consumer = make_consumer(connect_timeout_s=0.1) nc = _fake_nc(connect=AsyncMock(side_effect=_hang)) + # Outer guard: fails the test instead of hanging it if start() ignores its timeout. + start = asyncio.wait_for(consumer.start(), timeout=5) - with patch(CLIENT, return_value=nc), pytest.raises(TimeoutError): - await asyncio.wait_for(consumer.start(), timeout=5) + with patch(CLIENT, return_value=nc): + with pytest.raises(TimeoutError): + await start nc.close.assert_awaited_once() assert consumer.closed is True @@ -115,6 +119,9 @@ async def test_connection_events_are_logged_in_one_line(): with patch(CLIENT, return_value=nc): await consumer.start() callbacks = nc.connect.await_args.kwargs + # nats-py raises InvalidCallbackTypeError on connect for a non-coroutine callback. + for name in ('error_cb', 'disconnected_cb', 'reconnected_cb'): + assert inspect.iscoroutinefunction(callbacks[name]), name await callbacks['error_cb'](OSError('connection refused')) await callbacks['disconnected_cb']()