feat: add declarative sing-box egress control plane
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from vpn_egressctl.errors import CommandError
|
||||
from vpn_egressctl.guard import apply_guard
|
||||
|
||||
from tests.helpers import make_policy
|
||||
|
||||
|
||||
class GuardRunner:
|
||||
def __init__(self, *, missing_interface: bool = False, batch_result: int = 0) -> None:
|
||||
self.missing_interface = missing_interface
|
||||
self.batch_result = batch_result
|
||||
self.calls: list[tuple[list[str], str | None]] = []
|
||||
|
||||
def __call__(self, args: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
||||
input_text = kwargs.get("input")
|
||||
self.calls.append((list(args), input_text if isinstance(input_text, str) else None))
|
||||
if "link" in args and self.missing_interface:
|
||||
return subprocess.CompletedProcess(args, 1, "", "")
|
||||
if args[-2:] == ["-f", "-"]:
|
||||
return subprocess.CompletedProcess(args, self.batch_result, "", "")
|
||||
return subprocess.CompletedProcess(args, 0, "", "")
|
||||
|
||||
|
||||
class GuardTests(unittest.TestCase):
|
||||
def test_atomic_ruleset_uses_policy_interfaces(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
policy = make_policy(Path(directory))
|
||||
runner = GuardRunner()
|
||||
apply_guard(policy, runner)
|
||||
batch = [item for args, item in runner.calls if args[-2:] == ["-f", "-"]][0]
|
||||
self.assertIsNotNone(batch)
|
||||
self.assertIn("delete table inet vpn_egress_guard", batch)
|
||||
self.assertIn('iifname "eth1" oifname "eth0"', batch)
|
||||
self.assertIn("counter reject", batch)
|
||||
|
||||
def test_missing_interface_is_non_mutating(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
runner = GuardRunner(missing_interface=True)
|
||||
with self.assertRaises(CommandError):
|
||||
apply_guard(make_policy(Path(directory)), runner)
|
||||
self.assertFalse(any(args[-2:] == ["-f", "-"] for args, _ in runner.calls))
|
||||
|
||||
def test_batch_failure_is_reported(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
with self.assertRaises(CommandError):
|
||||
apply_guard(make_policy(Path(directory)), GuardRunner(batch_result=1))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user