feat(15-03): GET /modules/catalog — beide Statusflags in einer Antwort

- ModuleAccessService.getCatalogFlags(tenantId, userId, role) liefert je
  aktivem Modul isActiveForTenant + hasAccess in einer Auflösung
- ModuleRegistryController.findCatalog (GET /modules/catalog), erreichbar
  für jeden authentifizierten Benutzer wie GET /modules (D-08)
- ADMIN/SUPER_ADMIN: hasAccess immer wahr für aktive Module (D-03)
- 4 neue Tests für getCatalogFlags
This commit is contained in:
2026-08-04 18:41:23 +02:00
parent 072fb7f62f
commit 1c32543f58
3 changed files with 114 additions and 0 deletions
@@ -215,3 +215,48 @@ describe('ModuleAccessService.findAccessibleModules — ordering (PERM-04)', ()
expect(prisma.module.findMany).not.toHaveBeenCalled();
});
});
describe('ModuleAccessService.getCatalogFlags — Marketplace-Katalog (D-08)', () => {
it('aktives und freigegebenes Modul: isActiveForTenant und hasAccess beide wahr', async () => {
const prisma = makeFakePrisma({
activations: [{ moduleId: 'mod-1' }],
directGrants: [{ moduleId: 'mod-1' }],
});
const service = new ModuleAccessService(prisma as any);
const flags = await service.getCatalogFlags('t1', 'user-1', 'USER');
expect(flags.get('mod-1')).toEqual({ isActiveForTenant: true, hasAccess: true });
});
it('aktives, aber nicht freigegebenes Modul: isActiveForTenant wahr, hasAccess falsch', async () => {
const prisma = makeFakePrisma({
activations: [{ moduleId: 'mod-1' }],
});
const service = new ModuleAccessService(prisma as any);
const flags = await service.getCatalogFlags('t1', 'user-1', 'USER');
expect(flags.get('mod-1')).toEqual({ isActiveForTenant: true, hasAccess: false });
});
it('nicht aktiviertes Modul erscheint nicht in der Flag-Map (Controller mappt fehlenden Eintrag auf beide Flags falsch)', async () => {
const prisma = makeFakePrisma({ activations: [] });
const service = new ModuleAccessService(prisma as any);
const flags = await service.getCatalogFlags('t1', 'user-1', 'USER');
expect(flags.has('mod-1')).toBe(false);
});
it('D-03: als ADMIN ist jedes aktive Modul zugänglich, auch ohne Grant', async () => {
const prisma = makeFakePrisma({
activations: [{ moduleId: 'mod-1' }],
});
const service = new ModuleAccessService(prisma as any);
const flags = await service.getCatalogFlags('t1', 'admin-1', 'ADMIN');
expect(flags.get('mod-1')).toEqual({ isActiveForTenant: true, hasAccess: true });
});
});