+16 tests (432 -> 448): permissions-catalog least-privilege guard (non-board roles carry no high-risk capability; member/associate are read-only); new invite-logic suite (exact-match creator role, non-admin grants nothing, invalid/mis-cased targets); door-logic SSRF host-spoof edges (userinfo-spoofed host rejected, case-insensitive pin match, trailing-dot FQDN fails closed) + redactDoorSecrets regex-special password and end-of-string e= value; classes pickPromotion unlimited- capacity branch + equal-timestamp tie-break.
65 lines
2.3 KiB
TypeScript
65 lines
2.3 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
PERMISSIONS,
|
|
PERMISSION_CODES,
|
|
PERMISSION_GROUPS,
|
|
DEFAULT_ROLE_PERMISSIONS,
|
|
isValidPermission,
|
|
} from '@/lib/permissions-catalog'
|
|
|
|
describe('permissions catalog', () => {
|
|
it('has unique permission codes', () => {
|
|
expect(new Set(PERMISSION_CODES).size).toBe(PERMISSION_CODES.length)
|
|
})
|
|
|
|
it('every permission has a group present in PERMISSION_GROUPS', () => {
|
|
for (const p of PERMISSIONS) {
|
|
expect(PERMISSION_GROUPS).toContain(p.group)
|
|
}
|
|
})
|
|
|
|
it('isValidPermission accepts catalog codes and rejects others', () => {
|
|
expect(isValidPermission('ops.secrets.read')).toBe(true)
|
|
expect(isValidPermission('ops.kb.write')).toBe(true)
|
|
expect(isValidPermission('not.a.permission')).toBe(false)
|
|
expect(isValidPermission('')).toBe(false)
|
|
})
|
|
|
|
it('default role grants only reference real permissions', () => {
|
|
for (const [role, perms] of Object.entries(DEFAULT_ROLE_PERMISSIONS)) {
|
|
expect(role).not.toBe('admin') // admin is implicit-all, never seeded
|
|
for (const perm of perms) {
|
|
expect(isValidPermission(perm)).toBe(true)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('does not grant admin defaults (admin is implicit and cannot be locked out)', () => {
|
|
expect(DEFAULT_ROLE_PERMISSIONS.admin).toBeUndefined()
|
|
})
|
|
|
|
// Least-privilege guard: a future catalog edit must not silently hand
|
|
// non-board roles a high-risk capability. (treasurer legitimately has
|
|
// payments.manage; member/associate should be read-only.)
|
|
it('keeps high-risk capabilities off non-board default roles', () => {
|
|
const HIGH_RISK = [
|
|
'door.manage', 'door.operate', 'members.manage', 'governance.manage',
|
|
'settings.manage', 'customize.manage', 'forms.manage',
|
|
'certifications.grant', 'ops.secrets.read', 'ops.secrets.write',
|
|
'forum.moderate',
|
|
]
|
|
for (const role of ['treasurer', 'member', 'associate'] as const) {
|
|
const perms = DEFAULT_ROLE_PERMISSIONS[role] ?? []
|
|
expect(perms.filter(p => HIGH_RISK.includes(p))).toEqual([])
|
|
}
|
|
})
|
|
|
|
it('member and associate defaults are read-only', () => {
|
|
for (const role of ['member', 'associate'] as const) {
|
|
for (const p of DEFAULT_ROLE_PERMISSIONS[role] ?? []) {
|
|
expect(p.endsWith('.read')).toBe(true)
|
|
}
|
|
}
|
|
})
|
|
})
|