- Added logging functionality to app.py and rns.py for better error tracking. - Improved exception handling in RNSManager methods to log specific failures. - Refactored code in various modules to ensure consistent logging practices. - Updated UI components to handle exceptions with user feedback. - Cleaned up formatting in several files for better readability.
482 lines
18 KiB
Python
482 lines
18 KiB
Python
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from ren_browser.storage.storage import (
|
|
StorageManager,
|
|
get_storage_manager,
|
|
initialize_storage,
|
|
)
|
|
|
|
|
|
class TestStorageManager:
|
|
"""Test cases for the StorageManager class."""
|
|
|
|
def test_storage_manager_init_without_page(self):
|
|
"""Test StorageManager initialization without a page."""
|
|
with patch(
|
|
"ren_browser.storage.storage.StorageManager._get_storage_directory",
|
|
) as mock_get_dir:
|
|
mock_dir = Path("/mock/storage")
|
|
mock_get_dir.return_value = mock_dir
|
|
|
|
with patch("pathlib.Path.mkdir") as mock_mkdir:
|
|
storage = StorageManager()
|
|
|
|
assert storage.page is None
|
|
assert storage._storage_dir == mock_dir
|
|
mock_mkdir.assert_called_once_with(parents=True, exist_ok=True)
|
|
|
|
def test_storage_manager_init_with_page(self):
|
|
"""Test StorageManager initialization with a page."""
|
|
mock_page = Mock()
|
|
|
|
with patch(
|
|
"ren_browser.storage.storage.StorageManager._get_storage_directory",
|
|
) as mock_get_dir:
|
|
mock_dir = Path("/mock/storage")
|
|
mock_get_dir.return_value = mock_dir
|
|
|
|
with patch("pathlib.Path.mkdir"):
|
|
storage = StorageManager(mock_page)
|
|
|
|
assert storage.page == mock_page
|
|
assert storage._storage_dir == mock_dir
|
|
|
|
def test_get_storage_directory_desktop(self):
|
|
"""Test storage directory detection for desktop platforms."""
|
|
with (
|
|
patch("os.name", "posix"),
|
|
patch.dict(
|
|
"os.environ",
|
|
{"XDG_CONFIG_HOME": "/home/user/.config"},
|
|
clear=True,
|
|
),
|
|
patch("pathlib.Path.mkdir"),
|
|
patch(
|
|
"ren_browser.storage.storage.StorageManager._ensure_storage_directory",
|
|
),
|
|
):
|
|
storage = StorageManager()
|
|
storage._storage_dir = storage._get_storage_directory()
|
|
expected_dir = Path("/home/user/.config") / "ren_browser"
|
|
assert storage._storage_dir == expected_dir
|
|
|
|
def test_get_storage_directory_windows(self):
|
|
"""Test storage directory detection for Windows."""
|
|
# Skip this test on non-Windows systems to avoid path issues
|
|
pytest.skip("Windows path test skipped on non-Windows system")
|
|
|
|
def test_get_storage_directory_android_with_android_data(self):
|
|
"""Test storage directory detection for Android with ANDROID_DATA."""
|
|
with (
|
|
patch("os.name", "posix"),
|
|
patch.dict(
|
|
"os.environ",
|
|
{"ANDROID_ROOT": "/system", "ANDROID_DATA": "/data"},
|
|
clear=True,
|
|
),
|
|
patch("pathlib.Path.mkdir"),
|
|
patch(
|
|
"ren_browser.storage.storage.StorageManager._ensure_storage_directory",
|
|
),
|
|
):
|
|
storage = StorageManager()
|
|
storage._storage_dir = storage._get_storage_directory()
|
|
expected_dir = Path("/data/ren_browser")
|
|
assert storage._storage_dir == expected_dir
|
|
|
|
def test_get_storage_directory_android_with_external_storage(self):
|
|
"""Test storage directory detection for Android with EXTERNAL_STORAGE."""
|
|
with (
|
|
patch("os.name", "posix"),
|
|
patch.dict(
|
|
"os.environ",
|
|
{"ANDROID_ROOT": "/system", "EXTERNAL_STORAGE": "/storage/emulated/0"},
|
|
clear=True,
|
|
),
|
|
patch("pathlib.Path.mkdir"),
|
|
patch(
|
|
"ren_browser.storage.storage.StorageManager._ensure_storage_directory",
|
|
),
|
|
):
|
|
storage = StorageManager()
|
|
storage._storage_dir = storage._get_storage_directory()
|
|
expected_dir = Path("/storage/emulated/0/ren_browser")
|
|
assert storage._storage_dir == expected_dir
|
|
|
|
def test_get_storage_directory_android_fallback(self):
|
|
"""Test storage directory detection for Android with fallback."""
|
|
with (
|
|
patch("os.name", "posix"),
|
|
patch.dict("os.environ", {"ANDROID_ROOT": "/system"}, clear=True),
|
|
patch("pathlib.Path.mkdir"),
|
|
patch(
|
|
"ren_browser.storage.storage.StorageManager._ensure_storage_directory",
|
|
),
|
|
):
|
|
storage = StorageManager()
|
|
storage._storage_dir = storage._get_storage_directory()
|
|
expected_dir = Path("/data/local/tmp/ren_browser")
|
|
assert storage._storage_dir == expected_dir
|
|
|
|
def test_get_config_path(self):
|
|
"""Test getting config file path."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
config_path = storage.get_config_path()
|
|
expected_path = Path(temp_dir) / "config"
|
|
assert config_path == expected_path
|
|
|
|
def test_get_reticulum_config_path(self):
|
|
"""Test getting Reticulum config directory path."""
|
|
storage = StorageManager()
|
|
|
|
config_path = storage.get_reticulum_config_path()
|
|
expected_path = Path.home() / ".reticulum"
|
|
assert config_path == expected_path
|
|
|
|
def test_save_config_success(self):
|
|
"""Test successful config saving."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to use temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
config_content = "test config content"
|
|
result = storage.save_config(config_content)
|
|
|
|
assert result is True
|
|
config_path = storage.get_config_path()
|
|
assert config_path.exists()
|
|
assert config_path.read_text(encoding="utf-8") == config_content
|
|
|
|
def test_save_config_with_client_storage(self):
|
|
"""Test config saving with client storage."""
|
|
mock_page = Mock()
|
|
mock_page.client_storage.set = Mock()
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager(mock_page)
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to use temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
config_content = "test config content"
|
|
result = storage.save_config(config_content)
|
|
|
|
assert result is True
|
|
mock_page.client_storage.set.assert_called_with(
|
|
"ren_browser_config",
|
|
config_content,
|
|
)
|
|
|
|
def test_save_config_fallback(self):
|
|
"""Test config saving fallback when file system fails."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
mock_page = Mock()
|
|
mock_page.client_storage.set = Mock()
|
|
|
|
storage = StorageManager(mock_page)
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to use temp dir and cause failure
|
|
with (
|
|
patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
),
|
|
patch(
|
|
"pathlib.Path.write_text",
|
|
side_effect=PermissionError("Access denied"),
|
|
),
|
|
):
|
|
config_content = "test config content"
|
|
result = storage.save_config(config_content)
|
|
|
|
assert result is True
|
|
# Check that the config was set to client storage
|
|
mock_page.client_storage.set.assert_any_call(
|
|
"ren_browser_config",
|
|
config_content,
|
|
)
|
|
# Verify that client storage was called at least once
|
|
assert mock_page.client_storage.set.call_count >= 1
|
|
|
|
def test_load_config_from_file(self):
|
|
"""Test loading config from file."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to use temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
config_content = "test config content"
|
|
config_path = storage.get_config_path()
|
|
config_path.write_text(config_content, encoding="utf-8")
|
|
|
|
loaded_config = storage.load_config()
|
|
assert loaded_config == config_content
|
|
|
|
def test_load_config_from_client_storage(self):
|
|
"""Test loading config from client storage when file doesn't exist."""
|
|
mock_page = Mock()
|
|
mock_page.client_storage.get = Mock(return_value="client storage config")
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager(mock_page)
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to also be in temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
loaded_config = storage.load_config()
|
|
assert loaded_config == "client storage config"
|
|
mock_page.client_storage.get.assert_called_with("ren_browser_config")
|
|
|
|
def test_load_config_default(self):
|
|
"""Test loading default config when no config exists."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to also be in temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
loaded_config = storage.load_config()
|
|
assert loaded_config == ""
|
|
|
|
def test_save_bookmarks(self):
|
|
"""Test saving bookmarks."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
bookmarks = [{"name": "Test", "url": "test://example"}]
|
|
result = storage.save_bookmarks(bookmarks)
|
|
|
|
assert result is True
|
|
bookmarks_path = storage._storage_dir / "bookmarks.json"
|
|
assert bookmarks_path.exists()
|
|
|
|
with open(bookmarks_path, encoding="utf-8") as f:
|
|
loaded_bookmarks = json.load(f)
|
|
assert loaded_bookmarks == bookmarks
|
|
|
|
def test_load_bookmarks(self):
|
|
"""Test loading bookmarks."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
bookmarks = [{"name": "Test", "url": "test://example"}]
|
|
bookmarks_path = storage._storage_dir / "bookmarks.json"
|
|
|
|
with open(bookmarks_path, "w", encoding="utf-8") as f:
|
|
json.dump(bookmarks, f)
|
|
|
|
loaded_bookmarks = storage.load_bookmarks()
|
|
assert loaded_bookmarks == bookmarks
|
|
|
|
def test_load_bookmarks_empty(self):
|
|
"""Test loading bookmarks when none exist."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
loaded_bookmarks = storage.load_bookmarks()
|
|
assert loaded_bookmarks == []
|
|
|
|
def test_save_history(self):
|
|
"""Test saving history."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
history = [{"url": "test://example", "timestamp": 1234567890}]
|
|
result = storage.save_history(history)
|
|
|
|
assert result is True
|
|
history_path = storage._storage_dir / "history.json"
|
|
assert history_path.exists()
|
|
|
|
with open(history_path, encoding="utf-8") as f:
|
|
loaded_history = json.load(f)
|
|
assert loaded_history == history
|
|
|
|
def test_load_history(self):
|
|
"""Test loading history."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
history = [{"url": "test://example", "timestamp": 1234567890}]
|
|
history_path = storage._storage_dir / "history.json"
|
|
|
|
with open(history_path, "w", encoding="utf-8") as f:
|
|
json.dump(history, f)
|
|
|
|
loaded_history = storage.load_history()
|
|
assert loaded_history == history
|
|
|
|
def test_get_storage_info(self):
|
|
"""Test getting storage information."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
mock_page = Mock()
|
|
mock_page.client_storage = Mock()
|
|
|
|
storage = StorageManager(mock_page)
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
info = storage.get_storage_info()
|
|
|
|
assert "storage_dir" in info
|
|
assert "config_path" in info
|
|
assert "reticulum_config_path" in info
|
|
assert "storage_dir_exists" in info
|
|
assert "storage_dir_writable" in info
|
|
assert "has_client_storage" in info
|
|
|
|
assert info["storage_dir"] == str(Path(temp_dir))
|
|
assert info["storage_dir_exists"] is True
|
|
assert info["has_client_storage"] is True
|
|
|
|
def test_storage_directory_fallback(self):
|
|
"""Test fallback to temp directory when storage creation fails."""
|
|
with patch.object(StorageManager, "_get_storage_directory") as mock_get_dir:
|
|
mock_get_dir.return_value = Path("/nonexistent/path")
|
|
|
|
with (
|
|
patch(
|
|
"pathlib.Path.mkdir",
|
|
side_effect=[PermissionError("Access denied"), None],
|
|
),
|
|
patch("tempfile.gettempdir", return_value="/tmp"),
|
|
):
|
|
storage = StorageManager()
|
|
|
|
expected_fallback = Path("/tmp") / "ren_browser"
|
|
assert storage._storage_dir == expected_fallback
|
|
|
|
|
|
class TestStorageGlobalFunctions:
|
|
"""Test cases for global storage functions."""
|
|
|
|
def test_get_storage_manager_singleton(self):
|
|
"""Test that get_storage_manager returns the same instance."""
|
|
with patch("ren_browser.storage.storage._storage_manager", None):
|
|
storage1 = get_storage_manager()
|
|
storage2 = get_storage_manager()
|
|
|
|
assert storage1 is storage2
|
|
|
|
def test_get_storage_manager_with_page(self):
|
|
"""Test get_storage_manager with page parameter."""
|
|
mock_page = Mock()
|
|
|
|
with patch("ren_browser.storage.storage._storage_manager", None):
|
|
storage = get_storage_manager(mock_page)
|
|
|
|
assert storage.page == mock_page
|
|
|
|
def test_initialize_storage(self):
|
|
"""Test initialize_storage function."""
|
|
mock_page = Mock()
|
|
|
|
with patch("ren_browser.storage.storage._storage_manager", None):
|
|
storage = initialize_storage(mock_page)
|
|
|
|
assert storage.page == mock_page
|
|
assert get_storage_manager() is storage
|
|
|
|
|
|
class TestStorageManagerEdgeCases:
|
|
"""Test edge cases and error scenarios."""
|
|
|
|
def test_save_config_encoding_error(self):
|
|
"""Test config saving with encoding errors."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Mock the reticulum config path to use temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
# Test with content that might cause encoding issues
|
|
with patch(
|
|
"pathlib.Path.write_text",
|
|
side_effect=UnicodeEncodeError("utf-8", "", 0, 1, "error"),
|
|
):
|
|
result = storage.save_config("test content")
|
|
# Should still succeed due to fallback
|
|
assert result is False
|
|
|
|
def test_load_config_encoding_error(self):
|
|
"""Test config loading with encoding errors."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
storage._storage_dir = Path(temp_dir)
|
|
|
|
# Create a config file with invalid encoding
|
|
config_path = storage.get_config_path()
|
|
config_path.write_bytes(b"\xff\xfe invalid utf-8")
|
|
|
|
# Mock the reticulum config path to also be in temp dir
|
|
with patch.object(
|
|
storage,
|
|
"get_reticulum_config_path",
|
|
return_value=Path(temp_dir) / "reticulum",
|
|
):
|
|
# Should return empty string when encoding fails
|
|
config = storage.load_config()
|
|
assert config == ""
|
|
|
|
def test_is_writable_permission_denied(self):
|
|
"""Test _is_writable when permission is denied."""
|
|
storage = StorageManager()
|
|
|
|
with patch(
|
|
"pathlib.Path.write_text",
|
|
side_effect=PermissionError("Access denied"),
|
|
):
|
|
test_path = Path("/mock/path")
|
|
result = storage._is_writable(test_path)
|
|
assert result is False
|
|
|
|
def test_is_writable_success(self):
|
|
"""Test _is_writable when directory is writable."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
storage = StorageManager()
|
|
test_path = Path(temp_dir)
|
|
|
|
result = storage._is_writable(test_path)
|
|
assert result is True
|