Extract fail2ban restart orchestration into jail_service

This commit is contained in:
2026-04-17 15:23:54 +02:00
parent c21cf82e9e
commit 33643880ed
5 changed files with 109 additions and 60 deletions

View File

@@ -406,19 +406,9 @@ class TestRestartFail2ban:
async def test_204_on_success(self, config_client: AsyncClient) -> None:
"""POST /api/config/restart returns 204 when fail2ban restarts cleanly."""
with (
patch(
"app.routers.config_misc.jail_service.restart",
AsyncMock(return_value=None),
),
patch(
"app.routers.config_misc.start_daemon",
AsyncMock(return_value=True),
),
patch(
"app.routers.config_misc.wait_for_fail2ban",
AsyncMock(return_value=True),
),
with patch(
"app.routers.config_misc.jail_service.restart_daemon",
AsyncMock(return_value=True),
):
resp = await config_client.post("/api/config/restart")
@@ -426,19 +416,9 @@ class TestRestartFail2ban:
async def test_503_when_fail2ban_does_not_come_back(self, config_client: AsyncClient) -> None:
"""POST /api/config/restart returns 503 when fail2ban does not come back online."""
with (
patch(
"app.routers.config_misc.jail_service.restart",
AsyncMock(return_value=None),
),
patch(
"app.routers.config_misc.start_daemon",
AsyncMock(return_value=True),
),
patch(
"app.routers.config_misc.wait_for_fail2ban",
AsyncMock(return_value=False),
),
with patch(
"app.routers.config_misc.jail_service.restart_daemon",
AsyncMock(return_value=False),
):
resp = await config_client.post("/api/config/restart")
@@ -449,7 +429,7 @@ class TestRestartFail2ban:
from app.services.jail_service import JailOperationError
with patch(
"app.routers.config_misc.jail_service.restart",
"app.routers.config_misc.jail_service.restart_daemon",
AsyncMock(side_effect=JailOperationError("stop failed")),
):
resp = await config_client.post("/api/config/restart")
@@ -461,33 +441,24 @@ class TestRestartFail2ban:
from app.exceptions import Fail2BanConnectionError
with patch(
"app.routers.config_misc.jail_service.restart",
"app.routers.config_misc.jail_service.restart_daemon",
AsyncMock(side_effect=Fail2BanConnectionError("no socket", "/fake.sock")),
):
resp = await config_client.post("/api/config/restart")
assert resp.status_code == 502
async def test_start_daemon_called_after_stop(self, config_client: AsyncClient) -> None:
"""start_daemon is called after a successful stop."""
mock_start = AsyncMock(return_value=True)
with (
patch(
"app.routers.config_misc.jail_service.restart",
AsyncMock(return_value=None),
),
patch(
"app.routers.config_misc.start_daemon",
mock_start,
),
patch(
"app.routers.config_misc.wait_for_fail2ban",
AsyncMock(return_value=True),
),
async def test_service_restart_daemon_called(self, config_client: AsyncClient) -> None:
"""The router delegates restart orchestration to jail_service.restart_daemon."""
mock_restart = AsyncMock(return_value=True)
with patch(
"app.routers.config_misc.jail_service.restart_daemon",
mock_restart,
):
await config_client.post("/api/config/restart")
resp = await config_client.post("/api/config/restart")
mock_start.assert_awaited_once()
assert resp.status_code == 204
mock_restart.assert_awaited_once()
# ---------------------------------------------------------------------------

View File

@@ -8,12 +8,12 @@ from unittest.mock import AsyncMock, patch
import pytest
from app.exceptions import Fail2BanConnectionError
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
from app.exceptions import Fail2BanConnectionError
# ---------------------------------------------------------------------------
# Helpers
@@ -492,6 +492,47 @@ class TestJailControls:
):
await jail_service.restart(_SOCKET)
async def test_restart_daemon_returns_true_on_success(self) -> None:
"""restart_daemon returns True when stop, start, and probe all succeed."""
with (
patch("app.services.jail_service.restart", AsyncMock(return_value=None)),
patch("app.services.jail_service.start_daemon", AsyncMock(return_value=True)),
patch("app.services.jail_service.wait_for_fail2ban", AsyncMock(return_value=True)),
):
result = await jail_service.restart_daemon(
_SOCKET,
["fail2ban-client", "start"],
)
assert result is True
async def test_restart_daemon_returns_false_when_start_fails(self) -> None:
"""restart_daemon returns False when the configured start command fails."""
with (
patch("app.services.jail_service.restart", AsyncMock(return_value=None)),
patch("app.services.jail_service.start_daemon", AsyncMock(return_value=False)),
):
result = await jail_service.restart_daemon(
_SOCKET,
["fail2ban-client", "start"],
)
assert result is False
async def test_restart_daemon_returns_false_when_wait_fails(self) -> None:
"""restart_daemon returns False when fail2ban does not become responsive."""
with (
patch("app.services.jail_service.restart", AsyncMock(return_value=None)),
patch("app.services.jail_service.start_daemon", AsyncMock(return_value=True)),
patch("app.services.jail_service.wait_for_fail2ban", AsyncMock(return_value=False)),
):
result = await jail_service.restart_daemon(
_SOCKET,
["fail2ban-client", "start"],
)
assert result is False
async def test_start_not_found_raises(self) -> None:
"""start_jail raises JailNotFoundError for unknown jail."""
with _patch_client({"start|ghost": (1, Exception("Unknown jail: 'ghost'"))}), pytest.raises(JailNotFoundError):