feat(module-grants): Proxmox, Handelsware und DKV mit Freigabestufe Verwalten
- Proxmox-Schreibwege und Handelsware-Einstellungen auf @ModuleManage umgestellt - DKV-Fleet: ganze Klasse Verwalten-Stufe, Benutzen allein bleibt ohne Zugriff - Metadaten-Test belegt umgestellte und bewusst Administratoren vorbehaltene Handler - Webseiten (Proxmox, Handelsware, Widget) folgen canManage, DKV-Zugriffsseite erklärt die Stufe Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import 'reflect-metadata';
|
||||
import { GUARDS_METADATA } from '@nestjs/common/constants';
|
||||
import { Role } from '@prisma/client';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ROLES_KEY } from '../auth/decorators/roles.decorator';
|
||||
import { DkvController } from '../dkv/dkv.controller';
|
||||
import { ModuleGrantsController } from '../groups/module-grants.controller';
|
||||
import { HandelswareDatevController } from '../handelsware-datev/handelsware-datev.controller';
|
||||
import { KantineDatevController } from '../kantine-datev/kantine-datev.controller';
|
||||
import { ProxmoxController } from '../proxmox/proxmox.controller';
|
||||
import { TendersController } from '../tenders/tenders.controller';
|
||||
import { ModuleRegistryController } from './module-registry.controller';
|
||||
import { MODULE_MANAGE_KEY, MODULE_SLUG_KEY, ModuleGuard } from './module.guard';
|
||||
|
||||
/**
|
||||
* Metadaten-Beweis für die Freigabestufe Verwalten (261002-icv, L-04/L-09):
|
||||
* welche Handler auf `@ModuleManage` umgestellt wurden und welche bewusst
|
||||
* Administratoren vorbehalten bleiben (T-icv-01/06/07/08). Reine Metadaten —
|
||||
* kein Nest-Start, keine Datenbank.
|
||||
*/
|
||||
|
||||
const ADMIN_ONLY = [Role.ADMIN, Role.SUPER_ADMIN];
|
||||
|
||||
function methodsOf(controller: { prototype: object }): string[] {
|
||||
return Object.getOwnPropertyNames(controller.prototype).filter(
|
||||
(name) => name !== 'constructor' && typeof (controller.prototype as any)[name] === 'function',
|
||||
);
|
||||
}
|
||||
|
||||
function handler(controller: { prototype: object }, name: string) {
|
||||
return (controller.prototype as any)[name];
|
||||
}
|
||||
|
||||
function expectManage(controller: { prototype: object }, name: string, slug: string) {
|
||||
const fn = handler(controller, name);
|
||||
expect(Reflect.getMetadata(MODULE_MANAGE_KEY, fn), `${name} MODULE_MANAGE_KEY`).toBe(true);
|
||||
expect(Reflect.getMetadata(MODULE_SLUG_KEY, fn), `${name} MODULE_SLUG_KEY`).toBe(slug);
|
||||
expect(Reflect.getMetadata(GUARDS_METADATA, fn), `${name} guards`).toContain(ModuleGuard);
|
||||
expect(Reflect.getMetadata(ROLES_KEY, fn), `${name} ROLES_KEY`).toBeUndefined();
|
||||
}
|
||||
|
||||
function expectAdminOnly(controller: { prototype: object }, name: string) {
|
||||
const fn = handler(controller, name);
|
||||
expect(Reflect.getMetadata(ROLES_KEY, fn), `${name} ROLES_KEY`).toEqual(ADMIN_ONLY);
|
||||
expect(Reflect.getMetadata(MODULE_MANAGE_KEY, fn), `${name} MODULE_MANAGE_KEY`).toBeUndefined();
|
||||
}
|
||||
|
||||
describe('Umgestellte Handler (Verwalten)', () => {
|
||||
it('DkvController: ganze Klasse Verwalten, kein Handler trägt @Roles', () => {
|
||||
expect(Reflect.getMetadata(MODULE_SLUG_KEY, DkvController)).toBe('dkv-fleet');
|
||||
expect(Reflect.getMetadata(MODULE_MANAGE_KEY, DkvController)).toBe(true);
|
||||
expect(Reflect.getMetadata(GUARDS_METADATA, DkvController)).toContain(ModuleGuard);
|
||||
const names = methodsOf(DkvController).filter((n) => Reflect.hasMetadata('path', handler(DkvController, n)));
|
||||
expect(names.length).toBe(11);
|
||||
for (const name of names) {
|
||||
expect(Reflect.getMetadata(ROLES_KEY, handler(DkvController, name)), name).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it.each(['create', 'update', 'remove', 'poll', 'test', 'testDraft'])(
|
||||
'ProxmoxController.%s verlangt Verwalten für proxmox',
|
||||
(name) => {
|
||||
expectManage(ProxmoxController, name, 'proxmox');
|
||||
},
|
||||
);
|
||||
|
||||
it('ProxmoxController.list bleibt auf Benutzen-Ebene', () => {
|
||||
const fn = handler(ProxmoxController, 'list');
|
||||
expect(Reflect.getMetadata(MODULE_MANAGE_KEY, fn)).toBeUndefined();
|
||||
expect(Reflect.getMetadata(ROLES_KEY, fn)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('KantineDatevController.saveSettings und HandelswareDatevController.saveSettings verlangen Verwalten', () => {
|
||||
expectManage(KantineDatevController, 'saveSettings', 'kantine-datev');
|
||||
expectManage(HandelswareDatevController, 'saveSettings', 'handelsware-datev');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bewusst nur für Administratoren (T-icv-01, T-icv-07)', () => {
|
||||
it.each(['getSourceConfig', 'saveSourceConfig', 'pollNow'])(
|
||||
'TendersController.%s bleibt @Roles(ADMIN, SUPER_ADMIN)',
|
||||
(name) => {
|
||||
expectAdminOnly(TendersController, name);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(['matrix', 'userAccess', 'create', 'remove'])(
|
||||
'ModuleGrantsController.%s bleibt @Roles(ADMIN, SUPER_ADMIN)',
|
||||
(name) => {
|
||||
expectAdminOnly(ModuleGrantsController, name);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(['activate', 'deactivate'])(
|
||||
'ModuleRegistryController.%s bleibt @Roles(ADMIN, SUPER_ADMIN)',
|
||||
(name) => {
|
||||
expectAdminOnly(ModuleRegistryController, name);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user