Files
weichenandCursor 110c2e810c feat(stageflow): own task/stage types so coordinator drops bizdev
Coordinator now takes a StageTaskRunner and injected context/KB ports.
Chatflow assembler reads maturity tokens instead of the orchestration root.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 11:13:17 +00:00

141 lines
4.2 KiB
Go

package coordinator
import (
"context"
"strings"
"testing"
"chat-agent-system/internal/intent"
)
func TestClassifyRejectUtterance(t *testing.T) {
cases := []struct {
in string
want RejectUtteranceIntent
}{
{"注册路径可行性评估报告被驳回的原因是什么", RejectUtteranceQuery},
{"为什么被退回了", RejectUtteranceQuery},
{"把调研报告审核驳回,意见为:缺少抗扰度", RejectUtteranceExecute},
{"请驳回这份文档", RejectUtteranceExecute},
{"驳回了吗", RejectUtteranceOther},
}
for _, c := range cases {
got := ClassifyRejectUtterance(c.in)
if got != c.want {
t.Errorf("%q: got %s want %s", c.in, got, c.want)
}
}
}
func TestResolveRejectCard_AuthorQuery(t *testing.T) {
res := ResolveRejectCard("author", "drafting", "doc-feas", "可行性报告", "",
"缺少 YY 9706.102 电磁兼容专项", RejectUtteranceQuery)
if res.Kind != "reject_reason_info" {
t.Fatalf("kind=%s", res.Kind)
}
if res.Card == nil || res.Card.CardType != "REMINDER_NOTICE" {
t.Fatalf("expected REMINDER_NOTICE card")
}
for _, a := range res.Card.Actions {
if a.ActionID == "confirm_reject" {
t.Fatal("author query card must not include confirm_reject")
}
}
}
func TestResolveRejectCard_ReviewerExecute(t *testing.T) {
res := ResolveRejectCard("reviewer", "reviewing", "doc-002", "调研", "第3节",
"缺少抗扰度", RejectUtteranceExecute)
if res.Kind != "reject_confirm_review" {
t.Fatalf("kind=%s", res.Kind)
}
if res.Card == nil || len(res.Card.Actions) < 2 {
t.Fatal("expected confirm card with actions")
}
if res.Card.Actions[0].Label != "确认驳回退回编制" {
t.Errorf("label=%s", res.Card.Actions[0].Label)
}
if res.Card.Actions[1].Label != "放弃驳回" {
t.Errorf("cancel label=%s", res.Card.Actions[1].Label)
}
}
func TestResolveRejectCard_AuthorExecuteDenied(t *testing.T) {
res := ResolveRejectCard("author", "reviewing", "doc-feas", "可行性报告", "",
"", RejectUtteranceExecute)
if res.Kind != "deny_text" {
t.Fatalf("kind=%s", res.Kind)
}
if res.Card != nil {
t.Fatal("expected no card")
}
if !strings.Contains(res.Reply, "不具备") {
t.Errorf("reply=%s", res.Reply)
}
}
func TestHandleDocGovReject_UnauthorizedAuthor(t *testing.T) {
engine := NewCoordinatorEngine(nil)
userCtx := &UserProjectContext{
User: UserInfo{ID: "u1", Name: "张工", Role: "author", ProjectRole: "author"},
Project: ProjectInfo{Name: "NeuroPulse"},
}
recRes := &intent.IntentRecognitionResult{
TopIntent: &intent.CandidateIntent{
Intent: intent.IntentDocGovReviewReject,
FilledSlots: map[string]string{
intent.SlotTargetDoc: "doc-feas",
intent.SlotRejectReason: "测试意见",
},
},
}
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
if err != nil {
t.Fatal(err)
}
if res.Status != StatusRejected {
t.Fatalf("status=%s", res.Status)
}
if res.InteractiveCard != nil {
t.Fatal("unauthorized must not mount reject confirm card")
}
if res.RejectReason != "UNAUTHORIZED_REJECT_ROLE" {
t.Errorf("RejectReason=%s", res.RejectReason)
}
}
func TestHandleQueryRejectReason_AuthorInfoCard(t *testing.T) {
engine := NewCoordinatorEngine(nil)
userCtx := &UserProjectContext{
User: UserInfo{ID: "u1", Name: "张工", Role: "author", ProjectRole: "author"},
Project: ProjectInfo{Name: "NeuroPulse"},
BlackboardFacts: map[string]string{
"last_reject_reason": "缺少 YY 9706.102 电磁兼容专项与听神经保护方案,退回修改",
},
}
recRes := &intent.IntentRecognitionResult{
TopIntent: &intent.CandidateIntent{
Intent: intent.IntentQueryRejectReason,
FilledSlots: map[string]string{intent.SlotTargetDoc: "可行性报告"},
},
}
res, err := engine.ProcessIntent(context.Background(), recRes, userCtx)
if err != nil {
t.Fatal(err)
}
if res.Status != StatusAccepted {
t.Fatalf("status=%s", res.Status)
}
if res.InteractiveCard == nil || res.InteractiveCard.CardType != "REMINDER_NOTICE" {
t.Fatalf("expected info card, got %+v", res.InteractiveCard)
}
for _, a := range res.InteractiveCard.Actions {
if a.ActionID == "confirm_reject" {
t.Fatal("must not expose confirm_reject")
}
}
if !strings.Contains(res.NaturalReply, "YY 9706.102") {
t.Errorf("reply=%s", res.NaturalReply)
}
}