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>
153 lines
6.3 KiB
Go
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())
|
|
}
|
|
}
|