eeg_portal/eeg_frontend/src/app/pages/admin-user-approval/admin-user-approval.spec.ts
Bernhard Müller c6dfa31839 fix(frontend): pass userId to deleteUserTariff API to fix compilation error
The generated API service requires userId as a second parameter for
deleteUserTariff(). Inject AuthService and use getCurrentUserId() to
provide the required argument, unblocking the entire frontend test suite.
2026-07-24 09:43:37 +02:00

455 lines
17 KiB
TypeScript

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<AdminUserApprovalComponent>;
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');
});
});
});