package service import ( "context" "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 setupCustomAttrDefServiceTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("failed to open SQLite test database: %v", err) } if err := db.AutoMigrate( &model.Account{}, &model.User{}, &model.CustomAttributeDefinition{}, ); err != nil { t.Fatalf("failed to auto-migrate models: %v", err) } t.Cleanup(func() { sqlDB, _ := db.DB() sqlDB.Close() }) return db } func setupCustomAttrDefService(t *testing.T) (*CustomAttributeDefinitionService, uint) { t.Helper() db := setupCustomAttrDefServiceTestDB(t) repo := repository.NewCustomAttributeDefinitionRepo(db) svc := NewCustomAttributeDefinitionService(repo) account := createTestAccount(t, db) return svc, account.ID } // ========== Create ========== func TestCustomAttributeDefinitionService_Create(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "custom_priority_score", AttributeDisplayName: "Custom Priority Score", AttributeDisplayType: "text", AttributeModel: "conversation", AttributeDescription: "Conversation priority level", } def, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) assert.NotZero(t, def.ID) assert.Equal(t, accountID, def.AccountID) assert.Equal(t, "custom_priority_score", def.AttributeName) assert.Equal(t, "Custom Priority Score", def.AttributeDisplayName) assert.Equal(t, "text", def.AttributeType) assert.Equal(t, "conversation", def.AttributeModel) assert.Equal(t, "Conversation priority level", def.Description) } func TestCustomAttributeDefinitionService_Create_InvalidType(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "bad_type", AttributeDisplayName: "Bad Type", AttributeDisplayType: "invalid_type", AttributeModel: "conversation", } _, err := svc.Create(context.Background(), accountID, req) assert.Error(t, err, "invalid attribute_type should fail validation") } func TestCustomAttributeDefinitionService_Create_InvalidModel(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "bad_model", AttributeDisplayName: "Bad Model", AttributeDisplayType: "text", AttributeModel: "invalid_model", } _, err := svc.Create(context.Background(), accountID, req) assert.Error(t, err, "invalid attribute_model should fail validation") } func TestCustomAttributeDefinitionService_Create_EmptyName(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "", AttributeDisplayName: "Empty Name", AttributeDisplayType: "text", AttributeModel: "conversation", } _, err := svc.Create(context.Background(), accountID, req) assert.Error(t, err, "empty attribute_name should fail validation") } func TestCustomAttributeDefinitionService_Create_AllValidTypes(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) validTypes := []string{"text", "number", "date", "checkbox", "list", "link"} for _, attrType := range validTypes { req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "attr_" + attrType, AttributeDisplayName: "Attr " + attrType, AttributeDisplayType: attrType, AttributeModel: "conversation", } def, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err, "attribute_type=%s should be valid", attrType) assert.Equal(t, attrType, def.AttributeType) } } // ========== Get ========== func TestCustomAttributeDefinitionService_Get(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "status", AttributeDisplayName: "Status", AttributeDisplayType: "list", AttributeModel: "contact", } def, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) found, err := svc.Get(context.Background(), accountID, def.ID) require.NoError(t, err) assert.Equal(t, def.ID, found.ID) assert.Equal(t, "status", found.AttributeName) } func TestCustomAttributeDefinitionService_Get_WrongAccount(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "private_attr", AttributeDisplayName: "Private", AttributeDisplayType: "text", AttributeModel: "conversation", } def, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) // Trying to get from a different account should fail _, err = svc.Get(context.Background(), 9999, def.ID) assert.Error(t, err, "should not find definition belonging to different account") } func TestCustomAttributeDefinitionService_Get_NotFound(t *testing.T) { svc, _ := setupCustomAttrDefService(t) _, err := svc.Get(context.Background(), 1, 9999) assert.Error(t, err) } // ========== List ========== func TestCustomAttributeDefinitionService_List_All(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) for _, name := range []string{"attr_a", "attr_b"} { req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: name, AttributeDisplayName: name, AttributeDisplayType: "text", AttributeModel: "conversation", } _, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) } // Create a contact attribute req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "contact_level", AttributeDisplayName: "Contact Level", AttributeDisplayType: "text", AttributeModel: "contact", } _, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) defs, total, err := svc.List(context.Background(), accountID, "", 0, 10) require.NoError(t, err) assert.Equal(t, int64(3), total) assert.Len(t, defs, 3) } func TestCustomAttributeDefinitionService_List_FilterByModel(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) // Create conversation attributes for _, name := range []string{"conv_a", "conv_b"} { req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: name, AttributeDisplayName: name, AttributeDisplayType: "text", AttributeModel: "conversation", } _, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) } // Create contact attribute req := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "contact_c", AttributeDisplayName: "Contact C", AttributeDisplayType: "text", AttributeModel: "contact", } _, err := svc.Create(context.Background(), accountID, req) require.NoError(t, err) convDefs, convTotal, err := svc.List(context.Background(), accountID, "conversation", 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), convTotal) assert.Len(t, convDefs, 2) contactDefs, contactTotal, err := svc.List(context.Background(), accountID, "contact", 0, 10) require.NoError(t, err) assert.Equal(t, int64(1), contactTotal) assert.Len(t, contactDefs, 1) } // ========== Update ========== func TestCustomAttributeDefinitionService_Update(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) createReq := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "custom_priority_score", AttributeDisplayName: "Custom Priority Score", AttributeDisplayType: "text", AttributeModel: "conversation", } def, err := svc.Create(context.Background(), accountID, createReq) require.NoError(t, err) updateReq := &UpdateCustomAttributeDefinitionRequest{ AttributeDisplayName: "Priority Level", AttributeDisplayType: "number", AttributeDescription: "Updated description", } updated, err := svc.Update(context.Background(), accountID, def.ID, updateReq) require.NoError(t, err) assert.Equal(t, "Priority Level", updated.AttributeDisplayName) assert.Equal(t, "number", updated.AttributeType) assert.Equal(t, "Updated description", updated.Description) // Name and model should stay unchanged assert.Equal(t, "custom_priority_score", updated.AttributeName) assert.Equal(t, "conversation", updated.AttributeModel) } func TestCustomAttributeDefinitionService_Update_WrongAccount(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) createReq := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "private_attr", AttributeDisplayName: "Private", AttributeDisplayType: "text", AttributeModel: "conversation", } def, err := svc.Create(context.Background(), accountID, createReq) require.NoError(t, err) updateReq := &UpdateCustomAttributeDefinitionRequest{ AttributeDisplayName: "Hacked", } _, err = svc.Update(context.Background(), 9999, def.ID, updateReq) assert.Error(t, err, "should not update definition belonging to different account") } func TestCustomAttributeDefinitionService_Update_NotFound(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) updateReq := &UpdateCustomAttributeDefinitionRequest{ AttributeDisplayName: "Nonexistent", } _, err := svc.Update(context.Background(), accountID, 9999, updateReq) assert.Error(t, err) } // ========== Delete ========== func TestCustomAttributeDefinitionService_Delete(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) createReq := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "temp_attr", AttributeDisplayName: "Temp", AttributeDisplayType: "text", AttributeModel: "conversation", } def, err := svc.Create(context.Background(), accountID, createReq) require.NoError(t, err) err = svc.Delete(context.Background(), accountID, def.ID) require.NoError(t, err) // Should no longer be accessible via Get _, err = svc.Get(context.Background(), accountID, def.ID) assert.Error(t, err) } func TestCustomAttributeDefinitionService_Delete_WrongAccount(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) createReq := &CreateCustomAttributeDefinitionRequest{ AttributeKey: "private_attr", AttributeDisplayName: "Private", AttributeDisplayType: "text", AttributeModel: "conversation", } def, err := svc.Create(context.Background(), accountID, createReq) require.NoError(t, err) err = svc.Delete(context.Background(), 9999, def.ID) assert.Error(t, err, "should not delete definition belonging to different account") } func TestCustomAttributeDefinitionService_Delete_NotFound(t *testing.T) { svc, accountID := setupCustomAttrDefService(t) err := svc.Delete(context.Background(), accountID, 9999) assert.Error(t, err) }