Files
ObsiGate/tests/conftest.py
T
bruno ac16fc1f0e
CI / lint (push) Successful in 52s
CI / security (push) Successful in 36s
CI / test (push) Successful in 1m6s
CI / build (push) Successful in 1m15s
CI / e2e (push) Failing after 12m40s
Desktop Build / build-windows (push) Canceled after 0s
Desktop Build / build-linux (push) Canceled after 0s
feat: #78 Excalidraw finitions + #67 push + #68 health-detailed + #77 desktop jumplist
#78 Excalidraw — finitions B5/C8/F2/F3
- backend/indexer.py: port Python pur de lz-string decompressFromBase64 (bitsPerChar=6, resetValue=32) validé contre 4 fixtures JS truth (texte accentué, edge cases) — remplace le stub base64 non-fonctionnel
- B5 indexation: .excalidraw.md (format plugin Obsidian) maintenant décompressé côté Python → texte indexé pour recherche TF-IDF
- 7 nouveaux tests B5 dans tests/test_excalidraw.py (13/13 total)
- F2: excalidraw-viewer.test.mjs enregistré dans CI (lint job)
- F3: tests/e2e/excalidraw.spec.js (9 specs — ouverture .excalidraw/.excalidraw.md, création via modale/menu contextuel, toolbar, thème, pop-out, recherche)
- Fixtures test_vault/diagram.excalidraw + diagram.excalidraw.md

#67 Push notifications (Web Push API + VAPID)
- backend/push.py: router + VAPID keys + send_push_notification (pywebpush>=2.3.0)
- frontend/js/push.js: subscription UI + service worker integration
- tests/test_push.py: 10 tests (subscribe/list/unsubscribe/vapid key)
- requirements.txt + sw.js + locales push strings

#68 Health check enrichi
- backend/main.py: GET /api/health/detailed (admin) — memory/cpu/disk/backups/index/SSE connections
- backend/indexer.py: _last_full_index_ts tracking
- tests/conftest.py: admin_client fixture
- tests/test_api_main.py: 3 nouveaux tests health detailed

#77 Desktop jumplist
- desktop/src/jumplist.rs: Windows jumplist integration
- Cargo.toml/lock + capabilities + main.rs
- frontend/js/desktop.js: Tauri bridge (isTauriEnv, invoke, getSystemTheme, syncSystemTheme, shouldShowWizard, crash banner)
- tests/frontend/desktop.test.mjs: 21 tests JSDOM (détection, invoke degradation, theme gating, wizard, crash banner)
- tests/frontend/unit.test.mjs: desktop.js whitelisted (standalone global reader)

# Frontend & CI
- frontend/sw.js: réécriture complète (precache + push event handlers + offline)
- frontend/index.html: section push dans settings + about repositionné
- frontend/js/app.js: initDesktopIntegration + initPush
- CI .gitea/workflows/ci.yml: excalidraw-viewer.test.mjs ajouté au lint job

All checks: ruff clean, 552 backend tests pass, 9 frontend unit, 5 excalidraw-viewer, 21 desktop, 9 pane-manager, validate-imports 33 modules.
2026-09-09 23:18:29 -04:00

204 lines
6.2 KiB
Python

