refactor: 将客服后端重构为宿主可嵌入模块
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。 - 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。 - 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。 - 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -14,18 +15,17 @@ import (
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func TestReplyCommitStoresWorkflowRunIDOnAIMessage(t *testing.T) {
|
||||
func TestReplyCommitStoresAIMessage(t *testing.T) {
|
||||
db := setupReplyCommitTestDB(t)
|
||||
aiAgent := createReplyCommitTestAIAgent(t, db)
|
||||
conversation := createReplyCommitTestConversation(t, db, aiAgent.ID)
|
||||
|
||||
replyMessage, err := newReplyCommitService().CommitAIReply(replyCommitInput{
|
||||
Conversation: *conversation,
|
||||
Message: models.Message{ID: 101, RequestID: "trace-101"},
|
||||
AIAgent: *aiAgent,
|
||||
ReplyText: "AI reply",
|
||||
ClientPrefix: "ai_reply",
|
||||
WorkflowRunID: 9988,
|
||||
Conversation: *conversation,
|
||||
Message: models.Message{ID: 101, RequestID: "trace-101"},
|
||||
AIAgent: *aiAgent,
|
||||
ReplyText: "AI reply",
|
||||
ClientPrefix: "ai_reply",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CommitAIReply() error = %v", err)
|
||||
@@ -33,16 +33,12 @@ func TestReplyCommitStoresWorkflowRunIDOnAIMessage(t *testing.T) {
|
||||
if replyMessage == nil {
|
||||
t.Fatalf("expected reply message")
|
||||
}
|
||||
if replyMessage.WorkflowRunID != 9988 {
|
||||
t.Fatalf("replyMessage.WorkflowRunID=%d want 9988", replyMessage.WorkflowRunID)
|
||||
}
|
||||
|
||||
var stored models.Message
|
||||
if err := db.First(&stored, replyMessage.ID).Error; err != nil {
|
||||
t.Fatalf("find reply message: %v", err)
|
||||
}
|
||||
if stored.WorkflowRunID != 9988 {
|
||||
t.Fatalf("stored.WorkflowRunID=%d want 9988", stored.WorkflowRunID)
|
||||
if stored.Content != "AI reply" || stored.RequestID != "trace-101" {
|
||||
t.Fatalf("unexpected stored reply: %#v", stored)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +62,28 @@ func TestReplyCommitRejectsSensitiveModelOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailureReplyDeduplicatesByDeterministicClientMessageID(t *testing.T) {
|
||||
db := setupReplyCommitTestDB(t)
|
||||
aiAgent := createReplyCommitTestAIAgent(t, db)
|
||||
conversation := createReplyCommitTestConversation(t, db, aiAgent.ID)
|
||||
message := models.Message{ID: 103, ConversationID: conversation.ID, RequestID: "trace-shared"}
|
||||
service := newAIReplyService()
|
||||
|
||||
service.commitFailureReplyIfNeeded(*conversation, message, *aiAgent, context.DeadlineExceeded)
|
||||
// The request ID is deliberately changed: error idempotency is tied to the
|
||||
// triggering customer message, not a transport trace that can be regenerated.
|
||||
message.RequestID = "trace-retry"
|
||||
service.commitFailureReplyIfNeeded(*conversation, message, *aiAgent, context.DeadlineExceeded)
|
||||
|
||||
var messages []models.Message
|
||||
if err := db.Where("conversation_id = ? AND client_msg_id = ?", conversation.ID, "ai_error_103").Find(&messages).Error; err != nil {
|
||||
t.Fatalf("find failure messages: %v", err)
|
||||
}
|
||||
if len(messages) != 1 {
|
||||
t.Fatalf("failure reply count = %d, want 1", len(messages))
|
||||
}
|
||||
}
|
||||
|
||||
func setupReplyCommitTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
dbName := "reply_commit_test_" + strings.NewReplacer("/", "_").Replace(t.Name())
|
||||
|
||||
Reference in New Issue
Block a user