Move tests

This commit is contained in:
2025-03-18 22:09:27 +01:00
parent 8c1eabb9f5
commit c762f31449
25 changed files with 28 additions and 33 deletions

View File

@@ -0,0 +1,62 @@
package eventDemo.business.command
import eventDemo.business.command.command.GameCommand
import eventDemo.business.command.command.IWantToJoinTheGameCommand
import eventDemo.business.entity.GameId
import eventDemo.business.entity.Player
import eventDemo.business.event.projection.projectionListener.PlayerNotificationListener
import eventDemo.business.event.projection.projectionListener.ReactionListener
import eventDemo.business.notification.CommandSuccessNotification
import eventDemo.business.notification.Notification
import eventDemo.business.notification.WelcomeToTheGameNotification
import eventDemo.configuration.injection.appKoinModule
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.launch
import org.koin.dsl.koinApplication
import kotlin.test.assertIs
@OptIn(DelicateCoroutinesApi::class)
class GameCommandHandlerTest :
FunSpec({
test("handle a command should execute the command") {
koinApplication { modules(appKoinModule) }.koin.apply {
val commandHandler by inject<GameCommandHandler>()
val notificationListener by inject<PlayerNotificationListener>()
val gameId = GameId()
val player = Player("Tesla")
val channelCommand = Channel<GameCommand>(Channel.BUFFERED)
val channelNotification = Channel<Notification>(Channel.BUFFERED)
ReactionListener(get(), get()).init()
notificationListener.startListening(
{ channelNotification.trySendBlocking(it) },
player,
gameId,
)
GlobalScope.launch {
commandHandler.handle(
player,
gameId,
channelCommand,
channelNotification,
)
}
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player)).also { sendCommand ->
channelCommand.send(sendCommand)
channelNotification.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
assertIs<WelcomeToTheGameNotification>(channelNotification.receive()).let {
it.players shouldContain player
}
}
}
})

View File

@@ -0,0 +1,8 @@
package eventDemo.business.command
import io.kotest.core.spec.style.FunSpec
class GameCommandRunnerTest :
FunSpec({
test("run should run the correct command") { }
})

View File

@@ -0,0 +1,9 @@
package eventDemo.business.command.command
import io.kotest.core.spec.style.FunSpec
class ICantPlayCommandTest :
FunSpec({
xtest("run should publish the event") { }
})

View File

@@ -0,0 +1,9 @@
package eventDemo.business.command.command
import io.kotest.core.spec.style.FunSpec
class IWantToJoinTheGameCommandTest :
FunSpec({
xtest("run should publish the event") { }
})

View File

@@ -0,0 +1,9 @@
package eventDemo.business.command.command
import io.kotest.core.spec.style.FunSpec
class IWantToPlayCardCommandTest :
FunSpec({
xtest("run should publish the event") { }
})

View File

@@ -0,0 +1,9 @@
package eventDemo.business.command.command
import io.kotest.core.spec.style.FunSpec
class IamReadyToPlayCommandTest :
FunSpec({
xtest("run should publish the event") { }
})

View File

