refactor: convert script to automtu package with CI workflow

https://chatgpt.com/share/697112b2-0410-800f-93ff-9372b603d43f
This commit is contained in:
2026-01-21 18:53:44 +01:00
parent 78da3ffc73
commit dcc7a68973
23 changed files with 905 additions and 896 deletions

0
tests/unit/__init__.py Normal file
View File

View File

@@ -0,0 +1,14 @@
import unittest
import automtu
class TestInit(unittest.TestCase):
def test_version_is_exposed(self) -> None:
self.assertTrue(hasattr(automtu, "__version__"))
self.assertIsInstance(automtu.__version__, str)
self.assertRegex(automtu.__version__, r"^\d+\.\d+\.\d+$")
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,26 @@
import unittest
from unittest.mock import Mock, patch
import automtu.__main__ as entry
class TestMain(unittest.TestCase):
def test_main_calls_parser_and_core(self) -> None:
fake_args = object()
fake_parser = Mock()
fake_parser.parse_args.return_value = fake_args
with (
patch("automtu.__main__.build_parser", return_value=fake_parser) as p_build,
patch("automtu.__main__.run_automtu", return_value=0) as p_run,
):
rc = entry.main()
self.assertEqual(rc, 0)
p_build.assert_called_once_with()
fake_parser.parse_args.assert_called_once_with()
p_run.assert_called_once_with(fake_args)
if __name__ == "__main__":
unittest.main(verbosity=2)

62
tests/unit/test_cli.py Normal file
View File

@@ -0,0 +1,62 @@
import unittest
from automtu.cli import build_parser
class TestCli(unittest.TestCase):
def test_build_parser_accepts_known_args(self) -> None:
p = build_parser()
# Parse only; no side effects.
args = p.parse_args(
[
"--egress-if",
"eth0",
"--prefer-wg-egress",
"--pmtu-target",
"1.1.1.1,8.8.8.8",
"--pmtu-timeout",
"2.0",
"--pmtu-min-payload",
"1200",
"--pmtu-max-payload",
"1472",
"--pmtu-policy",
"median",
"--apply-egress-mtu",
"--apply-wg-mtu",
"--wg-if",
"wg0",
"--wg-overhead",
"80",
"--wg-min",
"1280",
"--auto-pmtu-from-wg",
"--set-wg-mtu",
"1372",
"--force-egress-mtu",
"1452",
"--dry-run",
]
)
self.assertEqual(args.egress_if, "eth0")
self.assertTrue(args.prefer_wg_egress)
self.assertEqual(args.pmtu_target, ["1.1.1.1,8.8.8.8"])
self.assertEqual(args.pmtu_timeout, 2.0)
self.assertEqual(args.pmtu_min_payload, 1200)
self.assertEqual(args.pmtu_max_payload, 1472)
self.assertEqual(args.pmtu_policy, "median")
self.assertTrue(args.apply_egress_mtu)
self.assertTrue(args.apply_wg_mtu)
self.assertEqual(args.wg_if, "wg0")
self.assertEqual(args.wg_overhead, 80)
self.assertEqual(args.wg_min, 1280)
self.assertTrue(args.auto_pmtu_from_wg)
self.assertEqual(args.set_wg_mtu, 1372)
self.assertEqual(args.force_egress_mtu, 1452)
self.assertTrue(args.dry_run)
if __name__ == "__main__":
unittest.main(verbosity=2)

94
tests/unit/test_core.py Normal file
View File

