Files
gochat/internal/service/service_test_helper.go_BAK
T
2026-06-04 15:44:48 +08:00

822 lines
27 KiB
Plaintext

package service
import (
"context"
"encoding/json"
"fmt"
"os"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/redis/go-redis/v9"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/auth"
"github.com/gochat/gochat/internal/channel"
"github.com/gochat/gochat/internal/config"
"github.com/gochat/gochat/internal/llm"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
)
// setupServiceTestDB 为 service 测试创建 SQLite 内存数据库并自动迁移所有模型。
// 每个测试函数获取独立的数据库实例,通过 t.Cleanup 关闭连接。
func setupServiceTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
t.Fatalf("无法打开 SQLite 测试数据库: %v", err)
}
// 自动迁移所有核心模型
if err := db.AutoMigrate(
&model.Account{},
&model.User{},
&model.AccountUser{},
&model.Inbox{},
&model.Contact{},
&model.ContactInbox{},
&model.Conversation{},
&model.Message{},
&model.Notification{},
&model.NotificationPreference{},
&model.Attachment{},
&model.InboxMember{},
&model.CustomRole{},
&model.PlatformApp{},
&model.CaptainCustomTool{},
&model.ReportingEvent{},
&model.ReportingEventsRollup{},
&model.DashboardApp{},
&model.Portal{},
&model.Category{},
&model.Article{},
&model.Folder{},
&model.PortalMember{},
&model.Team{},
&model.TeamMember{},
&model.Tag{},
&model.ConversationLabel{},
&model.PushToken{},
&model.WebhookSubscription{},
&model.WebhookDelivery{},
); err != nil {
t.Fatalf("无法自动迁移模型: %v", err)
}
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
return db
}
// setupAccountService 创建 AccountRepo + AccountService 测试实例。
func setupAccountService(t *testing.T) (*gorm.DB, *repository.AccountRepo, *AccountService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewAccountRepo(db)
svc := NewAccountService(repo)
return db, repo, svc
}
// setupInboxMemberService 创建 InboxMemberRepo + InboxMemberService 测试实例。
func setupInboxMemberService(t *testing.T) (*gorm.DB, *repository.InboxMemberRepo, *InboxMemberService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewInboxMemberRepo(db)
svc := NewInboxMemberService(repo)
return db, repo, svc
}
// setupInboxService 创建 InboxRepo + InboxService 测试实例。
func setupInboxService(t *testing.T) (*gorm.DB, *repository.InboxRepo, *InboxService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewInboxRepo(db)
svc := NewInboxService(repo)
return db, repo, svc
}
// setupContactService 创建 ContactRepo + ContactInboxService + ContactService 测试实例。
func setupContactService(t *testing.T) (*gorm.DB, *repository.ContactRepo, *ContactService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewContactRepo(db)
contactInboxSvc := NewContactInboxService(repository.NewContactInboxRepo(db))
svc := NewContactService(repo, contactInboxSvc)
return db, repo, svc
}
// setupContactInboxService 创建 ContactInboxRepo + ContactInboxService 测试实例。
func setupContactInboxService(t *testing.T) (*gorm.DB, *repository.ContactInboxRepo, *ContactInboxService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewContactInboxRepo(db)
svc := NewContactInboxService(repo)
return db, repo, svc
}
// setupConversationService 创建 ConversationRepo + MessageRepo + ConversationService 测试实例。
func setupConversationService(t *testing.T) (*gorm.DB, *repository.ConversationRepo, *repository.MessageRepo, *ConversationService) {
t.Helper()
db := setupServiceTestDB(t)
convRepo := repository.NewConversationRepo(db)
msgRepo := repository.NewMessageRepo(db)
dispatcher := channel.NewDispatcher()
inboxMemberSvc := NewInboxMemberService(repository.NewInboxMemberRepo(db))
svc := NewConversationService(convRepo, msgRepo, dispatcher, inboxMemberSvc)
return db, convRepo, msgRepo, svc
}
// setupAttachmentService 创建 AttachmentRepo + AttachmentService 测试实例。
func setupAttachmentService(t *testing.T) (*gorm.DB, *repository.AttachmentRepo, *AttachmentService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewAttachmentRepo(db)
svc := NewAttachmentService(repo)
return db, repo, svc
}
// createTestAccount 在数据库中创建一个测试 Account 并返回。
func createTestAccount(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
account := &model.Account{Name: "测试账户", Locale: "en", Status: "active"}
if err := db.Create(account).Error; err != nil {
t.Fatalf("无法创建测试账户: %v", err)
}
return account
}
// createTestAccountWithName 在数据库中创建一个指定名称的测试 Account 并返回。
func createTestAccountWithName(t *testing.T, db *gorm.DB, name string) *model.Account {
t.Helper()
account := &model.Account{Name: name, Locale: "en", Status: "active"}
if err := db.Create(account).Error; err != nil {
t.Fatalf("无法创建测试账户: %v", err)
}
return account
}
// createTestUser 在数据库中创建一个测试 User 并返回。
func createTestUser(t *testing.T, db *gorm.DB, accountID uint) *model.User {
t.Helper()
user := &model.User{
AccountID: accountID,
Name: "测试用户",
Email: "test@example.com",
Password: "hashedpassword",
Role: "agent",
Active: true,
}
if err := db.Create(user).Error; err != nil {
t.Fatalf("无法创建测试用户: %v", err)
}
return user
}
// createTestInbox 在数据库中创建一个测试 Inbox 并返回。
func createTestInbox(t *testing.T, db *gorm.DB, accountID uint, channelType string) *model.Inbox {
t.Helper()
inbox := &model.Inbox{
AccountID: accountID,
Name: "测试收件箱",
ChannelType: channelType,
Enabled: true,
}
if err := db.Create(inbox).Error; err != nil {
t.Fatalf("无法创建测试收件箱: %v", err)
}
return inbox
}
// createTestContact 在数据库中创建一个测试 Contact 并返回。
func createTestContact(t *testing.T, db *gorm.DB, accountID uint) *model.Contact {
t.Helper()
contact := &model.Contact{
AccountID: accountID,
Name: "测试联系人",
Email: "contact@example.com",
}
if err := db.Create(contact).Error; err != nil {
t.Fatalf("无法创建测试联系人: %v", err)
}
return contact
}
// createTestConversation 在数据库中创建一个测试 Conversation 并返回。
func createTestConversation(t *testing.T, db *gorm.DB, accountID, inboxID, contactID uint) *model.Conversation {
t.Helper()
conv := &model.Conversation{
AccountID: accountID,
InboxID: inboxID,
ContactID: contactID,
Status: string(model.ConversationStatusOpen),
Priority: string(model.ConversationPriorityMedium),
}
if err := db.Create(conv).Error; err != nil {
t.Fatalf("无法创建测试对话: %v", err)
}
return conv
}
// createTestMessage 在数据库中创建一个测试 Message 并返回。
func createTestMessage(t *testing.T, db *gorm.DB, accountID, inboxID, conversationID uint) *model.Message {
t.Helper()
msg := &model.Message{
AccountID: accountID,
InboxID: inboxID,
ConversationID: conversationID,
Content: "测试消息",
MessageType: "incoming",
ContentType: "text",
SenderType: "contact",
}
if err := db.Create(msg).Error; err != nil {
t.Fatalf("无法创建测试消息: %v", err)
}
return msg
}
// createTestAttachment 在数据库中创建一个测试 Attachment 并返回。
func createTestAttachment(t *testing.T, db *gorm.DB, messageID, accountID uint) *model.Attachment {
t.Helper()
att := &model.Attachment{
MessageID: messageID,
AccountID: accountID,
FileType: "image",
FileURL: "https://example.com/test.png",
FileName: "test.png",
FileSize: 1024,
}
if err := db.Create(att).Error; err != nil {
t.Fatalf("无法创建测试附件: %v", err)
}
return att
}
// setupCaptainCustomToolService 创建 CaptainCustomToolRepo + CaptainCustomToolService 测试实例。
func setupCaptainCustomToolService(t *testing.T) (*gorm.DB, *repository.CaptainCustomToolRepo, *CaptainCustomToolService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewCaptainCustomToolRepo(db)
svc := NewCaptainCustomToolService(repo)
return db, repo, svc
}
// createTestCaptainCustomTool 在数据库中创建一个测试 CaptainCustomTool 并返回。
func createTestCaptainCustomTool(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.CaptainCustomTool)) *model.CaptainCustomTool {
t.Helper()
tool := &model.CaptainCustomTool{
AccountID: accountID,
Title: "测试工具",
Slug: "test-tool",
Description: "用于测试的自定义工具",
EndpointURL: "https://example.com/api/test",
HTTPMethod: "GET",
AuthType: model.ToolAuthTypeNone,
Enabled: true,
}
for _, fn := range overrides {
fn(tool)
}
if err := db.Create(tool).Error; err != nil {
t.Fatalf("无法创建测试自定义工具: %v", err)
}
return tool
}
// setupAuthService 创建 AuthService 测试实例(含 JWTService、RefreshTokenStore、OAuthService、MFAService)。
// 返回 db、JWTService、RefreshTokenStore、OAuthService、MFAService、AuthService,以及用于验证的 miniredis 实例。
func setupAuthService(t *testing.T) (*gorm.DB, *auth.JWTService, *auth.RefreshTokenStore, *auth.OAuthService, *auth.MFAService, *AuthService) {
t.Helper()
db := setupServiceTestDB(t)
// 创建 miniredis 代替真实 Redis
mr := miniredis.RunT(t)
rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()})
t.Cleanup(func() {
rdb.Close()
mr.Close()
})
jwtCfg := &config.JWTConfig{
Secret: "test-secret-key-min-32-chars!!",
ExpiryHours: 24,
RefreshExpiryHours: 7 * 24,
AccessExpiryMinutes: 15,
}
jwtSvc := auth.NewJWTService(jwtCfg)
refreshStore := auth.NewRefreshTokenStore(rdb, jwtCfg)
appCfg := &config.Config{
JWT: *jwtCfg,
OAuth: config.OAuthConfig{
Google: config.OAuthProviderConfig{
ClientID: "test-google-client-id",
ClientSecret: "test-google-client-secret",
RedirectURL: "http://localhost:8080/auth/google/callback",
},
GitHub: config.OAuthProviderConfig{
ClientID: "test-github-client-id",
ClientSecret: "test-github-client-secret",
RedirectURL: "http://localhost:8080/auth/github/callback",
},
},
}
oauthSvc := auth.NewOAuthService(db, appCfg)
mfaSvc := auth.NewMFAService(db)
svc := NewAuthService(db, jwtSvc, refreshStore, nil, oauthSvc, mfaSvc)
return db, jwtSvc, refreshStore, oauthSvc, mfaSvc, svc
}
// setupNotificationService 创建 NotificationRepo + NotificationPreferenceRepo + NotificationService 测试实例。
func setupNotificationService(t *testing.T) (*gorm.DB, *repository.NotificationRepo, *repository.NotificationPreferenceRepo, *NotificationService) {
t.Helper()
db := setupServiceTestDB(t)
notifRepo := repository.NewNotificationRepo(db)
prefRepo := repository.NewNotificationPreferenceRepo(db)
svc := NewNotificationService(db, notifRepo, prefRepo)
return db, notifRepo, prefRepo, svc
}
// createTestNotification 在数据库中创建一个测试 Notification 并返回。
func createTestNotification(t *testing.T, db *gorm.DB, userID uint, accountID *uint, notifType string) *model.Notification {
t.Helper()
notification := &model.Notification{
UserID: userID,
AccountID: accountID,
NotificationType: notifType,
PrimaryActorType: "Conversation",
PrimaryActorID: 1,
PushEnabled: false,
EmailEnabled: false,
}
if err := db.Create(notification).Error; err != nil {
t.Fatalf("无法创建测试通知: %v", err)
}
return notification
}
// setupRBACService 创建 RBACService 测试实例。
func setupRBACService(t *testing.T) (*gorm.DB, *RBACService) {
t.Helper()
db := setupServiceTestDB(t)
svc := NewRBACService(db)
return db, svc
}
// createTestAccountForRBAC 在数据库中创建一个测试 Account 并返回。
func createTestAccountForRBAC(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
acc := &model.Account{
Name: "Test Account RBAC",
Active: true,
Status: "active",
}
if err := db.Create(acc).Error; err != nil {
t.Fatalf("无法创建测试账户: %v", err)
}
return acc
}
// createTestUserForRBAC 在数据库中创建一个测试 User 并返回。
func createTestUserForRBAC(t *testing.T, db *gorm.DB) *model.User {
t.Helper()
user := &model.User{
Name: "Test User RBAC",
Email: fmt.Sprintf("rbac_user_%d@test.com", time.Now().UnixNano()),
Password: "hashed_password",
Active: true,
}
if err := db.Create(user).Error; err != nil {
t.Fatalf("无法创建测试用户: %v", err)
}
return user
}
// mockLLMProvider is a mock implementation of llm.Provider for testing.
// It allows tests to control the response returned by ChatCompletion and CreateEmbedding.
type mockLLMProvider struct {
chatResponse *llm.ChatResponse
chatError error
embeddingResponse *llm.EmbeddingResponse
embeddingError error
lastChatRequest *llm.ChatRequest
}
func (m *mockLLMProvider) ChatCompletion(ctx context.Context, req llm.ChatRequest) (*llm.ChatResponse, error) {
m.lastChatRequest = &req
return m.chatResponse, m.chatError
}
func (m *mockLLMProvider) CreateEmbedding(ctx context.Context, req llm.EmbeddingRequest) (*llm.EmbeddingResponse, error) {
return m.embeddingResponse, m.embeddingError
}
// setupCaptainDocumentService 创建 CaptainDocumentRepo + mockLLMProvider + CaptainDocumentService 测试实例。
// 使用 mock LLM provider 避免真实 API 调用,返回 mock 以便测试中自定义 LLM 行为。
func setupCaptainDocumentService(t *testing.T) (*gorm.DB, *mockLLMProvider, *CaptainDocumentService) {
t.Helper()
db := setupServiceTestDB(t)
// 迁移 captain_document 相关模型
if err := db.AutoMigrate(&model.CaptainDocument{}); err != nil {
t.Fatalf("无法自动迁移 captain_document 模型: %v", err)
}
docRepo := repository.NewCaptainDocumentRepo(db)
mockProvider := &mockLLMProvider{}
svc := NewCaptainDocumentService(docRepo, mockProvider)
return db, mockProvider, svc
}
// createTestCaptainDocument 在数据库中创建一个测试 CaptainDocument 并返回。
func createTestCaptainDocument(t *testing.T, db *gorm.DB, accountID, assistantID uint, name, externalLink string) *model.CaptainDocument {
t.Helper()
doc := &model.CaptainDocument{
AccountID: accountID,
AssistantID: assistantID,
Name: name,
ExternalLink: externalLink,
Status: model.DocumentStatusPending,
}
if err := db.Create(doc).Error; err != nil {
t.Fatalf("无法创建测试 CaptainDocument: %v", err)
}
return doc
}
// setupCaptainAssistantService 创建 CaptainAssistantRepo + CaptainInboxRepo + CaptainDocumentRepo + CaptainAssistantResponseRepo + mockLLMProvider + CaptainAssistantService 测试实例。
// 使用 mock LLM provider 避免真实 API 调用,返回 mock 以便测试中自定义 LLM 行为。
func setupCaptainAssistantService(t *testing.T) (*gorm.DB, *mockLLMProvider, *CaptainAssistantService) {
t.Helper()
db := setupServiceTestDB(t)
// 迁移 captain_assistant 相关模型
if err := db.AutoMigrate(
&model.CaptainAssistant{},
&model.CaptainInbox{},
&model.CaptainDocument{},
&model.CaptainAssistantResponse{},
); err != nil {
t.Fatalf("无法自动迁移 captain_assistant 相关模型: %v", err)
}
assistantRepo := repository.NewCaptainAssistantRepo(db)
inboxRepo := repository.NewCaptainInboxRepo(db)
documentRepo := repository.NewCaptainDocumentRepo(db)
responseRepo := repository.NewCaptainAssistantResponseRepo(db)
mockProvider := &mockLLMProvider{}
svc := NewCaptainAssistantService(assistantRepo, inboxRepo, documentRepo, responseRepo, mockProvider)
return db, mockProvider, svc
}
// setupCaptainScenarioService 创建 CaptainScenarioRepo + CaptainScenarioService 测试实例。
func setupCaptainScenarioService(t *testing.T) (*gorm.DB, *repository.CaptainScenarioRepo, *CaptainScenarioService) {
t.Helper()
db := setupServiceTestDB(t)
// 迁移 captain_scenario 相关模型
if err := db.AutoMigrate(&model.CaptainScenario{}, &model.CaptainAssistant{}); err != nil {
t.Fatalf("无法自动迁移 captain_scenario 模型: %v", err)
}
repo := repository.NewCaptainScenarioRepo(db)
svc := NewCaptainScenarioService(repo)
return db, repo, svc
}
// createTestCaptainAssistant 在数据库中创建一个测试 CaptainAssistant 并返回。
func createTestCaptainAssistant(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.CaptainAssistant)) *model.CaptainAssistant {
t.Helper()
assistant := &model.CaptainAssistant{
AccountID: accountID,
Name: "测试助手",
Status: model.AssistantStatusActive,
}
for _, fn := range overrides {
fn(assistant)
}
if err := db.Create(assistant).Error; err != nil {
t.Fatalf("无法创建测试助手: %v", err)
}
return assistant
}
// createTestCaptainScenario 在数据库中创建一个测试 CaptainScenario 并返回。
func createTestCaptainScenario(t *testing.T, db *gorm.DB, accountID, assistantID uint, overrides ...func(*model.CaptainScenario)) *model.CaptainScenario {
t.Helper()
scenario := &model.CaptainScenario{
AccountID: accountID,
AssistantID: assistantID,
Title: "测试场景",
Description: "用于测试的场景",
Instruction: "按照以下步骤操作",
Enabled: true,
}
for _, fn := range overrides {
fn(scenario)
}
if err := db.Create(scenario).Error; err != nil {
t.Fatalf("无法创建测试场景: %v", err)
}
return scenario
}
// setupCopilotService 创建 CopilotThreadRepo + CopilotMessageRepo + CopilotService 测试实例。
// 使用 mock LLM provider 避免真实 API 调用,返回 mock 以便测试中自定义 LLM 行为。
func setupCopilotService(t *testing.T) (*gorm.DB, *mockLLMProvider, *CopilotService) {
t.Helper()
db := setupServiceTestDB(t)
// 迁移 copilot 相关模型
if err := db.AutoMigrate(&model.CopilotThread{}, &model.CopilotMessage{}); err != nil {
t.Fatalf("无法自动迁移 copilot 模型: %v", err)
}
threadRepo := repository.NewCopilotThreadRepo(db)
messageRepo := repository.NewCopilotMessageRepo(db)
mockProvider := &mockLLMProvider{}
svc := NewCopilotService(threadRepo, messageRepo, mockProvider)
return db, mockProvider, svc
}
// --- Analytics Service Helpers ---
// skipIfSQLite skips the test when the underlying database is SQLite.
// PG-only features (ILIKE, NOW(), NULLS LAST, etc.) should call this.
// In service tests, setupServiceTestDB creates a SQLite in-memory DB by default.
// To test PG-only features, set GOCHAT_TEST_DB=postgres and the helper must be
// updated to use PG instead. For now, service tests always use SQLite so
// PG-only features are skipped.
func skipIfSQLite(t *testing.T) {
t.Helper()
// Service test helpers currently always use SQLite in-memory.
// When GOCHAT_TEST_DB=postgres support is added at service level,
// this should check the actual driver like the repository version does.
if os.Getenv("GOCHAT_TEST_DB") != "postgres" {
t.Skip("Skipping: this test requires PostgreSQL (ILIKE / NOW() / NULLS LAST etc.)")
}
}
// setupAnalyticsService 创建 ReportingEventRepo + ReportingEventsRollupRepo + AnalyticsService 测试实例。
func setupAnalyticsService(t *testing.T) (*gorm.DB, *repository.ReportingEventRepo, *repository.ReportingEventsRollupRepo, *AnalyticsService) {
t.Helper()
db := setupServiceTestDB(t)
eventRepo := repository.NewReportingEventRepo(db)
rollupRepo := repository.NewReportingEventsRollupRepo(db)
svc := NewAnalyticsService(eventRepo, rollupRepo)
return db, eventRepo, rollupRepo, svc
}
// createTestReportingEvent 在数据库中创建一个测试 ReportingEvent 并返回。
func createTestReportingEvent(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.ReportingEvent)) *model.ReportingEvent {
t.Helper()
event := &model.ReportingEvent{
AccountID: accountID,
Name: model.MetricNameFirstResponse,
Value: 120.5,
ValueInBusinessHours: 60.0,
}
for _, fn := range overrides {
fn(event)
}
if err := db.Create(event).Error; err != nil {
t.Fatalf("无法创建测试 ReportingEvent: %v", err)
}
return event
}
// createTestRollup 在数据库中创建一个测试 ReportingEventsRollup 并返回。
func createTestRollup(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.ReportingEventsRollup)) *model.ReportingEventsRollup {
t.Helper()
rollup := &model.ReportingEventsRollup{
AccountID: accountID,
Date: time.Date(2026, 1, 15, 0, 0, 0, 0, time.UTC),
DimensionType: model.DimensionAccount,
DimensionID: accountID,
Metric: model.MetricFirstResponse,
Count: 5,
SumValue: 600.0,
SumValueBusinessHours: 300.0,
}
for _, fn := range overrides {
fn(rollup)
}
if err := db.Create(rollup).Error; err != nil {
t.Fatalf("无法创建测试 ReportingEventsRollup: %v", err)
}
return rollup
}
// setupDashboardAppService 创建 DashboardAppRepo + DashboardAppService 测试实例。
func setupDashboardAppService(t *testing.T) (*gorm.DB, *repository.DashboardAppRepo, *DashboardAppService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewDashboardAppRepo(db)
svc := NewDashboardAppService(repo)
return db, repo, svc
}
// setupPlatformAppService 创建 PlatformAppRepo + PlatformAppService 测试实例。
func setupPlatformAppService(t *testing.T) (*gorm.DB, *repository.PlatformAppRepo, *PlatformAppService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewPlatformAppRepo(db)
svc := NewPlatformAppService(repo)
return db, repo, svc
}
// createTestPlatformApp 在数据库中创建一个测试 PlatformApp 并返回。
func createTestPlatformApp(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.PlatformApp)) *model.PlatformApp {
t.Helper()
app := &model.PlatformApp{
AccountID: accountID,
Name: "测试平台应用",
APIKey: "test-api-key-" + fmt.Sprintf("%d", time.Now().UnixNano()),
Description: "测试描述",
Type: "api",
Status: "active",
}
for _, fn := range overrides {
fn(app)
}
if err := db.Create(app).Error; err != nil {
t.Fatalf("无法创建测试 PlatformApp: %v", err)
}
return app
}
// createTestDashboardApp 在数据库中创建一个测试 DashboardApp 并返回。
func createTestDashboardApp(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.DashboardApp)) *model.DashboardApp {
t.Helper()
app := &model.DashboardApp{
AccountID: accountID,
Title: "测试仪表盘",
Kind: "frame",
Active: model.BoolPtr(true),
Content: json.RawMessage(`[{"type":"frame","url":"https://example.com/widget"}]`),
}
for _, fn := range overrides {
fn(app)
}
if err := db.Create(app).Error; err != nil {
t.Fatalf("无法创建测试 DashboardApp: %v", err)
}
return app
}
// ========== KnowledgeBase setup helpers ==========
// setupPortalService creates PortalRepo + PortalService test instances.
func setupPortalService(t *testing.T) (*gorm.DB, *repository.PortalRepo, *PortalService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewPortalRepo(db)
svc := NewPortalService(repo)
return db, repo, svc
}
// setupCategoryService creates CategoryRepo + CategoryService test instances.
func setupCategoryService(t *testing.T) (*gorm.DB, *repository.CategoryRepo, *CategoryService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewCategoryRepo(db)
relatedRepo := repository.NewRelatedCategoryRepo(db)
svc := NewCategoryService(repo, relatedRepo)
return db, repo, svc
}
// setupArticleService creates ArticleRepo + ArticleService test instances.
func setupArticleService(t *testing.T) (*gorm.DB, *repository.ArticleRepo, *ArticleService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewArticleRepo(db)
svc := NewArticleService(repo)
return db, repo, svc
}
// setupFolderService creates FolderRepo + FolderService test instances.
func setupFolderService(t *testing.T) (*gorm.DB, *repository.FolderRepo, *FolderService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewFolderRepo(db)
svc := NewFolderService(repo)
return db, repo, svc
}
// setupPortalMemberService creates PortalMemberRepo + PortalMemberService test instances.
func setupPortalMemberService(t *testing.T) (*gorm.DB, *repository.PortalMemberRepo, *PortalMemberService) {
t.Helper()
db := setupServiceTestDB(t)
repo := repository.NewPortalMemberRepo(db)
svc := NewPortalMemberService(repo)
return db, repo, svc
}
// createTestPortal creates a test Portal in the database.
func createTestPortal(t *testing.T, db *gorm.DB, accountID uint) *model.Portal {
t.Helper()
portal := &model.Portal{
AccountID: accountID,
Name: "测试知识库门户",
Slug: "test-portal",
Description: "测试描述",
Locale: "en",
}
if err := db.Create(portal).Error; err != nil {
t.Fatalf("无法创建测试 Portal: %v", err)
}
return portal
}
// createTestCategory creates a test Category in the database.
func createTestCategory(t *testing.T, db *gorm.DB, portalID uint) *model.Category {
t.Helper()
cat := &model.Category{
PortalID: portalID,
Name: "测试分类",
Description: "测试分类描述",
Slug: "test-category",
}
if err := db.Create(cat).Error; err != nil {
t.Fatalf("无法创建测试 Category: %v", err)
}
return cat
}
// createTestFolder creates a test Folder in the database.
func createTestFolder(t *testing.T, db *gorm.DB, portalID uint) *model.Folder {
t.Helper()
folder := &model.Folder{
PortalID: portalID,
Name: "测试文件夹",
}
if err := db.Create(folder).Error; err != nil {
t.Fatalf("无法创建测试 Folder: %v", err)
}
return folder
}
// ========== Team & Profile service helpers ==========
// teamRepoFromDB creates a TeamRepo from a gorm.DB instance for tests.
func teamRepoFromDB(db *gorm.DB) *repository.TeamRepo {
return repository.NewTeamRepo(db)
}
// teamMemberRepoFromDB creates a TeamMemberRepo from a gorm.DB instance for tests.
func teamMemberRepoFromDB(db *gorm.DB) *repository.TeamMemberRepo {
return repository.NewTeamMemberRepo(db)
}
// userRepoFromDB creates a UserRepo from a gorm.DB instance for tests.
func userRepoFromDB(db *gorm.DB) *repository.UserRepo {
return repository.NewUserRepo(db)
}
// setupTeamService creates TeamRepo + TeamMemberRepo + TeamService test instances.
func setupTeamService(t *testing.T) (*gorm.DB, *TeamService) {
t.Helper()
db := setupServiceTestDB(t)
svc := NewTeamService(teamRepoFromDB(db), teamMemberRepoFromDB(db))
return db, svc
}
// setupProfileService creates UserRepo + ProfileService test instances.
func setupProfileService(t *testing.T) (*gorm.DB, *ProfileService) {
t.Helper()
db := setupServiceTestDB(t)
svc := NewProfileService(userRepoFromDB(db))
return db, svc
}
// setupTagService creates TagRepo + TagService test instances.
func setupTagService(t *testing.T) (*gorm.DB, *repository.TagRepo, *TagService) {
t.Helper()
db := setupServiceTestDB(t)
tagRepo := repository.NewTagRepo(db)
svc := NewTagService(tagRepo)
return db, tagRepo, svc
}
// setupLabelService creates ConversationLabelRepo + TagRepo + LabelService test instances.
func setupLabelService(t *testing.T) (*gorm.DB, *repository.ConversationLabelRepo, *repository.TagRepo, *LabelService) {
t.Helper()
db := setupServiceTestDB(t)
convLabelRepo := repository.NewConversationLabelRepo(db)
tagRepo := repository.NewTagRepo(db)
svc := NewLabelService(convLabelRepo, tagRepo)
return db, convLabelRepo, tagRepo, svc
}