@@ -0,0 +1,104 @@
package eventDemo.business.entity
import eventDemo.allCardCount
import eventDemo.allCards
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeUnique
import io.kotest.matchers.ints.shouldBeExactly
import kotlin.test.assertNotNull
class DeckTest :
FunSpec({
val totalCardsNumber = 104
test("newWithoutPlayers") {
// When
val deck = Deck.newWithoutPlayers()
// Then
deck.stack.size shouldBeExactly totalCardsNumber
deck.discard.size shouldBeExactly 0
deck.playersHands.size shouldBeExactly 0
deck.allCardCount() shouldBeExactly totalCardsNumber
deck.allCards().shouldBeUnique()
deck.allCards().map { it.id }.shouldBeUnique()
}
test("initHands should be generate the hands of all players from the stack") {
// Given
val playerNumbers = 4
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
val deck = Deck.newWithoutPlayers()
// When
val initDeck = deck.initHands(players)
// Then
initDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7)
initDeck.discard.size shouldBeExactly 0
initDeck.playersHands.size shouldBeExactly playerNumbers
initDeck.playersHands.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
initDeck.allCardCount() shouldBeExactly totalCardsNumber
}
test("takeOneCardFromStackTo player") {
// Given
val playerNumbers = 4
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
val deck = Deck.newWithoutPlayers().initHands(players)
val firstPlayer = players.first()
// When
val modifiedDeck = deck.takeOneCardFromStackTo(firstPlayer)
// Then
modifiedDeck.discard.size shouldBeExactly 0
modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1
modifiedDeck.playersHands.size shouldBeExactly playerNumbers
assertNotNull(modifiedDeck.playersHands.getHand(firstPlayer)).size shouldBeExactly 7 + 1
modifiedDeck.playersHands
.filterKeys { it != firstPlayer.id }
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
}
test("putOneCardFromHand") {
// Given
val playerNumbers = 4
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
val deck = Deck.newWithoutPlayers().initHands(players)
val firstPlayer = players.first()
// When
val card = deck.playersHands.getHand(firstPlayer)!!.first()
val modifiedDeck = deck.putOneCardFromHand(firstPlayer, card)
// Then
modifiedDeck.discard.size shouldBeExactly 1
modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7)
modifiedDeck.playersHands.size shouldBeExactly playerNumbers
assertNotNull(modifiedDeck.playersHands.getHand(firstPlayer)).size shouldBeExactly 6
modifiedDeck.playersHands
.filterKeys { it != firstPlayer.id }
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
}
test("placeFirstCardOnDiscard") {
// Given
val playerNumbers = 4
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
val deck = Deck.newWithoutPlayers().initHands(players)
// When
val modifiedDeck = deck.placeFirstCardOnDiscard()
// Then
modifiedDeck.discard.size shouldBeExactly 1
modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1
modifiedDeck.playersHands.size shouldBeExactly playerNumbers
modifiedDeck.playersHands
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
}
})

View File

@@ -0,0 +1,44 @@
package eventDemo.business.entity
import eventDemo.business.entity.Card
import eventDemo.business.entity.Player
import eventDemo.business.entity.PlayersHands
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.ints.shouldBeExactly
import kotlin.test.assertNotNull
class PlayerHandKtTest :
FunSpec({
test("addCards") {
// Given
val playerNumbers = 4
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
val firstPlayer = players.first()
val playersHands = PlayersHands(players)
val card = Card.NumericCard(0, Card.Color.Red)
// When
val newHands: PlayersHands = playersHands.addCards(firstPlayer, listOf(card))
assertNotNull(newHands.getHand(firstPlayer)).size shouldBeExactly 1
assertNotNull(newHands.getHand(players.last())).size shouldBeExactly 0
}
test("removeCard") {
// Given
val playerNumbers = 4
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
val firstPlayer = players.first()
val card1 = Card.NumericCard(1, Card.Color.Red)
val card2 = Card.NumericCard(2, Card.Color.Red)
val playersHands: PlayersHands =
PlayersHands(players)
.addCards(firstPlayer, listOf(card1, card2))
// When
val newHands: PlayersHands = playersHands.removeCard(firstPlayer, card1)
assertNotNull(newHands.getHand(firstPlayer)).size shouldBeExactly 1
assertNotNull(newHands.getHand(players.last())).size shouldBeExactly 0
}
})

View File

@@ -0,0 +1,15 @@
package eventDemo.business.entity
import io.kotest.core.spec.style.FunSpec
class PlayersHandsTest :
FunSpec({
xtest("getHand should return the hand of the player") { }
xtest("removeCard should remove the card") { }
xtest("addCard should add the card to the correct hand") { }
xtest("toPlayersHands should build object from map") { }
})

View File

@@ -0,0 +1,10 @@
package eventDemo.business.event
import io.kotest.core.spec.style.FunSpec
class GameEventHandlerTest :
FunSpec({
xtest("handle event should publish the event to the stream") { }
xtest("handle event should build the registered projection") { }
xtest("handle event should publish the event to the bus") { }
})

View File

