18c9354095
- 注入数据库、运行时配置、统一响应、文件存储和平台 AI 能力,补充业务读写工具与客户快捷操作契约。 - 移除模块内重复的组织、客户、工单、标签、技能、旧工作流、MCP 和迁移实现,将身份权限与业务主体交由宿主管理。 - 使用 libSQL 重构向量存储,并完善图片消息、访客身份、排队调度、企业微信和支持聊天页面。 - 统一 HTTP、DTO 与 WebSocket 的 snake_case 协议,补齐模块初始化、业务动作和公共载荷等回归测试。
174 lines
6.2 KiB
Go
174 lines
6.2 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/identity"
|
|
"code.tczkiot.com/wlw/ai-agent/internal/models"
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
|
|
svc "code.tczkiot.com/wlw/ai-agent/internal/services"
|
|
)
|
|
|
|
func TestResolveGuestBusinessIdentityFromCardNumber(t *testing.T) {
|
|
svc.SetQuerySubjects(func(_ context.Context, query identity.Query) ([]identity.Subject, error) {
|
|
if len(query.Types) == 1 && query.Types[0] == identity.SubjectCard && query.Keyword == "50506783" {
|
|
return []identity.Subject{{
|
|
Type: identity.SubjectCard,
|
|
Category: identity.CategoryUser,
|
|
ID: 17443,
|
|
Username: "50506783",
|
|
Name: "卡号 50506783",
|
|
Identifier: "898608691025D4186783",
|
|
Enabled: true,
|
|
}}, nil
|
|
}
|
|
return nil, nil
|
|
})
|
|
t.Cleanup(func() { svc.SetQuerySubjects(nil) })
|
|
|
|
resolution, err := resolveGuestBusinessIdentity(context.Background(), models.Conversation{
|
|
ID: 9,
|
|
CustomerType: string(enums.ExternalSourceGuest),
|
|
CustomerExternalID: "guest-1",
|
|
CustomerName: "访客",
|
|
CurrentAssigneeID: 0,
|
|
CustomerUnreadCount: 0,
|
|
AgentUnreadCount: 0,
|
|
}, models.Message{
|
|
ID: 20,
|
|
MessageType: enums.IMMessageTypeText,
|
|
Content: "卡号 50506783,帮我查流量",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolveGuestBusinessIdentity() error = %v", err)
|
|
}
|
|
if resolution.NeedsPrompt {
|
|
t.Fatal("resolved card identity must not request another identity prompt")
|
|
}
|
|
if resolution.Conversation.CustomerType != string(identity.SubjectCard) ||
|
|
resolution.Conversation.CustomerID != 17443 ||
|
|
resolution.Conversation.CustomerExternalID != "50506783" {
|
|
t.Fatalf("unexpected resolved conversation: %#v", resolution.Conversation)
|
|
}
|
|
}
|
|
|
|
func TestResolveGuestBusinessIdentityFromDeviceNumber(t *testing.T) {
|
|
svc.SetQuerySubjects(func(_ context.Context, query identity.Query) ([]identity.Subject, error) {
|
|
if len(query.Types) == 1 && query.Types[0] == identity.SubjectDevice && query.Keyword == "37012627000987" {
|
|
return []identity.Subject{{
|
|
Type: identity.SubjectDevice,
|
|
Category: identity.CategoryUser,
|
|
ID: 27,
|
|
Username: "37012627000987",
|
|
Name: "设备号 37012627000987",
|
|
Identifier: "37012627000987",
|
|
Enabled: true,
|
|
}}, nil
|
|
}
|
|
return nil, nil
|
|
})
|
|
t.Cleanup(func() { svc.SetQuerySubjects(nil) })
|
|
|
|
resolution, err := resolveGuestBusinessIdentity(context.Background(), models.Conversation{
|
|
ID: 10,
|
|
CustomerType: string(enums.ExternalSourceGuest),
|
|
CustomerExternalID: "guest-2",
|
|
}, models.Message{
|
|
ID: 21,
|
|
MessageType: enums.IMMessageTypeText,
|
|
Content: "设备号 37012627000987",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("resolveGuestBusinessIdentity() error = %v", err)
|
|
}
|
|
if resolution.NeedsPrompt || resolution.Conversation.CustomerType != string(identity.SubjectDevice) || resolution.Conversation.CustomerID != 27 {
|
|
t.Fatalf("unexpected resolved conversation: %#v", resolution.Conversation)
|
|
}
|
|
}
|
|
|
|
func TestBusinessIdentityCandidatesRequireExplicitIdentifier(t *testing.T) {
|
|
candidates, explicit := businessIdentityCandidates("请帮我查流量")
|
|
if len(candidates) != 0 || explicit {
|
|
t.Fatalf("unexpected candidates=%v explicit=%v", candidates, explicit)
|
|
}
|
|
|
|
candidates, explicit = businessIdentityCandidates("37012627000987")
|
|
if len(candidates) != 1 || candidates[0] != "37012627000987" || !explicit {
|
|
t.Fatalf("unexpected candidates=%v explicit=%v", candidates, explicit)
|
|
}
|
|
}
|
|
|
|
func TestBusinessIdentityMessageContentSupportsHTMLInput(t *testing.T) {
|
|
content := businessIdentityMessageContent(models.Message{
|
|
MessageType: enums.IMMessageTypeHTML,
|
|
Content: "<p>卡板50506783</p>",
|
|
})
|
|
if content != "卡板50506783" {
|
|
t.Fatalf("unexpected html message content: %q", content)
|
|
}
|
|
candidates, explicit := businessIdentityCandidates(content)
|
|
if len(candidates) != 1 || candidates[0] != "50506783" || !explicit {
|
|
t.Fatalf("html card identifier was not extracted: candidates=%#v explicit=%v", candidates, explicit)
|
|
}
|
|
}
|
|
|
|
func TestBusinessIdentityHTMLMenuInput(t *testing.T) {
|
|
if !isBusinessIdentityOnlyMessage(models.Message{
|
|
MessageType: enums.IMMessageTypeHTML,
|
|
Content: "<p>卡板50506783</p>",
|
|
}) {
|
|
t.Fatal("card-only html message must open the deterministic service menu")
|
|
}
|
|
if isBusinessIdentityOnlyMessage(models.Message{
|
|
MessageType: enums.IMMessageTypeHTML,
|
|
Content: "<p>卡号 50506783,请查询流量</p>",
|
|
}) {
|
|
t.Fatal("card query must execute the requested service instead of opening the menu")
|
|
}
|
|
selection, ok := businessIdentityMenuSelection(models.Message{
|
|
MessageType: enums.IMMessageTypeHTML,
|
|
Content: "<p>1</p>",
|
|
})
|
|
if !ok || selection != 1 {
|
|
t.Fatalf("unexpected html menu selection: selection=%d ok=%v", selection, ok)
|
|
}
|
|
}
|
|
|
|
func TestLegacyCardMenuActionCode(t *testing.T) {
|
|
statusMenu := &models.Message{MessageType: enums.IMMessageTypeText, Content: `已确认您查询的是卡板 50506783 的卡片状态。
|
|
1. 卡片提示停机/被暂停使用
|
|
2. 无法上网(有信号但连不上)
|
|
3. 无信号/无服务
|
|
4. 已充值但未恢复`}
|
|
for selection := 1; selection <= 4; selection++ {
|
|
code, ok := legacyCardMenuActionCode(statusMenu, selection)
|
|
if !ok || code != "card/network_diagnosis" {
|
|
t.Fatalf("status menu selection %d returned code=%q ok=%v", selection, code, ok)
|
|
}
|
|
}
|
|
|
|
helpMenu := &models.Message{MessageType: enums.IMMessageTypeText, Content: `请问您遇到的是哪种情况?
|
|
1. 卡片状态异常 / 停机
|
|
2. 无法上网 / 网络连接问题
|
|
3. 套餐或流量相关
|
|
4. 其他问题`}
|
|
wants := map[int]string{1: "card/status", 2: "card/network_diagnosis", 3: "card/package"}
|
|
for selection, want := range wants {
|
|
code, ok := legacyCardMenuActionCode(helpMenu, selection)
|
|
if !ok || code != want {
|
|
t.Fatalf("help menu selection %d returned code=%q ok=%v, want %q", selection, code, ok, want)
|
|
}
|
|
}
|
|
if _, ok := legacyCardMenuActionCode(helpMenu, 4); ok {
|
|
t.Fatal("free-form other problem must remain available to the AI")
|
|
}
|
|
}
|
|
|
|
func TestGuestBusinessIdentityPromptForInvalidIdentifier(t *testing.T) {
|
|
got := guestBusinessIdentityPrompt(guestBusinessIdentityResolution{CandidateProvided: true}, nil)
|
|
if got != invalidBusinessIdentityReply {
|
|
t.Fatalf("unexpected prompt: %q", got)
|
|
}
|
|
}
|