Files
gochat/internal/service/custom_attribute_value_service_test.go_BAK
T
2026-06-04 15:44:48 +08:00

485 lines
15 KiB
Plaintext

package service
import (
"context"
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/datatypes"
"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 setupCustomAttrValueServiceTestDB(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{},
&model.Contact{},
&model.ContactInbox{},
&model.Inbox{},
&model.Conversation{},
&model.Message{},
); err != nil {
t.Fatalf("failed to auto-migrate models: %v", err)
}
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
return db
}
func setupCustomAttrValueService(t *testing.T) (*CustomAttributeValueService, uint, *gorm.DB) {
t.Helper()
db := setupCustomAttrValueServiceTestDB(t)
defRepo := repository.NewCustomAttributeDefinitionRepo(db)
convRepo := repository.NewConversationRepo(db)
contactRepo := repository.NewContactRepo(db)
svc := NewCustomAttributeValueService(defRepo, convRepo, contactRepo)
account := createTestAccount(t, db)
return svc, account.ID, db
}
// Helper: create a conversation attribute definition
func createConvAttrDef(t *testing.T, svc *CustomAttributeValueService, accountID uint, attrType string) {
t.Helper()
defRepo := svc.defRepo
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "conv_" + attrType,
AttributeDisplayName: "Conv " + attrType,
AttributeType: attrType,
AttributeModel: "conversation",
}
require.NoError(t, defRepo.Create(context.Background(), def))
}
// Helper: create a contact attribute definition
func createContactAttrDef(t *testing.T, svc *CustomAttributeValueService, accountID uint, attrType string) {
t.Helper()
defRepo := svc.defRepo
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "contact_" + attrType,
AttributeDisplayName: "Contact " + attrType,
AttributeType: attrType,
AttributeModel: "contact",
}
require.NoError(t, defRepo.Create(context.Background(), def))
}
// Helper: create a minimal conversation
func createTestConversation(t *testing.T, db *gorm.DB, accountID uint) *model.Conversation {
t.Helper()
inbox := &model.Inbox{AccountID: accountID, Name: "Test Inbox", ChannelType: "web_widget"}
require.NoError(t, db.Create(inbox).Error)
contact := &model.Contact{AccountID: accountID, Name: "Test Contact"}
require.NoError(t, db.Create(contact).Error)
conv := &model.Conversation{AccountID: accountID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open"}
require.NoError(t, db.Create(conv).Error)
return conv
}
// Helper: create a minimal contact
func createTestContactForAttr(t *testing.T, db *gorm.DB, accountID uint) *model.Contact {
t.Helper()
contact := &model.Contact{AccountID: accountID, Name: "Attr Test Contact"}
require.NoError(t, db.Create(contact).Error)
return contact
}
// ========== validateValueType tests ==========
func TestValidateValueType_Text(t *testing.T) {
assert.NoError(t, validateValueType("text", "hello"))
assert.Error(t, validateValueType("text", 42))
assert.Error(t, validateValueType("text", true))
}
func TestValidateValueType_Link(t *testing.T) {
assert.NoError(t, validateValueType("link", "https://example.com"))
assert.Error(t, validateValueType("link", 42))
}
func TestValidateValueType_Number(t *testing.T) {
assert.NoError(t, validateValueType("number", float64(3.14)))
assert.NoError(t, validateValueType("number", int(42)))
assert.NoError(t, validateValueType("number", int64(100)))
assert.NoError(t, validateValueType("number", int32(5)))
assert.NoError(t, validateValueType("number", float32(1.5)))
assert.Error(t, validateValueType("number", "not a number"))
assert.Error(t, validateValueType("number", true))
}
func TestValidateValueType_Date(t *testing.T) {
assert.NoError(t, validateValueType("date", "2024-01-01"))
assert.Error(t, validateValueType("date", 42))
assert.Error(t, validateValueType("date", true))
}
func TestValidateValueType_Checkbox(t *testing.T) {
assert.NoError(t, validateValueType("checkbox", true))
assert.NoError(t, validateValueType("checkbox", false))
assert.Error(t, validateValueType("checkbox", "yes"))
assert.Error(t, validateValueType("checkbox", 1))
}
func TestValidateValueType_List(t *testing.T) {
// List accepts any value
assert.NoError(t, validateValueType("list", "anything"))
assert.NoError(t, validateValueType("list", 42))
assert.NoError(t, validateValueType("list", true))
assert.NoError(t, validateValueType("list", []string{"a", "b"}))
}
func TestValidateValueType_UnknownType(t *testing.T) {
// Unknown types are accepted
assert.NoError(t, validateValueType("unknown", "anything"))
}
// ========== mergeIntoJSON tests ==========
func TestMergeIntoJSON_NewKey(t *testing.T) {
result, err := mergeIntoJSON(nil, "priority", "high")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Equal(t, "high", data["priority"])
}
func TestMergeIntoJSON_ExistingJSON(t *testing.T) {
existing := datatypes.JSON(`{"status":"open","source":"web"}`)
result, err := mergeIntoJSON(existing, "priority", "high")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Equal(t, "high", data["priority"])
assert.Equal(t, "open", data["status"])
assert.Equal(t, "web", data["source"])
}
func TestMergeIntoJSON_OverwriteExistingKey(t *testing.T) {
existing := datatypes.JSON(`{"priority":"low"}`)
result, err := mergeIntoJSON(existing, "priority", "high")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Equal(t, "high", data["priority"])
}
func TestMergeIntoJSON_EmptyJSON(t *testing.T) {
result, err := mergeIntoJSON(datatypes.JSON(``), "key", "val")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Equal(t, "val", data["key"])
}
// ========== removeFromJSON tests ==========
func TestRemoveFromJSON_ExistingKey(t *testing.T) {
existing := datatypes.JSON(`{"status":"open","priority":"high"}`)
result, err := removeFromJSON(existing, "priority")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Equal(t, "open", data["status"])
assert.NotContains(t, data, "priority")
}
func TestRemoveFromJSON_NonexistentKey(t *testing.T) {
existing := datatypes.JSON(`{"status":"open"}`)
result, err := removeFromJSON(existing, "nonexistent")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Equal(t, "open", data["status"])
}
func TestRemoveFromJSON_NilOrEmpty(t *testing.T) {
result, err := removeFromJSON(nil, "key")
require.NoError(t, err)
assert.Equal(t, datatypes.JSON("{}"), result)
result, err = removeFromJSON(datatypes.JSON(``), "key")
require.NoError(t, err)
assert.Equal(t, datatypes.JSON("{}"), result)
}
func TestRemoveFromJSON_AllKeys(t *testing.T) {
existing := datatypes.JSON(`{"a":1,"b":2}`)
result, err := removeFromJSON(existing, "a")
require.NoError(t, err)
result, err = removeFromJSON(result, "b")
require.NoError(t, err)
var data map[string]interface{}
require.NoError(t, json.Unmarshal(result, &data))
assert.Empty(t, data)
}
// ========== SetConversationAttributeValue ==========
func TestCustomAttributeValueService_SetConversationAttributeValue(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
// Create a conversation attribute definition (text type)
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "conv_priority",
AttributeDisplayName: "Conv Priority",
AttributeType: "text",
AttributeModel: "conversation",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
conv := createTestConversation(t, db, accountID)
req := &SetAttributeValueRequest{
AttributeName: "conv_priority",
Value: "high",
}
result, err := svc.SetConversationAttributeValue(context.Background(), accountID, conv.ID, req)
require.NoError(t, err)
var attrs map[string]interface{}
require.NoError(t, json.Unmarshal(result.CustomAttributes, &attrs))
assert.Equal(t, "high", attrs["conv_priority"])
}
func TestCustomAttributeValueService_SetConversationAttributeValue_WrongType(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
// Create a number-type attribute definition
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "conv_score",
AttributeDisplayName: "Conv Score",
AttributeType: "number",
AttributeModel: "conversation",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
conv := createTestConversation(t, db, accountID)
req := &SetAttributeValueRequest{
AttributeName: "conv_score",
Value: "not_a_number", // wrong type
}
_, err := svc.SetConversationAttributeValue(context.Background(), accountID, conv.ID, req)
assert.Error(t, err, "should fail when value type doesn't match attribute type")
}
func TestCustomAttributeValueService_SetConversationAttributeValue_DefNotFound(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
conv := createTestConversation(t, db, accountID)
req := &SetAttributeValueRequest{
AttributeName: "nonexistent_attr",
Value: "val",
}
_, err := svc.SetConversationAttributeValue(context.Background(), accountID, conv.ID, req)
assert.Error(t, err, "should fail when attribute definition doesn't exist")
}
func TestCustomAttributeValueService_SetConversationAttributeValue_ConvNotFound(t *testing.T) {
svc, accountID, _ := setupCustomAttrValueService(t)
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "conv_priority",
AttributeDisplayName: "Conv Priority",
AttributeType: "text",
AttributeModel: "conversation",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
req := &SetAttributeValueRequest{
AttributeName: "conv_priority",
Value: "high",
}
_, err := svc.SetConversationAttributeValue(context.Background(), accountID, 9999, req)
assert.Error(t, err, "should fail when conversation doesn't exist")
}
// ========== RemoveConversationAttributeValue ==========
func TestCustomAttributeValueService_RemoveConversationAttributeValue(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "conv_priority",
AttributeDisplayName: "Conv Priority",
AttributeType: "text",
AttributeModel: "conversation",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
conv := createTestConversation(t, db, accountID)
// First set the value
setReq := &SetAttributeValueRequest{
AttributeName: "conv_priority",
Value: "high",
}
_, err := svc.SetConversationAttributeValue(context.Background(), accountID, conv.ID, setReq)
require.NoError(t, err)
// Then remove it
result, err := svc.RemoveConversationAttributeValue(context.Background(), accountID, conv.ID, "conv_priority")
require.NoError(t, err)
var attrs map[string]interface{}
require.NoError(t, json.Unmarshal(result.CustomAttributes, &attrs))
assert.NotContains(t, attrs, "conv_priority")
}
func TestCustomAttributeValueService_RemoveConversationAttributeValue_DefNotFound(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
conv := createTestConversation(t, db, accountID)
_, err := svc.RemoveConversationAttributeValue(context.Background(), accountID, conv.ID, "nonexistent")
assert.Error(t, err)
}
// ========== SetContactAttributeValue ==========
func TestCustomAttributeValueService_SetContactAttributeValue(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "contact_level",
AttributeDisplayName: "Contact Level",
AttributeType: "text",
AttributeModel: "contact",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
contact := createTestContactForAttr(t, db, accountID)
req := &SetAttributeValueRequest{
AttributeName: "contact_level",
Value: "VIP",
}
result, err := svc.SetContactAttributeValue(context.Background(), accountID, contact.ID, req)
require.NoError(t, err)
var attrs map[string]interface{}
require.NoError(t, json.Unmarshal(result.CustomAttributes, &attrs))
assert.Equal(t, "VIP", attrs["contact_level"])
}
func TestCustomAttributeValueService_SetContactAttributeValue_WrongType(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "contact_score",
AttributeDisplayName: "Contact Score",
AttributeType: "number",
AttributeModel: "contact",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
contact := createTestContactForAttr(t, db, accountID)
req := &SetAttributeValueRequest{
AttributeName: "contact_score",
Value: "not_a_number",
}
_, err := svc.SetContactAttributeValue(context.Background(), accountID, contact.ID, req)
assert.Error(t, err)
}
func TestCustomAttributeValueService_SetContactAttributeValue_DefNotFound(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
contact := createTestContactForAttr(t, db, accountID)
req := &SetAttributeValueRequest{
AttributeName: "nonexistent",
Value: "val",
}
_, err := svc.SetContactAttributeValue(context.Background(), accountID, contact.ID, req)
assert.Error(t, err)
}
// ========== RemoveContactAttributeValue ==========
func TestCustomAttributeValueService_RemoveContactAttributeValue(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
def := &model.CustomAttributeDefinition{
AccountID: accountID,
AttributeName: "contact_level",
AttributeDisplayName: "Contact Level",
AttributeType: "text",
AttributeModel: "contact",
}
require.NoError(t, svc.defRepo.Create(context.Background(), def))
contact := createTestContactForAttr(t, db, accountID)
// Set value first
setReq := &SetAttributeValueRequest{
AttributeName: "contact_level",
Value: "VIP",
}
_, err := svc.SetContactAttributeValue(context.Background(), accountID, contact.ID, setReq)
require.NoError(t, err)
// Remove it
result, err := svc.RemoveContactAttributeValue(context.Background(), accountID, contact.ID, "contact_level")
require.NoError(t, err)
var attrs map[string]interface{}
require.NoError(t, json.Unmarshal(result.CustomAttributes, &attrs))
assert.NotContains(t, attrs, "contact_level")
}
func TestCustomAttributeValueService_RemoveContactAttributeValue_DefNotFound(t *testing.T) {
svc, accountID, db := setupCustomAttrValueService(t)
contact := createTestContactForAttr(t, db, accountID)
_, err := svc.RemoveContactAttributeValue(context.Background(), accountID, contact.ID, "nonexistent")
assert.Error(t, err)
}