@@ -0,0 +1,128 @@
package eventDemo.business.event.projection
import eventDemo.business.entity.Card
import eventDemo.business.entity.GameId
import eventDemo.business.entity.Player
import eventDemo.business.event.event.CardIsPlayedEvent
import eventDemo.business.event.event.GameStartedEvent
import eventDemo.business.event.event.NewPlayerEvent
import eventDemo.business.event.event.PlayerReadyEvent
import eventDemo.business.event.event.disableShuffleDeck
import eventDemo.business.event.projection.gameState.GameState
import eventDemo.business.event.projection.gameState.apply
import eventDemo.libs.event.VersionBuilderLocal
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
import kotlin.test.assertIs
import kotlin.test.assertNotNull
class GameStateBuilderTest :
FunSpec({
test("apply") {
disableShuffleDeck()
val versionBuilder = VersionBuilderLocal()
val gameId = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
GameState(gameId)
.run {
val event =
NewPlayerEvent(
aggregateId = gameId,
player = player1,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
state.isReady shouldBeEqual false
state.isStarted shouldBeEqual false
}
}.run {
val event =
NewPlayerEvent(
aggregateId = gameId,
player = player2,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
state.players shouldBeEqual setOf(player1, player2)
}
}.run {
val event =
PlayerReadyEvent(
aggregateId = gameId,
player = player1,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
state.readyPlayers shouldBeEqual setOf(player1)
}
}.run {
val event =
PlayerReadyEvent(
aggregateId = gameId,
player = player2,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
state.readyPlayers shouldBeEqual setOf(player1, player2)
state.isReady shouldBeEqual true
state.isStarted shouldBeEqual false
}
}.run {
val event =
GameStartedEvent.new(
id = gameId,
players = setOf(player1, player2),
shuffleIsDisabled = true,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
state.isStarted shouldBeEqual true
assertIs<Card.NumericCard>(state.deck.stack.first()).let {
it.number shouldBeEqual 6
it.color shouldBeEqual Card.Color.Red
}
}
}.run {
val playedCard = playableCards(player1)[0]
val event =
CardIsPlayedEvent(
aggregateId = gameId,
card = playedCard,
player = player1,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
assertNotNull(state.cardOnCurrentStack) shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 0
it.color shouldBeEqual Card.Color.Red
}
}
}.run {
val playedCard = playableCards(player2)[0]
val event =
CardIsPlayedEvent(
aggregateId = gameId,
card = playedCard,
player = player2,
version = versionBuilder.buildNextVersion(gameId),
)
apply(event).also { state ->
state.aggregateId shouldBeEqual gameId
assertNotNull(state.cardOnCurrentStack) shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 7
it.color shouldBeEqual Card.Color.Red
}
}
}
}
})

View File

@@ -0,0 +1,131 @@
package eventDemo.business.event.projection
import eventDemo.business.entity.GameId
import eventDemo.business.entity.Player
import eventDemo.business.event.GameEventHandler
import eventDemo.business.event.event.NewPlayerEvent
import eventDemo.business.event.projection.gameState.GameStateRepository
import eventDemo.configuration.injection.appKoinModule
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.koin.core.context.stopKoin
import org.koin.dsl.koinApplication
import kotlin.test.assertNotNull
@OptIn(DelicateCoroutinesApi::class)
class GameStateRepositoryTest :
FunSpec({
val player1 = Player("Tesla")
val player2 = Player(name = "Einstein")
test("GameStateRepository should build the projection when a new event occurs") {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
eventHandler
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
.also { event ->
assertNotNull(repo.getUntil(event)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
assertNotNull(repo.getLast(aggregateId)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
}
stopKoin()
}
test("get should build the last version of the state") {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
eventHandler
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
.also {
assertNotNull(repo.getLast(aggregateId)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
eventHandler
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player2, version = it) }
.also {
assertNotNull(repo.getLast(aggregateId)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1, player2)
}
}
}
}
test("getUntil should build the state until the event") {
repeat(10) {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
val event1 =
eventHandler
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
.also { event1 ->
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
eventHandler
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player2, version = it) }
.also { event2 ->
assertNotNull(repo.getUntil(event2)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1, player2)
}
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.players) shouldBeEqual setOf(player1)
}
}
}
}
}
test("getUntil should be concurrently secure") {
val aggregateId = GameId()
koinApplication { modules(appKoinModule) }.koin.apply {
val repo = get<GameStateRepository>()
val eventHandler = get<GameEventHandler>()
(1..10)
.map { r ->
GlobalScope
.launch {
repeat(100) { r2 ->
val playerX = Player("player$r$r2")
eventHandler
.handle(aggregateId) {
NewPlayerEvent(
aggregateId = aggregateId,
player = playerX,
version = it,
)
}
}
}
}.joinAll()
repo.getLast(aggregateId).run {
lastEventVersion shouldBeEqual 1000
players shouldHaveSize 1000
}
}
}
xtest("get should be concurrently secure") { }
})

View File

