fix(frontend): add error handling for dashboard overview metering data API
The empty error callback in loadMeteringData() silently swallowed API failures, causing the chart to show 'Keine Daten' instead of the actual error. Added proper error logging and user-facing error message. Also added DashboardServiceTest for getMeteringDataOverview with scenarios for data, no metering points, empty time range, and multiple metering points.
This commit is contained in:
parent
cfe989c426
commit
0ef4f94933
@ -1,21 +1,29 @@
|
||||
package at.mueller.eeg.backend.dashboard.service;
|
||||
|
||||
import at.mueller.eeg.backend.community.domain.MembershipStatus;
|
||||
import at.mueller.eeg.backend.community.domain.*;
|
||||
import at.mueller.eeg.backend.community.domain.state.MakoState;
|
||||
import at.mueller.eeg.backend.community.repository.EnergyCommunityRepository;
|
||||
import at.mueller.eeg.backend.community.repository.MembershipRepository;
|
||||
import at.mueller.eeg.backend.community.repository.MeteringDataRepository;
|
||||
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
|
||||
import at.mueller.eeg.backend.community.repository.MembershipRepository;
|
||||
import at.mueller.eeg.backend.dashboard.api.dto.DashboardStatsDto;
|
||||
import at.mueller.eeg.backend.iam.domain.RegistrationStatus;
|
||||
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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 java.util.UUID;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@ -23,45 +31,131 @@ class DashboardServiceTest {
|
||||
|
||||
@Mock
|
||||
private EnergyCommunityRepository energyCommunityRepository;
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Mock
|
||||
private MembershipRepository membershipRepository;
|
||||
|
||||
@Mock
|
||||
private MeteringPointRepository meteringPointRepository;
|
||||
@Mock
|
||||
private MeteringDataRepository meteringDataRepository;
|
||||
|
||||
@InjectMocks
|
||||
private DashboardService dashboardService;
|
||||
|
||||
@Test
|
||||
void getAdminStats_returnsCorrectCounts() {
|
||||
when(energyCommunityRepository.count()).thenReturn(5L);
|
||||
when(userRepository.countByStatusAndEmailVerified(RegistrationStatus.PENDING, true)).thenReturn(3L);
|
||||
when(membershipRepository.countByStatus(MembershipStatus.PENDING)).thenReturn(2L);
|
||||
when(membershipRepository.countByStatus(MembershipStatus.ACTIVE)).thenReturn(10L);
|
||||
private UUID userId;
|
||||
private UUID meteringPointId;
|
||||
private MeteringPoint meteringPoint;
|
||||
|
||||
DashboardStatsDto.AdminStats stats = dashboardService.getAdminStats();
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userId = UUID.randomUUID();
|
||||
meteringPointId = UUID.randomUUID();
|
||||
|
||||
assertEquals(5, stats.totalCommunities());
|
||||
assertEquals(3, stats.pendingUsers());
|
||||
assertEquals(2, stats.pendingMemberships());
|
||||
assertEquals(10, stats.totalActiveMemberships());
|
||||
meteringPoint = new MeteringPoint();
|
||||
meteringPoint.setId(meteringPointId);
|
||||
meteringPoint.setUserId(userId);
|
||||
meteringPoint.setAtNumber("AT0010000000000000000000001234567");
|
||||
meteringPoint.setType(PointType.CONSUMER);
|
||||
meteringPoint.setMakoState(MakoState.ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUserStats_returnsCorrectCounts() {
|
||||
UUID userId = UUID.randomUUID();
|
||||
when(meteringPointRepository.countByUserId(userId)).thenReturn(2L);
|
||||
when(membershipRepository.countByUserIdAndStatus(userId, MembershipStatus.ACTIVE)).thenReturn(1L);
|
||||
when(membershipRepository.countByUserIdAndStatus(userId, MembershipStatus.PENDING)).thenReturn(1L);
|
||||
void getMeteringDataOverview_withData_returnsTimeSeries() {
|
||||
Instant from = Instant.parse("2026-01-01T00:00:00Z");
|
||||
Instant to = Instant.parse("2026-01-03T00:00:00Z");
|
||||
|
||||
DashboardStatsDto.UserStats stats = dashboardService.getUserStats(userId);
|
||||
when(meteringPointRepository.findAllByUserId(userId)).thenReturn(List.of(meteringPoint));
|
||||
|
||||
assertEquals(2, stats.totalMeteringPoints());
|
||||
assertEquals(1, stats.activeMemberships());
|
||||
assertEquals(1, stats.pendingMemberships());
|
||||
ZoneId systemZone = ZoneId.systemDefault();
|
||||
List<MeteringData> meteringData = List.of(
|
||||
createMeteringData(meteringPointId, Instant.parse("2026-01-01T10:00:00Z"), 5.0),
|
||||
createMeteringData(meteringPointId, Instant.parse("2026-01-02T10:00:00Z"), 3.0)
|
||||
);
|
||||
when(meteringDataRepository.findByMeteringPointIdAndIntervalStartBetween(meteringPointId, from, to))
|
||||
.thenReturn(meteringData);
|
||||
|
||||
DashboardStatsDto.MeteringDataOverview overview = dashboardService.getMeteringDataOverview(userId, from, to);
|
||||
|
||||
assertEquals(1, overview.meteringPoints().size());
|
||||
assertEquals("AT0010000000000000000000001234567", overview.meteringPoints().get(0).atNumber());
|
||||
assertEquals(8.0, overview.meteringPoints().get(0).totalKwh(), 0.01);
|
||||
assertEquals(2, overview.timeSeries().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMeteringDataOverview_noMeteringPoints_returnsEmptyOverview() {
|
||||
Instant from = Instant.parse("2026-01-01T00:00:00Z");
|
||||
Instant to = Instant.parse("2026-01-03T00:00:00Z");
|
||||
|
||||
when(meteringPointRepository.findAllByUserId(userId)).thenReturn(Collections.emptyList());
|
||||
|
||||
DashboardStatsDto.MeteringDataOverview overview = dashboardService.getMeteringDataOverview(userId, from, to);
|
||||
|
||||
assertTrue(overview.meteringPoints().isEmpty());
|
||||
assertTrue(overview.timeSeries().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMeteringDataOverview_noDataInTimeRange_returnsEmptyTimeSeries() {
|
||||
Instant from = Instant.parse("2026-01-01T00:00:00Z");
|
||||
Instant to = Instant.parse("2026-01-03T00:00:00Z");
|
||||
|
||||
when(meteringPointRepository.findAllByUserId(userId)).thenReturn(List.of(meteringPoint));
|
||||
when(meteringDataRepository.findByMeteringPointIdAndIntervalStartBetween(meteringPointId, from, to))
|
||||
.thenReturn(Collections.emptyList());
|
||||
|
||||
DashboardStatsDto.MeteringDataOverview overview = dashboardService.getMeteringDataOverview(userId, from, to);
|
||||
|
||||
assertEquals(1, overview.meteringPoints().size());
|
||||
assertEquals(0.0, overview.meteringPoints().get(0).totalKwh(), 0.01);
|
||||
assertTrue(overview.timeSeries().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMeteringDataOverview_multipleMeteringPoints_aggregatesCorrectly() {
|
||||
Instant from = Instant.parse("2026-06-01T00:00:00Z");
|
||||
Instant to = Instant.parse("2026-06-02T00:00:00Z");
|
||||
|
||||
MeteringPoint mp2 = new MeteringPoint();
|
||||
mp2.setId(UUID.randomUUID());
|
||||
mp2.setUserId(userId);
|
||||
mp2.setAtNumber("AT0010000000000000000000001234568");
|
||||
mp2.setType(PointType.PRODUCER);
|
||||
mp2.setMakoState(MakoState.ACTIVE);
|
||||
|
||||
when(meteringPointRepository.findAllByUserId(userId)).thenReturn(List.of(meteringPoint, mp2));
|
||||
|
||||
List<MeteringData> data1 = List.of(createMeteringData(meteringPointId, Instant.parse("2026-06-01T10:00:00Z"), 5.0));
|
||||
List<MeteringData> data2 = List.of(createMeteringData(mp2.getId(), Instant.parse("2026-06-01T10:00:00Z"), 3.0));
|
||||
|
||||
when(meteringDataRepository.findByMeteringPointIdAndIntervalStartBetween(meteringPointId, from, to))
|
||||
.thenReturn(data1);
|
||||
when(meteringDataRepository.findByMeteringPointIdAndIntervalStartBetween(mp2.getId(), from, to))
|
||||
.thenReturn(data2);
|
||||
|
||||
DashboardStatsDto.MeteringDataOverview overview = dashboardService.getMeteringDataOverview(userId, from, to);
|
||||
|
||||
assertEquals(2, overview.meteringPoints().size());
|
||||
assertEquals(1, overview.timeSeries().size());
|
||||
|
||||
DashboardStatsDto.TimeSeriesEntry entry = overview.timeSeries().get(0);
|
||||
assertEquals(5.0, entry.values().get("AT0010000000000000000000001234567"), 0.01);
|
||||
assertEquals(3.0, entry.values().get("AT0010000000000000000000001234568"), 0.01);
|
||||
}
|
||||
|
||||
private MeteringData createMeteringData(UUID meteringPointId, Instant intervalStart, double kwh) {
|
||||
MeteringPoint mp = new MeteringPoint();
|
||||
mp.setId(meteringPointId);
|
||||
|
||||
MeteringData data = new MeteringData();
|
||||
data.setMeteringPoint(mp);
|
||||
data.setDataType(MeteringDataType.CONSUMPTION);
|
||||
data.setIntervalStart(intervalStart);
|
||||
data.setIntervalEnd(intervalStart.plusSeconds(3600));
|
||||
data.setKwh(kwh);
|
||||
data.setSource(DataSource.EMAIL_XLSX);
|
||||
data.setUploadId(UUID.randomUUID());
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +65,11 @@ export class DashboardOverview implements OnInit {
|
||||
this.meteringData.set(data);
|
||||
this.buildChartOptions(data);
|
||||
},
|
||||
error: () => {}
|
||||
error: (err) => {
|
||||
console.error('Fehler beim Laden der Messdaten:', err);
|
||||
this.error.set('Fehler beim Laden der Messdaten.');
|
||||
this.isLoading.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user