package projectinstance import ( "context" "testing" "chat-agent-system/internal/workflowdef" ) func TestCheckDocTaskAccess_DisabledDocumentRejected(t *testing.T) { ctx := context.Background() store := NewMemoryStore() _ = SeedProjectMeta(store, 400, LifecyclePlanning, "", 1, 0) resolver := disabledDocResolver() svc := NewService(store, resolver, nil, nil) version := resolver.versions[workflowdef.OfficialKeyMain] plan := buildDefaultPlan(version) for i := range plan.Stages[0].DocTasks { if plan.Stages[0].DocTasks[i].TaskKey == "research" { plan.Stages[0].DocTasks[i].Enabled = false continue } plan.Stages[0].DocTasks[i].Duties = DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3} } _, _ = svc.SaveWorkflowPlan(ctx, 400, 1, true, SavePlanInput{Plan: plan, ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1}) preview, _ := svc.ActivationPreview(ctx, 400, 1, true) _, _ = svc.Activate(ctx, 400, 1, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }) decision := svc.CheckDocTaskAccess(ctx, 400, 1, "research", DocAccessActionRun, true) if decision.Allowed || decision.Code != AccessCodeDocTaskDisabled { t.Fatalf("expected DOC_TASK_DISABLED, got %+v", decision) } } func TestCheckDocTaskAccess_NonDutyUserRejected(t *testing.T) { ctx := context.Background() store := NewMemoryStore() _ = SeedProjectMeta(store, 401, LifecyclePlanning, "", 1, 0) svc := NewService(store, TestResolver(), nil, nil) _, _ = svc.SaveWorkflowPlan(ctx, 401, 1, true, SavePlanInput{ Plan: ValidTestPlan(), ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1, }) preview, _ := svc.ActivationPreview(ctx, 401, 1, true) _, _ = svc.Activate(ctx, 401, 1, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }) decision := svc.CheckDocTaskAccess(ctx, 401, 99, "intake", DocAccessActionRun, false) if decision.Allowed || decision.Code != AccessCodeForbiddenDuty { t.Fatalf("expected DOC_DUTY_FORBIDDEN, got %+v", decision) } } func TestCheckDocTaskAccess_UnmanagedDutyModes(t *testing.T) { ctx := context.Background() store := NewMemoryStore() _ = SeedProjectMeta(store, 402, LifecyclePlanning, "", 1, 0) svc := NewService(store, optionalDutyResolver(), nil, nil) plan := optionalDutyPlan( DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3}, DocDuty{}, ) if _, err := svc.SaveWorkflowPlan(ctx, 402, 1, true, SavePlanInput{ Plan: plan, ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1, }); err != nil { t.Fatalf("save: %v", err) } preview, err := svc.ActivationPreview(ctx, 402, 1, true) if err != nil { t.Fatalf("preview: %v", err) } if _, err := svc.Activate(ctx, 402, 1, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }); err != nil { t.Fatalf("activate: %v", err) } view := svc.CheckDocTaskAccess(ctx, 402, 99, "research", DocAccessActionView, false) if !view.Allowed { t.Fatalf("member view on none must allow, got %+v", view) } runMember := svc.CheckDocTaskAccess(ctx, 402, 99, "research", DocAccessActionRun, false) if runMember.Allowed || runMember.Code != AccessCodeForbiddenDuty { t.Fatalf("member run on none must deny, got %+v", runMember) } runCreator := svc.CheckDocTaskAccess(ctx, 402, 1, "research", DocAccessActionRun, true) if !runCreator.Allowed { t.Fatalf("creator run on none must allow, got %+v", runCreator) } review := svc.CheckDocTaskAccess(ctx, 402, 1, "research", DocAccessActionReview, true) if review.Allowed || review.Code != AccessCodeDutyNotManaged { t.Fatalf("review on none must be DOC_DUTY_NOT_MANAGED, got %+v", review) } approve := svc.CheckDocTaskAccess(ctx, 402, 1, "research", DocAccessActionApprove, true) if approve.Allowed || approve.Code != AccessCodeDutyNotManaged { t.Fatalf("approve on none must be DOC_DUTY_NOT_MANAGED, got %+v", approve) } submit := svc.CheckDocTaskAccess(ctx, 402, 1, "research", DocAccessActionSubmit, true) if submit.Allowed || submit.Code != AccessCodeDutyNotManaged { t.Fatalf("submit on none must be DOC_DUTY_NOT_MANAGED, got %+v", submit) } } func TestCheckDocTaskAccess_FullActionMatrix(t *testing.T) { ctx := context.Background() store := NewMemoryStore() _ = SeedProjectMeta(store, 403, LifecyclePlanning, "", 1, 0) svc := NewService(store, TestResolver(), nil, nil) plan := ValidTestPlan() plan.Stages[0].DocTasks[0].Duties = DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3} if _, err := svc.SaveWorkflowPlan(ctx, 403, 9, true, SavePlanInput{ Plan: plan, ExpectedPlanRevision: 0, ExpectedSelectionVersion: 1, }); err != nil { t.Fatalf("save: %v", err) } preview, err := svc.ActivationPreview(ctx, 403, 9, true) if err != nil { t.Fatalf("preview: %v", err) } if _, err := svc.Activate(ctx, 403, 9, true, ActivateInput{ ConfirmationToken: preview.ConfirmationToken, ExpectedProjectVersion: preview.ExpectedProjectVersion, ExpectedPlanRevision: preview.ExpectedPlanRevision, }); err != nil { t.Fatalf("activate: %v", err) } assert := func(userID int64, isCreator bool, action string, wantAllow bool, wantCode string) { t.Helper() got := svc.CheckDocTaskAccess(ctx, 403, userID, "intake", action, isCreator) if got.Allowed != wantAllow || (wantCode != "" && got.Code != wantCode) { t.Fatalf("user=%d creator=%v action=%s got %+v want allow=%v code=%s", userID, isCreator, action, got, wantAllow, wantCode) } } assert(1, false, DocAccessActionRun, true, "") assert(1, false, DocAccessActionReview, false, AccessCodeForbiddenDuty) assert(1, false, DocAccessActionApprove, false, AccessCodeForbiddenDuty) assert(2, false, DocAccessActionRun, false, AccessCodeForbiddenDuty) assert(2, false, DocAccessActionSubmit, false, AccessCodeForbiddenDuty) assert(2, false, DocAccessActionReview, true, "") assert(3, false, DocAccessActionRun, false, AccessCodeForbiddenDuty) assert(3, false, DocAccessActionReview, false, AccessCodeForbiddenDuty) assert(3, false, DocAccessActionApprove, true, "") assert(9, true, DocAccessActionView, true, "") assert(9, true, DocAccessActionRun, false, AccessCodeForbiddenDuty) assert(9, true, DocAccessActionSubmit, false, AccessCodeForbiddenDuty) assert(9, true, DocAccessActionReview, false, AccessCodeForbiddenDuty) assert(9, true, DocAccessActionApprove, false, AccessCodeForbiddenDuty) }