Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Feb 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}