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 setupTestRedis(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 TestNewTokenBlacklist(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) assert.NotNil(t, blacklist) assert.Equal(t, "centrifugo:blacklist:", blacklist.prefix) } func TestNewTokenBlacklistWithPrefix(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklistWithPrefix(rds, "custom:prefix:") assert.NotNil(t, blacklist) assert.Equal(t, "custom:prefix:", blacklist.prefix) } func TestRevokeToken(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() jti := "test-jti-123" ttl := 1 * time.Hour // 撤銷 Token err := blacklist.RevokeToken(ctx, jti, ttl) require.NoError(t, err) // 檢查是否被撤銷 revoked, err := blacklist.IsTokenRevoked(ctx, jti) require.NoError(t, err) assert.True(t, revoked) } func TestRevokeToken_EmptyJTI(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() err := blacklist.RevokeToken(ctx, "", time.Hour) assert.Error(t, err) assert.Contains(t, err.Error(), "jti cannot be empty") } func TestIsTokenRevoked_NotRevoked(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() // 檢查未撤銷的 Token revoked, err := blacklist.IsTokenRevoked(ctx, "non-existent-jti") require.NoError(t, err) assert.False(t, revoked) } func TestIsTokenRevoked_EmptyJTI(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() // 空 JTI 應該返回 false revoked, err := blacklist.IsTokenRevoked(ctx, "") require.NoError(t, err) assert.False(t, revoked) } func TestRevokeUserTokens(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() userID := "user-123" // 撤銷用戶所有 Token err := blacklist.RevokeUserTokens(ctx, userID) require.NoError(t, err) // 獲取版本 version, err := blacklist.GetUserTokenVersion(ctx, userID) require.NoError(t, err) assert.Greater(t, version, int64(0)) } func TestRevokeUserTokens_EmptyUserID(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() err := blacklist.RevokeUserTokens(ctx, "") assert.Error(t, err) assert.Contains(t, err.Error(), "userID cannot be empty") } func TestGetUserTokenVersion_NoVersion(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() // 未設置版本的用戶應該返回 0 version, err := blacklist.GetUserTokenVersion(ctx, "new-user") require.NoError(t, err) assert.Equal(t, int64(0), version) } func TestGetUserTokenVersion_EmptyUserID(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() version, err := blacklist.GetUserTokenVersion(ctx, "") require.NoError(t, err) assert.Equal(t, int64(0), version) } func TestIsTokenVersionValid(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() userID := "user-123" // 未設置版本時,任何版本都應該有效 valid, err := blacklist.IsTokenVersionValid(ctx, userID, 0) require.NoError(t, err) assert.True(t, valid) // 撤銷用戶 Token err = blacklist.RevokeUserTokens(ctx, userID) require.NoError(t, err) // 獲取當前版本 currentVersion, err := blacklist.GetUserTokenVersion(ctx, userID) require.NoError(t, err) // 舊版本應該無效 valid, err = blacklist.IsTokenVersionValid(ctx, userID, currentVersion-1) require.NoError(t, err) assert.False(t, valid) // 當前版本應該有效 valid, err = blacklist.IsTokenVersionValid(ctx, userID, currentVersion) require.NoError(t, err) assert.True(t, valid) // 更新版本應該有效 valid, err = blacklist.IsTokenVersionValid(ctx, userID, currentVersion+1) require.NoError(t, err) assert.True(t, valid) } func TestRevokeUserTokens_MultipleRevokes(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklist(rds) ctx := context.Background() userID := "user-123" // 第一次撤銷 err := blacklist.RevokeUserTokens(ctx, userID) require.NoError(t, err) version1, err := blacklist.GetUserTokenVersion(ctx, userID) require.NoError(t, err) // 等待一點時間確保時間戳不同 time.Sleep(10 * time.Millisecond) // 第二次撤銷 err = blacklist.RevokeUserTokens(ctx, userID) require.NoError(t, err) version2, err := blacklist.GetUserTokenVersion(ctx, userID) require.NoError(t, err) // 第二次的版本應該更大 assert.Greater(t, version2, version1) // 第一次的版本應該已經無效 valid, err := blacklist.IsTokenVersionValid(ctx, userID, version1) require.NoError(t, err) assert.False(t, valid) } func TestKeyGeneration(t *testing.T) { rds, cleanup := setupTestRedis(t) defer cleanup() blacklist := NewTokenBlacklistWithPrefix(rds, "test:") // 測試 key 生成 assert.Equal(t, "test:token:jti-123", blacklist.tokenKey("jti-123")) assert.Equal(t, "test:user_version:user-456", blacklist.userVersionKey("user-456")) }