diff --git a/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx b/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx index fdea52d..ceb96bf 100644 --- a/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx +++ b/apps/web/src/app/(portal)/admin/groups/groups-page.test.tsx @@ -24,15 +24,15 @@ const messages: Record> = { 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(); + + 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(); + + 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; diff --git a/apps/web/src/messages/de.json b/apps/web/src/messages/de.json index dee74a1..e2a33c0 100644 --- a/apps/web/src/messages/de.json +++ b/apps/web/src/messages/de.json @@ -410,14 +410,6 @@ "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", - "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." - }, "members": { "title": "Mitglieder", "remove": "Mitglied entfernen", diff --git a/apps/web/src/messages/en.json b/apps/web/src/messages/en.json index aa1f358..9bed80f 100644 --- a/apps/web/src/messages/en.json +++ b/apps/web/src/messages/en.json @@ -410,14 +410,6 @@ "internalNameHint": "Never changed by the sync. If left empty, the interface shows the AD name instead.", "createLdapHint": "AD groups are imported in the LDAP area, not created here.", "goToLdap": "Go to LDAP area", - "ldapBind": { - "hint": "Choose an AD group from the list to sync membership automatically.", - "bound": "Bound to: {ldapDn}", - "unbind": "Remove binding", - "searchPlaceholder": "Search AD groups...", - "discoverError": "Could not load AD groups. Please try again.", - "noResults": "No AD groups found." - }, "members": { "title": "Members", "remove": "Remove member",