Files
sub-store/internal/proxy/parser_test.go
T
2026-07-28 10:32:21 +08:00

293 lines
8.5 KiB
Go

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"])
}
}