Files
gochat/internal/service/service_test_helper.go
T

387 lines
11 KiB
Go

package service
import (
"encoding/json"
"fmt"
"testing"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
)
// createTestAccount creates a test Account in the database.
func createTestAccount(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
account := &model.Account{Name: "Test Account", Locale: "en", Status: "active"}
if err := db.Create(account).Error; err != nil {
t.Fatalf("failed to create test account: %v", err)
}
return account
}
// createTestAccountWithName creates a test Account with a custom name.
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("failed to create test account: %v", err)
}
return account
}
// setupServiceTestDB creates an in-memory SQLite database for service tests.
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("failed to open SQLite test database: %v", err)
}
if err := db.AutoMigrate(
&model.Account{},
&model.User{},
&model.AccountUser{},
&model.Inbox{},
&model.Contact{},
&model.ContactInbox{},
&model.Conversation{},
&model.ConversationParticipant{},
&model.Message{},
&model.Attachment{},
&model.InboxMember{},
&model.Notification{},
&model.NotificationPreference{},
&model.DashboardApp{},
&model.Portal{},
&model.Category{},
&model.RelatedCategory{},
&model.Article{},
&model.Folder{},
&model.PortalMember{},
&model.Company{},
&model.CompanyNote{},
&model.Tag{},
&model.ContactLabel{},
&model.ContactExport{},
&model.DataImport{},
&model.Notification{},
); err != nil {
t.Fatalf("failed to auto-migrate models: %v", err)
}
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
return db
}
// marshalAssistantConfig converts an arbitrary value to json.RawMessage for the Config field.
func marshalAssistantConfig(cfg interface{}) json.RawMessage {
b, _ := json.Marshal(cfg)
return json.RawMessage(b)
}
// ========== DashboardApp test helpers ==========
// setupDashboardAppService creates DashboardAppRepo + DashboardAppService test instances.
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
}
// createTestUser creates a test User in the database.
func createTestUser(t *testing.T, db *gorm.DB, accountID uint) *model.User {
t.Helper()
user := &model.User{
Name: "Test User",
Email: fmt.Sprintf("test-%d@example.com", time.Now().UnixNano()),
AccountID: accountID,
}
if err := db.Create(user).Error; err != nil {
t.Fatalf("failed to create test user: %v", err)
}
return user
}
// createTestDashboardApp creates a test DashboardApp in the database.
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 test helpers ==========
// setupKBServiceTestDB creates an in-memory SQLite with KB models migrated.
func setupKBServiceTestDB(t *testing.T) *gorm.DB {
t.Helper()
db := setupServiceTestDB(t)
return db
}
// createTestPortal creates a test Portal in the database.
func createTestPortal(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.Portal)) *model.Portal {
t.Helper()
portal := &model.Portal{
AccountID: accountID,
Name: "Test KB",
Slug: fmt.Sprintf("test-kb-%d", time.Now().UnixNano()),
Color: "#1f93ff",
Locale: "en",
SSLSettings: json.RawMessage(`{}`),
}
for _, fn := range overrides {
fn(portal)
}
if err := db.Create(portal).Error; err != nil {
t.Fatalf("failed to create test portal: %v", err)
}
return portal
}
// createTestCategory creates a test Category in the database.
func createTestCategory(t *testing.T, db *gorm.DB, portalID uint, overrides ...func(*model.Category)) *model.Category {
t.Helper()
category := &model.Category{
PortalID: portalID,
Name: "Test Category",
Description: "A test category",
Slug: fmt.Sprintf("test-cat-%d", time.Now().UnixNano()),
}
for _, fn := range overrides {
fn(category)
}
if err := db.Create(category).Error; err != nil {
t.Fatalf("failed to create test category: %v", err)
}
return category
}
// createTestFolder creates a test Folder in the database.
func createTestFolder(t *testing.T, db *gorm.DB, portalID uint, overrides ...func(*model.Folder)) *model.Folder {
t.Helper()
folder := &model.Folder{
PortalID: portalID,
Name: "Test Folder",
}
for _, fn := range overrides {
fn(folder)
}
if err := db.Create(folder).Error; err != nil {
t.Fatalf("failed to create test folder: %v", err)
}
return folder
}
// createTestArticle creates a test Article in the database.
func createTestArticle(t *testing.T, db *gorm.DB, accountID uint, portalID uint, overrides ...func(*model.Article)) *model.Article {
t.Helper()
article := &model.Article{
AccountID: accountID,
PortalID: portalID,
Title: "Test Article",
Slug: fmt.Sprintf("test-article-%d", time.Now().UnixNano()),
Content: "Test content",
Status: string(model.ArticleStatusDraft),
}
for _, fn := range overrides {
fn(article)
}
if err := db.Create(article).Error; err != nil {
t.Fatalf("failed to create test article: %v", err)
}
return article
}
// ========== Notification test helpers ==========
// setupNotificationService creates NotificationRepo + NotificationPreferenceRepo + NotificationService test instances.
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 creates a test Notification in the database.
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("failed to create test notification: %v", err)
}
return notification
}
// ========== Account test helpers ==========
// setupAccountService creates AccountRepo + AccountService test instances.
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
}
// ========== Contact test helpers ==========
// setupContactServiceTestDB creates an in-memory SQLite database with Contact-related models migrated.
func setupContactServiceTestDB(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("failed to open SQLite test database: %v", err)
}
if err := db.AutoMigrate(
&model.Account{},
&model.User{},
&model.AccountUser{},
&model.Inbox{},
&model.Contact{},
&model.ContactInbox{},
&model.Tag{},
&model.ContactLabel{},
&model.ContactExport{},
&model.DataImport{},
&model.Notification{},
); err != nil {
t.Fatalf("failed to auto-migrate contact models: %v", err)
}
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
return db
}
// setupContactService creates ContactRepo + ContactInboxService + ContactService test instances.
func setupContactService(t *testing.T) (*gorm.DB, *repository.ContactRepo, *ContactService) {
t.Helper()
db := setupContactServiceTestDB(t)
repo := repository.NewContactRepo(db)
contactInboxSvc := NewContactInboxService(repository.NewContactInboxRepo(db))
noteRepo := repository.NewNoteRepo(db)
svc := NewContactService(repo, contactInboxSvc, noteRepo)
return db, repo, svc
}
// createTestContact creates a test Contact in the database.
func createTestContact(t *testing.T, db *gorm.DB, accountID uint, overrides ...func(*model.Contact)) *model.Contact {
t.Helper()
contact := &model.Contact{
AccountID: accountID,
Name: "Test Contact",
Email: fmt.Sprintf("contact-%d@test.com", time.Now().UnixNano()),
}
for _, fn := range overrides {
fn(contact)
}
if err := db.Create(contact).Error; err != nil {
t.Fatalf("failed to create test contact: %v", err)
}
return contact
}
// createTestInbox creates a test Inbox in the database.
func createTestInbox(t *testing.T, db *gorm.DB, accountID uint, channelType string, overrides ...func(*model.Inbox)) *model.Inbox {
t.Helper()
inbox := &model.Inbox{
AccountID: accountID,
Name: "Test Inbox",
ChannelType: channelType,
ChannelID: 1,
}
for _, fn := range overrides {
fn(inbox)
}
if err := db.Create(inbox).Error; err != nil {
t.Fatalf("failed to create test inbox: %v", err)
}
return inbox
}
// ========== Conversation test helpers ==========
// createTestConversation creates a test Conversation in the database.
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),
ChannelType: "web_widget",
Channel: "web_widget",
}
if err := db.Create(conv).Error; err != nil {
t.Fatalf("failed to create test conversation: %v", err)
}
return conv
}
// ========== Message test helpers ==========
// createTestMessage creates a test Message in the database.
func createTestMessage(t *testing.T, db *gorm.DB, accountID, inboxID, conversationID uint, overrides ...func(*model.Message)) *model.Message {
t.Helper()
msg := &model.Message{
AccountID: accountID,
InboxID: inboxID,
ConversationID: conversationID,
Content: "Test message",
MessageType: "incoming",
ContentType: "text",
SenderType: "contact",
}
for _, fn := range overrides {
fn(msg)
}
if err := db.Create(msg).Error; err != nil {
t.Fatalf("failed to create test message: %v", err)
}
return msg
}
// skipIfSQLite skips the test when the underlying database is SQLite.
// Service tests always use SQLite in-memory, so this always skips.
// Use for PG-only features like ILIKE, trigram, pgvector, etc.
func skipIfSQLite(t *testing.T) {
t.Helper()
t.Skip("Skipping: this test requires PostgreSQL (ILIKE / trigram / pgvector etc.)")
}