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
This commit is contained in:
Bernhard Müller 2026-07-24 08:12:32 +02:00
parent e46b21c8ca
commit 71ecf47948

View File

@ -82,16 +82,14 @@ describe('AdminUserApprovalComponent', () => {
expect(component.pendingUsers()[0].firstName).toBe('Max'); 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(); fixture.detectChanges();
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
pendingReq.flush([]); pendingReq.flush([]);
component.switchTab('all'); component.switchTab('all');
const allReq = httpMock.expectOne( const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users');
(r) => r.url.includes('/admin/users') && !r.url.includes('/pending'),
);
expect(allReq.request.method).toBe('GET'); expect(allReq.request.method).toBe('GET');
allReq.flush([mockPendingUser, mockApprovedUser, mockRejectedUser]); allReq.flush([mockPendingUser, mockApprovedUser, mockRejectedUser]);
@ -104,9 +102,7 @@ describe('AdminUserApprovalComponent', () => {
req.flush([]); req.flush([]);
component.switchTab('all'); component.switchTab('all');
const allReq = httpMock.expectOne( const allReq = httpMock.expectOne((r) => r.url === '/api/admin/users');
(r) => r.url.includes('/admin/users') && !r.url.includes('/pending'),
);
allReq.flush([]); allReq.flush([]);
expect(component.activeTab()).toBe('all'); expect(component.activeTab()).toBe('all');
@ -114,6 +110,49 @@ describe('AdminUserApprovalComponent', () => {
expect(component.activeTab()).toBe('pending'); 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', () => { describe('getStatusLabel', () => {
it('should return "Ausstehend" for PENDING', () => { it('should return "Ausstehend" for PENDING', () => {
expect(component.getStatusLabel('PENDING')).toBe('Ausstehend'); expect(component.getStatusLabel('PENDING')).toBe('Ausstehend');
@ -175,6 +214,17 @@ describe('AdminUserApprovalComponent', () => {
expect(component.pendingUsers().length).toBe(0); 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', () => { it('should reject user and remove from pending list', () => {
fixture.detectChanges(); fixture.detectChanges();
const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); const pendingReq = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
@ -188,8 +238,45 @@ describe('AdminUserApprovalComponent', () => {
expect(component.pendingUsers().length).toBe(0); 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', () => { describe('template', () => {
it('should render tab navigation', () => { it('should render tab navigation with two tabs', () => {
fixture.detectChanges(); fixture.detectChanges();
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
req.flush([]); req.flush([]);
@ -211,7 +298,7 @@ describe('AdminUserApprovalComponent', () => {
expect(activeTab?.textContent).toContain('Ausstehende Anträge'); 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(); fixture.detectChanges();
const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending'));
req.flush([mockPendingUser]); req.flush([mockPendingUser]);
@ -226,5 +313,142 @@ describe('AdminUserApprovalComponent', () => {
} }
expect(statusCount).toBe(1); 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');
});
}); });
}); });