Files
OwnCord/Server/permissions/permissions_test.go
T

521 lines
20 KiB
Go
Raw Normal View History

package permissions_test
import (
"testing"
"github.com/J3vb/OwnCord/Server/permissions"
)
// ─── Constant value tests ─────────────────────────────────────────────────────
// TestPermissionBitValues verifies every constant matches the SCHEMA.md bitfield.
func TestPermissionBitValues(t *testing.T) {
cases := []struct {
name string
got int64
expected int64
}{
{"SendMessages", permissions.SendMessages, 0x0001},
{"ReadMessages", permissions.ReadMessages, 0x0002},
{"AttachFiles", permissions.AttachFiles, 0x0020},
{"AddReactions", permissions.AddReactions, 0x0040},
{"ConnectVoice", permissions.ConnectVoice, 0x0200},
{"SpeakVoice", permissions.SpeakVoice, 0x0400},
{"UseVideo", permissions.UseVideo, 0x0800},
{"ShareScreen", permissions.ShareScreen, 0x1000},
{"ManageMessages", permissions.ManageMessages, 0x10000},
{"ManageChannels", permissions.ManageChannels, 0x20000},
{"KickMembers", permissions.KickMembers, 0x40000},
{"BanMembers", permissions.BanMembers, 0x80000},
{"MuteMembers", permissions.MuteMembers, 0x100000},
{"ManageRoles", permissions.ManageRoles, 0x1000000},
{"ManageServer", permissions.ManageServer, 0x2000000},
{"ManageInvites", permissions.ManageInvites, 0x4000000},
{"ViewAuditLog", permissions.ViewAuditLog, 0x8000000},
{"Administrator", permissions.Administrator, 0x40000000},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.got != tc.expected {
t.Errorf("%s: got 0x%X, want 0x%X", tc.name, tc.got, tc.expected)
}
})
}
}
// TestRoleIDConstants verifies the predefined role IDs match SCHEMA.md defaults.
func TestRoleIDConstants(t *testing.T) {
cases := []struct {
name string
got int64
expected int64
}{
{"OwnerRoleID", permissions.OwnerRoleID, 1},
{"AdminRoleID", permissions.AdminRoleID, 2},
{"ModeratorRoleID", permissions.ModeratorRoleID, 3},
{"MemberRoleID", permissions.MemberRoleID, 4},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.got != tc.expected {
t.Errorf("%s: got %d, want %d", tc.name, tc.got, tc.expected)
}
})
}
}
// TestOwnerRolePosition verifies the owner position sentinel value.
func TestOwnerRolePosition(t *testing.T) {
if permissions.OwnerRolePosition != 100 {
t.Errorf("OwnerRolePosition: got %d, want 100", permissions.OwnerRolePosition)
}
}
// ─── HasPerm tests ────────────────────────────────────────────────────────────
func TestHasPerm_MatchingBitReturnsTrue(t *testing.T) {
rolePerms := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
if !permissions.HasPerm(rolePerms, permissions.SendMessages) {
t.Error("expected HasPerm to return true when bit is set")
}
}
func TestHasPerm_MissingBitReturnsFalse(t *testing.T) {
rolePerms := permissions.ReadMessages | permissions.ConnectVoice
if permissions.HasPerm(rolePerms, permissions.SendMessages) {
t.Error("expected HasPerm to return false when bit is not set")
}
}
func TestHasPerm_ZeroPermsReturnsFalse(t *testing.T) {
if permissions.HasPerm(0, permissions.SendMessages) {
t.Error("expected HasPerm(0, ...) to return false")
}
}
func TestHasPerm_ZeroRequiredReturnsFalse(t *testing.T) {
// Requiring perm 0 should never match — 0 is not a valid permission bit.
if permissions.HasPerm(permissions.Administrator, 0) {
t.Error("expected HasPerm(..., 0) to return false for zero required perm")
}
}
func TestHasPerm_MultipleBitsSetOnlyChecksRequired(t *testing.T) {
// rolePerms has many bits; we ask about one that is present.
rolePerms := permissions.SendMessages | permissions.ManageMessages | permissions.BanMembers
if !permissions.HasPerm(rolePerms, permissions.ManageMessages) {
t.Error("expected HasPerm to find ManageMessages in combined bitfield")
}
}
func TestHasPerm_AllBitsSet(t *testing.T) {
// 0x7FFFFFFF (Owner default) must satisfy every individual permission.
allPerms := int64(0x7FFFFFFF)
perms := []int64{
permissions.SendMessages, permissions.ReadMessages, permissions.AttachFiles,
2026-04-02 17:00:27 +02:00
permissions.AddReactions, permissions.ConnectVoice,
permissions.SpeakVoice, permissions.UseVideo, permissions.ShareScreen,
permissions.ManageMessages, permissions.ManageChannels, permissions.KickMembers,
permissions.BanMembers, permissions.MuteMembers, permissions.ManageRoles,
permissions.ManageServer, permissions.ManageInvites, permissions.ViewAuditLog,
permissions.Administrator,
}
for _, p := range perms {
if !permissions.HasPerm(allPerms, p) {
t.Errorf("expected all-bits owner to have perm 0x%X", p)
}
}
}
// ─── HasAdmin tests ───────────────────────────────────────────────────────────
func TestHasAdmin_AdministratorBitSet(t *testing.T) {
if !permissions.HasAdmin(permissions.Administrator) {
t.Error("expected HasAdmin to return true when Administrator bit is set")
}
}
func TestHasAdmin_AdministratorBitWithOthers(t *testing.T) {
combined := permissions.SendMessages | permissions.Administrator | permissions.BanMembers
if !permissions.HasAdmin(combined) {
t.Error("expected HasAdmin to return true with Administrator bit among others")
}
}
func TestHasAdmin_NoAdministratorBit(t *testing.T) {
if permissions.HasAdmin(permissions.SendMessages | permissions.BanMembers) {
t.Error("expected HasAdmin to return false without Administrator bit")
}
}
func TestHasAdmin_ZeroPerms(t *testing.T) {
if permissions.HasAdmin(0) {
t.Error("expected HasAdmin(0) to return false")
}
}
func TestHasAdmin_AdminRolePermsMissingBit(t *testing.T) {
// Admin role default is 0x3FFFFFFF — bit 30 (Administrator) is NOT set.
adminDefault := int64(0x3FFFFFFF)
if permissions.HasAdmin(adminDefault) {
t.Error("expected HasAdmin to return false for Admin role (0x3FFFFFFF lacks bit 30)")
}
}
func TestHasAdmin_OwnerRolePermsHasBit(t *testing.T) {
// Owner role default is 0x7FFFFFFF — bit 30 IS set.
ownerDefault := int64(0x7FFFFFFF)
if !permissions.HasAdmin(ownerDefault) {
t.Error("expected HasAdmin to return true for Owner role (0x7FFFFFFF has bit 30)")
}
}
// ─── HasServerPerm tests ──────────────────────────────────────────────────────
// TestHasServerPerm locks the contract api.RequirePermission inherits: admin
// bypass, and ALL-of semantics for multi-bit masks (a raw `perms&mask != 0`
// test would make them any-of).
func TestHasServerPerm(t *testing.T) {
tests := []struct {
name string
rolePerms int64
perm int64
want bool
}{
{"admin bypasses a bit it lacks", permissions.Administrator, permissions.ManageInvites, true},
// Deliberately UNLIKE HasPerm(Administrator, 0) == false: the admin
// bypass short-circuits before the zero-mask guard.
{"admin with zero mask", permissions.Administrator, 0, true},
{"exact bit held", permissions.ManageInvites, permissions.ManageInvites, true},
{"bit not held", permissions.SendMessages, permissions.ManageInvites, false},
{"non-admin with zero mask", permissions.SendMessages, 0, false},
{"multi-bit mask partially held", permissions.SendMessages, permissions.SendMessages | permissions.ManageRoles, false},
{"multi-bit mask fully held", permissions.SendMessages | permissions.ManageRoles, permissions.SendMessages | permissions.ManageRoles, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := permissions.HasServerPerm(tt.rolePerms, tt.perm); got != tt.want {
t.Errorf("HasServerPerm() = %v, want %v", got, tt.want)
}
})
}
}
// ─── EffectivePerms tests ─────────────────────────────────────────────────────
// EffectivePerms(rolePerm, allow, deny) = (rolePerm & ^deny) | allow
func TestEffectivePerms_NoOverrides(t *testing.T) {
base := permissions.SendMessages | permissions.ReadMessages
got := permissions.EffectivePerms(base, 0, 0)
if got != base {
t.Errorf("EffectivePerms with no overrides: got 0x%X, want 0x%X", got, base)
}
}
func TestEffectivePerms_AllowAddsPermission(t *testing.T) {
base := permissions.ReadMessages
allow := permissions.SendMessages
got := permissions.EffectivePerms(base, allow, 0)
want := permissions.ReadMessages | permissions.SendMessages
if got != want {
t.Errorf("EffectivePerms allow: got 0x%X, want 0x%X", got, want)
}
}
func TestEffectivePerms_DenyRemovesPermission(t *testing.T) {
base := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
deny := permissions.ConnectVoice
got := permissions.EffectivePerms(base, 0, deny)
want := permissions.SendMessages | permissions.ReadMessages
if got != want {
t.Errorf("EffectivePerms deny: got 0x%X, want 0x%X", got, want)
}
}
func TestEffectivePerms_AllowAndDenyTogether(t *testing.T) {
// deny removes ConnectVoice; allow grants ManageMessages.
base := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
allow := permissions.ManageMessages
deny := permissions.ConnectVoice
got := permissions.EffectivePerms(base, allow, deny)
want := permissions.SendMessages | permissions.ReadMessages | permissions.ManageMessages
if got != want {
t.Errorf("EffectivePerms allow+deny: got 0x%X, want 0x%X", got, want)
}
}
func TestEffectivePerms_AllowOverridesDeny(t *testing.T) {
// When both allow and deny target the same bit, allow wins
// because the formula applies deny first, then allow.
base := permissions.SendMessages
allow := permissions.ConnectVoice
deny := permissions.ConnectVoice
got := permissions.EffectivePerms(base, allow, deny)
// deny strips ConnectVoice, then allow adds it back.
want := permissions.SendMessages | permissions.ConnectVoice
if got != want {
t.Errorf("EffectivePerms allow overrides deny: got 0x%X, want 0x%X", got, want)
}
}
func TestEffectivePerms_ZeroBase(t *testing.T) {
allow := permissions.SendMessages | permissions.ReadMessages
got := permissions.EffectivePerms(0, allow, 0)
if got != allow {
t.Errorf("EffectivePerms zero base: got 0x%X, want 0x%X", got, allow)
}
}
func TestEffectivePerms_ZeroAll(t *testing.T) {
got := permissions.EffectivePerms(0, 0, 0)
if got != 0 {
t.Errorf("EffectivePerms all zero: got 0x%X, want 0", got)
}
}
func TestEffectivePerms_DenyAllGrantNone(t *testing.T) {
base := int64(0x7FFFFFFF)
deny := int64(0x7FFFFFFF)
got := permissions.EffectivePerms(base, 0, deny)
if got != 0 {
t.Errorf("EffectivePerms deny all: got 0x%X, want 0", got)
}
}
// ─── HasPerm — combined multi-bit checks ────────────────────────────────────
func TestHasPerm_RequiresAllBitsPresent(t *testing.T) {
// Require both SendMessages AND ManageMessages; user only has SendMessages.
rolePerms := permissions.SendMessages | permissions.ReadMessages
combined := permissions.SendMessages | permissions.ManageMessages
if permissions.HasPerm(rolePerms, combined) {
t.Error("should fail when only some of the required bits are present")
}
}
func TestHasPerm_CombinedBitsAllPresent(t *testing.T) {
rolePerms := permissions.SendMessages | permissions.ReadMessages | permissions.ManageMessages
combined := permissions.SendMessages | permissions.ManageMessages
if !permissions.HasPerm(rolePerms, combined) {
t.Error("should succeed when all required combined bits are present")
}
}
// ─── EffectivePerms — channel override edge cases ───────────────────────────
func TestEffectivePerms_DenyAllThenAllowOne(t *testing.T) {
base := permissions.SendMessages | permissions.ReadMessages | permissions.ConnectVoice
deny := int64(0x7FFFFFFF) // deny everything
allow := permissions.ReadMessages // re-allow just ReadMessages
eff := permissions.EffectivePerms(base, allow, deny)
if eff != permissions.ReadMessages {
t.Errorf("deny-all + allow-one: got 0x%X, want 0x%X", eff, permissions.ReadMessages)
}
}
func TestEffectivePerms_MultipleDenyMultipleAllow(t *testing.T) {
base := permissions.SendMessages | permissions.ReadMessages | permissions.AttachFiles | permissions.ConnectVoice
deny := permissions.SendMessages | permissions.ConnectVoice
allow := permissions.ManageChannels | permissions.ManageMessages
eff := permissions.EffectivePerms(base, allow, deny)
// Should keep: ReadMessages, AttachFiles (not denied)
// Should lose: SendMessages, ConnectVoice (denied)
// Should gain: ManageChannels, ManageMessages (allowed)
want := permissions.ReadMessages | permissions.AttachFiles | permissions.ManageChannels | permissions.ManageMessages
if eff != want {
t.Errorf("multi deny+allow: got 0x%X, want 0x%X", eff, want)
}
}
// ─── Role hierarchy simulation ──────────────────────────────────────────────
func TestRoleHierarchy_OwnerHasMorePermsThanAdmin(t *testing.T) {
ownerPerms := int64(0x7FFFFFFF) // Owner default
adminPerms := int64(0x3FFFFFFF) // Admin default (no Administrator bit)
if !permissions.HasAdmin(ownerPerms) {
t.Error("owner should be admin")
}
if permissions.HasAdmin(adminPerms) {
t.Error("admin role should NOT have Administrator bit")
}
// Owner can ManageServer via admin bypass.
// Admin can ManageServer via direct bit.
if !permissions.HasPerm(adminPerms, permissions.ManageServer) {
t.Error("admin should have ManageServer bit directly")
}
}
func TestRoleHierarchy_MemberLacksModPerms(t *testing.T) {
memberPerms := int64(1635) // Default member permissions from schema
modPerms := []int64{
permissions.ManageMessages,
permissions.ManageChannels,
permissions.KickMembers,
permissions.BanMembers,
permissions.ManageRoles,
permissions.ManageServer,
permissions.Administrator,
}
for _, p := range modPerms {
if permissions.HasPerm(memberPerms, p) {
t.Errorf("member (0x%X) should not have permission 0x%X", memberPerms, p)
}
}
}
func TestRoleHierarchy_MemberHasBasicPerms(t *testing.T) {
memberPerms := int64(1635) //nolint:gocritic // documenting the bitmask composition, not commented-out code
basicPerms := []struct {
name string
perm int64
}{
{"SendMessages", permissions.SendMessages},
{"ReadMessages", permissions.ReadMessages},
}
for _, tc := range basicPerms {
t.Run(tc.name, func(t *testing.T) {
if !permissions.HasPerm(memberPerms, tc.perm) {
t.Errorf("member should have %s", tc.name)
}
})
}
}
// ─── Permission bits are unique powers of 2 ─────────────────────────────────
func TestPermissionBits_AreDistinctPowersOfTwo(t *testing.T) {
bits := []int64{
permissions.SendMessages, permissions.ReadMessages, permissions.AttachFiles,
2026-04-02 17:00:27 +02:00
permissions.AddReactions, permissions.ConnectVoice,
permissions.SpeakVoice, permissions.UseVideo, permissions.ShareScreen,
permissions.ManageMessages, permissions.ManageChannels, permissions.KickMembers,
permissions.BanMembers, permissions.MuteMembers, permissions.ManageRoles,
permissions.ManageServer, permissions.ManageInvites, permissions.ViewAuditLog,
permissions.Administrator,
}
seen := make(map[int64]bool)
for _, b := range bits {
if b&(b-1) != 0 {
t.Errorf("permission 0x%X is not a power of 2", b)
}
if seen[b] {
t.Errorf("duplicate permission bit: 0x%X", b)
}
seen[b] = true
}
}
2026-08-01 22:06:14 +02:00
// ─── HasAnyPerm / AdminPerimeter / Name tests ────────────────────────────────
func TestHasAnyPerm_AnyOfSemantics(t *testing.T) {
rolePerms := permissions.KickMembers | permissions.SendMessages
// One matching bit is enough, unlike HasPerm's ALL-of.
if !permissions.HasAnyPerm(rolePerms, permissions.KickMembers|permissions.ManageServer) {
t.Error("expected HasAnyPerm to match on KickMembers")
}
if permissions.HasPerm(rolePerms, permissions.KickMembers|permissions.ManageServer) {
t.Error("HasPerm must stay ALL-of")
}
if permissions.HasAnyPerm(rolePerms, permissions.ManageServer|permissions.ViewAuditLog) {
t.Error("expected HasAnyPerm to reject a disjoint mask")
}
if permissions.HasAnyPerm(rolePerms, 0) {
t.Error("a zero mask must never be satisfied")
}
if permissions.HasAnyPerm(0, permissions.AdminPerimeter) {
t.Error("a zero role must never satisfy the perimeter")
}
}
func TestAdminPerimeter_Membership(t *testing.T) {
admitted := []int64{
permissions.Administrator, permissions.ManageChannels, permissions.ManageRoles,
permissions.ManageServer, permissions.ViewAuditLog, permissions.KickMembers,
permissions.BanMembers, permissions.MuteMembers,
}
for _, p := range admitted {
if !permissions.HasAnyPerm(p, permissions.AdminPerimeter) {
t.Errorf("%s should admit to the admin perimeter", permissions.Name(p))
}
}
// Bits with no admin-panel route must not open the perimeter.
refused := []int64{
permissions.SendMessages, permissions.ReadMessages, permissions.ManageMessages,
permissions.ManageInvites, permissions.ConnectVoice,
}
for _, p := range refused {
if permissions.HasAnyPerm(p, permissions.AdminPerimeter) {
t.Errorf("%s must not admit to the admin perimeter", permissions.Name(p))
}
}
// The seeded Moderator role (migration 001) gets in.
if !permissions.HasAnyPerm(0x000FFFFF, permissions.AdminPerimeter) {
t.Error("the seeded Moderator mask should admit to the admin perimeter")
}
// The seeded Member role does not.
if permissions.HasAnyPerm(1635, permissions.AdminPerimeter) {
t.Error("the seeded Member mask must not admit to the admin perimeter")
}
}
func TestName_KnownAndUnknownBits(t *testing.T) {
if got := permissions.Name(permissions.ManageChannels); got != "MANAGE_CHANNELS" {
t.Errorf("Name(ManageChannels) = %q", got)
}
if got := permissions.Name(permissions.ViewAuditLog); got != "VIEW_AUDIT_LOG" {
t.Errorf("Name(ViewAuditLog) = %q", got)
}
// Zero, multi-bit and undefined values have no single name.
for _, bit := range []int64{0, permissions.KickMembers | permissions.BanMembers, 0x4} {
if got := permissions.Name(bit); got != "UNKNOWN" {
t.Errorf("Name(0x%X) = %q, want UNKNOWN", bit, got)
}
}
}
// TestMentionEveryone_BitIsFreeAndNamed locks phase 3's new bit: 21 was
// unassigned, it is part of AllPerms so an externally supplied mask keeps it,
// and it stays out of the admin perimeter (mentioning is not moderation).
func TestMentionEveryone_BitIsFreeAndNamed(t *testing.T) {
if permissions.MentionEveryone != 0x200000 {
t.Errorf("MentionEveryone = 0x%X, want 0x200000 (bit 21)", permissions.MentionEveryone)
}
for _, other := range []int64{
permissions.SendMessages, permissions.ReadMessages, permissions.AttachFiles,
permissions.AddReactions, permissions.ConnectVoice, permissions.SpeakVoice,
permissions.UseVideo, permissions.ShareScreen, permissions.ManageMessages,
permissions.ManageChannels, permissions.KickMembers, permissions.BanMembers,
permissions.MuteMembers, permissions.ManageRoles, permissions.ManageServer,
permissions.ManageInvites, permissions.ViewAuditLog, permissions.Administrator,
} {
if other&permissions.MentionEveryone != 0 {
t.Errorf("bit 21 collides with 0x%X", other)
}
}
if permissions.AllPerms&permissions.MentionEveryone == 0 {
t.Error("AllPerms must include MentionEveryone")
}
if permissions.AdminPerimeter&permissions.MentionEveryone != 0 {
t.Error("MentionEveryone must not admit to the admin perimeter")
}
if got := permissions.Name(permissions.MentionEveryone); got != "MENTION_EVERYONE" {
t.Errorf("Name(MentionEveryone) = %q", got)
}
}