Files
learningcircuit--local-deep…/tests/security/test_path_validator_deep_coverage.py
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

232 lines
8.6 KiB
Python

"""
Tests for uncovered code paths in PathValidator.
Targets:
- validate_config_path: absolute path branch, restricted prefixes, null bytes
- sanitize_for_filesystem_ops: non-absolute path, safe_join failure
- validate_model_path: non-existent file, not-a-file, model_root creation
- validate_local_filesystem_path: control characters, tilde expansion, relative path
"""
import os
from pathlib import Path
import pytest
from local_deep_research.security.path_validator import PathValidator
class TestValidateConfigPathAbsolute:
"""Tests for validate_config_path with absolute paths."""
def test_absolute_path_rejected_by_safe_join(self, tmp_path):
"""Absolute Unix paths are rejected by safe_join in validate_config_path.
werkzeug safe_join("/", "/absolute/path") returns None because the
second argument starts with "/" which is treated as escaping the base.
This covers the `if safe_path is None: raise ValueError` branch.
"""
config_file = tmp_path / "test.json"
config_file.write_text("{}")
with pytest.raises(ValueError, match="Invalid"):
PathValidator.validate_config_path(str(config_file))
def test_config_path_null_bytes(self):
"""Config path with null bytes raises ValueError."""
with pytest.raises(ValueError, match="Null bytes"):
PathValidator.validate_config_path("/some/path\x00.json")
def test_config_path_empty_string(self):
"""Empty config path raises ValueError."""
with pytest.raises(ValueError, match="Invalid config path input"):
PathValidator.validate_config_path("")
def test_config_path_non_string(self):
"""Non-string config path raises ValueError."""
with pytest.raises(ValueError, match="Invalid config path input"):
PathValidator.validate_config_path(None) # type: ignore
def test_config_path_traversal_attempt(self):
"""Config path with .. raises ValueError."""
with pytest.raises(ValueError, match="traversal"):
PathValidator.validate_config_path("../../../etc/passwd")
class TestConfigPathRestrictedPrefixes:
"""Tests for restricted system directory prefixes in config path."""
@pytest.mark.parametrize(
"path",
[
"etc/passwd",
"/etc/passwd",
"proc/self/status",
"sys/kernel",
"dev/null",
],
)
def test_restricted_prefixes_blocked(self, path):
"""Config paths targeting system directories are blocked."""
with pytest.raises(ValueError, match="restricted system directory"):
PathValidator.validate_config_path(path)
class TestConfigPathRelative:
"""Tests for validate_config_path with relative paths."""
def test_relative_config_path_with_valid_extension(self, tmp_path):
"""Relative config path resolves within config_root."""
config_file = tmp_path / "settings.yaml"
config_file.write_text("key: value")
result = PathValidator.validate_config_path(
"settings.yaml", config_root=str(tmp_path)
)
assert result == config_file
def test_relative_config_path_invalid_extension(self, tmp_path):
"""Relative config path with wrong extension raises ValueError."""
with pytest.raises(ValueError, match="Invalid file type"):
PathValidator.validate_config_path(
"script.sh", config_root=str(tmp_path)
)
class TestSanitizeForFilesystemOps:
"""Tests for sanitize_for_filesystem_ops."""
def test_valid_absolute_path(self, tmp_path):
"""Valid absolute path is returned unchanged."""
result = PathValidator.sanitize_for_filesystem_ops(tmp_path)
assert result == tmp_path
def test_non_absolute_path_raises(self):
"""Relative path raises ValueError."""
with pytest.raises(ValueError, match="Path must be absolute"):
PathValidator.sanitize_for_filesystem_ops(Path("relative/path"))
class TestValidateModelPath:
"""Tests for validate_model_path edge cases."""
def test_model_path_nonexistent_file(self, tmp_path):
"""Model path to nonexistent file raises ValueError."""
with pytest.raises(ValueError, match="Model file not found"):
PathValidator.validate_model_path(
"nonexistent.bin", model_root=str(tmp_path)
)
def test_model_path_is_directory(self, tmp_path):
"""Model path pointing to directory raises ValueError."""
subdir = tmp_path / "model_dir"
subdir.mkdir()
with pytest.raises(ValueError, match="not a file"):
PathValidator.validate_model_path(
"model_dir", model_root=str(tmp_path)
)
def test_model_path_valid_file(self, tmp_path):
"""Valid model file is returned."""
model_file = tmp_path / "model.bin"
model_file.write_bytes(b"fake model data")
result = PathValidator.validate_model_path(
"model.bin", model_root=str(tmp_path)
)
assert result == model_file
def test_model_path_creates_root_directory(self, tmp_path):
"""Model root directory is created if it doesn't exist."""
new_root = tmp_path / "new_model_root"
# The directory doesn't exist yet but will be created
# Then the file won't exist, so it should raise
with pytest.raises(ValueError, match="Model file not found"):
PathValidator.validate_model_path(
"model.bin", model_root=str(new_root)
)
# But the directory should have been created
assert new_root.exists()
class TestValidateLocalFilesystemPath:
"""Tests for validate_local_filesystem_path edge cases."""
def test_control_characters_rejected(self):
"""Paths with control characters are rejected."""
with pytest.raises(ValueError, match="Control characters"):
PathValidator.validate_local_filesystem_path("/tmp/test\x01file")
def test_tilde_expansion(self):
"""Tilde is expanded to home directory."""
# Pass empty restricted_dirs to avoid /root being blocked in CI
result = PathValidator.validate_local_filesystem_path(
"~", restricted_dirs=[]
)
assert result == Path.home().resolve()
def test_tilde_with_subpath(self):
"""Tilde with subpath is expanded correctly."""
# Pass empty restricted_dirs to avoid /root being blocked in CI
result = PathValidator.validate_local_filesystem_path(
"~/Documents", restricted_dirs=[]
)
expected = (Path.home() / "Documents").resolve()
assert result == expected
def test_relative_path_resolved_to_cwd(self):
"""Relative path is resolved relative to CWD."""
result = PathValidator.validate_local_filesystem_path("some_dir")
expected = (Path(os.getcwd()) / "some_dir").resolve()
assert result == expected
def test_restricted_directories_blocked(self):
"""System directories like /etc are blocked."""
with pytest.raises(
ValueError, match="Cannot access system directories"
):
PathValidator.validate_local_filesystem_path("/etc/passwd")
def test_custom_restricted_dirs(self, tmp_path):
"""Custom restricted directories are respected."""
restricted = tmp_path / "restricted"
restricted.mkdir()
with pytest.raises(
ValueError, match="Cannot access system directories"
):
PathValidator.validate_local_filesystem_path(
str(restricted / "file.txt"),
restricted_dirs=[restricted],
)
def test_path_traversal_blocked(self):
"""Path traversal with .. is blocked."""
with pytest.raises(ValueError, match="Path traversal"):
PathValidator.validate_local_filesystem_path("/tmp/../etc/passwd")
def test_null_bytes_blocked(self):
"""Null bytes in path are blocked."""
with pytest.raises(ValueError, match="Null bytes"):
PathValidator.validate_local_filesystem_path("/tmp/file\x00.txt")
class TestValidateDataPath:
"""Tests for validate_data_path."""
def test_valid_data_path(self, tmp_path):
"""Valid relative data path is accepted."""
result = PathValidator.validate_data_path(
"subdir/file.txt", str(tmp_path)
)
assert result.parent.name == "subdir"
assert result.name == "file.txt"
def test_data_path_traversal_blocked(self, tmp_path):
"""Data path traversal is blocked."""
with pytest.raises(ValueError):
PathValidator.validate_data_path("../../etc/passwd", str(tmp_path))