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