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() }