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:
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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([])
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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([])
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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([])
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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([])
|
||||
|
||||
|
||||
@@ -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([])
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user