fix(16): WR-02 discriminate P2002 target in group rename branch
syncBoundGroupsForTenant()'s rename write updates name and ldapDn in one call, so a P2002 there can come from either @@unique([tenantId, name]) or @@unique([tenantId, ldapDn]). The catch previously reported every P2002 as a name collision unconditionally; it now inspects err.meta.target the same way importGroupsByDn() already does for its own create() call, so a non-name unique violation is no longer mislabelled and sent the admin down the wrong troubleshooting path.
This commit is contained in:
@@ -1260,7 +1260,7 @@ describe('LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verz
|
||||
expect(groups[0].ldapDn).toBe('cn=Vertrieb,dc=example,dc=com');
|
||||
});
|
||||
|
||||
it('a rename colliding with an existing local name (P2002) is reported and the group is left unchanged, run continues', async () => {
|
||||
it('a rename colliding with an existing local name (P2002 on the name unique index) is reported and the group is left unchanged, run continues', async () => {
|
||||
groups = [
|
||||
{
|
||||
id: 'g1',
|
||||
@@ -1283,6 +1283,10 @@ describe('LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verz
|
||||
if (args.where.id === 'g1') {
|
||||
const err: any = new Error('Unique constraint');
|
||||
err.code = 'P2002';
|
||||
// WR-02 (16-REVIEW.md): a realistic @@unique([tenantId, name])
|
||||
// violation carries this target — the fix must inspect it rather
|
||||
// than assuming every P2002 on this update() is a name collision.
|
||||
err.meta = { target: ['tenantId', 'name'] };
|
||||
return Promise.reject(err);
|
||||
}
|
||||
const g = groups.find((x) => x.id === args.where.id);
|
||||
@@ -1322,6 +1326,42 @@ describe('LdapService.syncBoundGroupsForTenant — Rekonziliation gegen das Verz
|
||||
expect(prisma.group.update).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('a rename P2002 whose meta.target does NOT include name (e.g. the ldapDn unique index) is reported as a binding collision, not mislabelled as a name collision (WR-02, 16-REVIEW.md)', async () => {
|
||||
groups = [
|
||||
{
|
||||
id: 'g1',
|
||||
tenantId: 't1',
|
||||
name: 'Sales',
|
||||
ldapDn: 'cn=Sales,dc=example,dc=com',
|
||||
ldapObjectGuid: guidHex,
|
||||
isDefault: false,
|
||||
},
|
||||
];
|
||||
prisma.group.update = vi.fn((args: any) => {
|
||||
const err: any = new Error('Unique constraint');
|
||||
err.code = 'P2002';
|
||||
// A P2002 on the OTHER unique index this update() can hit —
|
||||
// @@unique([tenantId, ldapDn]) — must not be reported as "kollidiert
|
||||
// mit einer bestehenden Gruppe" (that message is specifically for a
|
||||
// name collision).
|
||||
err.meta = { target: ['tenantId', 'ldapDn'] };
|
||||
return Promise.reject(err);
|
||||
});
|
||||
mockSearch.mockResolvedValue({
|
||||
searchEntries: [{ dn: 'cn=Vertrieb,dc=example,dc=com', cn: 'Vertrieb' }],
|
||||
});
|
||||
|
||||
const result = makeResult();
|
||||
await run(result);
|
||||
|
||||
expect(result.errors).toEqual([
|
||||
'Gruppe Sales: Aktualisierung kollidiert mit einer bestehenden Bindung (["tenantId","ldapDn"])',
|
||||
]);
|
||||
expect(result.errors[0]).not.toContain('Umbenennung nach');
|
||||
expect(result.groupsRenamed).toBe(0);
|
||||
expect(groups[0].name).toBe('Sales');
|
||||
});
|
||||
|
||||
it('no hit, not the default group, deletes it and increments groupsDeleted without moving the marker', async () => {
|
||||
groups = [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user