Skip to content

Commit

Permalink
issue-4. in case many event listeners present call all of them event …
Browse files Browse the repository at this point in the history
…if one fails (#6)

* issue-4. in case many event listeners present call all of them event if one fails

* fixed lift issue
  • Loading branch information
michaelfmnk authored Feb 26, 2023
1 parent c6c76f4 commit 18672e2
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 37 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.fomenko.springundocore;

import lombok.Getter;

import java.util.Collection;

public class UndoListenerInvocationException extends RuntimeException {
private final Collection<Throwable> causes;


public UndoListenerInvocationException(String msg, Collection<Throwable> causes) {
super(msg, causes.iterator().next());
this.causes = causes;
}

public Collection<Throwable> getCauses() {
return causes;
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package dev.fomenko.springundocore.service;

import dev.fomenko.springundocore.dto.ActionRecord;
import dev.fomenko.springundocore.UndoEventListener;
import dev.fomenko.springundocore.UndoListenerInvocationException;
import dev.fomenko.springundocore.dto.ActionRecord;
import dev.fomenko.springundocore.property.UndoProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.util.CollectionUtils;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -69,12 +71,23 @@ private void invokeListenersForRecord(ActionRecord<?> record,
Object action = record.getAction();
List<UndoEventListener<?>> undoEventListeners = listeners.get(action.getClass());

if (!CollectionUtils.isEmpty(undoEventListeners)) {
for (UndoEventListener<?> listener : undoEventListeners) {

if (CollectionUtils.isEmpty(undoEventListeners)) {
log.warn("There are no listeners for " + action.getClass());
return;
}

var errors = new ArrayList<Throwable>();
for (UndoEventListener<?> listener : undoEventListeners) {
try {
methodReference.accept((UndoEventListener<? super Object>) listener, action);
} catch (Throwable e) {
errors.add(e);
}
} else {
log.warn("There are no listeners for " + action.getClass());
}

if (!errors.isEmpty()) {
throw new UndoListenerInvocationException("There were errors while invoking undo listeners", errors);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package dev.fomenko.springundocore;

import dev.fomenko.springundocore.config.UndoTestConfiguration;
import dev.fomenko.springundocore.config.UndoTestConfiguration.FaultyListenerC;
import dev.fomenko.springundocore.config.UndoTestConfiguration.FirstListenerA;
import dev.fomenko.springundocore.config.UndoTestConfiguration.FirstListenerB;
import dev.fomenko.springundocore.config.UndoTestConfiguration.SecondListenerA;
import dev.fomenko.springundocore.config.UndoTestConfiguration.SecondListenerB;
import dev.fomenko.springundocore.config.UndoTestConfiguration.SecondListenerC;
import dev.fomenko.springundocore.config.UndoTestConfiguration.ThirdListenerC;
import dev.fomenko.springundocore.dto.ActionRecord;
import dev.fomenko.springundocore.dto.TestDtoA;
import dev.fomenko.springundocore.dto.TestDtoC;
import dev.fomenko.springundocore.service.ActionIdGenerator;
import dev.fomenko.springundocore.service.EventRecorder;
import dev.fomenko.springundocore.service.UndoService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -35,44 +44,88 @@ class UndoBaseTest {
@SpyBean
private ActionIdGenerator idGenerator;
@SpyBean
private UndoTestConfiguration.FirstListenerA firstListenerA;
private FirstListenerA firstListenerA;
@SpyBean
private UndoTestConfiguration.SecondListenerA secondListenerA;
private SecondListenerA secondListenerA;
@SpyBean
private UndoTestConfiguration.FirstListenerB firstListenerB;
private FirstListenerB firstListenerB;
@SpyBean
private UndoTestConfiguration.SecondListenerB secondListenerB;
private SecondListenerB secondListenerB;
@SpyBean
private FaultyListenerC faultyListenerC;
@SpyBean
private SecondListenerC secondListenerC;
@SpyBean
private ThirdListenerC thirdListenerB;

@Test
void shouldInvokeListeners() {
Mockito.when(eventRecorder.deleteRecordById(Mockito.eq("1"))).thenReturn(true);
// given
TestDtoA dtoA = new TestDtoA("a");
Mockito.when(eventRecorder.deleteRecordById(Mockito.eq("1"))).thenReturn(true);
Mockito.when(eventRecorder.getRecordById(Mockito.eq("1"))).thenReturn(
Optional.ofNullable(
ActionRecord.<TestDtoA>builder()
.expiresAt(LocalDateTime.now())
.action(dtoA)
.build()));
recordsService.invokeListenerByRecordId("1");
// when
undo.undo("1");

// then
Mockito.verify(firstListenerB, Mockito.never()).onUndo(Mockito.any());
Mockito.verify(secondListenerB, Mockito.never()).onUndo(Mockito.any());
Mockito.verify(firstListenerA, Mockito.times(1)).onUndo(Mockito.eq(dtoA));
Mockito.verify(secondListenerA, Mockito.times(1)).onUndo(Mockito.eq(dtoA));
Mockito.verify(firstListenerA).onUndo(Mockito.eq(dtoA));
Mockito.verify(secondListenerA).onUndo(Mockito.eq(dtoA));
}

@Test
void shouldSaveEventWithRecorder() {
// given
TestDtoA action = new TestDtoA("testA");
Mockito.when(idGenerator.generateId()).thenReturn("eventId");

// when
undo.publish(action, Duration.ofSeconds(1));

Mockito.verify(eventRecorder, Mockito.times(1))
.saveRecord(Mockito.eq(new ActionRecord<>("eventId", action, LocalDateTime.parse("2019-02-24T09:33:13"))));
// then
var expectedRecord = new ActionRecord<>("eventId", action, LocalDateTime.parse("2019-02-24T09:33:13"));
Mockito.verify(eventRecorder).saveRecord(Mockito.eq(expectedRecord));
}

@Test
@Disabled
void shouldInvokeAllUndoListenersEvenIfOneFails() {
// given

var testDto = new TestDtoC("testA");
String eventId = "eventId";

Mockito.when(idGenerator.generateId()).thenReturn(eventId);
Mockito.when(eventRecorder.deleteRecordById(Mockito.eq(eventId))).thenReturn(true);
Mockito.when(eventRecorder.getRecordById(Mockito.eq(eventId))).thenReturn(
Optional.ofNullable(
ActionRecord.<TestDtoC>builder()
.expiresAt(LocalDateTime.now())
.action(testDto)
.build()));

// when
undo.publish(testDto, Duration.ofSeconds(1));
var exception = Assertions.assertThrows(UndoListenerInvocationException.class,
() -> undo.undo(eventId));

// then
Assertions.assertEquals(1, exception.getCauses().size());

var inOrder = Mockito.inOrder(faultyListenerC, secondListenerC, thirdListenerB);

inOrder.verify(faultyListenerC).onUndo(Mockito.eq(testDto));
inOrder.verify(secondListenerC).onUndo(Mockito.eq(testDto));
inOrder.verify(thirdListenerB).onUndo(Mockito.eq(testDto));
}

@Test
@Disabled("for demo purposes")
void example() {
/// user will use only Undo component

Expand All @@ -86,5 +139,4 @@ void example() {
// then all the listeners being invoked
}


}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package dev.fomenko.springundocore.config;

import dev.fomenko.springundocore.UndoEventListener;
import dev.fomenko.springundocore.dto.TestDtoC;
import dev.fomenko.springundocore.dto.TestDtoA;
import dev.fomenko.springundocore.dto.TestDtoB;
import dev.fomenko.springundocore.service.TimeSupplier;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
Expand All @@ -26,52 +27,60 @@ public TimeSupplier timeSupplier() {
}

// action listeners for several events
@Bean
public FirstListenerA firstA() {
return new FirstListenerA();
}

@Bean
public SecondListenerA secondA() {
return new SecondListenerA();
}

@Bean
public FirstListenerB firstB() {
return new FirstListenerB();
}

@Bean
public SecondListenerB secondB() {
return new SecondListenerB();
}

@Component
public static class FirstListenerA extends UndoEventListener<TestDtoA> {
@Override
public void onUndo(TestDtoA action) {
System.err.println("FirstListenerA");
}
}

@Component
public static class SecondListenerA extends UndoEventListener<TestDtoA> {
@Override
public void onUndo(TestDtoA action) {
System.err.println("SecondListenerA");
}
}

@Component
public static class FirstListenerB extends UndoEventListener<TestDtoB> {
@Override
public void onUndo(TestDtoB action) {
System.err.println("FirstListenerB");
}
}

@Component
public static class SecondListenerB extends UndoEventListener<TestDtoB> {
@Override
public void onUndo(TestDtoB action) {
System.err.println("SecondListenerB");
}
}

@Component
public static class FaultyListenerC extends UndoEventListener<TestDtoC> {
@Override
public void onUndo(TestDtoC action) {
throw new RuntimeException("FaultyDtoListener");
}
}

@Component
public static class SecondListenerC extends UndoEventListener<TestDtoC> {
@Override
public void onUndo(TestDtoC action) {
System.err.println("SecondListenerC");
}
}

@Component
public static class ThirdListenerC extends UndoEventListener<TestDtoC> {
@Override
public void onUndo(TestDtoC action) {
System.err.println("ThirdListenerC");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.fomenko.springundocore.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestDtoC {
private String data;
}

0 comments on commit 18672e2

Please sign in to comment.