feat: harden support, localize knowledge and add operator availability [skip ci]
Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.
Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.
Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.
Expected source tree: 0a4a7528e9.
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
// Modified in AgentDeskNext: authenticated support availability and calendars.
|
||||
// Modified in the independent hardening fork; see docs/hardening.
|
||||
|
||||
package services
|
||||
|
||||
import (
|
||||
@@ -6,8 +9,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"agent-desk/internal/events"
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/constants"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/eventbus"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
@@ -112,11 +118,13 @@ func findHumanDispatchRealtimeEvent(t *testing.T, session *ClientSession, eventT
|
||||
|
||||
func captureHumanDispatchRealtimeSession(t *testing.T, topics ...string) *ClientSession {
|
||||
t.Helper()
|
||||
principal := seedRealtimeStaffPrincipal(t, sqls.DB())
|
||||
session := &ClientSession{
|
||||
ID: "test-session",
|
||||
Role: realtimeRoleAdmin,
|
||||
Topics: map[string]struct{}{},
|
||||
Send: make(chan []byte, 32),
|
||||
ID: "test-session",
|
||||
Role: realtimeRoleAdmin,
|
||||
Principal: principal,
|
||||
Topics: map[string]struct{}{},
|
||||
Send: make(chan []byte, 32),
|
||||
}
|
||||
WsService.manager.Register(session, topics)
|
||||
return session
|
||||
@@ -124,6 +132,7 @@ func captureHumanDispatchRealtimeSession(t *testing.T, topics ...string) *Client
|
||||
|
||||
func setupHumanDispatchRealtimeTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
previousDB, previousWs := sqls.DB(), WsService
|
||||
dbName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
|
||||
db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
@@ -150,6 +159,7 @@ func setupHumanDispatchRealtimeTestDB(t *testing.T) *gorm.DB {
|
||||
&models.AgentTeam{},
|
||||
&models.AgentTeamSchedule{},
|
||||
&models.AgentProfile{},
|
||||
&models.SupportCalendar{}, &models.AgentPresenceLease{}, &models.LoginSession{}, &models.Role{}, &models.Permission{}, &models.UserRole{}, &models.RolePermission{}, &models.UserPermission{},
|
||||
&models.Conversation{},
|
||||
&models.ConversationParticipant{},
|
||||
&models.ConversationAssignment{},
|
||||
@@ -161,6 +171,13 @@ func setupHumanDispatchRealtimeTestDB(t *testing.T) *gorm.DB {
|
||||
t.Fatalf("auto migrate error = %v", err)
|
||||
}
|
||||
sqls.SetDB(db)
|
||||
t.Cleanup(func() {
|
||||
// Assignment notifications run asynchronously. Join them before replacing
|
||||
// the global fixture DB/service or closing its SQLite connection.
|
||||
eventbus.Get[events.ConversationAssignedEvent]().Wait()
|
||||
sqls.SetDB(previousDB)
|
||||
WsService = previousWs
|
||||
})
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -205,6 +222,7 @@ func createHumanDispatchRealtimeAgentProfile(t *testing.T, db *gorm.DB, userID,
|
||||
Username: "agent",
|
||||
Nickname: "客服",
|
||||
Status: enums.StatusOk,
|
||||
UserType: enums.UserTypeEmployee,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create user error = %v", err)
|
||||
}
|
||||
@@ -220,6 +238,25 @@ func createHumanDispatchRealtimeAgentProfile(t *testing.T, db *gorm.DB, userID,
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("create profile error = %v", err)
|
||||
}
|
||||
role := models.Role{Code: "test-live-agent", Status: enums.StatusOk}
|
||||
permission := models.Permission{Code: constants.PermissionConversationView.Code, Status: enums.StatusOk}
|
||||
session := models.LoginSession{UserID: userID, Token: "ak_isolated_routing_fixture", ExpiredAt: time.Now().Add(time.Hour)}
|
||||
// The observing WebSocket already seeds this shared permission. Reuse it
|
||||
// without changing its state, while the assigned operator gets its own role.
|
||||
if err := db.Where("code = ?", permission.Code).FirstOrCreate(&permission).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, item := range []any{&role, &session} {
|
||||
if err := db.Create(item).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for _, item := range []any{&models.UserRole{UserID: userID, RoleID: role.ID}, &models.RolePermission{RoleID: role.ID, PermissionID: permission.ID}, &models.AgentPresenceLease{ID: "isolated-routing-presence", UserID: userID, LoginSessionID: session.ID, ExpiresAt: time.Now().Add(time.Minute)}} {
|
||||
if err := db.Create(item).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createHumanDispatchRealtimeConversation(t *testing.T, db *gorm.DB, aiAgentID int64) models.Conversation {
|
||||
|
||||
Reference in New Issue
Block a user