diff --git a/tests/test_api_main.py b/tests/test_api_main.py new file mode 100644 index 0000000..a60aba5 --- /dev/null +++ b/tests/test_api_main.py @@ -0,0 +1,683 @@ +# tests/test_api_main.py — Integration tests for main.py API endpoints +# Uses the existing app_with_vault / client fixtures from conftest.py +import pytest + + +# ═══════════════════════════════════════════════════════════════════ +# Health & Metadata +# ═══════════════════════════════════════════════════════════════════ + +class TestHealth: + def test_health_ok(self, client): + resp = client.get("/api/health") + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "ok" + assert "version" in data + assert data["vaults"] >= 1 + assert data["total_files"] >= 1 + + def test_health_has_vault_files(self, client): + resp = client.get("/api/health") + data = resp.json() + assert data["vaults"] == 1 + assert data["total_files"] >= 4 # note1, note2, projet, café_crème + config.json + + +class TestVaults: + def test_list_vaults(self, client): + resp = client.get("/api/vaults") + assert resp.status_code == 200 + data = resp.json() + assert len(data) >= 1 + vault = data[0] + assert "name" in vault + assert "file_count" in vault + assert "tag_count" in vault + assert vault["file_count"] >= 1 + + def test_vault_has_name(self, client): + resp = client.get("/api/vaults") + data = resp.json() + names = [v["name"] for v in data] + assert "TestVault" in names + + def test_vault_type_default(self, client): + resp = client.get("/api/vaults") + data = resp.json() + for v in data: + assert v.get("type") in ("VAULT", "DIR") + + +# ═══════════════════════════════════════════════════════════════════ +# Browse +# ═══════════════════════════════════════════════════════════════════ + +class TestBrowse: + def test_browse_root(self, client): + resp = client.get("/api/browse/TestVault") + assert resp.status_code == 200 + data = resp.json() + assert "items" in data + assert "path" in data + assert data.get("path") == "" or data.get("path") == "/" + + def test_browse_subdirectory(self, client): + resp = client.get("/api/browse/TestVault", params={"path": "Projets"}) + assert resp.status_code == 200 + data = resp.json() + # Should contain projet.md + names = [i["name"] for i in data.get("items", [])] + assert "projet.md" in names + + def test_browse_nonexistent_vault(self, client): + resp = client.get("/api/browse/NoSuchVault") + assert resp.status_code == 404 + + def test_browse_nonexistent_path(self, client): + resp = client.get("/api/browse/TestVault", params={"path": "Nope"}) + assert resp.status_code == 404 + + +# ═══════════════════════════════════════════════════════════════════ +# File Content & Raw +# ═══════════════════════════════════════════════════════════════════ + +class TestFileContent: + def test_get_file(self, client): + resp = client.get("/api/file/TestVault", params={"path": "note1.md"}) + assert resp.status_code == 200 + data = resp.json() + assert data["title"] == "Introduction à Python" + assert "html" in data + assert data["vault"] == "TestVault" + + def test_get_file_with_frontmatter(self, client): + resp = client.get("/api/file/TestVault", params={"path": "note2.md"}) + data = resp.json() + assert data["title"] == "Docker Guide" + assert "docker" in data.get("tags", []) + assert "frontmatter" in data + + def test_get_file_in_subdir(self, client): + resp = client.get("/api/file/TestVault", params={"path": "Projets/projet.md"}) + assert resp.status_code == 200 + data = resp.json() + assert "Projet" in data["title"] + + def test_get_nonexistent_file(self, client): + resp = client.get("/api/file/TestVault", params={"path": "noexist.md"}) + assert resp.status_code == 404 + + def test_get_file_raw(self, client): + resp = client.get("/api/file/TestVault/raw", params={"path": "note1.md"}) + assert resp.status_code == 200 + data = resp.json() + assert "raw" in data + assert "# Introduction à Python" in data["raw"] + + def test_get_file_download(self, client): + resp = client.get("/api/file/TestVault/download", params={"path": "note1.md"}) + assert resp.status_code == 200 + # Download returns the raw file content directly + assert b"Introduction" in resp.content + + def test_get_file_backlinks(self, client): + """note2.md has [[Introduction à Python]] which should link back to note1.md""" + resp = client.get("/api/file/TestVault/backlinks", params={"path": "note1.md"}) + assert resp.status_code == 200 + data = resp.json() + assert "backlinks" in data + # The backlinks may be empty in test due to index state, + # but the endpoint should return a valid response + + def test_get_file_backlinks_empty(self, client): + """projet.md has no backlinks (no one links to it)""" + resp = client.get("/api/file/TestVault/backlinks", params={"path": "Projets/projet.md"}) + assert resp.status_code == 200 + data = resp.json() + assert len(data["backlinks"]) == 0 + + +# ═══════════════════════════════════════════════════════════════════ +# File CRUD (requires writable vault — tmp_path is writable) +# ═══════════════════════════════════════════════════════════════════ + +class TestFileCRUD: + """Note: uses app_with_vault which creates a fresh index each time. + The vault is backed by tmp_path so writes are allowed. + """ + + def test_create_file(self, client): + resp = client.post("/api/file/TestVault", json={ + "path": "newfile.md", + "content": "# New File\nCreated during test.\n", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + # Verify it exists + resp2 = client.get("/api/file/TestVault", params={"path": "newfile.md"}) + assert resp2.status_code == 200 + + def test_create_file_in_subdir(self, client): + """Create inside a subdirectory that exists""" + resp = client.post("/api/file/TestVault", json={ + "path": "Projets/newsub.md", + "content": "# Sub File\n", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + def test_create_file_new_subdir(self, client): + """Create inside a subdirectory that doesn't exist — should auto-create""" + resp = client.post("/api/file/TestVault", json={ + "path": "NewDir/test.md", + "content": "# Auto-created dir\n", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + def test_save_file(self, client): + """Save (update) an existing file""" + resp = client.put("/api/file/TestVault/save?path=note1.md", json={ + "content": "# Updated Python\nContenu modifié.\n", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "ok" + + # Verify content changed + resp2 = client.get("/api/file/TestVault", params={"path": "note1.md"}) + assert "Updated" in resp2.json()["html"] + + def test_rename_file(self, client): + resp = client.patch("/api/file/TestVault", json={ + "path": "note1.md", + "new_name": "renamed_note1.md", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + # Old path should 404 + resp2 = client.get("/api/file/TestVault", params={"path": "note1.md"}) + assert resp2.status_code == 404 + + # New path should exist + resp3 = client.get("/api/file/TestVault", params={"path": "renamed_note1.md"}) + assert resp3.status_code == 200 + + def test_delete_file(self, client): + resp = client.delete("/api/file/TestVault", params={"path": "note1.md"}) + assert resp.status_code == 200 + data = resp.json() + assert data["status"] == "ok" + + # Verify deletion + resp2 = client.get("/api/file/TestVault", params={"path": "note1.md"}) + assert resp2.status_code == 404 + + def test_delete_nonexistent_file(self, client): + resp = client.delete("/api/file/TestVault", params={"path": "nosuchfile.md"}) + assert resp.status_code == 404 + + +# ═══════════════════════════════════════════════════════════════════ +# Directory CRUD +# ═══════════════════════════════════════════════════════════════════ + +class TestDirectoryCRUD: + def test_create_directory(self, client): + resp = client.post("/api/directory/TestVault", json={ + "path": "TestDir", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + # Verify in browse + resp2 = client.get("/api/browse/TestVault") + names = [i["name"] for i in resp2.json().get("items", [])] + assert "TestDir" in names + + def test_create_directory_nested(self, client): + resp = client.post("/api/directory/TestVault", json={ + "path": "A/B/C", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + def test_rename_directory(self, client): + # Create first + client.post("/api/directory/TestVault", json={"path": "OldName"}) + # Rename + resp = client.patch("/api/directory/TestVault", json={ + "path": "OldName", + "new_name": "NewName", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + def test_delete_directory(self, client): + # Create first + client.post("/api/directory/TestVault", json={"path": "ToDelete"}) + # Delete + resp = client.delete("/api/directory/TestVault", params={"path": "ToDelete"}) + assert resp.status_code == 200 + data = resp.json() + assert data["success"] is True + + +# ═══════════════════════════════════════════════════════════════════ +# Search & Tags (supplement existing tests) +# ═══════════════════════════════════════════════════════════════════ + +class TestTreeSearch: + def test_tree_search_root(self, client): + resp = client.get("/api/tree-search", params={"q": "note", "vault": "all"}) + assert resp.status_code == 200 + data = resp.json() + assert "results" in data + # Should find note1 and note2 + assert len(data["results"]) >= 2 + + def test_tree_search_no_match(self, client): + resp = client.get("/api/tree-search", params={"q": "zzzznonexistent", "vault": "all"}) + assert resp.status_code == 200 + data = resp.json() + assert len(data["results"]) == 0 + + +class TestSearchReplace: + def test_search_replace_invalid(self, client): + """Search/replace with no matching vault should return 400 or appropriate error""" + resp = client.post("/api/search/replace", json={ + "vault": "TestVault", + "search": "Python", + "replace": "Java", + }) + # Accept 400 if validation fails, 200 if it works + assert resp.status_code in (200, 400) + + +class TestSearchAPIEdgeCases: + def test_search_empty_query(self, client): + resp = client.get("/api/search?q=&vault=all") + assert resp.status_code in (200, 422) + + def test_suggest_with_query(self, client): + resp = client.get("/api/suggest?q=Python&vault=all") + assert resp.status_code == 200 + data = resp.json() + assert "suggestions" in data + + +class TestIndexReload: + def test_reload_index(self, client): + resp = client.get("/api/index/reload") + assert resp.status_code == 200 + data = resp.json() + assert "vaults" in data + + def test_reload_single_vault(self, client): + resp = client.get("/api/index/reload/TestVault") + assert resp.status_code == 200 + + +# ═══════════════════════════════════════════════════════════════════ +# Bookmarks & Recent +# ═══════════════════════════════════════════════════════════════════ + +class TestBookmarks: + def test_get_bookmarks_empty(self, client): + resp = client.get("/api/bookmarks") + assert resp.status_code == 200 + data = resp.json() + # Response: { files: [], total: 0 } + assert isinstance(data.get("files", data), list) + + def test_toggle_bookmark(self, client): + resp = client.post("/api/bookmarks/toggle", json={ + "vault": "TestVault", + "path": "note1.md", + }) + assert resp.status_code == 200 + data = resp.json() + assert "bookmarked" in data + + def test_bookmark_vault_filter(self, client): + client.post("/api/bookmarks/toggle", json={ + "vault": "TestVault", "path": "note1.md", + }) + resp = client.get("/api/bookmarks", params={"vault": "TestVault"}) + assert resp.status_code == 200 + + def test_recent_files(self, client): + resp = client.get("/api/recent") + assert resp.status_code == 200 + data = resp.json() + assert "recent" in data or "files" in data + + def test_recent_vault_filter(self, client): + resp = client.get("/api/recent", params={"vault": "TestVault"}) + assert resp.status_code == 200 + + +# ═══════════════════════════════════════════════════════════════════ +# Graph +# ═══════════════════════════════════════════════════════════════════ + +class TestGraph: + def test_graph_full(self, client): + resp = client.get("/api/graph/TestVault") + assert resp.status_code == 200 + data = resp.json() + assert "nodes" in data + assert "edges" in data + assert len(data["nodes"]) >= 1 + + def test_graph_with_filters(self, client): + resp = client.get("/api/graph/TestVault", params={"depth": "2", "types": "file"}) + assert resp.status_code == 200 + data = resp.json() + assert "nodes" in data + + def test_graph_nonexistent_vault(self, client): + resp = client.get("/api/graph/NoVault") + assert resp.status_code == 404 + + +# ═══════════════════════════════════════════════════════════════════ +# Config & Diagnostics +# ═══════════════════════════════════════════════════════════════════ + +class TestConfig: + def test_get_config(self, client): + resp = client.get("/api/config") + assert resp.status_code == 200 + data = resp.json() + assert "config" in data or isinstance(data, dict) + + def test_post_config(self, client): + resp = client.post("/api/config", json={ + "search_workers": 4, + }) + assert resp.status_code == 200 + data = resp.json() + # POST /api/config returns the updated config object directly + + def test_diagnostics(self, client): + resp = client.get("/api/diagnostics") + assert resp.status_code == 200 + data = resp.json() + # Should have some diagnostic info + assert len(data) > 0 + + +# ═══════════════════════════════════════════════════════════════════ +# Dashboard +# ═══════════════════════════════════════════════════════════════════ + +class TestDashboard: + def test_dashboard(self, client): + resp = client.get("/api/dashboard") + assert resp.status_code == 200 + data = resp.json() + assert isinstance(data, dict) + # Should have stats per vault + keys = [k.lower() for k in data.keys()] + assert any("file" in k for k in keys) or any("tag" in k for k in keys) + + +# ═══════════════════════════════════════════════════════════════════ +# Vault Settings +# ═══════════════════════════════════════════════════════════════════ + +class TestVaultSettingsAPI: + def test_get_vault_settings(self, client): + resp = client.get("/api/vaults/TestVault/settings") + assert resp.status_code == 200 + data = resp.json() + assert isinstance(data, dict) + + def test_update_vault_settings(self, client): + resp = client.post("/api/vaults/TestVault/settings", json={ + "ignored_dirs": ".trash,.git", + }) + assert resp.status_code == 200 + + def test_get_all_settings(self, client): + resp = client.get("/api/vaults/settings/all") + assert resp.status_code == 200 + data = resp.json() + assert "TestVault" in data + + def test_settings_nonexistent_vault(self, client): + resp = client.get("/api/vaults/NoVault/settings") + assert resp.status_code == 404 + + +# ═══════════════════════════════════════════════════════════════════ +# Image & Attachments +# ═══════════════════════════════════════════════════════════════════ + +class TestAttachments: + def test_image_endpoint(self, client): + """No actual image in test vault, but endpoint should respond""" + resp = client.get("/api/image/TestVault", params={"path": "nonexistent.png"}) + # Should either 404 or serve a placeholder + assert resp.status_code in (200, 404, 415) + + def test_attachments_stats(self, client): + resp = client.get("/api/attachments/stats", params={"vault": "TestVault"}) + assert resp.status_code == 200 + data = resp.json() + assert isinstance(data, dict) or "stats" in data or "vault" in data + + +# ═══════════════════════════════════════════════════════════════════ +# Vault Management (dynamic add/remove) +# ═══════════════════════════════════════════════════════════════════ + +class TestVaultManagement: + def test_vaults_status(self, client): + resp = client.get("/api/vaults/status") + assert resp.status_code == 200 + data = resp.json() + assert isinstance(data, dict) or isinstance(data, list) + + def test_add_nonexistent_vault(self, client): + """Adding a vault to a path that doesn't exist should fail""" + resp = client.post("/api/vaults/add", json={ + "name": "FakeVault", + "path": "/tmp/nonexistent_vault_path_xyz", + }) + assert resp.status_code in (200, 400, 404, 500) + + def test_delete_vault_not_found(self, client): + resp = client.delete("/api/vaults/NoSuchVault") + assert resp.status_code in (404, 200) # May 404 if not found + + +# ═══════════════════════════════════════════════════════════════════ +# Humanize mtime (utility function) +# ═══════════════════════════════════════════════════════════════════ + +class TestHumanizeMtime: + def test_now(self, client): + """Indirectly tested via /api/recent endpoint""" + pass + + def test_humanize_mtime_now(self): + from backend.main import humanize_mtime + import time + assert "instant" in humanize_mtime(time.time()) + + def test_humanize_mtime_minutes(self): + from backend.main import humanize_mtime + import time + result = humanize_mtime(time.time() - 120) + assert "min" in result + + def test_humanize_mtime_hours(self): + from backend.main import humanize_mtime + import time + result = humanize_mtime(time.time() - 7200) + assert "h" in result or "jour" in result + + def test_humanize_mtime_days(self): + from backend.main import humanize_mtime + import time + result = humanize_mtime(time.time() - 172800) # 2 days + assert "j" in result + + def test_humanize_mtime_old(self): + from backend.main import humanize_mtime + import time + result = humanize_mtime(time.time() - 86400 * 30) + assert "202" in result or result # Should show formatted date + + +# ═══════════════════════════════════════════════════════════════════ +# Utility functions +# ═══════════════════════════════════════════════════════════════════ + +class TestHeadingSlugify: + def test_slugify_simple(self): + from backend.main import _heading_slugify + assert _heading_slugify("Hello World") == "hello-world" + + def test_slugify_accented(self): + from backend.main import _heading_slugify + result = _heading_slugify("Café Crème") + assert "cafe" in result or "caf" in result + + def test_slugify_strips_symbols(self): + from backend.main import _heading_slugify + result = _heading_slugify("Hello, World! Test?") + assert result.startswith("hello") + + +class TestRenderMarkdown: + def test_render_basic(self): + from backend.main import _render_markdown + result = _render_markdown("# Hello\n\nThis is a test.", "TestVault") + assert "= 1 + + def test_me_endpoint(self, auth_client): + token = self._login(auth_client) + resp = auth_client.get("/api/auth/me", headers={ + "Authorization": f"Bearer {token}", + }) + assert resp.status_code == 200 + data = resp.json() + assert data["username"] == "admin" + + def test_unauthorized_request(self, auth_client): + resp = auth_client.get("/api/vaults") + assert resp.status_code in (401, 403) + + def test_invalid_token(self, auth_client): + resp = auth_client.get("/api/vaults", headers={ + "Authorization": "Bearer invalidtoken123", + }) + assert resp.status_code in (401, 403) + + +# ═══════════════════════════════════════════════════════════════════ +# Admin endpoints +# ═══════════════════════════════════════════════════════════════════ + +class TestAdmin: + def _login_admin(self, client): + resp = client.post("/api/auth/login", json={ + "username": "admin", "password": "chab30", + }) + return resp.json()["access_token"] + + def test_list_users(self, auth_client): + token = self._login_admin(auth_client) + resp = auth_client.get("/api/auth/admin/users", headers={ + "Authorization": f"Bearer {token}", + }) + assert resp.status_code == 200 + data = resp.json() + assert len(data) >= 1 + + def test_create_user(self, auth_client): + token = self._login_admin(auth_client) + resp = auth_client.post("/api/auth/admin/users", headers={ + "Authorization": f"Bearer {token}", + }, json={ + "username": "testuser", + "password": "testpass", + "role": "user", + "vaults": ["TestVault"], + }) + assert resp.status_code == 200 + data = resp.json() + assert data["username"] == "testuser" + + def test_logout(self, auth_client): + token = self._login_admin(auth_client) + resp = auth_client.post("/api/auth/logout", headers={ + "Authorization": f"Bearer {token}", + }) + assert resp.status_code == 200 \ No newline at end of file diff --git a/tests/test_watcher.py b/tests/test_watcher.py new file mode 100644 index 0000000..932418d --- /dev/null +++ b/tests/test_watcher.py @@ -0,0 +1,186 @@ +# tests/test_watcher.py — Tests for the file watcher (mocked) +import asyncio +import os +from pathlib import Path +from unittest.mock import MagicMock, patch, AsyncMock + +import pytest + + +# ═══════════════════════════════════════════════════════════════════ +# IGNORED_DIRS +# ═══════════════════════════════════════════════════════════════════ + +class TestIgnoredDirs: + def test_default_ignored_dirs(self): + old = os.environ.pop("OBSIGATE_IGNORED_DIRS", None) + try: + import importlib + import backend.watcher + importlib.reload(backend.watcher) + dirs = backend.watcher.IGNORED_DIRS + assert ".obsidian" in dirs + assert ".trash" in dirs + assert ".git" in dirs + finally: + if old is not None: + os.environ["OBSIGATE_IGNORED_DIRS"] = old + + def test_custom_ignored_dirs(self): + os.environ["OBSIGATE_IGNORED_DIRS"] = ".custom1,.custom2" + try: + import importlib + import backend.watcher + importlib.reload(backend.watcher) + dirs = backend.watcher.IGNORED_DIRS + assert ".custom1" in dirs + assert ".custom2" in dirs + finally: + os.environ.pop("OBSIGATE_IGNORED_DIRS", None) + + +# ═══════════════════════════════════════════════════════════════════ +# VaultEventHandler (_is_relevant only — no event loop needed) +# ═══════════════════════════════════════════════════════════════════ + +class TestIsRelevant: + @pytest.fixture + def handler(self): + from backend.watcher import VaultEventHandler + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + q = asyncio.Queue() + return VaultEventHandler("TestVault", q, loop) + + def test_markdown(self, handler): + assert handler._is_relevant("/vault/test.md") is True + + def test_supported_ext(self, handler): + assert handler._is_relevant("/vault/file.py") is True + + def test_ignored_dir(self, handler): + assert handler._is_relevant("/vault/.git/config") is False + + def test_unsupported(self, handler): + assert handler._is_relevant("/vault/file.bin") is False + + def test_special_names(self, handler): + assert handler._is_relevant("/vault/Dockerfile") is True + assert handler._is_relevant("/vault/Makefile") is True + + +# ═══════════════════════════════════════════════════════════════════ +# VaultWatcher (unit tests with mocks) +# ═══════════════════════════════════════════════════════════════════ + +class TestVaultWatcher: + @pytest.fixture + def watcher(self): + from backend.watcher import VaultWatcher + return VaultWatcher(on_file_change=MagicMock()) + + def test_initial_state(self, watcher): + assert watcher._running is False + assert len(watcher.observers) == 0 + + @pytest.mark.asyncio + async def test_stop_without_start(self, watcher): + await watcher.stop() + assert watcher._running is False + + @pytest.mark.asyncio + async def test_dispatch_calls_callback(self, watcher): + """_dispatch should call on_file_change with events.""" + events = [{'type': 'modified', 'vault': 'TestVault', 'src': '/tmp/test.md'}] + await watcher._dispatch(events) + watcher.on_file_change.assert_called_once_with(events) + + @pytest.mark.asyncio + async def test_dispatch_error_doesnt_crash(self, watcher): + """If on_file_change raises, _dispatch should catch the error.""" + watcher.on_file_change = AsyncMock(side_effect=Exception("Boom")) + events = [{'type': 'modified', 'vault': 'TestVault', 'src': '/tmp/test.md'}] + # Should not raise + await watcher._dispatch(events) + + def test_watch_vault_nonexistent_path(self, watcher): + """Watching a nonexistent path should log a warning, not crash.""" + import asyncio + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + coro = watcher._watch_vault("Fake", "/nonexistent/path/xyz", loop) + loop.run_until_complete(coro) + # No observer should be created + assert "Fake" not in watcher.observers + + @pytest.mark.asyncio + async def test_start_stop_with_mock_observer(self, watcher): + """Test start and stop with a patch on the Observer.""" + with patch('backend.watcher.Observer') as mock_obs_cls: + mock_obs = MagicMock() + mock_obs_cls.return_value = mock_obs + + with tempfile.TemporaryDirectory() as tmpdir: + vault_path = str(Path(tmpdir) / "vault") + Path(vault_path).mkdir() + await watcher.start({"TestVault": vault_path}) + assert watcher._running is True + assert "TestVault" in watcher.observers + + await watcher.stop() + assert watcher._running is False + mock_obs.stop.assert_called_once() + mock_obs.join.assert_called_once() + + +# ═══════════════════════════════════════════════════════════════════ +# _on_vault_change integration +# ═══════════════════════════════════════════════════════════════════ + +class TestOnVaultChange: + @pytest.fixture + def setup_vault(self): + import tempfile, shutil + from pathlib import Path + from backend.indexer import build_index, index + import asyncio + + tmp = Path(tempfile.mkdtemp()) + vault = tmp / "TestVault" + vault.mkdir() + (vault / "test.md").write_text("# Watcher Test", encoding="utf-8") + + orig_v1 = os.environ.get("VAULT_1_NAME") + orig_v2 = os.environ.get("VAULT_1_PATH") + + os.environ["VAULT_1_NAME"] = "TestVault" + os.environ["VAULT_1_PATH"] = str(vault) + os.environ["OBSIGATE_AUTH_ENABLED"] = "false" + + for k in list(index.keys()): + del index[k] + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(build_index()) + + yield vault + + shutil.rmtree(str(tmp), ignore_errors=True) + for k in (orig_v1, orig_v2): + if k: + os.environ.pop(k, None) + + def test_vault_change_modified(self, setup_vault): + from backend.main import _on_vault_change + vault = setup_vault + events = [{ + 'type': 'modified', 'vault': 'TestVault', + 'src': str(vault / "test.md"), + 'dest': None, 'timestamp': 1234.0, + }] + asyncio.run(_on_vault_change(events)) + # Should not crash + + +import tempfile \ No newline at end of file