Files
Agent-System/internal/server/e2e_workflow_governance_test.go
weichenandCursor 56c85bba11 fix(ci): green governance PG on isolated TEST_DATABASE_URL
Add duty holders as project members, drop sync notification assert
after outbox cutover, and add an explicit CI governance PG step.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:20:32 +00:00

153 lines
6.3 KiB
Go

package server_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/db"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/workflowdef"
)
// TestE2E_WorkflowGovernance_CompleteAndReplan 验证末阶段完成后项目回到 planning 并可再次获取目录。
func TestE2E_WorkflowGovernance_CompleteAndReplan(t *testing.T) {
dsn := db.SkipIfNoTestDatabaseURL(t)
_ = dsn
pool := openPGPoolWithMigrations(t)
if pool == nil {
return
}
defer pool.Close()
ctx := context.Background()
handler, srv := setupPGGovernanceServer(t, pool)
creatorToken := loginTestUser(t, handler, "zhanggong", "123456")
creatorID := fetchAuthUserID(t, handler, creatorToken)
wfKey := workflowdef.OfficialKeyTestManager
seedOfficialWorkflowManifest(t, pool, wfKey)
var projectID int64
code := "e2e-gov-" + 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, 'E2E Governance', 'active', $2, 'planning', '', 1, 0) RETURNING id
`, code, creatorID).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)
})
reqDefault := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/projects/%d/workflow-plan/default?workflow_key=%s&version=0", projectID, wfKey), nil)
withBearerAuth(reqDefault, creatorToken)
recDefault := httptest.NewRecorder()
handler.ServeHTTP(recDefault, reqDefault)
if recDefault.Code != http.StatusOK {
t.Fatalf("default plan: %d %s", recDefault.Code, recDefault.Body.String())
}
var plan projectinstance.WorkflowPlan
_ = json.Unmarshal(recDefault.Body.Bytes(), &plan)
reviewerID := fetchAuthUserID(t, handler, loginTestUser(t, handler, "lizhuguan", "123456"))
approverID := fetchAuthUserID(t, handler, loginTestUser(t, handler, "wangzong", "123456"))
ensureProjectMembers(t, pool, projectID, map[int64]string{
creatorID: "owner",
reviewerID: "member",
approverID: "member",
})
for i := range plan.Stages {
for j := range plan.Stages[i].DocTasks {
plan.Stages[i].DocTasks[j].Duties = projectinstance.DocDuty{
AuthorUserID: creatorID, ReviewerUserID: reviewerID, ApproverUserID: approverID,
}
}
}
saveBody, _ := json.Marshal(projectinstance.SavePlanInput{Plan: plan, ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1})
reqSave := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/projects/%d/workflow-plan", projectID), bytes.NewReader(saveBody))
withBearerAuth(reqSave, creatorToken)
recSave := httptest.NewRecorder()
handler.ServeHTTP(recSave, reqSave)
if recSave.Code != http.StatusOK {
t.Fatalf("save plan: %d %s", recSave.Code, recSave.Body.String())
}
reqPreview := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/projects/%d/activation-preview", projectID), nil)
withBearerAuth(reqPreview, creatorToken)
recPreview := httptest.NewRecorder()
handler.ServeHTTP(recPreview, reqPreview)
var preview projectinstance.ActivationPreview
_ = json.Unmarshal(recPreview.Body.Bytes(), &preview)
activateBody, _ := json.Marshal(projectinstance.ActivateInput{
ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion,
ExpectedPlanRevision: preview.ExpectedPlanRevision,
})
reqActivate := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/projects/%d/workflow-instances/activate", projectID), bytes.NewReader(activateBody))
withBearerAuth(reqActivate, creatorToken)
recActivate := httptest.NewRecorder()
handler.ServeHTTP(recActivate, reqActivate)
if recActivate.Code != http.StatusOK {
t.Fatalf("activate: %d %s", recActivate.Code, recActivate.Body.String())
}
var activated projectinstance.ActivateResult
_ = json.Unmarshal(recActivate.Body.Bytes(), &activated)
docID := fmt.Sprintf("doc_proj%d_test_manager", projectID)
docEngine := bizdev.Snapshot(srv.BizdevService()).Gov
_, err = docEngine.CreateOrGetDocument(ctx, docID, projectID, "test_manager", "系统验证方案及计划", "system_verification_planning", "",
creatorID, "Author", reviewerID, "Reviewer", approverID, "Approver")
if err != nil {
t.Fatalf("create doc: %v", err)
}
_ = docEngine.MarkDocumentContentUpdated(ctx, docID, "hash_e2e_te", false)
_, _ = docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author")
_, _ = docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok")
_, err = docEngine.ApproverApprove(ctx, docID, approverID, "approver", "Approver", "ok")
if err != nil {
t.Fatalf("approve doc: %v", err)
}
advanceBody, _ := json.Marshal(projectinstance.AdvanceStageInput{
StageKey: "system_verification_planning", ExpectedConfigRevision: activated.Instance.ConfigRevision,
})
reqAdvance := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/v1/workflow-instances/%d/stages/system_verification_planning/advance", activated.Instance.ID), bytes.NewReader(advanceBody))
withBearerAuth(reqAdvance, creatorToken)
recAdvance := httptest.NewRecorder()
handler.ServeHTTP(recAdvance, reqAdvance)
if recAdvance.Code != http.StatusOK {
t.Fatalf("advance: %d %s", recAdvance.Code, recAdvance.Body.String())
}
reqSettings := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/projects/%d/settings", projectID), nil)
withBearerAuth(reqSettings, creatorToken)
recSettings := httptest.NewRecorder()
handler.ServeHTTP(recSettings, reqSettings)
if recSettings.Code != http.StatusOK {
t.Fatalf("settings: %d %s", recSettings.Code, recSettings.Body.String())
}
var settings projectinstance.ProjectSettings
if err := json.Unmarshal(recSettings.Body.Bytes(), &settings); err != nil {
t.Fatalf("decode settings: %v", err)
}
if settings.LifecycleStatus != projectinstance.LifecyclePlanning {
t.Fatalf("expected planning after completion, got %q", settings.LifecycleStatus)
}
reqCatalog := httptest.NewRequest(http.MethodGet, "/api/v1/workflows/catalog", nil)
withBearerAuth(reqCatalog, creatorToken)
recCatalog := httptest.NewRecorder()
handler.ServeHTTP(recCatalog, reqCatalog)
if recCatalog.Code != http.StatusOK {
t.Fatalf("catalog: %d %s", recCatalog.Code, recCatalog.Body.String())
}
}