完成客服工作台闭环
This commit is contained in:
@@ -336,3 +336,105 @@ func TestKnowledgeEntryRespectsPlanCapacity(t *testing.T) {
|
||||
t.Fatalf("超出知识库容量状态码 = %d,期望 %d,响应 = %s", recorder.Code, http.StatusConflict, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkbenchSessionLifecycleUnreadNotesTransferAndImage(t *testing.T) {
|
||||
router := setupRouter(t)
|
||||
tenant := createTenant(t, "工作台租户", "normal")
|
||||
agentOne := createUser(t, tenant.ID, "workbench-agent-one", "agent")
|
||||
agentTwo := createUser(t, tenant.ID, "workbench-agent-two", "agent")
|
||||
customer := model.Customer{TenantID: tenant.ID, Name: "工作台客户", Source: "网页"}
|
||||
if err := model.DB.Create(&customer).Error; err != nil {
|
||||
t.Fatalf("创建工作台客户失败: %v", err)
|
||||
}
|
||||
session := model.Session{TenantID: tenant.ID, CustomerID: customer.ID, Status: "waiting", Priority: "normal"}
|
||||
if err := model.DB.Create(&session).Error; err != nil {
|
||||
t.Fatalf("创建等待会话失败: %v", err)
|
||||
}
|
||||
visitorMessage := model.Message{SessionID: session.ID, SenderType: "visitor", Content: "需要咨询", Type: "text", Seq: 1, SentAt: time.Now()}
|
||||
if err := model.DB.Create(&visitorMessage).Error; err != nil {
|
||||
t.Fatalf("创建访客消息失败: %v", err)
|
||||
}
|
||||
|
||||
availableRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(availableRecorder, bearerRequest(t, http.MethodGet, "/api/agents/available", nil, agentOne))
|
||||
if availableRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("查询在线客服失败: %d %s", availableRecorder.Code, availableRecorder.Body.String())
|
||||
}
|
||||
|
||||
claimRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(claimRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/assign", session.ID), []byte(`{}`), agentOne))
|
||||
if claimRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("领取会话失败: %d %s", claimRecorder.Code, claimRecorder.Body.String())
|
||||
}
|
||||
|
||||
unreadRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(unreadRecorder, bearerRequest(t, http.MethodGet, "/api/sessions", nil, agentOne))
|
||||
var unreadResponse struct {
|
||||
List []struct {
|
||||
ID uint `json:"id"`
|
||||
UnreadCount int `json:"unread_count"`
|
||||
} `json:"list"`
|
||||
}
|
||||
if err := json.Unmarshal(unreadRecorder.Body.Bytes(), &unreadResponse); err != nil {
|
||||
t.Fatalf("解析未读列表失败: %v", err)
|
||||
}
|
||||
if len(unreadResponse.List) != 1 || unreadResponse.List[0].UnreadCount != 1 {
|
||||
t.Fatalf("领取后的未读数不正确: %s", unreadRecorder.Body.String())
|
||||
}
|
||||
|
||||
readRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(readRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/read", session.ID), []byte(`{}`), agentOne))
|
||||
if readRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("标记已读失败: %d %s", readRecorder.Code, readRecorder.Body.String())
|
||||
}
|
||||
readListRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(readListRecorder, bearerRequest(t, http.MethodGet, "/api/sessions", nil, agentOne))
|
||||
var readListResponse struct {
|
||||
List []struct {
|
||||
UnreadCount int `json:"unread_count"`
|
||||
} `json:"list"`
|
||||
}
|
||||
if err := json.Unmarshal(readListRecorder.Body.Bytes(), &readListResponse); err != nil || len(readListResponse.List) != 1 || readListResponse.List[0].UnreadCount != 0 {
|
||||
t.Fatalf("标记已读后的未读数不正确: %s", readListRecorder.Body.String())
|
||||
}
|
||||
|
||||
noteRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(noteRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/notes", session.ID), []byte(`{"content":"已核对客户需求"}`), agentOne))
|
||||
if noteRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("添加内部备注失败: %d %s", noteRecorder.Code, noteRecorder.Body.String())
|
||||
}
|
||||
|
||||
transferRecorder := httptest.NewRecorder()
|
||||
transferBody := []byte(fmt.Sprintf(`{"agent_id":%d}`, agentTwo.ID))
|
||||
router.ServeHTTP(transferRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/transfer", session.ID), transferBody, agentOne))
|
||||
if transferRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("转接会话失败: %d %s", transferRecorder.Code, transferRecorder.Body.String())
|
||||
}
|
||||
|
||||
oldAgentMessageRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(oldAgentMessageRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/messages", session.ID), []byte(`{"content":"越权回复"}`), agentOne))
|
||||
if oldAgentMessageRecorder.Code != http.StatusForbidden {
|
||||
t.Fatalf("转接后原客服仍可回复: %d %s", oldAgentMessageRecorder.Code, oldAgentMessageRecorder.Body.String())
|
||||
}
|
||||
|
||||
imageRecorder := httptest.NewRecorder()
|
||||
imageBody := []byte(`{"content":"data:image/png;base64,aGVsbG8=","type":"image"}`)
|
||||
router.ServeHTTP(imageRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/messages", session.ID), imageBody, agentTwo))
|
||||
if imageRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("发送图片消息失败: %d %s", imageRecorder.Code, imageRecorder.Body.String())
|
||||
}
|
||||
|
||||
invalidEndRecorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(invalidEndRecorder, bearerRequest(t, http.MethodPost, fmt.Sprintf("/api/sessions/%d/end?reason=bad_reason", session.ID), []byte(`{}`), agentTwo))
|
||||
if invalidEndRecorder.Code != http.StatusBadRequest {
|
||||
t.Fatalf("无效结束原因未被拦截: %d %s", invalidEndRecorder.Code, invalidEndRecorder.Body.String())
|
||||
}
|
||||
|
||||
var savedSession model.Session
|
||||
if err := model.DB.First(&savedSession, session.ID).Error; err != nil {
|
||||
t.Fatalf("读取工作台会话失败: %v", err)
|
||||
}
|
||||
if savedSession.AgentID == nil || *savedSession.AgentID != agentTwo.ID || savedSession.LastReadSeq != 2 {
|
||||
t.Fatalf("会话领取/转接/已读状态错误: %+v", savedSession)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user