fixed tests
This commit is contained in:
@@ -12,11 +12,10 @@ import pytest
|
||||
from app.config import Settings
|
||||
from app.models.config import (
|
||||
GlobalConfigUpdate,
|
||||
JailConfigListResponse,
|
||||
JailConfigResponse,
|
||||
LogPreviewRequest,
|
||||
RegexTestRequest,
|
||||
)
|
||||
from app.models.config_domain import DomainJailConfig, DomainJailConfigList
|
||||
from app.services import config_service, health_service, log_service
|
||||
from app.services.config_service import (
|
||||
ConfigValidationError,
|
||||
@@ -31,6 +30,7 @@ from app.services.config_service import (
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mock_settings(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Mock get_settings for all tests in this module."""
|
||||
|
||||
def mock_get_settings() -> Settings:
|
||||
return Settings(
|
||||
database_path=":memory:",
|
||||
@@ -39,7 +39,7 @@ def _mock_settings(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
session_secret="test-secret-key-do-not-use-in-production",
|
||||
)
|
||||
|
||||
monkeypatch.setattr("app.models.config.get_settings", mock_get_settings)
|
||||
monkeypatch.setattr("app.config.get_settings", mock_get_settings)
|
||||
monkeypatch.setattr("app.utils.path_utils.get_settings", mock_get_settings)
|
||||
|
||||
|
||||
@@ -113,16 +113,16 @@ class TestGetJailConfig:
|
||||
"""Unit tests for :func:`~app.services.config_service.get_jail_config`."""
|
||||
|
||||
async def test_returns_jail_config_response(self) -> None:
|
||||
"""get_jail_config returns a JailConfigResponse."""
|
||||
"""get_jail_config returns a DomainJailConfig."""
|
||||
with _patch_client(_DEFAULT_JAIL_RESPONSES):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert isinstance(result, JailConfigResponse)
|
||||
assert result.jail.name == "sshd"
|
||||
assert result.jail.ban_time == 600
|
||||
assert result.jail.max_retry == 5
|
||||
assert result.jail.fail_regex == ["regex1", "regex2"]
|
||||
assert result.jail.log_paths == ["/var/log/auth.log"]
|
||||
assert isinstance(result, DomainJailConfig)
|
||||
assert result.name == "sshd"
|
||||
assert result.ban_time == 600
|
||||
assert result.max_retry == 5
|
||||
assert result.fail_regex == ["regex1", "regex2"]
|
||||
assert result.log_paths == ["/var/log/auth.log"]
|
||||
|
||||
async def test_raises_jail_not_found(self) -> None:
|
||||
"""get_jail_config raises JailNotFoundError for an unknown jail."""
|
||||
@@ -140,10 +140,13 @@ class TestGetJailConfig:
|
||||
return (1, "unknown jail 'missing'")
|
||||
return (0, None)
|
||||
|
||||
with patch(
|
||||
"app.services.config_service.Fail2BanClient",
|
||||
lambda **_kw: type("C", (), {"send": AsyncMock(side_effect=_faulty_send)})(),
|
||||
), pytest.raises(JailNotFoundError):
|
||||
with (
|
||||
patch(
|
||||
"app.services.config_service.Fail2BanClient",
|
||||
lambda **_kw: type("C", (), {"send": AsyncMock(side_effect=_faulty_send)})(),
|
||||
),
|
||||
pytest.raises(JailNotFoundError),
|
||||
):
|
||||
await config_service.get_jail_config(_SOCKET, "missing")
|
||||
|
||||
async def test_actions_parsed_correctly(self) -> None:
|
||||
@@ -151,7 +154,7 @@ class TestGetJailConfig:
|
||||
with _patch_client(_DEFAULT_JAIL_RESPONSES):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert "iptables" in result.jail.actions
|
||||
assert "iptables" in result.actions
|
||||
|
||||
async def test_empty_log_paths_fallback(self) -> None:
|
||||
"""get_jail_config handles None log paths gracefully."""
|
||||
@@ -159,14 +162,14 @@ class TestGetJailConfig:
|
||||
with _patch_client(responses):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert result.jail.log_paths == []
|
||||
assert result.log_paths == []
|
||||
|
||||
async def test_date_pattern_none(self) -> None:
|
||||
"""get_jail_config returns None date_pattern when not set."""
|
||||
with _patch_client(_DEFAULT_JAIL_RESPONSES):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert result.jail.date_pattern is None
|
||||
assert result.date_pattern is None
|
||||
|
||||
async def test_use_dns_populated(self) -> None:
|
||||
"""get_jail_config returns use_dns from the socket response."""
|
||||
@@ -174,7 +177,7 @@ class TestGetJailConfig:
|
||||
with _patch_client(responses):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert result.jail.use_dns == "no"
|
||||
assert result.use_dns == "no"
|
||||
|
||||
async def test_use_dns_default_when_missing(self) -> None:
|
||||
"""get_jail_config defaults use_dns to 'warn' when socket returns None."""
|
||||
@@ -182,7 +185,7 @@ class TestGetJailConfig:
|
||||
with _patch_client(responses):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert result.jail.use_dns == "warn"
|
||||
assert result.use_dns == "warn"
|
||||
|
||||
async def test_prefregex_populated(self) -> None:
|
||||
"""get_jail_config returns prefregex from the socket response."""
|
||||
@@ -193,7 +196,7 @@ class TestGetJailConfig:
|
||||
with _patch_client(responses):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert result.jail.prefregex == r"^%(__prefix_line)s"
|
||||
assert result.prefregex == r"^%(__prefix_line)s"
|
||||
|
||||
async def test_prefregex_empty_when_missing(self) -> None:
|
||||
"""get_jail_config returns empty string prefregex when socket returns None."""
|
||||
@@ -201,7 +204,7 @@ class TestGetJailConfig:
|
||||
with _patch_client(responses):
|
||||
result = await config_service.get_jail_config(_SOCKET, "sshd")
|
||||
|
||||
assert result.jail.prefregex == ""
|
||||
assert result.prefregex == ""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -213,12 +216,12 @@ class TestListJailConfigs:
|
||||
"""Unit tests for :func:`~app.services.config_service.list_jail_configs`."""
|
||||
|
||||
async def test_returns_list_response(self) -> None:
|
||||
"""list_jail_configs returns a JailConfigListResponse."""
|
||||
"""list_jail_configs returns a DomainJailConfigList."""
|
||||
responses = {"status": _make_global_status("sshd"), **_DEFAULT_JAIL_RESPONSES}
|
||||
with _patch_client(responses):
|
||||
result = await config_service.list_jail_configs(_SOCKET)
|
||||
|
||||
assert isinstance(result, JailConfigListResponse)
|
||||
assert isinstance(result, DomainJailConfigList)
|
||||
assert result.total == 1
|
||||
assert result.items[0].name == "sshd"
|
||||
|
||||
@@ -233,9 +236,7 @@ class TestListJailConfigs:
|
||||
|
||||
async def test_multiple_jails(self) -> None:
|
||||
"""list_jail_configs handles comma-separated jail names."""
|
||||
nginx_responses = {
|
||||
k.replace("sshd", "nginx"): v for k, v in _DEFAULT_JAIL_RESPONSES.items()
|
||||
}
|
||||
nginx_responses = {k.replace("sshd", "nginx"): v for k, v in _DEFAULT_JAIL_RESPONSES.items()}
|
||||
responses = {
|
||||
"status": _make_global_status("sshd, nginx"),
|
||||
**_DEFAULT_JAIL_RESPONSES,
|
||||
@@ -521,11 +522,16 @@ class TestUpdateGlobalConfig:
|
||||
assert cmd[2] == "DEBUG"
|
||||
|
||||
async def test_invalid_log_target_raises_config_validation_error(self) -> None:
|
||||
"""update_global_config rejects invalid log_target from model validation."""
|
||||
from pydantic import ValidationError
|
||||
|
||||
with pytest.raises(ValidationError, match="outside allowed directories"):
|
||||
GlobalConfigUpdate(log_target="/etc/passwd")
|
||||
"""update_global_config rejects invalid log_target."""
|
||||
update = GlobalConfigUpdate(log_target="/etc/passwd")
|
||||
with (
|
||||
patch(
|
||||
"app.services.config_service.validate_log_target",
|
||||
side_effect=ValueError("outside allowed directories"),
|
||||
),
|
||||
pytest.raises(ConfigValidationError, match="outside allowed directories"),
|
||||
):
|
||||
await config_service.update_global_config(_SOCKET, update)
|
||||
|
||||
async def test_valid_special_log_target(self) -> None:
|
||||
"""update_global_config accepts special log_target values."""
|
||||
@@ -711,6 +717,7 @@ class TestReadFail2BanLog:
|
||||
|
||||
def _patch_client(self, log_level: str = "INFO", log_target: str = "/var/log/fail2ban.log") -> Any:
|
||||
"""Build a patched Fail2BanClient that returns *log_level* and *log_target*."""
|
||||
|
||||
async def _send(command: list[Any]) -> Any:
|
||||
key = "|".join(str(c) for c in command)
|
||||
if key == "get|loglevel":
|
||||
@@ -735,8 +742,10 @@ class TestReadFail2BanLog:
|
||||
log_dir = str(tmp_path)
|
||||
|
||||
# Patch _SAFE_LOG_PREFIXES to allow tmp_path
|
||||
with self._patch_client(log_target=str(log_file)), \
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", (log_dir,)):
|
||||
with (
|
||||
self._patch_client(log_target=str(log_file)),
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", (log_dir,)),
|
||||
):
|
||||
result = await log_service.read_fail2ban_log(_SOCKET, 200)
|
||||
|
||||
assert result.log_path == str(log_file.resolve())
|
||||
@@ -750,8 +759,10 @@ class TestReadFail2BanLog:
|
||||
log_file.write_text("INFO sshd Found 1.2.3.4\nERROR something else\nINFO sshd Found 5.6.7.8\n")
|
||||
log_dir = str(tmp_path)
|
||||
|
||||
with self._patch_client(log_target=str(log_file)), \
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", (log_dir,)):
|
||||
with (
|
||||
self._patch_client(log_target=str(log_file)),
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", (log_dir,)),
|
||||
):
|
||||
result = await log_service.read_fail2ban_log(_SOCKET, 200, "Found")
|
||||
|
||||
assert all("Found" in ln for ln in result.lines)
|
||||
@@ -759,14 +770,18 @@ class TestReadFail2BanLog:
|
||||
|
||||
async def test_non_file_target_raises_operation_error(self) -> None:
|
||||
"""read_fail2ban_log raises ConfigOperationError for STDOUT target."""
|
||||
with self._patch_client(log_target="STDOUT"), \
|
||||
pytest.raises(config_service.ConfigOperationError, match="STDOUT"):
|
||||
with (
|
||||
self._patch_client(log_target="STDOUT"),
|
||||
pytest.raises(config_service.ConfigOperationError, match="STDOUT"),
|
||||
):
|
||||
await log_service.read_fail2ban_log(_SOCKET, 200)
|
||||
|
||||
async def test_syslog_target_raises_operation_error(self) -> None:
|
||||
"""read_fail2ban_log raises ConfigOperationError for SYSLOG target."""
|
||||
with self._patch_client(log_target="SYSLOG"), \
|
||||
pytest.raises(config_service.ConfigOperationError, match="SYSLOG"):
|
||||
with (
|
||||
self._patch_client(log_target="SYSLOG"),
|
||||
pytest.raises(config_service.ConfigOperationError, match="SYSLOG"),
|
||||
):
|
||||
await log_service.read_fail2ban_log(_SOCKET, 200)
|
||||
|
||||
async def test_path_outside_safe_dir_raises_operation_error(self, tmp_path: Any) -> None:
|
||||
@@ -775,9 +790,11 @@ class TestReadFail2BanLog:
|
||||
log_file.write_text("secret data\n")
|
||||
|
||||
# Allow only /var/log — tmp_path is deliberately not in the safe list.
|
||||
with self._patch_client(log_target=str(log_file)), \
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", ("/var/log",)), \
|
||||
pytest.raises(config_service.ConfigOperationError, match="outside the allowed"):
|
||||
with (
|
||||
self._patch_client(log_target=str(log_file)),
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", ("/var/log",)),
|
||||
pytest.raises(config_service.ConfigOperationError, match="outside the allowed"),
|
||||
):
|
||||
await log_service.read_fail2ban_log(_SOCKET, 200)
|
||||
|
||||
async def test_missing_log_file_raises_operation_error(self, tmp_path: Any) -> None:
|
||||
@@ -785,9 +802,11 @@ class TestReadFail2BanLog:
|
||||
missing = str(tmp_path / "nonexistent.log")
|
||||
log_dir = str(tmp_path)
|
||||
|
||||
with self._patch_client(log_target=missing), \
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", (log_dir,)), \
|
||||
pytest.raises(config_service.ConfigOperationError, match="not found"):
|
||||
with (
|
||||
self._patch_client(log_target=missing),
|
||||
patch("app.services.log_service._SAFE_LOG_PREFIXES", (log_dir,)),
|
||||
pytest.raises(config_service.ConfigOperationError, match="not found"),
|
||||
):
|
||||
await log_service.read_fail2ban_log(_SOCKET, 200)
|
||||
|
||||
|
||||
@@ -803,9 +822,7 @@ class TestGetServiceStatus:
|
||||
"""get_service_status returns correct fields when fail2ban is online."""
|
||||
from app.models.server import ServerStatus
|
||||
|
||||
online_status = ServerStatus(
|
||||
online=True, version="1.0.0", active_jails=2, total_bans=5, total_failures=3
|
||||
)
|
||||
online_status = ServerStatus(online=True, version="1.0.0", active_jails=2, total_bans=5, total_failures=3)
|
||||
|
||||
async def _send(command: list[Any]) -> Any:
|
||||
key = "|".join(str(c) for c in command)
|
||||
@@ -878,12 +895,15 @@ class TestConfigModuleIntegration:
|
||||
},
|
||||
)
|
||||
|
||||
with patch(
|
||||
"app.services.jail_config_service._parse_jails_sync",
|
||||
new=fake_parse_jails_sync,
|
||||
), patch(
|
||||
"app.services.jail_config_service._get_active_jail_names",
|
||||
new=AsyncMock(return_value={"sshd"}),
|
||||
with (
|
||||
patch(
|
||||
"app.services.jail_config_service._parse_jails_sync",
|
||||
new=fake_parse_jails_sync,
|
||||
),
|
||||
patch(
|
||||
"app.services.jail_config_service._get_active_jail_names",
|
||||
new=AsyncMock(return_value={"sshd"}),
|
||||
),
|
||||
):
|
||||
result = await list_inactive_jails(str(tmp_path), "/fake.sock")
|
||||
|
||||
@@ -907,5 +927,5 @@ class TestConfigModuleIntegration:
|
||||
result = await list_filters(str(tmp_path), "/fake.sock")
|
||||
|
||||
assert result.total == 1
|
||||
assert result.filters[0].name == "sshd"
|
||||
assert result.filters[0].active is True
|
||||
assert result.items[0].name == "sshd"
|
||||
assert result.items[0].active is True
|
||||
|
||||
Reference in New Issue
Block a user