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
187 lines
8.7 KiB
Python
187 lines
8.7 KiB
Python
"""
|
|
Tests targeting specific uncovered lines in path_validator.py.
|
|
|
|
Covers:
|
|
- Line 140: Malformed Windows path on win32
|
|
- Line 153: safe_join returns None for unix absolute path
|
|
- Lines 157-162: Windows absolute path handling (drive letter parsing)
|
|
- Lines 185-186: Windows system directory restrictions
|
|
- Line 274: validate_model_path when validate_safe_path returns None
|
|
- Line 308: validate_data_path when validate_safe_path returns None
|
|
- Lines 375-385: Absolute config path validation (suffix + existence)
|
|
- Line 400: Relative config path when validate_safe_path returns None
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from local_deep_research.security.path_validator import PathValidator
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Line 140: Malformed Windows path on win32
|
|
# The function does `import sys` locally (line 112), so we patch `sys.platform`
|
|
# on the actual sys module.
|
|
# ---------------------------------------------------------------------------
|
|
class TestMalformedWindowsPath:
|
|
"""Line 140: raise ValueError('Malformed Windows path input')"""
|
|
|
|
@patch("sys.platform", "win32")
|
|
def test_malformed_windows_path_slash_colon(self):
|
|
"""'/C:/Windows' on win32 triggers the malformed path guard."""
|
|
with pytest.raises(ValueError, match="Malformed Windows path input"):
|
|
PathValidator.validate_local_filesystem_path("/C:/Windows")
|
|
|
|
@patch("sys.platform", "win32")
|
|
def test_malformed_windows_path_backslash_colon(self):
|
|
"""'\\C:\\Windows' on win32 triggers the malformed path guard."""
|
|
with pytest.raises(ValueError, match="Malformed Windows path input"):
|
|
PathValidator.validate_local_filesystem_path("\\C:\\Windows")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Line 153: safe_join returns None for unix absolute path
|
|
# ---------------------------------------------------------------------------
|
|
class TestSafeJoinReturnsNoneUnix:
|
|
"""Line 153: raise ValueError('Invalid path - failed security validation')"""
|
|
|
|
@patch(
|
|
"local_deep_research.security.path_validator.safe_join",
|
|
return_value=None,
|
|
)
|
|
def test_safe_join_none_for_absolute_path(self, mock_safe_join):
|
|
with pytest.raises(ValueError, match="failed security validation"):
|
|
PathValidator.validate_local_filesystem_path("/home/user/docs")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 157-162: Windows absolute path handling (drive letter parsing)
|
|
# On Linux, a path like 'C:\Users\test' is not absolute (no leading /),
|
|
# so it enters the drive-letter branch at line 155.
|
|
# ---------------------------------------------------------------------------
|
|
class TestWindowsDriveLetterParsing:
|
|
"""Lines 157-162: Windows drive-letter absolute path branch."""
|
|
|
|
@patch("local_deep_research.security.path_validator.safe_join")
|
|
def test_windows_drive_letter_path_success(self, mock_safe_join):
|
|
"""Drive-letter path where safe_join succeeds (lines 157-162)."""
|
|
mock_safe_join.return_value = "/tmp/fake_resolved"
|
|
result = PathValidator.validate_local_filesystem_path("C:\\Users\\test")
|
|
assert result is not None
|
|
|
|
@patch(
|
|
"local_deep_research.security.path_validator.safe_join",
|
|
return_value=None,
|
|
)
|
|
def test_windows_drive_letter_safe_join_none(self, mock_safe_join):
|
|
"""Drive-letter path where safe_join returns None (line 160-161)."""
|
|
with pytest.raises(ValueError, match="failed security validation"):
|
|
PathValidator.validate_local_filesystem_path("C:\\Users\\test")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 185-186: Windows system directory restrictions
|
|
# When sys.platform == "win32" and restricted_dirs is None, extra Windows
|
|
# system dirs are added. We mock safe_join and Path.resolve so that the
|
|
# validated path falls inside one of those restricted dirs.
|
|
# ---------------------------------------------------------------------------
|
|
class TestWindowsSystemDirRestrictions:
|
|
"""Lines 184-193: Windows restricted directories added on win32."""
|
|
|
|
@patch("sys.platform", "win32")
|
|
@patch("local_deep_research.security.path_validator.safe_join")
|
|
def test_windows_restricted_dirs_block_access(self, mock_safe_join):
|
|
"""On win32, C:\\Windows is restricted by default."""
|
|
# The path 'C:\Windows\System32\cmd.exe' has len>2, [1]==':',
|
|
# so it enters the drive-letter branch.
|
|
# It does NOT start with / or \, so the malformed guard (line 135-140)
|
|
# is skipped.
|
|
fake = Path("C:\\Windows\\System32\\cmd.exe")
|
|
mock_safe_join.return_value = str(fake)
|
|
|
|
with patch.object(Path, "resolve", return_value=fake):
|
|
with patch.object(Path, "is_relative_to", return_value=True):
|
|
with pytest.raises(
|
|
ValueError, match="Cannot access system directories"
|
|
):
|
|
PathValidator.validate_local_filesystem_path(
|
|
"C:\\Windows\\System32\\cmd.exe"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Line 274: validate_model_path - validate_safe_path returns None
|
|
# ---------------------------------------------------------------------------
|
|
class TestValidateModelPathNone:
|
|
"""Line 274: raise ValueError('Invalid model path')"""
|
|
|
|
@patch.object(PathValidator, "validate_safe_path", return_value=None)
|
|
def test_model_path_returns_none(self, mock_vsp):
|
|
with pytest.raises(ValueError, match="Invalid model path"):
|
|
PathValidator.validate_model_path(
|
|
"bad_model.bin", model_root="/tmp"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Line 308: validate_data_path - validate_safe_path returns None
|
|
# ---------------------------------------------------------------------------
|
|
class TestValidateDataPathNone:
|
|
"""Line 308: raise ValueError('Invalid data path')"""
|
|
|
|
@patch.object(PathValidator, "validate_safe_path", return_value=None)
|
|
def test_data_path_returns_none(self, mock_vsp):
|
|
with pytest.raises(ValueError, match="Invalid data path"):
|
|
PathValidator.validate_data_path("bad_file.dat", data_root="/tmp")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lines 375-385: Absolute config path validation
|
|
# safe_join("/", config_path) returns None when config_path starts with "/",
|
|
# so on Unix these lines are unreachable through normal flow.
|
|
# We mock safe_join to return a value so we can exercise lines 375-385.
|
|
# ---------------------------------------------------------------------------
|
|
class TestAbsoluteConfigPath:
|
|
"""Lines 375-385: absolute config path suffix and existence checks."""
|
|
|
|
@patch("local_deep_research.security.path_validator.safe_join")
|
|
def test_absolute_config_bad_extension(self, mock_safe_join):
|
|
"""Line 378-379: suffix not in CONFIG_EXTENSIONS."""
|
|
mock_safe_join.return_value = "/tmp/config.exe"
|
|
with pytest.raises(ValueError, match="Invalid config file type"):
|
|
PathValidator.validate_config_path("/tmp/config.exe")
|
|
|
|
@patch("local_deep_research.security.path_validator.safe_join")
|
|
def test_absolute_config_does_not_exist(self, mock_safe_join, tmp_path):
|
|
"""Line 382-383: valid extension but file does not exist."""
|
|
nonexistent = str(tmp_path / "missing.json")
|
|
mock_safe_join.return_value = nonexistent
|
|
with pytest.raises(ValueError, match="Config file not found"):
|
|
PathValidator.validate_config_path(nonexistent)
|
|
|
|
@patch("local_deep_research.security.path_validator.safe_join")
|
|
def test_absolute_config_valid(self, mock_safe_join, tmp_path):
|
|
"""Lines 375-385: happy path - valid extension, file exists."""
|
|
cfg = tmp_path / "settings.yaml"
|
|
cfg.write_text("key: value")
|
|
cfg_str = str(cfg)
|
|
mock_safe_join.return_value = cfg_str
|
|
result = PathValidator.validate_config_path(cfg_str)
|
|
assert result == Path(cfg_str)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Line 400: Relative config path - validate_safe_path returns None
|
|
# ---------------------------------------------------------------------------
|
|
class TestRelativeConfigPathNone:
|
|
"""Line 400: raise ValueError('Invalid config path: ...')"""
|
|
|
|
@patch.object(PathValidator, "validate_safe_path", return_value=None)
|
|
def test_relative_config_path_none(self, mock_vsp):
|
|
with pytest.raises(ValueError, match="Invalid config path"):
|
|
PathValidator.validate_config_path(
|
|
"my_config.yaml", config_root="/tmp"
|
|
)
|