1750 lines
44 KiB
Go
1750 lines
44 KiB
Go
package repository
|
||
|
||
import (
|
||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain"
|
||
"code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/entity"
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"github.com/alicebob/miniredis/v2"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
func setupMiniRedis() (*miniredis.Miniredis, *redis.Redis) {
|
||
// 啟動 setupMiniRedis 作為模擬的 Redis 服務
|
||
mr, err := miniredis.Run()
|
||
if err != nil {
|
||
panic("failed to start miniRedis: " + err.Error())
|
||
}
|
||
|
||
// 使用 setupMiniRedis 的地址配置 go-zero Redis 客戶端
|
||
redisConf := redis.RedisConf{
|
||
Host: mr.Addr(),
|
||
Type: "node",
|
||
}
|
||
r := redis.MustNewRedis(redisConf)
|
||
|
||
return mr, r
|
||
}
|
||
|
||
func TestTokenRepository_Create(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
token := entity.Token{
|
||
ID: "token123",
|
||
UID: "user123",
|
||
DeviceID: "device123",
|
||
AccessToken: "access123",
|
||
ExpiresIn: time.Now().UTC().Add(10 * time.Second).UnixNano(), // 過期時間,現在加 10 秒 = 10 秒後
|
||
RefreshToken: "refresh123",
|
||
RefreshExpiresIn: time.Now().UTC().Add(10 * time.Second).UnixNano(), // 過期時間,現在加 10 秒 = 10 秒後
|
||
}
|
||
expiredTTL := 10 * time.Second // 過期時間
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
token entity.Token
|
||
prepareFunc func() error // 用於模擬 Redis 或序列化錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful token creation",
|
||
token: token,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis Pipeline error",
|
||
token: token,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 操作錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 或序列化錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 Create 方法
|
||
err := repo.Create(context.Background(), tt.token)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 檢查是否成功設置了 AccessToken、RefreshToken 和 UID 及 DeviceID 關聯
|
||
tokenKey := domain.GetAccessTokenRedisKey(tt.token.ID)
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(tt.token.RefreshToken)
|
||
uidKey := domain.GetUIDTokenRedisKey(tt.token.UID)
|
||
deviceIDKey := domain.GetDeviceTokenRedisKey(tt.token.DeviceID)
|
||
|
||
// 驗證 AccessToken 是否已設置
|
||
val, err := mr.Get(tokenKey)
|
||
assert.NoError(t, err)
|
||
expectedBody, _ := json.Marshal(tt.token)
|
||
assert.Equal(t, string(expectedBody), val)
|
||
|
||
// 驗證 RefreshToken 是否已設置
|
||
val, err = mr.Get(refreshTokenKey)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.token.ID, val)
|
||
|
||
// 檢查 UID 和 DeviceID 關聯是否已設置
|
||
uidSetMembers, err := mr.SMembers(uidKey)
|
||
assert.NoError(t, err)
|
||
assert.Contains(t, uidSetMembers, tt.token.ID)
|
||
|
||
deviceIDSetMembers, err := mr.SMembers(deviceIDKey)
|
||
assert.NoError(t, err)
|
||
assert.Contains(t, deviceIDSetMembers, tt.token.ID)
|
||
|
||
// 檢查 AccessToken 和 RefreshToken 的過期時間
|
||
accessTTL := mr.TTL(tokenKey)
|
||
assert.InDelta(t, expiredTTL.Seconds(), accessTTL.Seconds(), 2, "AccessToken TTL 與設置的過期 TTl 應該相近")
|
||
|
||
refreshTTLVal := mr.TTL(refreshTokenKey)
|
||
assert.InDelta(t, expiredTTL.Seconds(), refreshTTLVal.Seconds(), 2, "Refresh TTL 與 與設置的過期 TTl 應該相近")
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_retrieveToken(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 模擬一個 Token 實例並將其存入 Redis
|
||
now := time.Now().UTC().UnixNano()
|
||
token := entity.Token{
|
||
ID: "token123",
|
||
UID: "user123",
|
||
DeviceID: "device123",
|
||
AccessToken: "access123",
|
||
ExpiresIn: time.Now().UTC().Add(3600 * time.Second).UnixNano(),
|
||
AccessCreateAt: now,
|
||
RefreshToken: "refresh123",
|
||
RefreshExpiresIn: time.Now().UTC().Add(7200 * time.Second).UnixNano(),
|
||
RefreshCreateAt: now,
|
||
}
|
||
|
||
// 將 Token 序列化為 JSON 並存入 Redis
|
||
tokenKey := domain.GetAccessTokenRedisKey(token.ID)
|
||
tokenData, _ := json.Marshal(token)
|
||
err := mr.Set(tokenKey, string(tokenData))
|
||
assert.NoError(t, err)
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
key string
|
||
want entity.Token
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "ok",
|
||
key: tokenKey,
|
||
want: token,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Token not found",
|
||
key: domain.GetAccessTokenRedisKey("nonexistent"),
|
||
want: entity.Token{},
|
||
wantErr: true,
|
||
errMsg: "failed to found token",
|
||
},
|
||
{
|
||
name: "Invalid JSON format",
|
||
key: domain.GetAccessTokenRedisKey("invalid_json"),
|
||
want: entity.Token{},
|
||
wantErr: true,
|
||
errMsg: "failed to unmarshal token JSON: invalid character 'i' looking for beginning of object key string",
|
||
},
|
||
}
|
||
|
||
// 將錯誤的 JSON 格式設置到 Redis
|
||
err = mr.Set(domain.GetAccessTokenRedisKey("invalid_json"), "{invalid_json}")
|
||
assert.NoError(t, err)
|
||
|
||
// 執行測試
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got, err := repo.retrieveToken(context.Background(), tt.key)
|
||
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
} else {
|
||
assert.NoError(t, err)
|
||
// 比較 Token 的每個字段
|
||
assert.Equal(t, tt.want.ID, got.ID)
|
||
assert.Equal(t, tt.want.UID, got.UID)
|
||
assert.Equal(t, tt.want.DeviceID, got.DeviceID)
|
||
assert.Equal(t, tt.want.AccessToken, got.AccessToken)
|
||
assert.Equal(t, tt.want.ExpiresIn, got.ExpiresIn)
|
||
assert.Equal(t, tt.want.RefreshToken, got.RefreshToken)
|
||
assert.Equal(t, tt.want.RefreshExpiresIn, got.RefreshExpiresIn)
|
||
|
||
// 將時間字段轉換為 Unix() 格式進行比較
|
||
assert.Equal(t, tt.want.AccessCreateAt, got.AccessCreateAt)
|
||
assert.Equal(t, tt.want.RefreshCreateAt, got.RefreshCreateAt)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetTokensBySet(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 模擬兩個 Token 實例,一個過期,一個未過期,並將它們存入 Redis
|
||
now := time.Now().UTC()
|
||
unexpiredToken := entity.Token{
|
||
ID: "token123",
|
||
UID: "user123",
|
||
DeviceID: "device123",
|
||
AccessToken: "access123",
|
||
ExpiresIn: now.Add(time.Hour).UnixNano(), // 1 小時後過期
|
||
AccessCreateAt: now.UnixNano(),
|
||
RefreshToken: "refresh123",
|
||
RefreshExpiresIn: now.Add(2 * time.Hour).UnixNano(),
|
||
RefreshCreateAt: now.UnixNano(),
|
||
}
|
||
|
||
expiredToken := entity.Token{
|
||
ID: "token456",
|
||
UID: "user456",
|
||
DeviceID: "device456",
|
||
AccessToken: "access456",
|
||
ExpiresIn: now.Add(-time.Hour).UnixNano(), // 1 小時前過期
|
||
AccessCreateAt: now.Add(-2 * time.Hour).UnixNano(),
|
||
RefreshToken: "refresh456",
|
||
RefreshExpiresIn: now.Add(-30 * time.Minute).UnixNano(),
|
||
RefreshCreateAt: now.Add(-90 * time.Minute).UnixNano(),
|
||
}
|
||
|
||
// 將 Token 存入 Redis
|
||
unexpiredTokenData, _ := json.Marshal(unexpiredToken)
|
||
expiredTokenData, _ := json.Marshal(expiredToken)
|
||
err := mr.Set(domain.GetAccessTokenRedisKey(unexpiredToken.ID), string(unexpiredTokenData))
|
||
assert.NoError(t, err)
|
||
err = mr.Set(domain.GetAccessTokenRedisKey(expiredToken.ID), string(expiredTokenData))
|
||
assert.NoError(t, err)
|
||
|
||
// 將兩個 Token ID 添加到 Set 集合中
|
||
setKey := "permission:token_set"
|
||
_, err = mr.SAdd(setKey, unexpiredToken.ID)
|
||
if err != nil {
|
||
return
|
||
}
|
||
_, err = mr.SAdd(setKey, expiredToken.ID)
|
||
assert.NoError(t, err)
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
setKey string
|
||
wantTokens []entity.Token
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "Set contains unexpired and expired tokens",
|
||
setKey: setKey,
|
||
wantTokens: []entity.Token{unexpiredToken}, // 預期僅返回未過期的 Token
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Set key not found",
|
||
setKey: "permission:nonexistent_set",
|
||
wantTokens: nil, // 預期返回 nil
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
// 執行測試
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got, err := repo.getTokensBySet(context.Background(), tt.setKey)
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
} else {
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, len(tt.wantTokens), len(got))
|
||
|
||
// 比較每個返回的 Token,並檢查時間戳
|
||
for i, token := range got {
|
||
assert.Equal(t, tt.wantTokens[i].ID, token.ID)
|
||
assert.Equal(t, tt.wantTokens[i].UID, token.UID)
|
||
assert.Equal(t, tt.wantTokens[i].DeviceID, token.DeviceID)
|
||
assert.Equal(t, tt.wantTokens[i].AccessToken, token.AccessToken)
|
||
assert.Equal(t, tt.wantTokens[i].ExpiresIn, token.ExpiresIn)
|
||
assert.Equal(t, tt.wantTokens[i].RefreshToken, token.RefreshToken)
|
||
assert.Equal(t, tt.wantTokens[i].RefreshExpiresIn, token.RefreshExpiresIn)
|
||
assert.Equal(t, tt.wantTokens[i].AccessCreateAt, token.AccessCreateAt)
|
||
assert.Equal(t, tt.wantTokens[i].RefreshCreateAt, token.RefreshCreateAt)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetCountBySet(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試集合鍵和測試數據
|
||
setKey := "permission:token_set"
|
||
|
||
// 將測試數據存入 Redis
|
||
mr.SAdd(setKey, "token123")
|
||
mr.SAdd(setKey, "token456")
|
||
mr.SAdd(setKey, "token789")
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
setKey string
|
||
want int
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "Count of existing set",
|
||
setKey: setKey,
|
||
want: 3, // 預期集合中有 3 個元素
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Non-existent set",
|
||
setKey: "permission:nonexistent_set",
|
||
want: 0, // 預期集合不存在,返回 0
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
// 執行測試
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got, err := repo.getCountBySet(context.Background(), tt.setKey)
|
||
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
} else {
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.want, got)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_SetRelation(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
uid := "user123"
|
||
deviceID := "device123"
|
||
tokenID := "token123"
|
||
ttl := 10 * time.Second // 設置過期時間為 10 秒
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
uid string
|
||
deviceID string
|
||
tokenID string
|
||
ttl time.Duration
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Valid relation setting",
|
||
uid: uid,
|
||
deviceID: deviceID,
|
||
tokenID: tokenID,
|
||
ttl: ttl,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis SAdd error",
|
||
uid: uid,
|
||
deviceID: deviceID,
|
||
tokenID: tokenID,
|
||
ttl: ttl,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced SAdd error") // 模擬 SAdd 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced SAdd error",
|
||
},
|
||
{
|
||
name: "Redis Expire error",
|
||
uid: uid,
|
||
deviceID: deviceID,
|
||
tokenID: tokenID,
|
||
ttl: ttl,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Expire error") // 模擬 Expire 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Expire error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 構建 Redis 鍵
|
||
uidKey := domain.GetUIDTokenRedisKey(tt.uid)
|
||
deviceIDKey := domain.GetDeviceTokenRedisKey(tt.deviceID)
|
||
|
||
// 執行 Redis Pipeline
|
||
err := r.Pipelined(func(tx redis.Pipeliner) error {
|
||
return repo.setTokenRelation(context.Background(), tx, tt.uid, tt.deviceID, tt.tokenID, tt.ttl)
|
||
})
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 檢查 UID 和 DeviceID 關聯是否已設置
|
||
uidSetMembers, err := mr.SMembers(uidKey)
|
||
assert.NoError(t, err)
|
||
assert.Contains(t, uidSetMembers, tt.tokenID)
|
||
|
||
deviceIDSetMembers, err := mr.SMembers(deviceIDKey)
|
||
assert.NoError(t, err)
|
||
assert.Contains(t, deviceIDSetMembers, tt.tokenID)
|
||
|
||
// 檢查 UID 和 DeviceID 鍵的過期時間
|
||
uidTTL := mr.TTL(uidKey)
|
||
assert.Equal(t, tt.ttl.Seconds(), uidTTL.Seconds())
|
||
|
||
deviceIDTTL := mr.TTL(deviceIDKey)
|
||
assert.Equal(t, tt.ttl.Seconds(), deviceIDTTL.Seconds())
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_SetRefreshToken(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
ttl := 10 * time.Second // 設置過期時間為 10 秒
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
token entity.Token
|
||
ttl time.Duration
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Valid RefreshToken setting",
|
||
token: entity.Token{
|
||
ID: "token123",
|
||
RefreshToken: "refresh123",
|
||
},
|
||
ttl: ttl,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Empty RefreshToken",
|
||
token: entity.Token{
|
||
ID: "token456",
|
||
RefreshToken: "",
|
||
},
|
||
ttl: ttl,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis Set error",
|
||
token: entity.Token{
|
||
ID: "token789",
|
||
RefreshToken: "refresh789",
|
||
},
|
||
ttl: ttl,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Set error") // 模擬 Set 操作錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Set error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 Redis Pipeline
|
||
err := r.Pipelined(func(tx redis.Pipeliner) error {
|
||
return repo.setRefreshToken(context.Background(), tx, tt.token, tt.ttl)
|
||
})
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 如果 RefreshToken 不為空,檢查是否成功設置了鍵
|
||
if tt.token.RefreshToken != "" {
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(tt.token.RefreshToken)
|
||
val, err := mr.Get(refreshTokenKey)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.token.ID, val)
|
||
|
||
// 檢查 RefreshToken 鍵的過期時間
|
||
ttlVal := mr.TTL(refreshTokenKey)
|
||
assert.Equal(t, tt.ttl.Seconds(), ttlVal.Seconds())
|
||
}
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_SetToken(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
ttl := 10 * time.Second // 設置過期時間為 10 秒
|
||
token := entity.Token{
|
||
ID: "token123",
|
||
UID: "user123",
|
||
DeviceID: "device123",
|
||
AccessToken: "access123",
|
||
ExpiresIn: time.Now().UTC().Add(7200 * time.Second).UnixNano(),
|
||
RefreshToken: "refresh123",
|
||
}
|
||
body, _ := json.Marshal(token) // 將 Token 轉為 JSON 格式
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
token entity.Token
|
||
body []byte
|
||
ttl time.Duration
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Valid Token setting",
|
||
token: token,
|
||
body: body,
|
||
ttl: ttl,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis Set error",
|
||
token: token,
|
||
body: body,
|
||
ttl: ttl,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Set error") // 模擬 Set 操作錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Set error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 構建 Redis 鍵
|
||
tokenKey := domain.GetAccessTokenRedisKey(tt.token.ID)
|
||
|
||
// 執行 Redis Pipeline
|
||
err := r.Pipelined(func(tx redis.Pipeliner) error {
|
||
return repo.setToken(context.Background(), tx, tt.token.ID, tt.body, tt.ttl)
|
||
})
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 驗證 Token 是否已設置
|
||
val, err := mr.Get(tokenKey)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, string(tt.body), val)
|
||
|
||
// 檢查 Token 鍵的過期時間
|
||
ttlVal := mr.TTL(tokenKey)
|
||
assert.Equal(t, tt.ttl.Seconds(), ttlVal.Seconds())
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_RunPipeline(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
prepareFunc func() error // 準備函數,用於模擬 Redis 錯誤
|
||
fn func(tx redis.Pipeliner) error // 要在 Pipeline 中執行的函數
|
||
wantErr bool // 是否期望錯誤
|
||
errMsg string // 預期的錯誤信息
|
||
}{
|
||
{
|
||
name: "Successful Pipeline Execution",
|
||
fn: func(tx redis.Pipeliner) error {
|
||
// 模擬一個簡單的操作
|
||
return tx.Set(context.Background(), "testkey", "testvalue", 0).Err()
|
||
},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Pipeline Function Error",
|
||
fn: func(tx redis.Pipeliner) error {
|
||
return errors.New("forced function error") // 模擬 Pipeline 操作中的錯誤
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced function error",
|
||
},
|
||
{
|
||
name: "Redis Pipeline Error",
|
||
fn: func(tx redis.Pipeliner) error {
|
||
return tx.Set(context.Background(), "testkey", "testvalue", 0).Err()
|
||
},
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 操作錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
// 執行測試
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數以模擬 Redis 錯誤
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 runPipeline 並捕獲錯誤
|
||
err := repo.runPipeline(context.Background(), tt.fn)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 如果操作成功,驗證 Redis 中的鍵
|
||
if tt.name == "Successful Pipeline Execution" {
|
||
val, err := mr.Get("testkey")
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, "testvalue", val)
|
||
}
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
// 定義一個無法序列化的結構以模擬序列化錯誤
|
||
type Unserializable struct{}
|
||
|
||
func (u Unserializable) MarshalJSON() ([]byte, error) {
|
||
return nil, errors.New("forced JSON marshal error")
|
||
}
|
||
|
||
func TestTokenRepository_CreateOneTimeToken(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
key := "one_time_key"
|
||
duration := 10 * time.Second // 設置過期時間為 10 秒
|
||
ticket := entity.Ticket{
|
||
Data: "sample_data",
|
||
Token: entity.Token{
|
||
ID: "token123",
|
||
AccessToken: "access123",
|
||
},
|
||
}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
key string
|
||
ticket entity.Ticket
|
||
duration time.Duration
|
||
prepareFunc func() error // 用於模擬 Redis 或序列化錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful one-time token creation",
|
||
key: key,
|
||
ticket: ticket,
|
||
duration: duration,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "JSON marshal error",
|
||
key: key,
|
||
ticket: entity.Ticket{
|
||
Data: Unserializable{},
|
||
Token: entity.Token{
|
||
ID: "invalid_token",
|
||
},
|
||
},
|
||
duration: duration,
|
||
wantErr: true,
|
||
errMsg: "json: error calling MarshalJSON for type repository.Unserializable: forced JSON marshal error",
|
||
},
|
||
{
|
||
name: "Redis SetnxEx error",
|
||
key: key,
|
||
ticket: ticket,
|
||
duration: duration,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 操作錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 或序列化錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 CreateOneTimeToken 方法
|
||
err := repo.CreateOneTimeToken(context.Background(), tt.key, tt.ticket, tt.duration)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 構建預期的 Redis 鍵
|
||
oneTimeTokenKey := domain.GetRefreshTokenRedisKey(tt.key)
|
||
|
||
// 檢查 Redis 中是否設置了臨時 Token
|
||
val, err := mr.Get(oneTimeTokenKey)
|
||
assert.NoError(t, err)
|
||
|
||
expectedBody, _ := json.Marshal(tt.ticket)
|
||
assert.Equal(t, string(expectedBody), val)
|
||
|
||
// 檢查過期時間
|
||
ttl := mr.TTL(oneTimeTokenKey)
|
||
assert.Equal(t, tt.duration.Seconds(), ttl.Seconds())
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetAccessTokenByOneTimeToken(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
oneTimeToken := "one_time_token_123"
|
||
accessTokenID := "token123"
|
||
expectedToken := entity.Token{
|
||
ID: accessTokenID,
|
||
UID: "user123",
|
||
DeviceID: "device123",
|
||
AccessToken: "access123",
|
||
ExpiresIn: 3600,
|
||
RefreshToken: "refresh123",
|
||
}
|
||
|
||
// 在 Redis 中設置模擬的數據
|
||
_ = mr.Set(domain.GetRefreshTokenRedisKey(oneTimeToken), accessTokenID)
|
||
tokenData, _ := json.Marshal(expectedToken)
|
||
_ = mr.Set(domain.GetRefreshTokenRedisKey(oneTimeToken), string(tokenData))
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
oneTimeToken string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
expected entity.Token
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful retrieval of access token by one-time token",
|
||
oneTimeToken: oneTimeToken,
|
||
expected: expectedToken,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Token not found in Redis",
|
||
oneTimeToken: "nonexistent_token",
|
||
wantErr: true,
|
||
errMsg: "failed to found token",
|
||
},
|
||
{
|
||
name: "Redis Get error",
|
||
oneTimeToken: oneTimeToken,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 GetAccessTokenByOneTimeToken 方法
|
||
result, err := repo.GetAccessTokenByOneTimeToken(context.Background(), tt.oneTimeToken)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.expected, result)
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetAccessTokensByUID(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
uid := "user123"
|
||
tokens := []entity.Token{
|
||
{
|
||
ID: "token1",
|
||
UID: uid,
|
||
DeviceID: "device1",
|
||
AccessToken: "access1",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshToken: "refresh1",
|
||
},
|
||
{
|
||
ID: "token2",
|
||
UID: uid,
|
||
DeviceID: "device2",
|
||
AccessToken: "access2",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshToken: "refresh2",
|
||
},
|
||
}
|
||
|
||
for _, token := range tokens {
|
||
err := repo.Create(context.Background(), token)
|
||
assert.NoError(t, err)
|
||
}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
uid string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
expected []entity.Token
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful retrieval of tokens by UID",
|
||
uid: uid,
|
||
expected: tokens,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "UID not found in Redis",
|
||
uid: "nonexistent_user",
|
||
expected: []entity.Token{},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis SMember error",
|
||
uid: uid,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 GetAccessTokensByUID 方法
|
||
result, err := repo.GetAccessTokensByUID(context.Background(), tt.uid)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.expected, result)
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetAccessTokenCountByUID(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
uid := "user123"
|
||
uidKey := domain.GetUIDTokenRedisKey(uid)
|
||
|
||
// 在 Redis 中設置模擬的數據
|
||
_, _ = mr.SAdd(uidKey, "token1")
|
||
_, _ = mr.SAdd(uidKey, "token2")
|
||
_, _ = mr.SAdd(uidKey, "token3")
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
uid string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
expected int
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful retrieval of token count by UID",
|
||
uid: uid,
|
||
expected: 3,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "UID not found in Redis",
|
||
uid: "nonexistent_user",
|
||
expected: 0,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis Scard error",
|
||
uid: uid,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 GetAccessTokenCountByUID 方法
|
||
result, err := repo.GetAccessTokenCountByUID(context.Background(), tt.uid)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.expected, result)
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetAccessTokensByDeviceID(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
deviceID := "device123"
|
||
deviceKey := domain.GetDeviceTokenRedisKey(deviceID)
|
||
|
||
// 模擬在 Redis 中存儲多個 Token
|
||
tokens := []entity.Token{
|
||
{
|
||
ID: "token1",
|
||
UID: "user123",
|
||
DeviceID: deviceID,
|
||
AccessToken: "access1",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshToken: "refresh1",
|
||
},
|
||
{
|
||
ID: "token2",
|
||
UID: "user123",
|
||
DeviceID: deviceID,
|
||
AccessToken: "access2",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshToken: "refresh2",
|
||
},
|
||
}
|
||
|
||
// 在 Redis 中設置初始數據
|
||
for _, token := range tokens {
|
||
tokenData, _ := json.Marshal(token)
|
||
_ = mr.Set(domain.GetAccessTokenRedisKey(token.ID), string(tokenData))
|
||
_, _ = mr.SAdd(deviceKey, token.ID)
|
||
}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
deviceID string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
expected []entity.Token
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful retrieval of tokens by Device ID",
|
||
deviceID: deviceID,
|
||
expected: tokens,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Device ID not found in Redis",
|
||
deviceID: "nonexistent_device",
|
||
expected: []entity.Token{},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis SMember error",
|
||
deviceID: deviceID,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 GetAccessTokensByDeviceID 方法
|
||
result, err := repo.GetAccessTokensByDeviceID(context.Background(), tt.deviceID)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.expected, result)
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_GetAccessTokenCountByDeviceID(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
deviceID := "device123"
|
||
deviceKey := domain.GetDeviceTokenRedisKey(deviceID)
|
||
|
||
// 在 Redis 中設置模擬的數據
|
||
_, _ = mr.SAdd(deviceKey, "token1")
|
||
_, _ = mr.SAdd(deviceKey, "token2")
|
||
_, _ = mr.SAdd(deviceKey, "token3")
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
deviceID string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
expected int
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful retrieval of token count by Device ID",
|
||
deviceID: deviceID,
|
||
expected: 3,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Device ID not found in Redis",
|
||
deviceID: "nonexistent_device",
|
||
expected: 0,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis Scard error",
|
||
deviceID: deviceID,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis error") // 模擬 Redis 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 GetAccessTokenCountByDeviceID 方法
|
||
result, err := repo.GetAccessTokenCountByDeviceID(context.Background(), tt.deviceID)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, tt.expected, result)
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_Delete(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
token := entity.Token{
|
||
ID: "token123",
|
||
UID: "user123",
|
||
DeviceID: "device123",
|
||
AccessToken: "access123",
|
||
RefreshToken: "refresh123",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
}
|
||
|
||
// 模擬在 Redis 中存儲 Token 的數據
|
||
accessTokenKey := domain.GetAccessTokenRedisKey(token.ID)
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(token.RefreshToken)
|
||
uidKey := domain.GetUIDTokenRedisKey(token.UID)
|
||
deviceIDKey := domain.GetDeviceTokenRedisKey(token.DeviceID)
|
||
|
||
// 模擬在 Redis 中存儲 Token 的數據
|
||
repo.Create(context.TODO(), token)
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
token entity.Token
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
jump bool
|
||
}{
|
||
{
|
||
name: "Successful deletion of token",
|
||
token: token,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis delete error",
|
||
token: token,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis delete error") // 模擬 Redis 錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis delete error",
|
||
},
|
||
{
|
||
name: "Deletion of non-existent token",
|
||
token: entity.Token{ID: "nonexistent_token", UID: "user123", DeviceID: "device123"},
|
||
wantErr: false,
|
||
jump: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 Delete 方法
|
||
err := repo.Delete(context.Background(), tt.token)
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
if !tt.jump {
|
||
assert.NoError(t, err)
|
||
// 驗證 Token 的鍵已刪除
|
||
_, err = mr.Get(accessTokenKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
_, err = mr.Get(refreshTokenKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
|
||
// 驗證 UID 和 DeviceID 關聯已刪除
|
||
uidSetMembers, err := mr.SMembers(uidKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
assert.NotContains(t, uidSetMembers, token.ID)
|
||
|
||
deviceIDSetMembers, err := mr.SMembers(deviceIDKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
assert.NotContains(t, deviceIDSetMembers, token.ID)
|
||
}
|
||
}
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_DeleteAccessTokensByDeviceID(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
deviceID := "device123"
|
||
tokens := []entity.Token{
|
||
{
|
||
ID: "token1",
|
||
UID: "user123",
|
||
DeviceID: deviceID,
|
||
AccessToken: "access1",
|
||
RefreshToken: "refresh1",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
},
|
||
{
|
||
ID: "token2",
|
||
UID: "user123",
|
||
DeviceID: deviceID,
|
||
AccessToken: "access2",
|
||
RefreshToken: "refresh2",
|
||
ExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
RefreshExpiresIn: time.Now().UTC().Add(60 * time.Minute).UnixNano(),
|
||
},
|
||
}
|
||
|
||
// 在 Redis 中設置初始數據
|
||
deviceKey := domain.GetDeviceTokenRedisKey(deviceID)
|
||
for _, token := range tokens {
|
||
accessTokenKey := domain.GetAccessTokenRedisKey(token.ID)
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(token.RefreshToken)
|
||
uidKey := domain.GetUIDTokenRedisKey(token.UID)
|
||
|
||
_ = mr.Set(accessTokenKey, token.AccessToken)
|
||
_ = mr.Set(refreshTokenKey, token.ID)
|
||
_, _ = mr.SAdd(uidKey, token.ID)
|
||
_, _ = mr.SAdd(deviceKey, token.ID)
|
||
}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
deviceID string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful deletion of tokens by Device ID",
|
||
deviceID: deviceID,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "GetAccessTokensByDeviceID error",
|
||
deviceID: deviceID,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced error in GetAccessTokensByDeviceID") // 模擬錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced error in GetAccessTokensByDeviceID",
|
||
},
|
||
{
|
||
name: "Delete non-existent device ID",
|
||
deviceID: "nonexistent_device",
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 DeleteAccessTokensByDeviceID 方法
|
||
err := repo.DeleteAccessTokensByDeviceID(context.Background(), tt.deviceID)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 檢查是否刪除了 AccessToken、RefreshToken 和 UID 關聯的鍵
|
||
for _, token := range tokens {
|
||
accessTokenKey := domain.GetAccessTokenRedisKey(token.ID)
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(token.RefreshToken)
|
||
|
||
_, err = mr.Get(accessTokenKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
|
||
_, err = mr.Get(refreshTokenKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
|
||
}
|
||
|
||
// 檢查是否刪除了 deviceID 關聯的鍵
|
||
_, err = mr.Get(deviceKey)
|
||
assert.Equal(t, miniredis.ErrKeyNotFound, err)
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_DeleteOneTimeToken(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
ids := []string{"one_time_token1", "one_time_token2"}
|
||
tokens := []entity.Token{
|
||
{RefreshToken: "refresh_token1"},
|
||
{RefreshToken: "refresh_token2"},
|
||
}
|
||
|
||
// 在 Redis 中設置模擬的數據
|
||
for _, id := range ids {
|
||
_ = mr.Set(domain.GetRefreshTokenRedisKey(id), "dummy_value")
|
||
}
|
||
for _, token := range tokens {
|
||
_ = mr.Set(domain.GetRefreshTokenRedisKey(token.RefreshToken), "dummy_value")
|
||
}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
ids []string
|
||
tokens []entity.Token
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
}{
|
||
{
|
||
name: "Successful deletion of one-time tokens",
|
||
ids: ids,
|
||
tokens: tokens,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Deletion of non-existent one-time tokens",
|
||
ids: []string{"nonexistent_id1", "nonexistent_id2"},
|
||
tokens: []entity.Token{{RefreshToken: "nonexistent_refresh1"}, {RefreshToken: "nonexistent_refresh2"}},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "Redis delete error",
|
||
ids: ids,
|
||
tokens: tokens,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced Redis delete error") // 模擬 Redis 刪除錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced Redis delete error",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 DeleteOneTimeToken 方法
|
||
err := repo.DeleteOneTimeToken(context.Background(), tt.ids, tt.tokens)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
// 驗證 Redis 中的鍵已刪除
|
||
for _, id := range tt.ids {
|
||
key := domain.GetRefreshTokenRedisKey(id)
|
||
_, err := mr.Get(key)
|
||
assert.Equal(t, miniredis.ErrKeyNotFound, err)
|
||
}
|
||
for _, token := range tt.tokens {
|
||
key := domain.GetRefreshTokenRedisKey(token.RefreshToken)
|
||
_, err := mr.Get(key)
|
||
assert.Equal(t, miniredis.ErrKeyNotFound, err)
|
||
}
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestTokenRepository_DeleteAccessTokensByUID(t *testing.T) {
|
||
mr, r := setupMiniRedis()
|
||
defer mr.Close()
|
||
|
||
// 初始化 TokenRepository
|
||
repo := &TokenRepository{TokenRepositoryParam: TokenRepositoryParam{Redis: r}}
|
||
|
||
// 定義測試參數
|
||
uid := "user123"
|
||
tokens := []entity.Token{
|
||
{
|
||
ID: "token1",
|
||
UID: uid,
|
||
DeviceID: "device1",
|
||
AccessToken: "access1",
|
||
RefreshToken: "refresh1",
|
||
},
|
||
{
|
||
ID: "token2",
|
||
UID: uid,
|
||
DeviceID: "device2",
|
||
AccessToken: "access2",
|
||
RefreshToken: "refresh2",
|
||
},
|
||
}
|
||
|
||
// 在 Redis 中設置模擬的數據
|
||
for _, token := range tokens {
|
||
accessTokenKey := domain.GetAccessTokenRedisKey(token.ID)
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(token.RefreshToken)
|
||
uidKey := domain.GetUIDTokenRedisKey(uid)
|
||
|
||
_ = mr.Set(accessTokenKey, token.AccessToken)
|
||
_ = mr.Set(refreshTokenKey, token.ID)
|
||
_, _ = mr.SAdd(uidKey, token.ID)
|
||
}
|
||
|
||
// 定義測試場景
|
||
tests := []struct {
|
||
name string
|
||
uid string
|
||
prepareFunc func() error // 用於模擬 Redis 錯誤
|
||
wantErr bool
|
||
errMsg string
|
||
jump bool
|
||
}{
|
||
{
|
||
name: "Successful deletion of tokens by UID",
|
||
uid: uid,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "GetAccessTokensByUID error",
|
||
uid: uid,
|
||
prepareFunc: func() error {
|
||
mr.SetError("forced error in GetAccessTokensByUID") // 模擬查詢錯誤
|
||
return nil
|
||
},
|
||
wantErr: true,
|
||
errMsg: "forced error in GetAccessTokensByUID",
|
||
},
|
||
{
|
||
name: "Delete non-existent UID",
|
||
uid: "nonexistent_uid",
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
// 清除上一次的錯誤模擬
|
||
mr.SetError("")
|
||
|
||
// 執行準備函數(模擬 Redis 錯誤)
|
||
if tt.prepareFunc != nil {
|
||
tt.prepareFunc()
|
||
}
|
||
|
||
// 執行 DeleteAccessTokensByUID 方法
|
||
err := repo.DeleteAccessTokensByUID(context.Background(), tt.uid)
|
||
|
||
// 檢查是否出現預期錯誤
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
if err != nil {
|
||
assert.Contains(t, err.Error(), tt.errMsg)
|
||
}
|
||
} else {
|
||
assert.NoError(t, err)
|
||
|
||
if tt.jump {
|
||
// 驗證 Redis 中的鍵已刪除
|
||
for _, token := range tokens {
|
||
accessTokenKey := domain.GetAccessTokenRedisKey(token.ID)
|
||
refreshTokenKey := domain.GetRefreshTokenRedisKey(token.RefreshToken)
|
||
uidKey := domain.GetUIDTokenRedisKey(uid)
|
||
|
||
// 驗證 AccessToken 和 RefreshToken 鍵是否已刪除
|
||
_, err := mr.Get(accessTokenKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
|
||
_, err = mr.Get(refreshTokenKey)
|
||
assert.Error(t, miniredis.ErrKeyNotFound, err)
|
||
|
||
// 驗證 UID 關聯是否已刪除
|
||
uidSetMembers, err := mr.SMembers(uidKey)
|
||
assert.NoError(t, err)
|
||
assert.NotContains(t, uidSetMembers, token.ID)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清除模擬錯誤
|
||
mr.SetError("")
|
||
})
|
||
}
|
||
}
|