Files
Agent-System/internal/projectinstance/service_test.go
T
weichenandCursor ad6e38d11a test(workflow): add governance regression and activation PG coverage
Cover public auth config, catalog policy, official main workflow activation on Postgres, and conn-busy metadata writeback fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-13 02:50:23 +00:00

165 lines
5.0 KiB
Go

package projectinstance
import (
"context"
"errors"
"testing"
"chat-agent-system/internal/workflowdef"
)
type stubVersionResolver struct {
versions map[string]*workflowdef.Version
defs map[string]*workflowdef.Definition
}
func (s *stubVersionResolver) GetDefinitionByKey(_ context.Context, key string) (*workflowdef.Definition, error) {
d, ok := s.defs[key]
if !ok {
return nil, workflowdef.ErrNotFound
}
return d, nil
}
func (s *stubVersionResolver) GetVersion(_ context.Context, workflowID int64, version int) (*workflowdef.Version, error) {
for _, v := range s.versions {
if v.WorkflowID == workflowID && v.Version == version {
copyV := *v
return &copyV, nil
}
}
return nil, workflowdef.ErrNotFound
}
func (s *stubVersionResolver) ListDefinitions(_ context.Context) ([]workflowdef.Definition, error) {
out := make([]workflowdef.Definition, 0, len(s.defs))
for _, d := range s.defs {
out = append(out, *d)
}
return out, nil
}
func testResolver() *stubVersionResolver {
key := workflowdef.OfficialKeyMain
ver := &workflowdef.Version{
ID: 100, WorkflowID: 10, Version: 1, Status: workflowdef.StatusPublished,
WorkflowKey: key, WorkflowName: workflowdef.OfficialWorkflowName(key),
Definition: workflowdef.VersionDefinition{
SchemaVersion: 2,
WorkflowKey: key,
Stages: []workflowdef.StageDef{{
Key: "V0.1_triage", Name: "V0.1 需求摸底", Optional: false, Position: 10,
DocTasks: []workflowdef.DocTaskDef{{
Key: "intake", Title: "用户初始需求收集表", CreatorConfigurable: true, BlockingWhenEnabled: true, DefaultEnabled: true,
OutputDocType: "intake", OutputDocTitle: "用户初始需求收集表",
}},
}},
},
}
return &stubVersionResolver{
defs: map[string]*workflowdef.Definition{
key: {ID: 10, Key: key, Name: workflowdef.OfficialWorkflowName(key), LatestPublishedVersion: 1},
},
versions: map[string]*workflowdef.Version{key: ver},
}
}
func validPlan() WorkflowPlan {
return WorkflowPlan{
WorkflowDefinitionKey: workflowdef.OfficialKeyMain,
WorkflowVersion: 1,
Stages: []StagePlan{{
StageKey: "V0.1_triage", Enabled: true,
DocTasks: []DocTaskPlan{{
TaskKey: "intake", Enabled: true,
Duties: DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3},
}},
}},
}
}
func TestService_PlanPreviewActivate(t *testing.T) {
ctx := context.Background()
store := newMemStore()
_ = SeedProjectMeta(store, 42, LifecyclePlanning, "", 1, 0)
svc := NewService(store, testResolver(), nil, nil)
settings, err := svc.SaveWorkflowPlan(ctx, 42, 1, true, SavePlanInput{
Plan: validPlan(), ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1,
})
if err != nil {
t.Fatalf("SaveWorkflowPlan: %v", err)
}
if settings.Plan == nil || settings.Plan.WorkflowDefinitionKey != workflowdef.OfficialKeyMain {
t.Fatalf("expected saved plan, got %+v", settings.Plan)
}
preview, err := svc.ActivationPreview(ctx, 42, 1, true)
if err != nil {
t.Fatalf("ActivationPreview: %v", err)
}
if preview.ConfirmationToken == "" || preview.PlanHash == "" {
t.Fatal("expected confirmation token and plan hash")
}
result, err := svc.Activate(ctx, 42, 1, true, ActivateInput{
ConfirmationToken: preview.ConfirmationToken,
ExpectedProjectVersion: preview.ExpectedProjectVersion,
ExpectedPlanRevision: preview.ExpectedPlanRevision,
})
if err != nil {
t.Fatalf("Activate: %v", err)
}
if result.LifecycleStatus != LifecycleActive || result.ActiveWorkflowKey != workflowdef.OfficialKeyMain {
t.Fatalf("unexpected activate result: %+v", result)
}
live, _ := store.HasLiveInstance(ctx, 42)
if !live {
t.Fatal("expected live instance after activation")
}
}
func TestService_BlockPlanEditWhenActive(t *testing.T) {
ctx := context.Background()
store := newMemStore()
_ = SeedProjectMeta(store, 7, LifecycleActive, "main", 2, 1)
svc := NewService(store, testResolver(), nil, nil)
_, err := svc.SaveWorkflowPlan(ctx, 7, 1, true, SavePlanInput{
Plan: validPlan(), ExpectedPlanRevision: 1,
})
if !errors.Is(err, ErrPlanFrozen) {
t.Fatalf("expected ErrPlanFrozen, got %v", err)
}
}
func TestService_SeparationOfDutyValidation(t *testing.T) {
bad := validPlan()
bad.Stages[0].DocTasks[0].Duties.ReviewerUserID = 1
res := validatePlan(bad, testResolver().versions[workflowdef.OfficialKeyMain])
if res.Valid {
t.Fatal("expected separation of duty violation")
}
}
func TestService_SavePlanReturnsValidationIssues(t *testing.T) {
ctx := context.Background()
store := newMemStore()
_ = SeedProjectMeta(store, 8, LifecyclePlanning, "", 1, 0)
svc := NewService(store, testResolver(), nil, nil)
bad := validPlan()
bad.Stages[0].DocTasks[0].Duties = DocDuty{}
_, err := svc.SaveWorkflowPlan(ctx, 8, 1, true, SavePlanInput{
Plan: bad, ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1,
})
var validationErr *ValidationError
if !errors.As(err, &validationErr) {
t.Fatalf("expected ValidationError, got %v", err)
}
if len(validationErr.Issues) != 3 {
t.Fatalf("expected three missing-duty issues, got %+v", validationErr.Issues)
}
}