fix(tests): fix stale asyncio.sleep patches and missing tarpit guards in service isolation tests
After the ingester._sleep alias fix, three tests in test_service_isolation.py
still patched `decnet.web.ingester.asyncio.sleep` (the old global-singleton
path). The ingester now calls `_sleep` directly, so those patches no longer
controlled the ingester's sleep — the worker looped with real asyncio.sleep
and the tests hung indefinitely.
Also: four API lifespan tests had no tarpit_watcher_worker patch, letting the
real tarpit task start. And test_api_survives_db_init_failure patched
`decnet.web.api.asyncio.sleep` (the singleton) instead of the existing
`_retry_sleep` alias.
Fixes:
- patch("decnet.web.ingester._sleep", ...) in the three ingester tests
- add tarpit_watcher_worker patch to all four api lifespan tests
- patch("decnet.web.api._retry_sleep", ...) in db_init_failure test
This commit is contained in:
@@ -110,7 +110,7 @@ class TestIngesterIsolation:
|
||||
raise asyncio.CancelledError()
|
||||
|
||||
with patch.dict(os.environ, {"DECNET_INGEST_LOG_FILE": "/tmp/nonexistent-decnet-test.log"}):
|
||||
with patch("decnet.web.ingester.asyncio.sleep", side_effect=_controlled_sleep):
|
||||
with patch("decnet.web.ingester._sleep", side_effect=_controlled_sleep):
|
||||
task = asyncio.create_task(log_ingestion_worker(mock_repo))
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
@@ -153,7 +153,7 @@ class TestIngesterIsolation:
|
||||
raise asyncio.CancelledError()
|
||||
|
||||
with patch.dict(os.environ, {"DECNET_INGEST_LOG_FILE": str(tmp_path / "test.log")}):
|
||||
with patch("decnet.web.ingester.asyncio.sleep", side_effect=_controlled_sleep):
|
||||
with patch("decnet.web.ingester._sleep", side_effect=_controlled_sleep):
|
||||
task = asyncio.create_task(log_ingestion_worker(mock_repo))
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
@@ -345,9 +345,11 @@ class TestApiLifespanIsolation:
|
||||
side_effect=Exception("attacker exploded")):
|
||||
with patch("decnet.sniffer.sniffer_worker",
|
||||
side_effect=Exception("sniffer exploded")):
|
||||
# API should still start
|
||||
async with lifespan(mock_app):
|
||||
mock_repo.initialize.assert_awaited_once()
|
||||
with patch("decnet.web.api.tarpit_watcher_worker",
|
||||
return_value=asyncio.sleep(0)):
|
||||
# API should still start
|
||||
async with lifespan(mock_app):
|
||||
mock_repo.initialize.assert_awaited_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_survives_db_init_failure(self):
|
||||
@@ -359,13 +361,15 @@ class TestApiLifespanIsolation:
|
||||
mock_repo.initialize = AsyncMock(side_effect=Exception("DB locked"))
|
||||
|
||||
with patch("decnet.web.api.repo", mock_repo):
|
||||
with patch("decnet.web.api.asyncio.sleep", new_callable=AsyncMock):
|
||||
with patch("decnet.web.api._retry_sleep", new_callable=AsyncMock):
|
||||
with patch("decnet.web.api.log_ingestion_worker", return_value=asyncio.sleep(0)):
|
||||
with patch("decnet.web.api.log_collector_worker", return_value=asyncio.sleep(0)):
|
||||
with patch("decnet.web.api.attacker_profile_worker", return_value=asyncio.sleep(0)):
|
||||
async with lifespan(mock_app):
|
||||
# DB init failed 5 times but API is running
|
||||
assert mock_repo.initialize.await_count == 5
|
||||
with patch("decnet.web.api.tarpit_watcher_worker",
|
||||
return_value=asyncio.sleep(0)):
|
||||
async with lifespan(mock_app):
|
||||
# DB init failed 5 times but API is running
|
||||
assert mock_repo.initialize.await_count == 5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_survives_sniffer_import_failure(self):
|
||||
@@ -376,22 +380,23 @@ class TestApiLifespanIsolation:
|
||||
mock_repo = MagicMock()
|
||||
mock_repo.initialize = AsyncMock()
|
||||
|
||||
import builtins
|
||||
real_import = builtins.__import__
|
||||
|
||||
def _mock_import(name, *args, **kwargs):
|
||||
if name == "decnet.sniffer":
|
||||
raise ImportError("No module named 'scapy'")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
with patch("decnet.web.api.repo", mock_repo):
|
||||
with patch("decnet.web.api.log_ingestion_worker", return_value=asyncio.sleep(0)):
|
||||
with patch("decnet.web.api.log_collector_worker", return_value=asyncio.sleep(0)):
|
||||
with patch("decnet.web.api.attacker_profile_worker", return_value=asyncio.sleep(0)):
|
||||
# Simulate sniffer import failure
|
||||
import builtins
|
||||
real_import = builtins.__import__
|
||||
|
||||
def _mock_import(name, *args, **kwargs):
|
||||
if name == "decnet.sniffer":
|
||||
raise ImportError("No module named 'scapy'")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
with patch("builtins.__import__", side_effect=_mock_import):
|
||||
async with lifespan(mock_app):
|
||||
mock_repo.initialize.assert_awaited_once()
|
||||
with patch("decnet.web.api.tarpit_watcher_worker",
|
||||
return_value=asyncio.sleep(0)):
|
||||
with patch("builtins.__import__", side_effect=_mock_import):
|
||||
async with lifespan(mock_app):
|
||||
mock_repo.initialize.assert_awaited_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_shutdown_handles_already_dead_tasks(self):
|
||||
@@ -410,10 +415,12 @@ class TestApiLifespanIsolation:
|
||||
with patch("decnet.web.api.log_ingestion_worker", side_effect=_instant_worker):
|
||||
with patch("decnet.web.api.log_collector_worker", side_effect=_instant_worker):
|
||||
with patch("decnet.web.api.attacker_profile_worker", side_effect=_instant_worker):
|
||||
async with lifespan(mock_app):
|
||||
# Let tasks finish
|
||||
await asyncio.sleep(0.05)
|
||||
# Shutdown should handle already-done tasks gracefully
|
||||
with patch("decnet.web.api.tarpit_watcher_worker",
|
||||
side_effect=_instant_worker):
|
||||
async with lifespan(mock_app):
|
||||
# Let tasks finish
|
||||
await asyncio.sleep(0.05)
|
||||
# Shutdown should handle already-done tasks gracefully
|
||||
|
||||
|
||||
# ─── Cross-service cascade tests ────────────────────────────────────────────
|
||||
@@ -442,7 +449,7 @@ class TestCascadeIsolation:
|
||||
raise asyncio.CancelledError()
|
||||
|
||||
with patch.dict(os.environ, {"DECNET_INGEST_LOG_FILE": str(tmp_path / "cascade.log")}):
|
||||
with patch("decnet.web.ingester.asyncio.sleep", side_effect=_controlled_sleep):
|
||||
with patch("decnet.web.ingester._sleep", side_effect=_controlled_sleep):
|
||||
task = asyncio.create_task(log_ingestion_worker(mock_repo))
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
Reference in New Issue
Block a user