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:
@@ -12,6 +12,12 @@ from httpx import ASGITransport, AsyncClient
|
||||
from app.config import Settings
|
||||
from app.db import init_db
|
||||
from app.main import create_app
|
||||
from app.models.config import (
|
||||
ActionConfig,
|
||||
FilterConfig,
|
||||
JailFileConfig,
|
||||
JailSectionConfig,
|
||||
)
|
||||
from app.models.file_config import (
|
||||
ConfFileContent,
|
||||
ConfFileEntry,
|
||||
@@ -377,3 +383,331 @@ class TestCreateActionFile:
|
||||
|
||||
assert resp.status_code == 201
|
||||
assert resp.json()["filename"] == "myaction.conf"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# POST /api/config/jail-files
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestCreateJailConfigFile:
|
||||
async def test_201_creates_file(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.create_jail_config_file",
|
||||
AsyncMock(return_value="myjail.conf"),
|
||||
):
|
||||
resp = await file_config_client.post(
|
||||
"/api/config/jail-files",
|
||||
json={"name": "myjail", "content": "[myjail]\nenabled = true\n"},
|
||||
)
|
||||
|
||||
assert resp.status_code == 201
|
||||
assert resp.json()["filename"] == "myjail.conf"
|
||||
|
||||
async def test_409_conflict(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.create_jail_config_file",
|
||||
AsyncMock(side_effect=ConfigFileExistsError("myjail.conf")),
|
||||
):
|
||||
resp = await file_config_client.post(
|
||||
"/api/config/jail-files",
|
||||
json={"name": "myjail", "content": "[myjail]\nenabled = true\n"},
|
||||
)
|
||||
|
||||
assert resp.status_code == 409
|
||||
|
||||
async def test_400_invalid_name(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.create_jail_config_file",
|
||||
AsyncMock(side_effect=ConfigFileNameError("bad/../name")),
|
||||
):
|
||||
resp = await file_config_client.post(
|
||||
"/api/config/jail-files",
|
||||
json={"name": "../escape", "content": "[Definition]\n"},
|
||||
)
|
||||
|
||||
assert resp.status_code == 400
|
||||
|
||||
async def test_503_on_config_dir_error(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.create_jail_config_file",
|
||||
AsyncMock(side_effect=ConfigDirError("no dir")),
|
||||
):
|
||||
resp = await file_config_client.post(
|
||||
"/api/config/jail-files",
|
||||
json={"name": "anyjail", "content": "[anyjail]\nenabled = false\n"},
|
||||
)
|
||||
|
||||
assert resp.status_code == 503
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/config/filters/{name}/parsed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetParsedFilter:
|
||||
async def test_200_returns_parsed_config(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
cfg = FilterConfig(name="nginx", filename="nginx.conf")
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_filter_file",
|
||||
AsyncMock(return_value=cfg),
|
||||
):
|
||||
resp = await file_config_client.get("/api/config/filters/nginx/parsed")
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["name"] == "nginx"
|
||||
assert data["filename"] == "nginx.conf"
|
||||
|
||||
async def test_404_not_found(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_filter_file",
|
||||
AsyncMock(side_effect=ConfigFileNotFoundError("missing")),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/filters/missing/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
async def test_503_on_config_dir_error(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_filter_file",
|
||||
AsyncMock(side_effect=ConfigDirError("no dir")),
|
||||
):
|
||||
resp = await file_config_client.get("/api/config/filters/nginx/parsed")
|
||||
|
||||
assert resp.status_code == 503
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PUT /api/config/filters/{name}/parsed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUpdateParsedFilter:
|
||||
async def test_204_on_success(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_filter_file",
|
||||
AsyncMock(return_value=None),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/filters/nginx/parsed",
|
||||
json={"failregex": ["^<HOST> "]},
|
||||
)
|
||||
|
||||
assert resp.status_code == 204
|
||||
|
||||
async def test_404_not_found(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_filter_file",
|
||||
AsyncMock(side_effect=ConfigFileNotFoundError("missing")),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/filters/missing/parsed",
|
||||
json={"failregex": []},
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
async def test_400_write_error(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_filter_file",
|
||||
AsyncMock(side_effect=ConfigFileWriteError("disk full")),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/filters/nginx/parsed",
|
||||
json={"failregex": ["^<HOST> "]},
|
||||
)
|
||||
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/config/actions/{name}/parsed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetParsedAction:
|
||||
async def test_200_returns_parsed_config(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
cfg = ActionConfig(name="iptables", filename="iptables.conf")
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_action_file",
|
||||
AsyncMock(return_value=cfg),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/actions/iptables/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["name"] == "iptables"
|
||||
assert data["filename"] == "iptables.conf"
|
||||
|
||||
async def test_404_not_found(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_action_file",
|
||||
AsyncMock(side_effect=ConfigFileNotFoundError("missing")),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/actions/missing/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
async def test_503_on_config_dir_error(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_action_file",
|
||||
AsyncMock(side_effect=ConfigDirError("no dir")),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/actions/iptables/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 503
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PUT /api/config/actions/{name}/parsed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUpdateParsedAction:
|
||||
async def test_204_on_success(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_action_file",
|
||||
AsyncMock(return_value=None),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/actions/iptables/parsed",
|
||||
json={"actionban": "iptables -I INPUT -s <ip> -j DROP"},
|
||||
)
|
||||
|
||||
assert resp.status_code == 204
|
||||
|
||||
async def test_404_not_found(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_action_file",
|
||||
AsyncMock(side_effect=ConfigFileNotFoundError("missing")),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/actions/missing/parsed",
|
||||
json={"actionban": ""},
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
async def test_400_write_error(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_action_file",
|
||||
AsyncMock(side_effect=ConfigFileWriteError("disk full")),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/actions/iptables/parsed",
|
||||
json={"actionban": "iptables -I INPUT -s <ip> -j DROP"},
|
||||
)
|
||||
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /api/config/jail-files/{filename}/parsed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetParsedJailFile:
|
||||
async def test_200_returns_parsed_config(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
section = JailSectionConfig(enabled=True, port="ssh")
|
||||
cfg = JailFileConfig(filename="sshd.conf", jails={"sshd": section})
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_jail_file",
|
||||
AsyncMock(return_value=cfg),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/jail-files/sshd.conf/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
assert data["filename"] == "sshd.conf"
|
||||
assert "sshd" in data["jails"]
|
||||
|
||||
async def test_404_not_found(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_jail_file",
|
||||
AsyncMock(side_effect=ConfigFileNotFoundError("missing.conf")),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/jail-files/missing.conf/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
async def test_503_on_config_dir_error(
|
||||
self, file_config_client: AsyncClient
|
||||
) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.get_parsed_jail_file",
|
||||
AsyncMock(side_effect=ConfigDirError("no dir")),
|
||||
):
|
||||
resp = await file_config_client.get(
|
||||
"/api/config/jail-files/sshd.conf/parsed"
|
||||
)
|
||||
|
||||
assert resp.status_code == 503
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PUT /api/config/jail-files/{filename}/parsed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestUpdateParsedJailFile:
|
||||
async def test_204_on_success(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_jail_file",
|
||||
AsyncMock(return_value=None),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/jail-files/sshd.conf/parsed",
|
||||
json={"jails": {"sshd": {"enabled": False}}},
|
||||
)
|
||||
|
||||
assert resp.status_code == 204
|
||||
|
||||
async def test_404_not_found(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_jail_file",
|
||||
AsyncMock(side_effect=ConfigFileNotFoundError("missing.conf")),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/jail-files/missing.conf/parsed",
|
||||
json={"jails": {}},
|
||||
)
|
||||
|
||||
assert resp.status_code == 404
|
||||
|
||||
async def test_400_write_error(self, file_config_client: AsyncClient) -> None:
|
||||
with patch(
|
||||
"app.routers.file_config.file_config_service.update_parsed_jail_file",
|
||||
AsyncMock(side_effect=ConfigFileWriteError("disk full")),
|
||||
):
|
||||
resp = await file_config_client.put(
|
||||
"/api/config/jail-files/sshd.conf/parsed",
|
||||
json={"jails": {"sshd": {"enabled": True}}},
|
||||
)
|
||||
|
||||
assert resp.status_code == 400
|
||||
|
||||
Reference in New Issue
Block a user