import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideHttpClient } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { vi } from 'vitest'; import { AdminUserApprovalComponent } from './admin-user-approval'; import { UserProfileResponse } from '../../api'; describe('AdminUserApprovalComponent', () => { let component: AdminUserApprovalComponent; let fixture: ComponentFixture; let httpMock: HttpTestingController; const mockPendingUser: UserProfileResponse = { id: '1', firstName: 'Max', lastName: 'Mustermann', email: 'max@test.at', status: 'PENDING', participantType: UserProfileResponse.ParticipantTypeEnum.Private, }; const mockApprovedUser: UserProfileResponse = { id: '2', firstName: 'Anna', lastName: 'Schmidt', email: 'anna@test.at', status: 'APPROVED', participantType: UserProfileResponse.ParticipantTypeEnum.Company, }; const mockRejectedUser: UserProfileResponse = { id: '3', firstName: 'Tom', lastName: 'Berger', email: 'tom@test.at', status: 'REJECTED', participantType: UserProfileResponse.ParticipantTypeEnum.Private, }; beforeEach(async () => { await TestBed.configureTestingModule({ imports: [AdminUserApprovalComponent], providers: [provideHttpClient(), provideHttpClientTesting()], }).compileComponents(); fixture = TestBed.createComponent(AdminUserApprovalComponent); component = fixture.componentInstance; httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { httpMock.verify(); }); it('should create', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); expect(component).toBeTruthy(); }); it('should default activeTab to pending', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); expect(component.activeTab()).toBe('pending'); }); it('should initialize allUsers as empty array', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); expect(component.allUsers()).toEqual([]); }); it('should load pending users on init', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); expect(req.request.method).toBe('GET'); req.flush([mockPendingUser]); expect(component.pendingUsers().length).toBe(1); expect(component.pendingUsers()[0].firstName).toBe('Max'); }); 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('/api/admin/users') && !r.url.includes('/pending')); expect(allReq.request.method).toBe('GET'); allReq.flush([mockPendingUser, mockApprovedUser, mockRejectedUser]); expect(component.allUsers().length).toBe(3); }); it('should switch activeTab signal', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); component.switchTab('all'); const allReq = httpMock.expectOne((r) => r.url.includes('/api/admin/users') && !r.url.includes('/pending')); allReq.flush([]); expect(component.activeTab()).toBe('all'); component.switchTab('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.includes('/api/admin/users') && !r.url.includes('/pending')); 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.includes('/api/admin/users') && !r.url.includes('/pending')); expect(allReqs.length).toBe(0); }); 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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'); }); it('should return "Aktiv" for ACTIVE', () => { expect(component.getStatusLabel('ACTIVE')).toBe('Aktiv'); }); it('should return "Freigeschaltet" for APPROVED', () => { expect(component.getStatusLabel('APPROVED')).toBe('Freigeschaltet'); }); it('should return "Inaktiv" for INACTIVE', () => { expect(component.getStatusLabel('INACTIVE')).toBe('Inaktiv'); }); it('should return "Abgelehnt" for REJECTED', () => { expect(component.getStatusLabel('REJECTED')).toBe('Abgelehnt'); }); it('should return the original status for unknown values', () => { expect(component.getStatusLabel('UNKNOWN')).toBe('UNKNOWN'); }); }); describe('getStatusBadgeClass', () => { it('should return yellow badge for PENDING', () => { expect(component.getStatusBadgeClass('PENDING')).toContain('yellow'); }); it('should return green badge for APPROVED', () => { expect(component.getStatusBadgeClass('APPROVED')).toContain('green'); }); it('should return green badge for ACTIVE', () => { expect(component.getStatusBadgeClass('ACTIVE')).toContain('green'); }); it('should return red badge for REJECTED', () => { expect(component.getStatusBadgeClass('REJECTED')).toContain('red'); }); it('should return red badge for INACTIVE', () => { expect(component.getStatusBadgeClass('INACTIVE')).toContain('red'); }); }); it('should approve user and remove from pending list', () => { 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({}); 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')); pendingReq.flush([mockPendingUser]); vi.spyOn(window, 'confirm').mockReturnValue(true); component.rejectUser(mockPendingUser); const rejectReq = httpMock.expectOne((r) => r.url.includes('/reject')); rejectReq.flush({}); 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 with two tabs', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); fixture.detectChanges(); const tabs = fixture.nativeElement.querySelectorAll('[role="tab"]'); expect(tabs.length).toBe(2); expect(tabs[0].textContent).toContain('Ausstehende Anträge'); expect(tabs[1].textContent).toContain('Alle Benutzer'); }); it('should show pending tab as active by default', () => { fixture.detectChanges(); const req = httpMock.expectOne((r) => r.url.includes('/admin/users/pending')); req.flush([]); fixture.detectChanges(); const activeTab = fixture.nativeElement.querySelector('[role="tab"][aria-selected="true"]'); expect(activeTab?.textContent).toContain('Ausstehende Anträge'); }); 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]); fixture.detectChanges(); const allHeaders = fixture.nativeElement.querySelectorAll('th'); let statusCount = 0; for (let i = 0; i < allHeaders.length; i++) { if (allHeaders[i].textContent?.trim() === 'Status') { statusCount++; } } 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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.includes('/api/admin/users') && !r.url.includes('/pending')); 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'); }); }); });