Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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())