bf5be08e96
角色此前能分配但不生效(viewer 也能烧租户积分)。本次让角色真正挡动作: - store.RoleRank 角色层级:owner(4)>admin(3)>member(2)>viewer/billing_admin(1), 未知/非成员=0;billing_admin 定位为「财务只读」不跑任务 - 可复用中间件 RequireTenantRole(db, minRole),抽 MemberRoleResolver 小接口便于单测 - POST /tasks 挂 ≥member 门控:唯一会烧租户积分的入口(KB 入库不计租户),挡住即够 - 桌面端 StudioView 收 readOnly:viewer 时「运行」禁用+只读提示(UX 兜底,真闸在后端) - 测试:RoleRank 纯逻辑 + 中间件 7 门控 case(owner/admin/member 放行, viewer/billing_admin/非成员 403,未登录 401) 实机验证(gateway+docker):viewer 提交 403 → 升 member 202 → 降回 viewer 403 → owner 自租户 202(solo 用户不受影响),三态可逆、角色驱动、中间件先于 handler。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
25 lines
993 B
Go
25 lines
993 B
Go
package store
|
|
|
|
import "testing"
|
|
|
|
// RoleRank 是 RequireTenantRole 门控的判定核心:阶梯顺序 + billing_admin 与 viewer 同为只读 + 未知记 0。
|
|
func TestRoleRank(t *testing.T) {
|
|
if !(RoleRank(RoleOwner) > RoleRank(RoleAdmin) &&
|
|
RoleRank(RoleAdmin) > RoleRank(RoleMember) &&
|
|
RoleRank(RoleMember) > RoleRank(RoleViewer)) {
|
|
t.Fatal("阶梯应为 owner > admin > member > viewer")
|
|
}
|
|
if RoleRank(RoleBillingAdmin) != RoleRank(RoleViewer) {
|
|
t.Errorf("billing_admin 应与 viewer 同为只读级:%d vs %d", RoleRank(RoleBillingAdmin), RoleRank(RoleViewer))
|
|
}
|
|
if RoleRank(RoleBillingAdmin) >= RoleRank(RoleMember) {
|
|
t.Error("billing_admin(财务只读) 不应达到 member 操作级,否则能提交烧积分")
|
|
}
|
|
if RoleRank(RoleViewer) < RoleRank(RoleMember) == false {
|
|
t.Error("viewer 应低于 member(提交任务门控靠此挡下)")
|
|
}
|
|
if RoleRank("") != 0 || RoleRank("nonsense") != 0 {
|
|
t.Error("未知/非成员角色应记 0,一律拦下")
|
|
}
|
|
}
|