test(16-04): remove orphaned ldapBind i18n keys, add D-07 regression tests

Delete the admin.groups.ldapBind.* subtree (hint/bound/unbind/
searchPlaceholder/discoverError/noResults) from de.json and en.json —
fully orphaned since GroupFormModal.tsx no longer has an AD-binding
codepath. admin.groups.ldapBinding (column header) and every other
admin.groups.* key are untouched; key sets stay in parity across both
languages.

Test file's translation stub loses the same dead ldapBind block and
gains the seven Task 1 keys. Two new cases lock down D-07: opening the
create dialog issues no /ldap/groups request, and editing an imported
group renders a disabled name input plus the internal-name field
instead of any AD-search UI.
This commit is contained in:
2026-08-06 16:30:49 +02:00
parent 6802b48cd8
commit e900b43d57
3 changed files with 69 additions and 24 deletions
@@ -24,15 +24,15 @@ const messages: Record<string, Record<string, unknown>> = {
actions: 'Aktionen',
membersButton: 'Mitglieder',
saveError: 'Gruppe konnte nicht gespeichert werden. Bitte erneut versuchen.',
saveErrorNameTaken: 'Der Name ist bereits vergeben.',
deleteError: 'Gruppe konnte nicht gelöscht werden. Bitte erneut versuchen.',
ldapBind: {
hint: 'Wähle eine AD-Gruppe aus der Liste, um die Mitgliedschaft automatisch zu synchronisieren.',
bound: 'Gebunden an: {ldapDn}',
unbind: 'Bindung entfernen',
searchPlaceholder: 'AD-Gruppen durchsuchen...',
discoverError: 'AD-Gruppen konnten nicht geladen werden. Bitte erneut versuchen.',
noResults: 'Keine AD-Gruppen gefunden.',
},
nameLockedHint: 'Von der AD-Gruppe übernommen. Wird beim nächsten Sync automatisch aktualisiert.',
adDnLabel: 'AD-DN: {ldapDn}',
internalName: 'Interner Name',
internalNameHint:
'Wird von der Synchronisation nie verändert. Bleibt das Feld leer, zeigt die Oberfläche stattdessen den AD-Namen.',
createLdapHint: 'AD-Gruppen werden im LDAP-Bereich importiert, nicht hier angelegt.',
goToLdap: 'Zum LDAP-Bereich',
members: {
title: 'Mitglieder',
remove: 'Mitglied entfernen',
@@ -300,6 +300,67 @@ describe('AdminGroupsPage', () => {
});
});
describe('GroupFormModal (via AdminGroupsPage) — D-07: no AD-binding path', () => {
beforeEach(() => {
stubAdmin();
});
it('opens the create dialog without issuing any request against the AD-directory endpoint', async () => {
const fetchMock = vi.fn((url: string) => {
if (typeof url === 'string' && url.endsWith('/groups')) {
return Promise.resolve({ ok: true, json: () => Promise.resolve(mockGroups) });
}
return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
});
vi.stubGlobal('fetch', fetchMock);
render(<AdminGroupsPage />);
await waitFor(() => {
expect(screen.getByText('Buchhaltung')).toBeInTheDocument();
});
const createButtons = screen.getAllByText('Gruppe erstellen');
await userEvent.click(createButtons[0]);
await waitFor(() => {
expect(screen.getByRole('heading', { name: 'Gruppe erstellen' })).toBeInTheDocument();
});
const ldapCalls = fetchMock.mock.calls.filter(
(call) => typeof call[0] === 'string' && call[0].includes('/ldap/groups'),
);
expect(ldapCalls).toHaveLength(0);
});
it('locks the name field and shows an editable internal-name field for an imported group', async () => {
vi.stubGlobal(
'fetch',
vi.fn(() => Promise.resolve({ ok: true, json: () => Promise.resolve(mockGroups) })),
);
render(<AdminGroupsPage />);
await waitFor(() => {
expect(screen.getByText('Buchhaltung')).toBeInTheDocument();
});
// 'Buchhaltung' (g2, ldapDn set) is the imported row — its edit button
// is the last one in row order (row 1 = local 'Alle Benutzer').
const editButtons = screen.getAllByText('Bearbeiten');
await userEvent.click(editButtons[editButtons.length - 1]);
await waitFor(() => {
expect(screen.getByRole('heading', { name: 'Gruppe bearbeiten' })).toBeInTheDocument();
});
const nameInput = screen.getByDisplayValue('Buchhaltung') as HTMLInputElement;
expect(nameInput).toBeDisabled();
expect(screen.getByText('Interner Name')).toBeInTheDocument();
expect(screen.queryByText(/AD-Gruppen durchsuchen/)).not.toBeInTheDocument();
});
});
interface GroupMembership {
id: string;
groupId: string;