diff --git a/backend/app/exceptions.py b/backend/app/exceptions.py index dd41886..1d855bc 100644 --- a/backend/app/exceptions.py +++ b/backend/app/exceptions.py @@ -6,6 +6,10 @@ from __future__ import annotations class JailNotFoundError(Exception): """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): """Raised when a fail2ban jail operation fails.""" diff --git a/backend/app/services/config_service.py b/backend/app/services/config_service.py index f626c18..f2f08d8 100644 --- a/backend/app/services/config_service.py +++ b/backend/app/services/config_service.py @@ -719,7 +719,7 @@ async def read_fail2ban_log( total_lines, raw_lines = await asyncio.gather( 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 = ( diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 44fc64c..dfa4617 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -37,9 +37,15 @@ def test_settings(tmp_path: Path) -> Settings: Returns: 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( database_path=str(tmp_path / "test_bangui.db"), fail2ban_socket="/tmp/fake_fail2ban.sock", + fail2ban_config_dir=str(config_dir), session_secret="test-secret-key-do-not-use-in-production", session_duration_minutes=60, timezone="UTC", diff --git a/backend/tests/test_routers/test_config.py b/backend/tests/test_routers/test_config.py index d42da64..0f84986 100644 --- a/backend/tests/test_routers/test_config.py +++ b/backend/tests/test_routers/test_config.py @@ -725,7 +725,7 @@ class TestGetInactiveJails: mock_response = InactiveJailListResponse(jails=[mock_jail], total=1) 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), ): resp = await config_client.get("/api/config/jails/inactive") @@ -740,7 +740,7 @@ class TestGetInactiveJails: from app.models.config import InactiveJailListResponse 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)), ): resp = await config_client.get("/api/config/jails/inactive") @@ -776,7 +776,7 @@ class TestActivateJail: message="Jail 'apache-auth' activated successfully.", ) with patch( - "app.routers.config.config_file_service.activate_jail", + "app.routers.config.jail_config_service.activate_jail", AsyncMock(return_value=mock_response), ): resp = await config_client.post( @@ -796,7 +796,7 @@ class TestActivateJail: name="apache-auth", active=True, message="Activated." ) with patch( - "app.routers.config.config_file_service.activate_jail", + "app.routers.config.jail_config_service.activate_jail", AsyncMock(return_value=mock_response), ) as mock_activate: resp = await config_client.post( @@ -812,10 +812,10 @@ class TestActivateJail: async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.activate_jail", + "app.routers.config.jail_config_service.activate_jail", AsyncMock(side_effect=JailNotFoundInConfigError("missing")), ): resp = await config_client.post( @@ -826,10 +826,10 @@ class TestActivateJail: async def test_409_when_already_active(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.activate_jail", + "app.routers.config.jail_config_service.activate_jail", AsyncMock(side_effect=JailAlreadyActiveError("sshd")), ): resp = await config_client.post( @@ -840,10 +840,10 @@ class TestActivateJail: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.activate_jail", + "app.routers.config.jail_config_service.activate_jail", AsyncMock(side_effect=JailNameError("bad name")), ): resp = await config_client.post( @@ -872,7 +872,7 @@ class TestActivateJail: message="Jail 'airsonic-auth' cannot be activated: log file '/var/log/airsonic/airsonic.log' not found", ) with patch( - "app.routers.config.config_file_service.activate_jail", + "app.routers.config.jail_config_service.activate_jail", AsyncMock(return_value=blocked_response), ): resp = await config_client.post( @@ -905,7 +905,7 @@ class TestDeactivateJail: message="Jail 'sshd' deactivated successfully.", ) with patch( - "app.routers.config.config_file_service.deactivate_jail", + "app.routers.config.jail_config_service.deactivate_jail", AsyncMock(return_value=mock_response), ): resp = await config_client.post("/api/config/jails/sshd/deactivate") @@ -917,10 +917,10 @@ class TestDeactivateJail: async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.deactivate_jail", + "app.routers.config.jail_config_service.deactivate_jail", AsyncMock(side_effect=JailNotFoundInConfigError("missing")), ): resp = await config_client.post( @@ -931,10 +931,10 @@ class TestDeactivateJail: async def test_409_when_already_inactive(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.deactivate_jail", + "app.routers.config.jail_config_service.deactivate_jail", AsyncMock(side_effect=JailAlreadyInactiveError("apache-auth")), ): resp = await config_client.post( @@ -945,10 +945,10 @@ class TestDeactivateJail: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.deactivate_jail", + "app.routers.config.jail_config_service.deactivate_jail", AsyncMock(side_effect=JailNameError("bad")), ): resp = await config_client.post( @@ -976,7 +976,7 @@ class TestDeactivateJail: ) with ( patch( - "app.routers.config.config_file_service.deactivate_jail", + "app.routers.config.jail_config_service.deactivate_jail", AsyncMock(return_value=mock_response), ), patch( @@ -1027,7 +1027,7 @@ class TestListFilters: total=1, ) with patch( - "app.routers.config.config_file_service.list_filters", + "app.routers.config.filter_config_service.list_filters", AsyncMock(return_value=mock_response), ): resp = await config_client.get("/api/config/filters") @@ -1043,7 +1043,7 @@ class TestListFilters: from app.models.config import FilterListResponse 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)), ): resp = await config_client.get("/api/config/filters") @@ -1066,7 +1066,7 @@ class TestListFilters: total=2, ) with patch( - "app.routers.config.config_file_service.list_filters", + "app.routers.config.filter_config_service.list_filters", AsyncMock(return_value=mock_response), ): resp = await config_client.get("/api/config/filters") @@ -1095,7 +1095,7 @@ class TestGetFilter: async def test_200_returns_filter(self, config_client: AsyncClient) -> None: """GET /api/config/filters/sshd returns 200 with FilterConfig.""" 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")), ): resp = await config_client.get("/api/config/filters/sshd") @@ -1108,10 +1108,10 @@ class TestGetFilter: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.get_filter", + "app.routers.config.filter_config_service.get_filter", AsyncMock(side_effect=FilterNotFoundError("missing")), ): resp = await config_client.get("/api/config/filters/missing") @@ -1138,7 +1138,7 @@ class TestUpdateFilter: async def test_200_returns_updated_filter(self, config_client: AsyncClient) -> None: """PUT /api/config/filters/sshd returns 200 with updated FilterConfig.""" 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")), ): resp = await config_client.put( @@ -1151,10 +1151,10 @@ class TestUpdateFilter: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.update_filter", + "app.routers.config.filter_config_service.update_filter", AsyncMock(side_effect=FilterNotFoundError("missing")), ): resp = await config_client.put( @@ -1166,10 +1166,10 @@ class TestUpdateFilter: async def test_422_for_invalid_regex(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.update_filter", + "app.routers.config.filter_config_service.update_filter", AsyncMock(side_effect=FilterInvalidRegexError("[bad", "unterminated")), ): resp = await config_client.put( @@ -1181,10 +1181,10 @@ class TestUpdateFilter: async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.update_filter", + "app.routers.config.filter_config_service.update_filter", AsyncMock(side_effect=FilterNameError("bad")), ): resp = await config_client.put( @@ -1197,7 +1197,7 @@ class TestUpdateFilter: async def test_reload_query_param_passed(self, config_client: AsyncClient) -> None: """PUT /api/config/filters/sshd?reload=true passes do_reload=True.""" 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")), ) as mock_update: resp = await config_client.put( @@ -1228,7 +1228,7 @@ class TestCreateFilter: async def test_201_creates_filter(self, config_client: AsyncClient) -> None: """POST /api/config/filters returns 201 with FilterConfig.""" 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")), ): resp = await config_client.post( @@ -1241,10 +1241,10 @@ class TestCreateFilter: async def test_409_when_already_exists(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.create_filter", + "app.routers.config.filter_config_service.create_filter", AsyncMock(side_effect=FilterAlreadyExistsError("sshd")), ): resp = await config_client.post( @@ -1256,10 +1256,10 @@ class TestCreateFilter: async def test_422_for_invalid_regex(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.create_filter", + "app.routers.config.filter_config_service.create_filter", AsyncMock(side_effect=FilterInvalidRegexError("[bad", "unterminated")), ): resp = await config_client.post( @@ -1271,10 +1271,10 @@ class TestCreateFilter: async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.create_filter", + "app.routers.config.filter_config_service.create_filter", AsyncMock(side_effect=FilterNameError("bad")), ): resp = await config_client.post( @@ -1304,7 +1304,7 @@ class TestDeleteFilter: async def test_204_deletes_filter(self, config_client: AsyncClient) -> None: """DELETE /api/config/filters/my-custom returns 204.""" with patch( - "app.routers.config.config_file_service.delete_filter", + "app.routers.config.filter_config_service.delete_filter", AsyncMock(return_value=None), ): resp = await config_client.delete("/api/config/filters/my-custom") @@ -1313,10 +1313,10 @@ class TestDeleteFilter: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.delete_filter", + "app.routers.config.filter_config_service.delete_filter", AsyncMock(side_effect=FilterNotFoundError("missing")), ): resp = await config_client.delete("/api/config/filters/missing") @@ -1325,10 +1325,10 @@ class TestDeleteFilter: async def test_409_for_readonly_filter(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.delete_filter", + "app.routers.config.filter_config_service.delete_filter", AsyncMock(side_effect=FilterReadonlyError("sshd")), ): resp = await config_client.delete("/api/config/filters/sshd") @@ -1337,10 +1337,10 @@ class TestDeleteFilter: async def test_400_for_invalid_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.delete_filter", + "app.routers.config.filter_config_service.delete_filter", AsyncMock(side_effect=FilterNameError("bad")), ): resp = await config_client.delete("/api/config/filters/bad") @@ -1367,7 +1367,7 @@ class TestAssignFilterToJail: async def test_204_assigns_filter(self, config_client: AsyncClient) -> None: """POST /api/config/jails/sshd/filter returns 204 on success.""" 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), ): resp = await config_client.post( @@ -1379,10 +1379,10 @@ class TestAssignFilterToJail: async def test_404_for_unknown_jail(self, config_client: AsyncClient) -> None: """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( - "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")), ): resp = await config_client.post( @@ -1394,10 +1394,10 @@ class TestAssignFilterToJail: async def test_404_for_unknown_filter(self, config_client: AsyncClient) -> None: """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( - "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")), ): resp = await config_client.post( @@ -1409,10 +1409,10 @@ class TestAssignFilterToJail: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: """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( - "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")), ): resp = await config_client.post( @@ -1424,10 +1424,10 @@ class TestAssignFilterToJail: 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.""" - from app.services.config_file_service import FilterNameError + from app.services.filter_config_service import FilterNameError 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")), ): resp = await config_client.post( @@ -1440,7 +1440,7 @@ class TestAssignFilterToJail: async def test_reload_query_param_passed(self, config_client: AsyncClient) -> None: """POST /api/config/jails/sshd/filter?reload=true passes do_reload=True.""" 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), ) as mock_assign: resp = await config_client.post( @@ -1478,7 +1478,7 @@ class TestListActionsRouter: mock_response = ActionListResponse(actions=[mock_action], total=1) with patch( - "app.routers.config.config_file_service.list_actions", + "app.routers.config.action_config_service.list_actions", AsyncMock(return_value=mock_response), ): resp = await config_client.get("/api/config/actions") @@ -1496,7 +1496,7 @@ class TestListActionsRouter: mock_response = ActionListResponse(actions=[inactive, active], total=2) with patch( - "app.routers.config.config_file_service.list_actions", + "app.routers.config.action_config_service.list_actions", AsyncMock(return_value=mock_response), ): resp = await config_client.get("/api/config/actions") @@ -1524,7 +1524,7 @@ class TestGetActionRouter: ) with patch( - "app.routers.config.config_file_service.get_action", + "app.routers.config.action_config_service.get_action", AsyncMock(return_value=mock_action), ): resp = await config_client.get("/api/config/actions/iptables") @@ -1533,10 +1533,10 @@ class TestGetActionRouter: assert resp.json()["name"] == "iptables" 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( - "app.routers.config.config_file_service.get_action", + "app.routers.config.action_config_service.get_action", AsyncMock(side_effect=ActionNotFoundError("missing")), ): resp = await config_client.get("/api/config/actions/missing") @@ -1563,7 +1563,7 @@ class TestUpdateActionRouter: ) with patch( - "app.routers.config.config_file_service.update_action", + "app.routers.config.action_config_service.update_action", AsyncMock(return_value=updated), ): resp = await config_client.put( @@ -1575,10 +1575,10 @@ class TestUpdateActionRouter: assert resp.json()["actionban"] == "echo ban" 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( - "app.routers.config.config_file_service.update_action", + "app.routers.config.action_config_service.update_action", AsyncMock(side_effect=ActionNotFoundError("missing")), ): resp = await config_client.put( @@ -1588,10 +1588,10 @@ class TestUpdateActionRouter: assert resp.status_code == 404 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( - "app.routers.config.config_file_service.update_action", + "app.routers.config.action_config_service.update_action", AsyncMock(side_effect=ActionNameError()), ): resp = await config_client.put( @@ -1620,7 +1620,7 @@ class TestCreateActionRouter: ) with patch( - "app.routers.config.config_file_service.create_action", + "app.routers.config.action_config_service.create_action", AsyncMock(return_value=created), ): resp = await config_client.post( @@ -1632,10 +1632,10 @@ class TestCreateActionRouter: assert resp.json()["name"] == "custom" 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( - "app.routers.config.config_file_service.create_action", + "app.routers.config.action_config_service.create_action", AsyncMock(side_effect=ActionAlreadyExistsError("iptables")), ): resp = await config_client.post( @@ -1646,10 +1646,10 @@ class TestCreateActionRouter: assert resp.status_code == 409 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( - "app.routers.config.config_file_service.create_action", + "app.routers.config.action_config_service.create_action", AsyncMock(side_effect=ActionNameError()), ): resp = await config_client.post( @@ -1671,7 +1671,7 @@ class TestCreateActionRouter: class TestDeleteActionRouter: async def test_204_on_delete(self, config_client: AsyncClient) -> None: with patch( - "app.routers.config.config_file_service.delete_action", + "app.routers.config.action_config_service.delete_action", AsyncMock(return_value=None), ): resp = await config_client.delete("/api/config/actions/custom") @@ -1679,10 +1679,10 @@ class TestDeleteActionRouter: assert resp.status_code == 204 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( - "app.routers.config.config_file_service.delete_action", + "app.routers.config.action_config_service.delete_action", AsyncMock(side_effect=ActionNotFoundError("missing")), ): resp = await config_client.delete("/api/config/actions/missing") @@ -1690,10 +1690,10 @@ class TestDeleteActionRouter: assert resp.status_code == 404 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( - "app.routers.config.config_file_service.delete_action", + "app.routers.config.action_config_service.delete_action", AsyncMock(side_effect=ActionReadonlyError("iptables")), ): resp = await config_client.delete("/api/config/actions/iptables") @@ -1701,10 +1701,10 @@ class TestDeleteActionRouter: assert resp.status_code == 409 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( - "app.routers.config.config_file_service.delete_action", + "app.routers.config.action_config_service.delete_action", AsyncMock(side_effect=ActionNameError()), ): resp = await config_client.delete("/api/config/actions/badname") @@ -1723,7 +1723,7 @@ class TestDeleteActionRouter: class TestAssignActionToJailRouter: async def test_204_on_success(self, config_client: AsyncClient) -> None: 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), ): resp = await config_client.post( @@ -1734,10 +1734,10 @@ class TestAssignActionToJailRouter: assert resp.status_code == 204 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( - "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")), ): resp = await config_client.post( @@ -1748,10 +1748,10 @@ class TestAssignActionToJailRouter: assert resp.status_code == 404 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( - "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")), ): resp = await config_client.post( @@ -1762,10 +1762,10 @@ class TestAssignActionToJailRouter: assert resp.status_code == 404 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( - "app.routers.config.config_file_service.assign_action_to_jail", + "app.routers.config.action_config_service.assign_action_to_jail", AsyncMock(side_effect=JailNameError()), ): resp = await config_client.post( @@ -1776,10 +1776,10 @@ class TestAssignActionToJailRouter: assert resp.status_code == 400 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( - "app.routers.config.config_file_service.assign_action_to_jail", + "app.routers.config.action_config_service.assign_action_to_jail", AsyncMock(side_effect=ActionNameError()), ): resp = await config_client.post( @@ -1791,7 +1791,7 @@ class TestAssignActionToJailRouter: async def test_reload_param_passed(self, config_client: AsyncClient) -> None: 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), ) as mock_assign: resp = await config_client.post( @@ -1814,7 +1814,7 @@ class TestAssignActionToJailRouter: class TestRemoveActionFromJailRouter: async def test_204_on_success(self, config_client: AsyncClient) -> None: 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), ): resp = await config_client.delete( @@ -1824,10 +1824,10 @@ class TestRemoveActionFromJailRouter: assert resp.status_code == 204 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( - "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")), ): resp = await config_client.delete( @@ -1837,10 +1837,10 @@ class TestRemoveActionFromJailRouter: assert resp.status_code == 404 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( - "app.routers.config.config_file_service.remove_action_from_jail", + "app.routers.config.action_config_service.remove_action_from_jail", AsyncMock(side_effect=JailNameError()), ): resp = await config_client.delete( @@ -1850,10 +1850,10 @@ class TestRemoveActionFromJailRouter: assert resp.status_code == 400 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( - "app.routers.config.config_file_service.remove_action_from_jail", + "app.routers.config.action_config_service.remove_action_from_jail", AsyncMock(side_effect=ActionNameError()), ): resp = await config_client.delete( @@ -1864,7 +1864,7 @@ class TestRemoveActionFromJailRouter: async def test_reload_param_passed(self, config_client: AsyncClient) -> None: 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), ) as mock_rm: resp = await config_client.delete( @@ -2060,7 +2060,7 @@ class TestValidateJailEndpoint: jail_name="sshd", valid=True, issues=[] ) 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), ): resp = await config_client.post("/api/config/jails/sshd/validate") @@ -2080,7 +2080,7 @@ class TestValidateJailEndpoint: jail_name="sshd", valid=False, issues=[issue] ) 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), ): resp = await config_client.post("/api/config/jails/sshd/validate") @@ -2093,10 +2093,10 @@ class TestValidateJailEndpoint: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.validate_jail_config", + "app.routers.config.jail_config_service.validate_jail_config", AsyncMock(side_effect=JailNameError("bad name")), ): resp = await config_client.post("/api/config/jails/bad-name/validate") @@ -2188,7 +2188,7 @@ class TestRollbackEndpoint: message="Jail 'sshd' disabled and fail2ban restarted.", ) with patch( - "app.routers.config.config_file_service.rollback_jail", + "app.routers.config.jail_config_service.rollback_jail", AsyncMock(return_value=mock_result), ): resp = await config_client.post("/api/config/jails/sshd/rollback") @@ -2225,7 +2225,7 @@ class TestRollbackEndpoint: message="fail2ban did not come back online.", ) with patch( - "app.routers.config.config_file_service.rollback_jail", + "app.routers.config.jail_config_service.rollback_jail", AsyncMock(return_value=mock_result), ): resp = await config_client.post("/api/config/jails/sshd/rollback") @@ -2238,10 +2238,10 @@ class TestRollbackEndpoint: async def test_400_for_invalid_jail_name(self, config_client: AsyncClient) -> None: """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( - "app.routers.config.config_file_service.rollback_jail", + "app.routers.config.jail_config_service.rollback_jail", AsyncMock(side_effect=JailNameError("bad")), ): resp = await config_client.post("/api/config/jails/bad/rollback") diff --git a/backend/tests/test_routers/test_file_config.py b/backend/tests/test_routers/test_file_config.py index b6a88fc..e8cbed8 100644 --- a/backend/tests/test_routers/test_file_config.py +++ b/backend/tests/test_routers/test_file_config.py @@ -342,7 +342,7 @@ class TestListActionFiles: ) resp_data = ActionListResponse(actions=[mock_action], total=1) with patch( - "app.routers.config.config_file_service.list_actions", + "app.routers.config.action_config_service.list_actions", AsyncMock(return_value=resp_data), ): resp = await file_config_client.get("/api/config/actions") @@ -365,7 +365,7 @@ class TestCreateActionFile: actionban="echo ban ", ) with patch( - "app.routers.config.config_file_service.create_action", + "app.routers.config.action_config_service.create_action", AsyncMock(return_value=created), ): resp = await file_config_client.post(