test(dashboard,frontend): add DashboardControllerTest, fix broken spec files and add login tests

- Add DashboardControllerTest with 4 tests for admin and user dashboard endpoints
- Fix incorrect class name imports in header, admin-user-approval, metering-points, verify-email specs
- Add missing ActivatedRoute/Router/AuthService providers in all spec files
- Replace stub login spec with 10 comprehensive tests (form validation, role redirect, error handling)
- Remove stale 'should render title' test from app.spec.ts
This commit is contained in:
Bernhard Müller 2026-07-22 08:39:58 +02:00
parent c180d6fd32
commit 0bfa0d98d5
10 changed files with 219 additions and 40 deletions

View File

@ -0,0 +1,77 @@
package at.mueller.eeg.backend.dashboard.api;
import at.mueller.eeg.backend.dashboard.api.dto.DashboardStatsDto;
import at.mueller.eeg.backend.dashboard.service.DashboardService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class DashboardControllerTest {
@Mock
private DashboardService dashboardService;
@InjectMocks
private DashboardController dashboardController;
@Test
void getAdminDashboard() {
DashboardStatsDto.AdminStats stats = new DashboardStatsDto.AdminStats(5, 3, 12);
when(dashboardService.getAdminStats()).thenReturn(stats);
ResponseEntity<DashboardStatsDto.AdminStats> response = dashboardController.getAdminDashboard();
assertEquals(200, response.getStatusCode().value());
assertEquals(5, response.getBody().totalCommunities());
assertEquals(3, response.getBody().pendingUsers());
assertEquals(12, response.getBody().totalActiveMemberships());
}
@Test
void getAdminDashboard_empty() {
DashboardStatsDto.AdminStats stats = new DashboardStatsDto.AdminStats(0, 0, 0);
when(dashboardService.getAdminStats()).thenReturn(stats);
ResponseEntity<DashboardStatsDto.AdminStats> response = dashboardController.getAdminDashboard();
assertEquals(0, response.getBody().totalCommunities());
assertEquals(0, response.getBody().pendingUsers());
assertEquals(0, response.getBody().totalActiveMemberships());
}
@Test
void getUserDashboard() {
UUID userId = UUID.randomUUID();
DashboardStatsDto.UserStats stats = new DashboardStatsDto.UserStats(3, 2, 1);
when(dashboardService.getUserStats(userId)).thenReturn(stats);
ResponseEntity<DashboardStatsDto.UserStats> response = dashboardController.getUserDashboard(userId.toString());
assertEquals(200, response.getStatusCode().value());
assertEquals(3, response.getBody().totalMeteringPoints());
assertEquals(2, response.getBody().activeMemberships());
assertEquals(1, response.getBody().pendingMemberships());
}
@Test
void getUserDashboard_empty() {
UUID userId = UUID.randomUUID();
DashboardStatsDto.UserStats stats = new DashboardStatsDto.UserStats(0, 0, 0);
when(dashboardService.getUserStats(userId)).thenReturn(stats);
ResponseEntity<DashboardStatsDto.UserStats> response = dashboardController.getUserDashboard(userId.toString());
assertEquals(0, response.getBody().totalMeteringPoints());
assertEquals(0, response.getBody().activeMemberships());
assertEquals(0, response.getBody().pendingMemberships());
}
}

View File

@ -1,10 +1,15 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { App } from './app'; import { App } from './app';
describe('App', () => { describe('App', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [App], imports: [App],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } },
{ provide: Router, useValue: { events: { subscribe: () => {} }, navigate: () => {} } }
]
}).compileComponents(); }).compileComponents();
}); });
@ -13,11 +18,4 @@ describe('App', () => {
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app).toBeTruthy(); expect(app).toBeTruthy();
}); });
it('should render title', async () => {
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, eeg_frontend');
});
}); });

View File

