Files
elmprodvpn/selective-vpn-api/app/transport_singbox_dns_migration_test.go

227 lines
6.4 KiB
Go

package app
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestTransportMigrateSingBoxDNSConfigMapLegacyUDP(t *testing.T) {
root := map[string]any{
"dns": map[string]any{
"servers": []any{
map[string]any{
"tag": "dns-direct",
"address": "1.1.1.1",
"address_resolver": "dns-local",
"address_strategy": "prefer_ipv4",
},
},
},
}
changed, warns := transportMigrateSingBoxDNSConfigMap(root)
if !changed {
t.Fatalf("expected changed=true")
}
if len(warns) != 0 {
t.Fatalf("unexpected warnings: %#v", warns)
}
dns := root["dns"].(map[string]any)
servers := dns["servers"].([]any)
srv := servers[0].(map[string]any)
if strings.TrimSpace(asString(srv["type"])) != "udp" {
t.Fatalf("expected type=udp, got %#v", srv["type"])
}
if strings.TrimSpace(asString(srv["server"])) != "1.1.1.1" {
t.Fatalf("expected server=1.1.1.1, got %#v", srv["server"])
}
if _, ok := srv["address"]; ok {
t.Fatalf("legacy address key must be removed")
}
if strings.TrimSpace(asString(srv["domain_resolver"])) != "dns-local" {
t.Fatalf("expected domain_resolver migration, got %#v", srv["domain_resolver"])
}
if strings.TrimSpace(asString(srv["domain_strategy"])) != "prefer_ipv4" {
t.Fatalf("expected domain_strategy migration, got %#v", srv["domain_strategy"])
}
if _, ok := srv["detour"]; ok {
t.Fatalf("detour=direct should be removed in migrated config")
}
}
func TestTransportMigrateSingBoxDNSConfigMapHTTPS(t *testing.T) {
root := map[string]any{
"dns": map[string]any{
"servers": []any{
map[string]any{
"tag": "dns-doh",
"address": "https://1.1.1.1/dns-query",
},
},
},
}
changed, warns := transportMigrateSingBoxDNSConfigMap(root)
if !changed {
t.Fatalf("expected changed=true")
}
if len(warns) != 0 {
t.Fatalf("unexpected warnings: %#v", warns)
}
dns := root["dns"].(map[string]any)
srv := dns["servers"].([]any)[0].(map[string]any)
if strings.TrimSpace(asString(srv["type"])) != "https" {
t.Fatalf("expected type=https, got %#v", srv["type"])
}
if strings.TrimSpace(asString(srv["server"])) != "1.1.1.1" {
t.Fatalf("expected server=1.1.1.1, got %#v", srv["server"])
}
if _, ok := srv["path"]; ok {
t.Fatalf("default /dns-query path should not be forced into config")
}
}
func TestTransportMigrateSingBoxDNSConfigMapTypedServerDetourDirectRemoved(t *testing.T) {
root := map[string]any{
"dns": map[string]any{
"servers": []any{
map[string]any{
"tag": "dns-direct",
"type": "udp",
"server": "1.1.1.1",
"detour": "direct",
},
},
},
}
changed, warns := transportMigrateSingBoxDNSConfigMap(root)
if !changed {
t.Fatalf("expected changed=true when removing direct detour")
}
if len(warns) != 0 {
t.Fatalf("unexpected warnings: %#v", warns)
}
dns := root["dns"].(map[string]any)
srv := dns["servers"].([]any)[0].(map[string]any)
if _, ok := srv["detour"]; ok {
t.Fatalf("detour should be removed for typed DNS server")
}
}
func TestTransportSystemdBackendProvisionSingBoxDNSMigrationStrictFail(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
transportSystemdUnitsDir = t.TempDir()
transportRunCommand = func(_ time.Duration, _ string, _ ...string) (string, string, int, error) {
return "", "", 0, nil
}
cfgDir := t.TempDir()
cfg := filepath.Join(cfgDir, "singbox.json")
if err := os.WriteFile(cfg, []byte("{broken-json"), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
client := TransportClient{
ID: "sg-mig-strict",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "sg-mig-strict.service",
"bin": "/usr/bin/sing-box",
"singbox_config_path": cfg,
"singbox_dns_migrate_legacy": true,
"singbox_dns_migrate_strict": true,
},
}
res := selectTransportBackend(client).Provision(client)
if res.OK {
t.Fatalf("expected strict migration failure, got %#v", res)
}
if res.Code != "TRANSPORT_BACKEND_SINGBOX_DNS_MIGRATE_FAILED" {
t.Fatalf("unexpected code: %#v", res)
}
}
func TestTransportSystemdBackendProvisionSingBoxDNSMigrationWritesBackup(t *testing.T) {
origRunner := transportRunCommand
origUnitsDir := transportSystemdUnitsDir
defer func() {
transportRunCommand = origRunner
transportSystemdUnitsDir = origUnitsDir
}()
tmpDir := t.TempDir()
transportSystemdUnitsDir = tmpDir
transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) {
cmd := name + " " + strings.Join(args, " ")
if cmd == "systemctl daemon-reload" {
return "", "", 0, nil
}
return "", "", 0, nil
}
cfgDir := t.TempDir()
cfg := filepath.Join(cfgDir, "singbox.json")
legacy := map[string]any{
"dns": map[string]any{
"servers": []any{
map[string]any{
"tag": "dns-direct",
"address": "1.1.1.1",
},
},
},
}
raw, _ := json.Marshal(legacy)
if err := os.WriteFile(cfg, raw, 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
client := TransportClient{
ID: "sg-mig-ok",
Kind: TransportClientSingBox,
Config: map[string]any{
"runner": "systemd",
"unit": "sg-mig-ok.service",
"bin": "/usr/bin/sing-box",
"singbox_config_path": cfg,
"singbox_dns_migrate_legacy": true,
},
}
res := selectTransportBackend(client).Provision(client)
if !res.OK {
t.Fatalf("expected provision success, got %#v", res)
}
if !strings.Contains(strings.ToLower(res.Stdout), "dns-migrate:") {
t.Fatalf("expected migration note in stdout, got %#v", res.Stdout)
}
if _, err := os.Stat(cfg + ".legacy-dns.bak"); err != nil {
t.Fatalf("expected backup file, stat err=%v", err)
}
updatedRaw, err := os.ReadFile(cfg)
if err != nil {
t.Fatalf("read updated config: %v", err)
}
var updated map[string]any
if err := json.Unmarshal(updatedRaw, &updated); err != nil {
t.Fatalf("parse updated config: %v", err)
}
dns := updated["dns"].(map[string]any)
srv := dns["servers"].([]any)[0].(map[string]any)
if strings.TrimSpace(asString(srv["type"])) != "udp" {
t.Fatalf("expected migrated type=udp, got %#v", srv["type"])
}
if _, ok := srv["address"]; ok {
t.Fatalf("legacy address key should be removed after migration")
}
}