483 lines
14 KiB
Go
483 lines
14 KiB
Go
package coordinator_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/bizdev"
|
|
"chat-agent-system/internal/coordinator"
|
|
"chat-agent-system/internal/intent"
|
|
"chat-agent-system/internal/squad"
|
|
"strings"
|
|
)
|
|
|
|
func TestCoordinatorEngine_QueryProgress(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
|
|
userCtx := &coordinator.UserProjectContext{
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "便携式掌上超声项目",
|
|
},
|
|
LatestAdvisorBrief: &squad.AdvisorBriefing{
|
|
MaturityScore: 70,
|
|
MaturityLevel: "V0.1",
|
|
MaturityHeadline: "已具备基本产品定义,亟待补充探头频率与目标市场",
|
|
PreemptiveRisks: []string{"欧盟CE MDR认证周期较长"},
|
|
NextBestActions: []string{"推进进入V0.5可行性论证"},
|
|
CommunicationKit: "【给客户的建议沟通要点】...",
|
|
},
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentQueryProgress,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotQueryTopic: "risk",
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected status ACCEPTED, got %s", res.Status)
|
|
}
|
|
if res.AdvisorBrief == nil {
|
|
t.Errorf("expected advisor brief in result")
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_SupplementInfo(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
|
|
userCtx := &coordinator.UserProjectContext{
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "便携式掌上超声项目",
|
|
},
|
|
BlackboardFacts: make(map[string]string),
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentSupplementInfo,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotFactKey: "target_market",
|
|
intent.SlotFactValue: "国内及欧盟CE",
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected status ACCEPTED, got %s", res.Status)
|
|
}
|
|
if userCtx.BlackboardFacts["target_market"] != "国内及欧盟CE" {
|
|
t.Errorf("expected blackboard target_market updated, got %s", userCtx.BlackboardFacts["target_market"])
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_AdvanceStage(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
userCtx := &coordinator.UserProjectContext{
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "超声测试项目",
|
|
CurrentStage: bizdev.StageV01_Triage,
|
|
},
|
|
User: coordinator.UserInfo{
|
|
ProjectRole: "composer",
|
|
},
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentAdvanceStage,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetStage: string(bizdev.StageV05_Feasibility),
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected status ACCEPTED, got %s", res.Status)
|
|
}
|
|
if userCtx.Project.CurrentStage != bizdev.StageV05_Feasibility {
|
|
t.Errorf("expected stage V0.5_feasibility, got %s", userCtx.Project.CurrentStage)
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_DeclineExpectation(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
userCtx := &coordinator.UserProjectContext{
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "超声测试项目",
|
|
CurrentStage: bizdev.StageV01_Triage,
|
|
},
|
|
PendingExpectation: coordinator.BuildNextExpectation(bizdev.StageV01_Triage, "超声测试项目"),
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
Branch: intent.BranchExpectationHit,
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentDeclineExpectation,
|
|
Branch: intent.BranchExpectationHit,
|
|
IsDecline: true,
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected status ACCEPTED, got %s", res.Status)
|
|
}
|
|
if !res.ClearExpectation {
|
|
t.Errorf("expected ClearExpectation to be true")
|
|
}
|
|
if userCtx.PendingExpectation != nil {
|
|
t.Errorf("expected PendingExpectation to be cleared in userCtx")
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_AmbiguousConsult(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
userCtx := &coordinator.UserProjectContext{
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "超声测试项目",
|
|
CurrentStage: bizdev.StageV01_Triage,
|
|
},
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
Branch: intent.BranchAmbiguousFallback,
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentCasualChat,
|
|
Branch: intent.BranchAmbiguousFallback,
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Branch != intent.BranchAmbiguousFallback {
|
|
t.Errorf("expected branch AMBIGUOUS_FALLBACK, got %s", res.Branch)
|
|
}
|
|
if res.NextExpectation == nil {
|
|
t.Errorf("expected NextExpectation to be populated")
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_AdvanceStageWithPatch(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
userCtx := &coordinator.UserProjectContext{
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "超声测试项目",
|
|
CurrentStage: bizdev.StageV01_Triage,
|
|
},
|
|
BlackboardFacts: make(map[string]string),
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
Branch: intent.BranchExpectationHit,
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentAdvanceStage,
|
|
Branch: intent.BranchExpectationHit,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetStage: string(bizdev.StageV05_Feasibility),
|
|
"target_market": "欧洲CE",
|
|
},
|
|
PatchSlots: map[string]string{
|
|
"target_market": "欧洲CE",
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected status ACCEPTED, got %s", res.Status)
|
|
}
|
|
if userCtx.BlackboardFacts["target_market"] != "欧洲CE" {
|
|
t.Errorf("expected target_market '欧洲CE' in blackboard facts, got '%s'", userCtx.BlackboardFacts["target_market"])
|
|
}
|
|
if userCtx.Project.TargetMarket != "欧洲CE" {
|
|
t.Errorf("expected project TargetMarket '欧洲CE', got '%s'", userCtx.Project.TargetMarket)
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_ReviewRejectWithCard(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
|
|
userCtx := &coordinator.UserProjectContext{
|
|
User: coordinator.UserInfo{
|
|
ID: "user_102",
|
|
Name: "李主管",
|
|
Role: "reviewer",
|
|
},
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "微创内窥镜项目",
|
|
},
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentDocGovReviewReject,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetDoc: "doc-002",
|
|
intent.SlotRejectReason: "第3节缺少史赛克高频电刀的抗扰度指标",
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.InteractiveCard == nil {
|
|
t.Fatalf("expected interactive card payload in coordination result")
|
|
}
|
|
if res.InteractiveCard.CardType != "CONFIRMATION" {
|
|
t.Errorf("expected card type CONFIRMATION, got %s", res.InteractiveCard.CardType)
|
|
}
|
|
if len(res.InteractiveCard.Actions) < 2 {
|
|
t.Errorf("expected at least 2 actions (confirm and cancel)")
|
|
}
|
|
if !strings.Contains(res.NaturalReply, "驳回") {
|
|
t.Errorf("expected natural reply to mention 驳回, got %s", res.NaturalReply)
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_SecurityForbidden(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
|
|
userCtx := &coordinator.UserProjectContext{
|
|
User: coordinator.UserInfo{
|
|
ID: "user_101",
|
|
Name: "张工",
|
|
Role: "engineer",
|
|
ProjectRole: "composer",
|
|
},
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "医用超声设备项目",
|
|
},
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentSecurityForbidden,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotForbiddenTarget: "identity_or_settings",
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusRejected {
|
|
t.Errorf("expected status REJECTED, got %s", res.Status)
|
|
}
|
|
if !strings.Contains(res.NaturalReply, "合规权限拦截") || !strings.Contains(res.NaturalReply, "ISO 13485") {
|
|
t.Errorf("expected ISO 13485 compliance warning, got %s", res.NaturalReply)
|
|
}
|
|
// Verify user context was not modified
|
|
if userCtx.User.Name != "张工" || userCtx.User.Role != "engineer" {
|
|
t.Errorf("expected user context to remain unmodified")
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_QueryUserRights(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
|
|
userCtx := &coordinator.UserProjectContext{
|
|
User: coordinator.UserInfo{
|
|
ID: "user_102",
|
|
Name: "李主管",
|
|
Role: "reviewer",
|
|
ProjectRole: "reviewer",
|
|
},
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "微创内窥镜项目",
|
|
},
|
|
UserDocs: coordinator.UserDocsInfo{
|
|
Reviewed: []string{"技术与市场调研报告", "法规与注册论证报告"},
|
|
Claimed: []string{"技术与市场调研报告"},
|
|
},
|
|
}
|
|
|
|
recRes := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentQueryUserRights,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotQueryTopic: "user_rights",
|
|
},
|
|
},
|
|
}
|
|
|
|
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process intent failed: %v", err)
|
|
}
|
|
|
|
if res.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected status ACCEPTED, got %s", res.Status)
|
|
}
|
|
if !strings.Contains(res.NaturalReply, "李主管") || !strings.Contains(res.NaturalReply, "审核") {
|
|
t.Errorf("expected reply to mention user name and review rights, got %s", res.NaturalReply)
|
|
}
|
|
}
|
|
|
|
func TestCoordinatorEngine_DocGovActions(t *testing.T) {
|
|
engine := coordinator.NewCoordinatorEngine(nil)
|
|
|
|
userCtx := &coordinator.UserProjectContext{
|
|
User: coordinator.UserInfo{
|
|
ID: "user_101",
|
|
Name: "张工",
|
|
Role: "engineer",
|
|
ProjectRole: "composer",
|
|
},
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "智能内窥镜项目",
|
|
},
|
|
UserDocs: coordinator.UserDocsInfo{
|
|
Composed: []string{"doc-001"},
|
|
},
|
|
}
|
|
|
|
// 1. DocGovClaim
|
|
recClaim := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentDocGovClaim,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetDoc: "doc-001",
|
|
},
|
|
},
|
|
}
|
|
resClaim, err := engine.ProcessIntent(context.Background(), recClaim, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process claim failed: %v", err)
|
|
}
|
|
if resClaim.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected claim accepted, got %s", resClaim.Status)
|
|
}
|
|
|
|
// 2. DocGovSubmitReview
|
|
recSubmit := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentDocGovSubmitReview,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetDoc: "doc-001",
|
|
},
|
|
},
|
|
}
|
|
resSubmit, err := engine.ProcessIntent(context.Background(), recSubmit, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process submit failed: %v", err)
|
|
}
|
|
if resSubmit.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected submit accepted, got %s", resSubmit.Status)
|
|
}
|
|
|
|
// 3. DocGovApprove
|
|
reviewerCtx := &coordinator.UserProjectContext{
|
|
User: coordinator.UserInfo{
|
|
ID: "user_102",
|
|
Name: "李主管",
|
|
Role: "reviewer",
|
|
ProjectRole: "reviewer",
|
|
},
|
|
Project: coordinator.ProjectInfo{
|
|
Name: "智能内窥镜项目",
|
|
},
|
|
}
|
|
recApprove := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentDocGovReviewApprove,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetDoc: "doc-001",
|
|
},
|
|
},
|
|
}
|
|
resApprove, err := engine.ProcessIntent(context.Background(), recApprove, reviewerCtx)
|
|
if err != nil {
|
|
t.Fatalf("process approve failed: %v", err)
|
|
}
|
|
if resApprove.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected approve accepted, got %s", resApprove.Status)
|
|
}
|
|
|
|
// 4. DocGovProposalDecide
|
|
recProposal := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentDocGovProposalDecide,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotProposalAction: "accept",
|
|
},
|
|
},
|
|
}
|
|
resProposal, err := engine.ProcessIntent(context.Background(), recProposal, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process proposal decide failed: %v", err)
|
|
}
|
|
if resProposal.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected proposal decide accepted, got %s", resProposal.Status)
|
|
}
|
|
|
|
// 5. QueryRegulations
|
|
recReg := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentQueryRegulations,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotQueryTopic: "regulations",
|
|
},
|
|
},
|
|
}
|
|
resReg, err := engine.ProcessIntent(context.Background(), recReg, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process query regulations failed: %v", err)
|
|
}
|
|
if resReg.Status != coordinator.StatusAccepted || !strings.Contains(resReg.NaturalReply, "GB 9706") {
|
|
t.Errorf("expected GB 9706 in regulations reply, got %s", resReg.NaturalReply)
|
|
}
|
|
|
|
// 6. QuerySimilarCases
|
|
recCase := &intent.IntentRecognitionResult{
|
|
TopIntent: &intent.CandidateIntent{
|
|
Intent: intent.IntentQuerySimilarCases,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotQueryTopic: "similar_cases",
|
|
},
|
|
},
|
|
}
|
|
resCase, err := engine.ProcessIntent(context.Background(), recCase, userCtx)
|
|
if err != nil {
|
|
t.Fatalf("process query similar cases failed: %v", err)
|
|
}
|
|
if resCase.Status != coordinator.StatusAccepted {
|
|
t.Errorf("expected similar cases accepted, got %s", resCase.Status)
|
|
}
|
|
}
|
|
|