TASK-015: Add validation for GlobalConfigUpdate.log_target and log_level
- Add LogLevel Literal type: CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG - Add log_target validation to accept special values (STDOUT, STDERR, SYSLOG) or validated file paths within allowed directories - Update GlobalConfigResponse to use LogLevel type - Add field_validator for log_target in both GlobalConfigUpdate and GlobalConfigResponse following the same pattern as AddLogPathRequest - Add @autouse fixture to test_config_service.py to mock get_settings - Update existing tests to use uppercase log level values - Add 12 comprehensive tests for new validation in test_models.py - Update Features.md to document valid log_target and log_level values - Add section to Backend-Development.md documenting Literal types and field_validator patterns with examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -139,3 +139,150 @@ def test_add_log_path_request_custom_allowed_dirs(monkeypatch: pytest.MonkeyPatc
|
||||
with pytest.raises(ValidationError):
|
||||
AddLogPathRequest(log_path="/var/log/app.log", tail=True)
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GlobalConfigUpdate and GlobalConfigResponse
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_global_config_update_valid_log_level(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate accepts valid log levels."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
for level in ["CRITICAL", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"]:
|
||||
update = GlobalConfigUpdate(log_level=level)
|
||||
assert update.log_level == level
|
||||
|
||||
|
||||
def test_global_config_update_invalid_log_level(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate rejects invalid log levels."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
GlobalConfigUpdate(log_level="invalid")
|
||||
error_msg = str(exc_info.value)
|
||||
assert "CRITICAL" in error_msg
|
||||
|
||||
|
||||
def test_global_config_update_log_level_case_sensitive(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate log_level is case-sensitive (must be uppercase)."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
GlobalConfigUpdate(log_level="debug")
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
GlobalConfigUpdate(log_level="Debug")
|
||||
|
||||
|
||||
def test_global_config_update_valid_log_target_special(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate accepts special log target values."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
for target in ["STDOUT", "STDERR", "SYSLOG"]:
|
||||
update = GlobalConfigUpdate(log_target=target)
|
||||
assert update.log_target == target
|
||||
|
||||
|
||||
def test_global_config_update_valid_log_target_path(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate accepts log targets that are valid file paths."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
update = GlobalConfigUpdate(log_target="/var/log/fail2ban.log")
|
||||
assert update.log_target == "/var/log/fail2ban.log"
|
||||
|
||||
update = GlobalConfigUpdate(log_target="/config/log/app.log")
|
||||
assert update.log_target == "/config/log/app.log"
|
||||
|
||||
|
||||
def test_global_config_update_invalid_log_target_path(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate rejects log targets outside allowed directories."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
GlobalConfigUpdate(log_target="/etc/passwd")
|
||||
error_msg = str(exc_info.value)
|
||||
assert "outside allowed directories" in error_msg
|
||||
|
||||
|
||||
def test_global_config_update_log_target_case_insensitive(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate special log targets are accepted in any case."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
update = GlobalConfigUpdate(log_target="stdout")
|
||||
assert update.log_target == "stdout"
|
||||
|
||||
update = GlobalConfigUpdate(log_target="STDERR")
|
||||
assert update.log_target == "STDERR"
|
||||
|
||||
|
||||
def test_global_config_update_none_fields(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigUpdate allows None for optional fields."""
|
||||
from app.models.config import GlobalConfigUpdate
|
||||
|
||||
update = GlobalConfigUpdate()
|
||||
assert update.log_level is None
|
||||
assert update.log_target is None
|
||||
|
||||
|
||||
def test_global_config_response_log_level(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigResponse enforces valid log levels."""
|
||||
from app.models.config import GlobalConfigResponse
|
||||
|
||||
response = GlobalConfigResponse(
|
||||
log_level="INFO",
|
||||
log_target="STDOUT",
|
||||
db_purge_age=86400,
|
||||
db_max_matches=10,
|
||||
)
|
||||
assert response.log_level == "INFO"
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
GlobalConfigResponse(
|
||||
log_level="invalid",
|
||||
log_target="STDOUT",
|
||||
db_purge_age=86400,
|
||||
db_max_matches=10,
|
||||
)
|
||||
|
||||
|
||||
def test_global_config_response_log_target_special(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigResponse accepts special log targets."""
|
||||
from app.models.config import GlobalConfigResponse
|
||||
|
||||
response = GlobalConfigResponse(
|
||||
log_level="INFO",
|
||||
log_target="SYSLOG",
|
||||
db_purge_age=86400,
|
||||
db_max_matches=10,
|
||||
)
|
||||
assert response.log_target == "SYSLOG"
|
||||
|
||||
|
||||
def test_global_config_response_log_target_path(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigResponse accepts validated log target paths."""
|
||||
from app.models.config import GlobalConfigResponse
|
||||
|
||||
response = GlobalConfigResponse(
|
||||
log_level="INFO",
|
||||
log_target="/var/log/fail2ban.log",
|
||||
db_purge_age=86400,
|
||||
db_max_matches=10,
|
||||
)
|
||||
assert response.log_target == "/var/log/fail2ban.log"
|
||||
|
||||
|
||||
def test_global_config_response_log_target_invalid_path(_mock_allowed_dirs: None) -> None:
|
||||
"""GlobalConfigResponse rejects log targets outside allowed directories."""
|
||||
from app.models.config import GlobalConfigResponse
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
GlobalConfigResponse(
|
||||
log_level="INFO",
|
||||
log_target="/root/secret.log",
|
||||
db_purge_age=86400,
|
||||
db_max_matches=10,
|
||||
)
|
||||
error_msg = str(exc_info.value)
|
||||
assert "outside allowed directories" in error_msg
|
||||
|
||||
Reference in New Issue
Block a user