feat: ajouter les tags de session et les parametres par defaut de groupe
Tags : une session peut porter plusieurs tags libres (Session.tags), saisis comme une liste separee par des virgules dans l'editeur (normalises : espaces retires, doublons insensibles a la casse elimines), avec autocompletion sur les tags deja utilises. Affiches dans l'arbre des sessions, et filtrables via un champ dedie au-dessus de l'arbre (masque les sessions ne correspondant pas, sans toucher aux dossiers/terminaux locaux/hosts OpenSSH). Parametres de groupe : choix architectural deliberement plus simple qu'une resolution dynamique a la connexion. app/groups.py (GroupSettingsStore, meme pattern que les autres stores) associe un dossier (Session.group) a des valeurs par defaut (utilisateur, port, authentification, cle, bastion, keepalive). Ces valeurs ne s'appliquent qu'au moment de la creation d'une session, via un bouton explicite "↓ Heriter du groupe" dans SessionDialog - jamais de resolution automatique/retroactive. Ce choix evite d'introduire des valeurs sentinelles "herite vs. explicite" sur chaque champ (port=22 est a la fois une valeur par defaut ET une vraie valeur possible, par exemple) dans tout le reste du code (ConnectionController, SSHConnection...), pour une fonctionnalite que la priorisation du projet place parmi les secondaires. Une session reste ensuite un ensemble de valeurs normales, librement modifiables - "surcharger une valeur heritee" est donc trivialement satisfait. Clic droit sur un dossier dans le panneau lateral : "Nouvelle session dans ce dossier..." (pre-remplit le champ Dossier) et "Parametres du groupe..." (GroupSettingsDialog). tests/test_tags_and_groups.py : persistance des tags, CRUD GroupSettingsStore, normalisation de la saisie des tags, heritage applique/absent, filtrage dans l'arbre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
130
tests/test_tags_and_groups.py
Normal file
130
tests/test_tags_and_groups.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""Tags de session et parametres par defaut d'un groupe : persistance, saisie/
|
||||
normalisation des tags dans l'editeur, heritage explicite, filtrage par tag
|
||||
dans le panneau lateral."""
|
||||
import sys, pathlib
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QWidget
|
||||
|
||||
from app.session_manager import Session, SessionStore, AUTH_KEY
|
||||
from app.key_manager import KeyManager
|
||||
from app.groups import GroupSettings, GroupSettingsStore
|
||||
from app.dialogs.session_dialog import SessionDialog
|
||||
from app.controllers.broadcast_manager import BroadcastManager
|
||||
from app.controllers.session_controller import SessionController
|
||||
|
||||
app = QApplication.instance() or QApplication([])
|
||||
|
||||
tmp = Path(tempfile.mkdtemp())
|
||||
absent_ssh_config = tmp / "no-such-ssh-config"
|
||||
|
||||
print("=== 1. Tags : persistance via SessionStore ===")
|
||||
store = SessionStore(path=tmp / "sessions.json")
|
||||
session = Session(name="web1", host="10.0.0.1", username="root", tags=["prod", "docker"])
|
||||
store.add(session)
|
||||
reloaded = SessionStore(path=store.path)
|
||||
assert reloaded.get(session.id).tags == ["prod", "docker"]
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("=== 2. SessionStore.tags() : ensemble trie de tous les tags connus ===")
|
||||
store.add(Session(name="web2", host="10.0.0.2", username="root", tags=["prod", "linux"]))
|
||||
print(" tags:", store.tags())
|
||||
assert store.tags() == ["docker", "linux", "prod"]
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("=== 3. GroupSettingsStore : CRUD et persistance ===")
|
||||
gstore = GroupSettingsStore(path=tmp / "groups.json")
|
||||
settings = GroupSettings(path="Production/Web", username="deploy", port=2222)
|
||||
gstore.set(settings)
|
||||
assert gstore.get("Production/Web").username == "deploy"
|
||||
reloaded_gstore = GroupSettingsStore(path=gstore.path)
|
||||
assert reloaded_gstore.get("Production/Web").port == 2222
|
||||
settings.username = "admin"
|
||||
gstore.set(settings) # met a jour (meme path), ne duplique pas
|
||||
assert len(gstore.groups) == 1
|
||||
assert gstore.get("Production/Web").username == "admin"
|
||||
gstore.delete("Production/Web")
|
||||
assert gstore.get("Production/Web") is None
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("=== 4. SessionDialog : normalisation des tags saisis ===")
|
||||
key_manager = KeyManager(registry_path=tmp / "keys.json")
|
||||
dlg = SessionDialog(key_manager, session=Session(name="t", host="h", username="u"), session_store=store)
|
||||
dlg.tags_edit.setText(" Prod , docker,, prod , Docker ")
|
||||
dlg.host_edit.setText("h")
|
||||
dlg.user_edit.setText("u")
|
||||
dlg._on_accept()
|
||||
print(" tags normalises:", dlg.session.tags)
|
||||
# doublons retires en respectant la casse (et l'ordre) de la premiere occurrence
|
||||
assert dlg.session.tags == ["Prod", "docker"], dlg.session.tags
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("=== 5. SessionDialog : heriter du groupe applique les valeurs par defaut ===")
|
||||
gstore2 = GroupSettingsStore(path=tmp / "groups2.json")
|
||||
gstore2.set(GroupSettings(path="Homelab", username="admin", port=2200, auth_type=AUTH_KEY))
|
||||
dlg2 = SessionDialog(key_manager, session=Session(name="t2", host="h", group="Homelab"),
|
||||
session_store=store, group_store=gstore2)
|
||||
dlg2._apply_group_defaults()
|
||||
print(" utilisateur/port apres heritage:", dlg2.user_edit.text(), dlg2.port_spin.value())
|
||||
assert dlg2.user_edit.text() == "admin"
|
||||
assert dlg2.port_spin.value() == 2200
|
||||
assert dlg2.auth_combo.currentData() == AUTH_KEY
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("=== 6. SessionDialog : heriter d'un groupe sans parametres -> message, pas de crash ===")
|
||||
dlg3 = SessionDialog(key_manager, session=Session(name="t3", host="h", group="Inconnu"),
|
||||
session_store=store, group_store=gstore2)
|
||||
with patch("app.dialogs.session_dialog.QMessageBox.information") as info:
|
||||
dlg3._apply_group_defaults()
|
||||
assert info.called
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("=== 7. Filtrage par tag dans le panneau lateral (masque les sessions non concernees) ===")
|
||||
store3 = SessionStore(path=tmp / "sessions3.json")
|
||||
store3.add(Session(name="avec-tag", host="10.0.0.9", username="root", tags=["special"]))
|
||||
store3.add(Session(name="sans-tag", host="10.0.0.10", username="root"))
|
||||
host_widget = QWidget()
|
||||
broadcast = BroadcastManager(lambda: [host_widget])
|
||||
sc = SessionController(store3, key_manager, broadcast.button, parent_widget=host_widget,
|
||||
openssh_config_path=absent_ssh_config)
|
||||
|
||||
|
||||
def find_item(name):
|
||||
def visit(item):
|
||||
if item.text(0).strip().startswith(f"🖥 {name}"):
|
||||
return item
|
||||
for i in range(item.childCount()):
|
||||
found = visit(item.child(i))
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
for i in range(sc.tree.topLevelItemCount()):
|
||||
found = visit(sc.tree.topLevelItem(i))
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
sc.tag_filter_combo.setCurrentText("special")
|
||||
tagged_item = find_item("avec-tag")
|
||||
untagged_item = find_item("sans-tag")
|
||||
print(" 'avec-tag' masque:", tagged_item.isHidden(), "| 'sans-tag' masque:", untagged_item.isHidden())
|
||||
assert not tagged_item.isHidden()
|
||||
assert untagged_item.isHidden()
|
||||
|
||||
sc.tag_filter_combo.setCurrentText("")
|
||||
assert not find_item("sans-tag").isHidden()
|
||||
print(" OK")
|
||||
|
||||
print()
|
||||
print("TAGS ET GROUPES : TOUS LES TESTS OK")
|
||||
Reference in New Issue
Block a user