feat(quick-261003-387): Kategorien-API - Anlegen, Sortieren, Zuordnen, Loeschen mit Verschieben, Ueberlagerung in allen Modullisten
- Verwaltungs-Endpunkte nur fuer Administratoren, statische Routen vor :key - Loeschen verschiebt alle Eintraege inkl. persoenlicher eigener Module in einer Transaktion, ohne Ziel 409 - /modules, /modules/catalog und /module-grants/matrix liefern wirksame Kategorie und Reihenfolge - eigene Module pruefen die Kategorie gegen die Organisation (400 bei unbekannter) - Zugriffsinventar nachgezogen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ModuleGrantsController } from '../groups/module-grants.controller';
|
||||
import { ModuleRegistryController } from './module-registry.controller';
|
||||
|
||||
/**
|
||||
* quick-261003-387 (E-07): alle Modullisten laufen ueber
|
||||
* `ModuleCategoriesService.applyToModules`, damit Seitenleiste, Marktplatz,
|
||||
* Kategorieseite und Freigaben-Matrix dieselbe wirksame Kategorie sehen.
|
||||
*/
|
||||
const overlay = vi.fn(async (_tenantId: string, modules: Array<{ id: string }>) =>
|
||||
modules.map((m) => ({ ...m, category: 'ueberlagert', sortOrder: 7 })),
|
||||
);
|
||||
const categories = { applyToModules: overlay } as any;
|
||||
const req = { tenantId: 't1', user: { id: 'u1', role: 'USER', tenantId: 't1' } } as any;
|
||||
|
||||
describe('ModuleRegistryController — Kategorie-Ueberlagerung', () => {
|
||||
const registry = { findAll: vi.fn(async () => [{ id: 'a', name: 'A', category: 'fleet' }]) };
|
||||
const access = {
|
||||
findAccessibleModules: vi.fn(async () => [{ id: 'a', name: 'A', category: 'fleet' }]),
|
||||
getCatalogFlags: vi.fn(
|
||||
async () => new Map([['a', { isActiveForTenant: true, hasAccess: true }]]),
|
||||
),
|
||||
};
|
||||
const controller = new ModuleRegistryController(registry as any, access as any, categories);
|
||||
|
||||
it('GET /modules liefert die wirksame Kategorie', async () => {
|
||||
expect(await controller.findAll(req)).toEqual([
|
||||
{ id: 'a', name: 'A', category: 'ueberlagert', sortOrder: 7 },
|
||||
]);
|
||||
expect(overlay).toHaveBeenCalledWith('t1', expect.any(Array));
|
||||
});
|
||||
|
||||
it('GET /modules ohne Organisationskontext bleibt unveraendert', async () => {
|
||||
overlay.mockClear();
|
||||
expect(await controller.findAll({} as any)).toEqual([
|
||||
{ id: 'a', name: 'A', category: 'fleet' },
|
||||
]);
|
||||
expect(overlay).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('GET /modules/active liefert die wirksame Kategorie', async () => {
|
||||
expect(await controller.findActive(req)).toEqual([
|
||||
{ id: 'a', name: 'A', category: 'ueberlagert', sortOrder: 7 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('GET /modules/catalog liefert die wirksame Kategorie samt Flags', async () => {
|
||||
expect(await controller.findCatalog(req)).toEqual([
|
||||
{
|
||||
id: 'a',
|
||||
name: 'A',
|
||||
category: 'ueberlagert',
|
||||
sortOrder: 7,
|
||||
isActiveForTenant: true,
|
||||
hasAccess: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ModuleGrantsController.matrix — Kategorie-Ueberlagerung', () => {
|
||||
it('ersetzt nur die Module, Gruppen und Freigaben bleiben', async () => {
|
||||
const grants = {
|
||||
getMatrix: vi.fn(async () => ({
|
||||
modules: [{ id: 'a', name: 'A', category: 'fleet' }],
|
||||
groups: [{ id: 'g' }],
|
||||
grants: [{ moduleId: 'a', groupId: 'g', level: 'USE' }],
|
||||
})),
|
||||
};
|
||||
const controller = new ModuleGrantsController(grants as any, categories);
|
||||
const out = await controller.matrix(req);
|
||||
expect(out.modules).toEqual([{ id: 'a', name: 'A', category: 'ueberlagert', sortOrder: 7 }]);
|
||||
expect(out.groups).toEqual([{ id: 'g' }]);
|
||||
expect(out.grants).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -42,8 +42,13 @@ export class ModuleRegistryController {
|
||||
* Available to any authenticated user (T-03-03: module catalog is non-sensitive).
|
||||
*/
|
||||
@Get()
|
||||
async findAll() {
|
||||
return this.moduleRegistryService.findAll();
|
||||
async findAll(@Req() req: AuthenticatedRequest) {
|
||||
const modules = await this.moduleRegistryService.findAll();
|
||||
// quick-261003-387: wirksame Kategorie + Reihenfolge der Organisation;
|
||||
// ohne Organisationskontext bleibt die Liste unveraendert.
|
||||
const tenantId = req?.tenantId ?? req?.user?.tenantId;
|
||||
if (!tenantId) return modules;
|
||||
return this.moduleCategoriesService.applyToModules(tenantId, modules);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,7 +97,9 @@ export class ModuleRegistryController {
|
||||
this.moduleAccessService.getCatalogFlags(tenantId, userId, role),
|
||||
]);
|
||||
|
||||
return modules.map((module) => ({
|
||||
// quick-261003-387: wirksame Kategorie + Reihenfolge der Organisation.
|
||||
const withCategories = await this.moduleCategoriesService.applyToModules(tenantId, modules);
|
||||
return withCategories.map((module) => ({
|
||||
...module,
|
||||
isActiveForTenant: flags.get(module.id)?.isActiveForTenant ?? false,
|
||||
hasAccess: flags.get(module.id)?.hasAccess ?? false,
|
||||
|
||||
Reference in New Issue
Block a user