Files
Agent-System/internal/projectinstance/activation_official_pg_test.go
T
agent-bot ee1e53b510
CI / go-vet (pull_request) Failing after 10s
CI / backup-restore-drill (pull_request) Failing after 25s
CI / web (pull_request) Successful in 33s
fix(tests): per-package test databases + legitimate approval fixtures (issue #2 close)
Root causes of the remaining 5 CI PG failures (and the hidden flaky set):
1. All PG-integration packages shared ONE database under parallel package
   execution - migrations/seeds/truncates raced (nondeterministic failure
   sets, locally and in CI).
   Fix: RequireTestDatabaseURL/SkipIfNoTestDatabaseURL now derive a
   per-package database (<base>_<pkgHash>, advisory-lock-guarded
   CREATE); 9 raw-env straggler tests funnelled through the helpers;
   db-package tests call it unqualified (no import cycle).
2. Approval-chain fixtures were not production-shaped: freezeReviewSnapshot
   only honors head_snapshot_id, fake content hashes never froze a
   snapshot, so ApproverApprove rejected with 'approved requires frozen
   review_snapshot_id'. Errors were swallowed (_, _ =) hiding this.
   Fix (ownership-respecting, arch-enforced):
   - docrevision.SeedContentSnapshotForTest seeds the ledger tables it owns
   - docgov GovernanceEngine.FreezeHeadSnapshotForTest freezes head/review
     pointers on document_records (docgov-owned) and clears engine cache
   - all four integration fixtures use the pair; swallowed errors checked
3. Fixed-name fixtures collide once a package shares one DB (projects
   uq_projects_name_global): uniquified in projectinstance + server tests.
4. CAS/ctxmgr tests never bootstrapped migrations (relied on other packages
   having migrated the shared DB): explicit RunMigrationsOnceForTest.

Verified: full parallel short suite = 75 packages, 0 failures against an
isolated throwaway PG. CI skip list removed - short tests fully required.
2026-09-29 09:54:43 +00:00

100 lines
3.5 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 projectinstance_test
import (
"context"
"testing"
"time"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/projectworkflow"
"chat-agent-system/internal/workflowdef"
)
// TestPG_ActivateOfficialWorkflowRuntimeMetadata 回归激活链路:
// ActivateInstance.applyRuntimeMetadata 会遍历文档任务实例并逐条 UPDATE。
// 若在同一事务连接上 rows 未读完时就执行 Exec,pgx 会返回 "conn busy"。
// 本测试使用正式商务对接流程(3 阶段 7 任务)覆盖该路径。
func TestPG_ActivateOfficialWorkflowRuntimeMetadata(t *testing.T) {
pool := openPGPool(t)
defer pool.Close()
ctx := context.Background()
wfStore := workflowdef.NewStore(pool)
def, err := workflowdef.LoadManifestDefinition("main.v2.json")
if err != nil {
t.Fatalf("load main.v2 manifest: %v", err)
}
if err := wfStore.UpsertPublishedSeed(ctx, workflowdef.CreateDefinitionInput{
Key: workflowdef.OfficialKeyMain,
Name: "商务对接流程",
Definition: def,
}, 1); err != nil {
t.Fatalf("seed official main workflow: %v", err)
}
pwStore := projectworkflow.NewStore(pool)
instStore := projectinstance.NewStore(pool)
svc := projectinstance.NewService(instStore, wfStore, pwStore, nil)
var projectID int64
code := "pg-official-" + time.Now().Format("150405.000")
err = pool.QueryRow(ctx, `
INSERT INTO projects (code, name, status, creator_user_id, lifecycle_status, active_workflow_key, workflow_selection_version, workflow_plan_revision)
VALUES ($1, $2, 'active', 1, 'planning', '', 1, 0) RETURNING id
`, code, "PG Official Activation Test "+code).Scan(&projectID)
if err != nil {
t.Fatalf("insert project: %v", err)
}
t.Cleanup(func() {
_, _ = pool.Exec(context.Background(), `DELETE FROM projects WHERE id = $1`, projectID)
})
plan, err := svc.BuildDefaultPlan(ctx, workflowdef.OfficialKeyMain, 0)
if err != nil {
t.Fatalf("default plan: %v", err)
}
for i := range plan.Stages {
for j := range plan.Stages[i].DocTasks {
plan.Stages[i].DocTasks[j].Duties = projectinstance.DocDuty{
AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3,
}
}
}
if _, err := svc.SaveWorkflowPlan(ctx, projectID, 1, true, projectinstance.SavePlanInput{
Plan: *plan, ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1,
}); err != nil {
t.Fatalf("save plan: %v", err)
}
preview, err := svc.ActivationPreview(ctx, projectID, 1, true)
if err != nil {
t.Fatalf("preview: %v", err)
}
activated, err := svc.Activate(ctx, projectID, 1, true, projectinstance.ActivateInput{
ConfirmationToken: preview.ConfirmationToken,
ExpectedProjectVersion: preview.ExpectedProjectVersion,
ExpectedPlanRevision: preview.ExpectedPlanRevision,
})
if err != nil {
t.Fatalf("activate: %v", err)
}
if activated.Instance.Status != projectinstance.InstanceActive {
t.Fatalf("expected active instance, got %q", activated.Instance.Status)
}
// 运行时元数据应写回文档任务实例(回归 applyRuntimeMetadata 的逐条 UPDATE)。
var executorKey string
err = pool.QueryRow(ctx, `
SELECT dti.executor_key
FROM project_doc_task_instances dti
JOIN project_stage_instances si ON si.id = dti.stage_instance_id
WHERE si.workflow_instance_id = $1 AND dti.task_key IN ('intake', 'bd_intake')
`, activated.Instance.ID).Scan(&executorKey)
if err != nil {
t.Fatalf("query runtime metadata: %v", err)
}
if executorKey != "bizdev.intake.v1" {
t.Fatalf("expected executor_key bizdev.intake.v1, got %q", executorKey)
}
}