from __future__ import annotations import asyncio import sys import tempfile import unittest from pathlib import Path import httpx sys.path.insert(0, str(Path(__file__).resolve().parents[3])) from backend.app.dependencies import get_settings_service from backend.app.db.settings_repository import SettingsRepository from backend.app.main import app from backend.app.security.path_guard import PathGuard from backend.app.services.settings_service import SettingsService class SettingsApiGoldenTest(unittest.TestCase): def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() self.root_path = Path(self.temp_dir.name) / "storage1" self.root_path.mkdir() (self.root_path / "docs").mkdir() (self.root_path / "file.txt").write_text("sample", encoding="utf-8") repository = SettingsRepository(str(Path(self.temp_dir.name) / "settings.db")) service = SettingsService( repository=repository, path_guard=PathGuard({"storage1": str(self.root_path)}), ) async def _override_settings_service() -> SettingsService: return service app.dependency_overrides[get_settings_service] = _override_settings_service def tearDown(self) -> None: app.dependency_overrides.clear() self.temp_dir.cleanup() def _request(self, method: str, url: str, payload: dict | None = None) -> httpx.Response: async def _run() -> httpx.Response: transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: if method == "GET": return await client.get(url) return await client.post(url, json=payload) return asyncio.run(_run()) def test_settings_default_response(self) -> None: response = self._request("GET", "/api/settings") self.assertEqual(response.status_code, 200) self.assertEqual( response.json(), { "show_thumbnails": False, "preferred_startup_path_left": None, "preferred_startup_path_right": None, "selected_theme": "default", "selected_color_mode": "dark", }, ) def test_settings_legacy_single_path_is_used_only_for_left_fallback(self) -> None: repository = SettingsRepository(str(Path(self.temp_dir.name) / "settings.db")) repository.set_setting("preferred_startup_path", "storage1/docs") response = self._request("GET", "/api/settings") self.assertEqual(response.status_code, 200) self.assertEqual( response.json(), { "show_thumbnails": False, "preferred_startup_path_left": "storage1/docs", "preferred_startup_path_right": None, "selected_theme": "default", "selected_color_mode": "dark", }, ) def test_settings_update_persistence_left_and_right(self) -> None: response = self._request( "POST", "/api/settings", { "show_thumbnails": True, "preferred_startup_path_left": "storage1/docs", "preferred_startup_path_right": "storage1/docs", }, ) self.assertEqual(response.status_code, 200) self.assertEqual( response.json(), { "show_thumbnails": True, "preferred_startup_path_left": "storage1/docs", "preferred_startup_path_right": "storage1/docs", "selected_theme": "default", "selected_color_mode": "dark", }, ) self.assertEqual( self._request("GET", "/api/settings").json(), { "show_thumbnails": True, "preferred_startup_path_left": "storage1/docs", "preferred_startup_path_right": "storage1/docs", "selected_theme": "default", "selected_color_mode": "dark", }, ) def test_settings_preferred_startup_path_left_persistence(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_left": "storage1/docs"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["preferred_startup_path_left"], "storage1/docs") self.assertEqual(response.json()["preferred_startup_path_right"], None) self.assertEqual(response.json()["selected_theme"], "default") self.assertEqual(response.json()["selected_color_mode"], "dark") def test_settings_preferred_startup_path_right_persistence(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_right": "storage1/docs"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["preferred_startup_path_left"], None) self.assertEqual(response.json()["preferred_startup_path_right"], "storage1/docs") self.assertEqual(response.json()["selected_theme"], "default") self.assertEqual(response.json()["selected_color_mode"], "dark") def test_settings_preferred_startup_path_empty_string_resets_only_left_to_null(self) -> None: self._request( "POST", "/api/settings", { "preferred_startup_path_left": "storage1/docs", "preferred_startup_path_right": "storage1/docs", }, ) response = self._request("POST", "/api/settings", {"preferred_startup_path_left": " "}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["preferred_startup_path_left"], None) self.assertEqual(response.json()["preferred_startup_path_right"], "storage1/docs") self.assertEqual(response.json()["selected_theme"], "default") self.assertEqual(response.json()["selected_color_mode"], "dark") def test_settings_selected_theme_persistence(self) -> None: response = self._request("POST", "/api/settings", {"selected_theme": "midnight"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["selected_theme"], "midnight") self.assertEqual(response.json()["selected_color_mode"], "dark") def test_settings_selected_color_mode_persistence(self) -> None: response = self._request("POST", "/api/settings", {"selected_color_mode": "light"}) self.assertEqual(response.status_code, 200) self.assertEqual(response.json()["selected_theme"], "default") self.assertEqual(response.json()["selected_color_mode"], "light") def test_settings_rejects_invalid_selected_theme(self) -> None: response = self._request("POST", "/api/settings", {"selected_theme": "unknown"}) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"]["code"], "invalid_request") def test_settings_rejects_invalid_selected_color_mode(self) -> None: response = self._request("POST", "/api/settings", {"selected_color_mode": "sepia"}) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()["error"]["code"], "invalid_request") def test_settings_preferred_startup_path_left_rejects_file_path(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_left": "storage1/file.txt"}) self.assertEqual(response.status_code, 409) self.assertEqual(response.json()["error"]["code"], "path_type_conflict") def test_settings_preferred_startup_path_right_rejects_file_path(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_right": "storage1/file.txt"}) self.assertEqual(response.status_code, 409) self.assertEqual(response.json()["error"]["code"], "path_type_conflict") def test_settings_preferred_startup_path_left_rejects_traversal(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_left": "storage1/../etc"}) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"]["code"], "path_traversal_detected") def test_settings_preferred_startup_path_right_rejects_invalid_root_alias(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_right": "unknown/docs"}) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"]["code"], "invalid_root_alias") def test_settings_preferred_startup_path_left_rejects_missing_directory(self) -> None: response = self._request("POST", "/api/settings", {"preferred_startup_path_left": "storage1/missing"}) self.assertEqual(response.status_code, 404) self.assertEqual(response.json()["error"]["code"], "path_not_found") if __name__ == "__main__": unittest.main()