zapret-kvn/tests/test_singbox_schema_snapshot.py
loop-uh 308f6da16a
Some checks failed
Windows project source guards / test (push) Failing after 38s
Маршрутизация: структурный редактор sing-box по схеме ядра
Раздел «Маршрутизация» с подпунктами (Обзор, Правила, Наборы правил, DNS,
Исходящие, Система, JSON) редактирует тот же активный native JSON.
Формы строятся из JSON Schema закреплённого ядра (sing-box schema),
своей модели маршрутизации нет; неизвестные ключи и форма Listable
сохраняются. «Проверить» запускает sing-box check в фоне.

DNS больше не перезаписывается принудительно: при обновлении шаблонов
нетронутые верхние секции следуют за стоком, изменённые остаются.
Сброс к стоку — вручную, целиком или по разделу.
2026-09-26 20:30:17 +03:00

97 lines
4.6 KiB
Python

"""The routing GUI is driven by the schema of the pinned sing-box core."""
from __future__ import annotations
import json
from pathlib import Path
import unittest
from xray_fluent.singbox_config.schema import SCHEMA_DIR, SingboxSchema, bundled_schema, schema_meta
ROOT = Path(__file__).resolve().parents[1]
LOCK = json.loads((ROOT / "scripts" / "core-lock.windows-x64.json").read_text(encoding="utf-8"))
TEMPLATES = sorted((ROOT / "data" / "templates" / "sing-box").glob("*.json"))
# Sections the GUI edits: none of their fields may be loosened.
GUI_SECTIONS = ("route", "dns", "inbounds", "outbounds", "log", "experimental")
class SchemaSnapshotTests(unittest.TestCase):
def test_snapshot_matches_pinned_core(self) -> None:
meta = schema_meta()
self.assertEqual(meta["module"], LOCK["singbox_build"]["module"])
self.assertEqual(meta["version"], LOCK["singbox_build"]["version"])
def test_no_loosened_path_in_gui_sections(self) -> None:
loose = (SCHEMA_DIR / "loose-paths.txt").read_text(encoding="utf-8").split()
paths = [item for item in loose if item.startswith("Options.")]
self.assertTrue(paths, "loose-paths.txt lists the fork types the generator could not map")
for path in paths:
section = path.split(".")[1]
self.assertNotIn(section, GUI_SECTIONS, path)
def test_route_rule_is_match_group_plus_action_group(self) -> None:
schema = bundled_schema()
rule = schema.definition("Rule")
groups = schema.groups(rule, {"domain_suffix": ["example.org"], "action": "reject"})
self.assertEqual(groups[0].discriminator, "type")
self.assertEqual(groups[0].default_variant, "default")
self.assertIn("domain_suffix", {item.name for item in groups[0].fields})
self.assertEqual(groups[1].discriminator, "action")
self.assertEqual(groups[1].default_variant, "route")
self.assertEqual({item.name for item in groups[1].fields}, {"action", "method", "no_drop"})
self.assertIn("hijack-dns", groups[1].variants)
def test_listable_and_tag_reference_shapes(self) -> None:
schema = bundled_schema()
fields = schema.fields(schema.definition("Rule"), {"action": "route"})
self.assertTrue(fields["domain_suffix"].shape.listable)
self.assertEqual(fields["network"].shape.enum, ("tcp", "udp", "icmp"))
self.assertEqual(fields["rule_set"].shape.tag_ref, "rule_set")
self.assertEqual(fields["outbound"].shape.tag_ref, "outbound")
self.assertFalse(fields["outbound"].shape.listable)
def test_nested_rules_cannot_carry_actions(self) -> None:
schema = bundled_schema()
nested = schema.fields(schema.definition("NestedRule"), {})
self.assertNotIn("action", nested)
self.assertNotIn("outbound", nested)
def test_discriminated_sections_list_core_types(self) -> None:
schema = bundled_schema()
outbound_types = {item.value for item in schema.variants(schema.array_item("outbounds"))}
self.assertTrue({"direct", "block", "selector", "urltest", "vless"} <= outbound_types)
inbound_types = {item.value for item in schema.variants(schema.array_item("inbounds"))}
self.assertIn("tun", inbound_types)
self.assertIsNone(schema.default_variant(schema.array_item("outbounds")))
def test_shipped_templates_use_only_fields_the_core_knows(self) -> None:
schema = bundled_schema()
checks = (
(("route", "rules"), schema.definition("Rule")),
(("route", "rule_set"), schema.definition("RuleSet")),
(("dns", "servers"), schema.definition("DNSServer")),
(("dns", "rules"), schema.definition("DNSRule")),
(("outbounds",), schema.array_item("outbounds")),
(("inbounds",), schema.array_item("inbounds")),
)
for template in TEMPLATES:
document = json.loads(template.read_text(encoding="utf-8"))
for path, node in checks:
items = document
for key in path:
items = items.get(key, {}) if isinstance(items, dict) else {}
for index, item in enumerate(items or []):
with self.subTest(template=template.name, path=path, index=index):
known = set(schema.fields(node, item))
self.assertTrue(known, f"unknown variant {item}")
self.assertLessEqual(set(item), known)
def test_schema_loads_from_path(self) -> None:
loaded = SingboxSchema.load(SCHEMA_DIR / "schema.json")
self.assertIn("RouteOptions", loaded.defs)
if __name__ == "__main__":
unittest.main()