Fix module-level asyncio locks in jail_service

Initialize jail_service locks lazily to avoid import-time event loop binding and add regression tests for lock creation.
This commit is contained in:
2026-04-15 09:10:38 +02:00
parent a8f2d2d7b9
commit 56c511d905
3 changed files with 95 additions and 10 deletions

View File

@@ -2,12 +2,14 @@
from __future__ import annotations
import asyncio
from typing import Any
from unittest.mock import AsyncMock, patch
import pytest
from app.models.ban import ActiveBanListResponse, JailBannedIpsResponse
from app.models.geo import GeoDetail, GeoInfo
from app.models.jail import JailDetailResponse, JailListResponse
from app.services import jail_service
from app.services.jail_service import JailNotFoundError, JailOperationError
@@ -270,6 +272,28 @@ class TestListJails:
assert jail.idle is False
class TestLockInitialization:
"""Regression tests for asyncio lock creation in jail_service."""
async def test_reload_all_lock_is_lazy_initialised(self) -> None:
"""The reload-all lock should be created lazily on first use."""
jail_service._reload_all_lock = None
lock = _ = jail_service._get_reload_all_lock()
assert isinstance(lock, asyncio.Lock)
assert jail_service._reload_all_lock is lock
async def test_backend_cmd_lock_is_lazy_initialised(self) -> None:
"""The backend capability probe lock should be created lazily on first use."""
jail_service._backend_cmd_lock = None
lock = _ = jail_service._get_backend_cmd_lock()
assert isinstance(lock, asyncio.Lock)
assert jail_service._backend_cmd_lock is lock
class TestGetJail:
"""Unit tests for :func:`~app.services.jail_service.get_jail`."""
@@ -771,6 +795,30 @@ class TestLookupIp:
assert result["ip"] == "1.2.3.4"
assert "sshd" in result["currently_banned_in"]
async def test_geo_enricher_returns_geo_detail(self) -> None:
"""lookup_ip converts GeoInfo from the enricher into GeoDetail."""
responses = {
"get|--all|banned|1.2.3.4": (0, []),
"status": _make_global_status("sshd"),
"get|sshd|banip": (0, ["1.2.3.4", "5.6.7.8"]),
}
async def _enricher(ip: str) -> GeoInfo:
return GeoInfo(country_code="DE", country_name="Germany", asn="AS123", org="Acme")
with _patch_client(responses):
result = await jail_service.lookup_ip(
_SOCKET,
"1.2.3.4",
geo_enricher=_enricher,
)
assert isinstance(result["geo"], GeoDetail)
assert result["geo"].country_code == "DE"
assert result["geo"].country_name == "Germany"
assert result["geo"].asn == "AS123"
assert result["geo"].org == "Acme"
async def test_invalid_ip_raises(self) -> None:
"""lookup_ip raises ValueError for invalid IP."""
with pytest.raises(ValueError, match="Invalid IP"):