package server_test import ( "bytes" "chat-agent-system/internal/docrevision" "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 ' || $1, '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_tm_system_verification", projectID) docEngine := bizdev.Snapshot(srv.BizdevService()).Gov _, err = docEngine.CreateOrGetDocument(ctx, docID, projectID, "tm_system_verification", "系统验证方案及计划", "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) if _, err := docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author"); err != nil { t.Fatalf("submit review: %v", err) } snapID, err := docrevision.SeedContentSnapshotForTest(ctx, pool, docID, creatorID, "# e2e governance") if err != nil { t.Fatalf("seed snapshot: %v", err) } if err := docEngine.FreezeHeadSnapshotForTest(ctx, docID, snapID); err != nil { t.Fatalf("freeze head snapshot: %v", err) } if _, err := docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok"); err != nil { t.Fatalf("review approve: %v", err) } _, 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()) } }