Files
Agent-System/internal/chat/multi_turn_e2e_test.go
T
weichenandCursor c7aba86376 fix(test): stabilize PG seed users and CI short test suite.
Reactivate dev seed users after auth PG tests, use unique session tokens, fix import guard and coordinator assertions, and skip flaky E2E in short mode; add pg_restore CI drill job.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:23 +00:00

138 lines
5.6 KiB
Go

package chat_test
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/intent"
)
// TestRealE2EWorkspaceChatSimulation 基于真实 Agent 生成的业务工作区开展端到端参谋对话与三分支消解实机验证
func TestRealE2EWorkspaceChatSimulation(t *testing.T) {
if testing.Short() {
t.Skip("skipping workspace-bound E2E in short mode")
}
wsDir := "data/dev/workspaces/bizdev/intake_1788792827436879000"
if _, err := os.Stat(wsDir); os.IsNotExist(err) {
wsDir = filepath.Join("..", "..", wsDir)
}
if _, err := os.Stat(wsDir); os.IsNotExist(err) {
t.Skipf("workspace not found: %s", wsDir)
}
u := coordinator.UserInfo{
ID: "u_test_user",
Name: "张工",
Role: "研发负责人",
ProjectRole: "composer",
}
assembler := coordinator.NewContextAssembler()
userCtx, err := assembler.AssembleFromWorkspace(wsDir, u)
if err != nil {
t.Fatalf("failed to assemble context from real workspace: %v", err)
}
if userCtx.Project.Name != "研发流程辅助智能体系统" {
t.Errorf("expected project name '研发流程辅助智能体系统', got %s", userCtx.Project.Name)
}
if userCtx.Project.CustomerName != "北京水木东方科技有限公司" {
t.Errorf("expected customer name '北京水木东方科技有限公司', got %s", userCtx.Project.CustomerName)
}
rec := intent.NewRecognizer()
engine := coordinator.NewCoordinatorEngine(nil) // 参谋交互层验证协调决策与上下文更新
session := chat.NewSession("real_e2e_session", userCtx, rec, engine)
// Turn 1: 真实工作区状态与背景查询 (分支 2: 新意图抢占 QUERY_PROGRESS)
t.Log("=== Turn 1: 询问真实工作区当前状态与风险 ===")
resp1, err := session.SendMessage(context.Background(), "帮我看看当前项目的整体进展如何,材料到底够不够?")
if err != nil {
t.Fatalf("Turn 1 failed: %v", err)
}
if resp1.Branch != intent.BranchNewIntentPreempt {
t.Errorf("Turn 1 expected NEW_INTENT_PREEMPT, got %s", resp1.Branch)
}
if resp1.IntentResult.TopIntent.Intent != intent.IntentQueryProgress {
t.Errorf("Turn 1 expected QUERY_PROGRESS, got %s", resp1.IntentResult.TopIntent.Intent)
}
if !strings.Contains(resp1.ReplyText, "研发流程辅助智能体系统") {
t.Errorf("Turn 1 reply should mention project name, got: %s", resp1.ReplyText)
}
t.Logf("Turn 1 响应:\n%s\n", resp1.ReplyText)
// Turn 2: 补充事实黑板 (分支 2: 补充信息 SUPPLEMENT_INFO)
t.Log("=== Turn 2: 补充事实设定至真实项目黑板 ===")
resp2, err := session.SendMessage(context.Background(), "客户确定目标市场不仅是国内,还要扩展到欧洲CE")
if err != nil {
t.Fatalf("Turn 2 failed: %v", err)
}
if resp2.Branch != intent.BranchNewIntentPreempt {
t.Errorf("Turn 2 expected NEW_INTENT_PREEMPT, got %s", resp2.Branch)
}
if resp2.IntentResult.TopIntent.Intent != intent.IntentSupplementInfo {
t.Errorf("Turn 2 expected SUPPLEMENT_INFO, got %s", resp2.IntentResult.TopIntent.Intent)
}
if !strings.Contains(session.UserCtx.BlackboardFacts["target_market"], "欧洲CE") {
t.Errorf("target_market not properly updated in blackboard: %v", session.UserCtx.BlackboardFacts)
}
t.Logf("Turn 2 响应:\n%s\n", resp2.ReplyText)
// Turn 3: 模糊分忧/咨询 (分支 3: 模糊降级 AMBIGUOUS_FALLBACK)
t.Log("=== Turn 3: 用户犹豫不决,参谋给出客观建议与选项 ===")
resp3, err := session.SendMessage(context.Background(), "这个先放放,你觉得呢")
if err != nil {
t.Fatalf("Turn 3 failed: %v", err)
}
if resp3.Branch != intent.BranchAmbiguousFallback {
t.Errorf("Turn 3 expected AMBIGUOUS_FALLBACK, got %s", resp3.Branch)
}
if !strings.Contains(resp3.ReplyText, "完全理解") && !strings.Contains(resp3.ReplyText, "建议") {
t.Errorf("Turn 3 expected advisor consultation style, got: %s", resp3.ReplyText)
}
t.Logf("Turn 3 响应:\n%s\n", resp3.ReplyText)
// Turn 4: 极速肯定兑现推进 (分支 1: 挂起预期命中 EXPECTATION_HIT)
t.Log("=== Turn 4: 用户极速回复“好”,直接兑现挂起预期推进至 V0.5 ===")
resp4, err := session.SendMessage(context.Background(), "好")
if err != nil {
t.Fatalf("Turn 4 failed: %v", err)
}
if resp4.Branch != intent.BranchExpectationHit {
t.Errorf("Turn 4 expected EXPECTATION_HIT, got %s", resp4.Branch)
}
if resp4.IntentResult.TopIntent.Intent != intent.IntentAdvanceStage {
t.Errorf("Turn 4 expected ADVANCE_STAGE, got %s", resp4.IntentResult.TopIntent.Intent)
}
if session.UserCtx.Project.CurrentStage != bizdev.StageV05_Feasibility {
t.Errorf("expected project advanced to V0.5, got %s", session.UserCtx.Project.CurrentStage)
}
t.Logf("Turn 4 响应:\n%s\n", resp4.ReplyText)
// Turn 5: 否定/暂缓挂起预期 (分支 1: 显式拒绝 DECLINE_EXPECTATION)
t.Log("=== Turn 5: 用户对新阶段的下一步建议表示暂缓 ===")
resp5, err := session.SendMessage(context.Background(), "先别推,等等")
if err != nil {
t.Fatalf("Turn 5 failed: %v", err)
}
if resp5.Branch != intent.BranchExpectationHit {
t.Errorf("Turn 5 expected EXPECTATION_HIT, got %s", resp5.Branch)
}
if resp5.IntentResult.TopIntent.Intent != intent.IntentDeclineExpectation {
t.Errorf("Turn 5 expected DECLINE_EXPECTATION, got %s", resp5.IntentResult.TopIntent.Intent)
}
if !strings.Contains(resp5.ReplyText, "听您的") {
t.Errorf("Turn 5 expected polite reply, got: %s", resp5.ReplyText)
}
if session.UserCtx.PendingExpectation != nil {
t.Errorf("expected PendingExpectation to be cleared after decline")
}
t.Logf("Turn 5 响应:\n%s\n", resp5.ReplyText)
}