fix(shangwutong): initialize contact attributes on inbox creation
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
pkgvalidator "github.com/gochat/gochat/pkg/validator"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const InboxLimitExceededMessage = "Account limit exceeded. Upgrade to a higher plan"
|
||||
@@ -362,6 +363,38 @@ func (s *InboxService) syncAPIChannel(ctx context.Context, inbox *model.Inbox) e
|
||||
|
||||
var shangwutongSessionIDPattern = regexp.MustCompile(`^[A-Za-z0-9]{11}$`)
|
||||
|
||||
var shangwutongContactAttributeDefinitions = []struct {
|
||||
name, displayName, description string
|
||||
}{
|
||||
{"swt_ip", "访客 IP", "商务通访客 IP 地址"},
|
||||
{"swt_ip_location", "IP 归属地", "商务通解析的访客 IP 归属地"},
|
||||
{"swt_resolution", "屏幕分辨率", "商务通访客设备屏幕分辨率"},
|
||||
{"swt_language", "浏览器语言", "商务通访客浏览器语言"},
|
||||
{"swt_timezone", "访客时区", "商务通访客设备时区"},
|
||||
{"swt_os", "操作系统", "商务通访客设备操作系统"},
|
||||
{"swt_browser", "浏览器", "商务通访客浏览器名称"},
|
||||
{"swt_browser_version", "浏览器版本", "商务通访客浏览器版本"},
|
||||
{"swt_user_agent", "User Agent", "商务通访客浏览器 User Agent"},
|
||||
{"swt_device", "访客设备", "商务通访客设备信息"},
|
||||
{"swt_query_title", "搜索主题", "商务通访客进入页面时的搜索主题"},
|
||||
{"swt_query_word", "搜索关键词", "商务通访客进入页面时的搜索关键词"},
|
||||
{"swt_traffic_source", "流量来源", "商务通访客流量来源"},
|
||||
{"swt_profile_channel", "来源渠道", "商务通访客来源渠道"},
|
||||
{"swt_site_id", "商务通站点 ID", "商务通站点标识"},
|
||||
{"swt_environment_version", "环境协议版本", "商务通 kind=7 环境信息格式版本;不是网络运营商"},
|
||||
}
|
||||
|
||||
func ensureShangwutongContactAttributeDefinitions(tx *gorm.DB, accountID uint) error {
|
||||
definitions := make([]model.CustomAttributeDefinition, len(shangwutongContactAttributeDefinitions))
|
||||
for index, definition := range shangwutongContactAttributeDefinitions {
|
||||
definitions[index] = model.CustomAttributeDefinition{
|
||||
AccountID: accountID, AttributeName: definition.name, AttributeDisplayName: definition.displayName,
|
||||
AttributeType: "text", AttributeModel: "contact_attribute", Description: definition.description,
|
||||
}
|
||||
}
|
||||
return tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&definitions).Error
|
||||
}
|
||||
|
||||
func (s *InboxService) createShangwutongInbox(ctx context.Context, accountID uint, req CreateInboxRequest) (*model.Inbox, error) {
|
||||
if err := validateShangwutongChannelKeys(req.Channel); err != nil {
|
||||
return nil, err
|
||||
@@ -399,6 +432,9 @@ func (s *InboxService) createShangwutongInbox(ctx context.Context, accountID uin
|
||||
if err := tx.Create(inbox).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureShangwutongContactAttributeDefinitions(tx, accountID); err != nil {
|
||||
return err
|
||||
}
|
||||
channelAPI.InboxID = inbox.ID
|
||||
config.InboxID = inbox.ID
|
||||
if err := tx.Create(channelAPI).Error; err != nil {
|
||||
|
||||
@@ -36,6 +36,7 @@ func setupInboxServiceTest(t *testing.T) (*InboxService, *gorm.DB) {
|
||||
&model.Account{},
|
||||
&model.AgentBot{},
|
||||
&model.Inbox{},
|
||||
&model.CustomAttributeDefinition{},
|
||||
&model.AgentBotInbox{},
|
||||
&model.WebhookSubscription{},
|
||||
&model.BackgroundJob{},
|
||||
@@ -346,6 +347,83 @@ func TestInboxService_CreateShangwutongPersistsSecretsOutsideInboxConfig(t *test
|
||||
assert.Equal(t, int64(1), config.ConfigVersion)
|
||||
}
|
||||
|
||||
func TestInboxService_CreateShangwutongEnsuresContactAttributeDefinitions(t *testing.T) {
|
||||
svc, db := setupInboxServiceTest(t)
|
||||
account := &model.Account{Name: "SWT attributes", Locale: "en", Active: true}
|
||||
require.NoError(t, db.Create(account).Error)
|
||||
require.NoError(t, db.Create(&model.CustomAttributeDefinition{
|
||||
AccountID: account.ID, AttributeName: "swt_ip", AttributeDisplayName: "保留现有名称",
|
||||
AttributeType: "number", AttributeModel: "contact_attribute", Description: "保留现有说明",
|
||||
}).Error)
|
||||
|
||||
create := func(name, sessionID, username string) {
|
||||
t.Helper()
|
||||
_, err := svc.Create(context.Background(), account.ID, CreateInboxRequest{
|
||||
Name: name, ChannelType: "shangwutong", Channel: map[string]any{
|
||||
"session_id": sessionID, "username": username, "password": "secret",
|
||||
"desired_presence": "online", "webhook_url": "http://connector:9100/webhooks/gochat/v1",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
create("商务通一", "BYT99917999", "agent1")
|
||||
create("商务通二", "BYT99917998", "agent2")
|
||||
|
||||
var definitions []model.CustomAttributeDefinition
|
||||
require.NoError(t, db.Where("account_id = ? AND attribute_model = ? AND deleted_at IS NULL", account.ID, "contact_attribute").Order("attribute_name").Find(&definitions).Error)
|
||||
require.Len(t, definitions, 16)
|
||||
|
||||
want := []string{
|
||||
"swt_browser", "swt_browser_version", "swt_device", "swt_environment_version",
|
||||
"swt_ip", "swt_ip_location", "swt_language", "swt_os", "swt_profile_channel",
|
||||
"swt_query_title", "swt_query_word", "swt_resolution", "swt_site_id",
|
||||
"swt_timezone", "swt_traffic_source", "swt_user_agent",
|
||||
}
|
||||
for index, definition := range definitions {
|
||||
require.Equal(t, want[index], definition.AttributeName)
|
||||
}
|
||||
require.Equal(t, "保留现有名称", definitions[4].AttributeDisplayName)
|
||||
require.Equal(t, "number", definitions[4].AttributeType)
|
||||
require.Equal(t, "保留现有说明", definitions[4].Description)
|
||||
|
||||
var legacySourceDefinitions int64
|
||||
require.NoError(t, db.Model(&model.CustomAttributeDefinition{}).
|
||||
Where("account_id = ? AND attribute_name LIKE ?", account.ID, "swt_source_%").
|
||||
Count(&legacySourceDefinitions).Error)
|
||||
require.Zero(t, legacySourceDefinitions)
|
||||
}
|
||||
|
||||
func TestInboxService_CreateShangwutongRollsBackWhenAttributeInitializationFails(t *testing.T) {
|
||||
svc, db := setupInboxServiceTest(t)
|
||||
account := &model.Account{Name: "SWT attribute rollback", Locale: "en", Active: true}
|
||||
require.NoError(t, db.Create(account).Error)
|
||||
require.NoError(t, db.Exec(`
|
||||
CREATE TRIGGER fail_swt_attribute_definition
|
||||
BEFORE INSERT ON custom_attribute_definitions
|
||||
WHEN NEW.attribute_name = 'swt_ip_location'
|
||||
BEGIN
|
||||
SELECT RAISE(FAIL, 'forced attribute initialization failure');
|
||||
END;
|
||||
`).Error)
|
||||
|
||||
inbox, err := svc.Create(context.Background(), account.ID, CreateInboxRequest{
|
||||
Name: "商务通回滚", ChannelType: "shangwutong", Channel: map[string]any{
|
||||
"session_id": "BYT99917999", "username": "agent", "password": "secret",
|
||||
"desired_presence": "online", "webhook_url": "http://connector:9100/webhooks/gochat/v1",
|
||||
},
|
||||
})
|
||||
require.ErrorContains(t, err, "forced attribute initialization failure")
|
||||
require.Nil(t, inbox)
|
||||
|
||||
for _, target := range []any{
|
||||
&model.Inbox{}, &channelmodel.ChannelAPI{}, &model.ChannelShangwutongConfig{}, &model.CustomAttributeDefinition{},
|
||||
} {
|
||||
var count int64
|
||||
require.NoError(t, db.Model(target).Count(&count).Error)
|
||||
require.Zero(t, count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxService_ShangwutongLifecycleJobsAreTransactionalAndKeepRotationDeleteSnapshots(t *testing.T) {
|
||||
svc, db := setupInboxServiceTest(t)
|
||||
wp := worker.NewWorkerPool(db)
|
||||
|
||||
Reference in New Issue
Block a user