7a0da7932b
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
199 lines
7.5 KiB
Python
199 lines
7.5 KiB
Python
"""
|
|
Behavioral tests for config/paths module.
|
|
|
|
Tests the real logic: LDR_DATA_DIR env var override, SHA-256 username hashing,
|
|
and structural invariants (all subdirs under data dir).
|
|
"""
|
|
|
|
import re
|
|
|
|
|
|
class TestDataDirectoryEnvOverride:
|
|
"""Tests for LDR_DATA_DIR environment variable handling."""
|
|
|
|
def test_custom_dir_from_env(self, tmp_path, monkeypatch):
|
|
"""LDR_DATA_DIR overrides the default platformdirs path."""
|
|
from local_deep_research.config.paths import get_data_directory
|
|
|
|
custom_dir = str(tmp_path / "custom_data")
|
|
monkeypatch.setenv("LDR_DATA_DIR", custom_dir)
|
|
assert get_data_directory() == tmp_path / "custom_data"
|
|
|
|
def test_empty_env_var_uses_default(self, monkeypatch):
|
|
"""Empty LDR_DATA_DIR is falsy — falls through to platformdirs."""
|
|
from local_deep_research.config.paths import get_data_directory
|
|
|
|
monkeypatch.setenv("LDR_DATA_DIR", "")
|
|
result = get_data_directory()
|
|
# Empty string is falsy, so os.getenv returns "" but `if custom_path:`
|
|
# is False — should use platformdirs default
|
|
assert "local-deep-research" in str(result)
|
|
|
|
def test_unset_env_var_uses_default(self, monkeypatch):
|
|
"""Without LDR_DATA_DIR, uses platformdirs default."""
|
|
from local_deep_research.config.paths import get_data_directory
|
|
|
|
monkeypatch.delenv("LDR_DATA_DIR", raising=False)
|
|
result = get_data_directory()
|
|
assert "local-deep-research" in str(result)
|
|
|
|
def test_custom_dir_propagates_to_all_subdirectories(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
"""LDR_DATA_DIR affects every subdirectory function."""
|
|
from local_deep_research.config.paths import (
|
|
get_cache_directory,
|
|
get_config_directory,
|
|
get_data_directory,
|
|
get_encrypted_database_path,
|
|
get_library_directory,
|
|
get_logs_directory,
|
|
get_models_directory,
|
|
get_research_outputs_directory,
|
|
)
|
|
|
|
custom_dir = str(tmp_path / "custom")
|
|
monkeypatch.setenv("LDR_DATA_DIR", custom_dir)
|
|
|
|
data_dir = get_data_directory()
|
|
subdirs = [
|
|
get_research_outputs_directory(),
|
|
get_cache_directory(),
|
|
get_logs_directory(),
|
|
get_encrypted_database_path(),
|
|
get_library_directory(),
|
|
get_config_directory(),
|
|
get_models_directory(),
|
|
]
|
|
for subdir in subdirs:
|
|
assert subdir.parent == data_dir, f"{subdir} not under {data_dir}"
|
|
|
|
|
|
class TestUserDatabaseFilenameHashing:
|
|
"""Tests for get_user_database_filename SHA-256 hashing logic."""
|
|
|
|
def test_deterministic_same_input(self):
|
|
"""Same username always produces the same filename."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
assert get_user_database_filename(
|
|
"alice"
|
|
) == get_user_database_filename("alice")
|
|
|
|
def test_different_users_different_filenames(self):
|
|
"""Different usernames produce different filenames."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
assert get_user_database_filename(
|
|
"alice"
|
|
) != get_user_database_filename("bob")
|
|
|
|
def test_case_sensitive(self):
|
|
"""'Alice' and 'alice' hash differently (SHA-256 is case-sensitive)."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
assert get_user_database_filename(
|
|
"Alice"
|
|
) != get_user_database_filename("alice")
|
|
|
|
def test_hash_is_16_hex_characters(self):
|
|
"""Hash portion is exactly 16 lowercase hex characters."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
result = get_user_database_filename("testuser")
|
|
hash_part = result.removeprefix("ldr_user_").removesuffix(".db")
|
|
assert len(hash_part) == 16
|
|
assert re.fullmatch(r"[0-9a-f]+", hash_part), (
|
|
f"Non-hex chars in hash: {hash_part}"
|
|
)
|
|
|
|
def test_special_characters_sanitized_by_hashing(self):
|
|
"""Path-traversal chars in username don't leak into filename."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
result = get_user_database_filename("user@example.com/../../etc/passwd")
|
|
hash_part = result.removeprefix("ldr_user_").removesuffix(".db")
|
|
assert re.fullmatch(r"[0-9a-f]+", hash_part)
|
|
|
|
def test_unicode_username_hashed_via_utf8(self):
|
|
"""Unicode usernames are encoded as UTF-8 before hashing."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
result = get_user_database_filename("用户名")
|
|
hash_part = result.removeprefix("ldr_user_").removesuffix(".db")
|
|
assert len(hash_part) == 16
|
|
|
|
def test_empty_username_produces_known_hash_prefix(self):
|
|
"""Empty string is valid input — SHA-256('') starts with e3b0c442."""
|
|
from local_deep_research.config.paths import get_user_database_filename
|
|
|
|
result = get_user_database_filename("")
|
|
hash_part = result.removeprefix("ldr_user_").removesuffix(".db")
|
|
assert hash_part.startswith("e3b0c442")
|
|
|
|
|
|
class TestSubdirectoryStructure:
|
|
"""Tests for structural invariants across all subdirectory functions."""
|
|
|
|
def test_all_subdirs_have_correct_names_and_parent(self):
|
|
"""Each subdirectory function returns the right name under data dir."""
|
|
from local_deep_research.config.paths import (
|
|
get_cache_directory,
|
|
get_config_directory,
|
|
get_data_directory,
|
|
get_encrypted_database_path,
|
|
get_library_directory,
|
|
get_logs_directory,
|
|
get_models_directory,
|
|
get_research_outputs_directory,
|
|
)
|
|
|
|
data_dir = get_data_directory()
|
|
expected = {
|
|
"research_outputs": get_research_outputs_directory,
|
|
"cache": get_cache_directory,
|
|
"logs": get_logs_directory,
|
|
"encrypted_databases": get_encrypted_database_path,
|
|
"library": get_library_directory,
|
|
"config": get_config_directory,
|
|
"models": get_models_directory,
|
|
}
|
|
for name, func in expected.items():
|
|
result = func()
|
|
assert result.parent == data_dir, (
|
|
f"{func.__name__} not under data dir"
|
|
)
|
|
assert result.name == name, f"{func.__name__} has wrong name"
|
|
|
|
def test_no_subdirectory_name_collisions(self):
|
|
"""All 7 subdirectory functions use distinct names."""
|
|
from local_deep_research.config.paths import (
|
|
get_cache_directory,
|
|
get_config_directory,
|
|
get_encrypted_database_path,
|
|
get_library_directory,
|
|
get_logs_directory,
|
|
get_models_directory,
|
|
get_research_outputs_directory,
|
|
)
|
|
|
|
names = [
|
|
get_research_outputs_directory().name,
|
|
get_cache_directory().name,
|
|
get_logs_directory().name,
|
|
get_encrypted_database_path().name,
|
|
get_library_directory().name,
|
|
get_config_directory().name,
|
|
get_models_directory().name,
|
|
]
|
|
assert len(names) == len(set(names)), f"Duplicate names: {names}"
|
|
|
|
def test_backward_compat_get_data_dir_returns_string(self):
|
|
"""get_data_dir() returns string version of get_data_directory()."""
|
|
from local_deep_research.config.paths import (
|
|
get_data_dir,
|
|
get_data_directory,
|
|
)
|
|
|
|
assert get_data_dir() == str(get_data_directory())
|