Files
Agent-System/internal/bizdev/doc_id_test.go
T
weichenandCursor 645c4b67b0 fix(bizdev): surface Materialize failures and block invalid auto-commit
TaskRunResult now fails visibly when ledger/report materialization errors;
invalid or unassessed reports are never auto-committed.

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

233 lines
6.7 KiB
Go

package bizdev
import (
"context"
"strings"
"testing"
"time"
"chat-agent-system/internal/config"
)
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)
}
}
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_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.DocRevision().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.DocRevision().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)
}
}