// @vitest-environment node import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' import { createPrismaClient, loadEnvFromRepoRoot, type PrismaClient } from '@afis/db' loadEnvFromRepoRoot() const url = process.env.DIRECT_DATABASE_URL ?? process.env.DATABASE_URL const suite = url ? describe : describe.skip let db: PrismaClient const marke = `rechte-${Date.now()}` const rollen = { afis: `${marke}-rolle-afis`, gast: `${marke}-rolle-gast`, agent: `${marke}-rolle-agent`, leitung: `${marke}-rolle-leitung`, zugangHoch: `${marke}-rolle-zugang-hoch`, zugangNiedrig: `${marke}-rolle-zugang-niedrig`, } const nutzer = { mitglied: `${marke}-mitglied`, gast: `${marke}-gast`, notfallAdmin: `${marke}-notfall`, } suite('Rechteauflösung', () => { beforeAll(async () => { db = createPrismaClient(url) await db.roleMapping.createMany({ data: [ { discordRoleId: rollen.afis, label: 'AfiS', category: 'MEMBERSHIP', grantsMembership: true }, { discordRoleId: rollen.gast, label: 'Gast', category: 'UNSORTED' }, { discordRoleId: rollen.agent, label: 'Agent', category: 'RANK', permissions: ['case.read', 'entry.create'], }, { discordRoleId: rollen.leitung, label: 'Section Commander', category: 'RANK', permissions: ['case.read', 'case.close', 'export.approve'], }, { discordRoleId: rollen.zugangNiedrig, label: 'Restricted Access', category: 'CLEARANCE', clearanceLevel: 1, }, { discordRoleId: rollen.zugangHoch, label: 'Critical Operations', category: 'CLEARANCE', clearanceLevel: 4, }, ], }) for (const id of Object.values(nutzer)) { await db.user.create({ data: { id, username: id, displayName: id } }) } await db.userRole.createMany({ data: [ { userId: nutzer.mitglied, discordRoleId: rollen.afis }, { userId: nutzer.mitglied, discordRoleId: rollen.agent }, { userId: nutzer.mitglied, discordRoleId: rollen.leitung }, { userId: nutzer.mitglied, discordRoleId: rollen.zugangNiedrig }, { userId: nutzer.mitglied, discordRoleId: rollen.zugangHoch }, { userId: nutzer.gast, discordRoleId: rollen.gast }, ], }) }) afterAll(async () => { await db.$disconnect() }) beforeEach(async () => { const { invalidateAll } = await import('@/lib/auth/effective') invalidateAll() }) it('nimmt die höchste Stufe und die Vereinigung der Rechte', async () => { const { resolvePermissions } = await import('@/lib/auth/effective') const p = await resolvePermissions(nutzer.mitglied) expect(p.clearanceLevel).toBe(4) expect([...p.permissions].sort()).toEqual([ 'case.close', 'case.read', 'entry.create', 'export.approve', ]) expect(p.hasMembership).toBe(true) expect(p.isAdmin).toBe(false) }) it('erkennt einen Gast ohne AfiS-Rolle als nicht zugangsberechtigt', async () => { const { resolvePermissions } = await import('@/lib/auth/effective') const p = await resolvePermissions(nutzer.gast) expect(p.hasMembership).toBe(false) expect(p.clearanceLevel).toBe(0) expect(p.permissions.size).toBe(0) }) it('gibt dem Notfallzugang Stufe 5 und alle Rechte ohne Rollen', async () => { process.env.BOOTSTRAP_ADMIN_IDS = nutzer.notfallAdmin const { resetEnvCache } = await import('@/lib/env') resetEnvCache() const { resolvePermissions, invalidateAll } = await import('@/lib/auth/effective') invalidateAll() const p = await resolvePermissions(nutzer.notfallAdmin) expect(p.isAdmin).toBe(true) expect(p.clearanceLevel).toBe(5) expect(p.hasMembership).toBe(true) expect(p.permissions.has('roles.manage')).toBe(true) expect(p.permissions.has('entry.retract')).toBe(true) }) it('verwirft den Cache, wenn sich das Rollen-Mapping ändert', async () => { const { resolvePermissions, invalidateAll } = await import('@/lib/auth/effective') expect((await resolvePermissions(nutzer.mitglied)).clearanceLevel).toBe(4) await db.roleMapping.update({ where: { discordRoleId: rollen.zugangHoch }, data: { clearanceLevel: 2 }, }) // Ohne Verwerfen liefert der Cache weiterhin die alte Stufe. expect((await resolvePermissions(nutzer.mitglied)).clearanceLevel).toBe(4) invalidateAll() expect((await resolvePermissions(nutzer.mitglied)).clearanceLevel).toBe(2) }) })