init
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
_ "goravel/tests"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if err := facades.Artisan().Call("migrate"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exit := m.Run()
|
||||
|
||||
os.Exit(exit)
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
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 NotificationServiceTestSuite struct {
|
||||
suite.Suite
|
||||
tests.TestCase
|
||||
service services.NotificationService
|
||||
}
|
||||
|
||||
func TestNotificationServiceTestSuite(t *testing.T) {
|
||||
suite.Run(t, &NotificationServiceTestSuite{})
|
||||
}
|
||||
|
||||
func (s *NotificationServiceTestSuite) SetupSuite() {
|
||||
s.service = services.NewNotificationServiceImpl()
|
||||
}
|
||||
|
||||
func (s *NotificationServiceTestSuite) SetupTest() {
|
||||
s.RefreshDatabase()
|
||||
s.Seed()
|
||||
}
|
||||
|
||||
func (s *NotificationServiceTestSuite) TearDownTest() {
|
||||
// 清理测试数据
|
||||
_, _ = facades.Orm().Query().Where("type", "test").Delete(&models.Notification{})
|
||||
}
|
||||
|
||||
// ==================== Create 方法测试 ====================
|
||||
|
||||
// TestCreate_SingleNotification 测试创建单个通知
|
||||
func (s *NotificationServiceTestSuite) TestCreate_SingleNotification() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
receiverID := admin.ID
|
||||
notification, err := s.service.Create("测试标题", "测试内容", "test", nil, &receiverID)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotNil(notification)
|
||||
s.Equal("测试标题", notification.Title)
|
||||
s.Equal("测试内容", notification.Content)
|
||||
s.Equal("test", notification.Type)
|
||||
s.Equal(receiverID, *notification.ReceiverID)
|
||||
s.NotZero(notification.ID)
|
||||
}
|
||||
|
||||
// TestCreate_BroadcastToAllAdmins 测试批量创建通知给所有管理员
|
||||
func (s *NotificationServiceTestSuite) TestCreate_BroadcastToAllAdmins() {
|
||||
// 获取所有管理员
|
||||
var admins []models.Admin
|
||||
err := facades.Orm().Query().Find(&admins)
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotEmpty(admins, "应该有至少一个管理员")
|
||||
|
||||
// 创建通知给所有管理员(receiverID 为 nil)
|
||||
notification, err := s.service.Create("系统通知", "这是一条系统通知", "system", nil, nil)
|
||||
|
||||
s.NoError(err)
|
||||
s.NotNil(notification)
|
||||
s.Equal("系统通知", notification.Title)
|
||||
|
||||
// 验证所有管理员都收到了通知
|
||||
count, err := facades.Orm().Query().
|
||||
Model(&models.Notification{}).
|
||||
Where("title", "系统通知").
|
||||
Where("type", "system").
|
||||
Count()
|
||||
s.NoError(err)
|
||||
s.Equal(int64(len(admins)), count, "应该为每个管理员创建一条通知")
|
||||
}
|
||||
|
||||
// TestCreate_NoAdminsFound 测试没有管理员时的错误处理
|
||||
func (s *NotificationServiceTestSuite) TestCreate_NoAdminsFound() {
|
||||
// 删除所有管理员
|
||||
_, err := facades.Orm().Query().Delete(&models.Admin{})
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 尝试创建通知给所有管理员
|
||||
notification, err := s.service.Create("测试", "内容", "test", nil, nil)
|
||||
|
||||
s.Error(err)
|
||||
s.Nil(notification)
|
||||
|
||||
// 验证返回的是业务错误
|
||||
businessErr, ok := apperrors.GetBusinessError(err)
|
||||
s.True(ok, "应该返回业务错误")
|
||||
s.Equal("record_not_found", businessErr.Code)
|
||||
}
|
||||
|
||||
// ==================== List 方法测试 ====================
|
||||
|
||||
// TestList_BasicQuery 测试基本查询
|
||||
func (s *NotificationServiceTestSuite) TestList_BasicQuery() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建几条测试通知
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// 查询通知列表
|
||||
notifications, total, err := s.service.List(admin.ID, 1, 10, "", "")
|
||||
|
||||
s.NoError(err)
|
||||
s.GreaterOrEqual(int(total), 5)
|
||||
s.NotEmpty(notifications)
|
||||
}
|
||||
|
||||
// TestList_WithTypeFilter 测试按类型筛选
|
||||
func (s *NotificationServiceTestSuite) TestList_WithTypeFilter() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建不同类型的通知
|
||||
_, err = s.service.Create("系统通知", "内容", "system", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
_, err = s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 只查询 system 类型的通知
|
||||
notifications, total, err := s.service.List(admin.ID, 1, 10, "system", "")
|
||||
|
||||
s.NoError(err)
|
||||
s.GreaterOrEqual(int(total), 1)
|
||||
for _, notif := range notifications {
|
||||
s.Equal("system", notif.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// TestList_WithReadStatusFilter 测试按已读状态筛选
|
||||
func (s *NotificationServiceTestSuite) TestList_WithReadStatusFilter() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建通知
|
||||
notification, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 标记为已读
|
||||
err = s.service.MarkRead(admin.ID, notification.ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 查询未读通知
|
||||
unread, total, err := s.service.List(admin.ID, 1, 10, "", "false")
|
||||
s.NoError(err)
|
||||
|
||||
// 验证未读通知不包含已读的通知
|
||||
found := false
|
||||
for _, notif := range unread {
|
||||
if notif.ID == notification.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
s.False(found, "已读通知不应出现在未读列表中")
|
||||
|
||||
// 查询已读通知
|
||||
read, total, err := s.service.List(admin.ID, 1, 10, "", "true")
|
||||
s.NoError(err)
|
||||
s.GreaterOrEqual(int(total), 1)
|
||||
|
||||
// 验证已读通知包含该通知
|
||||
found = false
|
||||
for _, notif := range read {
|
||||
if notif.ID == notification.ID {
|
||||
found = true
|
||||
s.True(notif.IsRead)
|
||||
break
|
||||
}
|
||||
}
|
||||
s.True(found, "已读通知应出现在已读列表中")
|
||||
}
|
||||
|
||||
// TestList_Pagination 测试分页
|
||||
func (s *NotificationServiceTestSuite) TestList_Pagination() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建 15 条通知
|
||||
for i := 0; i < 15; i++ {
|
||||
_, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// 第一页,每页 10 条
|
||||
page1, total, err := s.service.List(admin.ID, 1, 10, "", "")
|
||||
s.NoError(err)
|
||||
s.GreaterOrEqual(int(total), 15)
|
||||
s.LessOrEqual(len(page1), 10)
|
||||
|
||||
// 第二页
|
||||
page2, _, err := s.service.List(admin.ID, 2, 10, "", "")
|
||||
s.NoError(err)
|
||||
s.LessOrEqual(len(page2), 10)
|
||||
|
||||
// 验证两页的数据不重复
|
||||
if len(page1) > 0 && len(page2) > 0 {
|
||||
s.NotEqual(page1[0].ID, page2[0].ID, "两页的数据应该不同")
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== MarkRead 方法测试 ====================
|
||||
|
||||
// TestMarkRead_Success 测试标记为已读成功
|
||||
func (s *NotificationServiceTestSuite) TestMarkRead_Success() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建通知
|
||||
notification, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
s.False(notification.IsRead, "新通知应该是未读状态")
|
||||
|
||||
// 标记为已读
|
||||
err = s.service.MarkRead(admin.ID, notification.ID)
|
||||
s.NoError(err)
|
||||
|
||||
// 验证已读状态
|
||||
var updated models.Notification
|
||||
err = facades.Orm().Query().Where("id", notification.ID).First(&updated)
|
||||
s.NoError(err)
|
||||
s.True(updated.IsRead)
|
||||
s.NotNil(updated.ReadAt)
|
||||
}
|
||||
|
||||
// TestMarkRead_NotFound 测试通知不存在
|
||||
func (s *NotificationServiceTestSuite) TestMarkRead_NotFound() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 尝试标记不存在的通知为已读
|
||||
err = s.service.MarkRead(admin.ID, 99999)
|
||||
s.Error(err)
|
||||
|
||||
// 验证返回的是业务错误
|
||||
businessErr, ok := apperrors.GetBusinessError(err)
|
||||
s.True(ok, "应该返回业务错误")
|
||||
s.Equal("record_not_found", businessErr.Code)
|
||||
}
|
||||
|
||||
// TestMarkRead_AlreadyRead 测试已读通知再次标记
|
||||
func (s *NotificationServiceTestSuite) TestMarkRead_AlreadyRead() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建并标记为已读
|
||||
notification, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.service.MarkRead(admin.ID, notification.ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 再次标记为已读(应该不报错)
|
||||
err = s.service.MarkRead(admin.ID, notification.ID)
|
||||
s.NoError(err, "已读通知再次标记应该不报错")
|
||||
}
|
||||
|
||||
// ==================== MarkAllRead 方法测试 ====================
|
||||
|
||||
// TestMarkAllRead_Success 测试标记所有通知为已读
|
||||
func (s *NotificationServiceTestSuite) TestMarkAllRead_Success() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建多条未读通知
|
||||
for i := 0; i < 5; i++ {
|
||||
_, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// 标记所有为已读
|
||||
err = s.service.MarkAllRead(admin.ID)
|
||||
s.NoError(err)
|
||||
|
||||
// 验证未读数量为 0
|
||||
count, err := s.service.UnreadCount(admin.ID)
|
||||
s.NoError(err)
|
||||
s.Equal(int64(0), count)
|
||||
}
|
||||
|
||||
// ==================== UnreadCount 方法测试 ====================
|
||||
|
||||
// TestUnreadCount_Basic 测试未读数量统计
|
||||
func (s *NotificationServiceTestSuite) TestUnreadCount_Basic() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建 3 条未读通知
|
||||
for i := 0; i < 3; i++ {
|
||||
_, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
// 验证未读数量
|
||||
count, err := s.service.UnreadCount(admin.ID)
|
||||
s.NoError(err)
|
||||
s.GreaterOrEqual(count, int64(3))
|
||||
|
||||
// 标记一条为已读
|
||||
notifications, _, err := s.service.List(admin.ID, 1, 1, "", "false")
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotEmpty(notifications)
|
||||
|
||||
err = s.service.MarkRead(admin.ID, notifications[0].ID)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 验证未读数量减少
|
||||
newCount, err := s.service.UnreadCount(admin.ID)
|
||||
s.NoError(err)
|
||||
s.Equal(count-1, newCount)
|
||||
}
|
||||
|
||||
// ==================== ListRecent 方法测试 ====================
|
||||
|
||||
// TestListRecent_Basic 测试获取最近通知
|
||||
func (s *NotificationServiceTestSuite) TestListRecent_Basic() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 创建多条通知
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := s.service.Create("测试通知", "内容", "test", nil, &admin.ID)
|
||||
s.Require().NoError(err)
|
||||
time.Sleep(10 * time.Millisecond) // 确保时间戳不同
|
||||
}
|
||||
|
||||
// 获取最近 5 条
|
||||
recent, err := s.service.ListRecent(admin.ID, 5)
|
||||
s.NoError(err)
|
||||
s.LessOrEqual(len(recent), 5)
|
||||
s.GreaterOrEqual(len(recent), 1)
|
||||
|
||||
// 验证返回了数据(排序验证在数据库层面完成,这里只验证数据存在)
|
||||
s.NotEmpty(recent, "应该返回最近的通知")
|
||||
}
|
||||
|
||||
// TestListRecent_LimitValidation 测试限制验证
|
||||
func (s *NotificationServiceTestSuite) TestListRecent_LimitValidation() {
|
||||
var admin models.Admin
|
||||
err := facades.Orm().Query().First(&admin)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// 测试负数限制(应该使用默认值 5)
|
||||
recent, err := s.service.ListRecent(admin.ID, -1)
|
||||
s.NoError(err)
|
||||
s.LessOrEqual(len(recent), 5)
|
||||
|
||||
// 测试超过最大值的限制(应该使用默认值 5)
|
||||
recent, err = s.service.ListRecent(admin.ID, 100)
|
||||
s.NoError(err)
|
||||
s.LessOrEqual(len(recent), 5)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"goravel/app/models"
|
||||
"goravel/app/services"
|
||||
"goravel/app/utils/traceid"
|
||||
)
|
||||
|
||||
func TestSystemLogTraceIDPersistence(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uniqueTraceID := fmt.Sprintf("trace-%d", time.Now().UnixNano())
|
||||
log := models.SystemLog{
|
||||
Level: "info",
|
||||
Module: "trace_test",
|
||||
TraceID: uniqueTraceID,
|
||||
Message: "trace id persistence test",
|
||||
Context: `{"test":true}`,
|
||||
}
|
||||
|
||||
err := facades.Orm().Query().Create(&log)
|
||||
assert.NoError(t, err)
|
||||
assert.NotZero(t, log.ID)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_, _ = facades.Orm().Query().Where("id", log.ID).Delete(&models.SystemLog{})
|
||||
})
|
||||
|
||||
var stored models.SystemLog
|
||||
err = facades.Orm().Query().Where("id", log.ID).First(&stored)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, uniqueTraceID, stored.TraceID)
|
||||
|
||||
var filtered models.SystemLog
|
||||
err = facades.Orm().Query().Where("trace_id", uniqueTraceID).First(&filtered)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, log.ID, filtered.ID)
|
||||
}
|
||||
|
||||
func TestSystemLogServiceRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
service := services.NewSystemLogService()
|
||||
ctx, traceID := traceid.EnsureContext(context.Background())
|
||||
|
||||
err := service.Record(ctx, "error", "unit-test", "trace helper test", map[string]any{"ok": true})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var stored models.SystemLog
|
||||
err = facades.Orm().Query().Where("trace_id", traceID).Order("id desc").First(&stored)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "unit-test", stored.Module)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_, _ = facades.Orm().Query().Where("trace_id", traceID).Delete(&models.SystemLog{})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
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) // 或者根据实际实现调整断言
|
||||
}
|
||||
Reference in New Issue
Block a user