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: "
卡板50506783
", }) 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: "卡板50506783
", }) { t.Fatal("card-only html message must open the deterministic service menu") } if isBusinessIdentityOnlyMessage(models.Message{ MessageType: enums.IMMessageTypeHTML, Content: "卡号 50506783,请查询流量
", }) { t.Fatal("card query must execute the requested service instead of opening the menu") } selection, ok := businessIdentityMenuSelection(models.Message{ MessageType: enums.IMMessageTypeHTML, Content: "1
", }) 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) } }