Added signal event

This commit is contained in:
Brage Skjønborg 2026-01-31 18:16:23 +01:00
parent f5cc32487f
commit a9779d2371
3 changed files with 96 additions and 23 deletions

View File

@ -2,6 +2,7 @@ package no.iktdev.eventi.events
import no.iktdev.eventi.models.DeleteEvent import no.iktdev.eventi.models.DeleteEvent
import no.iktdev.eventi.models.Event import no.iktdev.eventi.models.Event
import no.iktdev.eventi.models.SignalEvent
import no.iktdev.eventi.stores.EventStore import no.iktdev.eventi.stores.EventStore
import java.util.UUID import java.util.UUID
@ -11,6 +12,7 @@ open class EventDispatcher(val eventStore: EventStore) {
val derivedFromIds = events.mapNotNull { it.metadata.derivedFromId }.flatten().toSet() val derivedFromIds = events.mapNotNull { it.metadata.derivedFromId }.flatten().toSet()
val deletedEventIds = events.filterIsInstance<DeleteEvent>().map { it.deletedEventId } val deletedEventIds = events.filterIsInstance<DeleteEvent>().map { it.deletedEventId }
val candidates = events val candidates = events
.filterNot { it is SignalEvent }
.filter { it.eventId !in derivedFromIds } .filter { it.eventId !in derivedFromIds }
.filter { it.eventId !in deletedEventIds } .filter { it.eventId !in deletedEventIds }

View File

@ -43,6 +43,6 @@ abstract class DeleteEvent(
open val deletedEventId: UUID open val deletedEventId: UUID
) : Event() ) : Event()
abstract class SignalEvent(): Event()

View File

