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>
80 lines
3.0 KiB
Python
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"
|