@ -1,6 +1,8 @@
import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ActivatedRoute, Router} from '@angular/router';
import {of} from 'rxjs';
import {DashboardLayout} from './dashboard-layout'; import {DashboardLayout} from './dashboard-layout';
import {AuthService} from '../../services/auth';
describe('DashboardLayout', () => { describe('DashboardLayout', () => {
let component: DashboardLayout; let component: DashboardLayout;
@ -9,6 +11,11 @@ describe('DashboardLayout', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [DashboardLayout], imports: [DashboardLayout],
providers: [
{provide: ActivatedRoute, useValue: {snapshot: {queryParamMap: {get: () => null}}}},
{provide: Router, useValue: {events: of(), navigate: () => {}}},
{provide: AuthService, useValue: {currentUserRole: () => 'MEMBER', logout: () => {}}}
]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(DashboardLayout); fixture = TestBed.createComponent(DashboardLayout);

View File

@ -1,17 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { Header } from './header'; import { HeaderComponent } from './header';
import { AuthService } from '../../services/auth';
describe('Header', () => { describe('Header', () => {
let component: Header; let component: HeaderComponent;
let fixture: ComponentFixture<Header>; let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [Header], imports: [HeaderComponent],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } },
{ provide: Router, useValue: { navigate: () => {} } },
{ provide: AuthService, useValue: { isLoggedIn: () => false, currentUserRole: () => null, logout: () => {} } }
]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(Header); fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
await fixture.whenStable(); await fixture.whenStable();
}); });

View File

@ -1,17 +1,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminUserApproval } from './admin-user-approval'; import { AdminUserApprovalComponent } from './admin-user-approval';
describe('AdminUserApproval', () => { describe('AdminUserApproval', () => {
let component: AdminUserApproval; let component: AdminUserApprovalComponent;
let fixture: ComponentFixture<AdminUserApproval>; let fixture: ComponentFixture<AdminUserApprovalComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [AdminUserApproval], imports: [AdminUserApprovalComponent],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(AdminUserApproval); fixture = TestBed.createComponent(AdminUserApprovalComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
await fixture.whenStable(); await fixture.whenStable();
}); });

View File

@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { LandingPage } from './landing-page'; import { LandingPage } from './landing-page';
describe('LandingPage', () => { describe('LandingPage', () => {
@ -9,6 +9,9 @@ describe('LandingPage', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [LandingPage], imports: [LandingPage],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } }
]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(LandingPage); fixture = TestBed.createComponent(LandingPage);

View File

@ -1,22 +1,104 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { vi, expect, it, describe, beforeEach } from 'vitest';
import { of, throwError } from 'rxjs';
import { LoginComponent } from './login';
import { AuthService } from '../../services/auth';
import { Login } from './login'; describe('LoginComponent', () => {
let component: LoginComponent;
describe('Login', () => { let fixture: ComponentFixture<LoginComponent>;
let component: Login; let authService: { login: ReturnType<typeof vi.fn> };
let fixture: ComponentFixture<Login>; let router: { navigate: ReturnType<typeof vi.fn> };
beforeEach(async () => { beforeEach(async () => {
authService = { login: vi.fn() };
router = { navigate: vi.fn() };
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [Login], imports: [LoginComponent, ReactiveFormsModule],
providers: [
{ provide: AuthService, useValue: authService },
{ provide: Router, useValue: router },
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } }
]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(Login); fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
await fixture.whenStable(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should have invalid form when empty', () => {
expect(component.loginForm.valid).toBe(false);
});
it('should have valid form when email and password are filled', () => {
component.loginForm.setValue({ email: 'test@example.at', password: 'password123' });
expect(component.loginForm.valid).toBe(true);
});
it('should not call login when form is invalid', () => {
component.onSubmit();
expect(authService.login).not.toHaveBeenCalled();
});
it('should call login and redirect to /dashboard for ADMIN role', () => {
authService.login.mockReturnValue(of({ token: 'abc', role: 'ADMIN' }));
component.loginForm.setValue({ email: 'admin@example.at', password: 'password123' });
component.onSubmit();
expect(authService.login).toHaveBeenCalledWith({ email: 'admin@example.at', password: 'password123' });
expect(router.navigate).toHaveBeenCalledWith(['/dashboard']);
});
it('should call login and redirect to /dashboard for MEMBER role', () => {
authService.login.mockReturnValue(of({ token: 'abc', role: 'MEMBER' }));
component.loginForm.setValue({ email: 'member@example.at', password: 'password123' });
component.onSubmit();
expect(router.navigate).toHaveBeenCalledWith(['/dashboard']);
});
it('should redirect to / for unknown role', () => {
authService.login.mockReturnValue(of({ token: 'abc', role: 'GUEST' }));
component.loginForm.setValue({ email: 'guest@example.at', password: 'password123' });
component.onSubmit();
expect(router.navigate).toHaveBeenCalledWith(['/']);
});
it('should display error message on login failure', () => {
authService.login.mockReturnValue(throwError(() => ({
error: { message: 'Ungültige Anmeldedaten' }
})));
component.loginForm.setValue({ email: 'test@example.at', password: 'wrong' });
component.onSubmit();
expect(component.errorMessage).toBe('Ungültige Anmeldedaten');
});
it('should display generic error when server returns no message', () => {
authService.login.mockReturnValue(throwError(() => ({ error: {} })));
component.loginForm.setValue({ email: 'test@example.at', password: 'wrong' });
component.onSubmit();
expect(component.errorMessage).toBe('Ein unbekannter Fehler ist aufgetreten.');
});
it('should have disabled submit button when form is invalid', () => {
fixture.detectChanges();
const button = fixture.nativeElement.querySelector('button[type="submit"]');
expect(button.disabled).toBe(true);
});
}); });

View File

@ -1,17 +1,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MeteringPoints } from './metering-points'; import { MeteringPointsComponent } from './metering-points';
describe('MeteringPoints', () => { describe('MeteringPoints', () => {
let component: MeteringPoints; let component: MeteringPointsComponent;
let fixture: ComponentFixture<MeteringPoints>; let fixture: ComponentFixture<MeteringPointsComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [MeteringPoints], imports: [MeteringPointsComponent],
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(MeteringPoints); fixture = TestBed.createComponent(MeteringPointsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
await fixture.whenStable(); await fixture.whenStable();
}); });

View File

@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { Register } from './register'; import { Register } from './register';
describe('Register', () => { describe('Register', () => {
@ -9,6 +9,9 @@ describe('Register', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [Register], imports: [Register],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } }
]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(Register); fixture = TestBed.createComponent(Register);

View File

@ -1,17 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { VerifyEmail } from './verify-email'; import { VerifyEmailComponent } from './verify-email';
describe('VerifyEmail', () => { describe('VerifyEmail', () => {
let component: VerifyEmail; let component: VerifyEmailComponent;
let fixture: ComponentFixture<VerifyEmail>; let fixture: ComponentFixture<VerifyEmailComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [VerifyEmail], imports: [VerifyEmailComponent],
providers: [
{ provide: ActivatedRoute, useValue: { snapshot: { queryParamMap: { get: () => null } } } }
]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(VerifyEmail); fixture = TestBed.createComponent(VerifyEmailComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
await fixture.whenStable(); await fixture.whenStable();
}); });