Open member free-lane for chat initiation; auto-claim on author view; harden lease persist/ownership with readable DOC_BUSY; add notification inbox and supplement-impact HTTP decision APIs wired to composition root.
165 lines
6.8 KiB
Go
165 lines
6.8 KiB
Go
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_NonDutyUserCanRunButNotSubmit(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,
|
|
})
|
|
|
|
run := svc.CheckDocTaskAccess(ctx, 401, 99, "intake", DocAccessActionRun, false)
|
|
if !run.Allowed {
|
|
t.Fatalf("non-duty member must run, got %+v", run)
|
|
}
|
|
edit := svc.CheckDocTaskAccess(ctx, 401, 99, "intake", DocAccessActionEdit, false)
|
|
if !edit.Allowed {
|
|
t.Fatalf("non-duty member must edit, got %+v", edit)
|
|
}
|
|
submit := svc.CheckDocTaskAccess(ctx, 401, 99, "intake", DocAccessActionSubmit, false)
|
|
if submit.Allowed || submit.Code != AccessCodeForbiddenDuty {
|
|
t.Fatalf("non-duty member must not submit, got %+v", submit)
|
|
}
|
|
}
|
|
|
|
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 {
|
|
t.Fatalf("member run on none must allow, 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, true, "")
|
|
assert(2, false, DocAccessActionSubmit, false, AccessCodeForbiddenDuty)
|
|
assert(2, false, DocAccessActionReview, true, "")
|
|
assert(3, false, DocAccessActionRun, true, "")
|
|
assert(3, false, DocAccessActionReview, false, AccessCodeForbiddenDuty)
|
|
assert(3, false, DocAccessActionApprove, true, "")
|
|
assert(9, true, DocAccessActionView, true, "")
|
|
assert(9, true, DocAccessActionRun, true, "")
|
|
assert(9, true, DocAccessActionEdit, true, "")
|
|
assert(9, true, DocAccessActionSubmit, false, AccessCodeForbiddenDuty)
|
|
assert(9, true, DocAccessActionReview, false, AccessCodeForbiddenDuty)
|
|
assert(9, true, DocAccessActionApprove, false, AccessCodeForbiddenDuty)
|
|
}
|