feat: add declarative sing-box egress control plane
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from vpn_egressctl.errors import ValidationError
|
||||
from vpn_egressctl.policy import load_policy
|
||||
|
||||
|
||||
class PolicyTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.raw = json.loads(Path("config/policy.json").read_text(encoding="utf-8"))
|
||||
|
||||
def load(self, raw: dict) -> object:
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path = Path(directory) / "policy.json"
|
||||
path.write_text(json.dumps(raw), encoding="utf-8")
|
||||
return load_policy(path)
|
||||
|
||||
def test_production_policy(self) -> None:
|
||||
policy = self.load(self.raw)
|
||||
self.assertEqual(policy.sing_box.required_version, "1.13.19")
|
||||
self.assertEqual(policy.network.iproute2_table_index, 2022)
|
||||
|
||||
def test_unknown_root_key(self) -> None:
|
||||
self.raw["future"] = True
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
def test_unknown_nested_key(self) -> None:
|
||||
self.raw["network"]["typo"] = 1
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
def test_missing_key(self) -> None:
|
||||
del self.raw["dns"]["strategy"]
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
def test_version_is_exactly_pinned(self) -> None:
|
||||
for value in ("1.13", ">=1.13,<1.14", "1.14.0"):
|
||||
raw = json.loads(json.dumps(self.raw))
|
||||
raw["sing_box"]["required_version"] = value
|
||||
with self.subTest(value=value), self.assertRaises(ValidationError):
|
||||
self.load(raw)
|
||||
|
||||
def test_public_exclusion_is_valid_but_diagnosable(self) -> None:
|
||||
self.raw["network"]["route_exclude_address"].append("203.0.113.1/32")
|
||||
policy = self.load(self.raw)
|
||||
self.assertIn("203.0.113.1/32", policy.network.route_exclude_address)
|
||||
|
||||
def test_invalid_network_rejected(self) -> None:
|
||||
self.raw["network"]["route_exclude_address"] = ["10.20.0.1/24"]
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
def test_insecure_health_url_rejected(self) -> None:
|
||||
self.raw["healthcheck"]["url"] = "http://example.com/"
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
def test_health_url_credentials_rejected(self) -> None:
|
||||
self.raw["healthcheck"]["url"] = "https://user:pass@example.com/check"
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
def test_dns_path_must_be_absolute(self) -> None:
|
||||
self.raw["dns"]["remote_path"] = "dns-query"
|
||||
with self.assertRaises(ValidationError):
|
||||
self.load(self.raw)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user