package proxy import ( "encoding/base64" "encoding/json" "strings" "testing" "github.com/peterqiu0516/sub-store/internal/model" ) func TestParseSS(t *testing.T) { plain := "aes-256-gcm:password123@1.2.3.4:8388" encoded := base64.RawURLEncoding.EncodeToString([]byte(plain)) uri := "ss://" + encoded + "#TestSS" nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "ss" { t.Errorf("expected type ss, got %v", n["type"]) } if n["server"] != "1.2.3.4" { t.Errorf("expected server 1.2.3.4, got %v", n["server"]) } if n["name"] != "TestSS" { t.Errorf("expected name TestSS, got %v", n["name"]) } if n["cipher"] != "aes-256-gcm" { t.Errorf("expected cipher aes-256-gcm, got %v", n["cipher"]) } if n["password"] != "password123" { t.Errorf("expected password password123, got %v", n["password"]) } port, _ := n["port"].(float64) if port != 8388 { t.Errorf("expected port 8388, got %v", n["port"]) } } func TestParseSSURLForm(t *testing.T) { uri := "ss://aes-256-gcm:password123@1.2.3.4:8388#TestSS" nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "ss" { t.Errorf("expected type ss, got %v", n["type"]) } if n["cipher"] != "aes-256-gcm" { t.Errorf("expected cipher aes-256-gcm, got %v", n["cipher"]) } } func TestParseSSR(t *testing.T) { // SSR format: base64(server:port:protocol:method:obfs:base64(password)/?remarks=base64(name)) ssrMain := "1.2.3.4:8388:auth_aes128_sha1:aes-256-cfb:http_simple:" + base64.RawURLEncoding.EncodeToString([]byte("pass")) encoded := base64.RawURLEncoding.EncodeToString([]byte(ssrMain + "/?remarks=" + base64.RawURLEncoding.EncodeToString([]byte("TestSSR")))) uri := "ssr://" + encoded nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "ssr" { t.Errorf("expected type ssr, got %v", n["type"]) } if n["server"] != "1.2.3.4" { t.Errorf("expected server 1.2.3.4, got %v", n["server"]) } if n["obfs"] != "http_simple" { t.Errorf("expected obfs http_simple, got %v", n["obfs"]) } } func TestParseVMess(t *testing.T) { vmJSON := `{"v":"2","ps":"TestVM","add":"1.2.3.4","port":"443","id":"uuid-1234","aid":"0","scy":"auto","net":"ws","type":"none","host":"example.com","path":"/path","tls":"tls","sni":"example.com"}` encoded := base64.RawURLEncoding.EncodeToString([]byte(vmJSON)) uri := "vmess://" + encoded nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "vmess" { t.Errorf("expected type vmess, got %v", n["type"]) } if n["server"] != "1.2.3.4" { t.Errorf("expected server 1.2.3.4, got %v", n["server"]) } if n["uuid"] != "uuid-1234" { t.Errorf("expected uuid uuid-1234, got %v", n["uuid"]) } if n["network"] != "ws" { t.Errorf("expected network ws, got %v", n["network"]) } ws, ok := n["ws-opts"].(map[string]any) if !ok { t.Fatalf("expected ws-opts map, got %T", n["ws-opts"]) } if ws["path"] != "/path" { t.Errorf("expected ws path /path, got %v", ws["path"]) } if n["tls"] != true { t.Errorf("expected tls true, got %v", n["tls"]) } } func TestParseVLESS(t *testing.T) { uri := "vless://uuid-1234@1.2.3.4:443?encryption=none&security=tls&type=ws&host=example.com&path=%2Fpath&sni=example.com#TestVLESS" nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "vless" { t.Errorf("expected type vless, got %v", n["type"]) } if n["uuid"] != "uuid-1234" { t.Errorf("expected uuid uuid-1234, got %v", n["uuid"]) } if n["network"] != "ws" { t.Errorf("expected network ws, got %v", n["network"]) } ws, ok := n["ws-opts"].(map[string]any) if !ok { t.Fatalf("expected ws-opts map, got %T", n["ws-opts"]) } if ws["path"] != "/path" { t.Errorf("expected ws path /path, got %v", ws["path"]) } if n["tls"] != true { t.Errorf("expected tls true, got %v", n["tls"]) } } func TestParseTrojan(t *testing.T) { uri := "trojan://password123@1.2.3.4:443?sni=example.com&type=ws&host=example.com&path=%2Fpath#TestTrojan" nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "trojan" { t.Errorf("expected type trojan, got %v", n["type"]) } if n["password"] != "password123" { t.Errorf("expected password password123, got %v", n["password"]) } if n["sni"] != "example.com" { t.Errorf("expected sni example.com, got %v", n["sni"]) } } func TestParseHysteria2(t *testing.T) { uri := "hysteria2://password123@1.2.3.4:443?sni=example.com&insecure=1#TestHys2" nodes := ParseProxies(uri) if len(nodes) != 1 { t.Fatalf("expected 1 node, got %d", len(nodes)) } n := nodes[0] if n["type"] != "hysteria2" { t.Errorf("expected type hysteria2, got %v", n["type"]) } if n["password"] != "password123" { t.Errorf("expected password password123, got %v", n["password"]) } } func TestParseMultipleLines(t *testing.T) { lines := []string{ "ss://aes-256-gcm:pass@1.2.3.4:8388#Node1", "ss://aes-256-gcm:pass@5.6.7.8:8388#Node2", "ss://aes-256-gcm:pass@9.10.11.12:8388#Node3", } nodes := ParseProxies(strings.Join(lines, "\n")) if len(nodes) != 3 { t.Fatalf("expected 3 nodes, got %d", len(nodes)) } if nodes[0]["name"] != "Node1" { t.Errorf("expected name Node1, got %v", nodes[0]["name"]) } if nodes[2]["name"] != "Node3" { t.Errorf("expected name Node3, got %v", nodes[2]["name"]) } } func TestParseBase64Content(t *testing.T) { lines := []string{ "ss://aes-256-gcm:pass@1.2.3.4:8388#Node1", "ss://aes-256-gcm:pass@5.6.7.8:8388#Node2", } plain := strings.Join(lines, "\n") encoded := base64.StdEncoding.EncodeToString([]byte(plain)) // DecodeMaybeBase64 should detect and decode base64 content decoded := DecodeMaybeBase64(encoded) nodes := ParseProxies(decoded) if len(nodes) != 2 { t.Fatalf("expected 2 nodes from base64 content, got %d", len(nodes)) } } func TestParseEmptyContent(t *testing.T) { nodes := ParseProxies("") if len(nodes) != 0 { t.Errorf("expected 0 nodes for empty content, got %d", len(nodes)) } } func TestParseInvalidContent(t *testing.T) { nodes := ParseProxies("this is not a valid proxy URI") if len(nodes) != 0 { t.Errorf("expected 0 nodes for invalid content, got %d", len(nodes)) } } func TestParseJSONContent(t *testing.T) { jsonContent := `[{"type":"ss","server":"1.2.3.4","port":8388,"cipher":"aes-256-gcm","password":"pass","name":"TestJSON"}]` nodes := ParseProxies(jsonContent) if len(nodes) != 1 { t.Fatalf("expected 1 node from JSON, got %d", len(nodes)) } if nodes[0]["type"] != "ss" { t.Errorf("expected type ss, got %v", nodes[0]["type"]) } } func TestEnsureUniqueProxyNames(t *testing.T) { nodes := []model.ProxyNode{ {"name": "Same", "server": "1.1.1.1"}, {"name": "Same", "server": "2.2.2.2"}, {"name": "Same", "server": "3.3.3.3"}, {"name": "Unique", "server": "4.4.4.4"}, } result := EnsureUniqueProxyNames(nodes) if result[0]["name"] != "Same" { t.Errorf("first name should be unchanged, got %v", result[0]["name"]) } if result[1]["name"] == "Same" { t.Error("second duplicate should be renamed") } if result[3]["name"] != "Unique" { t.Errorf("unique name should be unchanged, got %v", result[3]["name"]) } } func TestAddPreviewIds(t *testing.T) { nodes := []model.ProxyNode{ {"name": "A", "server": "1.1.1.1"}, {"name": "B", "server": "2.2.2.2"}, } result := AddPreviewIds(nodes) if _, ok := result[0]["id"]; !ok { t.Error("expected id field on first node") } if _, ok := result[1]["id"]; !ok { t.Error("expected id field on second node") } } func TestDecodeMaybeBase64(t *testing.T) { plain := "ss://pass@host:port#name" if DecodeMaybeBase64(plain) != plain { t.Error("plain text should pass through") } encoded := base64.StdEncoding.EncodeToString([]byte(plain)) decoded := DecodeMaybeBase64(encoded) if !strings.Contains(decoded, "ss://") { t.Errorf("base64 should be decoded, got: %s", decoded) } } func TestNodeJSONSerialization(t *testing.T) { uri := "ss://aes-256-gcm:pass@1.2.3.4:8388#Test" nodes := ParseProxies(uri) if len(nodes) == 0 { t.Fatal("no nodes parsed") } data, err := json.Marshal(nodes[0]) if err != nil { t.Fatalf("failed to marshal node: %v", err) } var back map[string]any if err := json.Unmarshal(data, &back); err != nil { t.Fatalf("failed to unmarshal node: %v", err) } if back["type"] != "ss" { t.Errorf("expected type ss after round-trip, got %v", back["type"]) } }