364 lines
8.7 KiB
Go
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) // 或者根据实际实现调整断言
|
|
}
|