package config_test import ( "encoding/json" "strings" "testing" "vpnem/internal/config" "vpnem/internal/models" ) func TestBuildConfigSocks(t *testing.T) { server := models.Server{ Tag: "nl-1", Region: "NL", Type: "socks", Server: "5.180.97.200", ServerPort: 54101, UDPOverTCP: true, } mode := *config.ModeByName("Lovense + OBS + AnyDesk + Discord") ruleSets := []models.RuleSet{} cfg := config.BuildConfig(server, mode, ruleSets, []string{"5.180.97.200"}) data, err := json.Marshal(cfg) if err != nil { t.Fatalf("marshal: %v", err) } s := string(data) // Verify outbound type if !strings.Contains(s, `"type":"socks"`) { t.Error("expected socks outbound") } if !strings.Contains(s, `"type":"socks"`) { t.Error("expected local socks inbound") } if !strings.Contains(s, `"listen":"127.0.0.1"`) { t.Error("expected local socks proxy listen host") } if !strings.Contains(s, `"listen_port":10800`) && !strings.Contains(s, `"listen_port": 10800`) { t.Error("expected local socks proxy on port 10800") } // Verify bypass processes present if !strings.Contains(s, "chromium.exe") { t.Error("expected chromium.exe in direct bypass list") } if !strings.Contains(s, "Performer Application v5.x.exe") { t.Error("expected Performer Application v5.x.exe in direct bypass list") } if !strings.Contains(s, "Яндекс Музыка.exe") { t.Error("expected Яндекс Музыка.exe in direct bypass list") } if strings.Contains(s, "chrome.exe") { t.Error("did not expect chrome.exe in direct bypass list") } if strings.Contains(s, "firefox.exe") { t.Error("did not expect firefox.exe in direct bypass list") } if strings.Contains(s, "msedgewebview2.exe") { t.Error("did not expect msedgewebview2.exe in direct bypass list") } if !strings.Contains(s, "obs64.exe") { t.Error("expected obs64.exe in config rules") } // Verify Lovense regex if !strings.Contains(s, "lovense") { t.Error("expected lovense process regex") } // Verify ip_is_private if !strings.Contains(s, "ip_is_private") { t.Error("expected ip_is_private rule") } // Verify NCSI domains if !strings.Contains(s, "msftconnecttest.com") { t.Error("expected NCSI domain") } // Verify Telegram if !strings.Contains(s, "telegram.org") { t.Error("expected telegram domains") } if !strings.Contains(s, "Telegram.exe") { t.Error("expected Telegram.exe process rule") } // Verify Discord IPs if !strings.Contains(s, "162.159.130.234/32") { t.Error("expected discord IPs") } // Verify final is direct if !strings.Contains(s, `"final":"direct"`) { t.Error("expected final: direct") } // Verify TUN config if !strings.Contains(s, "vpnem") { t.Error("expected TUN interface name vpnem") } // Verify DNS if !strings.Contains(s, "proxy-dns") { t.Error("expected proxy-dns server") } // Verify cache_file if !strings.Contains(s, "cache_file") { t.Error("expected cache_file in experimental") } // sing-box 1.12: sniff/hijack-dns are route actions, not inbound flags. if strings.Contains(s, `"sniff":true`) { t.Error("did not expect legacy inbound sniff flags in 1.12 config") } if strings.Contains(s, `"sniff_override_destination":true`) { t.Error("did not expect legacy sniff_override_destination in 1.12 config") } if !strings.Contains(s, `"action":"sniff"`) { t.Error("expected route sniff action in 1.12 config") } if !strings.Contains(s, `"action":"hijack-dns"`) { t.Error("expected route hijack-dns action in 1.12 config") } // sing-box 1.12: DoH servers use type+server, not address URLs. if strings.Contains(s, `dns-query`) { t.Error("did not expect legacy dns-query URLs in 1.12 config") } if !strings.Contains(s, `"type":"https"`) { t.Error("expected https DNS server type") } if !strings.Contains(s, `"server":"1.1.1.1"`) { t.Error("expected 1.1.1.1 DoH server") } if !strings.Contains(s, "default_domain_resolver") { t.Error("expected default_domain_resolver in route") } } func TestBuildConfigVLESS(t *testing.T) { server := models.Server{ Tag: "nl-vless", Region: "NL", Type: "vless", Server: "5.180.97.200", ServerPort: 443, UUID: "test-uuid", TLS: &models.TLS{Enabled: true, ServerName: "test.example.com"}, Transport: &models.Transport{Type: "ws", Path: "/test"}, } mode := *config.ModeByName("Full (All Traffic)") cfg := config.BuildConfig(server, mode, nil, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, `"type":"vless"`) { t.Error("expected vless outbound") } if !strings.Contains(s, "test-uuid") { t.Error("expected uuid") } if !strings.Contains(s, `"final":"proxy"`) { t.Error("expected final: proxy for Full mode") } } func TestBuildConfigVLESSReality(t *testing.T) { server := models.Server{ Tag: "nl-reality", Region: "NL", Type: "vless-reality", Server: "203.0.113.20", ServerPort: 443, UUID: "33333333-3333-3333-3333-333333333333", TLS: &models.TLS{ Enabled: true, ServerName: "login.microsoftonline.com", Reality: &models.Reality{ Enabled: true, PublicKey: "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0", ShortID: "0123456789abcdef", Fingerprint: "chrome", }, }, } mode := *config.ModeByName("Full (All Traffic)") cfg := config.BuildConfig(server, mode, nil, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, `"type":"vless"`) { t.Error("expected vless outbound for reality") } if !strings.Contains(s, `"public_key":"jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0"`) { t.Error("expected reality public key") } if !strings.Contains(s, `"short_id":"0123456789abcdef"`) { t.Error("expected reality short id") } if !strings.Contains(s, `"fingerprint":"chrome"`) { t.Error("expected reality utls fingerprint") } } func TestBuildConfigShadowsocks(t *testing.T) { server := models.Server{ Tag: "nl-ss", Region: "NL", Type: "shadowsocks", Server: "5.180.97.200", ServerPort: 36728, Method: "chacha20-ietf-poly1305", Password: "test-pass", } mode := *config.ModeByName("Discord Only") cfg := config.BuildConfig(server, mode, nil, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, `"type":"shadowsocks"`) { t.Error("expected shadowsocks outbound") } if !strings.Contains(s, "chacha20-ietf-poly1305") { t.Error("expected method") } } func TestBuildConfigVMess(t *testing.T) { server := models.Server{ Tag: "nl-vmess", Region: "NL", Type: "vmess", Server: "nl.example.com", ServerPort: 8444, UUID: "22222222-2222-2222-2222-222222222222", TLS: &models.TLS{Enabled: true, ServerName: "nl.example.com"}, Transport: &models.Transport{Type: "ws", Path: "/vmess"}, } mode := *config.ModeByName("Full (All Traffic)") cfg := config.BuildConfig(server, mode, nil, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, `"type":"vmess"`) { t.Error("expected vmess outbound") } if !strings.Contains(s, "22222222-2222-2222-2222-222222222222") { t.Error("expected vmess uuid") } if !strings.Contains(s, `"/vmess"`) { t.Error("expected vmess ws path") } } func TestBuildConfigHysteria2(t *testing.T) { server := models.Server{ Tag: "nl-hy2", Region: "NL", Type: "hysteria2", Server: "nl.example.com", ServerPort: 9443, Password: "hy2-secret", ObfsPassword: "obfs-secret", UpMbps: 80, DownMbps: 90, TLS: &models.TLS{Enabled: true, ServerName: "nl.example.com", Insecure: true, ALPN: []string{"h3"}, MinVersion: "1.3", MaxVersion: "1.3"}, } mode := *config.ModeByName("Full (All Traffic)") cfg := config.BuildConfig(server, mode, nil, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, `"type":"hysteria2"`) { t.Error("expected hysteria2 outbound") } if !strings.Contains(s, `"password":"hy2-secret"`) { t.Error("expected hysteria2 password") } if !strings.Contains(s, `"salamander"`) { t.Error("expected hysteria2 obfs configuration") } if !strings.Contains(s, `"up_mbps":80`) && !strings.Contains(s, `"up_mbps": 80`) { t.Error("expected hysteria2 up_mbps") } if !strings.Contains(s, `"insecure":true`) && !strings.Contains(s, `"insecure": true`) { t.Error("expected hysteria2 tls.insecure") } if !strings.Contains(s, `"alpn":["h3"]`) && !strings.Contains(s, `"alpn": ["h3"]`) { t.Error("expected hysteria2 tls alpn h3") } if !strings.Contains(s, `"min_version":"1.3"`) && !strings.Contains(s, `"min_version": "1.3"`) { t.Error("expected hysteria2 tls min_version") } } func TestBuildConfigSplitRealityHysteria2(t *testing.T) { server := models.Server{ Tag: "nl-multi", Region: "NL", Type: "vless-reality", Server: "203.0.113.50", ServerPort: 443, UUID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", TLS: &models.TLS{ Enabled: true, ServerName: "www.microsoft.com", Reality: &models.Reality{ Enabled: true, PublicKey: "pubkey", ShortID: "abcdef1234567890", Fingerprint: "chrome", }, }, Companions: []models.Server{ { Tag: "nl-multi-hysteria2", Region: "NL", Type: "hysteria2", Server: "203.0.113.50", ServerPort: 443, Password: "hy2-secret", ObfsPassword: "obfs-secret", UpMbps: 100, DownMbps: 100, TLS: &models.TLS{ Enabled: true, Insecure: true, ALPN: []string{"h3"}, MinVersion: "1.3", MaxVersion: "1.3", }, }, }, } mode := *config.ModeByName("Full (All Traffic)") cfg := config.BuildConfig(server, mode, nil, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, `"tag":"vless-out"`) { t.Fatal("expected vless-out outbound tag") } if !strings.Contains(s, `"tag":"hysteria2-out"`) { t.Fatal("expected hysteria2-out outbound tag") } if !strings.Contains(s, `"network":["tcp"]`) || !strings.Contains(s, `"outbound":"vless-out"`) { t.Fatal("expected tcp split routing rule") } if !strings.Contains(s, `"network":["udp"]`) || !strings.Contains(s, `"outbound":"hysteria2-out"`) { t.Fatal("expected udp split routing rule") } if !strings.Contains(s, `"detour":"vless-out"`) { t.Fatal("expected proxy DNS detour via vless-out") } } func TestBuildConfigWithRuleSets(t *testing.T) { server := models.Server{ Tag: "nl-1", Type: "socks", Server: "1.2.3.4", ServerPort: 1080, } mode := *config.ModeByName("Re-filter (обход блокировок РФ)") ruleSets := []models.RuleSet{ {Tag: "refilter-domains", URL: "https://example.com/domains.srs", Format: "binary"}, {Tag: "refilter-ip", URL: "https://example.com/ip.srs", Format: "binary"}, {Tag: "discord-voice", URL: "https://example.com/discord.srs", Format: "binary"}, } cfg := config.BuildConfig(server, mode, ruleSets, nil) data, _ := json.Marshal(cfg) s := string(data) if !strings.Contains(s, "refilter-domains") { t.Error("expected refilter-domains rule_set") } if !strings.Contains(s, "download_detour") { t.Error("expected download_detour in rule_set") } if !strings.Contains(s, "update_interval") { t.Error("expected update_interval in rule_set") } } func TestBuildBypassIPs(t *testing.T) { ips := config.BuildBypassIPs(nil, []string{"1.2.3.4", "5.180.97.200"}) found := false for _, ip := range ips { if ip == "1.2.3.4/32" { found = true } } if !found { t.Error("expected dynamic server IP in bypass list") } // 5.180.97.200 is already in StaticBypassIPs, should not be duplicated count := 0 for _, ip := range ips { if ip == "5.180.97.200/32" { count++ } } if count != 1 { t.Errorf("expected 5.180.97.200/32 exactly once, got %d", count) } } func TestBuildBypassIPsIgnoresHostnames(t *testing.T) { ips := config.BuildBypassIPs(nil, []string{"xui5.em-sysadmin.xyz", "1.2.3.4"}) for _, ip := range ips { if ip == "xui5.em-sysadmin.xyz/32" { t.Fatal("expected hostname to be ignored in bypass IP list") } } } func TestAllModes(t *testing.T) { modes := config.AllModes() if len(modes) != 7 { t.Errorf("expected 7 modes, got %d", len(modes)) } names := config.ModeNames() expected := []string{ "Lovense + OBS + AnyDesk", "Lovense + OBS + AnyDesk + Discord", "Lovense + OBS + AnyDesk + Discord + Teams", "Discord Only", "Full (All Traffic)", "Re-filter (обход блокировок РФ)", "Комбо (приложения + Re-filter)", } for i, name := range expected { if names[i] != name { t.Errorf("mode %d: expected %q, got %q", i, name, names[i]) } } } func TestModeByName(t *testing.T) { m := config.ModeByName("Full (All Traffic)") if m == nil { t.Fatal("expected to find Full mode") } if m.Final != "proxy" { t.Errorf("Full mode final should be proxy, got %s", m.Final) } if config.ModeByName("nonexistent") != nil { t.Error("expected nil for nonexistent mode") } }