Files
weichen 143ee934b8 feat(collab): free-lane chat, claim-on-view, lease ownership, notify inbox
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.
2026-09-24 11:49:43 +00:00

141 lines
5.8 KiB
Go

package projectinstance
import "testing"
func TestRequiredDutyRole(t *testing.T) {
if requiredDutyRole(DocAccessActionRun) != "" {
t.Fatal("run is member-level (no duty role)")
}
if requiredDutyRole(DocAccessActionEdit) != "" {
t.Fatal("edit is member-level (no duty role)")
}
if requiredDutyRole(DocAccessActionSubmit) != DutyRoleAuthor {
t.Fatal("submit -> author")
}
if requiredDutyRole(DocAccessActionReview) != DutyRoleReviewer {
t.Fatal("review -> reviewer")
}
if requiredDutyRole(DocAccessActionApprove) != DutyRoleApprover {
t.Fatal("approve -> approver")
}
if requiredDutyRole(DocAccessActionView) != "" {
t.Fatal("view has no duty role")
}
}
func TestClassifyAccess_FullDutyMatrix(t *testing.T) {
dt := DocTaskPlan{Enabled: true, Duties: DocDuty{AuthorUserID: 1, ReviewerUserID: 2, ApproverUserID: 3}}
creatorID := int64(9)
cases := []struct {
name string
userID int64
isCreator bool
action string
wantAllow bool
wantCode string
}{
{"author run", 1, false, DocAccessActionRun, true, ""},
{"author edit", 1, false, DocAccessActionEdit, true, ""},
{"author submit", 1, false, DocAccessActionSubmit, true, ""},
{"author review denied", 1, false, DocAccessActionReview, false, AccessCodeForbiddenDuty},
{"author approve denied", 1, false, DocAccessActionApprove, false, AccessCodeForbiddenDuty},
{"reviewer run allowed", 2, false, DocAccessActionRun, true, ""},
{"reviewer edit allowed", 2, false, DocAccessActionEdit, true, ""},
{"reviewer submit denied", 2, false, DocAccessActionSubmit, false, AccessCodeForbiddenDuty},
{"reviewer review", 2, false, DocAccessActionReview, true, ""},
{"approver run allowed", 3, false, DocAccessActionRun, true, ""},
{"approver edit allowed", 3, false, DocAccessActionEdit, true, ""},
{"approver review denied", 3, false, DocAccessActionReview, false, AccessCodeForbiddenDuty},
{"approver approve", 3, false, DocAccessActionApprove, true, ""},
{"creator view", creatorID, true, DocAccessActionView, true, ""},
{"member run allowed", creatorID, true, DocAccessActionRun, true, ""},
{"member edit allowed", creatorID, true, DocAccessActionEdit, true, ""},
{"creator submit denied", creatorID, true, DocAccessActionSubmit, false, AccessCodeForbiddenDuty},
{"creator review denied", creatorID, true, DocAccessActionReview, false, AccessCodeForbiddenDuty},
{"creator approve denied", creatorID, true, DocAccessActionApprove, false, AccessCodeForbiddenDuty},
{"reviewer can re-run after reject", 2, false, DocAccessActionRun, true, ""},
}
for _, tc := range cases {
got := classifyAccess(dt, tc.action, tc.userID, tc.isCreator, true)
if got.Allowed != tc.wantAllow || (tc.wantCode != "" && got.Code != tc.wantCode) {
t.Fatalf("%s: got %+v want allow=%v code=%s", tc.name, got, tc.wantAllow, tc.wantCode)
}
}
}
func TestClassifyAccess_NoneAndPreActivation(t *testing.T) {
none := DocTaskPlan{Enabled: true, Duties: DocDuty{}}
creatorRun := classifyAccess(none, DocAccessActionRun, 1, true, true)
if !creatorRun.Allowed {
t.Fatalf("creator run on none: %+v", creatorRun)
}
creatorEdit := classifyAccess(none, DocAccessActionEdit, 1, true, true)
if !creatorEdit.Allowed {
t.Fatalf("creator edit on none: %+v", creatorEdit)
}
memberRun := classifyAccess(none, DocAccessActionRun, 2, false, true)
if !memberRun.Allowed {
t.Fatalf("member run on none must allow: %+v", memberRun)
}
memberEdit := classifyAccess(none, DocAccessActionEdit, 2, false, true)
if !memberEdit.Allowed {
t.Fatalf("member edit on none must allow: %+v", memberEdit)
}
submit := classifyAccess(none, DocAccessActionSubmit, 1, true, true)
if submit.Allowed || submit.Code != AccessCodeDutyNotManaged {
t.Fatalf("submit on none: %+v", submit)
}
preRun := classifyAccess(none, DocAccessActionRun, 1, true, false)
if !preRun.Allowed || preRun.Code != AccessCodeNotActivated {
t.Fatalf("pre-activation run: %+v", preRun)
}
preSubmit := classifyAccess(none, DocAccessActionSubmit, 1, true, false)
if preSubmit.Allowed || preSubmit.Code != AccessCodeNotActivated {
t.Fatalf("pre-activation submit: %+v", preSubmit)
}
}
func TestClassifyAccess_PartialStillDeniesRunEdit(t *testing.T) {
partial := DocTaskPlan{Enabled: true, Duties: DocDuty{AuthorUserID: 1}}
for _, action := range []string{DocAccessActionRun, DocAccessActionEdit, DocAccessActionSubmit} {
got := classifyAccess(partial, action, 1, true, true)
if got.Allowed || got.Code != AccessCodeForbiddenDuty {
t.Fatalf("partial %s must deny: %+v", action, got)
}
}
}
func TestClassifyAccess_AuthorOnlyMatrix(t *testing.T) {
dt := DocTaskPlan{
Enabled: true,
DutyPolicy: DutyPolicyAuthorOnly,
Duties: DocDuty{AuthorUserID: 7},
}
cases := []struct {
name string
userID int64
action string
wantAllow bool
wantCode string
}{
{"author run", 7, DocAccessActionRun, true, ""},
{"author edit", 7, DocAccessActionEdit, true, ""},
{"author submit", 7, DocAccessActionSubmit, true, ""},
{"author review", 7, DocAccessActionReview, true, ""},
{"author approve", 7, DocAccessActionApprove, true, ""},
{"other run allowed", 2, DocAccessActionRun, true, ""},
{"other edit allowed", 2, DocAccessActionEdit, true, ""},
{"other submit denied", 2, DocAccessActionSubmit, false, AccessCodeForbiddenDuty},
{"other review denied", 2, DocAccessActionReview, false, AccessCodeForbiddenDuty},
{"other approve denied", 3, DocAccessActionApprove, false, AccessCodeForbiddenDuty},
{"creator cannot elevate approve", 9, DocAccessActionApprove, false, AccessCodeForbiddenDuty},
}
for _, tc := range cases {
got := classifyAccess(dt, tc.action, tc.userID, tc.userID == 9, true)
if got.Allowed != tc.wantAllow || (tc.wantCode != "" && got.Code != tc.wantCode) {
t.Fatalf("%s: got %+v want allow=%v code=%s", tc.name, got, tc.wantAllow, tc.wantCode)
}
}
}