package centrifugo import ( "context" "testing" "time" "github.com/alicebob/miniredis/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zeromicro/go-zero/core/stores/redis" ) func setupOnlineTestRedis(t *testing.T) (*redis.Redis, func()) { mr, err := miniredis.Run() require.NoError(t, err) rds, err := redis.NewRedis(redis.RedisConf{ Host: mr.Addr(), Type: "node", }) require.NoError(t, err) return rds, func() { mr.Close() } } func TestNewRedisOnlineStore(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) assert.NotNil(t, store) assert.Equal(t, "online:", store.keyPrefix) } func TestNewRedisOnlineStoreWithPrefix(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStoreWithPrefix(rds, "custom:online:") assert.NotNil(t, store) assert.Equal(t, "custom:online:", store.keyPrefix) } func TestSetOnline(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() userID := "user-123" ttl := 5 * time.Minute // 設置在線 err := store.SetOnline(ctx, userID, ttl) require.NoError(t, err) // 檢查是否在線 online, err := store.IsOnline(ctx, userID) require.NoError(t, err) assert.True(t, online) } func TestSetOffline(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() userID := "user-123" // 先設置在線 err := store.SetOnline(ctx, userID, 5*time.Minute) require.NoError(t, err) // 設置離線 err = store.SetOffline(ctx, userID) require.NoError(t, err) // 檢查是否離線 online, err := store.IsOnline(ctx, userID) require.NoError(t, err) assert.False(t, online) } func TestIsOnline_NotOnline(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() // 未設置在線的用戶應該返回 false online, err := store.IsOnline(ctx, "non-existent-user") require.NoError(t, err) assert.False(t, online) } func TestGetOnlineUsers(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() // 設置一些用戶在線 err := store.SetOnline(ctx, "user-1", 5*time.Minute) require.NoError(t, err) err = store.SetOnline(ctx, "user-3", 5*time.Minute) require.NoError(t, err) // 批量獲取在線狀態 userIDs := []string{"user-1", "user-2", "user-3"} status, err := store.GetOnlineUsers(ctx, userIDs) require.NoError(t, err) assert.True(t, status["user-1"]) assert.False(t, status["user-2"]) assert.True(t, status["user-3"]) } func TestGetOnlineUsers_Empty(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() // 空列表應該返回空 map status, err := store.GetOnlineUsers(ctx, []string{}) require.NoError(t, err) assert.Empty(t, status) } func TestIncrClient(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() userID := "user-123" // 第一次增加 count, err := store.IncrClient(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(1), count) // 第二次增加 count, err = store.IncrClient(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(2), count) // 獲取連線數 count, err = store.GetClientCount(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(2), count) } func TestDecrClient(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() userID := "user-123" // 先增加到 2 _, err := store.IncrClient(ctx, userID) require.NoError(t, err) _, err = store.IncrClient(ctx, userID) require.NoError(t, err) // 減少一次 count, err := store.DecrClient(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(1), count) // 再減少一次 count, err = store.DecrClient(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(0), count) } func TestDecrClient_NegativeProtection(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() userID := "user-123" // 直接減少(沒有先增加) count, err := store.DecrClient(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(0), count) // 應該被保護為 0 // 再次減少 count, err = store.DecrClient(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(0), count) // 仍然是 0 } func TestGetClientCount_NoClient(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() // 未設置連線數的用戶應該返回 0 count, err := store.GetClientCount(ctx, "non-existent-user") require.NoError(t, err) assert.Equal(t, int64(0), count) } func TestSetOffline_ClearsClientCount(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) ctx := context.Background() userID := "user-123" // 設置在線和連線數 err := store.SetOnline(ctx, userID, 5*time.Minute) require.NoError(t, err) _, err = store.IncrClient(ctx, userID) require.NoError(t, err) _, err = store.IncrClient(ctx, userID) require.NoError(t, err) // 設置離線 err = store.SetOffline(ctx, userID) require.NoError(t, err) // 連線數也應該被清除 count, err := store.GetClientCount(ctx, userID) require.NoError(t, err) assert.Equal(t, int64(0), count) } func TestKeyGeneration_OnlineStore(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStoreWithPrefix(rds, "test:") // 測試 key 生成 assert.Equal(t, "test:user-123", store.key("user-123")) assert.Equal(t, "test:clients:user-456", store.clientCountKey("user-456")) } func TestOnlineStore_ImplementsInterface(t *testing.T) { rds, cleanup := setupOnlineTestRedis(t) defer cleanup() store := NewRedisOnlineStore(rds) // 確保 RedisOnlineStore 實現了 OnlineStore 介面 var _ OnlineStore = store }