HH-554: connect auto-assignment runtime policy (#135)

* fix(HH-554): connect auto-assignment runtime policy

* fix(HH-554): bound and coalesce assignment jobs

* fix(HH-554): drain assignment backlog safely

* fix(HH-554): hold assignment claim through retries

---------

Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
Rogee
2026-08-23 22:28:43 +08:00
committed by GitHub
co-authored by rogee
parent 35727be51a
commit 17244bcc9d
15 changed files with 826 additions and 269 deletions
@@ -28,7 +28,8 @@ func migrateFullAADB_Cov9(t *testing.T, db *gorm.DB) {
&model.Account{}, &model.User{}, &model.AccountUser{},
&model.Inbox{}, &model.InboxMember{}, &model.Contact{}, &model.ContactInbox{},
&model.Conversation{}, &model.Message{},
&AssignmentPolicy{}, &InboxAssignmentPolicy{},
&model.Team{}, &model.TeamMember{}, &model.BackgroundJob{},
&model.AssignmentPolicy{}, &model.InboxAssignmentPolicy{},
&model.AgentCapacityPolicy{}, &model.InboxCapacityLimit{},
))
}
@@ -110,29 +111,16 @@ func seedAssignableConversation_Cov9(t *testing.T, db *gorm.DB) (*model.Account,
return acc, agent, inbox, conv
}
func TestEffectivePolicyLimitWindow_ActiveInactive_Cov9(t *testing.T) {
activeAccount := &AssignmentPolicy{Policy: PolicyLongestWaiting, FairDistributionLimit: 11, FairDistributionWindow: 111, Active: true}
inactiveAccount := &AssignmentPolicy{Policy: PolicyLowestLoad, FairDistributionLimit: 22, FairDistributionWindow: 222, Active: false}
activeInbox := &InboxAssignmentPolicy{Policy: PolicyLowestLoad, FairDistributionLimit: 33, FairDistributionWindow: 333, Active: true}
inactiveInbox := &InboxAssignmentPolicy{Policy: PolicyLowestLoad, FairDistributionLimit: 44, FairDistributionWindow: 444, Active: false}
func TestEffectivePolicyLimitWindow_Cov9(t *testing.T) {
policy := &model.AssignmentPolicy{AssignmentOrder: 1, FairDistributionLimit: 11, FairDistributionWindow: 111, Enabled: true}
require.Equal(t, PolicyRoundRobin, EffectivePolicy(nil, nil))
require.Equal(t, PolicyLongestWaiting, EffectivePolicy(activeAccount, nil))
require.Equal(t, PolicyRoundRobin, EffectivePolicy(inactiveAccount, nil))
require.Equal(t, PolicyLowestLoad, EffectivePolicy(activeAccount, activeInbox))
require.Equal(t, PolicyLongestWaiting, EffectivePolicy(activeAccount, inactiveInbox))
require.Equal(t, 5, EffectiveLimit(nil, nil))
require.Equal(t, 11, EffectiveLimit(activeAccount, nil))
require.Equal(t, 22, EffectiveLimit(inactiveAccount, nil))
require.Equal(t, 33, EffectiveLimit(activeAccount, activeInbox))
require.Equal(t, 11, EffectiveLimit(activeAccount, inactiveInbox))
require.Equal(t, 300, EffectiveWindow(nil, nil))
require.Equal(t, 111, EffectiveWindow(activeAccount, nil))
require.Equal(t, 222, EffectiveWindow(inactiveAccount, nil))
require.Equal(t, 333, EffectiveWindow(activeAccount, activeInbox))
require.Equal(t, 111, EffectiveWindow(activeAccount, inactiveInbox))
require.Equal(t, PolicyRoundRobin, EffectivePolicy(nil, false))
require.Equal(t, PolicyRoundRobin, EffectivePolicy(policy, false))
require.Equal(t, PolicyLowestLoad, EffectivePolicy(policy, true))
require.Equal(t, 5, EffectiveLimit(nil))
require.Equal(t, 11, EffectiveLimit(policy))
require.Equal(t, 300, EffectiveWindow(nil))
require.Equal(t, 111, EffectiveWindow(policy))
}
func TestAssignmentService_AssignConversation_Success_Cov9(t *testing.T) {
@@ -172,7 +160,7 @@ func TestAssignmentServiceRejectsCandidateDeactivatedAfterSelection(t *testing.T
db, rdb := setupFullAADB_Cov9(t)
account, agent, inbox, conversation := seedAssignableConversation_Cov9(t, db)
svc := NewAssignmentService(db, rdb)
agents, err := svc.getEligibleAgents(context.Background(), inbox.ID, account.ID)
agents, err := svc.getEligibleAgents(context.Background(), inbox.ID, account.ID, nil, false)
require.NoError(t, err)
require.Equal(t, []uint{agent.ID}, agents)
@@ -314,7 +302,7 @@ func TestAssignmentServiceExcludesNonAgentInboxMembers(t *testing.T) {
Where("account_id = ? AND user_id = ?", account.ID, agent.ID).
Update("role", "member").Error)
agents, err := NewAssignmentService(db, rdb).getEligibleAgents(context.Background(), inbox.ID, account.ID)
agents, err := NewAssignmentService(db, rdb).getEligibleAgents(context.Background(), inbox.ID, account.ID, nil, false)
require.NoError(t, err)
require.Empty(t, agents)
}
@@ -323,20 +311,20 @@ func TestAssignmentService_GetAndPolicyHelpers_Cov9(t *testing.T) {
db, rdb := setupFullAADB_Cov9(t)
acc, _, inbox, conv := seedAssignableConversation_Cov9(t, db)
svc := NewAssignmentService(db, rdb)
policy := &AssignmentPolicy{AccountID: acc.ID, Policy: PolicyLowestLoad, FairDistributionLimit: 7, FairDistributionWindow: 77, Active: true}
policy := &model.AssignmentPolicy{AccountID: acc.ID, Name: "Runtime", AssignmentOrder: 1, FairDistributionLimit: 7, FairDistributionWindow: 77, Enabled: true}
require.NoError(t, db.Create(policy).Error)
inboxPolicy := &InboxAssignmentPolicy{AccountID: acc.ID, InboxID: inbox.ID, Policy: PolicyLongestWaiting, FairDistributionLimit: 8, FairDistributionWindow: 88, Active: true}
require.NoError(t, db.Create(inboxPolicy).Error)
require.NoError(t, db.Create(&model.InboxAssignmentPolicy{InboxID: inbox.ID, AssignmentPolicyID: policy.ID}).Error)
loadedInbox, err := svc.getInbox(context.Background(), inbox.ID)
require.NoError(t, err)
require.Equal(t, inbox.ID, loadedInbox.ID)
convs, err := svc.findUnassignedConversations(context.Background(), inbox.ID, acc.ID)
convs, err := svc.findUnassignedConversations(context.Background(), inbox.ID, acc.ID, policy)
require.NoError(t, err)
require.NotEmpty(t, convs)
_ = conv
require.Equal(t, PolicyLowestLoad, svc.getAccountPolicy(context.Background(), acc.ID).Policy)
require.Equal(t, PolicyLongestWaiting, svc.getInboxPolicy(context.Background(), inbox.ID).Policy)
loadedPolicy, err := svc.getInboxPolicy(context.Background(), acc.ID, inbox.ID)
require.NoError(t, err)
require.Equal(t, policy.ID, loadedPolicy.ID)
}
func TestAssignmentService_AgentHasInboxCapacity_Limited_Cov9(t *testing.T) {