enforceChatProjectAccess before allowAnyStageAccess on GET /context/project so activated projects are no longer readable by arbitrary authenticated users. Align failing gate fixtures with official workflow keys, isolated TEST_DATABASE_URL, unique project names (00057), duty membership, and in-memory bizdev-cli envs. Co-authored-by: Cursor <cursoragent@cursor.com>
124 lines
4.0 KiB
Go
124 lines
4.0 KiB
Go
package projectinstance_test
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
"time"
|
||
|
||
"chat-agent-system/internal/projectinstance"
|
||
"chat-agent-system/internal/projectworkflow"
|
||
"chat-agent-system/internal/workflowdef"
|
||
)
|
||
|
||
// TestPG_RDevActivateWithNAClose 激活 rdev(60 任务)并将 rd_sw_plan 规划关闭。
|
||
func TestPG_RDevActivateWithNAClose(t *testing.T) {
|
||
pool := openPGPool(t)
|
||
defer pool.Close()
|
||
ctx := context.Background()
|
||
|
||
wfStore := workflowdef.NewStore(pool)
|
||
def, err := workflowdef.LoadManifestDefinition("rdev.v2.json")
|
||
if err != nil {
|
||
t.Fatalf("load rdev manifest: %v", err)
|
||
}
|
||
if err := wfStore.UpsertPublishedSeed(ctx, workflowdef.CreateDefinitionInput{
|
||
Key: workflowdef.OfficialKeyRDev,
|
||
Name: "研发设计控制流程",
|
||
Definition: def,
|
||
}, 1); err != nil {
|
||
t.Fatalf("seed rdev: %v", err)
|
||
}
|
||
|
||
pwStore := projectworkflow.NewStore(pool)
|
||
instStore := projectinstance.NewStore(pool)
|
||
svc := projectinstance.NewService(instStore, wfStore, pwStore, nil)
|
||
|
||
var projectID int64
|
||
code := "pg-rdev-" + 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 RDev Activation "+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.OfficialKeyRDev, 0)
|
||
if err != nil {
|
||
t.Fatalf("default plan: %v", err)
|
||
}
|
||
if len(plan.Stages) != 13 {
|
||
t.Fatalf("want 13 stages, got %d", len(plan.Stages))
|
||
}
|
||
taskCount := 0
|
||
for i := range plan.Stages {
|
||
for j := range plan.Stages[i].DocTasks {
|
||
taskCount++
|
||
plan.Stages[i].DocTasks[j].Duties = projectinstance.DocDuty{
|
||
AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3,
|
||
}
|
||
if plan.Stages[i].DocTasks[j].TaskKey == "rd_sw_plan" {
|
||
plan.Stages[i].DocTasks[j].Enabled = false
|
||
}
|
||
}
|
||
}
|
||
if taskCount != 60 {
|
||
t.Fatalf("want 60 tasks, got %d", taskCount)
|
||
}
|
||
|
||
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, got %q", activated.Instance.Status)
|
||
}
|
||
if activated.Instance.ConfigSnapshot.WorkflowDefinitionKey != workflowdef.OfficialKeyRDev {
|
||
t.Fatalf("key=%s", activated.Instance.ConfigSnapshot.WorkflowDefinitionKey)
|
||
}
|
||
|
||
var executorKey string
|
||
var enabled bool
|
||
err = pool.QueryRow(ctx, `
|
||
SELECT dti.executor_key, dti.enabled
|
||
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 = 'rd_proposal'
|
||
`, activated.Instance.ID).Scan(&executorKey, &enabled)
|
||
if err != nil {
|
||
t.Fatalf("query rd_proposal: %v", err)
|
||
}
|
||
if executorKey != "squad.rdev_doc.v1" || !enabled {
|
||
t.Fatalf("rd_proposal executor=%q enabled=%v", executorKey, enabled)
|
||
}
|
||
|
||
err = pool.QueryRow(ctx, `
|
||
SELECT dti.enabled
|
||
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 = 'rd_sw_plan'
|
||
`, activated.Instance.ID).Scan(&enabled)
|
||
if err != nil {
|
||
t.Fatalf("query rd_sw_plan: %v", err)
|
||
}
|
||
if enabled {
|
||
t.Fatal("rd_sw_plan should be disabled (N/A planning close)")
|
||
}
|
||
}
|