"""Tests for paths module.""" import hashlib from pathlib import Path from unittest.mock import patch import pytest from local_deep_research.config.paths import ( get_data_directory, get_research_outputs_directory, get_cache_directory, get_logs_directory, get_encrypted_database_path, get_user_database_filename, get_data_dir, ) class TestGetDataDirectory: """Tests for get_data_directory function.""" def test_uses_env_var_override(self, tmp_path, monkeypatch): """Should use LDR_DATA_DIR if set.""" monkeypatch.setenv("LDR_DATA_DIR", str(tmp_path)) result = get_data_directory() assert result == tmp_path def test_uses_platformdirs_when_no_env_var(self, monkeypatch): """Should use platformdirs when no env var set.""" monkeypatch.delenv("LDR_DATA_DIR", raising=False) with patch("platformdirs.user_data_dir", return_value="/mock/path"): result = get_data_directory() assert result == Path("/mock/path") def test_returns_path_object(self, mock_env_data_dir): """Should return Path object.""" result = get_data_directory() assert isinstance(result, Path) def test_apostrophe_in_path_is_allowed(self, tmp_path, monkeypatch): """Regression for #3090: a legitimate POSIX path containing an apostrophe (e.g. /home/O'Brien/ldr) must NOT crash startup. The over-broad quote rejection was removed; the actual ATTACH-DATABASE sink validates the interpolated path separately.""" data_dir = tmp_path / "O'Brien" / "ldr" monkeypatch.setenv("LDR_DATA_DIR", str(data_dir)) result = get_data_directory() assert result == data_dir.resolve() def test_relative_path_rejected(self, monkeypatch): """A relative LDR_DATA_DIR is rejected (must be absolute).""" monkeypatch.setenv("LDR_DATA_DIR", "relative/dir") with pytest.raises(ValueError, match="absolute"): get_data_directory() def test_control_characters_rejected(self, tmp_path, monkeypatch): """A newline in LDR_DATA_DIR is still rejected (could break a SQL/ ATTACH statement), and the offending value is not echoed.""" monkeypatch.setenv("LDR_DATA_DIR", str(tmp_path) + "\nDROP") with pytest.raises(ValueError, match="control character") as exc_info: get_data_directory() assert "DROP" not in str(exc_info.value) class TestGetResearchOutputsDirectory: """Tests for get_research_outputs_directory function.""" def test_returns_subdirectory_of_data_dir(self, mock_env_data_dir): """Should return research_outputs subdirectory.""" result = get_research_outputs_directory() assert result.name == "research_outputs" assert result.parent == mock_env_data_dir def test_creates_directory(self, mock_env_data_dir): """Should create directory if it doesn't exist.""" result = get_research_outputs_directory() assert result.exists() assert result.is_dir() class TestGetCacheDirectory: """Tests for get_cache_directory function.""" def test_returns_cache_subdirectory(self, mock_env_data_dir): """Should return cache subdirectory.""" result = get_cache_directory() assert result.name == "cache" assert result.parent == mock_env_data_dir def test_creates_directory(self, mock_env_data_dir): """Should create directory if it doesn't exist.""" result = get_cache_directory() assert result.exists() class TestGetLogsDirectory: """Tests for get_logs_directory function.""" def test_returns_logs_subdirectory(self, mock_env_data_dir): """Should return logs subdirectory.""" result = get_logs_directory() assert result.name == "logs" assert result.parent == mock_env_data_dir def test_creates_directory(self, mock_env_data_dir): """Should create directory if it doesn't exist.""" result = get_logs_directory() assert result.exists() class TestGetEncryptedDatabasePath: """Tests for get_encrypted_database_path function.""" def test_returns_encrypted_databases_subdirectory(self, mock_env_data_dir): """Should return encrypted_databases subdirectory.""" result = get_encrypted_database_path() assert result.name == "encrypted_databases" assert result.parent == mock_env_data_dir def test_creates_directory(self, mock_env_data_dir): """Should create directory if it doesn't exist.""" result = get_encrypted_database_path() assert result.exists() class TestGetUserDatabaseFilename: """Tests for get_user_database_filename function.""" def test_generates_hashed_filename(self): """Should generate filename with username hash.""" result = get_user_database_filename("testuser") expected_hash = hashlib.sha256("testuser".encode()).hexdigest()[:16] assert result == f"ldr_user_{expected_hash}.db" def test_consistent_for_same_username(self): """Should return same filename for same username.""" result1 = get_user_database_filename("testuser") result2 = get_user_database_filename("testuser") assert result1 == result2 def test_different_for_different_usernames(self): """Should return different filenames for different usernames.""" result1 = get_user_database_filename("user1") result2 = get_user_database_filename("user2") assert result1 != result2 def test_handles_special_characters(self): """Should handle special characters in username.""" result = get_user_database_filename("user@domain.com") assert result.startswith("ldr_user_") assert result.endswith(".db") class TestGetDataDir: """Tests for get_data_dir backward compat function.""" def test_returns_string(self, mock_env_data_dir): """Should return string instead of Path.""" result = get_data_dir() assert isinstance(result, str) assert result == str(mock_env_data_dir)