390 lines
12 KiB
Go
390 lines
12 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.CsatTemplate{},
|
|
&model.DataImport{},
|
|
&model.Notification{},
|
|
&model.BackgroundJob{},
|
|
); 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{},
|
|
&model.BackgroundJob{},
|
|
); 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.)")
|
|
}
|