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_bookmark_service from backend.app.db.bookmark_repository import BookmarkRepository from backend.app.main import app from backend.app.security.path_guard import PathGuard from backend.app.services.bookmark_service import BookmarkService class BookmarksApiGoldenTest(unittest.TestCase): def setUp(self) -> None: self.temp_dir = tempfile.TemporaryDirectory() self.root = Path(self.temp_dir.name) / "root" self.root.mkdir(parents=True, exist_ok=True) self.repo = BookmarkRepository(str(Path(self.temp_dir.name) / "bookmarks.db")) path_guard = PathGuard({"storage1": str(self.root)}) service = BookmarkService(path_guard=path_guard, repository=self.repo) async def _override_bookmark_service() -> BookmarkService: return service app.dependency_overrides[get_bookmark_service] = _override_bookmark_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 == "POST": return await client.post(url, json=payload) if method == "DELETE": return await client.delete(url) return await client.get(url) return asyncio.run(_run()) def test_create_success(self) -> None: response = self._request( "POST", "/api/bookmarks", {"path": "storage1/my/path", "label": "My Path"}, ) self.assertEqual(response.status_code, 200) body = response.json() self.assertEqual(body["path"], "storage1/my/path") self.assertEqual(body["label"], "My Path") self.assertIn("id", body) self.assertIn("created_at", body) def test_list_shape(self) -> None: self._request( "POST", "/api/bookmarks", {"path": "storage1/a", "label": "A"}, ) response = self._request("GET", "/api/bookmarks") self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()["items"]), 1) item = response.json()["items"][0] self.assertEqual(set(item.keys()), {"id", "path", "label", "created_at"}) def test_delete_success(self) -> None: created = self._request( "POST", "/api/bookmarks", {"path": "storage1/a", "label": "A"}, ).json() response = self._request("DELETE", f"/api/bookmarks/{created['id']}") self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"id": created["id"]}) def test_invalid_path(self) -> None: response = self._request( "POST", "/api/bookmarks", {"path": "unknown/path", "label": "A"}, ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"]["code"], "invalid_root_alias") def test_invalid_label(self) -> None: response = self._request( "POST", "/api/bookmarks", {"path": "storage1/a", "label": " "}, ) self.assertEqual(response.status_code, 400) self.assertEqual( response.json(), { "error": { "code": "invalid_request", "message": "Label is required", "details": {"label": " "}, } }, ) def test_duplicate_conflict(self) -> None: self._request( "POST", "/api/bookmarks", {"path": "storage1/a", "label": "A"}, ) response = self._request( "POST", "/api/bookmarks", {"path": "storage1/a", "label": "Again"}, ) self.assertEqual(response.status_code, 409) self.assertEqual( response.json(), { "error": { "code": "already_exists", "message": "Bookmark already exists for path", "details": {"path": "storage1/a"}, } }, ) def test_traversal_attempt(self) -> None: response = self._request( "POST", "/api/bookmarks", {"path": "storage1/../etc", "label": "Bad"}, ) self.assertEqual(response.status_code, 403) self.assertEqual(response.json()["error"]["code"], "path_traversal_detected") if __name__ == "__main__": unittest.main()