From 71ecf479489f2fab177c3e3f0514ab6647cee703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernhard=20M=C3=BCller?= Date: Fri, 24 Jul 2026 08:12:32 +0200 Subject: [PATCH] test(frontend): enhance AdminUserApprovalComponent tests with full coverage Add comprehensive tests covering: - Tab navigation (pending/all) with signal verification - loadAllUsers() calls correct endpoint via generated AdminIAMService - Error handling for loadPendingUsers and loadAllUsers failures - Approve/reject user actions including undefined userId guards - Template rendering: pending tab with Freigeben/Ablehnen buttons - Template rendering: all users tab without action buttons - Status badge rendering in both tabs - Role column display (Unternehmen/Privatperson) - Empty state messages for both tabs - Duplicate Status column header bug fix verification - No reload of allUsers if already loaded --- .../admin-user-approval.spec.ts | 242 +++++++++++++++++- 1 file changed, 233 insertions(+), 9 deletions(-) diff --git a/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts b/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts index 827f08d..057af48 100644 --- a/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts +++ b/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts @@ -82,16 +82,14 @@ describe('AdminUserApprovalComponent', () => { expect(component.pendingUsers()[0].firstName).toBe('Max'); }); - it('should load all users when switching to all tab', () => { + it('should load all users via getAllUsers endpoint when switching to all tab', () => { fixture.detectChanges(); const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); pendingReq.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne( - (r) => r.url.includes('/admin/users') && !r.url.includes('/pending'), - ); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); expect(allReq.request.method).toBe('GET'); allReq.flush([mockPendingUser, mockApprovedUser, mockRejectedUser]); @@ -104,9 +102,7 @@ describe('AdminUserApprovalComponent', () => { req.flush([]); component.switchTab('all'); - const allReq = httpMock.expectOne( - (r) => r.url.includes('/admin/users') && !r.url.includes('/pending'), - ); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); allReq.flush([]); expect(component.activeTab()).toBe('all'); @@ -114,6 +110,49 @@ describe('AdminUserApprovalComponent', () => { expect(component.activeTab()).toBe('pending'); }); + it('should not reload all users if already loaded', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([mockApprovedUser]); + fixture.detectChanges(); + + component.switchTab('pending'); + expect(component.activeTab()).toBe('pending'); + + component.switchTab('all'); + expect(component.activeTab()).toBe('all'); + const allReqs = httpMock.match((r) => r.url === '/api/admin/users'); + expect(allReqs.length).toBe(1); + }); + + it('should set error message when loadPendingUsers fails', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + req.flush('Error', { status: 500, statusText: 'Server Error' }); + + expect(component.errorMessage()).toBe( + 'Fehler beim Laden der Daten aus dem EDA-Netz-Check.', + ); + expect(component.isLoading()).toBe(false); + }); + + it('should set error message when loadAllUsers fails', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush('Error', { status: 500, statusText: 'Server Error' }); + + expect(component.errorMessage()).toBe('Fehler beim Laden aller Benutzer.'); + expect(component.isLoading()).toBe(false); + }); + describe('getStatusLabel', () => { it('should return "Ausstehend" for PENDING', () => { expect(component.getStatusLabel('PENDING')).toBe('Ausstehend'); @@ -175,6 +214,17 @@ describe('AdminUserApprovalComponent', () => { expect(component.pendingUsers().length).toBe(0); }); + it('should not call API when approveUser has undefined userId', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.approveUser(undefined); + + const approveReqs = httpMock.match((r) => r.url.includes('/approve')); + expect(approveReqs.length).toBe(0); + }); + it('should reject user and remove from pending list', () => { fixture.detectChanges(); const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); @@ -188,8 +238,45 @@ describe('AdminUserApprovalComponent', () => { expect(component.pendingUsers().length).toBe(0); }); + it('should not call API when rejectUser has undefined id', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.rejectUser({ ...mockPendingUser, id: undefined }); + + const rejectReqs = httpMock.match((r) => r.url.includes('/reject')); + expect(rejectReqs.length).toBe(0); + }); + + it('should show error message when approveUser fails', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([mockPendingUser]); + + vi.spyOn(window, 'confirm').mockReturnValue(true); + component.approveUser(mockPendingUser.id); + + const approveReq = httpMock.expectOne((r) => r.url.includes('/approve')); + approveReq.flush('Error', { status: 500, statusText: 'Server Error' }); + + expect(component.errorMessage()).toBe('Der User konnte nicht freigegeben werden.'); + }); + + it('should show error toast when rejectUser fails', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([mockPendingUser]); + + vi.spyOn(window, 'confirm').mockReturnValue(true); + component.rejectUser(mockPendingUser); + + const rejectReq = httpMock.expectOne((r) => r.url.includes('/reject')); + rejectReq.flush({ message: 'Ablehnung fehlgeschlagen' }, { status: 500, statusText: 'Server Error' }); + }); + describe('template', () => { - it('should render tab navigation', () => { + it('should render tab navigation with two tabs', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); @@ -211,7 +298,7 @@ describe('AdminUserApprovalComponent', () => { expect(activeTab?.textContent).toContain('Ausstehende Anträge'); }); - it('should show only one Status column header (no duplicate)', () => { + it('should show only one Status column header in pending tab (no duplicate)', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([mockPendingUser]); @@ -226,5 +313,142 @@ describe('AdminUserApprovalComponent', () => { } expect(statusCount).toBe(1); }); + + it('should show pending users table with Freigeben and Ablehnen buttons', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + req.flush([mockPendingUser]); + fixture.detectChanges(); + + const table = fixture.nativeElement.querySelector('table'); + expect(table).toBeTruthy(); + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + expect(rows.length).toBe(1); + + const buttons = rows[0].querySelectorAll('button'); + expect(buttons.length).toBe(2); + expect(buttons[0].textContent?.trim()).toBe('Freigeben'); + expect(buttons[1].textContent?.trim()).toBe('Ablehnen'); + }); + + it('should show empty state message when no pending users', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + req.flush([]); + fixture.detectChanges(); + + const emptyMsg = fixture.nativeElement.querySelector('.bg-gray-50 p'); + expect(emptyMsg?.textContent).toContain( + 'Aktuell gibt es keine neuen User im Status PENDING', + ); + }); + + it('should show all users table without action buttons', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([mockApprovedUser]); + fixture.detectChanges(); + + const table = fixture.nativeElement.querySelector('table'); + expect(table).toBeTruthy(); + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + expect(rows.length).toBe(1); + + const buttons = rows[0].querySelectorAll('button'); + expect(buttons.length).toBe(0); + }); + + it('should show empty state when no users exist', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([]); + fixture.detectChanges(); + + const emptyMsg = fixture.nativeElement.querySelector('.bg-gray-50 p'); + expect(emptyMsg?.textContent).toContain('Keine Benutzer vorhanden'); + }); + + it('should display role as Unternehmen for Company participant type', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([mockApprovedUser]); + fixture.detectChanges(); + + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + const roleCell = rows[0].querySelectorAll('td')[2]; + expect(roleCell?.textContent?.trim()).toBe('Unternehmen'); + }); + + it('should display role as Privatperson for Private participant type', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([mockRejectedUser]); + fixture.detectChanges(); + + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + const roleCell = rows[0].querySelectorAll('td')[2]; + expect(roleCell?.textContent?.trim()).toBe('Privatperson'); + }); + + it('should show status badge in pending users table', () => { + fixture.detectChanges(); + const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + req.flush([mockPendingUser]); + fixture.detectChanges(); + + const badge = fixture.nativeElement.querySelector('span.rounded-full'); + expect(badge?.textContent?.trim()).toBe('Ausstehend'); + }); + + it('should show status badge in all users table', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([mockApprovedUser]); + fixture.detectChanges(); + + const badge = fixture.nativeElement.querySelector('span.rounded-full'); + expect(badge?.textContent?.trim()).toBe('Freigeschaltet'); + }); + + it('should switch to all tab and show user data with role column', () => { + fixture.detectChanges(); + const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); + pendingReq.flush([]); + + component.switchTab('all'); + const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users'); + allReq.flush([mockApprovedUser, mockPendingUser]); + fixture.detectChanges(); + + const rows = fixture.nativeElement.querySelectorAll('tbody tr'); + expect(rows.length).toBe(2); + + const allHeaders = fixture.nativeElement.querySelectorAll('th'); + const headerTexts: string[] = []; + for (let i = 0; i < allHeaders.length; i++) { + headerTexts.push(allHeaders[i].textContent?.trim() || ''); + } + expect(headerTexts).toContain('Rolle'); + }); }); });