package service import ( "context" "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" ) // ========== Test Setup ========== func setupAgentBotInboxService(t *testing.T) (*AgentBotInboxService, *gorm.DB) { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err, "failed to open SQLite test db") require.NoError(t, db.AutoMigrate( &model.Account{}, &model.AgentBot{}, &model.Inbox{}, &model.AgentBotInbox{}, ), "failed to auto-migrate") t.Cleanup(func() { sqlDB, _ := db.DB() sqlDB.Close() }) repo := repository.NewAgentBotInboxRepo(db) botRepo := repository.NewAgentBotRepo(db) svc := NewAgentBotInboxService(repo, botRepo) return svc, db } // Helper: create prerequisite Account, AgentBot, and Inbox for service tests. func createSvcPrereqs(t *testing.T, db *gorm.DB) (*model.Account, *model.AgentBot, *model.Inbox) { t.Helper() account := &model.Account{Name: "SvcTestOrg", Locale: "en", Active: true} require.NoError(t, db.Create(account).Error) bot := &model.AgentBot{ AccountID: &account.ID, Name: "SvcTestBot", BotType: "default", Secret: "secret-svcbot", AccessToken: "token-svcbot", } require.NoError(t, db.Create(bot).Error) inbox := &model.Inbox{ AccountID: account.ID, Name: "SvcTestInbox", ChannelType: "web_widget", ChannelID: 1, } require.NoError(t, db.Create(inbox).Error) return account, bot, inbox } // Helper: create a second bot with unique secret/token for SQLite UNIQUE constraint. func createSvcSecondBot(t *testing.T, db *gorm.DB, accountID uint, suffix string) *model.AgentBot { t.Helper() bot := &model.AgentBot{ AccountID: &accountID, Name: "SvcBot-" + suffix, BotType: "default", Secret: fmt.Sprintf("secret-svc-%s", suffix), AccessToken: fmt.Sprintf("token-svc-%s", suffix), } require.NoError(t, db.Create(bot).Error) return bot } // ========== 1. Bind ========== func TestAgentBotInboxService_Bind(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) abi, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) assert.NotZero(t, abi.ID) assert.Equal(t, bot.ID, abi.AgentBotID) assert.Equal(t, inbox.ID, abi.InboxID) assert.Equal(t, model.AgentBotInboxActive, abi.Status) } func TestAgentBotInboxService_Bind_BotNotFound(t *testing.T) { svc, _ := setupAgentBotInboxService(t) abi, err := svc.Bind(context.Background(), 1, BindBotToInboxRequest{ AgentBotID: 9999, InboxID: 1, }) assert.Error(t, err) assert.Nil(t, abi) assert.Contains(t, err.Error(), "agent bot not found") } func TestAgentBotInboxService_Bind_Duplicate(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) // First bind succeeds _, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) // Second bind fails (duplicate) abi, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) assert.Error(t, err) assert.Nil(t, abi) assert.Contains(t, err.Error(), "already bound") } // ========== 2. Unbind ========== func TestAgentBotInboxService_Unbind(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) abi, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) err = svc.Unbind(context.Background(), abi.ID) assert.NoError(t, err) // Verify binding is gone abis, err := svc.ListByInbox(context.Background(), inbox.ID) assert.NoError(t, err) assert.Len(t, abis, 0) } func TestAgentBotInboxService_Unbind_NotFound(t *testing.T) { svc, _ := setupAgentBotInboxService(t) err := svc.Unbind(context.Background(), 9999) assert.NoError(t, err) // GORM delete on non-existent row returns no error } // ========== 3. UpdateStatus ========== func TestAgentBotInboxService_UpdateStatus(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) abi, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) assert.Equal(t, model.AgentBotInboxActive, abi.Status) // Toggle to inactive updated, err := svc.UpdateStatus(context.Background(), abi.ID, UpdateBindingStatusRequest{Status: 1}) require.NoError(t, err) assert.Equal(t, model.AgentBotInboxInactive, updated.Status) // Toggle back to active updated2, err := svc.UpdateStatus(context.Background(), abi.ID, UpdateBindingStatusRequest{Status: 0}) require.NoError(t, err) assert.Equal(t, model.AgentBotInboxActive, updated2.Status) } func TestAgentBotInboxService_UpdateStatus_NotFound(t *testing.T) { svc, _ := setupAgentBotInboxService(t) abi, err := svc.UpdateStatus(context.Background(), 9999, UpdateBindingStatusRequest{Status: 1}) assert.Error(t, err) assert.Nil(t, abi) assert.Contains(t, err.Error(), "binding not found") } // ========== 4. ListByInbox ========== func TestAgentBotInboxService_ListByInbox(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) _, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) abis, err := svc.ListByInbox(context.Background(), inbox.ID) assert.NoError(t, err) assert.Len(t, abis, 1) assert.Equal(t, bot.ID, abis[0].AgentBotID) } func TestAgentBotInboxService_ListByInbox_Empty(t *testing.T) { svc, _ := setupAgentBotInboxService(t) abis, err := svc.ListByInbox(context.Background(), 9999) assert.NoError(t, err) assert.Len(t, abis, 0) } // ========== 5. ListActiveByInbox ========== func TestAgentBotInboxService_ListActiveByInbox(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) abi, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) // Initially active abis, err := svc.ListActiveByInbox(context.Background(), inbox.ID) assert.NoError(t, err) assert.Len(t, abis, 1) // Toggle to inactive _, err = svc.UpdateStatus(context.Background(), abi.ID, UpdateBindingStatusRequest{Status: 1}) require.NoError(t, err) // No active bindings now abis, err = svc.ListActiveByInbox(context.Background(), inbox.ID) assert.NoError(t, err) assert.Len(t, abis, 0) // But ListByInbox still shows 1 allAbis, err := svc.ListByInbox(context.Background(), inbox.ID) assert.NoError(t, err) assert.Len(t, allAbis, 1) } // ========== 6. ListByBot ========== func TestAgentBotInboxService_ListByBot(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, inbox := createSvcPrereqs(t, db) _, err := svc.Bind(context.Background(), inbox.AccountID, BindBotToInboxRequest{ AgentBotID: bot.ID, InboxID: inbox.ID, }) require.NoError(t, err) abis, err := svc.ListByBot(context.Background(), bot.ID) assert.NoError(t, err) assert.Len(t, abis, 1) assert.Equal(t, inbox.ID, abis[0].InboxID) } func TestAgentBotInboxService_ListByBot_Empty(t *testing.T) { svc, db := setupAgentBotInboxService(t) _, bot, _ := createSvcPrereqs(t, db) abis, err := svc.ListByBot(context.Background(), bot.ID) assert.NoError(t, err) assert.Len(t, abis, 0) }