package conversion import ( "testing" "github.com/subconverter-go/internal/config" "github.com/subconverter-go/internal/generator" "github.com/subconverter-go/internal/logging" "github.com/subconverter-go/internal/parser" ) func newTestConversionEngine(t *testing.T) *ConversionEngine { t.Helper() logger, err := logging.NewLogger(&logging.LoggingConfig{ Level: "error", Format: "text", Output: "stdout", }) if err != nil { t.Fatalf("failed to create logger: %v", err) } configMgr, err := config.NewConfigManager("") if err != nil { t.Fatalf("failed to create config manager: %v", err) } parserMgr := parser.NewParserManager(logger, configMgr) generatorMgr := generator.NewGeneratorManager(logger) return NewConversionEngine(logger, parserMgr, generatorMgr) } func TestTrimLineCleansCommentsAndBom(t *testing.T) { engine := newTestConversionEngine(t) cases := map[string]string{ "\ufeff ss://example": "ss://example", "# leading comment": "", " // spaced comment": "", "\t;remark": "", "ss://example": "ss://example", } for input, expected := range cases { if got := engine.trimLine(input); got != expected { t.Fatalf("trimLine(%q) = %q, expected %q", input, got, expected) } } } func TestParseConfigurationsHandlesLegacyProtocols(t *testing.T) { engine := newTestConversionEngine(t) vmessPayload := "eyJ2IjoiMiIsInBzIjoidGVzdCIsImFkZCI6IjE5Mi4xNjguMS4xIiwicG9ydCI6IjQ0MyIsImlkIjoiMTIzNDU2NzgtMTIzNC0xMjM0LTEyMzQtMTIzNDU2Nzg5MDEyIiwiYWlkIjoiMCIsIm5ldCI6IndzIiwidHlwZSI6Im5vbmUiLCJob3N0IjoidGVzdC5jb20iLCJwYXRoIjoiL3Rlc3QiLCJ0bHMiOiJ0bHMifQ==" input := "\ufeff# first line is a comment\n" + "socks://user:pass@example.com:1080#socks-proxy\n" + "vmess1://" + vmessPayload + "\n" configs, result, err := engine.parseConfigurations(input, true) if err != nil { t.Fatalf("parseConfigurations returned error: %v", err) } if len(result.InvalidInputs) != 0 { t.Fatalf("expected no invalid inputs, got %v", result.InvalidInputs) } if len(configs) != 2 { t.Fatalf("expected 2 valid configs, got %d", len(configs)) } protocols := []string{configs[0].Protocol, configs[1].Protocol} expected := map[string]bool{"socks5": false, "vmess": false} for _, protocol := range protocols { if _, ok := expected[protocol]; !ok { t.Fatalf("unexpected protocol parsed: %s", protocol) } expected[protocol] = true } for proto, seen := range expected { if !seen { t.Fatalf("protocol %s not detected in parsed configs", proto) } } } func TestGenerateStatsIncludedInDebugInfo(t *testing.T) { engine := newTestConversionEngine(t) req := &ConversionRequest{Target: "clash", URL: "https://example.com"} configs := []*parser.ProxyConfig{{Protocol: "socks5"}} parseResult := &ParseResult{ ValidConfigs: configs, ProtocolStats: map[string]int{"socks5": 1}, } generation := &GenerateResult{ GeneratedOutput: "payload", FormatFeatures: map[string]bool{"success": true}, GenerateStats: &GenerateStats{ GeneratedProxies: 1, GeneratedGroups: 2, GeneratedRules: 3, FormatFeatures: map[string]bool{"success": true}, }, } resp := engine.createSuccessResponse(req, "payload", configs, parseResult, generation) statsVal, ok := resp.DebugInfo["generate_stats"].(map[string]interface{}) if !ok { t.Fatalf("expected generate_stats in debug info, got %v", resp.DebugInfo) } if statsVal["generated_proxies"].(int) != 1 { t.Fatalf("expected generated_proxies=1, got %v", statsVal["generated_proxies"]) } if statsVal["generated_groups"].(int) != 2 { t.Fatalf("expected generated_groups=2, got %v", statsVal["generated_groups"]) } if statsVal["generated_rules"].(int) != 3 { t.Fatalf("expected generated_rules=3, got %v", statsVal["generated_rules"]) } } func TestRemoveLeadingEmojiHandlesVariants(t *testing.T) { cases := map[string]string{ "🛰️ ss-192.168.1.1:8388": "ss-192.168.1.1:8388", "🇭🇰 ss-node": "ss-node", "🇭🇰ss-node": "ss-node", " ss-node": "ss-node", "": "", } for input, expected := range cases { if got := removeLeadingEmoji(input); got != expected { t.Fatalf("removeLeadingEmoji(%q) = %q, want %q", input, got, expected) } } } func TestApplyTransformationsRemoveEmojiAfterRename(t *testing.T) { engine := newTestConversionEngine(t) req := NewConversionRequest() req.RenameRules = []string{".*@🇭🇰 $0"} req.RemoveEmoji = true config := &parser.ProxyConfig{ Name: "ss-192.168.1.1:8388", Remarks: "", Protocol: "ss", Type: "ss", Settings: map[string]interface{}{"method": "aes-256-cfb"}, } updated, err := engine.applyTransformations([]*parser.ProxyConfig{config}, req) if err != nil { t.Fatalf("applyTransformations returned error: %v", err) } if len(updated) != 1 { t.Fatalf("expected one config, got %d", len(updated)) } if updated[0].Name != "ss-192.168.1.1:8388" { t.Fatalf("expected name without emoji, got %q", updated[0].Name) } }