Files
Agent-System/internal/server/chat_document_reader_test.go
T
WeiCHEN a3b5d4291b fix(chat): resolve latest-upload summary via CAS assets
Recognize “刚刚上传” as @latest_upload and summarize the project’s newest CAS attachment (with clear parse pending/failed/no-asset replies) instead of searching only controlled workflow docs.
2026-09-23 09:42:28 +08:00

185 lines
6.3 KiB
Go

package server
import (
"context"
"errors"
"strings"
"testing"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/config"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/storage"
"chat-agent-system/internal/workflowscope"
)
func TestSelectReadableWorkflowDocumentMatchesInitialRequirementsAlias(t *testing.T) {
docs := []docgov.WorkflowDocumentSummary{
{DocID: "doc_proj143_intake", TaskKey: "intake", Title: "用户初始需求收集表"},
{DocID: "doc_proj143_research", TaskKey: "research", Title: "技术与市场调研报告"},
}
doc, err := selectReadableWorkflowDocument(docs, "用户初始需求表", "", nil)
if err != nil {
t.Fatalf("select: %v", err)
}
if doc.DocID != "doc_proj143_intake" {
t.Fatalf("doc=%s", doc.DocID)
}
}
func TestSelectReadableWorkflowDocumentDoesNotGuessWhenTargetMissing(t *testing.T) {
docs := []docgov.WorkflowDocumentSummary{
{DocID: "doc_a", TaskKey: "intake", Title: "用户初始需求收集表"},
{DocID: "doc_b", TaskKey: "research", Title: "调研报告"},
}
if _, err := selectReadableWorkflowDocument(docs, "", "", nil); err == nil {
t.Fatal("expected target-required error")
}
}
func TestSelectReadableWorkflowDocumentScopesToActiveWorkflow(t *testing.T) {
docs := []docgov.WorkflowDocumentSummary{
{DocID: "doc_proj1_research", TaskKey: "bd_research", Title: "调研报告"},
{DocID: "doc_proj1_proposal", TaskKey: "rd_proposal", Title: "项目建议书"},
}
scope := map[string]bool{"bd_research": true, "research": true}
doc, err := selectReadableWorkflowDocument(docs, "调研报告", "main", scope)
if err != nil {
t.Fatalf("in-scope select: %v", err)
}
if doc.TaskKey != "bd_research" {
t.Fatalf("want bd_research, got %s", doc.TaskKey)
}
_, err = selectReadableWorkflowDocument(docs, "项目建议书", "main", scope)
if err == nil {
t.Fatal("expected wrong-workflow error")
}
var wrong *coordinator.ErrWrongWorkflow
if !errors.As(err, &wrong) {
t.Fatalf("want ErrWrongWorkflow, got %T %v", err, err)
}
if !strings.Contains(wrong.Message, "切换") {
t.Fatalf("guidance should mention 切换, got %q", wrong.Message)
}
}
func TestSelectReadableWorkflowDocumentNoScopeKeepsLegacy(t *testing.T) {
docs := []docgov.WorkflowDocumentSummary{
{DocID: "doc_proj1_proposal", TaskKey: "rd_proposal", Title: "项目建议书"},
}
doc, err := selectReadableWorkflowDocument(docs, "项目建议书", "", nil)
if err != nil {
t.Fatalf("legacy passthrough: %v", err)
}
if doc.TaskKey != "rd_proposal" {
t.Fatalf("want rd_proposal, got %s", doc.TaskKey)
}
}
func TestSelectReadableWeightedAllowsExactCrossRead(t *testing.T) {
workflowscope.SetModeForTest(workflowscope.ModeWeighted)
t.Cleanup(workflowscope.ResetModeForTest)
docs := []docgov.WorkflowDocumentSummary{
{DocID: "doc_proj1_research", TaskKey: "bd_research", Title: "调研报告"},
{DocID: "doc_proj1_proposal", TaskKey: "rd_proposal", Title: "项目建议书"},
}
scope := map[string]bool{"bd_research": true}
doc, err := selectReadableWorkflowDocument(docs, "项目建议书", "main", scope)
if err != nil {
t.Fatalf("weighted cross read: %v", err)
}
if doc.TaskKey != "rd_proposal" {
t.Fatalf("want rd_proposal, got %s", doc.TaskKey)
}
inScope, err := selectReadableWorkflowDocument(docs, "调研报告", "main", scope)
if err != nil || inScope.TaskKey != "bd_research" {
t.Fatalf("in-scope still wins: %v %v", inScope, err)
}
}
func TestCrossWorkflowTaskVisible(t *testing.T) {
open := &projectinstance.StageAccess{
Restricted: true,
ViewableTaskKeys: map[string]bool{"bd_research": true},
}
scope := map[string]bool{"bd_research": true, "bd_hidden": true}
if !crossWorkflowTaskVisible(open, scope, "rd_proposal") {
t.Fatal("foreign key should be readable when the caller can view the active instance")
}
if crossWorkflowTaskVisible(open, scope, "bd_hidden") {
t.Fatal("in-scope but not viewable must be denied")
}
closed := &projectinstance.StageAccess{Restricted: true, ViewableTaskKeys: map[string]bool{}}
if crossWorkflowTaskVisible(closed, scope, "rd_proposal") {
t.Fatal("closed instance must deny cross read")
}
if !crossWorkflowTaskVisible(nil, scope, "rd_proposal") {
t.Fatal("unrestricted access must allow")
}
}
func TestPickLatestFileReference(t *testing.T) {
older := &storage.FileReference{ID: 1, FileName: "a.txt", CreatedAt: time.Unix(100, 0)}
newer := &storage.FileReference{ID: 2, FileName: "b.txt", CreatedAt: time.Unix(200, 0)}
got := pickLatestFileReference([]*storage.FileReference{older, newer, nil})
if got == nil || got.ID != 2 {
t.Fatalf("want id=2, got %+v", got)
}
sameTimeHigherID := &storage.FileReference{ID: 9, FileName: "c.txt", CreatedAt: time.Unix(200, 0)}
got = pickLatestFileReference([]*storage.FileReference{newer, sameTimeHigherID})
if got == nil || got.ID != 9 {
t.Fatalf("same created_at should prefer higher id, got %+v", got)
}
}
func TestResolveLatestUploadDocument_TextAsset(t *testing.T) {
cfg := &config.Config{Env: "test", DataDir: t.TempDir()}
svc := bizdev.MustNewService(t, cfg)
srv := NewServer(cfg, svc)
if srv.cas == nil {
t.Fatal("expected CAS")
}
projectID := int64(42)
userID := int64(7)
_, err := srv.cas.StoreFile(context.Background(), projectID, userID, "shockwave.txt", "material",
[]byte("冲击波治疗仪核心适应症:软组织损伤与疼痛管理。\n关键约束:治疗强度需医师确认。"), "text/plain")
if err != nil {
t.Fatalf("store: %v", err)
}
doc, err := srv.resolveLatestUploadDocument(context.Background(), projectID)
if err != nil {
t.Fatalf("resolve: %v", err)
}
if doc == nil || !strings.Contains(doc.Markdown, "冲击波治疗仪") {
t.Fatalf("unexpected doc: %+v", doc)
}
if doc.Title != "shockwave" {
t.Fatalf("title=%q", doc.Title)
}
if doc.LifecycleState != "uploaded" {
t.Fatalf("lifecycle=%q", doc.LifecycleState)
}
}
func TestResolveLatestUploadDocument_NoAsset(t *testing.T) {
cfg := &config.Config{Env: "test", DataDir: t.TempDir()}
svc := bizdev.MustNewService(t, cfg)
srv := NewServer(cfg, svc)
_, err := srv.resolveLatestUploadDocument(context.Background(), 99)
if err == nil {
t.Fatal("expected ErrNoUploadAsset")
}
var noAsset *coordinator.ErrNoUploadAsset
if !errors.As(err, &noAsset) {
t.Fatalf("want ErrNoUploadAsset, got %T %v", err, err)
}
}