@ -8,6 +8,7 @@ import no.iktdev.eventi.events.EventListenerRegistry
import no.iktdev.eventi.events.EventTypeRegistry import no.iktdev.eventi.events.EventTypeRegistry
import no.iktdev.eventi.models.DeleteEvent import no.iktdev.eventi.models.DeleteEvent
import no.iktdev.eventi.models.Event import no.iktdev.eventi.models.Event
import no.iktdev.eventi.models.SignalEvent
import no.iktdev.eventi.testUtil.wipe import no.iktdev.eventi.testUtil.wipe
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
@ -19,12 +20,14 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.util.UUID import java.util.UUID
@DisplayName(""" @DisplayName(
"""
EventDispatcher EventDispatcher
Når hendelser dispatches til lyttere Når hendelser dispatches til lyttere
Hvis hendelsene inneholder avledede, slettede eller nye events Hvis hendelsene inneholder avledede, slettede eller nye events
skal dispatcheren håndtere filtrering, replays og historikk korrekt skal dispatcheren håndtere filtrering, replays og historikk korrekt
""") """
)
class EventDispatcherTest : TestBase() { class EventDispatcherTest : TestBase() {
val dispatcher = EventDispatcher(eventStore) val dispatcher = EventDispatcher(eventStore)
@ -51,11 +54,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en TriggerEvent dispatches Når en TriggerEvent dispatches
Hvis en lytter produserer én DerivedEvent Hvis en lytter produserer én DerivedEvent
skal kun én ny event produseres og prosessen stoppe skal kun én ny event produseres og prosessen stoppe
""") """
)
fun shouldProduceOneEventAndStop() { fun shouldProduceOneEventAndStop() {
ProducingListener() ProducingListener()
@ -72,11 +77,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en event allerede har avledet en DerivedEvent Når en event allerede har avledet en DerivedEvent
Hvis dispatcheren replays historikken Hvis dispatcheren replays historikken
skal ikke DerivedEvent produseres nytt skal ikke DerivedEvent produseres nytt
""") """
)
fun shouldSkipAlreadyDerivedEvents() { fun shouldSkipAlreadyDerivedEvents() {
ProducingListener() ProducingListener()
@ -91,11 +98,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når flere events dispatches Når flere events dispatches
Hvis en lytter mottar en event Hvis en lytter mottar en event
skal hele historikken leveres i context skal hele historikken leveres i context
""") """
)
fun shouldPassFullContextToListener() { fun shouldPassFullContextToListener() {
val listener = ContextCapturingListener() val listener = ContextCapturingListener()
@ -107,11 +116,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en replay skjer Når en replay skjer
Hvis en event allerede har produsert en DerivedEvent Hvis en event allerede har produsert en DerivedEvent
skal ikke DerivedEvent produseres nytt skal ikke DerivedEvent produseres nytt
""") """
)
fun shouldBehaveDeterministicallyAcrossReplays() { fun shouldBehaveDeterministicallyAcrossReplays() {
ProducingListener() ProducingListener()
@ -125,11 +136,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en DeleteEvent peker en tidligere event Når en DeleteEvent peker en tidligere event
Hvis dispatcheren filtrerer kandidater Hvis dispatcheren filtrerer kandidater
skal slettede events ikke leveres som kandidater skal slettede events ikke leveres som kandidater
""") """
)
fun shouldNotDeliverDeletedEventsAsCandidates() { fun shouldNotDeliverDeletedEventsAsCandidates() {
val dispatcher = EventDispatcher(eventStore) val dispatcher = EventDispatcher(eventStore)
val received = mutableListOf<Event>() val received = mutableListOf<Event>()
@ -162,11 +175,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en DeleteEvent dispatches alene Når en DeleteEvent dispatches alene
Hvis en lytter reagerer DeleteEvent Hvis en lytter reagerer DeleteEvent
skal DeleteEvent leveres som kandidat skal DeleteEvent leveres som kandidat
""") """
)
fun shouldDeliverDeleteEventToListenersThatReactToIt() { fun shouldDeliverDeleteEventToListenersThatReactToIt() {
val received = mutableListOf<Event>() val received = mutableListOf<Event>()
object : EventListener() { object : EventListener() {
@ -183,11 +198,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en event har avledet en ny event Når en event har avledet en ny event
Hvis dispatcheren replays historikken Hvis dispatcheren replays historikken
skal ikke original-eventen leveres som kandidat igjen skal ikke original-eventen leveres som kandidat igjen
""") """
)
fun shouldNotRedeliverEventsThatHaveProducedDerivedEvents() { fun shouldNotRedeliverEventsThatHaveProducedDerivedEvents() {
ProducingListener() ProducingListener()
@ -211,11 +228,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en DeleteEvent slettet en tidligere event Når en DeleteEvent slettet en tidligere event
Hvis dispatcheren bygger historikk Hvis dispatcheren bygger historikk
skal slettede events ikke være med i history skal slettede events ikke være med i history
""") """
)
fun historyShouldExcludeDeletedEvents() { fun historyShouldExcludeDeletedEvents() {
val dispatcher = EventDispatcher(eventStore) val dispatcher = EventDispatcher(eventStore)
@ -238,11 +257,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en DeleteEvent slettet en event Når en DeleteEvent slettet en event
Hvis andre events fortsatt er gyldige Hvis andre events fortsatt er gyldige
skal history kun inneholde de ikke-slettede events skal history kun inneholde de ikke-slettede events
""") """
)
fun historyShouldKeepNonDeletedEvents() { fun historyShouldKeepNonDeletedEvents() {
val dispatcher = EventDispatcher(eventStore) val dispatcher = EventDispatcher(eventStore)
@ -267,11 +288,13 @@ class EventDispatcherTest : TestBase() {
} }
@Test @Test
@DisplayName(""" @DisplayName(
"""
Når en DeleteEvent er kandidat Når en DeleteEvent er kandidat
Hvis historikken kun inneholder slettede events Hvis historikken kun inneholder slettede events
skal history være tom skal history være tom
""") """
)
fun deleteEventShouldBeDeliveredButHistoryEmpty() { fun deleteEventShouldBeDeliveredButHistoryEmpty() {
val dispatcher = EventDispatcher(eventStore) val dispatcher = EventDispatcher(eventStore)
@ -295,6 +318,54 @@ class EventDispatcherTest : TestBase() {
assertTrue(receivedHistory.isEmpty()) assertTrue(receivedHistory.isEmpty())
} }
@Test
@DisplayName(
"""
Når en SignalEvent dispatches
Hvis SignalEvent ikke skal være kandidat
skal den ikke leveres til lyttere, men fortsatt være i historikken
"""
)
fun shouldNotDeliverSignalEventAsCandidate() {
// Arrange
class TestSignalEvent : SignalEvent()
EventTypeRegistry.register(listOf(TestSignalEvent::class.java,))
val received = mutableListOf<Event>()
var finalHistory: List<Event>? = null
object : EventListener() {
override fun onEvent(event: Event, history: List<Event>): Event? {
received += event
finalHistory = history
return null
}
}
val refId = UUID.randomUUID()
val trigger = TriggerEvent().usingReferenceId(refId)
val signal = TestSignalEvent().usingReferenceId(refId)
// Act
dispatcher.dispatch(trigger.referenceId, listOf(trigger, signal))
// Assert
// 1) TriggerEvent skal leveres
assertTrue(received.any { it is TriggerEvent }) {
"TriggerEvent skal leveres som kandidat"
}
// 2) SignalEvent skal IKKE leveres
assertFalse(received.any { it is TestSignalEvent }) {
"SignalEvent skal ikke leveres som kandidat"
}
assertNotNull(finalHistory)
assertTrue(finalHistory!!.any { it is TestSignalEvent }) {
"SignalEvent skal være i historikken selv om den ikke er kandidat"
}
}
// --- Test helpers --- // --- Test helpers ---
class ProducingListener : EventListener() { class ProducingListener : EventListener() {