test: add pytest suite - 97 tests, search + indexer + auth
Create comprehensive test suite with 97 passing tests: - tests/conftest.py: fixtures (TestClient, temp vault dirs, index setup) - tests/test_search.py (27 tests): tokenizer, snippets, highlight, tag filter, search API, advanced search, suggest, tags API - tests/test_indexer.py (32 tests): frontmatter parsing, inline tags, title extraction, scan_vault, find_file_in_index, backlinks - tests/test_auth.py (38 tests): password hashing, JWT create/decode, token revocation, user CRUD, login lockout, rate limiting, middleware Also fix: lazy WeasyPrint import (graceful fallback when GTK missing), add data/ to .gitignore (runtime files from test runs).
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# tests/conftest.py — Shared fixtures for ObsiGate test suite
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
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, vault_config, 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, _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
|
||||
Reference in New Issue
Block a user