package service import ( "context" "encoding/json" "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 setupACPolicyServiceTest(t *testing.T) (*AgentCapacityPolicyService, *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.User{}, &model.AccountUser{}, &model.Inbox{}, &model.AgentCapacityPolicy{}, &model.InboxCapacityLimit{}, ), "failed to auto-migrate") t.Cleanup(func() { sqlDB, _ := db.DB() sqlDB.Close() }) repo := repository.NewAgentCapacityPolicyRepo(db) svc := NewAgentCapacityPolicyService(repo) return svc, db } func acStringPtr(value string) *string { return &value } func createACPSvcTestAccount(t *testing.T, db *gorm.DB) *model.Account { t.Helper() account := &model.Account{Name: "ACPServiceOrg", Locale: "en", Active: true} require.NoError(t, db.Create(account).Error) return account } // ========== List ========== func TestAgentCapacityPolicyService_List(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) _, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "PolicyA", AssignmentLogic: "round_robin", ExclusionRules: json.RawMessage(`{"exclude_offline":true}`), }) require.NoError(t, err) _, err = svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "PolicyB", AssignmentLogic: "least_busy", ExclusionRules: json.RawMessage(`{"exclude_offline":false}`), }) require.NoError(t, err) policies, total, err := svc.List(context.Background(), account.ID, 1, 25) assert.NoError(t, err) assert.Equal(t, int64(2), total) assert.Len(t, policies, 2) } func TestAgentCapacityPolicyService_List_Empty(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) policies, total, err := svc.List(context.Background(), account.ID, 1, 25) assert.NoError(t, err) assert.Equal(t, int64(0), total) assert.Len(t, policies, 0) } // ========== Create ========== func TestAgentCapacityPolicyService_Create(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) policy, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "RoundRobinPolicy", ExclusionRules: json.RawMessage(`{"exclude_offline":true}`), }) assert.NoError(t, err) assert.NotZero(t, policy.ID) assert.Equal(t, "RoundRobinPolicy", policy.Name) assert.Equal(t, "round_robin", policy.AssignmentLogic) } func TestAgentCapacityPolicyService_Create_InvalidLogic(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) _, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "BadPolicy", AssignmentLogic: "invalid_logic", }) assert.Error(t, err) assert.Contains(t, err.Error(), "invalid assignment_logic") } // ========== GetByID ========== func TestAgentCapacityPolicyService_GetByID(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) created, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "TestPolicy", AssignmentLogic: "least_busy", }) require.NoError(t, err) found, err := svc.GetByID(context.Background(), created.ID, account.ID) assert.NoError(t, err) assert.Equal(t, created.ID, found.ID) assert.Equal(t, "TestPolicy", found.Name) } func TestAgentCapacityPolicyService_GetByID_WrongAccount(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) created, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "TestPolicy", AssignmentLogic: "round_robin", }) require.NoError(t, err) _, err = svc.GetByID(context.Background(), created.ID, 9999) assert.Error(t, err) } func TestAgentCapacityPolicyService_GetByID_NotFound(t *testing.T) { svc, _ := setupACPolicyServiceTest(t) _, err := svc.GetByID(context.Background(), 9999, 1) assert.Error(t, err) } // ========== Update ========== func TestAgentCapacityPolicyService_Update(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) created, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "OldPolicy", AssignmentLogic: "round_robin", }) require.NoError(t, err) updated, err := svc.Update(context.Background(), created.ID, account.ID, UpdateAgentCapacityPolicyRequest{ Name: acStringPtr("NewPolicy"), AssignmentLogic: "least_busy", }) assert.NoError(t, err) assert.Equal(t, "NewPolicy", updated.Name) assert.Equal(t, "least_busy", updated.AssignmentLogic) } func TestAgentCapacityPolicyService_Update_InvalidLogic(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) created, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "TestPolicy", AssignmentLogic: "round_robin", }) require.NoError(t, err) _, err = svc.Update(context.Background(), created.ID, account.ID, UpdateAgentCapacityPolicyRequest{ AssignmentLogic: "invalid", }) assert.Error(t, err) } func TestAgentCapacityPolicyService_Update_NotFound(t *testing.T) { svc, _ := setupACPolicyServiceTest(t) _, err := svc.Update(context.Background(), 9999, 1, UpdateAgentCapacityPolicyRequest{ Name: acStringPtr("Ghost"), }) assert.Error(t, err) } func TestAgentCapacityPolicyService_InboxCapacityLimitFlow(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) inbox := &model.Inbox{AccountID: account.ID, Name: "Priority", ChannelType: "web_widget"} require.NoError(t, db.Create(inbox).Error) otherAccount := createACPSvcTestAccount(t, db) otherInbox := &model.Inbox{AccountID: otherAccount.ID, Name: "Other", ChannelType: "web_widget"} require.NoError(t, db.Create(otherInbox).Error) policy, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{Name: "Policy"}) require.NoError(t, err) limit, err := svc.CreateInboxCapacityLimit(context.Background(), policy.ID, account.ID, CreateInboxCapacityLimitRequest{ InboxID: inbox.ID, ConversationLimit: 7, }) require.NoError(t, err) assert.Equal(t, inbox.ID, limit.InboxID) assert.Equal(t, 7, limit.ConversationLimit) _, err = svc.CreateInboxCapacityLimit(context.Background(), policy.ID, account.ID, CreateInboxCapacityLimitRequest{InboxID: inbox.ID, ConversationLimit: 9}) assert.Error(t, err) assert.Contains(t, err.Error(), "already been assigned") _, err = svc.CreateInboxCapacityLimit(context.Background(), policy.ID, account.ID, CreateInboxCapacityLimitRequest{InboxID: otherInbox.ID, ConversationLimit: 1}) assert.Error(t, err) assert.Contains(t, err.Error(), "inbox not found") _, err = svc.UpdateInboxCapacityLimit(context.Background(), policy.ID, account.ID, limit.ID, UpdateInboxCapacityLimitRequest{ConversationLimit: -1}) assert.Error(t, err) assert.Contains(t, err.Error(), "greater than or equal") updated, err := svc.UpdateInboxCapacityLimit(context.Background(), policy.ID, account.ID, limit.ID, UpdateInboxCapacityLimitRequest{ConversationLimit: 11}) require.NoError(t, err) assert.Equal(t, 11, updated.ConversationLimit) assert.Equal(t, "Priority", updated.Inbox.Name) require.NoError(t, svc.DeleteInboxCapacityLimit(context.Background(), policy.ID, account.ID, limit.ID)) _, err = svc.UpdateInboxCapacityLimit(context.Background(), policy.ID, account.ID, limit.ID, UpdateInboxCapacityLimitRequest{ConversationLimit: 1}) assert.Error(t, err) } func TestAgentCapacityPolicyService_UserAssignmentFlow(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) policy, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{Name: "Policy"}) require.NoError(t, err) user := &model.User{AccountID: account.ID, Name: "Agent", Email: "agent@example.com", Password: "secret", Role: "agent"} require.NoError(t, db.Create(user).Error) accountUser := &model.AccountUser{AccountID: account.ID, UserID: user.ID, Role: "agent"} require.NoError(t, db.Create(accountUser).Error) assigned, err := svc.AssignUser(context.Background(), policy.ID, account.ID, AssignCapacityPolicyUserRequest{UserID: user.ID}) require.NoError(t, err) assert.Equal(t, user.ID, assigned.ID) users, err := svc.ListUsers(context.Background(), policy.ID, account.ID) require.NoError(t, err) assert.Len(t, users, 1) require.NoError(t, svc.RemoveUser(context.Background(), policy.ID, account.ID, user.ID)) users, err = svc.ListUsers(context.Background(), policy.ID, account.ID) require.NoError(t, err) assert.Empty(t, users) } // ========== Delete ========== func TestAgentCapacityPolicyService_Delete(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) created, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "DeletablePolicy", AssignmentLogic: "round_robin", }) require.NoError(t, err) err = svc.Delete(context.Background(), created.ID, account.ID) assert.NoError(t, err) // Should no longer find it _, err = svc.GetByID(context.Background(), created.ID, account.ID) assert.Error(t, err) } func TestAgentCapacityPolicyService_Delete_WrongAccount(t *testing.T) { svc, db := setupACPolicyServiceTest(t) account := createACPSvcTestAccount(t, db) created, err := svc.Create(context.Background(), account.ID, CreateAgentCapacityPolicyRequest{ Name: "ProtectedPolicy", AssignmentLogic: "round_robin", }) require.NoError(t, err) err = svc.Delete(context.Background(), created.ID, 9999) assert.Error(t, err) // Policy should still exist found, err := svc.GetByID(context.Background(), created.ID, account.ID) assert.NoError(t, err) assert.Equal(t, created.ID, found.ID) } func TestAgentCapacityPolicyService_Delete_NotFound(t *testing.T) { svc, _ := setupACPolicyServiceTest(t) err := svc.Delete(context.Background(), 9999, 1) assert.Error(t, err) }