platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
226
selective-vpn-api/app/transport_singbox_dns_migration_test.go
Normal file
226
selective-vpn-api/app/transport_singbox_dns_migration_test.go
Normal file
@@ -0,0 +1,226 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user