refactor(mako,common): configurable topology delay, ThreadPool executor, event listener tests
- SimulatedEdaTopologyService: topology delay configurable via eda.simulation.topology-delay-ms - Add AsyncConfig with ThreadPoolTaskExecutor (4 core, 8 max threads) - Remove @EnableAsync from EegPortalApplication (now in AsyncConfig) - Add topology-delay-ms and consent-delay-ms to application-dev.yml - Add MakoConsentListenerTest (4 tests) - Add MeteringPointEventListenerTest (5 tests) - Add NotificationListenerTest (8 tests)
This commit is contained in:
parent
7baa1c72af
commit
fea8332976
@ -2,10 +2,8 @@ package at.mueller.eeg.backend;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableAsync
|
||||
public class EegPortalApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package at.mueller.eeg.backend.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean(name = "taskExecutor")
|
||||
public Executor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(4);
|
||||
executor.setMaxPoolSize(8);
|
||||
executor.setQueueCapacity(50);
|
||||
executor.setThreadNamePrefix("eeg-async-");
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ package at.mueller.eeg.backend.mako.service.impl;
|
||||
import at.mueller.eeg.backend.mako.api.dto.EdaTopologyResponse;
|
||||
import at.mueller.eeg.backend.mako.service.EdaTopologyService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -11,11 +12,18 @@ import java.util.concurrent.TimeUnit;
|
||||
@Service
|
||||
public class SimulatedEdaTopologyService implements EdaTopologyService {
|
||||
|
||||
private final long topologyDelayMs;
|
||||
|
||||
public SimulatedEdaTopologyService(
|
||||
@Value("${eda.simulation.topology-delay-ms:10000}") long topologyDelayMs) {
|
||||
this.topologyDelayMs = topologyDelayMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EdaTopologyResponse checkTopology(String atNumber) {
|
||||
log.info("Starte simulierte EDA-Topologieabfrage für Zählpunkt: {}", atNumber);
|
||||
try {
|
||||
TimeUnit.SECONDS.sleep(10);
|
||||
TimeUnit.MILLISECONDS.sleep(topologyDelayMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("EDA Simulation unterbrochen", e);
|
||||
|
||||
@ -5,3 +5,7 @@ jwt:
|
||||
secret: dev-only-secret-do-not-use-in-production-2025
|
||||
app:
|
||||
cors-origins: http://localhost:4200
|
||||
eda:
|
||||
simulation:
|
||||
consent-delay-ms: 3000
|
||||
topology-delay-ms: 1000
|
||||
|
||||
@ -0,0 +1,132 @@
|
||||
package at.mueller.eeg.backend.common.event.listener;
|
||||
|
||||
import at.mueller.eeg.backend.common.domain.NotificationType;
|
||||
import at.mueller.eeg.backend.common.event.*;
|
||||
import at.mueller.eeg.backend.common.service.NotificationService;
|
||||
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
||||
import at.mueller.eeg.backend.community.domain.PointType;
|
||||
import at.mueller.eeg.backend.community.domain.state.MakoState;
|
||||
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class NotificationListenerTest {
|
||||
|
||||
@Mock
|
||||
private NotificationService notificationService;
|
||||
|
||||
@Mock
|
||||
private MeteringPointRepository meteringPointRepository;
|
||||
|
||||
@InjectMocks
|
||||
private NotificationListener notificationListener;
|
||||
|
||||
private UUID testUserId;
|
||||
private String testAtNumber;
|
||||
private MeteringPoint testPoint;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testUserId = UUID.randomUUID();
|
||||
testAtNumber = "AT1234567890123456789012345678901";
|
||||
|
||||
testPoint = new MeteringPoint();
|
||||
testPoint.setId(UUID.randomUUID());
|
||||
testPoint.setUserId(testUserId);
|
||||
testPoint.setAtNumber(testAtNumber);
|
||||
testPoint.setType(PointType.CONSUMER);
|
||||
testPoint.setMakoState(MakoState.CONSENT_GRANTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleUserApproved_createsSuccessNotification() {
|
||||
notificationListener.handleUserApproved(new UserApprovedEvent(testUserId));
|
||||
|
||||
ArgumentCaptor<NotificationType> typeCaptor = ArgumentCaptor.forClass(NotificationType.class);
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), typeCaptor.capture());
|
||||
assertEquals(NotificationType.SUCCESS, typeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleUserRejected_createsErrorNotification() {
|
||||
notificationListener.handleUserRejected(new UserRejectedEvent(testUserId));
|
||||
|
||||
ArgumentCaptor<NotificationType> typeCaptor = ArgumentCaptor.forClass(NotificationType.class);
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), typeCaptor.capture());
|
||||
assertEquals(NotificationType.ERROR, typeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentApproved_createsNotificationForMeteringPointOwner() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
notificationListener.handleMakoConsentApproved(new MakoConsentApprovedEvent(testAtNumber));
|
||||
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), eq(NotificationType.SUCCESS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentRejected_createsErrorNotification() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
notificationListener.handleMakoConsentRejected(new MakoConsentRejectedEvent(testAtNumber, "Abgelehnt"));
|
||||
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), eq(NotificationType.ERROR));
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyCompleted_eligible_createsSuccessNotification() {
|
||||
notificationListener.handleEdaTopologyCompleted(new EdaTopologyCompletedEvent(
|
||||
testUserId, testPoint.getId(), true, "Op", "UW", "T", "7"
|
||||
));
|
||||
|
||||
ArgumentCaptor<NotificationType> typeCaptor = ArgumentCaptor.forClass(NotificationType.class);
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), typeCaptor.capture());
|
||||
assertEquals(NotificationType.SUCCESS, typeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyCompleted_notEligible_createsWarningNotification() {
|
||||
notificationListener.handleEdaTopologyCompleted(new EdaTopologyCompletedEvent(
|
||||
testUserId, testPoint.getId(), false, null, null, null, null
|
||||
));
|
||||
|
||||
ArgumentCaptor<NotificationType> typeCaptor = ArgumentCaptor.forClass(NotificationType.class);
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), typeCaptor.capture());
|
||||
assertEquals(NotificationType.WARNING, typeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyFailed_createsErrorNotification() {
|
||||
notificationListener.handleEdaTopologyFailed(new EdaTopologyFailedEvent(
|
||||
testUserId, testPoint.getId(), "Timeout"
|
||||
));
|
||||
|
||||
ArgumentCaptor<NotificationType> typeCaptor = ArgumentCaptor.forClass(NotificationType.class);
|
||||
verify(notificationService).create(eq(testUserId), anyString(), anyString(), typeCaptor.capture());
|
||||
assertEquals(NotificationType.ERROR, typeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentApproved_pointNotFound_doesNotCreateNotification() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.empty());
|
||||
|
||||
notificationListener.handleMakoConsentApproved(new MakoConsentApprovedEvent(testAtNumber));
|
||||
|
||||
verify(notificationService, never()).create(any(), anyString(), anyString(), any());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package at.mueller.eeg.backend.community.event.listener;
|
||||
|
||||
import at.mueller.eeg.backend.common.event.EdaTopologyCheckEvent;
|
||||
import at.mueller.eeg.backend.common.event.MakoConsentApprovedEvent;
|
||||
import at.mueller.eeg.backend.common.event.MakoConsentRejectedEvent;
|
||||
import at.mueller.eeg.backend.common.event.MakoConsentTechnicalFailedEvent;
|
||||
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
||||
import at.mueller.eeg.backend.community.domain.PointType;
|
||||
import at.mueller.eeg.backend.community.domain.state.MakoState;
|
||||
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
|
||||
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 org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MakoConsentListenerTest {
|
||||
|
||||
@Mock
|
||||
private MeteringPointRepository meteringPointRepository;
|
||||
|
||||
@Mock
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@InjectMocks
|
||||
private MakoConsentListener makoConsentListener;
|
||||
|
||||
private MeteringPoint testPoint;
|
||||
private String testAtNumber;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testAtNumber = "AT1234567890123456789012345678901";
|
||||
testPoint = new MeteringPoint();
|
||||
testPoint.setId(UUID.randomUUID());
|
||||
testPoint.setUserId(UUID.randomUUID());
|
||||
testPoint.setAtNumber(testAtNumber);
|
||||
testPoint.setType(PointType.CONSUMER);
|
||||
testPoint.setMakoState(MakoState.WAITING_FOR_CONSENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentApproved_triggersTopologyCheck() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
makoConsentListener.handleMakoConsentApproved(new MakoConsentApprovedEvent(testAtNumber));
|
||||
|
||||
verify(meteringPointRepository).save(testPoint);
|
||||
verify(eventPublisher).publishEvent(any(EdaTopologyCheckEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentRejected_setsRejectedState() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
makoConsentListener.handleMakoConsentRejected(new MakoConsentRejectedEvent(testAtNumber, "Kunde abgelehnt"));
|
||||
|
||||
verify(meteringPointRepository).save(testPoint);
|
||||
verify(eventPublisher, never()).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentTechnicalFailed_setsErrorState() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
makoConsentListener.handleMakoConsentTechnicalFailed(new MakoConsentTechnicalFailedEvent(testAtNumber, "Timeout"));
|
||||
|
||||
verify(meteringPointRepository).save(testPoint);
|
||||
verify(eventPublisher, never()).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleMakoConsentApproved_pointNotFound_doesNotThrow() {
|
||||
when(meteringPointRepository.findByAtNumber(testAtNumber)).thenReturn(Optional.empty());
|
||||
|
||||
makoConsentListener.handleMakoConsentApproved(new MakoConsentApprovedEvent(testAtNumber));
|
||||
|
||||
verify(meteringPointRepository, never()).save(any());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
package at.mueller.eeg.backend.community.event.listener;
|
||||
|
||||
import at.mueller.eeg.backend.common.event.EdaTopologyCompletedEvent;
|
||||
import at.mueller.eeg.backend.common.event.EdaTopologyFailedEvent;
|
||||
import at.mueller.eeg.backend.common.event.UserRegisteredEvent;
|
||||
import at.mueller.eeg.backend.common.event.UserRejectedEvent;
|
||||
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
||||
import at.mueller.eeg.backend.community.domain.PointType;
|
||||
import at.mueller.eeg.backend.community.domain.state.MakoState;
|
||||
import at.mueller.eeg.backend.community.domain.state.MakoTrigger;
|
||||
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class MeteringPointEventListenerTest {
|
||||
|
||||
@Mock
|
||||
private MeteringPointRepository meteringPointRepository;
|
||||
|
||||
@Mock
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@InjectMocks
|
||||
private MeteringPointEventListener listener;
|
||||
|
||||
private MeteringPoint testPoint;
|
||||
private UUID testPointId;
|
||||
private UUID testUserId;
|
||||
private String testAtNumber;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
testPointId = UUID.randomUUID();
|
||||
testUserId = UUID.randomUUID();
|
||||
testAtNumber = "AT1234567890123456789012345678901";
|
||||
|
||||
testPoint = new MeteringPoint();
|
||||
testPoint.setId(testPointId);
|
||||
testPoint.setUserId(testUserId);
|
||||
testPoint.setAtNumber(testAtNumber);
|
||||
testPoint.setType(PointType.CONSUMER);
|
||||
testPoint.setMakoState(MakoState.CONSENT_GRANTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyCompleted_eligible_setsActiveState() {
|
||||
when(meteringPointRepository.findById(testPointId)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
listener.handleEdaTopologyCompleted(new EdaTopologyCompletedEvent(
|
||||
testUserId, testPointId, true, "Wiener Netze", "UW-01", "Trafo-01", "7"
|
||||
));
|
||||
|
||||
ArgumentCaptor<MeteringPoint> captor = ArgumentCaptor.forClass(MeteringPoint.class);
|
||||
verify(meteringPointRepository).save(captor.capture());
|
||||
assertEquals(MakoState.ACTIVE, captor.getValue().getMakoState());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyCompleted_notEligible_setsRejectedState() {
|
||||
when(meteringPointRepository.findById(testPointId)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
listener.handleEdaTopologyCompleted(new EdaTopologyCompletedEvent(
|
||||
testUserId, testPointId, false, null, null, null, null
|
||||
));
|
||||
|
||||
ArgumentCaptor<MeteringPoint> captor = ArgumentCaptor.forClass(MeteringPoint.class);
|
||||
verify(meteringPointRepository).save(captor.capture());
|
||||
assertEquals(MakoState.REJECTED, captor.getValue().getMakoState());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyFailed_setsErrorState() {
|
||||
when(meteringPointRepository.findById(testPointId)).thenReturn(Optional.of(testPoint));
|
||||
|
||||
listener.handleEdaTopologyFailed(new EdaTopologyFailedEvent(testUserId, testPointId, "Timeout"));
|
||||
|
||||
ArgumentCaptor<MeteringPoint> captor = ArgumentCaptor.forClass(MeteringPoint.class);
|
||||
verify(meteringPointRepository).save(captor.capture());
|
||||
assertEquals(MakoState.ERROR, captor.getValue().getMakoState());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleEdaTopologyCompleted_pointNotFound_doesNotThrow() {
|
||||
when(meteringPointRepository.findById(testPointId)).thenReturn(Optional.empty());
|
||||
|
||||
listener.handleEdaTopologyCompleted(new EdaTopologyCompletedEvent(
|
||||
testUserId, testPointId, true, "Op", "UW", "T", "7"
|
||||
));
|
||||
|
||||
verify(meteringPointRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void handleUserRejected_deletesMeteringPoints() {
|
||||
listener.handleUserRejected(new UserRejectedEvent(testUserId));
|
||||
|
||||
verify(meteringPointRepository).deleteByUserId(testUserId);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user