package node import ( "fmt" "sort" "strings" "testing" ) // TestServerUserListCacheKeysForServer 验证按 server 枚举出的协议缓存 key 覆盖 // 所有客户端可能携带的 protocol(包括 hysteria2 兼容字段),形态为 // `server:user:{server_id}:{protocol}`。HIF-1: Del 路径必须用枚举而非 SCAN。 func TestServerUserListCacheKeysForServer(t *testing.T) { const serverId int64 = 42 keys := ServerUserListCacheKeysForServer(serverId) if len(keys) != len(AllProtocols) { t.Fatalf("expected %d keys, got %d: %v", len(AllProtocols), len(keys), keys) } got := make([]string, len(keys)) copy(got, keys) sort.Strings(got) want := make([]string, 0, len(AllProtocols)) for _, p := range AllProtocols { want = append(want, fmt.Sprintf("%s%d:%s", ServerUserListCacheKey, serverId, p)) } sort.Strings(want) for i := range got { if got[i] != want[i] { t.Errorf("key[%d] = %q, want %q", i, got[i], want[i]) } } } // TestServerUserListCacheKeysForServer_NoLegacyFlatKey 验证清缓存路径不再使用 // 旧的扁平 key `server:user:{server_id}`(无 protocol 维度)——旧 key 一旦再出现, // 与 P0-1 写入的带 protocol key 形态不一致,Del 会失效,回归 HIF-1 缺陷。 func TestServerUserListCacheKeysForServer_NoLegacyFlatKey(t *testing.T) { const serverId int64 = 7 legacy := fmt.Sprintf("%s%d", ServerUserListCacheKey, serverId) for _, key := range ServerUserListCacheKeysForServer(serverId) { if key == legacy { t.Fatalf("Del path still emits legacy flat key %q without protocol", legacy) } if !strings.HasPrefix(key, legacy+":") { t.Errorf("key %q does not match `:` shape", key) } } } // TestAllProtocolsContainsKnownProtocols 兜底保证 AllProtocols 至少覆盖关键协议; // 缺失任一会让对应协议的用户列表缓存清不掉。 func TestAllProtocolsContainsKnownProtocols(t *testing.T) { required := []string{ "shadowsocks", "vmess", "vless", "trojan", "tuic", "hysteria", "hysteria2", } set := make(map[string]struct{}, len(AllProtocols)) for _, p := range AllProtocols { set[p] = struct{}{} } for _, r := range required { if _, ok := set[r]; !ok { t.Errorf("AllProtocols missing %q", r) } } }