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") } // Verify bypass processes present if !strings.Contains(s, "chrome.exe") { t.Error("expected chrome.exe in bypass processes") } if !strings.Contains(s, "obs64.exe") { t.Error("expected obs64.exe in bypass processes") } // 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") } // 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, "singbox") { t.Error("expected TUN interface name singbox") } // Verify DNS if !strings.Contains(s, "dns-proxy") { t.Error("expected dns-proxy server") } // Verify cache_file if !strings.Contains(s, "cache_file") { t.Error("expected cache_file in experimental") } // sing-box 1.13: sniff action in route rules, not inbound if strings.Contains(s, `"sniff":true`) { t.Error("sniff should NOT be in inbound (removed in 1.13)") } if !strings.Contains(s, `"action":"sniff"`) { t.Error("expected action:sniff in route rules") } // sing-box 1.13: hijack-dns action if !strings.Contains(s, `"action":"hijack-dns"`) { t.Error("expected hijack-dns action") } // sing-box 1.12: new DNS server format (type+server, not address) if strings.Contains(s, `dns-query`) { t.Error("DNS should use type+server, not address URL (deprecated in 1.12)") } if !strings.Contains(s, `"type":"https"`) { t.Error("expected https DNS server") } if !strings.Contains(s, `"1.1.1.1"`) { t.Error("expected 1.1.1.1 DoH server") } // sing-box 1.12: domain_resolver on outbounds if !strings.Contains(s, "domain_resolver") { t.Error("expected domain_resolver on outbounds") } } 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 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 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([]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 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") } }