Files
Agent-System/internal/bizdev/doc_id_test.go
T
weichenandCursor 3f42149457 feat(bizdev): snapshot collaborators onto Runtime and Server ports
Composition roots copy Parts once so HTTP/CLI handlers stop calling locator getters on every request.

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

299 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package bizdev
import (
"context"
"errors"
"strings"
"testing"
"time"
"chat-agent-system/internal/config"
"chat-agent-system/internal/docgov"
)
func TestNormalizeDocID_LegacyPatterns(t *testing.T) {
tests := []struct {
in string
project int64
taskKey string
expected string
}{
{"doc-p56-research", 56, "research", "doc_proj56_research"},
{"doc-proj56-research", 56, "research", "doc_proj56_research"},
{"doc_proj56_research", 56, "research", "doc_proj56_research"},
{"", 56, "research", "doc_proj56_research"},
{"", 0, "research", ""},
}
for _, tc := range tests {
got := normalizeDocID(tc.in, tc.project, tc.taskKey)
if got != tc.expected {
t.Fatalf("normalizeDocID(%q) = %q, want %q", tc.in, got, tc.expected)
}
}
}
func TestResolveAndValidateDocID_LegacyFallbackWhenNotRegistered(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
ctx := context.Background()
got, err := svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
DocID: "doc-p56-research",
ProjectID: 56,
TaskKey: "research",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "doc_proj56_research" {
t.Fatalf("got %q, want doc_proj56_research", got)
}
}
func TestResolveAndValidateDocID_ExistingDocMismatchRejected(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
ctx := context.Background()
docID := "doc_proj56_research"
_, err := svc.docGov.CreateOrGetDocument(
ctx, docID, 56, "research", "调研报告", "V0.5", "",
1, "张工", 2, "李主管", 3, "王总监",
)
if err != nil {
t.Fatalf("CreateOrGetDocument: %v", err)
}
_, err = svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
DocID: docID,
ProjectID: 99,
TaskKey: "research",
})
if err == nil || !strings.Contains(err.Error(), "does not belong to project") {
t.Fatalf("expected project mismatch error, got %v", err)
}
_, err = svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
DocID: docID,
ProjectID: 56,
TaskKey: "intake",
})
if err == nil || !strings.Contains(err.Error(), "task_key mismatch") {
t.Fatalf("expected task_key mismatch error, got %v", err)
}
}
// 客户端不得用 doc_id 改写 task_key:UI-05 误路由若仍带 intake 文档 + feasibility_reg,必须拒绝。
func TestResolveAndValidateDocID_ClientDocIDCannotOverrideTaskKey(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
ctx := context.Background()
docID := "doc_proj6109_intake"
_, err := svc.docGov.CreateOrGetDocument(
ctx, docID, 6109, "intake", "用户初始需求收集表", "V0.1", "",
1, "张工", 2, "李主管", 3, "王总监",
)
if err != nil {
t.Fatalf("CreateOrGetDocument: %v", err)
}
_, err = svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
DocID: docID,
ProjectID: 6109,
TaskKey: "feasibility_reg",
})
if err == nil || !strings.Contains(err.Error(), "task_key mismatch") {
t.Fatalf("expected task_key mismatch, got %v", err)
}
got, err := svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
DocID: docID,
ProjectID: 6109,
TaskKey: "intake",
})
if err != nil {
t.Fatalf("matching task_key must be accepted: %v", err)
}
if got != docID {
t.Fatalf("got %q, want %q", got, docID)
}
}
func TestResolveAndValidateDocID_ExistingDocAccepted(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
ctx := context.Background()
docID := "doc_proj56_research"
_, err := svc.docGov.CreateOrGetDocument(
ctx, docID, 56, "research", "调研报告", "V0.5", "",
1, "张工", 2, "李主管", 3, "王总监",
)
if err != nil {
t.Fatalf("CreateOrGetDocument: %v", err)
}
got, err := svc.ResolveAndValidateDocID(ctx, TaskRunRequest{
DocID: "doc-p56-research",
ProjectID: 56,
TaskKey: "research",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != docID {
t.Fatalf("got %q, want %q", got, docID)
}
}
func TestCreateOrGetDocument_ConcurrentSameDocID(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
ctx := context.Background()
docID := "doc_proj77_research"
type outcome struct {
id string
err error
}
ch := make(chan outcome, 2)
create := func() {
doc, err := svc.docGov.CreateOrGetDocument(
ctx, docID, 77, "research", "调研报告", "V0.5", "",
1, "张工", 2, "李主管", 3, "王总监",
)
id := ""
if doc != nil {
id = doc.DocID
}
ch <- outcome{id: id, err: err}
}
go create()
go create()
first := <-ch
second := <-ch
if first.err != nil {
t.Fatalf("first create failed: %v", first.err)
}
if second.err != nil {
t.Fatalf("second create failed: %v", second.err)
}
if first.id != docID || second.id != docID {
t.Fatalf("doc ids mismatch: first=%q second=%q want %q", first.id, second.id, docID)
}
doc, err := svc.docGov.GetDocument(ctx, docID)
if err != nil || doc == nil {
t.Fatalf("expected single persisted doc %s, got err=%v doc=%v", docID, err, doc)
}
}
func TestExecuteTask_LegacyDocIDDoesNotFailValidation(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir()})
ctx := context.Background()
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
TaskKey: "research",
ProjectID: 56,
ProjectName: "智能体演示项目",
CustomerName: "北京水木东方",
DeviceType: "医疗器械",
Backend: "stub",
Timeout: 10 * time.Second,
DocID: "doc-p56-research",
})
if err != nil {
t.Fatalf("ExecuteTask with legacy doc_id failed: %v", err)
}
if !res.Success {
if strings.Contains(res.ErrorReason, "doc_id not found") {
t.Fatalf("legacy doc_id still rejected: %s", res.ErrorReason)
}
t.Fatalf("task failed for other reason: %s", res.ErrorReason)
}
if res.DocRecord == nil || res.DocRecord.DocID != "doc_proj56_research" {
t.Fatalf("expected doc_proj56_research record, got %#v", res.DocRecord)
}
}
func TestExecuteTask_CreateDocumentFailureIsNotSuccess(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"})
svc.createDocumentHook = func(context.Context, string, int64, string, string, string, string, int64, string, int64, string, int64, string) (*docgov.DocumentRecord, error) {
return nil, errors.New("ledger unavailable")
}
res, err := svc.ExecuteTask(context.Background(), TaskRunRequest{
TaskKey: "research",
ProjectID: 56,
ProjectName: "智能体演示项目",
CustomerName: "北京水木东方",
DeviceType: "医疗器械",
Backend: "stub",
Timeout: 10 * time.Second,
})
if err == nil || !strings.Contains(err.Error(), "ledger unavailable") {
t.Fatalf("want ledger error, got %v", err)
}
if res == nil || res.Success {
t.Fatalf("no ledger must not report success: %+v", res)
}
if !strings.Contains(res.ErrorReason, "create document ledger failed") {
t.Fatalf("ErrorReason=%q", res.ErrorReason)
}
if res.DocRecord != nil {
t.Fatalf("failed create must not return a ledger, got %#v", res.DocRecord)
}
}
func TestExecuteTask_EmptyDocIDSkipsDocRevision(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"})
ctx := context.Background()
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
TaskKey: "intake",
ProjectID: 0,
DocID: "",
ProjectName: "掌上便携超声",
CustomerName: "水木清研",
DeviceType: "二类掌上超声",
Backend: "stub",
Timeout: 10 * time.Second,
})
if err != nil {
t.Fatalf("empty docID ExecuteTask panicked or failed: %v", err)
}
if res == nil || !res.Success {
t.Fatalf("CLI-like stage path should succeed without doc ledger, got %#v", res)
}
if res.DocRecord != nil {
t.Fatalf("empty docID must not create a document record, got %#v", res.DocRecord)
}
if _, stErr := svc.docRev.GetRevisionState(ctx, ""); !strings.Contains(stErr.Error(), "invalid doc_id") {
t.Fatalf("empty GetRevisionState should be invalid, got %v", stErr)
}
}
func TestExecuteTask_MaterializeFailureSurfacesInTaskRunResult(t *testing.T) {
svc := MustNewService(t, &config.Config{DataDir: t.TempDir(), AgentBackend: "stub"})
ctx := context.Background()
svc.docRev.AgentCommit = false // force MaterializeAgentOutput to fail after squad success
res, err := svc.ExecuteTask(ctx, TaskRunRequest{
TaskKey: "research",
ProjectID: 56,
ProjectName: "智能体演示项目",
CustomerName: "北京水木东方",
DeviceType: "医疗器械",
Backend: "stub",
Timeout: 10 * time.Second,
DocID: "doc-p56-research",
UserID: 1,
})
if err == nil {
t.Fatal("expected Materialize error to be returned")
}
if res == nil {
t.Fatal("TaskRunResult must be non-nil with visible failure")
}
if res.Success {
t.Fatal("TaskRunResult.Success must be false when ledger/report materialize fails")
}
if !strings.Contains(res.ErrorReason, "文档版本账本/变更报告落盘失败") {
t.Fatalf("ErrorReason must be visible to callers, got %q", res.ErrorReason)
}
}