Files
ObsiGate/tests/test_user_home.py
T
bruno 8235d632b8
CI / lint (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / security (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / e2e (push) Canceled after 0s
feat: dossier personnel par utilisateur — vault home-<user> + registre de vaults persistant #194
- backend/user_home.py : ensure_user_home()/release_user_home() idempotents
  (mkdir, vault, watcher, octroi dans user.vaults), rappelés à la création
  d'un compte et au démarrage (migration + réparation + sweep des vaults
  home-* orphelins), dossier conservé à la suppression.
- backend/indexer.py : data/vaults.json, registre persistant des vaults
  ajoutés à la volée, fusionné par load_vault_config() — ils disparaissaient
  au premier reindex complet et au redémarrage. reload_single_vault remet
  vault_config[name] après remove_vault_from_index.
- backend/auth/router.py + routers/vaults.py : appels ensure/release +
  persistance à l'ajout/retrait d'un vault.
- docker-compose.yml : montage unique /NFS/OBSIDIAN_DOC/Home:/vaults/Home +
  OBSIGATE_HOME_ROOT (absente = fonctionnalité inactive).
- Tests : tests/test_user_home.py (12) + tests/test_auth_api.py::TestUserHome (2).
2026-10-10 00:40:35 -04:00

225 lines
7.3 KiB
Python

# tests/test_user_home.py — #194 dossier personnel par utilisateur
import asyncio
import json
import os
import pytest
def _run(coro):
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(coro)
finally:
loop.close()
@pytest.fixture
def home_env(tmp_path, monkeypatch):
"""Racine Home + registre vaults + users.json isolés, globals sauvegardés."""
root = tmp_path / "Home"
root.mkdir()
monkeypatch.setenv("OBSIGATE_HOME_ROOT", str(root))
from backend import indexer
monkeypatch.setattr(indexer, "DYNAMIC_VAULTS_FILE", tmp_path / "vaults.json")
from backend.auth import user_store
monkeypatch.setattr(user_store, "USERS_FILE", tmp_path / "users.json")
from backend import watcher_state
monkeypatch.setattr(watcher_state, "get_watcher", lambda: None)
saved_index = dict(indexer.index)
saved_config = dict(indexer.vault_config)
yield root
indexer.index.clear()
indexer.index.update(saved_index)
indexer.vault_config.clear()
indexer.vault_config.update(saved_config)
def _create_user(name: str) -> dict:
from backend.auth.user_store import create_user
return create_user(name, "Motdepasse1!", role="user")
def _registry() -> dict:
"""Contenu du registre dynamique (chemin monkeypatché par home_env)."""
from backend import indexer
return json.loads(indexer.DYNAMIC_VAULTS_FILE.read_text(encoding="utf-8"))
class TestEnsureUserHome:
def test_creates_folder_vault_and_grant(self, home_env):
from backend.auth.user_store import get_user
from backend.indexer import index
from backend.user_home import ensure_user_home
_create_user("alice")
name = _run(ensure_user_home("alice"))
assert name == "home-alice"
assert (home_env / "alice").is_dir()
assert "home-alice" in index
assert "home-alice" in get_user("alice")["vaults"]
# Registre persistant rempli → survit au prochain rebuild/redémarrage
assert "home-alice" in _registry()["vaults"]
def test_is_idempotent(self, home_env):
from backend.auth.user_store import get_user
from backend.indexer import index
from backend.user_home import ensure_user_home
_create_user("bob")
_run(ensure_user_home("bob"))
index_snapshot = dict(index)
_run(ensure_user_home("bob"))
assert get_user("bob")["vaults"].count("home-bob") == 1
assert index == index_snapshot
def test_invalid_username_never_touches_disk(self, home_env):
from backend.indexer import index
from backend.user_home import ensure_user_home
assert _run(ensure_user_home("../evil")) is None
assert _run(ensure_user_home("a/b")) is None
assert not (home_env.parent / "evil").exists()
assert not any(name.startswith("home-") for name in index)
def test_disabled_without_env(self, home_env, monkeypatch):
from backend.user_home import ensure_user_home
monkeypatch.delenv("OBSIGATE_HOME_ROOT")
assert _run(ensure_user_home("alice")) is None
assert not (home_env / "alice").exists()
class TestPersistence:
def test_registry_reloaded_by_load_vault_config(self, home_env):
"""data/vaults.json doit être relu par load_vault_config (rebuild/restart)."""
from backend import indexer
_create_user("carol")
from backend.user_home import ensure_user_home
_run(ensure_user_home("carol"))
# Simule un redémarrage : vault_config vidé, rechargé depuis la source
indexer.vault_config.clear()
config = indexer.load_vault_config()
assert config["home-carol"]["path"] == str(home_env / "carol")
def test_unpersist_drops_registry_entry(self, home_env):
from backend import indexer
from backend.user_home import ensure_user_home
_create_user("dan")
_run(ensure_user_home("dan"))
indexer.unpersist_vault("home-dan")
assert "home-dan" not in _registry()["vaults"]
class TestRelease:
def test_release_closes_vault_but_keeps_folder(self, home_env):
from backend.indexer import index
from backend.user_home import ensure_user_home, release_user_home
_create_user("erin")
_run(ensure_user_home("erin"))
assert "home-erin" in index
_run(release_user_home("erin"))
assert "home-erin" not in index
assert (home_env / "erin").is_dir() # décision : dossier conservé
assert "home-erin" not in _registry()["vaults"]
class TestOrphanSweep:
def test_orphan_home_released_when_user_deleted_outside_route(self, home_env):
"""create_admin.py delete ne passe pas par la route → réparé au boot."""
from backend.auth.user_store import delete_user
from backend.indexer import index
from backend.user_home import ensure_all_user_homes, ensure_user_home
_create_user("hank")
_run(ensure_user_home("hank"))
delete_user("hank")
assert "home-hank" in index
_run(ensure_all_user_homes())
assert "home-hank" not in index
assert (home_env / "hank").is_dir()
def test_vault_named_home_outside_root_is_left_alone(self, home_env):
"""Un vault admin nommé « home-… » ailleurs dans le FS n'est pas touché."""
from backend.indexer import index
from backend.user_home import ensure_all_user_homes
other = home_env.parent / "Elsewhere"
other.mkdir()
_run(add_vault("home-keep", str(other)))
_run(ensure_all_user_homes())
assert "home-keep" in index
def add_vault(name: str, path: str):
from backend.indexer import add_vault_to_index
return add_vault_to_index(name, path)
class TestIsolation:
def test_user_cannot_access_another_home_vault(self, home_env):
from backend.auth.middleware import check_vault_access
from backend.auth.user_store import get_user
from backend.user_home import ensure_user_home
_create_user("alice")
_create_user("bob")
_run(ensure_user_home("alice"))
_run(ensure_user_home("bob"))
assert check_vault_access("home-alice", get_user("alice")) is True
assert check_vault_access("home-alice", get_user("bob")) is False
assert check_vault_access("home-bob", get_user("alice")) is False
class TestEnsureAll:
def test_repairs_every_existing_user(self, home_env):
from backend.auth.user_store import get_user
from backend.user_home import ensure_all_user_homes
_create_user("fab")
_create_user("gab")
# Dossier supprimé à la main + user créé hors API (pas de vault)
(home_env / "fab").mkdir()
(home_env / "fab").rmdir()
created = _run(ensure_all_user_homes())
assert created == 2
assert (home_env / "fab").is_dir()
assert (home_env / "gab").is_dir()
assert "home-fab" in get_user("fab")["vaults"]
assert "home-gab" in get_user("gab")["vaults"]
def test_home_root_env_contract():
"""OBSIGATE_HOME_ROOT absent → fonctionnalité inactive (dev/tests/desktop)."""
from backend.user_home import home_root
saved = os.environ.pop("OBSIGATE_HOME_ROOT", None)
try:
assert home_root() is None
finally:
if saved is not None:
os.environ["OBSIGATE_HOME_ROOT"] = saved