@@ -0,0 +1,94 @@
import io
import unittest
from contextlib import redirect_stdout
from types import SimpleNamespace
from unittest.mock import patch
from automtu.core import run_automtu
class TestCore(unittest.TestCase):
def test_run_automtu_happy_path_all_mocked(self) -> None:
args = SimpleNamespace(
dry_run=True,
egress_if=None,
prefer_wg_egress=False,
force_egress_mtu=None,
pmtu_target=["1.1.1.1,8.8.8.8"],
auto_pmtu_from_wg=False,
pmtu_min_payload=1200,
pmtu_max_payload=1472,
pmtu_timeout=1.0,
pmtu_policy="min",
apply_egress_mtu=True,
apply_wg_mtu=True,
wg_if="wg0",
wg_overhead=80,
wg_min=1280,
set_wg_mtu=None,
)
# PMTU probes: 1452 and 1500 -> min policy => 1452, effective=min(base(1500),1452)=1452
# wg_mtu = 1452-80 = 1372
with (
patch("automtu.core.require_root", return_value=None),
patch("automtu.core.detect_egress_iface", return_value="eth0"),
patch("automtu.core.iface_exists", return_value=True),
patch("automtu.core.read_iface_mtu", return_value=1500),
patch("automtu.core.probe_pmtu", side_effect=[1452, 1500]),
patch("automtu.core.set_iface_mtu") as mock_set,
patch("automtu.core.wg_is_active", return_value=False),
patch("automtu.core.wg_peer_endpoints", return_value=[]),
patch("automtu.core.default_route_uses_iface", return_value=False),
):
buf = io.StringIO()
with redirect_stdout(buf):
rc = run_automtu(args)
self.assertEqual(rc, 0)
s = buf.getvalue()
self.assertIn("Detected egress interface: eth0", s)
self.assertIn("Egress base MTU: 1500", s)
self.assertIn("Selected Path MTU (policy=min): 1452", s)
self.assertIn("Computed wg0 MTU: 1372", s)
# apply egress and wg are both true -> two calls in order
mock_set.assert_any_call("eth0", 1452, True)
mock_set.assert_any_call("wg0", 1372, True)
def test_run_automtu_does_not_apply_wg_without_flag(self) -> None:
args = SimpleNamespace(
dry_run=True,
egress_if="eth0",
prefer_wg_egress=False,
force_egress_mtu=None,
pmtu_target=None,
auto_pmtu_from_wg=False,
pmtu_min_payload=1200,
pmtu_max_payload=1472,
pmtu_timeout=1.0,
pmtu_policy="min",
apply_egress_mtu=False,
apply_wg_mtu=False,
wg_if="wg0",
wg_overhead=80,
wg_min=1280,
set_wg_mtu=None,
)
with (
patch("automtu.core.require_root", return_value=None),
patch("automtu.core.iface_exists", return_value=True),
patch("automtu.core.read_iface_mtu", return_value=1500),
patch("automtu.core.set_iface_mtu") as mock_set,
):
buf = io.StringIO()
with redirect_stdout(buf):
rc = run_automtu(args)
self.assertEqual(rc, 0)
mock_set.assert_not_called()
if __name__ == "__main__":
unittest.main(verbosity=2)

49
tests/unit/test_net.py Normal file
View File

@@ -0,0 +1,49 @@
import unittest
from unittest.mock import patch
import automtu.net as net
class TestNet(unittest.TestCase):
def test_detect_egress_iface_parses_default_route_and_ignores_vpn(self) -> None:
# Simulate: ip route show default -> dev wg0 first, then eth0
ip4_default = (
"default via 10.0.0.1 dev wg0 proto dhcp src 10.0.0.2 metric 100\n"
)
ip6_default = "default via fe80::1 dev eth0 proto ra metric 100\n"
def fake_run(cmd: list[str]) -> str:
if cmd[:4] == ["ip", "-4", "route", "show"]:
return ip4_default.strip()
if cmd[:4] == ["ip", "-6", "route", "show"]:
return ip6_default.strip()
return ""
with (
patch("automtu.net._run", side_effect=fake_run),
patch("automtu.net.iface_exists", return_value=True),
):
# ignore_vpn=True -> should skip wg0 and pick eth0
self.assertEqual(net.detect_egress_iface(ignore_vpn=True), "eth0")
# ignore_vpn=False -> first seen is wg0
self.assertEqual(net.detect_egress_iface(ignore_vpn=False), "wg0")
def test_default_route_uses_iface_true_false(self) -> None:
ip4_default = "default via 10.0.0.1 dev eth0\n"
ip6_default = ""
def fake_run(cmd: list[str]) -> str:
if cmd[:4] == ["ip", "-4", "route", "show"]:
return ip4_default.strip()
if cmd[:4] == ["ip", "-6", "route", "show"]:
return ip6_default.strip()
return ""
with patch("automtu.net._run", side_effect=fake_run):
self.assertTrue(net.default_route_uses_iface("eth0"))
self.assertFalse(net.default_route_uses_iface("wg0"))
if __name__ == "__main__":
unittest.main(verbosity=2)

