dealspace/lib/rbac_test.go

388 lines
9.2 KiB
Go

package lib
import (
"testing"
"time"
"github.com/google/uuid"
)
func TestCheckAccess(t *testing.T) {
db, cfg := testDB(t)
// Create owner
ownerID := uuid.New().String()
now := time.Now().UnixMilli()
owner := &User{
UserID: ownerID,
Email: "owner@test.com",
Name: "Owner",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, owner)
projectID := testProject(t, db, cfg, ownerID)
// IB admin can read
err := CheckAccessRead(db, ownerID, projectID, "")
if err != nil {
t.Errorf("IB admin should have read access: %v", err)
}
// IB admin can write
err = CheckAccessWrite(db, ownerID, projectID, "")
if err != nil {
t.Errorf("IB admin should have write access: %v", err)
}
// IB admin can delete
err = CheckAccessDelete(db, ownerID, projectID, "")
if err != nil {
t.Errorf("IB admin should have delete access: %v", err)
}
// Create seller user
sellerID := testUser(t, db, cfg, projectID, RoleSellerMember)
// Seller can read
err = CheckAccessRead(db, sellerID, projectID, "")
if err != nil {
t.Errorf("seller should have read access: %v", err)
}
// Seller can write
err = CheckAccessWrite(db, sellerID, projectID, "")
if err != nil {
t.Errorf("seller should have write access: %v", err)
}
// Create buyer user (read-only)
buyerID := testUser(t, db, cfg, projectID, RoleBuyerMember)
// Buyer can read
err = CheckAccessRead(db, buyerID, projectID, "")
if err != nil {
t.Errorf("buyer should have read access: %v", err)
}
// Buyer cannot write
err = CheckAccessWrite(db, buyerID, projectID, "")
if err != ErrAccessDenied {
t.Errorf("buyer should NOT have write access, got: %v", err)
}
}
func TestRoleHierarchy(t *testing.T) {
// Verify hierarchy levels: buyer < seller_member < seller_admin < ib_analyst < ib_member < ib_admin
expected := []struct {
role string
level int
}{
{RoleObserver, 10},
{RoleBuyerMember, 30},
{RoleBuyerAdmin, 40},
{RoleSellerMember, 50},
{RoleSellerAdmin, 70},
{RoleIBMember, 80},
{RoleIBAdmin, 100},
}
for _, tc := range expected {
level, ok := RoleHierarchy[tc.role]
if !ok {
t.Errorf("role %s not in hierarchy", tc.role)
continue
}
if level != tc.level {
t.Errorf("role %s: expected level %d, got %d", tc.role, tc.level, level)
}
}
// Verify ordering
if RoleHierarchy[RoleBuyerMember] >= RoleHierarchy[RoleSellerMember] {
t.Error("buyer should be lower than seller_member")
}
if RoleHierarchy[RoleSellerMember] >= RoleHierarchy[RoleSellerAdmin] {
t.Error("seller_member should be lower than seller_admin")
}
if RoleHierarchy[RoleSellerAdmin] >= RoleHierarchy[RoleIBMember] {
t.Error("seller_admin should be lower than ib_member")
}
if RoleHierarchy[RoleIBMember] >= RoleHierarchy[RoleIBAdmin] {
t.Error("ib_member should be lower than ib_admin")
}
}
func TestCanGrantRole(t *testing.T) {
tests := []struct {
granter string
target string
canDo bool
}{
// IB admin can grant anything
{RoleIBAdmin, RoleIBAdmin, true},
{RoleIBAdmin, RoleIBMember, true},
{RoleIBAdmin, RoleSellerAdmin, true},
{RoleIBAdmin, RoleBuyerMember, true},
// IB member can grant lower roles
{RoleIBMember, RoleIBAdmin, false},
{RoleIBMember, RoleIBMember, true},
{RoleIBMember, RoleSellerAdmin, true},
// Seller admin can grant seller and buyer roles
{RoleSellerAdmin, RoleIBMember, false},
{RoleSellerAdmin, RoleSellerAdmin, true},
{RoleSellerAdmin, RoleSellerMember, true},
{RoleSellerAdmin, RoleBuyerMember, true},
// Buyer cannot grant higher roles
{RoleBuyerAdmin, RoleSellerMember, false},
{RoleBuyerAdmin, RoleBuyerAdmin, true},
{RoleBuyerAdmin, RoleBuyerMember, true},
}
for _, tc := range tests {
result := CanGrantRole(tc.granter, tc.target)
if result != tc.canDo {
t.Errorf("CanGrantRole(%s, %s) = %v, want %v", tc.granter, tc.target, result, tc.canDo)
}
}
}
func TestGrantRevoke(t *testing.T) {
db, cfg := testDB(t)
// Create admin
adminID := uuid.New().String()
now := time.Now().UnixMilli()
admin := &User{
UserID: adminID,
Email: "admin@test.com",
Name: "Admin",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, admin)
projectID := testProject(t, db, cfg, adminID)
// Create user with no access
userID := uuid.New().String()
user := &User{
UserID: userID,
Email: "user@test.com",
Name: "User",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, user)
// Verify no access
err := CheckAccessRead(db, userID, projectID, "")
if err != ErrAccessDenied {
t.Error("user should have no access initially")
}
// Grant access
accessID := uuid.New().String()
access := &Access{
ID: accessID,
ProjectID: projectID,
UserID: userID,
Role: RoleBuyerMember,
Ops: "r",
CanGrant: false,
GrantedBy: adminID,
GrantedAt: now,
}
if err := AccessGrant(db, access); err != nil {
t.Fatalf("AccessGrant: %v", err)
}
// Verify access granted
err = CheckAccessRead(db, userID, projectID, "")
if err != nil {
t.Errorf("user should have read access after grant: %v", err)
}
// Revoke access
if err := AccessRevoke(db, accessID, adminID); err != nil {
t.Fatalf("AccessRevoke: %v", err)
}
// Verify access revoked
err = CheckAccessRead(db, userID, projectID, "")
if err != ErrAccessDenied {
t.Error("user should have no access after revoke")
}
}
func TestIsBuyerRole(t *testing.T) {
buyers := []string{RoleBuyerAdmin, RoleBuyerMember}
nonBuyers := []string{RoleIBAdmin, RoleIBMember, RoleSellerAdmin, RoleSellerMember, RoleObserver}
for _, role := range buyers {
if !IsBuyerRole(role) {
t.Errorf("%s should be buyer role", role)
}
}
for _, role := range nonBuyers {
if IsBuyerRole(role) {
t.Errorf("%s should NOT be buyer role", role)
}
}
}
func TestGetUserHighestRole(t *testing.T) {
db, cfg := testDB(t)
// Create admin and project
adminID := uuid.New().String()
now := time.Now().UnixMilli()
admin := &User{
UserID: adminID,
Email: "admin@test.com",
Name: "Admin",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, admin)
projectID := testProject(t, db, cfg, adminID)
// Admin's highest role should be ib_admin
role, err := GetUserHighestRole(db, adminID, projectID)
if err != nil {
t.Fatalf("GetUserHighestRole: %v", err)
}
if role != RoleIBAdmin {
t.Errorf("expected ib_admin, got %s", role)
}
// Create user with multiple roles
userID := uuid.New().String()
user := &User{
UserID: userID,
Email: "multi@test.com",
Name: "Multi Role User",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, user)
// Grant buyer role
AccessGrant(db, &Access{
ID: uuid.New().String(),
ProjectID: projectID,
UserID: userID,
Role: RoleBuyerMember,
Ops: "r",
GrantedBy: adminID,
GrantedAt: now,
})
// Grant seller role (higher)
AccessGrant(db, &Access{
ID: uuid.New().String(),
ProjectID: projectID,
UserID: userID,
Role: RoleSellerMember,
Ops: "rw",
GrantedBy: adminID,
GrantedAt: now,
})
// Highest should be seller_member
role, err = GetUserHighestRole(db, userID, projectID)
if err != nil {
t.Fatalf("GetUserHighestRole: %v", err)
}
if role != RoleSellerMember {
t.Errorf("expected seller_member (highest), got %s", role)
}
// User with no access
noAccessID := uuid.New().String()
noAccessUser := &User{
UserID: noAccessID,
Email: "noaccess@test.com",
Name: "No Access",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, noAccessUser)
_, err = GetUserHighestRole(db, noAccessID, projectID)
if err != ErrAccessDenied {
t.Errorf("expected ErrAccessDenied for user with no access, got %v", err)
}
}
func TestWorkstreamAccess(t *testing.T) {
db, cfg := testDB(t)
// Create admin and project
adminID := uuid.New().String()
now := time.Now().UnixMilli()
admin := &User{
UserID: adminID,
Email: "admin@test.com",
Name: "Admin",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, admin)
projectID := testProject(t, db, cfg, adminID)
// Create user with access to specific workstream only
userID := uuid.New().String()
user := &User{
UserID: userID,
Email: "ws@test.com",
Name: "Workstream User",
Password: "$2a$10$test",
Active: true,
CreatedAt: now,
UpdatedAt: now,
}
UserCreate(db, user)
workstreamID := "workstream-1"
AccessGrant(db, &Access{
ID: uuid.New().String(),
ProjectID: projectID,
WorkstreamID: workstreamID,
UserID: userID,
Role: RoleSellerMember,
Ops: "rw",
GrantedBy: adminID,
GrantedAt: now,
})
// User has access to their workstream
_, err := CheckAccess(db, userID, projectID, workstreamID, "r")
if err != nil {
t.Errorf("user should have access to their workstream: %v", err)
}
// User should NOT have access to different workstream
_, err = CheckAccess(db, userID, projectID, "different-workstream", "r")
if err != ErrAccessDenied {
t.Error("user should NOT have access to different workstream")
}
}