Files
server/tests/services/token_test.go
T
2026-01-16 15:49:34 +08:00

364 lines
8.7 KiB
Go

package services
import (
"testing"
"time"
"github.com/goravel/framework/facades"
"github.com/stretchr/testify/suite"
apperrors "goravel/app/errors"
"goravel/app/models"
"goravel/app/services"
"goravel/tests"
)
type TokenServiceFeatureTestSuite struct {
suite.Suite
tests.TestCase
service services.TokenService
}
func TestTokenServiceFeatureTestSuite(t *testing.T) {
suite.Run(t, &TokenServiceFeatureTestSuite{})
}
func (s *TokenServiceFeatureTestSuite) SetupSuite() {
s.service = services.NewTokenServiceImpl()
}
func (s *TokenServiceFeatureTestSuite) SetupTest() {
s.RefreshDatabase()
s.Seed()
}
func (s *TokenServiceFeatureTestSuite) TearDownTest() {
// 清理测试数据
_, _ = facades.Orm().Query().Where("name LIKE ?", "test_%").Delete(&models.PersonalAccessToken{})
}
// ==================== CreateToken 方法测试 ====================
// TestCreateToken_Success 测试创建 token 成功
func (s *TokenServiceFeatureTestSuite) TestCreateToken_Success() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
expiresAt := time.Now().Add(24 * time.Hour)
plainToken, accessToken, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
&expiresAt,
"Chrome",
"127.0.0.1",
"Windows",
"session123",
)
s.NoError(err)
s.NotEmpty(plainToken)
s.NotNil(accessToken)
s.Equal("admin", accessToken.TokenableType)
s.Equal(admin.ID, accessToken.TokenableID)
s.Equal("test_token", accessToken.Name)
s.Equal("Chrome", accessToken.Browser)
s.Equal("127.0.0.1", accessToken.IP)
s.Equal("Windows", accessToken.OS)
s.Equal("session123", accessToken.SessionID)
s.NotNil(accessToken.LastUsedAt, "创建时应该设置 LastUsedAt")
}
// TestCreateToken_WithoutExpiresAt 测试创建无过期时间的 token
func (s *TokenServiceFeatureTestSuite) TestCreateToken_WithoutExpiresAt() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
plainToken, accessToken, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
nil, // 无过期时间
"",
"",
"",
"",
)
s.NoError(err)
s.NotEmpty(plainToken)
s.NotNil(accessToken)
s.Nil(accessToken.ExpiresAt, "无过期时间的 token 应该 ExpiresAt 为 nil")
}
// TestCreateToken_GenerateSessionID 测试自动生成 SessionID
func (s *TokenServiceFeatureTestSuite) TestCreateToken_GenerateSessionID() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
plainToken, accessToken, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
nil,
"",
"",
"",
"", // 不提供 SessionID,应该自动生成
)
s.NoError(err)
s.NotEmpty(plainToken)
s.NotNil(accessToken)
s.NotEmpty(accessToken.SessionID, "应该自动生成 SessionID")
}
// ==================== FindToken 方法测试 ====================
// TestFindToken_Success 测试查找 token 成功
func (s *TokenServiceFeatureTestSuite) TestFindToken_Success() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
// 创建 token
plainToken, _, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
nil,
"",
"",
"",
"",
)
s.Require().NoError(err)
// 查找 token
foundToken, err := s.service.FindToken(plainToken)
s.NoError(err)
s.NotNil(foundToken)
s.Equal(admin.ID, foundToken.TokenableID)
}
// TestFindToken_EmptyToken 测试空 token
func (s *TokenServiceFeatureTestSuite) TestFindToken_EmptyToken() {
token, err := s.service.FindToken("")
s.Error(err)
s.Nil(token)
// 验证返回的是业务错误
businessErr, ok := apperrors.GetBusinessError(err)
s.True(ok, "应该返回业务错误")
s.Equal("invalid_argument", businessErr.Code)
}
// TestFindToken_NotFound 测试 token 不存在
func (s *TokenServiceFeatureTestSuite) TestFindToken_NotFound() {
// 使用一个不存在的 token
token, err := s.service.FindToken("non-existent-token-12345")
s.Error(err)
s.Nil(token)
}
// TestFindToken_ExpiredToken 测试过期 token
func (s *TokenServiceFeatureTestSuite) TestFindToken_ExpiredToken() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
// 创建已过期的 token
pastTime := time.Now().Add(-1 * time.Hour)
plainToken, _, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
&pastTime,
"",
"",
"",
"",
)
s.Require().NoError(err)
// 尝试查找过期 token
token, err := s.service.FindToken(plainToken)
s.Error(err)
s.Nil(token)
// 验证返回的是业务错误
businessErr, ok := apperrors.GetBusinessError(err)
s.True(ok, "应该返回业务错误")
s.Equal("invalid_argument", businessErr.Code)
s.Contains(businessErr.Message, "expired")
// 验证过期 token 已被删除
count, err := facades.Orm().Query().
Model(&models.PersonalAccessToken{}).
Where("tokenable_id", admin.ID).
Where("name", "test_token").
Count()
s.NoError(err)
s.Equal(int64(0), count, "过期 token 应该被删除")
}
// ==================== DeleteToken 方法测试 ====================
// TestDeleteToken_Success 测试删除 token 成功
func (s *TokenServiceFeatureTestSuite) TestDeleteToken_Success() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
// 创建 token
plainToken, _, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
nil,
"",
"",
"",
"",
)
s.Require().NoError(err)
// 删除 token
err = s.service.DeleteToken(plainToken)
s.NoError(err)
// 验证 token 已被删除
foundToken, err := s.service.FindToken(plainToken)
s.Error(err)
s.Nil(foundToken)
}
// ==================== DeleteTokensByUser 方法测试 ====================
// TestDeleteTokensByUser_Success 测试删除用户的所有 token
func (s *TokenServiceFeatureTestSuite) TestDeleteTokensByUser_Success() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
// 创建多个 token
for i := 0; i < 3; i++ {
_, _, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
nil,
"",
"",
"",
"",
)
s.Require().NoError(err)
}
// 验证创建成功
tokens, err := s.service.GetTokensByUser("admin", admin.ID)
s.Require().NoError(err)
s.GreaterOrEqual(len(tokens), 3)
// 删除用户的所有 token
err = s.service.DeleteTokensByUser("admin", admin.ID)
s.NoError(err)
// 验证所有 token 已被删除
tokens, err = s.service.GetTokensByUser("admin", admin.ID)
s.NoError(err)
s.Empty(tokens)
}
// ==================== GetTokensByUser 方法测试 ====================
// TestGetTokensByUser_Success 测试获取用户的所有 token
func (s *TokenServiceFeatureTestSuite) TestGetTokensByUser_Success() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
// 创建多个 token
tokenNames := []string{"token1", "token2", "token3"}
for _, name := range tokenNames {
_, _, err := s.service.CreateToken(
"admin",
admin.ID,
name,
nil,
"",
"",
"",
"",
)
s.Require().NoError(err)
time.Sleep(10 * time.Millisecond) // 确保时间戳不同
}
// 获取用户的所有 token
tokens, err := s.service.GetTokensByUser("admin", admin.ID)
s.NoError(err)
s.GreaterOrEqual(len(tokens), len(tokenNames))
// 验证返回了数据(排序验证在数据库层面完成,这里只验证数据存在)
s.NotEmpty(tokens, "应该返回用户的 token")
}
// ==================== UpdateLastUsedAt 方法测试 ====================
// TestUpdateLastUsedAt_Success 测试更新最后使用时间
func (s *TokenServiceFeatureTestSuite) TestUpdateLastUsedAt_Success() {
var admin models.Admin
err := facades.Orm().Query().First(&admin)
s.Require().NoError(err)
// 创建 token
plainToken, accessToken, err := s.service.CreateToken(
"admin",
admin.ID,
"test_token",
nil,
"",
"",
"",
"",
)
s.Require().NoError(err)
initialLastUsedAt := accessToken.LastUsedAt
s.Require().NotNil(initialLastUsedAt)
// 等待一小段时间确保时间不同
time.Sleep(100 * time.Millisecond)
// 更新最后使用时间
err = s.service.UpdateLastUsedAt(plainToken)
s.NoError(err)
// 验证最后使用时间已更新
updatedToken, err := s.service.FindToken(plainToken)
s.NoError(err)
s.NotNil(updatedToken)
s.NotNil(updatedToken.LastUsedAt)
s.True(updatedToken.LastUsedAt.After(*initialLastUsedAt),
"最后使用时间应该已更新")
}
// TestUpdateLastUsedAt_NotFound 测试更新不存在的 token
func (s *TokenServiceFeatureTestSuite) TestUpdateLastUsedAt_NotFound() {
// 尝试更新不存在的 token
err := s.service.UpdateLastUsedAt("non-existent-token")
// 注意:UpdateLastUsedAt 可能不会返回错误,只是不更新任何记录
// 这取决于 ORM 的实现
s.NoError(err) // 或者根据实际实现调整断言
}