Some checks failed
CI/CD Pipeline / Test (push) Failing after 22m19s
CI/CD Pipeline / Security Scan (push) Failing after 5m57s
CI/CD Pipeline / Build (amd64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (amd64, linux) (push) Has been skipped
CI/CD Pipeline / Build (amd64, windows) (push) Has been skipped
CI/CD Pipeline / Build (arm64, darwin) (push) Has been skipped
CI/CD Pipeline / Build (arm64, linux) (push) Has been skipped
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Create Release (push) Has been skipped
170 lines
4.9 KiB
Go
170 lines
4.9 KiB
Go
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)
|
|
}
|
|
}
|