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 setupServiceTestRedis(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 TestNewService(t *testing.T) { rds, cleanup := setupServiceTestRedis(t) defer cleanup() svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: rds, }) assert.NotNil(t, svc) assert.NotNil(t, svc.Client()) assert.NotNil(t, svc.Token()) assert.NotNil(t, svc.Blacklist()) assert.NotNil(t, svc.Online()) } func TestNewService_WithoutRedis(t *testing.T) { svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: nil, // 沒有 Redis }) assert.NotNil(t, svc) assert.NotNil(t, svc.Client()) assert.NotNil(t, svc.Token()) assert.Nil(t, svc.Blacklist()) // 應該為 nil assert.Nil(t, svc.Online()) // 應該為 nil } func TestNewService_DefaultValues(t *testing.T) { svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", }) assert.NotNil(t, svc) // 檢查預設值已被應用(通過生成 Token 間接驗證) token, err := svc.GenerateToken("user-123") require.NoError(t, err) assert.NotEmpty(t, token) } func TestNewService_WithCustomConfig(t *testing.T) { rds, cleanup := setupServiceTestRedis(t) defer cleanup() customExpire := 24 * time.Hour customTTL := 10 * time.Minute customPrefix := "custom:" svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", TokenExpire: customExpire, Redis: rds, OnlineTTL: customTTL, KeyPrefix: customPrefix, }) assert.NotNil(t, svc) } func TestService_GenerateToken(t *testing.T) { svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", }) token, err := svc.GenerateToken("user-123") require.NoError(t, err) assert.NotEmpty(t, token) } func TestService_GenerateTokenWithInfo(t *testing.T) { svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", }) info := map[string]interface{}{ "name": "Daniel", "avatar": "https://example.com/avatar.jpg", } token, err := svc.GenerateTokenWithInfo("user-123", info) require.NoError(t, err) assert.NotEmpty(t, token) } func TestService_IsUserOnline_WithoutRedis(t *testing.T) { svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: nil, }) _, err := svc.IsUserOnline(context.Background(), "user-123") assert.Error(t, err) assert.ErrorIs(t, err, ErrOnlineStoreNotConfigured) } func TestService_GetUsersOnlineStatus_WithoutRedis(t *testing.T) { svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: nil, }) _, err := svc.GetUsersOnlineStatus(context.Background(), []string{"user-1", "user-2"}) assert.Error(t, err) assert.ErrorIs(t, err, ErrOnlineStoreNotConfigured) } func TestService_IsUserOnline_WithRedis(t *testing.T) { rds, cleanup := setupServiceTestRedis(t) defer cleanup() svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: rds, }) ctx := context.Background() userID := "user-123" // 初始狀態應該是離線 online, err := svc.IsUserOnline(ctx, userID) require.NoError(t, err) assert.False(t, online) // 處理連線事件 err = svc.Online().HandleConnect(ctx, userID) require.NoError(t, err) // 現在應該在線 online, err = svc.IsUserOnline(ctx, userID) require.NoError(t, err) assert.True(t, online) // 處理斷線事件 err = svc.Online().HandleDisconnect(ctx, userID) require.NoError(t, err) // 現在應該離線 online, err = svc.IsUserOnline(ctx, userID) require.NoError(t, err) assert.False(t, online) } func TestService_GetUsersOnlineStatus_WithRedis(t *testing.T) { rds, cleanup := setupServiceTestRedis(t) defer cleanup() svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: rds, }) ctx := context.Background() // 設置一些用戶在線 err := svc.Online().HandleConnect(ctx, "user-1") require.NoError(t, err) err = svc.Online().HandleConnect(ctx, "user-3") require.NoError(t, err) // 批量獲取在線狀態 status, err := svc.GetUsersOnlineStatus(ctx, []string{"user-1", "user-2", "user-3"}) require.NoError(t, err) assert.True(t, status["user-1"]) assert.False(t, status["user-2"]) assert.True(t, status["user-3"]) } func TestService_Blacklist_Integration(t *testing.T) { rds, cleanup := setupServiceTestRedis(t) defer cleanup() svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: rds, }) ctx := context.Background() userID := "user-123" // 撤銷用戶所有 Token err := svc.Blacklist().RevokeUserTokens(ctx, userID) require.NoError(t, err) // 獲取版本 version, err := svc.Blacklist().GetUserTokenVersion(ctx, userID) require.NoError(t, err) assert.Greater(t, version, int64(0)) // 檢查舊版本無效 valid, err := svc.Blacklist().IsTokenVersionValid(ctx, userID, version-1) require.NoError(t, err) assert.False(t, valid) // 檢查當前版本有效 valid, err = svc.Blacklist().IsTokenVersionValid(ctx, userID, version) require.NoError(t, err) assert.True(t, valid) } func TestService_MultipleConnections(t *testing.T) { rds, cleanup := setupServiceTestRedis(t) defer cleanup() svc := NewService(ServiceConfig{ APIURL: "http://localhost:8000", APIKey: "test-api-key", TokenSecret: "test-secret", Redis: rds, }) ctx := context.Background() userID := "user-123" // 模擬多個設備連線 err := svc.Online().HandleConnect(ctx, userID) require.NoError(t, err) err = svc.Online().HandleConnect(ctx, userID) require.NoError(t, err) err = svc.Online().HandleConnect(ctx, userID) require.NoError(t, err) // 用戶應該在線 online, err := svc.IsUserOnline(ctx, userID) require.NoError(t, err) assert.True(t, online) // 斷開一個設備 err = svc.Online().HandleDisconnect(ctx, userID) require.NoError(t, err) // 用戶仍然在線(還有 2 個連線) online, err = svc.IsUserOnline(ctx, userID) require.NoError(t, err) assert.True(t, online) // 斷開剩餘設備 err = svc.Online().HandleDisconnect(ctx, userID) require.NoError(t, err) err = svc.Online().HandleDisconnect(ctx, userID) require.NoError(t, err) // 用戶現在離線 online, err = svc.IsUserOnline(ctx, userID) require.NoError(t, err) assert.False(t, online) }