36
tests/unit/test_pmtu.py Normal file
View File

@@ -0,0 +1,36 @@
import unittest
from unittest.mock import patch
import automtu.pmtu as pmtu
class TestPmtu(unittest.TestCase):
def test_probe_pmtu_binary_search_and_hdr_addition(self) -> None:
# Mock _is_ipv6 -> IPv4, so hdr = 28.
# Mock _ping_ok so that payload <= 1400 works, >1400 fails.
def fake_ping_ok(payload: int, target: str, timeout_s: float) -> bool:
return payload <= 1400
with (
patch("automtu.pmtu._is_ipv6", return_value=False),
patch("automtu.pmtu._ping_ok", side_effect=fake_ping_ok),
):
# lo=1200 works, hi=1472 partially works -> best = 1400 -> mtu = 1400+28 = 1428
mtu = pmtu.probe_pmtu(
"1.1.1.1", lo_payload=1200, hi_payload=1472, timeout=1.0
)
self.assertEqual(mtu, 1428)
def test_probe_pmtu_returns_none_if_even_floor_fails(self) -> None:
with (
patch("automtu.pmtu._is_ipv6", return_value=False),
patch("automtu.pmtu._ping_ok", return_value=False),
):
mtu = pmtu.probe_pmtu(
"1.1.1.1", lo_payload=1200, hi_payload=1472, timeout=1.0
)
self.assertIsNone(mtu)
if __name__ == "__main__":
unittest.main(verbosity=2)

58
tests/unit/test_wg.py Normal file
View File

@@ -0,0 +1,58 @@
import unittest
from unittest.mock import patch
import automtu.wg as wg
class TestWg(unittest.TestCase):
def test_wg_peer_endpoints_from_wg_show(self) -> None:
# wg show wg0 endpoints output
out = "abcde12345\t46.4.224.77:51820\nfffff00000\t[2a01:db8::1]:51820\n"
with (
patch(
"automtu.wg._run",
side_effect=lambda cmd: out if cmd[:3] == ["wg", "show", "wg0"] else "",
),
patch("automtu.wg.iface_exists", return_value=True),
patch("automtu.wg._rc", return_value=0),
):
eps = wg.wg_peer_endpoints("wg0")
self.assertEqual(eps, ["46.4.224.77", "2a01:db8::1"])
def test_wg_peer_endpoints_fallback_showconf(self) -> None:
show_endpoints_empty = ""
showconf = (
"[Interface]\n"
"PrivateKey = x\n"
"[Peer]\n"
"Endpoint = 46.4.224.77:51820\n"
"[Peer]\n"
"Endpoint = [2a01:db8::1]:51820\n"
)
def fake_run(cmd: list[str]) -> str:
if cmd == ["wg", "show", "wg0", "endpoints"]:
return show_endpoints_empty
if cmd == ["wg", "showconf", "wg0"]:
return showconf
return ""
with patch("automtu.wg._run", side_effect=fake_run):
eps = wg.wg_peer_endpoints("wg0")
self.assertEqual(eps, ["46.4.224.77", "2a01:db8::1"])
def test_wg_is_active_uses_iface_exists_and_wg_show_rc(self) -> None:
with (
patch("automtu.wg.iface_exists", return_value=True),
patch("automtu.wg._rc", return_value=0),
):
self.assertTrue(wg.wg_is_active("wg0"))
with patch("automtu.wg.iface_exists", return_value=False):
self.assertFalse(wg.wg_is_active("wg0"))
if __name__ == "__main__":
unittest.main(verbosity=2)