Files
hi-server/internal/logic/server/getServerUserListLogic_test.go
T
shanshanzhong147 19d28a8f89 修复(#1): 服务器用户列表缓存按 protocol 隔离 + 兜底不写缓存
服务器用户列表缓存跨协议污染修复(HIF-1 / 详见 PR #5 四件套):

1. 缓存 key 加 protocol 维度(`server:user:{server_id}:{protocol}`),对齐 ServerConfig 已有约定
2. 显式枚举协议清除用户列表缓存(AllProtocols + ServerUserListCacheKeysForServer),不用 SCAN
3. 三个兜底分支不写缓存 + Errorw 日志(带 server_id + protocol 字段)
4. hysteria2 → hysteria 兼容归一化 + 6 个新单测

Closes HIF-1
2026-06-03 05:37:03 -07:00

79 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 形态与 ServerUserListCacheKeysForServerDel 路径)枚举出的 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)
}
}