Qualite : pyproject.toml (ruff/pytest/pyright) + CI Gitea (lint/tests/build)

pyproject.toml devient le point d'entree unique de configuration des outils
(pytest.ini supprime, sa config y est deplacee). Les dependances restent dans
requirements.txt/requirements-dev.txt (deja utilises par setup.bat) plutot que
dupliquees ici : ce projet se distribue par PyInstaller, pas par "pip install".

ruff : configure puis passe sur tout app/ + tests/ (154 corrections
automatiques, purement mecaniques - tri des imports, imports multiples sur une
ligne separes, alias depreciees) suivies de 7 corrections manuelles ciblees :
  - app/ssh_connection.py : `raise ... from None` pour le refus explicite de
    passphrase (ce n'est pas un echec a diagnostiquer).
  - app/controllers/workspace_manager.py : zip() sans strict= documente en
    commentaire (deliberement tolerant a un workspace legerement perime).
  - tests/test_ai_redaction.py : nom de variable ambigu renomme.
  - E702 (construction compacte "widget = X(); widget.setY(...)" dans
    sftp_panel.py/tunnel_dialog.py) et E402 (quelques tests qui importent un
    symbole juste avant la section qui l'utilise) : ignores explicitement, ce
    sont des styles deliberes preexistants, pas des oublis.
`ruff check app main.py tests` est desormais propre.

pyright : mode "basic" (le code n'a jamais ete type). Plus de 200 signalements
sur ~300 etaient le meme faux positif connu de l'ecosysteme PySide6 (acces
plat aux enums Qt, ex. Qt.UserRole, valide a l'execution mais que les stubs ne
reconnaissent que sous leur forme imbriquee) : desactive avec justification en
commentaire. Reste un socle de 77 signalements reels (surtout des acces
Optional non filtres dans app/ui/tabs.py et app/ssh_connection.py) : non
corrige dans cette phase - resserrer le typage la ou pyright a raison est un
travail a part, pas une consequence automatique de l'ajout de l'outil. La CI
le lance en mode informatif (continue-on-error) tant que ce socle n'est pas
resorbe.

.gitea/workflows/ci.yml : lint (ruff, bloquant), tests (pytest, bloquant),
typecheck (pyright, informatif), build (PyInstaller, sur main uniquement,
artefact du dossier portable). Necessite un runner Gitea Actions Windows
enregistre - PySide6/pywinpty/Credential Manager/ACL NTFS sont Windows
uniquement, comme l'application elle-meme ; non verifiable depuis ce poste.

Suite complete revalidee apres coup (33/33) pour confirmer qu'aucune des 154
corrections automatiques n'a modifie de comportement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Florian
2026-09-05 07:46:50 +02:00
parent 09b7ed092c
commit c39ef9c038
72 changed files with 697 additions and 268 deletions

View File

@@ -1,7 +1,9 @@
"""AICommandDialog : extraction de la commande depuis la reponse de l'IA
(bloc de code s'il y en a un), et les 3 actions possibles (Copier / Inserer
dans le terminal / Annuler) - jamais d'execution automatique."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from PySide6.QtWidgets import QApplication

View File

@@ -1,9 +1,11 @@
"""Detection locale de commandes potentiellement destructives proposees par
l'assistant IA (jamais un blocage, seulement un avertissement)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from app.ai.danger import is_dangerous, check_dangerous
from app.ai.danger import check_dangerous, is_dangerous
print("=== 1. Commandes destructrices detectees ===")
dangerous_examples = [

View File

@@ -2,7 +2,9 @@
la verification de securite (avis + secrets) est bien invoquee avant tout
envoi, que les reponses/erreurs mettent a jour la conversation, et qu'une
reponse perimee (generation obsolete) est ignoree."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from unittest.mock import patch
@@ -10,9 +12,9 @@ from unittest.mock import patch
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
from app.ai.provider import AIProvider, AIError
from app.ai.service import KIND_EXPLAIN, KIND_ASK
from app.dialogs.ai_prompts import MASK, SEND, CANCEL
from app.ai.provider import AIError, AIProvider
from app.ai.service import KIND_EXPLAIN
from app.dialogs.ai_prompts import CANCEL, MASK, SEND
from app.ui.ai_panel import AIPanel
app = QApplication.instance() or QApplication(sys.argv)

View File

@@ -3,7 +3,9 @@ affiche une seule fois (persist dans app_settings, redirige vers un fichier
temporaire), et verification des secrets a chaque envoi. QMessageBox est
simule (exec()/clickedButton() patches) : aucune boite modale reelle ne doit
bloquer les tests."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
@@ -13,7 +15,13 @@ from unittest.mock import patch
from PySide6.QtWidgets import QApplication, QMessageBox
import app.app_settings as app_settings
from app.dialogs.ai_prompts import ensure_ai_notice_acknowledged, confirm_sensitive_content, MASK, SEND, CANCEL
from app.dialogs.ai_prompts import (
CANCEL,
MASK,
SEND,
confirm_sensitive_content,
ensure_ai_notice_acknowledged,
)
app = QApplication.instance() or QApplication(sys.argv)

View File

@@ -1,15 +1,17 @@
"""OpenAICompatibleProvider : requete HTTP construite correctement, et gestion
des erreurs (401, 429, timeout, reseau, reponse invalide) - urllib.request.urlopen
est entierement mocke, aucun appel reseau reel."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import io
import json
import urllib.error
from unittest.mock import patch, MagicMock
from unittest.mock import MagicMock, patch
from app.ai.provider import AIProvider, AIError, OpenAICompatibleProvider
from app.ai.provider import AIError, AIProvider, OpenAICompatibleProvider
def fake_response(payload: dict):

View File

@@ -1,5 +1,7 @@
"""Detection/redaction de secrets avant envoi a l'assistant IA."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from app.ai.redaction import detect_secrets, redact
@@ -16,8 +18,8 @@ print("=== 2. Jeton Bearer detecte ===")
found = detect_secrets("Authorization: Bearer sk-abcdefghijklmnopqrstuvwxyz123456")
labels = [f.label for f in found]
print(" detecte:", labels)
assert any("bearer" in l.lower() for l in labels)
assert any("authorization" in l.lower() for l in labels)
assert any("bearer" in label.lower() for label in labels)
assert any("authorization" in label.lower() for label in labels)
print(" OK")
print()

View File

@@ -1,7 +1,9 @@
"""AIRequestWorker : execution d'une requete IA dans un vrai QThread en
arriere-plan, jamais sur le thread Qt principal - avec dispatch correct selon
le type de requete et remontee des erreurs via signal plutot qu'exception."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import threading
@@ -9,9 +11,15 @@ import threading
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
from app.ai.provider import AIProvider, AIError
from app.ai.provider import AIError, AIProvider
from app.ai.service import (
AIRequestWorker, KIND_EXPLAIN, KIND_DIAGNOSE, KIND_SOLUTION, KIND_GENERATE, KIND_ASK, KIND_TEST,
KIND_ASK,
KIND_DIAGNOSE,
KIND_EXPLAIN,
KIND_GENERATE,
KIND_SOLUTION,
KIND_TEST,
AIRequestWorker,
)
app = QApplication.instance() or QApplication(sys.argv)

View File

@@ -2,20 +2,28 @@
app_settings (redirige vers un fichier temporaire pour ne jamais toucher les
vrais reglages), cle API exclusivement via CredentialStore, et construction du
fournisseur a partir des deux."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
from pathlib import Path
from unittest.mock import patch
from app.credentials import CredentialStore
from app.ai.settings import (
AISettings, load_ai_settings, save_ai_settings, load_api_key, save_api_key,
build_provider_from_settings, AI_API_KEY_CREDENTIAL, DEFAULT_BASE_URL,
)
from app.ai.provider import OpenAICompatibleProvider
import app.app_settings as app_settings
from app.ai.provider import OpenAICompatibleProvider
from app.ai.settings import (
AI_API_KEY_CREDENTIAL,
DEFAULT_BASE_URL,
AISettings,
build_provider_from_settings,
load_ai_settings,
load_api_key,
save_ai_settings,
save_api_key,
)
from app.credentials import CredentialStore
class FakeCredentialStore(CredentialStore):

View File

@@ -5,7 +5,9 @@ automatiquement, avec avertissement si dangereuse).
LocalTerminalConnection.start() est neutralisee comme dans test_snippets.py :
aucun sous-processus reel, send() depose seulement les octets dans une file."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from unittest.mock import patch
@@ -13,9 +15,9 @@ from unittest.mock import patch
from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QApplication
from app.local_terminal import LocalTerminalConnection
from app.ai.provider import AIProvider
from app.dialogs.ai_prompts import SEND
from app.local_terminal import LocalTerminalConnection
from app.ui.ai_panel import AIPanel
from app.ui.tabs import LocalTerminalTabWidget

View File

@@ -1,16 +1,18 @@
"""Stockage JSON : ecriture atomique, schema_version, migration, recuperation
automatique depuis .bak quand le fichier principal est corrompu."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import json
import tempfile
from pathlib import Path
from app.key_manager import KeyManager, compute_fingerprint
from app.session_manager import Session, SessionStore
from app.storage.atomic_file import atomic_write_text
from app.storage.json_store import load_json_document, save_json_document
from app.session_manager import SessionStore, Session
from app.key_manager import KeyManager, compute_fingerprint
tmp = Path(tempfile.mkdtemp())

View File

@@ -9,7 +9,9 @@ de programmation du cote de l'onglet, qui n'a besoin d'aucune connexion reseau
reelle - cela evite aussi qu'un thread reste bloque dans un connect() lent et
ne bloque la fin du script.
"""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import time

View File

@@ -1,7 +1,9 @@
"""ProxyJump / Bastion : resolution (app.bastion) pour les 3 sources (session,
OpenSSH, manuel), et etablissement du canal direct-tcpip par SSHConnection via
un bastion entierement mocke (aucun reseau reel)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
@@ -10,12 +12,18 @@ from unittest.mock import MagicMock, patch
from PySide6.QtWidgets import QApplication
from app.bastion import BastionResolutionError, BastionTarget, resolve_bastion_target
from app.session_manager import (
SessionStore, Session, BastionConfig,
AUTH_AUTO, AUTH_KEY,
BASTION_NONE, BASTION_SESSION, BASTION_OPENSSH, BASTION_MANUAL,
AUTH_AUTO,
AUTH_KEY,
BASTION_MANUAL,
BASTION_NONE,
BASTION_OPENSSH,
BASTION_SESSION,
BastionConfig,
Session,
SessionStore,
)
from app.bastion import resolve_bastion_target, BastionResolutionError, BastionTarget
app = QApplication.instance() or QApplication([])

View File

@@ -1,9 +1,11 @@
"""Copier/coller du terminal : selection, presse-papiers, raccourcis."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QEvent, QPoint, Qt
from PySide6.QtGui import QKeyEvent
from PySide6.QtCore import QEvent, Qt, QPoint
from PySide6.QtWidgets import QApplication
from app.terminal_widget import TerminalWidget
@@ -90,8 +92,8 @@ print(" OK")
print()
print("=== 9. Souris : selection = copie, clic droit = collage (comme sous Unix) ===")
from PySide6.QtGui import QMouseEvent, QContextMenuEvent
from PySide6.QtCore import QPointF
from PySide6.QtGui import QContextMenuEvent, QMouseEvent
def clic(bouton, col, row, type_evt=QEvent.MouseButtonPress):

View File

@@ -4,7 +4,9 @@ passe (ConnectionController, bastion, suppression de session).
SSHConnection.start() est neutralisee pour les tests d'integration qui
construisent un onglet reel (aucune connexion reseau necessaire)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import json
@@ -12,18 +14,21 @@ import tempfile
from pathlib import Path
from unittest.mock import patch
from PySide6.QtWidgets import QApplication, QWidget, QMessageBox
from PySide6.QtWidgets import QApplication, QMessageBox, QWidget
from app.credentials import (
CredentialStore, NullCredentialStore, WindowsCredentialStore, session_credential_key,
default_credential_store,
)
from app.session_manager import Session, SessionStore, AUTH_PASSWORD, PASSWORD_STORAGE_SECURE
from app.ssh_connection import SSHConnection
from app.bastion import BastionTarget
from app.controllers.broadcast_manager import BroadcastManager
from app.controllers.connection_controller import ConnectionController
from app.controllers.session_controller import SessionController
from app.bastion import BastionTarget
from app.credentials import (
CredentialStore,
NullCredentialStore,
WindowsCredentialStore,
default_credential_store,
session_credential_key,
)
from app.session_manager import AUTH_PASSWORD, PASSWORD_STORAGE_SECURE, Session, SessionStore
from app.ssh_connection import SSHConnection
app = QApplication.instance() or QApplication([])
@@ -103,6 +108,7 @@ session_store = SessionStore(path=tmp / "sessions.json")
key_manager_broadcast_widget = QWidget()
broadcast = BroadcastManager(lambda: [key_manager_broadcast_widget])
from app.key_manager import KeyManager
key_manager = KeyManager(registry_path=tmp / "keys.json")
fake_store = FakeCredentialStore()
cc = ConnectionController(session_store, key_manager, broadcast, credential_store=fake_store,

View File

@@ -1,4 +1,6 @@
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from app.main_window import detect_ssh_target

View File

@@ -1,6 +1,9 @@
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from PySide6.QtWidgets import QApplication
from app.main_window import MainWindow
from app.theme import apply_theme, build_app_icon

View File

@@ -1,5 +1,7 @@
"""Recherche floue (app.services.fuzzy) : sous-sequence, score, tri."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from app.services.fuzzy import fuzzy_score

View File

@@ -1,7 +1,9 @@
"""Keepalive de SSHConnection : verifie l'envoi periodique et l'abandon apres
`keepalive_count_max` echecs consecutifs, avec un transport paramiko mocke
(aucune connexion reseau reelle)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import threading

View File

@@ -2,7 +2,9 @@
(sans dependre des ACL reelles du systeme de fichiers, pour rester fiable quelle
que soit la machine qui execute le test) et le bon aiguillage vers le repli chmod
ailleurs que sous Windows."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import os
@@ -11,7 +13,7 @@ import tempfile
from pathlib import Path
from unittest.mock import patch
from app.security.key_protection import protect_private_key, _protect_windows
from app.security.key_protection import _protect_windows, protect_private_key
tmp = Path(tempfile.mkdtemp())
key_file = tmp / "id_test"

View File

@@ -1,6 +1,8 @@
"""Generation de cles SSH : tous les types proposes doivent produire une cle
relisible par paramiko, donc utilisable pour se connecter."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
@@ -9,7 +11,11 @@ from pathlib import Path
import paramiko
from app.key_manager import (
KeyManager, KEY_TYPE_LABELS, KEY_TYPE_RSA, KEY_TYPE_ED25519, ECDSA_CURVES,
ECDSA_CURVES,
KEY_TYPE_ED25519,
KEY_TYPE_LABELS,
KEY_TYPE_RSA,
KeyManager,
)
tmp = Path(tempfile.mkdtemp())

View File

@@ -4,14 +4,16 @@ sont les seuls endroits, en dehors des fichiers de stockage deja couverts par
test_credentials.py/test_ai_settings.py, ou un secret pourrait fuiter sans
etre ecrit intentionnellement (une boite de dialogue d'erreur, une trace
affichee a l'utilisateur ou capturee dans un rapport de bug)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import io
import urllib.error
from unittest.mock import patch, MagicMock
from unittest.mock import MagicMock, patch
from app.ai.provider import OpenAICompatibleProvider, AIError
from app.ai.provider import AIError, OpenAICompatibleProvider
SECRET_API_KEY = "sk-tres-secret-ne-jamais-fuiter-1234567890"

View File

@@ -1,5 +1,7 @@
"""Lecture de ~/.ssh/config : hosts detectes, champs minimum, conversion en Session."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile

View File

@@ -5,29 +5,31 @@ activation.
SSHConnection.start()/LocalTerminalConnection.start() sont neutralisees, et
~/.ssh/config est redirige vers un fichier absent (pas de dependance au poste
qui execute le test)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
from pathlib import Path
from unittest.mock import patch
from PySide6.QtCore import Qt, QEvent
from PySide6.QtCore import QEvent, Qt
from PySide6.QtGui import QKeyEvent
from PySide6.QtWidgets import QApplication, QWidget
from app.session_manager import Session, SessionStore
from app.key_manager import KeyManager
from app.ssh_connection import SSHConnection
from app.local_terminal import LocalTerminalConnection
import app.app_settings as app_settings
from app.controllers.broadcast_manager import BroadcastManager
from app.controllers.connection_controller import ConnectionController
from app.controllers.session_controller import SessionController
from app.controllers.workspace_manager import WorkspaceManager
from app.workspace import WorkspaceStore
from app.snippets import Snippet, SnippetStore
from app.dialogs.quick_connect_dialog import QuickConnectDialog
import app.app_settings as app_settings
from app.key_manager import KeyManager
from app.local_terminal import LocalTerminalConnection
from app.session_manager import Session, SessionStore
from app.snippets import Snippet, SnippetStore
from app.ssh_connection import SSHConnection
from app.workspace import WorkspaceStore
app = QApplication.instance() or QApplication([])
@@ -118,6 +120,7 @@ dialog2._activate_current()
print(f" onglets avant/apres activation: {before}/{cc2.tabs.count()}")
assert cc2.tabs.count() == before + 1
from app.ui.tabs import SessionTabWidget
assert isinstance(cc2.tabs.widget(cc2.tabs.count() - 1), SessionTabWidget)
print(" OK")

View File

@@ -1,5 +1,7 @@
"""Calcul du delai de reconnexion automatique (backoff exponentiel plafonne)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from app.services.reconnect import compute_backoff_delay

View File

@@ -1,6 +1,8 @@
"""Editeur de session : section Bastion / Jump Host (construction, pre-remplissage
a l'edition, et resultat renvoye pour chacun des 4 modes)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
@@ -9,12 +11,18 @@ from unittest.mock import patch
from PySide6.QtWidgets import QApplication
from app.session_manager import (
SessionStore, Session, BastionConfig,
BASTION_NONE, BASTION_SESSION, BASTION_OPENSSH, BASTION_MANUAL, AUTH_KEY,
)
from app.key_manager import KeyManager
from app.dialogs.session_dialog import SessionDialog
from app.key_manager import KeyManager
from app.session_manager import (
AUTH_KEY,
BASTION_MANUAL,
BASTION_NONE,
BASTION_OPENSSH,
BASTION_SESSION,
BastionConfig,
Session,
SessionStore,
)
app = QApplication.instance() or QApplication([])

View File

@@ -1,8 +1,9 @@
"""Test de la logique SFTP avec un client SFTP simule (adosse a un dossier temporaire)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import os
import posixpath
import shutil
import stat as statmod
import tempfile
@@ -10,9 +11,8 @@ import time
from pathlib import Path
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import Qt
from app.sftp_panel import SFTPPanel, TransferJob, NAME_ROLE, ISDIR_ROLE, PARENT_ROLE
from app.sftp_panel import NAME_ROLE, PARENT_ROLE, SFTPPanel
class FakeAttr:

View File

@@ -4,18 +4,20 @@
LocalTerminalConnection.start() est neutralisee : send() ne fait que deposer
les octets dans une file interne (aucun sous-processus reel), ce qui suffit a
verifier ce qui a ete envoye sans avoir besoin d'un vrai terminal."""
import sys, pathlib
import pathlib
import sys
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 PySide6.QtWidgets import QApplication
from app.snippets import Snippet, SnippetStore, DEFAULT_SNIPPETS
from app.local_terminal import LocalTerminalConnection
from app.controllers.broadcast_manager import BroadcastManager
from app.local_terminal import LocalTerminalConnection
from app.snippets import DEFAULT_SNIPPETS, Snippet, SnippetStore
from app.ui.tabs import LocalTerminalTabWidget
app = QApplication.instance() or QApplication([])

View File

@@ -2,14 +2,16 @@
correctement la destination demandee par le client puis relaie le trafic a
travers un (faux) transport SSH -- sans connexion SSH reelle, comme le fait
deja test_tunnels.py pour la redirection locale."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import socket
import threading
import time
from app.tunnels import TunnelManager, DIRECTION_DYNAMIC
from app.tunnels import DIRECTION_DYNAMIC, TunnelManager
class EchoServer(threading.Thread):

View File

@@ -5,7 +5,9 @@ fermeture globale d'un onglet divise.
SSHConnection.start() et LocalTerminalConnection.start() sont neutralisees :
ce module teste l'agencement des panneaux et le cycle de vie des onglets, pas
une vraie connexion (deja couverte par test_keepalive.py / test_bastion.py)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import itertools
@@ -15,14 +17,13 @@ from unittest.mock import patch
from PySide6.QtWidgets import QApplication, QWidget
from app.session_manager import SessionStore
from app.key_manager import KeyManager
from app.ssh_connection import SSHConnection
from app.local_terminal import LocalTerminalConnection
from app.controllers.broadcast_manager import BroadcastManager
from app.controllers.connection_controller import ConnectionController
from app.ui.tabs import LocalTerminalTabWidget
from app.ui.split_pane import SplitContainer, LAYOUT_2V, LAYOUT_4
from app.key_manager import KeyManager
from app.local_terminal import LocalTerminalConnection
from app.session_manager import SessionStore
from app.ssh_connection import SSHConnection
from app.ui.split_pane import LAYOUT_2V, LAYOUT_4, SplitContainer
app = QApplication.instance() or QApplication([])

View File

@@ -1,7 +1,9 @@
"""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
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import tempfile
@@ -10,12 +12,12 @@ 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
from app.dialogs.session_dialog import SessionDialog
from app.groups import GroupSettings, GroupSettingsStore
from app.key_manager import KeyManager
from app.session_manager import AUTH_KEY, Session, SessionStore
app = QApplication.instance() or QApplication([])

View File

@@ -1,10 +1,12 @@
"""Confort du terminal : zoom de police, barre de defilement, recherche dans l'historique."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QKeyEvent
from PySide6.QtCore import QEvent, Qt
from PySide6.QtGui import QKeyEvent
from PySide6.QtWidgets import QApplication
from app.terminal_widget import TerminalWidget

View File

@@ -4,14 +4,16 @@ Le test monte un vrai serveur TCP local et un faux transport SSH dont le canal
'direct-tcpip' est un simple socket vers ce serveur : cela valide toute la
mecanique de relais sans avoir besoin d'un serveur SSH.
"""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import socket
import threading
import time
from app.tunnels import TunnelManager, DIRECTION_LOCAL, describe
from app.tunnels import DIRECTION_LOCAL, TunnelManager, describe
class EchoServer(threading.Thread):

View File

@@ -6,7 +6,9 @@ SSHConnection.start()/LocalTerminalConnection.start() sont neutralisees (pas de
connexion reelle necessaire). app.app_settings.SETTINGS_FILE est redirige vers
un fichier temporaire pour ne jamais toucher les vrais reglages de l'utilisateur
(restore() met a jour last_workspace_id a chaque restauration)."""
import sys, pathlib
import pathlib
import sys
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent))
import itertools
@@ -16,18 +18,20 @@ from unittest.mock import patch
from PySide6.QtWidgets import QApplication, QWidget
from app.session_manager import Session, SessionStore
from app.key_manager import KeyManager
from app.ssh_connection import SSHConnection
from app.local_terminal import LocalTerminalConnection
import app.app_settings as app_settings
from app.controllers.broadcast_manager import BroadcastManager
from app.controllers.connection_controller import ConnectionController
from app.controllers.workspace_manager import (
WorkspaceManager, SETTING_RESTORE_ON_STARTUP, SETTING_LAST_WORKSPACE_ID,
SETTING_LAST_WORKSPACE_ID,
SETTING_RESTORE_ON_STARTUP,
WorkspaceManager,
)
from app.workspace import WorkspaceStore, ITEM_SESSION_SSH, ITEM_LOCAL_SHELL
from app.key_manager import KeyManager
from app.local_terminal import LocalTerminalConnection
from app.session_manager import Session, SessionStore
from app.ssh_connection import SSHConnection
from app.ui.split_pane import LAYOUT_2V
import app.app_settings as app_settings
from app.workspace import ITEM_LOCAL_SHELL, ITEM_SESSION_SSH, WorkspaceStore
app = QApplication.instance() or QApplication([])
@@ -95,7 +99,8 @@ print(f" onglets ouverts: {cc2.tabs.count()}, elements ignores: {skipped}")
assert cc2.tabs.count() == 2
assert skipped == 0
from app.ui.split_pane import SplitContainer
from app.ui.tabs import SessionTabWidget, LocalTerminalTabWidget
from app.ui.tabs import LocalTerminalTabWidget, SessionTabWidget
assert isinstance(cc2.tabs.widget(0), LocalTerminalTabWidget)
container = cc2.tabs.widget(1)
assert isinstance(container, SplitContainer)