erplibre/test/test_todo_socks_proxy.py
Mathieu Benoit 5411b102a9 [ADD] déploiement : un proxy SOCKS par SSH, et son mode d'emploi
« -L » relaie UN service ; « -D » ouvre un relais SOCKS, par lequel le
navigateur atteint n'importe quelle destination depuis la machine distante —
une interface qui n'écoute que sur sa boucle locale, un hôte de son réseau sans
route depuis ici. Le port par défaut est 1080, modifiable, et contrôlé libre
avant d'ouvrir. La saisie de l'adresse est celle de sshfs, désormais partagée :
l'alias de ~/.ssh/config part tel quel à ssh, faute de quoi son ProxyJump se
perdrait et une VM imbriquée deviendrait injoignable. Le réglage de Firefox
s'affiche AVANT le lancement, la commande ne rendant la main qu'au Ctrl+C.
Vérifié : 9 tests neufs, et la numérotation du menu que deux autres gardent.

--- EN ---

"-L" relays ONE service; "-D" opens a SOCKS relay, through which the browser
reaches any destination from the remote machine — an interface listening only
on its loopback, a host of its network with no route from here. The default
port is 1080, changeable, and checked free before opening. The address prompt
is sshfs's, now shared: the ~/.ssh/config alias goes to ssh as is, failing
which its ProxyJump would be lost and a nested VM become unreachable. The
Firefox settings print BEFORE the launch, the command only returning on
Ctrl+C.
Checked: 9 new tests, and the menu numbering two others guard.

Assisted-by: Claude Opus 5
2026-09-24 13:56:57 -04:00

129 lines
4.4 KiB
Python

#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Le proxy SOCKS ouvre-t-il le bon tunnel, et le dit-il assez ?
« -D » ne relaie pas UN service comme « -L » : il ouvre un relais SOCKS, par
lequel le navigateur atteint n'importe quelle destination depuis la machine
distante. Trois choses décident si la commande sert à quelque chose, et ces
tests les gardent :
- l'ALIAS de ~/.ssh/config est passé tel quel à ssh. Le remplacer par
« user@hôte » perdrait son ProxyJump, et une VM imbriquée sans route directe
deviendrait injoignable ;
- le port choisi se retrouve dans la commande ET dans le mode d'emploi du
navigateur, sans quoi l'utilisateur règle Firefox sur un port qui n'écoute
pas ;
- le mode d'emploi passe AVANT le lancement : la commande ne rend la main
qu'au Ctrl+C, et c'est pendant qu'elle tourne qu'on règle le navigateur.
"""
import builtins
import contextlib
import io
import sys
import unittest
from pathlib import Path
RACINE = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(RACINE))
from script.todo.todo import TODO # noqa: E402
class ExecuteFactice:
def __init__(self):
self.commandes = []
def exec_command_live(self, cmd, **kwargs):
self.commandes.append(cmd)
class BancProxy(unittest.TestCase):
def joue(
self,
reponses,
cible=("vm-essai", "u", "10.0.0.1", "vm-essai", True),
):
"""Déroule la commande sur des réponses écrites d'avance."""
todo = TODO.__new__(TODO)
todo.execute = ExecuteFactice()
todo._ask_ssh_target = lambda: cible
suite = iter(reponses)
ancien = builtins.input
builtins.input = lambda *a, **k: next(suite, "")
sortie = io.StringIO()
try:
with contextlib.redirect_stdout(sortie):
todo._deploy_socks_proxy()
finally:
builtins.input = ancien
return todo.execute.commandes, sortie.getvalue()
class TestLaCommande(BancProxy):
def test_le_port_par_defaut_est_1080(self):
commandes, _ = self.joue([""])
self.assertEqual(["ssh -D 1080 -N -C vm-essai"], commandes)
def test_le_port_se_change(self):
commandes, _ = self.joue(["9050"])
self.assertEqual(["ssh -D 9050 -N -C vm-essai"], commandes)
def test_un_port_qui_n_est_pas_un_nombre_retombe_sur_1080(self):
commandes, _ = self.joue(["mille-quatre-vingts"])
self.assertEqual(["ssh -D 1080 -N -C vm-essai"], commandes)
def test_l_alias_ssh_est_passe_tel_quel(self):
"""Le remplacer par user@hôte perdrait son ProxyJump."""
commandes, _ = self.joue(
[""], cible=("bond", "u", "10.0.0.2", "bond", True)
)
self.assertIn(" bond", commandes[0])
self.assertNotIn("@", commandes[0])
def test_renoncer_a_l_adresse_ne_lance_rien(self):
todo = TODO.__new__(TODO)
todo.execute = ExecuteFactice()
todo._ask_ssh_target = lambda: None
with contextlib.redirect_stdout(io.StringIO()):
todo._deploy_socks_proxy()
self.assertEqual([], todo.execute.commandes)
class TestLeModeDEmploi(BancProxy):
def test_il_nomme_le_port_choisi(self):
_, sortie = self.joue(["9050"])
self.assertIn("127.0.0.1", sortie)
self.assertIn("9050", sortie)
self.assertNotIn("1080", sortie)
def test_il_precede_le_lancement(self):
"""La commande ne rend la main qu'au Ctrl+C."""
_, sortie = self.joue([""])
self.assertLess(sortie.index("127.0.0.1"), sortie.index("Ctrl+C"))
def test_il_parle_du_dns_distant(self):
_, sortie = self.joue([""])
self.assertIn("SOCKS v5", sortie)
self.assertIn("DNS", sortie.upper())
class TestLeMenu(unittest.TestCase):
def test_l_entree_ferme_la_section_locale(self):
"""Quatrième, et la suite glisse : le VPN passe de 9 à 10."""
source = (RACINE / "script/todo/todo.py").read_text(encoding="utf-8")
debut = source.index("def prompt_execute_deploy(self)")
menu = source[debut : source.index("def prompt_execute_deploy_ssh")]
self.assertIn(
'elif status == "4":\n self._deploy_socks_proxy()',
menu,
)
self.assertIn(
'elif status == "10":\n self.prompt_execute_vpn()',
menu,
)
if __name__ == "__main__":
unittest.main()