Files
3x-ui/internal/xray/dnsconf/dnsconf_test.go
T
DIMFLIX 2730e4d071 feat(sub): let the panel set the JSON subscription DNS servers (#6485)
* feat(sub): let the panel set the JSON subscription DNS servers

A baked routing profile (#6402) carries only the DNS its preset defines, so an
operator who wants their own resolvers has to override the whole profile or
patch the subscription behind a proxy.

Add the subJsonDns setting: either a full xray dns block or a bare array of
servers. It wins over the profile's DNS while leaving the profile's routing
rules intact, and reaches per-inbound, balancer and info-node documents alike.

The value is validated with xray's own schema (internal/xray/dnsconf): a block
the client could not load is rejected when the settings are saved and ignored
with a warning at request time, instead of being baked into every document.
Both the sub server and the settings API share that validator, so a stored
value can never be silently dropped.

xray's Build() is deliberately not used for validation: it resolves geosite
tokens from the geodata files and would reject valid configs whenever those
are absent from the panel's working directory.

* style(dnsconf): drop the ineffectual initial map assignment

golangci's ineffassign flagged the zero-value map whose value both paths
overwrite: the object branch now assigns the decoded map directly.

* docs(sub): scope the DNS setting to the documents it rewrites

The Routing header mirrored to Happ/INCY keeps the routing profile's own
resolvers, so the setting description and the header-source comment now say
so instead of claiming the profile's DNS is replaced everywhere.

Also trims two comments in the new dnsconf package to the repo's two-line cap.
2026-09-13 11:51:56 +02:00

69 lines
3.3 KiB
Go

package dnsconf
import (
"strings"
"testing"
)
func TestParse(t *testing.T) {
tests := []struct {
name string
value string
wantErr string
wantNil bool
wantCount int
}{
{name: "blank", value: " ", wantNil: true},
{name: "array of strings", value: `["1.1.1.1", "tls://1.0.0.1"]`, wantCount: 2},
{name: "array with object entry", value: `[{"address": "1.1.1.1", "domains": ["geosite:youtube"]}]`, wantCount: 1},
{name: "object with hosts and strategy", value: `{"queryStrategy": "UseIPv4", "hosts": {"example.com": "1.2.3.4"}, "servers": ["https://dns.google/dns-query"]}`, wantCount: 1},
{name: "hosts value list", value: `{"hosts": {"example.com": ["1.2.3.4", "5.6.7.8"]}, "servers": ["1.1.1.1"]}`, wantCount: 1},
{name: "comma separated domains", value: `[{"address": "1.1.1.1", "domains": "geosite:youtube,geosite:netflix"}]`, wantCount: 1},
{name: "client ip set", value: `{"clientIp": "1.2.3.4", "servers": [{"address": "1.1.1.1", "clientIp": "2001:db8::1"}]}`, wantCount: 1},
{name: "malformed JSON", value: `[`, wantErr: "invalid DNS JSON"},
{name: "scalar", value: `42`, wantErr: "must be a JSON object or an array of servers"},
{name: "empty array", value: `[]`, wantErr: `"servers" must list at least one DNS server`},
{name: "object without servers", value: `{"hosts": {"a": "b"}}`, wantErr: `"servers" must list at least one DNS server`},
{name: "misspelled servers key", value: `{"server": ["1.1.1.1"]}`, wantErr: `"servers" must list at least one DNS server`},
{name: "non-string server entry", value: `[53]`, wantErr: "invalid DNS config"},
{name: "server without address", value: `[{"skipFallback": true}]`, wantErr: `needs a non-empty "address"`},
{name: "empty server string", value: `[""]`, wantErr: "is empty"},
{name: "empty server address", value: `[{"address": " "}]`, wantErr: `needs a non-empty "address"`},
{name: "server address wrong type", value: `[{"address": 53}]`, wantErr: "invalid DNS config"},
{name: "hosts wrong type", value: `{"hosts": 5, "servers": ["1.1.1.1"]}`, wantErr: "invalid DNS config"},
{name: "strategy wrong type", value: `{"queryStrategy": 123, "servers": ["1.1.1.1"]}`, wantErr: "invalid DNS config"},
{name: "client ip not an address", value: `{"clientIp": "not-an-ip", "servers": ["1.1.1.1"]}`, wantErr: "clientIp must be an IP address"},
{name: "server client ip not an address", value: `[{"address": "1.1.1.1", "clientIp": "example.com"}]`, wantErr: "clientIp must be an IP address"},
{name: "server port as string", value: `[{"address": "1.1.1.1", "port": "53"}]`, wantErr: "invalid DNS config"},
{name: "server domains wrong type", value: `[{"address": "1.1.1.1", "domains": 5}]`, wantErr: "invalid DNS config"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
block, err := Parse(tc.value)
if tc.wantErr != "" {
if err == nil || !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("err = %v, want %q", err, tc.wantErr)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if tc.wantNil {
if block != nil {
t.Fatalf("block = %v, want nil", block)
}
return
}
if block == nil {
t.Fatal("block = nil")
}
servers, _ := block["servers"].([]any)
if len(servers) != tc.wantCount {
t.Fatalf("servers = %v, want %d", servers, tc.wantCount)
}
})
}
}