Files
Agent-System/internal/docgov/engine_db_test.go
T
weichenandCursor c2efcdbacd fix(docgov): map zero duty ids to NULL on CreateOrGetDocument
Optional reviewer/approver duties leave UserID=0; writing 0 into
document_records FK columns failed with SQLSTATE 23503. Align create
path with docduty nullIfZero and surface ensureUser failures in logs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 06:08:35 +00:00

161 lines
4.4 KiB
Go

package docgov_test
import (
"context"
"fmt"
"testing"
"time"
"chat-agent-system/internal/config"
"chat-agent-system/internal/docgov"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestDocGov_DatabaseIntegration(t *testing.T) {
cfg, err := config.Load(".env")
if err != nil || cfg == nil || cfg.ContextDbHost == "" {
t.Skip("Skipping DB integration test: context DB not configured")
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pool, err := pgxpool.New(ctx, cfg.ContextDSN())
if err != nil || pool.Ping(ctx) != nil {
t.Skipf("Skipping DB integration test: cannot connect to %s: %v", cfg.ContextDSN(), err)
}
defer pool.Close()
engine := docgov.NewGovernanceEngine(pool)
testDocID := fmt.Sprintf("doc_test_db_%d", time.Now().UnixNano())
// 1. 创建受控文档
doc, err := engine.CreateOrGetDocument(
ctx,
testDocID,
1,
"feasibility_reg",
"DB集成测试文档",
"V0.5_feasibility",
"data/dev/workspaces/test_db",
1, "张工",
2, "李审核",
3, "王总监",
)
if err != nil {
t.Fatalf("CreateOrGetDocument in DB failed: %v", err)
}
if doc.DocID != testDocID {
t.Fatalf("unexpected docID: %s", doc.DocID)
}
// 2. 标记正文非模板并认领
_ = engine.MarkDocumentContentUpdated(ctx, testDocID, "db_test_sha256", false)
doc, err = engine.RecordDocumentViewed(ctx, testDocID, 1, docgov.RoleAuthor, "张工")
if err != nil {
t.Fatalf("RecordDocumentViewed failed: %v", err)
}
if !doc.AuthorClaimed {
t.Fatalf("expected author_claimed in DB")
}
// 3. 生成变更提案
prop, err := engine.ProposeModification(
ctx,
testDocID,
"agent_squad",
"测试修改提要",
"测试影响分析",
"测试潜在风险",
)
if err != nil {
t.Fatalf("ProposeModification in DB failed: %v", err)
}
// 4. 拍板同意提案
prop, err = engine.DecideModificationProposal(ctx, prop.ProposalID, 1, true, "DB测试同意")
if err != nil {
t.Fatalf("DecideModificationProposal in DB failed: %v", err)
}
if prop.Status != docgov.ProposalApprovedByAuthor {
t.Fatalf("expected proposal approved, got %s", prop.Status)
}
// 5. 批注与送审流程
_, err = engine.AddComment(ctx, testDocID, 2, "李审核", docgov.RoleReviewer, "## 结论", docgov.CommentSuggestion, "请增加法规出处")
if err != nil {
t.Fatalf("AddComment in DB failed: %v", err)
}
doc, err = engine.SubmitForReview(ctx, testDocID, 1, docgov.RoleAuthor, "张工")
if err != nil {
t.Fatalf("SubmitForReview in DB failed: %v", err)
}
if !doc.IsLocked {
t.Fatalf("expected doc locked in reviewing")
}
}
// TestDocGov_CreateOrGetDocument_OptionalDutiesNullFK ensures reviewer/approver=0
// persist as SQL NULL (optional DutyMode) instead of FK-violating 0.
func TestDocGov_CreateOrGetDocument_OptionalDutiesNullFK(t *testing.T) {
cfg, err := config.Load(".env")
if err != nil || cfg == nil || cfg.ContextDbHost == "" {
t.Skip("Skipping DB integration test: context DB not configured")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
pool, err := pgxpool.New(ctx, cfg.ContextDSN())
if err != nil || pool.Ping(ctx) != nil {
t.Skipf("Skipping DB integration test: cannot connect to %s: %v", cfg.ContextDSN(), err)
}
defer pool.Close()
engine := docgov.NewGovernanceEngine(pool)
testDocID := fmt.Sprintf("doc_opt_duty_%d", time.Now().UnixNano())
projectID := time.Now().UnixNano()%900000 + 100000
doc, err := engine.CreateOrGetDocument(
ctx,
testDocID,
projectID,
"intake",
"可选职责空审核人",
"V0.5_feasibility",
"",
9, "系统管理员",
0, "",
0, "",
)
if err != nil {
t.Fatalf("CreateOrGetDocument with reviewer/approver=0 failed: %v", err)
}
if doc.DocID != testDocID {
t.Fatalf("unexpected docID: %s", doc.DocID)
}
var reviewerNull, approverNull bool
var authorID int64
err = pool.QueryRow(ctx, `
SELECT author_id,
reviewer_id IS NULL,
approver_id IS NULL
FROM document_records WHERE doc_id = $1
`, testDocID).Scan(&authorID, &reviewerNull, &approverNull)
if err != nil {
t.Fatalf("query document_records: %v", err)
}
if authorID != 9 {
t.Fatalf("author_id=%d want 9", authorID)
}
if !reviewerNull || !approverNull {
t.Fatalf("expected NULL reviewer/approver, got reviewerNull=%v approverNull=%v", reviewerNull, approverNull)
}
_, _ = pool.Exec(ctx, `DELETE FROM document_records WHERE doc_id = $1`, testDocID)
}