"""Tests for research_library/utils/__init__.py — utility functions.""" import hashlib from unittest.mock import MagicMock, patch # --------------------------------------------------------------------------- # get_url_hash # --------------------------------------------------------------------------- class TestGetUrlHash: """Tests for get_url_hash().""" def test_returns_sha256_hex(self): from local_deep_research.research_library.utils import get_url_hash url = "https://example.com/page" expected = hashlib.sha256(url.lower().encode()).hexdigest() assert get_url_hash(url) == expected def test_case_insensitive(self): from local_deep_research.research_library.utils import get_url_hash assert get_url_hash("HTTPS://EXAMPLE.COM") == get_url_hash( "https://example.com" ) def test_different_urls_different_hashes(self): from local_deep_research.research_library.utils import get_url_hash assert get_url_hash("https://a.com") != get_url_hash("https://b.com") def test_empty_url(self): from local_deep_research.research_library.utils import get_url_hash result = get_url_hash("") assert isinstance(result, str) assert len(result) == 64 # SHA256 hex length # --------------------------------------------------------------------------- # get_library_storage_path # --------------------------------------------------------------------------- class TestGetLibraryStoragePath: """Tests for get_library_storage_path().""" @patch("local_deep_research.utilities.db_utils.get_settings_manager") @patch("local_deep_research.research_library.utils.get_library_directory") def test_user_isolation_mode(self, mock_lib_dir, mock_get_sm, tmp_path): mock_lib_dir.return_value = tmp_path / "library" mock_sm = MagicMock() mock_sm.get_setting.side_effect = lambda key, default: { "research_library.storage_path": str(tmp_path / "library"), "research_library.shared_library": False, }.get(key, default) mock_get_sm.return_value = mock_sm from local_deep_research.research_library.utils import ( get_library_storage_path, ) result = get_library_storage_path("alice") assert result == tmp_path / "library" / "alice" assert result.exists() @patch("local_deep_research.utilities.db_utils.get_settings_manager") @patch("local_deep_research.research_library.utils.get_library_directory") def test_shared_library_mode(self, mock_lib_dir, mock_get_sm, tmp_path): mock_lib_dir.return_value = tmp_path / "library" mock_sm = MagicMock() mock_sm.get_setting.side_effect = lambda key, default: { "research_library.storage_path": str(tmp_path / "library"), "research_library.shared_library": True, }.get(key, default) mock_get_sm.return_value = mock_sm from local_deep_research.research_library.utils import ( get_library_storage_path, ) result = get_library_storage_path("alice") # Shared mode: no username subdirectory assert result == tmp_path / "library" assert result.exists() @patch("local_deep_research.utilities.db_utils.get_settings_manager") @patch("local_deep_research.research_library.utils.get_library_directory") def test_creates_directory_if_not_exists( self, mock_lib_dir, mock_get_sm, tmp_path ): base = tmp_path / "new_dir" mock_lib_dir.return_value = base mock_sm = MagicMock() mock_sm.get_setting.side_effect = lambda key, default: { "research_library.storage_path": str(base), "research_library.shared_library": False, }.get(key, default) mock_get_sm.return_value = mock_sm from local_deep_research.research_library.utils import ( get_library_storage_path, ) result = get_library_storage_path("bob") assert result.exists() assert result.is_dir() # --------------------------------------------------------------------------- # open_file_location # --------------------------------------------------------------------------- class TestOpenFileLocation: """Tests for open_file_location().""" @patch("local_deep_research.research_library.utils.PathValidator") @patch("local_deep_research.research_library.utils.subprocess.run") @patch("local_deep_research.research_library.utils.sys") def test_linux_success(self, mock_sys, mock_run, mock_validator, tmp_path): mock_sys.platform = "linux" mock_validator.validate_local_filesystem_path.return_value = ( tmp_path / "file.txt" ) mock_run.return_value = MagicMock(returncode=0) from local_deep_research.research_library.utils import ( open_file_location, ) assert open_file_location(str(tmp_path / "file.txt")) is True mock_run.assert_called_once_with( ["xdg-open", str(tmp_path)], capture_output=True, text=True, shell=False, ) @patch("local_deep_research.research_library.utils.PathValidator") @patch("local_deep_research.research_library.utils.subprocess.run") @patch("local_deep_research.research_library.utils.sys") def test_linux_failure(self, mock_sys, mock_run, mock_validator, tmp_path): mock_sys.platform = "linux" mock_validator.validate_local_filesystem_path.return_value = ( tmp_path / "file.txt" ) mock_run.return_value = MagicMock(returncode=1, stderr="error") from local_deep_research.research_library.utils import ( open_file_location, ) assert open_file_location(str(tmp_path / "file.txt")) is False @patch("local_deep_research.research_library.utils.PathValidator") @patch("local_deep_research.research_library.utils.subprocess.run") @patch("local_deep_research.research_library.utils.sys") def test_macos_success(self, mock_sys, mock_run, mock_validator, tmp_path): mock_sys.platform = "darwin" mock_validator.validate_local_filesystem_path.return_value = ( tmp_path / "file.txt" ) mock_run.return_value = MagicMock(returncode=0) from local_deep_research.research_library.utils import ( open_file_location, ) assert open_file_location(str(tmp_path / "file.txt")) is True mock_run.assert_called_once_with( ["open", str(tmp_path)], capture_output=True, text=True, shell=False, ) @patch("local_deep_research.research_library.utils.PathValidator") def test_path_validation_failure(self, mock_validator): mock_validator.validate_local_filesystem_path.side_effect = ValueError( "blocked" ) from local_deep_research.research_library.utils import ( open_file_location, ) assert open_file_location("/etc/passwd") is False # --------------------------------------------------------------------------- # get_absolute_library_path # --------------------------------------------------------------------------- class TestLibraryPathConversions: """Tests for get_absolute_library_path.""" @patch( "local_deep_research.research_library.utils.get_library_storage_path" ) def test_absolute_path_from_relative(self, mock_storage, tmp_path): library_root = tmp_path / "library" / "alice" mock_storage.return_value = library_root from local_deep_research.research_library.utils import ( get_absolute_library_path, ) result = get_absolute_library_path("subdir/file.pdf", "alice") assert result == library_root / "subdir" / "file.pdf" # --------------------------------------------------------------------------- # get_absolute_path_from_settings # --------------------------------------------------------------------------- class TestGetAbsolutePathFromSettings: """Tests for get_absolute_path_from_settings.""" @patch("local_deep_research.utilities.db_utils.get_settings_manager") @patch("local_deep_research.research_library.utils.get_library_directory") def test_with_relative_path(self, mock_lib_dir, mock_get_sm, tmp_path): mock_lib_dir.return_value = tmp_path / "library" mock_sm = MagicMock() mock_sm.get_setting.return_value = str(tmp_path / "library") mock_get_sm.return_value = mock_sm from local_deep_research.research_library.utils import ( get_absolute_path_from_settings, ) result = get_absolute_path_from_settings("docs/file.pdf") assert result == tmp_path / "library" / "docs" / "file.pdf" @patch("local_deep_research.utilities.db_utils.get_settings_manager") @patch("local_deep_research.research_library.utils.get_library_directory") def test_empty_relative_path_returns_root( self, mock_lib_dir, mock_get_sm, tmp_path ): mock_lib_dir.return_value = tmp_path / "library" mock_sm = MagicMock() mock_sm.get_setting.return_value = str(tmp_path / "library") mock_get_sm.return_value = mock_sm from local_deep_research.research_library.utils import ( get_absolute_path_from_settings, ) result = get_absolute_path_from_settings("") assert result == tmp_path / "library" # --------------------------------------------------------------------------- # handle_api_error # --------------------------------------------------------------------------- class TestHandleApiError: """Tests for handle_api_error.""" def test_returns_generic_error_message(self, app): from local_deep_research.research_library.utils import handle_api_error with app.app_context(): response, status_code = handle_api_error( "test operation", ValueError("secret details"), 500 ) data = response.get_json() assert status_code == 500 assert data["success"] is False # Must NOT leak the exception message assert "secret details" not in data["error"] assert "internal error" in data["error"].lower() def test_custom_status_code(self, app): from local_deep_research.research_library.utils import handle_api_error with app.app_context(): _, status_code = handle_api_error("op", RuntimeError("err"), 503) assert status_code == 503 def test_default_status_code_is_500(self, app): from local_deep_research.research_library.utils import handle_api_error with app.app_context(): _, status_code = handle_api_error("op", RuntimeError("err")) assert status_code == 500 # --------------------------------------------------------------------------- # ensure_in_collection # --------------------------------------------------------------------------- class TestEnsureInCollection: """Tests for ensure_in_collection().""" def test_returns_existing_row_without_adding(self): from local_deep_research.research_library.utils import ( ensure_in_collection, ) existing = MagicMock(name="existing_DocumentCollection") session = MagicMock() session.query.return_value.filter_by.return_value.first.return_value = ( existing ) result = ensure_in_collection(session, "doc-1", "coll-1") assert result is existing session.query.return_value.filter_by.assert_called_once_with( document_id="doc-1", collection_id="coll-1" ) session.add.assert_not_called() def test_creates_and_adds_when_missing(self): from local_deep_research.research_library.utils import ( ensure_in_collection, ) session = MagicMock() session.query.return_value.filter_by.return_value.first.return_value = ( None ) result = ensure_in_collection(session, "doc-2", "coll-2") session.add.assert_called_once_with(result) assert result.document_id == "doc-2" assert result.collection_id == "coll-2" assert result.indexed is False