Files
anonymisation/tests/unit/test_gui_v6_profile_editor.py
Domi31tls 72841ed7b3 feat(gui): onglet Profils éditable (création/modification/persistance)
Retour Dom : remplacer la page vitrine par un vrai éditeur de profils.

- gui_v6/profile_editor.py : couche logique (build_profile_spec,
  profile_is_editable runtime vs defaut, list_profile_choices, slug_for_copy,
  save/set_default/delete) au-dessus de profile_defaults — persistance dans
  config/profiles.yml.
- gui_v6/editable_list.py : EditableTermList (tableau scrollable de termes,
  ajout/suppression, pas de pastilles) — reste lisible à 50+ termes.
- tab_config : sous-onglet « 👤 Profils » réintroduit comme éditeur — menu
  déroulant « Profil à modifier », boutons Nouveau / Dupliquer / Enregistrer /
  Annuler / Définir par défaut, sections Identité, Masquage (require_manual_mask,
  template), Moteurs (force_disable_vlm), Mots (à masquer/conserver/ignorer
  éditables), Règles « à venir ». Profils défaut = lecture seule (dupliquer
  pour modifier). Confirmation non bloquante (pas de modale).
- Réglages : bouton « ✏️ Modifier le profil… » → ouvre Profils sur le profil
  actif. Pas de pastilles inline.

Persiste : label, description, require_manual_mask, force_disable_vlm,
preferred_manual_mask_template, param_lists (3 listes). 260 tests unit OK
(0 régression), self-test OK, nav 5 sous-onglets + thème OK. Préserve
1bbe70a/d30f7b7. Aucun build/push sans GO Dom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 23:09:01 +02:00

80 lines
3.0 KiB
Python

"""Couche logique de l'éditeur de profils (persistance via profile_defaults).
Tests sans display, avec un fichier profiles.yml temporaire.
"""
from __future__ import annotations
from gui_v6.profile_editor import (
build_profile_spec,
list_profile_choices,
profile_is_editable,
save_profile,
slug_for_copy,
)
def test_build_profile_spec_structure_and_strip():
spec = build_profile_spec(
label=" Mon profil ",
description="desc",
require_manual_mask=True,
force_disable_vlm=False,
preferred_manual_mask_template="config/mask_templates/x.json",
whitelist=[" garder ", "", "garder2"],
blacklist=["CHUXX"],
stopwords=[],
)
assert spec["label"] == "Mon profil"
assert spec["require_manual_mask"] is True
assert spec["force_disable_vlm"] is False
assert spec["preferred_manual_mask_template"] == "config/mask_templates/x.json"
assert spec["param_lists"]["whitelist_phrases"] == ["garder", "garder2"] # strip + vides retirés
assert spec["param_lists"]["blacklist_force_mask_terms"] == ["CHUXX"]
assert spec["param_lists"]["additional_stopwords"] == []
def test_save_and_reload_roundtrip(tmp_path):
profiles = tmp_path / "profiles.yml"
spec = build_profile_spec(
label="Test runtime", description="d", require_manual_mask=True,
force_disable_vlm=True, preferred_manual_mask_template="",
whitelist=["a"], blacklist=["b", "c"], stopwords=["d"],
)
save_profile("mon_profil", spec, path=profiles)
from profile_defaults import list_effective_profiles
effective = list_effective_profiles(profiles)
assert "mon_profil" in effective
saved = effective["mon_profil"]
assert saved["label"] == "Test runtime"
assert saved["require_manual_mask"] is True
assert saved["force_disable_vlm"] is True
assert saved["param_lists"]["blacklist_force_mask_terms"] == ["b", "c"]
def test_profile_is_editable_runtime_vs_default(tmp_path):
profiles = tmp_path / "profiles.yml"
save_profile("runtime_one", build_profile_spec(label="R1"), path=profiles)
assert profile_is_editable("runtime_one", path=profiles) is True
# un profil par défaut (non présent dans l'overlay runtime) n'est pas éditable
assert profile_is_editable("standard_local", path=profiles) is False
def test_list_profile_choices_marks_editable(tmp_path):
profiles = tmp_path / "profiles.yml"
save_profile("runtime_one", build_profile_spec(label="R1"), path=profiles)
choices = list_profile_choices(path=profiles)
by_key = {c["key"]: c for c in choices}
assert by_key["runtime_one"]["editable"] is True
assert by_key["runtime_one"]["label"] == "R1"
# un profil défaut présent et non éditable
assert "standard_local" in by_key
assert by_key["standard_local"]["editable"] is False
def test_slug_for_copy_avoids_collision():
assert slug_for_copy("std", set()) == "std_copie"
assert slug_for_copy("std", {"std_copie"}) == "std_copie_2"
assert slug_for_copy("std", {"std_copie", "std_copie_2"}) == "std_copie_3"