test(backend): add tests for conf-file parser, file-config service and router
- test_conffile_parser.py: unit tests for section/key parsing, comment preservation, and round-trip write correctness - test_file_config_service.py: service-level tests with mock filesystem - test_file_config.py: router integration tests covering GET / PUT endpoints for jails, actions, and filters
This commit is contained in:
@@ -6,6 +6,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.config import ActionConfigUpdate, FilterConfigUpdate, JailFileConfigUpdate
|
||||
from app.models.file_config import ConfFileCreateRequest, ConfFileUpdateRequest
|
||||
from app.services.file_config_service import (
|
||||
ConfigDirError,
|
||||
@@ -18,13 +19,20 @@ from app.services.file_config_service import (
|
||||
_validate_new_name,
|
||||
create_action_file,
|
||||
create_filter_file,
|
||||
create_jail_config_file,
|
||||
get_action_file,
|
||||
get_filter_file,
|
||||
get_jail_config_file,
|
||||
get_parsed_action_file,
|
||||
get_parsed_filter_file,
|
||||
get_parsed_jail_file,
|
||||
list_action_files,
|
||||
list_filter_files,
|
||||
list_jail_config_files,
|
||||
set_jail_config_enabled,
|
||||
update_parsed_action_file,
|
||||
update_parsed_filter_file,
|
||||
update_parsed_jail_file,
|
||||
write_action_file,
|
||||
write_filter_file,
|
||||
)
|
||||
@@ -399,3 +407,183 @@ async def test_create_action_file_creates_file(tmp_path: Path) -> None:
|
||||
|
||||
assert result == "my-action.conf"
|
||||
assert (config_dir / "action.d" / "my-action.conf").is_file()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# create_jail_config_file
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_jail_config_file_creates_file(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
req = ConfFileCreateRequest(name="myjail", content="[myjail]\nenabled = true\n")
|
||||
|
||||
result = await create_jail_config_file(str(config_dir), req)
|
||||
|
||||
assert result == "myjail.conf"
|
||||
assert (config_dir / "jail.d" / "myjail.conf").is_file()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_jail_config_file_conflict(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
(config_dir / "jail.d" / "sshd.conf").write_text("[sshd]\nenabled = true\n")
|
||||
|
||||
req = ConfFileCreateRequest(name="sshd", content="[sshd]\nenabled = true\n")
|
||||
with pytest.raises(ConfigFileExistsError):
|
||||
await create_jail_config_file(str(config_dir), req)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_jail_config_file_invalid_name(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
req = ConfFileCreateRequest(name="../escape", content="[x]\nenabled = true\n")
|
||||
with pytest.raises(ConfigFileNameError):
|
||||
await create_jail_config_file(str(config_dir), req)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_parsed_filter_file / update_parsed_filter_file
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsed_filter_file_returns_structured_model(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
content = "[Definition]\nfailregex = ^<HOST>\nignoreregex = ^.*ignore.*$\n"
|
||||
(config_dir / "filter.d" / "nginx.conf").write_text(content)
|
||||
|
||||
result = await get_parsed_filter_file(str(config_dir), "nginx")
|
||||
|
||||
assert result.name == "nginx"
|
||||
assert result.filename == "nginx.conf"
|
||||
assert result.failregex == ["^<HOST>"]
|
||||
assert result.ignoreregex == ["^.*ignore.*$"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsed_filter_file_not_found(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
with pytest.raises(ConfigFileNotFoundError):
|
||||
await get_parsed_filter_file(str(config_dir), "missing")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_parsed_filter_file_writes_changes(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
(config_dir / "filter.d" / "nginx.conf").write_text(
|
||||
"[Definition]\nfailregex = ^<HOST> old\n"
|
||||
)
|
||||
update = FilterConfigUpdate(failregex=["^<HOST> new"])
|
||||
|
||||
await update_parsed_filter_file(str(config_dir), "nginx", update)
|
||||
|
||||
written = (config_dir / "filter.d" / "nginx.conf").read_text()
|
||||
assert "new" in written
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_parsed_filter_file_not_found(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
update = FilterConfigUpdate(failregex=["^<HOST>"])
|
||||
with pytest.raises(ConfigFileNotFoundError):
|
||||
await update_parsed_filter_file(str(config_dir), "missing", update)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_parsed_action_file / update_parsed_action_file
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsed_action_file_returns_structured_model(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
content = "[Definition]\nactionban = iptables -I INPUT -s <ip> -j DROP\n"
|
||||
(config_dir / "action.d" / "iptables.conf").write_text(content)
|
||||
|
||||
result = await get_parsed_action_file(str(config_dir), "iptables")
|
||||
|
||||
assert result.name == "iptables"
|
||||
assert result.filename == "iptables.conf"
|
||||
assert result.actionban is not None
|
||||
assert "<ip>" in result.actionban
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsed_action_file_not_found(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
with pytest.raises(ConfigFileNotFoundError):
|
||||
await get_parsed_action_file(str(config_dir), "missing")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_parsed_action_file_writes_changes(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
(config_dir / "action.d" / "iptables.conf").write_text(
|
||||
"[Definition]\nactionban = iptables -I INPUT -s <ip> -j DROP\n"
|
||||
)
|
||||
update = ActionConfigUpdate(actionban="nft add element inet f2b-table <ip>")
|
||||
|
||||
await update_parsed_action_file(str(config_dir), "iptables", update)
|
||||
|
||||
written = (config_dir / "action.d" / "iptables.conf").read_text()
|
||||
assert "nft" in written
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_parsed_action_file_not_found(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
update = ActionConfigUpdate(actionban="iptables -I INPUT -s <ip> -j DROP")
|
||||
with pytest.raises(ConfigFileNotFoundError):
|
||||
await update_parsed_action_file(str(config_dir), "missing", update)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# get_parsed_jail_file / update_parsed_jail_file
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsed_jail_file_returns_structured_model(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
content = "[sshd]\nenabled = true\nport = ssh\nmaxretry = 5\n"
|
||||
(config_dir / "jail.d" / "sshd.conf").write_text(content)
|
||||
|
||||
result = await get_parsed_jail_file(str(config_dir), "sshd.conf")
|
||||
|
||||
assert result.filename == "sshd.conf"
|
||||
assert "sshd" in result.jails
|
||||
assert result.jails["sshd"].enabled is True
|
||||
assert result.jails["sshd"].maxretry == 5
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_parsed_jail_file_not_found(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
with pytest.raises(ConfigFileNotFoundError):
|
||||
await get_parsed_jail_file(str(config_dir), "missing.conf")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_parsed_jail_file_writes_changes(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
(config_dir / "jail.d" / "sshd.conf").write_text(
|
||||
"[sshd]\nenabled = true\nport = ssh\n"
|
||||
)
|
||||
from app.models.config import JailSectionConfig
|
||||
|
||||
update = JailFileConfigUpdate(jails={"sshd": JailSectionConfig(enabled=False)})
|
||||
|
||||
await update_parsed_jail_file(str(config_dir), "sshd.conf", update)
|
||||
|
||||
written = (config_dir / "jail.d" / "sshd.conf").read_text()
|
||||
assert "false" in written.lower()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_parsed_jail_file_not_found(tmp_path: Path) -> None:
|
||||
config_dir = _make_config_dir(tmp_path)
|
||||
update = JailFileConfigUpdate(jails={})
|
||||
with pytest.raises(ConfigFileNotFoundError):
|
||||
await update_parsed_jail_file(str(config_dir), "missing.conf", update)
|
||||
|
||||
Reference in New Issue
Block a user