diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
new file mode 100644
index 0000000..9437b7c
--- /dev/null
+++ b/.github/workflows/pr.yml
@@ -0,0 +1,40 @@
+name: Test & Coverage
+on:
+ pull_request:
+ branches:
+ - '*'
+
+jobs:
+ test-and-coverage:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v3
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+
+ - name: Set up the Maven dependencies caching
+ uses: actions/cache@v3
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2
+
+ - name: Run tests
+ run: mvn --batch-mode --update-snapshots verify -Dgpg.skip=true
+
+ - name: Add coverage
+ uses: madrapps/jacoco-report@v1.6.1
+ with:
+ paths: |
+ ${{ github.workspace }}/**/target/site/jacoco/jacoco.xml
+ token: ${{ secrets.GITHUB_TOKEN }}
+ title: '### :zap: Coverage report'
+ update-comment: true
+ min-coverage-overall: 80
+ min-coverage-changed-files: 60
+ continue-on-error: false
diff --git a/pom.xml b/pom.xml
index e28858a..bf36dc4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,6 +26,7 @@
21
+ 4.11.0
@@ -58,6 +59,18 @@
spring-kafka-test
test
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
@@ -122,9 +135,27 @@
false
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+
-
scm:git:https://github.com/sipios/spring-data-event.git
scm:git:git@github.com:sipios/spring-data-event.git
diff --git a/src/test/java/com/sipios/spring/data/event/broadcaster/DataEventBroadcasterTest.java b/src/test/java/com/sipios/spring/data/event/broadcaster/DataEventBroadcasterTest.java
new file mode 100644
index 0000000..c43235a
--- /dev/null
+++ b/src/test/java/com/sipios/spring/data/event/broadcaster/DataEventBroadcasterTest.java
@@ -0,0 +1,113 @@
+package com.sipios.spring.data.event.broadcaster;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.CallbackException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.springframework.kafka.core.KafkaTemplate;
+
+public class DataEventBroadcasterTest {
+ private DataEventBroadcaster broadcaster;
+ private KafkaTemplate kafkaTemplate;
+ private ObjectMapper objectMapper;
+
+ @BeforeEach
+ void beforeEach() {
+ kafkaTemplate = mock(KafkaTemplate.class);
+ objectMapper = new ObjectMapper();
+ broadcaster = new DataEventBroadcaster(kafkaTemplate, objectMapper);
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "testEntity.created, testEntity.created",
+ "'', testentity.created"
+ })
+ void testBroadcastEntityCreated(String topicLabel, String expectedTopic) throws Exception {
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ String expectedJson = objectMapper.writeValueAsString(entity);
+
+ broadcaster.broadcastEntityCreated(entity, topicLabel);
+
+ verify(kafkaTemplate).send(eq(expectedTopic), eq(expectedJson));
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "testEntity.updated, testEntity.updated",
+ "'', testentity.updated"
+ })
+ void testBroadcastEntityUpdated(String topicLabel, String expectedTopic) throws Exception {
+ TestEntity entity = new TestEntity(1, "Test Name", false);
+ String expectedJson = objectMapper.writeValueAsString(entity);
+
+ broadcaster.broadcastEntityUpdated(entity, topicLabel);
+
+ verify(kafkaTemplate).send(eq(expectedTopic), eq(expectedJson));
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "testEntity.deleted, testEntity.deleted",
+ "'', testentity.deleted"
+ })
+ void testBroadcastEntityDeleted(String topicLabel, String expectedTopic) throws Exception {
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ String expectedJson = objectMapper.writeValueAsString(entity);
+
+ broadcaster.broadcastEntityDeleted(entity, topicLabel);
+
+ verify(kafkaTemplate).send(eq(expectedTopic), eq(expectedJson));
+ }
+
+ @Test
+ void testBroadcastEntityCreatedJsonProcessingException() throws Exception {
+ objectMapper = mock(ObjectMapper.class);
+ broadcaster = new DataEventBroadcaster(kafkaTemplate, objectMapper);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+
+ when(objectMapper.writeValueAsString(entity)).thenThrow(new JsonProcessingException("JSON processing error") {});
+
+ assertThrows(CallbackException.class, () -> broadcaster.broadcastEntityCreated(entity, "testEntity.created"));
+ }
+
+ @Test
+ void testBroadcastEntityUpdatedJsonProcessingException() throws Exception {
+ objectMapper = mock(ObjectMapper.class);
+ broadcaster = new DataEventBroadcaster(kafkaTemplate, objectMapper);
+ TestEntity entity = new TestEntity(1, "Test Name", false);
+
+ when(objectMapper.writeValueAsString(entity)).thenThrow(new JsonProcessingException("JSON processing error") {});
+
+ assertThrows(CallbackException.class, () -> broadcaster.broadcastEntityUpdated(entity, "testEntity.updated"));
+ }
+
+ @Test
+ void testBroadcastEntityDeletedJsonProcessingException() throws Exception {
+ objectMapper = mock(ObjectMapper.class);
+ broadcaster = new DataEventBroadcaster(kafkaTemplate, objectMapper);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+
+ when(objectMapper.writeValueAsString(entity)).thenThrow(new JsonProcessingException("JSON processing error") {});
+
+ assertThrows(CallbackException.class, () -> broadcaster.broadcastEntityDeleted(entity, "testEntity.deleted"));
+ }
+
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ public static class TestEntity {
+ private int id;
+ private String name;
+ private boolean active;
+ }
+}
diff --git a/src/test/java/com/sipios/spring/data/event/listener/DataEventListenerTest.java b/src/test/java/com/sipios/spring/data/event/listener/DataEventListenerTest.java
new file mode 100644
index 0000000..e61c6de
--- /dev/null
+++ b/src/test/java/com/sipios/spring/data/event/listener/DataEventListenerTest.java
@@ -0,0 +1,151 @@
+package com.sipios.spring.data.event.listener;
+
+import com.sipios.spring.data.event.annotation.DataEventEntity;
+import com.sipios.spring.data.event.broadcaster.DataEventBroadcaster;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.event.spi.PostDeleteEvent;
+import org.hibernate.event.spi.PostInsertEvent;
+import org.hibernate.event.spi.PostUpdateEvent;
+import org.junit.jupiter.api.Nested;
+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 static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class DataEventListenerTest {
+ @Mock
+ private DataEventBroadcaster dataEventBroadcaster;
+
+ @InjectMocks
+ private DataEventListener listener;
+
+ @Nested
+ class AnnotatedEntityTests {
+
+ @Test
+ public void testOnPostInsert() {
+ PostInsertEvent event = mock(PostInsertEvent.class);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+
+ listener.onPostInsert(event);
+
+ verify(dataEventBroadcaster).broadcastEntityCreated(entity, "testEntity.created");
+ }
+
+ @Test
+ public void testOnPostUpdate() {
+ PostUpdateEvent event = mock(PostUpdateEvent.class);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+
+ listener.onPostUpdate(event);
+
+ verify(dataEventBroadcaster).broadcastEntityUpdated(entity, "testEntity.updated");
+ }
+
+ @Test
+ public void testOnPostDelete() {
+ PostDeleteEvent event = mock(PostDeleteEvent.class);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+
+ listener.onPostDelete(event);
+
+ verify(dataEventBroadcaster).broadcastEntityDeleted(entity, "testEntity.deleted");
+ }
+
+ @Test
+ public void testOnPostInsertJsonProcessingException() throws Exception {
+ PostInsertEvent event = mock(PostInsertEvent.class);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+ doThrow(new RuntimeException("JSON processing error")).when(dataEventBroadcaster).broadcastEntityCreated(any(), any());
+
+ assertThrows(RuntimeException.class, () -> listener.onPostInsert(event));
+ }
+
+ @Test
+ public void testOnPostUpdateJsonProcessingException() throws Exception {
+ PostUpdateEvent event = mock(PostUpdateEvent.class);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+ doThrow(new RuntimeException("JSON processing error")).when(dataEventBroadcaster).broadcastEntityUpdated(any(), any());
+
+ assertThrows(RuntimeException.class, () -> listener.onPostUpdate(event));
+ }
+
+ @Test
+ public void testOnPostDeleteJsonProcessingException() throws Exception {
+ PostDeleteEvent event = mock(PostDeleteEvent.class);
+ TestEntity entity = new TestEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+ doThrow(new RuntimeException("JSON processing error")).when(dataEventBroadcaster).broadcastEntityDeleted(any(), any());
+
+ assertThrows(RuntimeException.class, () -> listener.onPostDelete(event));
+ }
+
+ @DataEventEntity(creationTopic = "testEntity.created", updateTopic = "testEntity.updated", deletionTopic = "testEntity.deleted")
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ private static class TestEntity {
+ private int id;
+ private String name;
+ private boolean active;
+ }
+ }
+
+ @Nested
+ class NonAnnotatedEntityTests {
+
+ @Test
+ public void testOnPostInsert() {
+ PostInsertEvent event = mock(PostInsertEvent.class);
+ NonAnnotatedEntity entity = new NonAnnotatedEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+
+ listener.onPostInsert(event);
+
+ verify(dataEventBroadcaster, never()).broadcastEntityCreated(any(), any());
+ }
+
+ @Test
+ public void testOnPostUpdate() {
+ PostUpdateEvent event = mock(PostUpdateEvent.class);
+ NonAnnotatedEntity entity = new NonAnnotatedEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+
+ listener.onPostUpdate(event);
+
+ verify(dataEventBroadcaster, never()).broadcastEntityUpdated(any(), any());
+ }
+
+ @Test
+ public void testOnPostDelete() {
+ PostDeleteEvent event = mock(PostDeleteEvent.class);
+ NonAnnotatedEntity entity = new NonAnnotatedEntity(1, "Test Name", true);
+ when(event.getEntity()).thenReturn(entity);
+
+ listener.onPostDelete(event);
+
+ verify(dataEventBroadcaster, never()).broadcastEntityDeleted(any(), any());
+ }
+
+ @Getter
+ @Setter
+ @AllArgsConstructor
+ private static class NonAnnotatedEntity {
+ private int id;
+ private String name;
+ private boolean active;
+ }
+ }
+}