backend/pkg/library/centrifugo/token_test.go

264 lines
6.7 KiB
Go
Raw Permalink Normal View History

2026-01-06 07:15:18 +00:00
package centrifugo
import (
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewTokenGenerator(t *testing.T) {
secret := "test-secret"
gen := NewTokenGenerator(secret)
assert.NotNil(t, gen)
assert.Equal(t, secret, gen.config.Secret)
assert.Equal(t, time.Hour, gen.config.ExpireIn)
}
func TestNewTokenGeneratorWithConfig(t *testing.T) {
config := TokenConfig{
Secret: "custom-secret",
ExpireIn: 24 * time.Hour,
}
gen := NewTokenGeneratorWithConfig(config)
assert.NotNil(t, gen)
assert.Equal(t, config.Secret, gen.config.Secret)
assert.Equal(t, config.ExpireIn, gen.config.ExpireIn)
}
func TestNewTokenGeneratorWithConfig_DefaultExpire(t *testing.T) {
config := TokenConfig{
Secret: "test-secret",
ExpireIn: 0, // 應該使用預設值
}
gen := NewTokenGeneratorWithConfig(config)
assert.Equal(t, time.Hour, gen.config.ExpireIn)
}
func TestQuickConnectionToken(t *testing.T) {
gen := NewTokenGenerator("test-secret")
userID := "user-123"
token, err := gen.QuickConnectionToken(userID)
require.NoError(t, err)
assert.NotEmpty(t, token)
// 驗證 Token 內容
claims := &ConnectionClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
require.NoError(t, err)
assert.True(t, parsedToken.Valid)
assert.Equal(t, userID, claims.Sub)
assert.NotNil(t, claims.ExpiresAt)
assert.NotNil(t, claims.IssuedAt)
}
func TestGenerateConnectionToken(t *testing.T) {
gen := NewTokenGenerator("test-secret")
tests := []struct {
name string
opts ConnectionTokenOptions
checkFn func(t *testing.T, claims *ConnectionClaims)
}{
{
name: "basic token",
opts: ConnectionTokenOptions{
UserID: "user-123",
},
checkFn: func(t *testing.T, claims *ConnectionClaims) {
assert.Equal(t, "user-123", claims.Sub)
assert.Nil(t, claims.Info)
assert.Nil(t, claims.Channels)
},
},
{
name: "token with info",
opts: ConnectionTokenOptions{
UserID: "user-456",
Info: map[string]interface{}{
"name": "Daniel",
"role": "admin",
},
},
checkFn: func(t *testing.T, claims *ConnectionClaims) {
assert.Equal(t, "user-456", claims.Sub)
assert.NotNil(t, claims.Info)
assert.Equal(t, "Daniel", claims.Info["name"])
assert.Equal(t, "admin", claims.Info["role"])
},
},
{
name: "token with channels",
opts: ConnectionTokenOptions{
UserID: "user-789",
Channels: []string{"chat:room-1", "chat:room-2"},
},
checkFn: func(t *testing.T, claims *ConnectionClaims) {
assert.Equal(t, "user-789", claims.Sub)
assert.Equal(t, []string{"chat:room-1", "chat:room-2"}, claims.Channels)
},
},
{
name: "token with custom expire",
opts: ConnectionTokenOptions{
UserID: "user-abc",
ExpireAt: ptrTime(time.Now().Add(48 * time.Hour)),
},
checkFn: func(t *testing.T, claims *ConnectionClaims) {
assert.Equal(t, "user-abc", claims.Sub)
// 檢查過期時間大約在 48 小時後
expireTime := claims.ExpiresAt.Time
assert.True(t, expireTime.After(time.Now().Add(47*time.Hour)))
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
token, err := gen.GenerateConnectionToken(tt.opts)
require.NoError(t, err)
assert.NotEmpty(t, token)
// 解析 Token
claims := &ConnectionClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
require.NoError(t, err)
assert.True(t, parsedToken.Valid)
tt.checkFn(t, claims)
})
}
}
func TestQuickSubscriptionToken(t *testing.T) {
gen := NewTokenGenerator("test-secret")
userID := "user-123"
channel := "private:room-456"
token, err := gen.QuickSubscriptionToken(userID, channel)
require.NoError(t, err)
assert.NotEmpty(t, token)
// 驗證 Token 內容
claims := &SubscriptionClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
require.NoError(t, err)
assert.True(t, parsedToken.Valid)
assert.Equal(t, userID, claims.Sub)
assert.Equal(t, channel, claims.Channel)
}
func TestGenerateSubscriptionToken(t *testing.T) {
gen := NewTokenGenerator("test-secret")
opts := SubscriptionTokenOptions{
UserID: "user-123",
Channel: "private:room-456",
Info: map[string]interface{}{
"role": "moderator",
},
}
token, err := gen.GenerateSubscriptionToken(opts)
require.NoError(t, err)
assert.NotEmpty(t, token)
// 驗證 Token 內容
claims := &SubscriptionClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
require.NoError(t, err)
assert.True(t, parsedToken.Valid)
assert.Equal(t, opts.UserID, claims.Sub)
assert.Equal(t, opts.Channel, claims.Channel)
assert.Equal(t, "moderator", claims.Info["role"])
}
func TestGenerateAnonymousToken(t *testing.T) {
gen := NewTokenGenerator("test-secret")
token, err := gen.GenerateAnonymousToken()
require.NoError(t, err)
assert.NotEmpty(t, token)
// 驗證 Token 內容
claims := &ConnectionClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
require.NoError(t, err)
assert.True(t, parsedToken.Valid)
assert.Equal(t, "", claims.Sub) // 匿名用戶的 UserID 為空
}
func TestTokenExpiration(t *testing.T) {
// 創建一個很短過期時間的生成器
gen := NewTokenGeneratorWithConfig(TokenConfig{
Secret: "test-secret",
ExpireIn: 1 * time.Second,
})
token, err := gen.QuickConnectionToken("user-123")
require.NoError(t, err)
// 立即驗證應該成功
claims := &ConnectionClaims{}
parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
require.NoError(t, err)
assert.True(t, parsedToken.Valid)
// 等待過期
time.Sleep(2 * time.Second)
// 過期後驗證應該失敗
claims2 := &ConnectionClaims{}
_, err = jwt.ParseWithClaims(token, claims2, func(token *jwt.Token) (interface{}, error) {
return []byte("test-secret"), nil
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "token is expired")
}
func TestTokenWithWrongSecret(t *testing.T) {
gen := NewTokenGenerator("correct-secret")
token, err := gen.QuickConnectionToken("user-123")
require.NoError(t, err)
// 使用錯誤的密鑰驗證
claims := &ConnectionClaims{}
_, err = jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte("wrong-secret"), nil
})
assert.Error(t, err)
}
// 輔助函數
func ptrTime(t time.Time) *time.Time {
return &t
}