# tests/conftest.py — Shared fixtures for ObsiGate test suite
import os
import sys
import shutil
import json
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
# Add project root to path so we can import backend modules
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
@pytest.fixture(autouse=True)
def _clean_env():
"""Ensure no vault env vars leak between tests — but preserve test vault config."""
saved = {}
# Only clean VAULT_N/DIR_N vars, NOT OBSIGATE_ vars needed by the app
for key in list(os.environ.keys()):
if key.startswith(("VAULT_", "DIR_")):
saved[key] = os.environ.pop(key)
yield
# Restore
for key in list(os.environ.keys()):
if key.startswith(("VAULT_", "DIR_")):
if key not in saved:
os.environ.pop(key)
os.environ.update(saved)
@pytest.fixture
def test_vault_dir(tmp_path: Path) -> str:
"""Create a temporary Obsidian-style vault with markdown files."""
vault = tmp_path / "TestVault"
vault.mkdir()
# Simple markdown file
(vault / "note1.md").write_text(
"---\ntags:\n - python\n - tutorial\ntitle: Introduction à Python\n---\n"
"# Introduction à Python\nPython est un langage de programmation moderne.\n"
"Il supporte la programmation orientée objet et fonctionnelle.\n"
"La syntaxe de Python est claire et lisible.\n",
encoding="utf-8",
)
# File with inline tags and wikilinks
(vault / "note2.md").write_text(
"---\ntags:\n - docker\nstatut: actif\nauteur: Jean Dupont\ntitle: Docker Guide\n---\n"
"# Docker Guide\nDocker est une plateforme de conteneurisation.\n"
"Voir aussi [[Introduction à Python]] pour les scripts.\n"
"Et aussi [[Proxmox Setup]] pour l'infrastructure.\n"
"Un tag inline #devops pour le fun.\n",
encoding="utf-8",
)
# File in subdirectory
sub = vault / "Projets"
sub.mkdir()
(sub / "projet.md").write_text(
"---\ntags:\n - projet\n - python\ntitle: Mon Projet\n---\n"
"# Mon Projet\nUtilise Docker et Python.\n"
"Voir [[Introduction à Python]].\n",
encoding="utf-8",
)
# Non-markdown file
(vault / "config.json").write_text('{"key": "value"}', encoding="utf-8")
# File with accents in title
(vault / "café_crème.md").write_text(
"---\ntitle: Café Crème\n---\n# Café Crème\nUn bon café.\n",
encoding="utf-8",
)
return str(vault)
@pytest.fixture
def app_with_vault(test_vault_dir: str):
"""Create a FastAPI TestClient with a test vault configured.
Imports app lazily to avoid side-effects at module load time.
Disables auth and watcher for testing.
"""
os.environ["VAULT_1_NAME"] = "TestVault"
os.environ["VAULT_1_PATH"] = test_vault_dir
os.environ["OBSIGATE_AUTH_ENABLED"] = "false"
# Prevent watcher from starting (not needed for tests)
import backend.main
backend.main._load_config = lambda: {"watcher_enabled": False}
from backend.main import app
from backend.indexer import build_index
import asyncio
# Build the index
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(build_index())
# Build inverted index for search
from backend.search import init_inverted_index
init_inverted_index()
client = TestClient(app)
return client
@ pytest.fixture
def client(app_with_vault):
"""Alias for app_with_vault."""
return app_with_vault
@ pytest.fixture
def admin_client(tmp_path):
"""TestClient with auth enabled, isolated temp data, admin user provisioned.
Used by tests that need admin authentication (e.g., /api/health/detailed).
"""
data_dir = tmp_path / "data"
data_dir.mkdir()
import json
from backend.auth.password import hash_password
pw_hash = hash_password("chab30")
users = {
"version": 1,
"users": {
"admin": {
"id": "admin-1",
"username": "admin",
"display_name": "admin",
"password_hash": pw_hash,
"role": "admin",
"vaults": ["*"],
"active": True,
"created_at": "2026-01-01T00:00:00",
},
"normaluser": {
"id": "user-1",
"username": "normaluser",
"display_name": "normal",
"password_hash": hash_password("normal123"),
"role": "user",
"vaults": ["TestVault"],
"active": True,
"created_at": "2026-01-01T00:00:00",
},
},
}
(data_dir / "users.json").write_text(json.dumps(users), encoding="utf-8")
src_secret = Path("data/secret.key")
if src_secret.exists():
shutil.copy2(str(src_secret), str(data_dir / "secret.key"))
orig_cwd = os.getcwd()
test_vault_path = os.path.abspath("test-vault")
os.chdir(str(tmp_path))
os.environ["VAULT_1_NAME"] = "TestVault"
os.environ["VAULT_1_PATH"] = test_vault_path
os.environ["OBSIGATE_AUTH_ENABLED"] = "true"
os.environ["OBSIGATE_ADMIN_USER"] = "admin"
os.environ["OBSIGATE_ADMIN_PASSWORD"] = "chab30"
os.environ["OBSIGATE_WATCHER_ENABLED"] = "false"
import backend.main
backend.main._load_config = lambda: {"watcher_enabled": False}
from backend.main import app
from backend.indexer import build_index, index
import asyncio
for key in list(index.keys()):
del index[key]
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(build_index())
from backend.search import init_inverted_index
init_inverted_index()
from fastapi.testclient import TestClient
client = TestClient(app)
yield client
if hasattr(client, "close"):
client.close()
loop.run_until_complete(asyncio.sleep(0))
os.chdir(orig_cwd)
shutil.rmtree(str(tmp_path), ignore_errors=True)
for k in [
"VAULT_1_NAME", "VAULT_1_PATH", "OBSIGATE_AUTH_ENABLED",
"OBSIGATE_ADMIN_USER", "OBSIGATE_ADMIN_PASSWORD", "OBSIGATE_WATCHER_ENABLED",
]:
os.environ.pop(k, None)