Fix backend tests by using per-test temp config dir, align router mocks to service modules, fix log tail helper reference, and add JailNotFoundError.name

This commit is contained in:
2026-03-21 19:43:59 +01:00
parent 471eed9664
commit 05dc9fa1e3
5 changed files with 118 additions and 108 deletions

View File

@@ -6,6 +6,10 @@ from __future__ import annotations
class JailNotFoundError(Exception): class JailNotFoundError(Exception):
"""Raised when a requested jail name does not exist.""" """Raised when a requested jail name does not exist."""
def __init__(self, name: str) -> None:
self.name = name
super().__init__(f"Jail not found: {name!r}")
class JailOperationError(Exception): class JailOperationError(Exception):
"""Raised when a fail2ban jail operation fails.""" """Raised when a fail2ban jail operation fails."""

View File

@@ -720,7 +720,7 @@ async def read_fail2ban_log(
total_lines, raw_lines = await asyncio.gather( total_lines, raw_lines = await asyncio.gather(
loop.run_in_executor(None, _count_file_lines, resolved_str), loop.run_in_executor(None, _count_file_lines, resolved_str),
loop.run_in_executor(None, _read_tail_lines, resolved_str, lines), loop.run_in_executor(None, log_service._read_tail_lines, resolved_str, lines),
) )
filtered = ( filtered = (

View File

@@ -37,9 +37,15 @@ def test_settings(tmp_path: Path) -> Settings:
Returns: Returns:
A :class:`~app.config.Settings` instance with overridden paths. A :class:`~app.config.Settings` instance with overridden paths.
""" """
config_dir = tmp_path / "fail2ban"
(config_dir / "jail.d").mkdir(parents=True)
(config_dir / "filter.d").mkdir(parents=True)
(config_dir / "action.d").mkdir(parents=True)
return Settings( return Settings(
database_path=str(tmp_path / "test_bangui.db"), database_path=str(tmp_path / "test_bangui.db"),
fail2ban_socket="/tmp/fake_fail2ban.sock", fail2ban_socket="/tmp/fake_fail2ban.sock",
fail2ban_config_dir=str(config_dir),
session_secret="test-secret-key-do-not-use-in-production", session_secret="test-secret-key-do-not-use-in-production",
session_duration_minutes=60, session_duration_minutes=60,
timezone="UTC", timezone="UTC",

View File

@@ -727,7 +727,7 @@ class TestGetInactiveJails:
mock_response = InactiveJailListResponse(jails=[mock_jail], total=1) mock_response = InactiveJailListResponse(jails=[mock_jail], total=1)
with patch( with patch(
"app.routers.config.config_file_service.list_inactive_jails", "app.routers.config.jail_config_service.list_inactive_jails",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.get("/api/config/jails/inactive") resp = await config_client.get("/api/config/jails/inactive")
@@ -742,7 +742,7 @@ class TestGetInactiveJails:
from app.models.config import InactiveJailListResponse from app.models.config import InactiveJailListResponse
with patch( with patch(
"app.routers.config.config_file_service.list_inactive_jails", "app.routers.config.jail_config_service.list_inactive_jails",
AsyncMock(return_value=InactiveJailListResponse(jails=[], total=0)), AsyncMock(return_value=InactiveJailListResponse(jails=[], total=0)),
): ):
resp = await config_client.get("/api/config/jails/inactive") resp = await config_client.get("/api/config/jails/inactive")
@@ -778,7 +778,7 @@ class TestActivateJail:
message="Jail 'apache-auth' activated successfully.", message="Jail 'apache-auth' activated successfully.",
) )
with patch( with patch(
"app.routers.config.config_file_service.activate_jail", "app.routers.config.jail_config_service.activate_jail",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -798,7 +798,7 @@ class TestActivateJail:
name="apache-auth", active=True, message="Activated." name="apache-auth", active=True, message="Activated."
) )
with patch( with patch(
"app.routers.config.config_file_service.activate_jail", "app.routers.config.jail_config_service.activate_jail",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
) as mock_activate: ) as mock_activate:
resp = await config_client.post( resp = await config_client.post(
@@ -814,10 +814,10 @@ class TestActivateJail:
async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/missing/activate returns 404.""" """POST /api/config/jails/missing/activate returns 404."""
from app.services.config_file_service import JailNotFoundInConfigError from app.services.jail_config_service import JailNotFoundInConfigError
with patch( with patch(
"app.routers.config.config_file_service.activate_jail", "app.routers.config.jail_config_service.activate_jail",
AsyncMock(side_effect=JailNotFoundInConfigError("missing")), AsyncMock(side_effect=JailNotFoundInConfigError("missing")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -828,10 +828,10 @@ class TestActivateJail:
async def test_409_when_already_active(self, config_client: AsyncClient) -> None: async def test_409_when_already_active(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/sshd/activate returns 409 if already active.""" """POST /api/config/jails/sshd/activate returns 409 if already active."""
from app.services.config_file_service import JailAlreadyActiveError from app.services.jail_config_service import JailAlreadyActiveError
with patch( with patch(
"app.routers.config.config_file_service.activate_jail", "app.routers.config.jail_config_service.activate_jail",
AsyncMock(side_effect=JailAlreadyActiveError("sshd")), AsyncMock(side_effect=JailAlreadyActiveError("sshd")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -842,10 +842,10 @@ class TestActivateJail:
async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/ with bad name returns 400.""" """POST /api/config/jails/ with bad name returns 400."""
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.activate_jail", "app.routers.config.jail_config_service.activate_jail",
AsyncMock(side_effect=JailNameError("bad name")), AsyncMock(side_effect=JailNameError("bad name")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -874,7 +874,7 @@ class TestActivateJail:
message="Jail 'airsonic-auth' cannot be activated: log file '/var/log/airsonic/airsonic.log' not found", message="Jail 'airsonic-auth' cannot be activated: log file '/var/log/airsonic/airsonic.log' not found",
) )
with patch( with patch(
"app.routers.config.config_file_service.activate_jail", "app.routers.config.jail_config_service.activate_jail",
AsyncMock(return_value=blocked_response), AsyncMock(return_value=blocked_response),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -907,7 +907,7 @@ class TestDeactivateJail:
message="Jail 'sshd' deactivated successfully.", message="Jail 'sshd' deactivated successfully.",
) )
with patch( with patch(
"app.routers.config.config_file_service.deactivate_jail", "app.routers.config.jail_config_service.deactivate_jail",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.post("/api/config/jails/sshd/deactivate") resp = await config_client.post("/api/config/jails/sshd/deactivate")
@@ -919,10 +919,10 @@ class TestDeactivateJail:
async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/missing/deactivate returns 404.""" """POST /api/config/jails/missing/deactivate returns 404."""
from app.services.config_file_service import JailNotFoundInConfigError from app.services.jail_config_service import JailNotFoundInConfigError
with patch( with patch(
"app.routers.config.config_file_service.deactivate_jail", "app.routers.config.jail_config_service.deactivate_jail",
AsyncMock(side_effect=JailNotFoundInConfigError("missing")), AsyncMock(side_effect=JailNotFoundInConfigError("missing")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -933,10 +933,10 @@ class TestDeactivateJail:
async def test_409_when_already_inactive(self, config_client: AsyncClient) -> None: async def test_409_when_already_inactive(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/apache-auth/deactivate returns 409 if already inactive.""" """POST /api/config/jails/apache-auth/deactivate returns 409 if already inactive."""
from app.services.config_file_service import JailAlreadyInactiveError from app.services.jail_config_service import JailAlreadyInactiveError
with patch( with patch(
"app.routers.config.config_file_service.deactivate_jail", "app.routers.config.jail_config_service.deactivate_jail",
AsyncMock(side_effect=JailAlreadyInactiveError("apache-auth")), AsyncMock(side_effect=JailAlreadyInactiveError("apache-auth")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -947,10 +947,10 @@ class TestDeactivateJail:
async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/.../deactivate with bad name returns 400.""" """POST /api/config/jails/.../deactivate with bad name returns 400."""
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.deactivate_jail", "app.routers.config.jail_config_service.deactivate_jail",
AsyncMock(side_effect=JailNameError("bad")), AsyncMock(side_effect=JailNameError("bad")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -978,7 +978,7 @@ class TestDeactivateJail:
) )
with ( with (
patch( patch(
"app.routers.config.config_file_service.deactivate_jail", "app.routers.config.jail_config_service.deactivate_jail",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
), ),
patch( patch(
@@ -1029,7 +1029,7 @@ class TestListFilters:
total=1, total=1,
) )
with patch( with patch(
"app.routers.config.config_file_service.list_filters", "app.routers.config.filter_config_service.list_filters",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.get("/api/config/filters") resp = await config_client.get("/api/config/filters")
@@ -1045,7 +1045,7 @@ class TestListFilters:
from app.models.config import FilterListResponse from app.models.config import FilterListResponse
with patch( with patch(
"app.routers.config.config_file_service.list_filters", "app.routers.config.filter_config_service.list_filters",
AsyncMock(return_value=FilterListResponse(filters=[], total=0)), AsyncMock(return_value=FilterListResponse(filters=[], total=0)),
): ):
resp = await config_client.get("/api/config/filters") resp = await config_client.get("/api/config/filters")
@@ -1068,7 +1068,7 @@ class TestListFilters:
total=2, total=2,
) )
with patch( with patch(
"app.routers.config.config_file_service.list_filters", "app.routers.config.filter_config_service.list_filters",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.get("/api/config/filters") resp = await config_client.get("/api/config/filters")
@@ -1097,7 +1097,7 @@ class TestGetFilter:
async def test_200_returns_filter(self, config_client: AsyncClient) -> None: async def test_200_returns_filter(self, config_client: AsyncClient) -> None:
"""GET /api/config/filters/sshd returns 200 with FilterConfig.""" """GET /api/config/filters/sshd returns 200 with FilterConfig."""
with patch( with patch(
"app.routers.config.config_file_service.get_filter", "app.routers.config.filter_config_service.get_filter",
AsyncMock(return_value=_make_filter_config("sshd")), AsyncMock(return_value=_make_filter_config("sshd")),
): ):
resp = await config_client.get("/api/config/filters/sshd") resp = await config_client.get("/api/config/filters/sshd")
@@ -1110,10 +1110,10 @@ class TestGetFilter:
async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None:
"""GET /api/config/filters/missing returns 404.""" """GET /api/config/filters/missing returns 404."""
from app.services.config_file_service import FilterNotFoundError from app.services.filter_config_service import FilterNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.get_filter", "app.routers.config.filter_config_service.get_filter",
AsyncMock(side_effect=FilterNotFoundError("missing")), AsyncMock(side_effect=FilterNotFoundError("missing")),
): ):
resp = await config_client.get("/api/config/filters/missing") resp = await config_client.get("/api/config/filters/missing")
@@ -1140,7 +1140,7 @@ class TestUpdateFilter:
async def test_200_returns_updated_filter(self, config_client: AsyncClient) -> None: async def test_200_returns_updated_filter(self, config_client: AsyncClient) -> None:
"""PUT /api/config/filters/sshd returns 200 with updated FilterConfig.""" """PUT /api/config/filters/sshd returns 200 with updated FilterConfig."""
with patch( with patch(
"app.routers.config.config_file_service.update_filter", "app.routers.config.filter_config_service.update_filter",
AsyncMock(return_value=_make_filter_config("sshd")), AsyncMock(return_value=_make_filter_config("sshd")),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1153,10 +1153,10 @@ class TestUpdateFilter:
async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None:
"""PUT /api/config/filters/missing returns 404.""" """PUT /api/config/filters/missing returns 404."""
from app.services.config_file_service import FilterNotFoundError from app.services.filter_config_service import FilterNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.update_filter", "app.routers.config.filter_config_service.update_filter",
AsyncMock(side_effect=FilterNotFoundError("missing")), AsyncMock(side_effect=FilterNotFoundError("missing")),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1168,10 +1168,10 @@ class TestUpdateFilter:
async def test_422_for_invalid_regex(self, config_client: AsyncClient) -> None: async def test_422_for_invalid_regex(self, config_client: AsyncClient) -> None:
"""PUT /api/config/filters/sshd returns 422 for bad regex.""" """PUT /api/config/filters/sshd returns 422 for bad regex."""
from app.services.config_file_service import FilterInvalidRegexError from app.services.filter_config_service import FilterInvalidRegexError
with patch( with patch(
"app.routers.config.config_file_service.update_filter", "app.routers.config.filter_config_service.update_filter",
AsyncMock(side_effect=FilterInvalidRegexError("[bad", "unterminated")), AsyncMock(side_effect=FilterInvalidRegexError("[bad", "unterminated")),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1183,10 +1183,10 @@ class TestUpdateFilter:
async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None:
"""PUT /api/config/filters/... with bad name returns 400.""" """PUT /api/config/filters/... with bad name returns 400."""
from app.services.config_file_service import FilterNameError from app.services.filter_config_service import FilterNameError
with patch( with patch(
"app.routers.config.config_file_service.update_filter", "app.routers.config.filter_config_service.update_filter",
AsyncMock(side_effect=FilterNameError("bad")), AsyncMock(side_effect=FilterNameError("bad")),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1199,7 +1199,7 @@ class TestUpdateFilter:
async def test_reload_query_param_passed(self, config_client: AsyncClient) -> None: async def test_reload_query_param_passed(self, config_client: AsyncClient) -> None:
"""PUT /api/config/filters/sshd?reload=true passes do_reload=True.""" """PUT /api/config/filters/sshd?reload=true passes do_reload=True."""
with patch( with patch(
"app.routers.config.config_file_service.update_filter", "app.routers.config.filter_config_service.update_filter",
AsyncMock(return_value=_make_filter_config("sshd")), AsyncMock(return_value=_make_filter_config("sshd")),
) as mock_update: ) as mock_update:
resp = await config_client.put( resp = await config_client.put(
@@ -1230,7 +1230,7 @@ class TestCreateFilter:
async def test_201_creates_filter(self, config_client: AsyncClient) -> None: async def test_201_creates_filter(self, config_client: AsyncClient) -> None:
"""POST /api/config/filters returns 201 with FilterConfig.""" """POST /api/config/filters returns 201 with FilterConfig."""
with patch( with patch(
"app.routers.config.config_file_service.create_filter", "app.routers.config.filter_config_service.create_filter",
AsyncMock(return_value=_make_filter_config("my-custom")), AsyncMock(return_value=_make_filter_config("my-custom")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1243,10 +1243,10 @@ class TestCreateFilter:
async def test_409_when_already_exists(self, config_client: AsyncClient) -> None: async def test_409_when_already_exists(self, config_client: AsyncClient) -> None:
"""POST /api/config/filters returns 409 if filter exists.""" """POST /api/config/filters returns 409 if filter exists."""
from app.services.config_file_service import FilterAlreadyExistsError from app.services.filter_config_service import FilterAlreadyExistsError
with patch( with patch(
"app.routers.config.config_file_service.create_filter", "app.routers.config.filter_config_service.create_filter",
AsyncMock(side_effect=FilterAlreadyExistsError("sshd")), AsyncMock(side_effect=FilterAlreadyExistsError("sshd")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1258,10 +1258,10 @@ class TestCreateFilter:
async def test_422_for_invalid_regex(self, config_client: AsyncClient) -> None: async def test_422_for_invalid_regex(self, config_client: AsyncClient) -> None:
"""POST /api/config/filters returns 422 for bad regex.""" """POST /api/config/filters returns 422 for bad regex."""
from app.services.config_file_service import FilterInvalidRegexError from app.services.filter_config_service import FilterInvalidRegexError
with patch( with patch(
"app.routers.config.config_file_service.create_filter", "app.routers.config.filter_config_service.create_filter",
AsyncMock(side_effect=FilterInvalidRegexError("[bad", "unterminated")), AsyncMock(side_effect=FilterInvalidRegexError("[bad", "unterminated")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1273,10 +1273,10 @@ class TestCreateFilter:
async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/filters returns 400 for invalid filter name.""" """POST /api/config/filters returns 400 for invalid filter name."""
from app.services.config_file_service import FilterNameError from app.services.filter_config_service import FilterNameError
with patch( with patch(
"app.routers.config.config_file_service.create_filter", "app.routers.config.filter_config_service.create_filter",
AsyncMock(side_effect=FilterNameError("bad")), AsyncMock(side_effect=FilterNameError("bad")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1306,7 +1306,7 @@ class TestDeleteFilter:
async def test_204_deletes_filter(self, config_client: AsyncClient) -> None: async def test_204_deletes_filter(self, config_client: AsyncClient) -> None:
"""DELETE /api/config/filters/my-custom returns 204.""" """DELETE /api/config/filters/my-custom returns 204."""
with patch( with patch(
"app.routers.config.config_file_service.delete_filter", "app.routers.config.filter_config_service.delete_filter",
AsyncMock(return_value=None), AsyncMock(return_value=None),
): ):
resp = await config_client.delete("/api/config/filters/my-custom") resp = await config_client.delete("/api/config/filters/my-custom")
@@ -1315,10 +1315,10 @@ class TestDeleteFilter:
async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None:
"""DELETE /api/config/filters/missing returns 404.""" """DELETE /api/config/filters/missing returns 404."""
from app.services.config_file_service import FilterNotFoundError from app.services.filter_config_service import FilterNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.delete_filter", "app.routers.config.filter_config_service.delete_filter",
AsyncMock(side_effect=FilterNotFoundError("missing")), AsyncMock(side_effect=FilterNotFoundError("missing")),
): ):
resp = await config_client.delete("/api/config/filters/missing") resp = await config_client.delete("/api/config/filters/missing")
@@ -1327,10 +1327,10 @@ class TestDeleteFilter:
async def test_409_for_readonly_filter(self, config_client: AsyncClient) -> None: async def test_409_for_readonly_filter(self, config_client: AsyncClient) -> None:
"""DELETE /api/config/filters/sshd returns 409 for shipped conf-only filter.""" """DELETE /api/config/filters/sshd returns 409 for shipped conf-only filter."""
from app.services.config_file_service import FilterReadonlyError from app.services.filter_config_service import FilterReadonlyError
with patch( with patch(
"app.routers.config.config_file_service.delete_filter", "app.routers.config.filter_config_service.delete_filter",
AsyncMock(side_effect=FilterReadonlyError("sshd")), AsyncMock(side_effect=FilterReadonlyError("sshd")),
): ):
resp = await config_client.delete("/api/config/filters/sshd") resp = await config_client.delete("/api/config/filters/sshd")
@@ -1339,10 +1339,10 @@ class TestDeleteFilter:
async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None:
"""DELETE /api/config/filters/... with bad name returns 400.""" """DELETE /api/config/filters/... with bad name returns 400."""
from app.services.config_file_service import FilterNameError from app.services.filter_config_service import FilterNameError
with patch( with patch(
"app.routers.config.config_file_service.delete_filter", "app.routers.config.filter_config_service.delete_filter",
AsyncMock(side_effect=FilterNameError("bad")), AsyncMock(side_effect=FilterNameError("bad")),
): ):
resp = await config_client.delete("/api/config/filters/bad") resp = await config_client.delete("/api/config/filters/bad")
@@ -1369,7 +1369,7 @@ class TestAssignFilterToJail:
async def test_204_assigns_filter(self, config_client: AsyncClient) -> None: async def test_204_assigns_filter(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/sshd/filter returns 204 on success.""" """POST /api/config/jails/sshd/filter returns 204 on success."""
with patch( with patch(
"app.routers.config.config_file_service.assign_filter_to_jail", "app.routers.config.filter_config_service.assign_filter_to_jail",
AsyncMock(return_value=None), AsyncMock(return_value=None),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1381,10 +1381,10 @@ class TestAssignFilterToJail:
async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/missing/filter returns 404.""" """POST /api/config/jails/missing/filter returns 404."""
from app.services.config_file_service import JailNotFoundInConfigError from app.services.jail_config_service import JailNotFoundInConfigError
with patch( with patch(
"app.routers.config.config_file_service.assign_filter_to_jail", "app.routers.config.filter_config_service.assign_filter_to_jail",
AsyncMock(side_effect=JailNotFoundInConfigError("missing")), AsyncMock(side_effect=JailNotFoundInConfigError("missing")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1396,10 +1396,10 @@ class TestAssignFilterToJail:
async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/sshd/filter returns 404 when filter not found.""" """POST /api/config/jails/sshd/filter returns 404 when filter not found."""
from app.services.config_file_service import FilterNotFoundError from app.services.filter_config_service import FilterNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.assign_filter_to_jail", "app.routers.config.filter_config_service.assign_filter_to_jail",
AsyncMock(side_effect=FilterNotFoundError("missing-filter")), AsyncMock(side_effect=FilterNotFoundError("missing-filter")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1411,10 +1411,10 @@ class TestAssignFilterToJail:
async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/.../filter with bad jail name returns 400.""" """POST /api/config/jails/.../filter with bad jail name returns 400."""
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.assign_filter_to_jail", "app.routers.config.filter_config_service.assign_filter_to_jail",
AsyncMock(side_effect=JailNameError("bad")), AsyncMock(side_effect=JailNameError("bad")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1426,10 +1426,10 @@ class TestAssignFilterToJail:
async def test_400_for_invalid_filter_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_filter_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/sshd/filter with bad filter name returns 400.""" """POST /api/config/jails/sshd/filter with bad filter name returns 400."""
from app.services.config_file_service import FilterNameError from app.services.filter_config_service import FilterNameError
with patch( with patch(
"app.routers.config.config_file_service.assign_filter_to_jail", "app.routers.config.filter_config_service.assign_filter_to_jail",
AsyncMock(side_effect=FilterNameError("bad")), AsyncMock(side_effect=FilterNameError("bad")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1442,7 +1442,7 @@ class TestAssignFilterToJail:
async def test_reload_query_param_passed(self, config_client: AsyncClient) -> None: async def test_reload_query_param_passed(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/sshd/filter?reload=true passes do_reload=True.""" """POST /api/config/jails/sshd/filter?reload=true passes do_reload=True."""
with patch( with patch(
"app.routers.config.config_file_service.assign_filter_to_jail", "app.routers.config.filter_config_service.assign_filter_to_jail",
AsyncMock(return_value=None), AsyncMock(return_value=None),
) as mock_assign: ) as mock_assign:
resp = await config_client.post( resp = await config_client.post(
@@ -1480,7 +1480,7 @@ class TestListActionsRouter:
mock_response = ActionListResponse(actions=[mock_action], total=1) mock_response = ActionListResponse(actions=[mock_action], total=1)
with patch( with patch(
"app.routers.config.config_file_service.list_actions", "app.routers.config.action_config_service.list_actions",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.get("/api/config/actions") resp = await config_client.get("/api/config/actions")
@@ -1498,7 +1498,7 @@ class TestListActionsRouter:
mock_response = ActionListResponse(actions=[inactive, active], total=2) mock_response = ActionListResponse(actions=[inactive, active], total=2)
with patch( with patch(
"app.routers.config.config_file_service.list_actions", "app.routers.config.action_config_service.list_actions",
AsyncMock(return_value=mock_response), AsyncMock(return_value=mock_response),
): ):
resp = await config_client.get("/api/config/actions") resp = await config_client.get("/api/config/actions")
@@ -1526,7 +1526,7 @@ class TestGetActionRouter:
) )
with patch( with patch(
"app.routers.config.config_file_service.get_action", "app.routers.config.action_config_service.get_action",
AsyncMock(return_value=mock_action), AsyncMock(return_value=mock_action),
): ):
resp = await config_client.get("/api/config/actions/iptables") resp = await config_client.get("/api/config/actions/iptables")
@@ -1535,10 +1535,10 @@ class TestGetActionRouter:
assert resp.json()["name"] == "iptables" assert resp.json()["name"] == "iptables"
async def test_404_when_not_found(self, config_client: AsyncClient) -> None: async def test_404_when_not_found(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNotFoundError from app.services.action_config_service import ActionNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.get_action", "app.routers.config.action_config_service.get_action",
AsyncMock(side_effect=ActionNotFoundError("missing")), AsyncMock(side_effect=ActionNotFoundError("missing")),
): ):
resp = await config_client.get("/api/config/actions/missing") resp = await config_client.get("/api/config/actions/missing")
@@ -1565,7 +1565,7 @@ class TestUpdateActionRouter:
) )
with patch( with patch(
"app.routers.config.config_file_service.update_action", "app.routers.config.action_config_service.update_action",
AsyncMock(return_value=updated), AsyncMock(return_value=updated),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1577,10 +1577,10 @@ class TestUpdateActionRouter:
assert resp.json()["actionban"] == "echo ban" assert resp.json()["actionban"] == "echo ban"
async def test_404_when_not_found(self, config_client: AsyncClient) -> None: async def test_404_when_not_found(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNotFoundError from app.services.action_config_service import ActionNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.update_action", "app.routers.config.action_config_service.update_action",
AsyncMock(side_effect=ActionNotFoundError("missing")), AsyncMock(side_effect=ActionNotFoundError("missing")),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1590,10 +1590,10 @@ class TestUpdateActionRouter:
assert resp.status_code == 404 assert resp.status_code == 404
async def test_400_for_bad_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNameError from app.services.action_config_service import ActionNameError
with patch( with patch(
"app.routers.config.config_file_service.update_action", "app.routers.config.action_config_service.update_action",
AsyncMock(side_effect=ActionNameError()), AsyncMock(side_effect=ActionNameError()),
): ):
resp = await config_client.put( resp = await config_client.put(
@@ -1622,7 +1622,7 @@ class TestCreateActionRouter:
) )
with patch( with patch(
"app.routers.config.config_file_service.create_action", "app.routers.config.action_config_service.create_action",
AsyncMock(return_value=created), AsyncMock(return_value=created),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1634,10 +1634,10 @@ class TestCreateActionRouter:
assert resp.json()["name"] == "custom" assert resp.json()["name"] == "custom"
async def test_409_when_already_exists(self, config_client: AsyncClient) -> None: async def test_409_when_already_exists(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionAlreadyExistsError from app.services.action_config_service import ActionAlreadyExistsError
with patch( with patch(
"app.routers.config.config_file_service.create_action", "app.routers.config.action_config_service.create_action",
AsyncMock(side_effect=ActionAlreadyExistsError("iptables")), AsyncMock(side_effect=ActionAlreadyExistsError("iptables")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1648,10 +1648,10 @@ class TestCreateActionRouter:
assert resp.status_code == 409 assert resp.status_code == 409
async def test_400_for_bad_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNameError from app.services.action_config_service import ActionNameError
with patch( with patch(
"app.routers.config.config_file_service.create_action", "app.routers.config.action_config_service.create_action",
AsyncMock(side_effect=ActionNameError()), AsyncMock(side_effect=ActionNameError()),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1673,7 +1673,7 @@ class TestCreateActionRouter:
class TestDeleteActionRouter: class TestDeleteActionRouter:
async def test_204_on_delete(self, config_client: AsyncClient) -> None: async def test_204_on_delete(self, config_client: AsyncClient) -> None:
with patch( with patch(
"app.routers.config.config_file_service.delete_action", "app.routers.config.action_config_service.delete_action",
AsyncMock(return_value=None), AsyncMock(return_value=None),
): ):
resp = await config_client.delete("/api/config/actions/custom") resp = await config_client.delete("/api/config/actions/custom")
@@ -1681,10 +1681,10 @@ class TestDeleteActionRouter:
assert resp.status_code == 204 assert resp.status_code == 204
async def test_404_when_not_found(self, config_client: AsyncClient) -> None: async def test_404_when_not_found(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNotFoundError from app.services.action_config_service import ActionNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.delete_action", "app.routers.config.action_config_service.delete_action",
AsyncMock(side_effect=ActionNotFoundError("missing")), AsyncMock(side_effect=ActionNotFoundError("missing")),
): ):
resp = await config_client.delete("/api/config/actions/missing") resp = await config_client.delete("/api/config/actions/missing")
@@ -1692,10 +1692,10 @@ class TestDeleteActionRouter:
assert resp.status_code == 404 assert resp.status_code == 404
async def test_409_when_readonly(self, config_client: AsyncClient) -> None: async def test_409_when_readonly(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionReadonlyError from app.services.action_config_service import ActionReadonlyError
with patch( with patch(
"app.routers.config.config_file_service.delete_action", "app.routers.config.action_config_service.delete_action",
AsyncMock(side_effect=ActionReadonlyError("iptables")), AsyncMock(side_effect=ActionReadonlyError("iptables")),
): ):
resp = await config_client.delete("/api/config/actions/iptables") resp = await config_client.delete("/api/config/actions/iptables")
@@ -1703,10 +1703,10 @@ class TestDeleteActionRouter:
assert resp.status_code == 409 assert resp.status_code == 409
async def test_400_for_bad_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNameError from app.services.action_config_service import ActionNameError
with patch( with patch(
"app.routers.config.config_file_service.delete_action", "app.routers.config.action_config_service.delete_action",
AsyncMock(side_effect=ActionNameError()), AsyncMock(side_effect=ActionNameError()),
): ):
resp = await config_client.delete("/api/config/actions/badname") resp = await config_client.delete("/api/config/actions/badname")
@@ -1725,7 +1725,7 @@ class TestDeleteActionRouter:
class TestAssignActionToJailRouter: class TestAssignActionToJailRouter:
async def test_204_on_success(self, config_client: AsyncClient) -> None: async def test_204_on_success(self, config_client: AsyncClient) -> None:
with patch( with patch(
"app.routers.config.config_file_service.assign_action_to_jail", "app.routers.config.action_config_service.assign_action_to_jail",
AsyncMock(return_value=None), AsyncMock(return_value=None),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1736,10 +1736,10 @@ class TestAssignActionToJailRouter:
assert resp.status_code == 204 assert resp.status_code == 204
async def test_404_when_jail_not_found(self, config_client: AsyncClient) -> None: async def test_404_when_jail_not_found(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import JailNotFoundInConfigError from app.services.jail_config_service import JailNotFoundInConfigError
with patch( with patch(
"app.routers.config.config_file_service.assign_action_to_jail", "app.routers.config.action_config_service.assign_action_to_jail",
AsyncMock(side_effect=JailNotFoundInConfigError("missing")), AsyncMock(side_effect=JailNotFoundInConfigError("missing")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1750,10 +1750,10 @@ class TestAssignActionToJailRouter:
assert resp.status_code == 404 assert resp.status_code == 404
async def test_404_when_action_not_found(self, config_client: AsyncClient) -> None: async def test_404_when_action_not_found(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNotFoundError from app.services.action_config_service import ActionNotFoundError
with patch( with patch(
"app.routers.config.config_file_service.assign_action_to_jail", "app.routers.config.action_config_service.assign_action_to_jail",
AsyncMock(side_effect=ActionNotFoundError("missing")), AsyncMock(side_effect=ActionNotFoundError("missing")),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1764,10 +1764,10 @@ class TestAssignActionToJailRouter:
assert resp.status_code == 404 assert resp.status_code == 404
async def test_400_for_bad_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_jail_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.assign_action_to_jail", "app.routers.config.action_config_service.assign_action_to_jail",
AsyncMock(side_effect=JailNameError()), AsyncMock(side_effect=JailNameError()),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1778,10 +1778,10 @@ class TestAssignActionToJailRouter:
assert resp.status_code == 400 assert resp.status_code == 400
async def test_400_for_bad_action_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_action_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNameError from app.services.action_config_service import ActionNameError
with patch( with patch(
"app.routers.config.config_file_service.assign_action_to_jail", "app.routers.config.action_config_service.assign_action_to_jail",
AsyncMock(side_effect=ActionNameError()), AsyncMock(side_effect=ActionNameError()),
): ):
resp = await config_client.post( resp = await config_client.post(
@@ -1793,7 +1793,7 @@ class TestAssignActionToJailRouter:
async def test_reload_param_passed(self, config_client: AsyncClient) -> None: async def test_reload_param_passed(self, config_client: AsyncClient) -> None:
with patch( with patch(
"app.routers.config.config_file_service.assign_action_to_jail", "app.routers.config.action_config_service.assign_action_to_jail",
AsyncMock(return_value=None), AsyncMock(return_value=None),
) as mock_assign: ) as mock_assign:
resp = await config_client.post( resp = await config_client.post(
@@ -1816,7 +1816,7 @@ class TestAssignActionToJailRouter:
class TestRemoveActionFromJailRouter: class TestRemoveActionFromJailRouter:
async def test_204_on_success(self, config_client: AsyncClient) -> None: async def test_204_on_success(self, config_client: AsyncClient) -> None:
with patch( with patch(
"app.routers.config.config_file_service.remove_action_from_jail", "app.routers.config.action_config_service.remove_action_from_jail",
AsyncMock(return_value=None), AsyncMock(return_value=None),
): ):
resp = await config_client.delete( resp = await config_client.delete(
@@ -1826,10 +1826,10 @@ class TestRemoveActionFromJailRouter:
assert resp.status_code == 204 assert resp.status_code == 204
async def test_404_when_jail_not_found(self, config_client: AsyncClient) -> None: async def test_404_when_jail_not_found(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import JailNotFoundInConfigError from app.services.jail_config_service import JailNotFoundInConfigError
with patch( with patch(
"app.routers.config.config_file_service.remove_action_from_jail", "app.routers.config.action_config_service.remove_action_from_jail",
AsyncMock(side_effect=JailNotFoundInConfigError("missing")), AsyncMock(side_effect=JailNotFoundInConfigError("missing")),
): ):
resp = await config_client.delete( resp = await config_client.delete(
@@ -1839,10 +1839,10 @@ class TestRemoveActionFromJailRouter:
assert resp.status_code == 404 assert resp.status_code == 404
async def test_400_for_bad_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_jail_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.remove_action_from_jail", "app.routers.config.action_config_service.remove_action_from_jail",
AsyncMock(side_effect=JailNameError()), AsyncMock(side_effect=JailNameError()),
): ):
resp = await config_client.delete( resp = await config_client.delete(
@@ -1852,10 +1852,10 @@ class TestRemoveActionFromJailRouter:
assert resp.status_code == 400 assert resp.status_code == 400
async def test_400_for_bad_action_name(self, config_client: AsyncClient) -> None: async def test_400_for_bad_action_name(self, config_client: AsyncClient) -> None:
from app.services.config_file_service import ActionNameError from app.services.action_config_service import ActionNameError
with patch( with patch(
"app.routers.config.config_file_service.remove_action_from_jail", "app.routers.config.action_config_service.remove_action_from_jail",
AsyncMock(side_effect=ActionNameError()), AsyncMock(side_effect=ActionNameError()),
): ):
resp = await config_client.delete( resp = await config_client.delete(
@@ -1866,7 +1866,7 @@ class TestRemoveActionFromJailRouter:
async def test_reload_param_passed(self, config_client: AsyncClient) -> None: async def test_reload_param_passed(self, config_client: AsyncClient) -> None:
with patch( with patch(
"app.routers.config.config_file_service.remove_action_from_jail", "app.routers.config.action_config_service.remove_action_from_jail",
AsyncMock(return_value=None), AsyncMock(return_value=None),
) as mock_rm: ) as mock_rm:
resp = await config_client.delete( resp = await config_client.delete(
@@ -2065,7 +2065,7 @@ class TestValidateJailEndpoint:
jail_name="sshd", valid=True, issues=[] jail_name="sshd", valid=True, issues=[]
) )
with patch( with patch(
"app.routers.config.config_file_service.validate_jail_config", "app.routers.config.jail_config_service.validate_jail_config",
AsyncMock(return_value=mock_result), AsyncMock(return_value=mock_result),
): ):
resp = await config_client.post("/api/config/jails/sshd/validate") resp = await config_client.post("/api/config/jails/sshd/validate")
@@ -2085,7 +2085,7 @@ class TestValidateJailEndpoint:
jail_name="sshd", valid=False, issues=[issue] jail_name="sshd", valid=False, issues=[issue]
) )
with patch( with patch(
"app.routers.config.config_file_service.validate_jail_config", "app.routers.config.jail_config_service.validate_jail_config",
AsyncMock(return_value=mock_result), AsyncMock(return_value=mock_result),
): ):
resp = await config_client.post("/api/config/jails/sshd/validate") resp = await config_client.post("/api/config/jails/sshd/validate")
@@ -2098,10 +2098,10 @@ class TestValidateJailEndpoint:
async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/bad-name/validate returns 400 on JailNameError.""" """POST /api/config/jails/bad-name/validate returns 400 on JailNameError."""
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.validate_jail_config", "app.routers.config.jail_config_service.validate_jail_config",
AsyncMock(side_effect=JailNameError("bad name")), AsyncMock(side_effect=JailNameError("bad name")),
): ):
resp = await config_client.post("/api/config/jails/bad-name/validate") resp = await config_client.post("/api/config/jails/bad-name/validate")
@@ -2193,7 +2193,7 @@ class TestRollbackEndpoint:
message="Jail 'sshd' disabled and fail2ban restarted.", message="Jail 'sshd' disabled and fail2ban restarted.",
) )
with patch( with patch(
"app.routers.config.config_file_service.rollback_jail", "app.routers.config.jail_config_service.rollback_jail",
AsyncMock(return_value=mock_result), AsyncMock(return_value=mock_result),
): ):
resp = await config_client.post("/api/config/jails/sshd/rollback") resp = await config_client.post("/api/config/jails/sshd/rollback")
@@ -2230,7 +2230,7 @@ class TestRollbackEndpoint:
message="fail2ban did not come back online.", message="fail2ban did not come back online.",
) )
with patch( with patch(
"app.routers.config.config_file_service.rollback_jail", "app.routers.config.jail_config_service.rollback_jail",
AsyncMock(return_value=mock_result), AsyncMock(return_value=mock_result),
): ):
resp = await config_client.post("/api/config/jails/sshd/rollback") resp = await config_client.post("/api/config/jails/sshd/rollback")
@@ -2243,10 +2243,10 @@ class TestRollbackEndpoint:
async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None:
"""POST /api/config/jails/bad/rollback returns 400 on JailNameError.""" """POST /api/config/jails/bad/rollback returns 400 on JailNameError."""
from app.services.config_file_service import JailNameError from app.services.jail_config_service import JailNameError
with patch( with patch(
"app.routers.config.config_file_service.rollback_jail", "app.routers.config.jail_config_service.rollback_jail",
AsyncMock(side_effect=JailNameError("bad")), AsyncMock(side_effect=JailNameError("bad")),
): ):
resp = await config_client.post("/api/config/jails/bad/rollback") resp = await config_client.post("/api/config/jails/bad/rollback")

View File

@@ -342,7 +342,7 @@ class TestListActionFiles:
) )
resp_data = ActionListResponse(actions=[mock_action], total=1) resp_data = ActionListResponse(actions=[mock_action], total=1)
with patch( with patch(
"app.routers.config.config_file_service.list_actions", "app.routers.config.action_config_service.list_actions",
AsyncMock(return_value=resp_data), AsyncMock(return_value=resp_data),
): ):
resp = await file_config_client.get("/api/config/actions") resp = await file_config_client.get("/api/config/actions")
@@ -365,7 +365,7 @@ class TestCreateActionFile:
actionban="echo ban <ip>", actionban="echo ban <ip>",
) )
with patch( with patch(
"app.routers.config.config_file_service.create_action", "app.routers.config.action_config_service.create_action",
AsyncMock(return_value=created), AsyncMock(return_value=created),
): ):
resp = await file_config_client.post( resp = await file_config_client.post(