Resolve conflicts: both CURRENT claim tables (slot preserved), local multi-host coordination plan + origin phased companion, origin module-boundaries, combined PG isolation with remote product tests. FEATURE-MATRIX takes origin record_id layout; A-G done bump deferred pending evidence row apply.
192 lines
5.6 KiB
Go
192 lines
5.6 KiB
Go
package docgov_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/db"
|
|
"chat-agent-system/internal/docgov"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// openIsolatedPool 仅连接显式提供的隔离测试库(TEST_DATABASE_URL),
|
|
// 禁止回退到共享环境库(agent_system_dev/test/main)。
|
|
func openIsolatedPool(t *testing.T) *pgxpool.Pool {
|
|
t.Helper()
|
|
dsn := db.SkipIfNoTestDatabaseURL(t)
|
|
db.RefuseSharedEnvDSN(t, dsn)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
pool, err := pgxpool.New(ctx, dsn)
|
|
if err != nil {
|
|
t.Skipf("cannot connect to isolated test db: %v", err)
|
|
}
|
|
if err := pool.Ping(ctx); err != nil {
|
|
pool.Close()
|
|
t.Skipf("isolated test db ping failed: %v", err)
|
|
}
|
|
if err := db.RunMigrationsOnceForTest(ctx, pool, dsn); err != nil {
|
|
pool.Close()
|
|
t.Skipf("migration failed: %v", err)
|
|
}
|
|
return pool
|
|
}
|
|
|
|
// seedTestProject 创建本测试专属项目行,满足 document_records.project_id 外键。
|
|
func seedTestProject(t *testing.T, pool *pgxpool.Pool) int64 {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
stamp := time.Now().Format("20060102150405.000000")
|
|
var projectID int64
|
|
err := pool.QueryRow(ctx, `
|
|
INSERT INTO projects (code, name, status, creator_user_id, lifecycle_status)
|
|
VALUES ('docgov-' || $1, 'DocGov Engine Test ' || $1, 'active', 2, 'active')
|
|
RETURNING id
|
|
`, stamp).Scan(&projectID)
|
|
if err != nil {
|
|
t.Fatalf("seed project: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
cleanupCtx := context.Background()
|
|
_, _ = pool.Exec(cleanupCtx, `DELETE FROM document_comments WHERE doc_id IN (SELECT doc_id FROM document_records WHERE project_id = $1)`, projectID)
|
|
_, _ = pool.Exec(cleanupCtx, `DELETE FROM document_change_proposals WHERE doc_id IN (SELECT doc_id FROM document_records WHERE project_id = $1)`, projectID)
|
|
_, _ = pool.Exec(cleanupCtx, `DELETE FROM document_records WHERE project_id = $1`, projectID)
|
|
_, _ = pool.Exec(cleanupCtx, `DELETE FROM projects WHERE id = $1`, projectID)
|
|
})
|
|
return projectID
|
|
}
|
|
|
|
func TestDocGov_DatabaseIntegration(t *testing.T) {
|
|
pool := openIsolatedPool(t)
|
|
defer pool.Close()
|
|
projectID := seedTestProject(t, pool)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
engine := docgov.NewGovernanceEngine(pool)
|
|
|
|
testDocID := fmt.Sprintf("doc_test_db_%d", time.Now().UnixNano())
|
|
|
|
// 1. 创建受控文档
|
|
doc, err := engine.CreateOrGetDocument(
|
|
ctx,
|
|
testDocID,
|
|
projectID,
|
|
"feasibility_reg",
|
|
"DB集成测试文档",
|
|
"V0.5_feasibility",
|
|
"data/dev/workspaces/test_db",
|
|
2, "张工",
|
|
3, "李主管",
|
|
1, "Seed Admin",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("CreateOrGetDocument in DB failed: %v", err)
|
|
}
|
|
if doc.DocID != testDocID {
|
|
t.Fatalf("unexpected docID: %s", testDocID)
|
|
}
|
|
|
|
// 2. 标记正文非模板并认领
|
|
_ = engine.MarkDocumentContentUpdated(ctx, testDocID, "db_test_sha256", false)
|
|
doc, err = engine.RecordDocumentViewed(ctx, testDocID, 2, 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, 2, 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, 3, "李主管", docgov.RoleReviewer, "## 结论", docgov.CommentSuggestion, "请增加法规出处")
|
|
if err != nil {
|
|
t.Fatalf("AddComment in DB failed: %v", err)
|
|
}
|
|
|
|
doc, err = engine.SubmitForReview(ctx, testDocID, 2, docgov.RoleAuthor, "张工")
|
|
if err != nil {
|
|
t.Fatalf("SubmitForReview 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) {
|
|
pool := openIsolatedPool(t)
|
|
defer pool.Close()
|
|
projectID := seedTestProject(t, pool)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
engine := docgov.NewGovernanceEngine(pool)
|
|
testDocID := fmt.Sprintf("doc_opt_duty_%d", time.Now().UnixNano())
|
|
|
|
doc, err := engine.CreateOrGetDocument(
|
|
ctx,
|
|
testDocID,
|
|
projectID,
|
|
"intake",
|
|
"可选职责空审核人",
|
|
"V0.5_feasibility",
|
|
"",
|
|
2, "张工",
|
|
0, "",
|
|
0, "",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("CreateOrGetDocument with reviewer/approver=0 failed: %v", err)
|
|
}
|
|
if doc.DocID != testDocID {
|
|
t.Fatalf("unexpected docID: %s", testDocID)
|
|
}
|
|
|
|
var reviewerNull, approverNull bool
|
|
var gotAuthor 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(&gotAuthor, &reviewerNull, &approverNull)
|
|
if err != nil {
|
|
t.Fatalf("query document_records: %v", err)
|
|
}
|
|
if gotAuthor != 2 {
|
|
t.Fatalf("author_id=%d want 2", gotAuthor)
|
|
}
|
|
if !reviewerNull || !approverNull {
|
|
t.Fatalf("expected NULL reviewer/approver, got reviewerNull=%v approverNull=%v", reviewerNull, approverNull)
|
|
}
|
|
}
|