zapret-kvn/tests/test_singbox_default_domain_resolver.py
loop-uh d643e3cd07
Some checks failed
Windows project source guards / test (push) Failing after 49s
fix: always set route.default_domain_resolver for sing-box 1.14+
sing-box 1.12 deprecated and 1.14 removed implicit domain resolution in dial
fields, so a config that dials any domain must name route.default_domain_resolver
(or a per-outbound domain_resolver). Managed templates set it, but imported
configs / raw keys often omit it — those still run on Android (pinned to 1.13,
which only warns) yet the core rejects them on Windows (1.14+):
"missing route.default_domain_resolver or domain_resolver in dial fields …".

Inject a route-level default pointing at a real DNS server tag (preferring
proxy-dns, then bootstrap-dns, then the first available) whenever it is missing
or dangling, right before the DNS contract validation that every runtime plan
runs. Per-outbound domain_resolver still takes precedence; valid existing
resolvers are left untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-14 18:20:07 +03:00

60 lines
2.4 KiB
Python

from __future__ import annotations
import unittest
from xray_fluent.engines.singbox.runtime_planner import (
_ensure_default_domain_resolver,
_validate_runtime_dns_contract,
)
def _dns(*tags: str) -> dict:
return {"dns": {"servers": [{"tag": tag} for tag in tags]}}
class EnsureDefaultDomainResolverTests(unittest.TestCase):
def test_injects_missing_resolver_preferring_proxy_dns(self) -> None:
payload = _dns("bootstrap-dns", "proxy-dns")
_ensure_default_domain_resolver(payload)
self.assertEqual(payload["route"]["default_domain_resolver"], "proxy-dns")
def test_falls_back_to_bootstrap_then_first_tag(self) -> None:
boot = _dns("bootstrap-dns", "other")
_ensure_default_domain_resolver(boot)
self.assertEqual(boot["route"]["default_domain_resolver"], "bootstrap-dns")
custom = _dns("my-dns", "second")
_ensure_default_domain_resolver(custom)
self.assertEqual(custom["route"]["default_domain_resolver"], "my-dns")
def test_keeps_existing_valid_resolver(self) -> None:
payload = _dns("proxy-dns", "bootstrap-dns")
payload["route"] = {"default_domain_resolver": "bootstrap-dns"}
_ensure_default_domain_resolver(payload)
self.assertEqual(payload["route"]["default_domain_resolver"], "bootstrap-dns")
def test_replaces_resolver_pointing_at_missing_tag(self) -> None:
payload = _dns("proxy-dns")
payload["route"] = {"default_domain_resolver": "ghost-dns"}
_ensure_default_domain_resolver(payload)
self.assertEqual(payload["route"]["default_domain_resolver"], "proxy-dns")
def test_no_dns_servers_leaves_config_untouched(self) -> None:
payload: dict = {"outbounds": []}
_ensure_default_domain_resolver(payload)
self.assertNotIn("route", payload)
def test_validator_injects_resolver_for_imported_config(self) -> None:
# An imported config that omits the resolver (works on Android/1.13, fatal
# on Windows/1.14) must come out of validation with a valid resolver set.
payload = {
"dns": {"servers": [{"tag": "proxy-dns"}, {"tag": "bootstrap-dns"}]},
"outbounds": [{"tag": "proxy", "type": "vless", "server": "example.com"}],
"route": {"rules": []},
}
_validate_runtime_dns_contract(payload)
self.assertEqual(payload["route"]["default_domain_resolver"], "proxy-dns")
if __name__ == "__main__":
unittest.main()