@@ -0,0 +1,159 @@
package eventDemo.business.event.projection
import eventDemo.business.entity.Card
import eventDemo.business.entity.Deck
import eventDemo.business.entity.Discard
import eventDemo.business.entity.GameId
import eventDemo.business.entity.Player
import eventDemo.business.entity.PlayersHands
import eventDemo.business.event.projection.gameState.GameState
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
class GameStateTest :
FunSpec({
val player1 = Player("Tesla")
val player2 = Player("Einstein")
test("isReady return false when all players in the game is not ready") {
GameState(
aggregateId = GameId(),
players = setOf(player1, player2),
readyPlayers = setOf(player1),
).isReady shouldBeEqual false
}
test("isReady return true when all players in the game is ready") {
GameState(
aggregateId = GameId(),
players = setOf(player1, player2),
readyPlayers = setOf(player1, player2),
).isReady shouldBeEqual true
}
xtest("nextPlayerTurn") { }
xtest("playerDiffIndex") { }
xtest("cardOnBoardIsForYou") { }
xtest("playableCards") { }
xtest("playerHasNoCardLeft") { }
test("canBePlayThisCard return true when a card can be played on the current game") {
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.NumericCard(5, Card.Color.Red),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.NumericCard(0, Card.Color.Blue),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.NumericCard(0, Card.Color.Green),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.Plus2Card(Card.Color.Red),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.Plus2Card(Card.Color.Red),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.Plus4Card(),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.Plus4Card(),
playedCard = Card.Plus4Card(),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.Plus4Card(),
playedCard = Card.Plus4Card(),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.Plus2Card(Card.Color.Red),
playedCard = Card.Plus2Card(Card.Color.Blue),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.ChangeColorCard(),
) shouldBeEqual true
canBePlayThisCard(
onTheDeck = Card.ReverseCard(Card.Color.Red),
playedCard = Card.NumericCard(0, Card.Color.Red),
) shouldBeEqual true
}
test("canBePlayThisCard return false when a card cannot be played on the current game") {
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.NumericCard(9, Card.Color.Blue),
) shouldBeEqual false
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.Plus2Card(Card.Color.Blue),
) shouldBeEqual false
canBePlayThisCard(
onTheDeck = Card.NumericCard(0, Card.Color.Red),
playedCard = Card.Plus2Card(Card.Color.Blue),
) shouldBeEqual false
canBePlayThisCard(
onTheDeck = Card.Plus2Card(Card.Color.Red),
playedCard = Card.Plus4Card(),
) shouldBeEqual false
canBePlayThisCard(
onTheDeck = Card.Plus2Card(Card.Color.Red),
playedCard = Card.ChangeColorCard(),
) shouldBeEqual false
canBePlayThisCard(
onTheDeck = Card.Plus2Card(Card.Color.Red),
playedCard = Card.NumericCard(0, Card.Color.Red),
) shouldBeEqual false
}
})
private fun canBePlayThisCard(
onTheDeck: Card,
playedCard: Card,
): Boolean {
val player1 = Player("Tesla")
return gameStateWithCard(
player = player1,
onTheDeck = onTheDeck,
playerHand = listOf(playedCard),
).canBePlayThisCard(player1, playedCard)
}
private fun gameStateWithCard(
player: Player,
onTheDeck: Card,
playerHand: List<Card>,
): GameState {
val player2 = Player("Einstein")
return GameState(
aggregateId = GameId(),
players = setOf(player, player2),
readyPlayers = setOf(player, player2),
lastCardPlayer = player2,
deck =
Deck(
discard = Discard(setOf(onTheDeck)),
playersHands = PlayersHands(mapOf(player.id to playerHand)),
),
)
}

View File

