fix(16): WR-01 name lock in GroupsService.update() also checks ldapDn

A legacy binding from plan 15-06 has ldapDn set but ldapObjectGuid stays
null until the first syncBoundGroupsForTenant() run backfills it. Until
then, GroupsService.update() let a direct PATCH rename through even
though GroupFormModal.tsx already treats the same group as AD-bound
(isImported = ldapDn != null) — the D-03 name lock was only a UI
convention for that window, not the backend invariant the 16-02 summary
claimed.
This commit is contained in:
2026-08-06 16:56:30 +02:00
parent 7464d32625
commit dd59bf592f
2 changed files with 33 additions and 5 deletions
@@ -409,6 +409,28 @@ describe('GroupsService', () => {
expect(result.name).toBe('Neu');
});
it('lehnt name fuer eine Alt-Bindung (ldapDn gesetzt, ldapObjectGuid noch null) mit BadRequestException ab (WR-01, 16-REVIEW.md)', async () => {
// Eine Gruppe aus der alten Plan-15-06-Bindung: ldapDn ist gesetzt,
// aber der neue Rekonziliations-Schritt (syncBoundGroupsForTenant,
// Legacy-Backfill) hat sie noch nicht durchlaufen — ldapObjectGuid
// ist deshalb noch null. Ohne die ldapDn-Bedingung wuerde dieser
// direkte PATCH-Aufruf durchgehen, obwohl GroupFormModal.tsx dieselbe
// Gruppe bereits als importiert sperrt (isImported = ldapDn != null).
const prisma = makeFakePrisma();
const service = new GroupsService(prisma as any);
const group = await service.create('t1', { name: 'Vertrieb' });
await (prisma as any).group.update({
where: { id: group.id },
data: { ldapDn: 'cn=Vertrieb,dc=example,dc=com', ldapObjectGuid: null },
});
await expect(
service.update('t1', group.id, { name: 'Umbenannt' }),
).rejects.toBeInstanceOf(BadRequestException);
const list = await service.listForTenant('t1');
expect(list.find((g) => g.id === group.id)!.name).toBe('Vertrieb');
});
it('setzt internalName auf einer importierten Gruppe, name bleibt unangetastet', async () => {
const prisma = makeFakePrisma();
const service = new GroupsService(prisma as any);