169 lines
5.4 KiB
Go
169 lines
5.4 KiB
Go
package coordinator_test
|
||
|
||
import (
|
||
"context"
|
||
"strings"
|
||
"testing"
|
||
|
||
"chat-agent-system/internal/contracts/vocabulary"
|
||
"chat-agent-system/internal/coordinator"
|
||
"chat-agent-system/internal/intent"
|
||
)
|
||
|
||
type stubDocResolverDialogue struct {
|
||
doc *coordinator.ResolvedDoc
|
||
ok bool
|
||
candidates []string
|
||
err error
|
||
}
|
||
|
||
func (s *stubDocResolverDialogue) ResolveDoc(context.Context, int64, string, string) (*coordinator.ResolvedDoc, bool, []string, error) {
|
||
return s.doc, s.ok, s.candidates, s.err
|
||
}
|
||
|
||
func TestListDocResolver_ZeroAndMultiMatchClarify(t *testing.T) {
|
||
r := &coordinator.ListDocResolver{
|
||
List: func(context.Context, int64) ([]coordinator.DocListItem, error) {
|
||
return []coordinator.DocListItem{
|
||
{DocID: "d1", Title: "需求收集表", State: vocabulary.StateDrafting, TaskKey: "intake"},
|
||
{DocID: "d2", Title: "需求评估表", State: vocabulary.StateDrafting, TaskKey: "eval"},
|
||
}, nil
|
||
},
|
||
}
|
||
_, ok, cands, err := r.ResolveDoc(context.Background(), 1, "需求", "")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if ok {
|
||
t.Fatal("expected multi-match not ok")
|
||
}
|
||
if len(cands) < 2 {
|
||
t.Fatalf("expected candidates, got %v", cands)
|
||
}
|
||
|
||
_, ok, cands, err = r.ResolveDoc(context.Background(), 1, "不存在的文档XYZ", "")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if ok {
|
||
t.Fatal("expected zero unique match")
|
||
}
|
||
if len(cands) == 0 {
|
||
t.Fatal("expected inventory candidates on zero match")
|
||
}
|
||
|
||
doc, ok, _, err := r.ResolveDoc(context.Background(), 1, "需求收集表", "")
|
||
if err != nil || !ok || doc == nil || doc.DocID != "d1" {
|
||
t.Fatalf("unique title match failed: ok=%v doc=%+v err=%v", ok, doc, err)
|
||
}
|
||
|
||
doc, ok, _, err = r.ResolveDoc(context.Background(), 1, "@active", "d2")
|
||
if err != nil || !ok || doc.DocID != "d2" {
|
||
t.Fatalf("active resolve failed: %+v", doc)
|
||
}
|
||
}
|
||
|
||
func TestHandleDocGovSubmitReview_ConfirmationCard(t *testing.T) {
|
||
engine := coordinator.NewCoordinatorEngine(nil)
|
||
engine.SetDocResolver(&stubDocResolver{
|
||
doc: &coordinator.ResolvedDoc{DocID: "doc-001", Title: "需求收集表", State: vocabulary.StateDrafting, TaskKey: "intake"},
|
||
ok: true,
|
||
})
|
||
userCtx := &coordinator.UserProjectContext{
|
||
User: coordinator.UserInfo{ID: "1", Name: "张工", Role: "author", ProjectRole: "author"},
|
||
Project: coordinator.ProjectInfo{ID: "10", Name: "测试项目"},
|
||
}
|
||
res, err := engine.ProcessIntent(context.Background(), &intent.IntentRecognitionResult{
|
||
TopIntent: &intent.CandidateIntent{
|
||
Intent: intent.IntentDocGovSubmitReview,
|
||
FilledSlots: map[string]string{intent.SlotTargetDoc: "需求收集表"},
|
||
},
|
||
}, userCtx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if res.Status != coordinator.StatusAccepted {
|
||
t.Fatalf("status=%s", res.Status)
|
||
}
|
||
if res.InteractiveCard == nil || res.InteractiveCard.CardType != "CONFIRMATION" {
|
||
t.Fatalf("expected CONFIRMATION card, got %+v", res.InteractiveCard)
|
||
}
|
||
if res.PendingAction != nil {
|
||
t.Fatal("must not execute before confirm")
|
||
}
|
||
|
||
// confirm
|
||
res2, err := engine.ProcessIntent(context.Background(), &intent.IntentRecognitionResult{
|
||
TopIntent: &intent.CandidateIntent{
|
||
Intent: intent.IntentDocGovProposalDecide,
|
||
FilledSlots: map[string]string{
|
||
"target_action": "confirm_submit_review",
|
||
"target_doc": "doc-001",
|
||
},
|
||
},
|
||
}, userCtx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if res2.PendingAction == nil || res2.PendingAction.Kind != "docgov.submit_review" {
|
||
t.Fatalf("expected submit_review pending, got %+v", res2.PendingAction)
|
||
}
|
||
if res2.PendingAction.DocID != "doc-001" {
|
||
t.Fatalf("doc id=%s", res2.PendingAction.DocID)
|
||
}
|
||
}
|
||
|
||
func TestHandleDocGovCommentAdd_ClarifyOnMultiDoc(t *testing.T) {
|
||
engine := coordinator.NewCoordinatorEngine(nil)
|
||
engine.SetDocResolver(&stubDocResolver{
|
||
ok: false,
|
||
candidates: []string{"需求收集表(d1)", "需求评估表(d2)"},
|
||
})
|
||
userCtx := &coordinator.UserProjectContext{
|
||
User: coordinator.UserInfo{ID: "1", Role: "author", ProjectRole: "author"},
|
||
Project: coordinator.ProjectInfo{ID: "10"},
|
||
}
|
||
res, err := engine.ProcessIntent(context.Background(), &intent.IntentRecognitionResult{
|
||
TopIntent: &intent.CandidateIntent{
|
||
Intent: intent.IntentDocGovCommentAdd,
|
||
FilledSlots: map[string]string{
|
||
intent.SlotTargetDoc: "需求",
|
||
intent.SlotCommentContent: "缺风险分析",
|
||
},
|
||
},
|
||
}, userCtx)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if res.Status != coordinator.StatusClarificationRequired {
|
||
t.Fatalf("expected clarification, got %s", res.Status)
|
||
}
|
||
if !strings.Contains(res.NaturalReply, "需求收集表") {
|
||
t.Fatalf("reply should list candidates: %s", res.NaturalReply)
|
||
}
|
||
}
|
||
|
||
func TestHandleQueryPendingSupplements(t *testing.T) {
|
||
engine := coordinator.NewCoordinatorEngine(nil)
|
||
engine.SetPendingSupplementReader(&stubPendingReader{rows: []coordinator.PendingSupplementView{
|
||
{DocID: "d1", DocTitle: "需求表", SourceSummary: "预算改为200万", ImpactReason: "锁定中"},
|
||
}})
|
||
res, err := engine.ProcessIntent(context.Background(), &intent.IntentRecognitionResult{
|
||
TopIntent: &intent.CandidateIntent{Intent: intent.IntentQueryPendingSupplements},
|
||
}, &coordinator.UserProjectContext{Project: coordinator.ProjectInfo{ID: "1"}})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !strings.Contains(res.NaturalReply, "预算改为200万") {
|
||
t.Fatalf("reply=%s", res.NaturalReply)
|
||
}
|
||
}
|
||
|
||
type stubPendingReader struct {
|
||
rows []coordinator.PendingSupplementView
|
||
}
|
||
|
||
func (s *stubPendingReader) ListPendingSupplements(context.Context, int64, string) ([]coordinator.PendingSupplementView, error) {
|
||
return s.rows, nil
|
||
}
|