Le bouton "Tester la connexion" est la première interaction que la plupart des utilisateurs ont avec QoreDB. Il paraît trivial : on entre un host, un port, des credentials, on clique, et une pastille verte ou rouge apparaît. En pratique, ce bouton déclenche une chaîne d'appels traversant la couche Tauri, le SessionManager, des garde-fous de sécurité, éventuellement un tunnel SSH, et le driver cible. Le résultat doit revenir vite, sans laisser d'état derrière lui.
Cet article détaille comment ce test est câblé et pourquoi il diverge volontairement d'une connexion normale sur plusieurs points.
Un dry-run par conception
La commande Tauri test_connection reçoit un ConnectionConfig et le passe à SessionManager::test_connection. Ce dernier construit une future qui, selon la topologie, ouvre un tunnel puis délègue au driver. La méthode test_connection du driver ne renvoie qu'un EngineResult<()> : pas de SessionId, pas d'entrée dans la HashMap sessions, pas de pool conservé. Le connecteur ouvert pour le test est refermé immédiatement.
Concrètement, pour les drivers SQLx, on crée un pool minimal (1 connexion max, 0 en idle, timeout 10 secondes) et on appelle pool.close() juste après. Pour SQL Server, on ouvre un client Tiberius brut et on exécute un SELECT 1 avant de laisser le drop se produire. Pour MongoDB, on instancie un client et on envoie un ping. Aucun de ces chemins ne passe par la logique de connect() qui, elle, dimensionne le pool selon la configuration utilisateur et publie une session dans le manager.
Séparer explicitement les deux chemins évite un piège classique des clients : cliquer sur "tester" et se retrouver avec une session fantôme dans le manager, comptée dans les métriques, monitorée par le keep-alive, et jamais fermée par l'utilisateur.
Un timeout global qui protège l'UI
Le test entier est enveloppé dans un tokio::time::timeout de 10 secondes (TEST_TIMEOUT_MS). Ce plafond englobe tout : ouverture d'un tunnel SSH ou d'un proxy si nécessaire, résolution DNS, handshake TLS, authentification, requête de vérification. Si l'ensemble dépasse dix secondes, la future est abandonnée et la couche session renvoie une EngineError::Timeout avec la valeur du délai.
Le choix de 10 secondes vient d'un arbitrage simple. Un utilisateur qui teste une connexion attend un feedback quasi immédiat. Passé ce délai, il va cliquer plusieurs fois, ouvrir d'autres onglets, ou supposer que l'app est figée. Une valeur plus longue rendrait certains cas légitimes (VPN lent, base à froid) tolérables mais dégraderait l'expérience générale. Une valeur plus courte casserait les cas légitimes. Dix secondes est le compromis retenu.
Ce timeout est distinct de CONNECT_TIMEOUT_MS (15 secondes), utilisé par connect, et de PING_TIMEOUT_MS (5 secondes), utilisé par le health check. Chaque opération a son propre horizon, dimensionné pour son usage.
Les garde-fous appliqués avant le test
Un test de connexion n'est pas une zone de non-droit : il applique les mêmes règles qu'une connexion réelle. Deux vérifications tournent avant de toucher le driver.
La première, enforce_ssh_host_key_policy, rejette la politique StrictHostKeyChecking=no lorsque l'environnement est marqué comme production. Il n'y a aucune raison légitime de désactiver la vérification de host key en prod, et laisser un utilisateur "juste tester" avec cette configuration reviendrait à normaliser une erreur.
La seconde, enforce_supported_topology, refuse la combinaison proxy réseau plus tunnel SSH sur la même connexion. Cette combinaison n'est pas supportée pour une raison technique : le ssh -L résout son adresse cible côté bastion, donc empiler les deux revient à sortir sur la loopback du bastion, ce qui n'est presque jamais ce que veut l'utilisateur. Signaler l'erreur au test, avant même que le driver ne tente quoi que ce soit, évite un message d'échec trompeur plus tard.
Traiter proprement les tunnels éphémères
Si la configuration inclut un proxy ou un tunnel SSH, le test ouvre ce tunnel pour la durée du test, réécrit host et port sur 127.0.0.1 et le port local alloué, appelle le driver, puis referme le tunnel dans tous les cas, y compris en erreur. Le pattern est le même pour les deux : ouverture, test, fermeture explicite.
Ce cycle court sert plusieurs objectifs. Il vérifie que le tunnel s'ouvre et que le bastion est joignable. Il vérifie que la cible finale répond à travers le tunnel. Il ne consomme aucun port local persistant. Il n'accroche aucun process ssh en arrière-plan si l'utilisateur ferme la fenêtre entre-temps. Le test est atomique : il tient ou il tombe, il ne laisse rien.
Un contrat unique, des implémentations spécifiques
Le trait DataEngine impose à chaque driver une méthode test_connection(&self, config: &ConnectionConfig) -> EngineResult<()>. La signature est identique partout, l'implémentation reflète les contraintes de chaque moteur.
PostgreSQL, CockroachDB, Neon, Supabase, TimescaleDB et MotherDuck partagent pg_compat::test_connection : la construction d'un pool SQLx avec une seule connexion, puis sa fermeture. MySQL suit exactement le même schéma via son propre create_pool. SQLite ajoute une étape en amont : validate_path vérifie que le chemin est un fichier accessible avant de créer le pool, parce qu'un chemin invalide n'a pas besoin d'un aller-retour asynchrone pour être détecté.
SQL Server ne passe pas par un pool bb8 pour le test : il ouvre un connect_raw directement avec Tiberius, exécute SELECT 1 et consomme le stream de résultats. C'est le minimum pour valider que le handshake TLS, l'authentification NTLM ou SQL, et l'accès à la base ciblée sont fonctionnels.
MongoDB appelle create_client_and_ping avec un flag indiquant qu'il s'agit d'un test, ce qui court-circuite l'inférence du support des transactions. Elasticsearch et OpenSearch délèguent à search_compat::test_connection qui interroge un endpoint racine. Redis, DuckDB, ClickHouse ont chacun leur logique propre. Le contrat commun reste le même : validation minimale, aucun état résiduel.
Le feedback côté UI
Côté React, le ConnectionModal garde un état local à trois valeurs : testing (un spinner s'affiche), testResult à 'success' (pastille verte) ou 'error' (pastille rouge et message). Le résultat vient d'un ConnectionResponse retourné par la commande Tauri, dans lequel session_id est toujours None pour un test.
Une subtilité importante : les erreurs remontées à l'UI passent par sanitized_message et sanitized(). Les messages d'erreur des drivers exposent parfois des connection strings, des noms de rôles ou des mots de passe présents dans la trace. La sanitisation retire ces éléments avant que le message n'atteigne l'écran. C'est cohérent avec le reste de l'architecture : la même règle s'applique aux logs et à l'historique de requêtes.
Une brique adaptée au contexte desktop
Le test de connexion tel qu'il est câblé dans QoreDB reflète une décision de fond : chaque interaction utilisateur doit être bornée dans le temps, ne rien laisser derrière elle, et appliquer les mêmes règles de sécurité qu'un usage normal. C'est un design adapté à un client desktop mono-utilisateur où le feedback est immédiat, l'état est local, et la surface de configuration est directement manipulée par la personne qui exécute l'action.
Ce genre de brique est modeste dans le code, mais elle définit la façon dont l'utilisateur perçoit la fiabilité de l'app. Un test qui fige l'UI, qui laisse un pool ouvert, ou qui accepte des configurations dangereuses "pour voir", finit toujours par créer des incidents. Le contrat inverse, silencieux quand tout va bien, précis quand ça échoue, est ce qui rend le reste de l'expérience prévisible.
Restez informé des nouveautés
Rejoignez notre newsletter pour recevoir les mises à jour majeures, les nouveaux drivers et nos coulisses techniques.

