Files
gochat/backend/internal/service/coverage_test.go
T
2026-08-18 10:20:11 +08:00

1071 lines
40 KiB
Go

package service
import (
"context"
"fmt"
"reflect"
"sync"
"testing"
"time"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/pkg/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// ponytail: coverage tests are sequential; use per-test DBs if they adopt t.Parallel.
var (
simpleServiceTestDBMu sync.Mutex
simpleServiceDBs = make(map[string]*gorm.DB)
)
func newSimpleServiceTestDB(t *testing.T, models ...interface{}) *gorm.DB {
t.Helper()
simpleServiceTestDBMu.Lock()
defer simpleServiceTestDBMu.Unlock()
key := ""
for _, model := range models {
key += "|" + reflect.TypeOf(model).String()
}
db := simpleServiceDBs[key]
if db == nil {
baseModels := []interface{}{
&model.Account{}, &model.User{}, &model.AccountUser{},
&model.Inbox{}, &model.Contact{}, &model.ContactInbox{},
&model.Conversation{}, &model.Message{}, &model.Attachment{},
&model.Notification{}, &model.NotificationPreference{},
&model.Tag{}, &model.ConversationLabel{}, &model.ContactLabel{},
&model.Team{}, &model.TeamMember{},
&model.Banner{}, &model.Note{}, &model.ContactNote{},
&model.InstallationConfig{},
&model.InboxLimit{}, &model.InboxMember{},
&model.AgentBot{}, &model.AgentBotInbox{},
&model.DeliveryStatus{},
&model.ReportingEvent{},
&model.SlaEvent{}, &model.SlaPolicy{}, &model.AppliedSLA{},
&model.WidgetTest{},
&model.PushToken{},
&model.NotificationSubscription{},
&model.EmailChannelMigration{},
&model.Article{},
&model.Folder{},
&model.CustomAttributeDefinition{},
}
allModels := append(baseModels, models...)
var err error
db, err = gorm.Open(sqlite.Open(fmt.Sprintf("file:service-coverage-%d?mode=memory&cache=shared", len(simpleServiceDBs))), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(allModels...))
simpleServiceDBs[key] = db
}
t.Cleanup(func() { testutil.TruncateAll(t, db) })
return db
}
// === AgentBotService tests ===
func TestAgentBotService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Test Bot",
Description: "A test bot",
OutgoingURL: "https://example.com/webhook",
BotType: "webhook",
})
require.NoError(t, err)
assert.NotZero(t, bot.ID)
assert.Equal(t, "Test Bot", bot.Name)
assert.NotEmpty(t, bot.AccessToken)
assert.NotEmpty(t, bot.Secret)
}
func TestAgentBotService_Create_ValidationError(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
_, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "A", // too short
})
require.Error(t, err)
}
func TestAgentBotService_Get(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Get Bot",
})
require.NoError(t, err)
got, err := svc.Get(context.Background(), bot.ID)
require.NoError(t, err)
assert.Equal(t, bot.Name, got.Name)
}
func TestAgentBotService_Get_NotFound(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
_, err := svc.Get(context.Background(), 9999)
require.Error(t, err)
}
func TestAgentBotService_ListAccessible(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
_, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Scoped Bot",
AccountID: &aid,
})
require.NoError(t, err)
_, err = svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Global Bot",
})
require.NoError(t, err)
bots, total, err := svc.ListAccessible(context.Background(), 1, 0, 10)
require.NoError(t, err)
assert.Equal(t, int64(2), total)
assert.Len(t, bots, 2)
}
func TestAgentBotService_Update(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Original",
})
require.NoError(t, err)
nameUpdated := "Updated"
updated, err := svc.Update(context.Background(), bot.ID, UpdateAgentBotRequest{
Name: &nameUpdated,
})
require.NoError(t, err)
assert.Equal(t, "Updated", updated.Name)
}
func TestAgentBotService_Delete(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Delete Me",
})
require.NoError(t, err)
err = svc.Delete(context.Background(), bot.ID)
require.NoError(t, err)
_, err = svc.Get(context.Background(), bot.ID)
require.Error(t, err)
}
func TestAgentBotService_ResetToken(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Token Bot",
})
require.NoError(t, err)
oldToken := bot.AccessToken
newToken, err := svc.ResetToken(context.Background(), bot.ID)
require.NoError(t, err)
assert.NotEmpty(t, newToken)
assert.NotEqual(t, oldToken, newToken)
}
func TestAgentBotService_ResetSecret(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Secret Bot",
})
require.NoError(t, err)
oldSecret := bot.Secret
newSecret, err := svc.ResetSecret(context.Background(), bot.ID)
require.NoError(t, err)
assert.NotEmpty(t, newSecret)
assert.NotEqual(t, oldSecret, newSecret)
}
func TestAgentBotService_ResetConfig(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Config Bot",
})
require.NoError(t, err)
_, err = svc.ResetConfig(context.Background(), bot.ID)
require.NoError(t, err)
}
func TestAgentBotService_DeleteByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Acct Bot",
AccountID: &aid,
})
require.NoError(t, err)
err = svc.DeleteByAccount(context.Background(), 1, bot.ID)
require.NoError(t, err)
}
func TestAgentBotService_List(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
_, err := svc.Create(context.Background(), CreateAgentBotRequest{Name: "Bot1", AccountID: &aid})
require.NoError(t, err)
bots, err := svc.List(context.Background(), 1)
require.NoError(t, err)
assert.NotEmpty(t, bots)
}
func TestAgentBotService_ListAccessibleAll(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
_, err := svc.Create(context.Background(), CreateAgentBotRequest{Name: "Global"})
require.NoError(t, err)
bots, err := svc.ListAccessibleAll(context.Background(), 1)
require.NoError(t, err)
assert.NotEmpty(t, bots)
}
func TestAgentBotService_GetAccessible(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Scoped",
AccountID: &aid,
})
require.NoError(t, err)
got, err := svc.GetAccessible(context.Background(), 1, bot.ID)
require.NoError(t, err)
assert.Equal(t, bot.Name, got.Name)
}
func TestAgentBotService_UpdateByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Orig",
AccountID: &aid,
})
require.NoError(t, err)
nameUpdated2 := "Updated"
updated, err := svc.UpdateByAccount(context.Background(), 1, bot.ID, UpdateAgentBotRequest{
Name: &nameUpdated2,
})
require.NoError(t, err)
assert.Equal(t, "Updated", updated.Name)
}
func TestAgentBotService_ResetTokenByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Token Bot",
AccountID: &aid,
})
require.NoError(t, err)
_, err = svc.ResetTokenByAccount(context.Background(), 1, bot.ID)
require.NoError(t, err)
}
func TestAgentBotService_ResetSecretByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Secret Bot",
AccountID: &aid,
})
require.NoError(t, err)
_, err = svc.ResetSecretByAccount(context.Background(), 1, bot.ID)
require.NoError(t, err)
}
func TestAgentBotService_DeleteAvatar(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{Name: "Avatar Bot"})
require.NoError(t, err)
_, err = svc.DeleteAvatar(context.Background(), bot.ID)
require.NoError(t, err)
}
func TestAgentBotService_DeleteAvatarByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAgentBotService(repository.NewAgentBotRepo(db))
aid := uint(1)
bot, err := svc.Create(context.Background(), CreateAgentBotRequest{
Name: "Avatar Bot",
AccountID: &aid,
})
require.NoError(t, err)
_, err = svc.DeleteAvatarByAccount(context.Background(), 1, bot.ID)
require.NoError(t, err)
}
func TestGenerateBotAccessToken(t *testing.T) {
token, err := generateBotAccessToken()
require.NoError(t, err)
assert.Len(t, token, 64)
}
func TestGenerateBotSecret(t *testing.T) {
secret, err := generateBotSecret()
require.NoError(t, err)
assert.NotEmpty(t, secret)
}
// === BannerService tests ===
func TestBannerService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
err := svc.Create(context.Background(), &model.Banner{
Title: "Test Banner",
Content: "Content",
BannerType: "info",
CreatedBy: uintPtr(1),
})
require.NoError(t, err)
}
func TestBannerService_Create_ValidationError(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
err := svc.Create(context.Background(), &model.Banner{
Title: "",
})
require.Error(t, err)
}
func TestBannerService_List(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
require.NoError(t, svc.Create(context.Background(), &model.Banner{
Title: "B1", Content: "C1", BannerType: "info", Active: true, CreatedBy: uintPtr(1),
}))
banners, total, err := svc.List(context.Background(), 0, 10)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Len(t, banners, 1)
}
func TestBannerService_Get(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
b := &model.Banner{Title: "Get", Content: "C", BannerType: "info", CreatedBy: uintPtr(1)}
require.NoError(t, svc.Create(context.Background(), b))
got, err := svc.Get(context.Background(), b.ID)
require.NoError(t, err)
assert.Equal(t, "Get", got.Title)
}
func TestBannerService_Update(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
b := &model.Banner{Title: "Orig", Content: "C", BannerType: "info", CreatedBy: uintPtr(1)}
require.NoError(t, svc.Create(context.Background(), b))
err := svc.Update(context.Background(), b.ID, map[string]interface{}{"title": "Updated"})
require.NoError(t, err)
}
func TestBannerService_Delete(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
b := &model.Banner{Title: "Del", Content: "C", BannerType: "info", CreatedBy: uintPtr(1)}
require.NoError(t, svc.Create(context.Background(), b))
err := svc.Delete(context.Background(), b.ID)
require.NoError(t, err)
}
func TestBannerService_Delete_InvalidID(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
err := svc.Delete(context.Background(), 0)
require.Error(t, err)
}
func TestBannerService_ListActive(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Banner{})
svc := NewBannerService(repository.NewBannerRepo(db))
require.NoError(t, svc.Create(context.Background(), &model.Banner{
Title: "Active", Content: "C", BannerType: "info", Active: true, CreatedBy: uintPtr(1),
}))
require.NoError(t, svc.Create(context.Background(), &model.Banner{
Title: "Inactive", Content: "C", BannerType: "info", Active: false, CreatedBy: uintPtr(1),
}))
active, err := svc.ListActive(context.Background())
require.NoError(t, err)
assert.Len(t, active, 1)
}
// === NoteService tests ===
func TestNoteService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
note, err := svc.Create(1, 10, 5, "Test note")
require.NoError(t, err)
assert.NotZero(t, note.ID)
assert.Equal(t, "Test note", note.Content)
}
func TestNoteService_Create_EmptyContent(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
_, err := svc.Create(1, 10, 5, "")
require.Error(t, err)
}
func TestNoteService_List(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
_, err := svc.Create(1, 10, 5, "Note 1")
require.NoError(t, err)
_, err = svc.Create(1, 10, 5, "Note 2")
require.NoError(t, err)
notes, err := svc.List(1, 10)
require.NoError(t, err)
assert.Len(t, notes, 2)
}
func TestNoteService_Get(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
note, err := svc.Create(1, 10, 5, "Get me")
require.NoError(t, err)
got, err := svc.Get(1, 10, note.ID)
require.NoError(t, err)
assert.Equal(t, note.Content, got.Content)
}
func TestNoteService_Get_NotFound(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
_, err := svc.Get(1, 10, 9999)
require.Error(t, err)
}
func TestNoteService_Update(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
note, err := svc.Create(1, 10, 5, "Original")
require.NoError(t, err)
updated, err := svc.Update(1, 10, note.ID, "Updated")
require.NoError(t, err)
assert.Equal(t, "Updated", updated.Content)
}
func TestNoteService_Update_EmptyContent(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
note, err := svc.Create(1, 10, 5, "Original")
require.NoError(t, err)
_, err = svc.Update(1, 10, note.ID, "")
require.Error(t, err)
}
func TestNoteService_Delete(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
note, err := svc.Create(1, 10, 5, "Delete me")
require.NoError(t, err)
err = svc.Delete(1, 10, note.ID)
require.NoError(t, err)
}
func TestNoteService_Delete_NotFound(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.Note{})
svc := NewNoteService(repository.NewNoteRepo(db))
err := svc.Delete(1, 10, 9999)
// Delete on non-existent record may or may not error depending on implementation
_ = err
}
// === LabelService tests ===
func TestLabelService_AddLabelToConversation(t *testing.T) {
db := newSimpleServiceTestDB(t)
// Seed
tag := &model.Tag{AccountID: 1, Name: "urgent", Color: "#ff0000", ShowOnSidebar: boolPtr(true)}
require.NoError(t, db.Create(tag).Error)
conv := &model.Conversation{AccountID: 1, InboxID: 1, ContactID: 1, ChannelType: "web_widget", Channel: "web_widget"}
require.NoError(t, db.Create(conv).Error)
svc := NewLabelService(repository.NewConversationLabelRepo(db), repository.NewTagRepo(db))
cl, err := svc.AddLabelToConversation(context.Background(), 1, conv.ID, tag.ID)
require.NoError(t, err)
assert.NotZero(t, cl.ID)
}
func TestLabelService_AddLabel_Duplicate(t *testing.T) {
db := newSimpleServiceTestDB(t)
tag := &model.Tag{AccountID: 1, Name: "dup", Color: "#ff0000", ShowOnSidebar: boolPtr(true)}
require.NoError(t, db.Create(tag).Error)
conv := &model.Conversation{AccountID: 1, InboxID: 1, ContactID: 1, ChannelType: "web_widget", Channel: "web_widget"}
require.NoError(t, db.Create(conv).Error)
svc := NewLabelService(repository.NewConversationLabelRepo(db), repository.NewTagRepo(db))
_, err := svc.AddLabelToConversation(context.Background(), 1, conv.ID, tag.ID)
require.NoError(t, err)
_, err = svc.AddLabelToConversation(context.Background(), 1, conv.ID, tag.ID)
require.Error(t, err)
}
func TestLabelService_AddLabel_TagNotFound(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewLabelService(repository.NewConversationLabelRepo(db), repository.NewTagRepo(db))
_, err := svc.AddLabelToConversation(context.Background(), 1, 1, 9999)
require.Error(t, err)
}
func TestLabelService_RemoveLabel(t *testing.T) {
db := newSimpleServiceTestDB(t)
tag := &model.Tag{AccountID: 1, Name: "rm", Color: "#ff0000", ShowOnSidebar: boolPtr(true)}
require.NoError(t, db.Create(tag).Error)
conv := &model.Conversation{AccountID: 1, InboxID: 1, ContactID: 1, ChannelType: "web_widget", Channel: "web_widget"}
require.NoError(t, db.Create(conv).Error)
svc := NewLabelService(repository.NewConversationLabelRepo(db), repository.NewTagRepo(db))
_, err := svc.AddLabelToConversation(context.Background(), 1, conv.ID, tag.ID)
require.NoError(t, err)
err = svc.RemoveLabelFromConversation(context.Background(), conv.ID, tag.ID)
require.NoError(t, err)
}
func TestLabelService_GetConversationLabels(t *testing.T) {
db := newSimpleServiceTestDB(t)
tag := &model.Tag{AccountID: 1, Name: "lbl", Color: "#ff0000", ShowOnSidebar: boolPtr(true)}
require.NoError(t, db.Create(tag).Error)
conv := &model.Conversation{AccountID: 1, InboxID: 1, ContactID: 1, ChannelType: "web_widget", Channel: "web_widget"}
require.NoError(t, db.Create(conv).Error)
svc := NewLabelService(repository.NewConversationLabelRepo(db), repository.NewTagRepo(db))
_, err := svc.AddLabelToConversation(context.Background(), 1, conv.ID, tag.ID)
require.NoError(t, err)
labels, err := svc.GetConversationLabels(context.Background(), conv.ID)
require.NoError(t, err)
assert.Len(t, labels, 1)
}
func TestLabelService_ReplaceConversationLabels(t *testing.T) {
db := newSimpleServiceTestDB(t)
tag1 := &model.Tag{AccountID: 1, Name: "t1", Color: "#ff0000", ShowOnSidebar: boolPtr(true)}
tag2 := &model.Tag{AccountID: 1, Name: "t2", Color: "#00ff00", ShowOnSidebar: boolPtr(true)}
require.NoError(t, db.Create(tag1).Error)
require.NoError(t, db.Create(tag2).Error)
conv := &model.Conversation{AccountID: 1, InboxID: 1, ContactID: 1, ChannelType: "web_widget", Channel: "web_widget"}
require.NoError(t, db.Create(conv).Error)
svc := NewLabelService(repository.NewConversationLabelRepo(db), repository.NewTagRepo(db))
_, err := svc.AddLabelToConversation(context.Background(), 1, conv.ID, tag1.ID)
require.NoError(t, err)
_, err = svc.ReplaceConversationLabels(context.Background(), 1, conv.ID, []uint{tag2.ID})
require.NoError(t, err)
labels, err := svc.GetConversationLabels(context.Background(), conv.ID)
require.NoError(t, err)
assert.Len(t, labels, 1)
assert.Equal(t, tag2.ID, labels[0].TagID)
}
// === InboxLimitService tests ===
func TestInboxLimitService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InboxLimit{})
svc := NewInboxLimitService(repository.NewInboxLimitRepo(db))
limit, err := svc.Create(context.Background(), 1, CreateInboxLimitRequest{Type: "conversation", Value: 100})
require.NoError(t, err)
assert.NotZero(t, limit.ID)
assert.Equal(t, "conversation", limit.Type)
assert.Equal(t, 100, limit.Value)
}
func TestInboxLimitService_Create_ValidationError(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InboxLimit{})
svc := NewInboxLimitService(repository.NewInboxLimitRepo(db))
_, err := svc.Create(context.Background(), 1, CreateInboxLimitRequest{Type: "", Value: 0})
require.Error(t, err)
}
func TestInboxLimitService_Update(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InboxLimit{})
svc := NewInboxLimitService(repository.NewInboxLimitRepo(db))
limit, err := svc.Create(context.Background(), 1, CreateInboxLimitRequest{Type: "conversation", Value: 50})
require.NoError(t, err)
updated, err := svc.Update(context.Background(), limit.ID, UpdateInboxLimitRequest{Value: 100})
require.NoError(t, err)
assert.Equal(t, 100, updated.Value)
}
func TestInboxLimitService_Delete(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InboxLimit{})
svc := NewInboxLimitService(repository.NewInboxLimitRepo(db))
limit, err := svc.Create(context.Background(), 1, CreateInboxLimitRequest{Type: "conversation", Value: 50})
require.NoError(t, err)
err = svc.Delete(context.Background(), limit.ID)
require.NoError(t, err)
}
func TestInboxLimitService_Delete_NotFound(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InboxLimit{})
svc := NewInboxLimitService(repository.NewInboxLimitRepo(db))
err := svc.Delete(context.Background(), 9999)
require.Error(t, err)
}
// === InstallationConfigService tests ===
func TestInstallationConfigService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
cfg, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{
Name: "TEST_CONFIG", Value: "test_value",
})
require.NoError(t, err)
assert.NotZero(t, cfg.ID)
}
func TestInstallationConfigService_Create_ValidationError(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
_, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "", Value: ""})
require.Error(t, err)
}
func TestInstallationConfigService_Get(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
cfg, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "GET_CFG", Value: "val"})
require.NoError(t, err)
got, err := svc.Get(context.Background(), cfg.ID)
require.NoError(t, err)
assert.Equal(t, cfg.Name, got.Name)
}
func TestInstallationConfigService_GetByName(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
_, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "BY_NAME", Value: "val"})
require.NoError(t, err)
got, err := svc.GetByName(context.Background(), "BY_NAME")
require.NoError(t, err)
assert.Equal(t, "val", got.Value)
}
func TestInstallationConfigService_List(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
_, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "L1", Value: "v1"})
require.NoError(t, err)
_, err = svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "L2", Value: "v2"})
require.NoError(t, err)
cfgs, total, err := svc.List(context.Background(), 0, 10)
require.NoError(t, err)
assert.Equal(t, int64(2), total)
assert.Len(t, cfgs, 2)
}
func TestInstallationConfigService_Update(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
cfg, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "UPD", Value: "orig"})
require.NoError(t, err)
updated, err := svc.Update(context.Background(), cfg.ID, &UpdateInstallationConfigRequest{Value: "new"})
require.NoError(t, err)
assert.Equal(t, "new", updated.Value)
}
func TestInstallationConfigService_Delete(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.InstallationConfig{})
svc := NewInstallationConfigService(repository.NewInstallationConfigRepo(db))
cfg, err := svc.Create(context.Background(), &CreateInstallationConfigRequest{Name: "DEL", Value: "v"})
require.NoError(t, err)
err = svc.Delete(context.Background(), cfg.ID)
require.NoError(t, err)
}
// === WidgetTestService tests ===
func TestWidgetTestService_List(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.WidgetTest{})
svc := NewWidgetTestService(repository.NewWidgetTestRepo(db))
require.NoError(t, db.Create(&model.WidgetTest{Name: "Test1", Type: "scenario", Config: []byte("{}")}).Error)
tests, err := svc.List(context.Background())
require.NoError(t, err)
assert.Len(t, tests, 1)
}
func TestWidgetTestService_ListByType(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.WidgetTest{})
svc := NewWidgetTestService(repository.NewWidgetTestRepo(db))
require.NoError(t, db.Create(&model.WidgetTest{Name: "T1", Type: "scenario", Config: []byte("{}")}).Error)
require.NoError(t, db.Create(&model.WidgetTest{Name: "T2", Type: "faq", Config: []byte("{}")}).Error)
tests, err := svc.ListByType(context.Background(), "scenario")
require.NoError(t, err)
assert.Len(t, tests, 1)
}
func TestWidgetTestService_ListByType_EmptyType(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.WidgetTest{})
svc := NewWidgetTestService(repository.NewWidgetTestRepo(db))
_, err := svc.ListByType(context.Background(), "")
require.Error(t, err)
}
// === PushSubscriptionService tests ===
func TestPushSubscriptionService_RegisterPushToken(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.PushToken{})
svc := NewPushSubscriptionService(repository.NewPushTokenRepo(db))
pt, err := svc.RegisterPushToken(context.Background(), 1, "token123", "web", "device1", "p256dh", "auth")
require.NoError(t, err)
assert.NotZero(t, pt.ID)
assert.Equal(t, "token123", pt.Token)
}
func TestPushSubscriptionService_ListPushTokens(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.PushToken{})
svc := NewPushSubscriptionService(repository.NewPushTokenRepo(db))
_, err := svc.RegisterPushToken(context.Background(), 1, "t1", "web", "d1", "", "")
require.NoError(t, err)
_, err = svc.RegisterPushToken(context.Background(), 1, "t2", "web", "d2", "", "")
require.NoError(t, err)
tokens, err := svc.ListPushTokens(context.Background(), 1)
require.NoError(t, err)
assert.Len(t, tokens, 2)
}
func TestPushSubscriptionService_RemovePushToken(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.PushToken{})
svc := NewPushSubscriptionService(repository.NewPushTokenRepo(db))
pt, err := svc.RegisterPushToken(context.Background(), 1, "rm", "web", "d", "", "")
require.NoError(t, err)
err = svc.RemovePushToken(context.Background(), pt.ID)
require.NoError(t, err)
}
func TestPushSubscriptionService_RemovePushTokenByValue(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.PushToken{})
svc := NewPushSubscriptionService(repository.NewPushTokenRepo(db))
_, err := svc.RegisterPushToken(context.Background(), 1, "val123", "web", "d", "", "")
require.NoError(t, err)
err = svc.RemovePushTokenByValue(context.Background(), "val123", 1)
require.NoError(t, err)
}
// === EmailChannelMigrationService tests ===
func TestEmailChannelMigrationService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.EmailChannelMigration{})
svc := NewEmailChannelMigrationService(repository.NewEmailChannelMigrationRepo(db))
migration := &model.EmailChannelMigration{
AccountID: 1,
InboxID: 10,
TargetInboxID: 20,
}
err := svc.Create(context.Background(), migration)
require.NoError(t, err)
assert.NotZero(t, migration.ID)
assert.Equal(t, "pending", migration.MigrationStatus)
}
func TestEmailChannelMigrationService_Create_ValidationError(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.EmailChannelMigration{})
svc := NewEmailChannelMigrationService(repository.NewEmailChannelMigrationRepo(db))
err := svc.Create(context.Background(), &model.EmailChannelMigration{AccountID: 0})
require.Error(t, err)
}
func TestEmailChannelMigrationService_ListByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.EmailChannelMigration{})
svc := NewEmailChannelMigrationService(repository.NewEmailChannelMigrationRepo(db))
require.NoError(t, svc.Create(context.Background(), &model.EmailChannelMigration{AccountID: 1, InboxID: 10, TargetInboxID: 20}))
migrations, err := svc.ListByAccount(context.Background(), 1)
require.NoError(t, err)
assert.Len(t, migrations, 1)
}
// === DeliveryStatusService tests ===
func TestDeliveryStatusService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.DeliveryStatus{})
svc := NewDeliveryStatusService(repository.NewMessageRepo(db), repository.NewDeliveryStatusRepo(db))
msg := &model.Message{AccountID: 1, ConversationID: 10, InboxID: 5, Content: "test", MessageType: "outgoing", SenderType: "user"}
require.NoError(t, db.Create(msg).Error)
status, err := svc.Create(context.Background(), 1, CreateDeliveryStatusRequest{
MessageID: msg.ID, InboxID: 5, ContactID: 3, Status: "sent",
})
require.NoError(t, err)
assert.NotZero(t, status.ID)
}
func TestDeliveryStatusService_Create_MessageNotFound(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.DeliveryStatus{})
svc := NewDeliveryStatusService(repository.NewMessageRepo(db), repository.NewDeliveryStatusRepo(db))
_, err := svc.Create(context.Background(), 1, CreateDeliveryStatusRequest{
MessageID: 9999, InboxID: 5, ContactID: 3, Status: "sent",
})
require.Error(t, err)
}
func TestDeliveryStatusService_ListByMessage(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.DeliveryStatus{})
svc := NewDeliveryStatusService(repository.NewMessageRepo(db), repository.NewDeliveryStatusRepo(db))
msg := &model.Message{AccountID: 1, ConversationID: 10, InboxID: 5, Content: "test", MessageType: "outgoing", SenderType: "user"}
require.NoError(t, db.Create(msg).Error)
_, err := svc.Create(context.Background(), 1, CreateDeliveryStatusRequest{
MessageID: msg.ID, InboxID: 5, ContactID: 3, Status: "sent",
})
require.NoError(t, err)
statuses, err := svc.ListByMessage(context.Background(), 1, 10, msg.ID)
require.NoError(t, err)
assert.Len(t, statuses, 1)
}
func TestDeliveryStatusService_ListByMessage_MessageNotFound(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.DeliveryStatus{})
svc := NewDeliveryStatusService(repository.NewMessageRepo(db), repository.NewDeliveryStatusRepo(db))
_, err := svc.ListByMessage(context.Background(), 1, 10, 9999)
require.Error(t, err)
}
// === AttachmentService tests ===
func TestAttachmentService_GetByID(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAttachmentService(repository.NewAttachmentRepo(db))
att := &model.Attachment{MessageID: 1, AccountID: 1, FileType: "image", FileURL: "/test.png"}
require.NoError(t, db.Create(att).Error)
got, err := svc.GetByID(context.Background(), att.ID)
require.NoError(t, err)
assert.Equal(t, "image", got.FileType)
}
func TestAttachmentService_ListByMessage(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewAttachmentService(repository.NewAttachmentRepo(db))
require.NoError(t, db.Create(&model.Attachment{MessageID: 1, AccountID: 1, FileType: "image"}).Error)
require.NoError(t, db.Create(&model.Attachment{MessageID: 1, AccountID: 1, FileType: "file"}).Error)
atts, err := svc.ListByMessage(context.Background(), 1)
require.NoError(t, err)
assert.Len(t, atts, 2)
}
// === TeamService tests ===
func TestTeamService_Create(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{
Name: "Engineering",
Description: "Eng team",
})
require.NoError(t, err)
assert.NotZero(t, team.ID)
assert.Equal(t, "Engineering", team.Name)
}
func TestTeamService_Create_ValidationError(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
_, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "A"})
require.Error(t, err)
}
func TestTeamService_Get(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "GetTeam"})
require.NoError(t, err)
got, err := svc.Get(context.Background(), team.ID, 1)
require.NoError(t, err)
assert.Equal(t, team.Name, got.Name)
}
func TestTeamService_List(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
_, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "Team1"})
require.NoError(t, err)
_, err = svc.Create(context.Background(), 1, CreateTeamRequest{Name: "Team2"})
require.NoError(t, err)
teams, total, err := svc.List(context.Background(), 1, 0, 10)
require.NoError(t, err)
assert.Equal(t, int64(2), total)
assert.Len(t, teams, 2)
}
func TestTeamService_Update(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "Orig"})
require.NoError(t, err)
updated, err := svc.Update(context.Background(), team.ID, 1, UpdateTeamRequest{Name: "Updated"})
require.NoError(t, err)
assert.Equal(t, "Updated", updated.Name)
}
func TestTeamService_Delete(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "Delete"})
require.NoError(t, err)
err = svc.Delete(context.Background(), team.ID, 1)
require.NoError(t, err)
}
func TestTeamService_AddAndListMembers(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "Members"})
require.NoError(t, err)
// Create a user first
user := &model.User{AccountID: 1, Name: "Test", Email: "test@example.com", Password: "x", Role: "agent", Active: true}
require.NoError(t, db.Create(user).Error)
members, err := svc.AddMembers(context.Background(), team.ID, 1, []uint{user.ID})
require.NoError(t, err)
assert.Len(t, members, 1)
listed, err := svc.ListMembers(context.Background(), team.ID, 1)
require.NoError(t, err)
assert.Len(t, listed, 1)
}
func TestTeamService_RemoveMember(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "RmMember"})
require.NoError(t, err)
user := &model.User{AccountID: 1, Name: "Test", Email: "rm@example.com", Password: "x", Role: "agent", Active: true}
require.NoError(t, db.Create(user).Error)
_, err = svc.AddMembers(context.Background(), team.ID, 1, []uint{user.ID})
require.NoError(t, err)
err = svc.RemoveMember(context.Background(), team.ID, user.ID, 1)
require.NoError(t, err)
}
func TestTeamService_UpdateMembers(t *testing.T) {
db := newSimpleServiceTestDB(t)
svc := NewTeamService(repository.NewTeamRepo(db), repository.NewTeamMemberRepo(db), db)
team, err := svc.Create(context.Background(), 1, CreateTeamRequest{Name: "UpdMembers"})
require.NoError(t, err)
user1 := &model.User{AccountID: 1, Name: "U1", Email: "u1@example.com", Password: "x", Role: "agent", Active: true}
user2 := &model.User{AccountID: 1, Name: "U2", Email: "u2@example.com", Password: "x", Role: "agent", Active: true}
require.NoError(t, db.Create(user1).Error)
require.NoError(t, db.Create(user2).Error)
// Create AccountUser records so validateUserIDsBelongToAccount passes
require.NoError(t, db.Create(&model.AccountUser{UserID: user1.ID, AccountID: 1, Role: "agent"}).Error)
require.NoError(t, db.Create(&model.AccountUser{UserID: user2.ID, AccountID: 1, Role: "agent"}).Error)
_, err = svc.AddMembers(context.Background(), team.ID, 1, []uint{user1.ID})
require.NoError(t, err)
members, err := svc.UpdateMembers(context.Background(), team.ID, 1, []uint{user2.ID})
require.NoError(t, err)
assert.Len(t, members, 1)
assert.Equal(t, user2.ID, members[0].UserID)
}
// === SlaEventService tests ===
func TestSlaEventService_CreateMissedEvent(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.SlaEvent{})
svc := NewSlaEventServiceSimple(repository.NewSlaEventRepo(db))
appliedSla := &model.AppliedSLA{
AccountID: 1,
ConversationID: 10,
SlaPolicyID: 5,
}
require.NoError(t, db.Create(appliedSla).Error)
err := svc.CreateMissedEvent(context.Background(), appliedSla, model.SLAEventFRT)
require.NoError(t, err)
}
func TestSlaEventService_CreateMissedEvent_WithConversationRepo(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.SlaEvent{})
svc := NewSlaEventService(repository.NewSlaEventRepo(db), repository.NewConversationRepo(db))
conv := &model.Conversation{AccountID: 1, InboxID: 5, ContactID: 1, ChannelType: "web_widget", Channel: "web_widget"}
require.NoError(t, db.Create(conv).Error)
appliedSla := &model.AppliedSLA{
AccountID: 1,
ConversationID: conv.ID,
SlaPolicyID: 5,
}
require.NoError(t, db.Create(appliedSla).Error)
err := svc.CreateMissedEvent(context.Background(), appliedSla, model.SLAEventNRT)
require.NoError(t, err)
}
// === ReportingEventService tests ===
func TestReportingEventService_ListByAccount(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.ReportingEvent{})
svc := NewReportingEventService(repository.NewReportingEventRepo(db))
inboxID := uint(5)
require.NoError(t, db.Create(&model.ReportingEvent{
AccountID: 1, InboxID: &inboxID, Name: "conversation_created",
EventStartTime: startTime(), EventEndTime: endTime(),
}).Error)
events, err := svc.ListByAccount(context.Background(), 1, startTime(), endTime())
require.NoError(t, err)
assert.NotEmpty(t, events)
}
// === SummaryReportService tests ===
func TestSummaryReportService_GetAgentSummary(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.ReportingEventsRollup{})
svc := NewSummaryReportService(repository.NewReportingEventsRollupRepo(db))
result, err := svc.GetAgentSummary(context.Background(), 1, startTime(), endTime())
require.NoError(t, err)
_ = result // can be empty
}
func TestSummaryReportService_GetTeamSummary(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.ReportingEventsRollup{})
svc := NewSummaryReportService(repository.NewReportingEventsRollupRepo(db))
_, err := svc.GetTeamSummary(context.Background(), 1, startTime(), endTime())
require.NoError(t, err)
}
func TestSummaryReportService_GetInboxSummary(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.ReportingEventsRollup{})
svc := NewSummaryReportService(repository.NewReportingEventsRollupRepo(db))
_, err := svc.GetInboxSummary(context.Background(), 1, startTime(), endTime())
require.NoError(t, err)
}
func TestSummaryReportService_GetLabelSummary(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.ReportingEventsRollup{})
svc := NewSummaryReportService(repository.NewReportingEventsRollupRepo(db))
_, err := svc.GetLabelSummary(context.Background(), 1, startTime(), endTime())
require.NoError(t, err)
}
func TestSummaryReportService_GetAccountSummary(t *testing.T) {
db := newSimpleServiceTestDB(t, &model.ReportingEventsRollup{})
svc := NewSummaryReportService(repository.NewReportingEventsRollupRepo(db))
_, err := svc.GetAccountSummary(context.Background(), 1, startTime(), endTime())
require.NoError(t, err)
}
// helper functions
func boolPtr(v bool) *bool { return &v }
func uintPtr(v uint) *uint { return &v }
func startTime() (t time.Time) {
return time.Now().AddDate(0, -1, 0)
}
func endTime() time.Time {
return time.Now()
}