package server import ( "fmt" "slices" "testing" "github.com/perfect-panel/server/internal/model/node" ) // TestNormalizeServerUserListProtocol 验证客户端 hysteria2 兼容字段被映射回 // DB 规范名 "hysteria";其他协议必须原样返回,不可被误改。 // 缺这一步会导致 hysteria2 永远查不到节点、永远走兜底,HIF-1 表象。 func TestNormalizeServerUserListProtocol(t *testing.T) { cases := []struct { name string in string want string }{ {"hysteria2 maps to hysteria", "hysteria2", "hysteria"}, {"hysteria unchanged", "hysteria", "hysteria"}, {"trojan unchanged", "trojan", "trojan"}, {"vless unchanged", "vless", "vless"}, {"vmess unchanged", "vmess", "vmess"}, {"tuic unchanged", "tuic", "tuic"}, {"shadowsocks unchanged", "shadowsocks", "shadowsocks"}, {"anytls unchanged", "anytls", "anytls"}, {"empty unchanged", "", ""}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { got := normalizeServerUserListProtocol(c.in) if got != c.want { t.Errorf("normalizeServerUserListProtocol(%q) = %q, want %q", c.in, got, c.want) } }) } } // TestServerUserListCacheKey_ProtocolIsolation 验证缓存 key 在 server_id 相同、 // protocol 不同(如 trojan vs tuic)时一定不同——这是 HIF-1 的根因防回归。 // 若 key 重叠,tuic 的兜底假用户会污染 trojan 的真实用户列表。 func TestServerUserListCacheKey_ProtocolIsolation(t *testing.T) { const serverId int64 = 42 build := func(protocol string) string { return fmt.Sprintf("%s%d:%s", node.ServerUserListCacheKey, serverId, normalizeServerUserListProtocol(protocol)) } protocols := []string{"trojan", "vless", "vmess", "tuic", "shadowsocks", "anytls"} seen := make(map[string]string, len(protocols)) for _, p := range protocols { key := build(p) if other, exists := seen[key]; exists { t.Fatalf("cache key collision: %q (%q) == %q (%q)", p, key, other, key) } seen[key] = p } // hysteria 与 hysteria2 必须落到同一 key,否则 hysteria2 客户端拿不到 hysteria 节点列表 hysteriaKey := build("hysteria") hysteria2Key := build("hysteria2") if hysteriaKey != hysteria2Key { t.Errorf("hysteria/hysteria2 expected to share cache key, got %q vs %q", hysteriaKey, hysteria2Key) } } // TestServerUserListCacheKey_ShapeMatchesDelPath 验证 GetServerUserList 写入的 // cache key 形态与 ServerUserListCacheKeysForServer(Del 路径)枚举出的 key // 形态完全一致。形态一旦漂移,Del 会清不到刚写入的 key,HIF-1 重现。 func TestServerUserListCacheKey_ShapeMatchesDelPath(t *testing.T) { const serverId int64 = 7 writeShape := fmt.Sprintf("%s%d:%s", node.ServerUserListCacheKey, serverId, normalizeServerUserListProtocol("trojan")) delKeys := node.ServerUserListCacheKeysForServer(serverId) if !slices.Contains(delKeys, writeShape) { t.Fatalf("write-path key %q not present in Del-path enumeration %v", writeShape, delKeys) } }