@@ -0,0 +1,173 @@
package eventDemo.business.event.projection
import eventDemo.libs.event.AggregateId
import eventDemo.libs.event.Event
import eventDemo.libs.event.EventStore
import eventDemo.libs.event.EventStoreInMemory
import eventDemo.libs.event.VersionBuilderLocal
import eventDemo.libs.event.projection.Projection
import eventDemo.libs.event.projection.ProjectionSnapshotRepositoryInMemory
import eventDemo.libs.event.projection.SnapshotConfig
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.test.assertNotNull
@OptIn(DelicateCoroutinesApi::class)
class ProjectionSnapshotRepositoryInMemoryTest :
FunSpec({
test("when call applyAndPutToCache, the getUntil method must be use the built projection cache") {
val eventStore: EventStore<TestEvents, IdTest> = EventStoreInMemory()
val repo = getSnapshotRepoTest(eventStore)
val aggregateId = IdTest()
val eventOther = Event2Test(value2 = "valOther", version = 1, aggregateId = IdTest())
eventStore.publish(eventOther)
repo.applyAndPutToCache(eventOther)
assertNotNull(repo.getUntil(eventOther)).also {
assertNotNull(it.value) shouldBeEqual "valOther"
}
val event1 = Event1Test(value1 = "val1", version = 1, aggregateId = aggregateId)
eventStore.publish(event1)
repo.applyAndPutToCache(event1)
assertNotNull(repo.getLast(event1.aggregateId)).also {
assertNotNull(it.value) shouldBeEqual "val1"
}
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.value) shouldBeEqual "val1"
}
val event2 = Event2Test(value2 = "val2", version = 2, aggregateId = aggregateId)
eventStore.publish(event2)
repo.applyAndPutToCache(event2)
assertNotNull(repo.getLast(event2.aggregateId)).also {
assertNotNull(it.value) shouldBeEqual "val1val2"
}
assertNotNull(repo.getUntil(event1)).also {
assertNotNull(it.value) shouldBeEqual "val1"
}
assertNotNull(repo.getUntil(event2)).also {
assertNotNull(it.value) shouldBeEqual "val1val2"
}
}
test("ProjectionSnapshotRepositoryInMemory should be thread safe") {
val eventStore: EventStore<TestEvents, IdTest> = EventStoreInMemory()
val repo = getSnapshotRepoTest(eventStore)
val aggregateId = IdTest()
val versionBuilder = VersionBuilderLocal()
val lock = ReentrantLock()
(0..9)
.map {
GlobalScope.launch {
(1..10).map {
val eventX =
lock.withLock {
EventXTest(num = 1, version = versionBuilder.buildNextVersion(aggregateId), aggregateId = aggregateId)
.also { eventStore.publish(it) }
}
repo.applyAndPutToCache(eventX)
}
}
}.joinAll()
assertNotNull(repo.getLast(aggregateId)).num shouldBeEqual 100
}
test("removeOldSnapshot") {
val versionBuilder = VersionBuilderLocal()
val eventStore: EventStore<TestEvents, IdTest> = EventStoreInMemory()
val repo = getSnapshotRepoTest(eventStore, SnapshotConfig(2))
val aggregateId = IdTest()
fun buildEndSendEventX() {
EventXTest(num = 1, version = versionBuilder.buildNextVersion(aggregateId), aggregateId = aggregateId)
.also { eventStore.publish(it) }
.also { repo.applyAndPutToCache(it) }
}
buildEndSendEventX()
repo.getLast(aggregateId).num shouldBeEqual 1
buildEndSendEventX()
repo.getLast(aggregateId).num shouldBeEqual 2
buildEndSendEventX()
repo.getLast(aggregateId).num shouldBeEqual 3
buildEndSendEventX()
repo.getLast(aggregateId).num shouldBeEqual 4
}
})
@JvmInline
private value class IdTest(
override val id: UUID = UUID.randomUUID(),
) : AggregateId
private data class ProjectionTest(
override val aggregateId: IdTest,
override val lastEventVersion: Int = 0,
var value: String? = null,
var num: Int = 0,
) : Projection<IdTest>
private sealed interface TestEvents : Event<IdTest>
private data class Event1Test(
override val eventId: UUID = UUID.randomUUID(),
override val aggregateId: IdTest,
override val createdAt: Instant = Clock.System.now(),
override val version: Int,
val value1: String,
) : TestEvents
private data class Event2Test(
override val eventId: UUID = UUID.randomUUID(),
override val aggregateId: IdTest,
override val createdAt: Instant = Clock.System.now(),
override val version: Int,
val value2: String,
) : TestEvents
private data class EventXTest(
override val eventId: UUID = UUID.randomUUID(),
override val aggregateId: IdTest,
override val createdAt: Instant = Clock.System.now(),
override val version: Int,
val num: Int,
) : TestEvents
private fun getSnapshotRepoTest(
eventStore: EventStore<TestEvents, IdTest>,
snapshotConfig: SnapshotConfig = SnapshotConfig(2000),
): ProjectionSnapshotRepositoryInMemory<TestEvents, ProjectionTest, IdTest> =
ProjectionSnapshotRepositoryInMemory(
eventStore = eventStore,
initialStateBuilder = { aggregateId: IdTest -> ProjectionTest(aggregateId) },
snapshotCacheConfig = snapshotConfig,
) { event ->
this.let { projection ->
when (event) {
is Event1Test -> {
projection.copy(value = (projection.value ?: "") + event.value1)
}
is Event2Test -> {
projection.copy(value = (projection.value ?: "") + event.value2)
}
is EventXTest -> {
projection.copy(num = projection.num + event.num)
}
}.copy(
lastEventVersion = event.version,
)
}
}