test: fix GameStateRouteTest

This commit is contained in:
2025-04-14 21:29:36 +02:00
parent 4086a21dcb
commit a7f82e6bce
10 changed files with 35 additions and 66 deletions

View File

@@ -30,7 +30,10 @@ class ReactionListener(
} }
} }
} else { } else {
logger.error { "${this::class.simpleName} is already init for this bus" } "${this::class.simpleName} is already init for this bus".let {
logger.error { it }
error(it)
}
} }
} }

View File

@@ -13,7 +13,7 @@ class BusInMemory<E>(
override suspend fun publish(item: E) { override suspend fun publish(item: E) {
withLoggingContext("busItem" to item.toString()) { withLoggingContext("busItem" to item.toString()) {
logger.info { "Item sent to the bus: $item" } logger.info { "Item sent to the bus" }
subscribers subscribers
.forEach { .forEach {
coroutineScope { coroutineScope {

View File

@@ -6,6 +6,7 @@ import com.rabbitmq.client.Connection
import com.rabbitmq.client.ConnectionFactory import com.rabbitmq.client.ConnectionFactory
import com.rabbitmq.client.DefaultConsumer import com.rabbitmq.client.DefaultConsumer
import com.rabbitmq.client.Envelope import com.rabbitmq.client.Envelope
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.utils.io.core.toByteArray import io.ktor.utils.io.core.toByteArray
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -15,6 +16,8 @@ class BusInRabbitMQ<E>(
private val objectToString: (E) -> String, private val objectToString: (E) -> String,
private val stringToObject: (String) -> E, private val stringToObject: (String) -> E,
) : Bus<E> { ) : Bus<E> {
private val logger = KotlinLogging.logger { }
private val connection: Connection = connectionFactory.newConnection() private val connection: Connection = connectionFactory.newConnection()
get() { get() {
return if (field.isOpen) { return if (field.isOpen) {
@@ -50,6 +53,7 @@ class BusInRabbitMQ<E>(
objectToString(item).toByteArray(), objectToString(item).toByteArray(),
) )
} }
logger.info { "Item sent to the bus" }
} }
override fun subscribe(block: suspend (E) -> Unit): Bus.Subscription { override fun subscribe(block: suspend (E) -> Unit): Bus.Subscription {

View File

@@ -7,7 +7,7 @@ interface ProjectionSnapshotRepository<E : Event<ID>, P : Projection<ID>, ID : A
/** /**
* Create a snapshot for the event * Create a snapshot for the event
*/ */
fun applyAndPutToCache(event: E): P suspend fun applyAndPutToCache(event: E): P
fun count(aggregateId: ID): Int fun count(aggregateId: ID): Int

View File

@@ -32,7 +32,7 @@ class ProjectionSnapshotRepositoryInMemory<E : Event<ID>, P : Projection<ID>, ID
* 5. save it * 5. save it
* 6. remove old one * 6. remove old one
*/ */
override fun applyAndPutToCache(event: E): P = override suspend fun applyAndPutToCache(event: E): P =
getUntil(event) getUntil(event)
.also { .also {
withLoggingContext("projection" to it.toString()) { withLoggingContext("projection" to it.toString()) {

View File

@@ -33,7 +33,7 @@ class ProjectionSnapshotRepositoryInRedis<E : Event<ID>, P : Projection<ID>, ID
* 5. save it * 5. save it
* 6. remove old one * 6. remove old one
*/ */
override fun applyAndPutToCache(event: E): P = override suspend fun applyAndPutToCache(event: E): P =
getUntil(event) getUntil(event)
.also { .also {
withLoggingContext(mapOf("projection" to it.toString(), "event" to event.toString())) { withLoggingContext(mapOf("projection" to it.toString(), "event" to event.toString())) {
@@ -131,17 +131,14 @@ class ProjectionSnapshotRepositoryInRedis<E : Event<ID>, P : Projection<ID>, ID
} }
private fun save(projection: P) { private fun save(projection: P) {
repeat(5) {
val added = jedis.zadd(projection.redisKey, projection.lastEventVersion.toDouble(), projectionToJson(projection)) val added = jedis.zadd(projection.redisKey, projection.lastEventVersion.toDouble(), projectionToJson(projection))
if (added < 1) { if (added < 1) {
logger.error { "Projection NOT saved" } logger.error { "Projection NOT saved (already exists)" }
} else { } else {
logger.info { "Projection saved" } logger.info { "Projection saved" }
return
}
}
jedis.expire(projection.redisKey, snapshotCacheConfig.maxSnapshotCacheTtl.inWholeSeconds) jedis.expire(projection.redisKey, snapshotCacheConfig.maxSnapshotCacheTtl.inWholeSeconds)
} }
}
/** /**
* Apply events to the projection. * Apply events to the projection.
@@ -195,7 +192,7 @@ class ProjectionSnapshotRepositoryInRedis<E : Event<ID>, P : Projection<ID>, ID
it.last.toDouble(), it.last.toDouble(),
).also { removedCount -> ).also { removedCount ->
if (removedCount > 0) { if (removedCount > 0) {
logger.info { logger.debug {
"$removedCount snapshot removed Modulo(${snapshotCacheConfig.modulo}) (${it.first} to ${it.last}) [lastVersion=$lastVersion]" "$removedCount snapshot removed Modulo(${snapshotCacheConfig.modulo}) (${it.first} to ${it.last}) [lastVersion=$lastVersion]"
} }
} }

View File

@@ -48,7 +48,6 @@ fun testApplicationWithConfig(
application { application {
val koin = getKoin() val koin = getKoin()
koin.cleanDataTest() koin.cleanDataTest()
koin.configureGameListener()
configBuilder(koin) configBuilder(koin)
} }
block() block()

View File

@@ -8,9 +8,7 @@ import eventDemo.business.event.event.NewPlayerEvent
import eventDemo.business.event.event.PlayerReadyEvent import eventDemo.business.event.event.PlayerReadyEvent
import eventDemo.business.event.projection.gameList.GameList import eventDemo.business.event.projection.gameList.GameList
import eventDemo.testApplicationWithConfig import eventDemo.testApplicationWithConfig
import io.kotest.assertions.nondeterministic.continually
import io.kotest.assertions.nondeterministic.eventually import io.kotest.assertions.nondeterministic.eventually
import io.kotest.assertions.nondeterministic.eventuallyConfig
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.collections.shouldHaveSize
@@ -24,7 +22,6 @@ import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class GameListRouteTest : class GameListRouteTest :
@@ -60,13 +57,7 @@ class GameListRouteTest :
}, },
) { ) {
// Wait until the projection is created // Wait until the projection is created
eventually( eventually(1.seconds) {
eventuallyConfig {
duration = 3.seconds
interval = 300.milliseconds
},
) {
continually(1.seconds) {
httpClient() httpClient()
.get("/games") { .get("/games") {
withAuth(player1) withAuth(player1)
@@ -83,7 +74,6 @@ class GameListRouteTest :
} }
} }
} }
}
test("/games return a game with status IS_STARTED") { test("/games return a game with status IS_STARTED") {
val gameId = GameId() val gameId = GameId()

View File

@@ -5,7 +5,6 @@ import eventDemo.business.entity.GameId
import eventDemo.business.entity.Player import eventDemo.business.entity.Player
import eventDemo.business.event.GameEventHandler import eventDemo.business.event.GameEventHandler
import eventDemo.business.event.event.CardIsPlayedEvent import eventDemo.business.event.event.CardIsPlayedEvent
import eventDemo.business.event.event.GameStartedEvent
import eventDemo.business.event.event.NewPlayerEvent import eventDemo.business.event.event.NewPlayerEvent
import eventDemo.business.event.event.PlayerReadyEvent import eventDemo.business.event.event.PlayerReadyEvent
import eventDemo.business.event.event.disableShuffleDeck import eventDemo.business.event.event.disableShuffleDeck
@@ -22,7 +21,6 @@ import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertIs import kotlin.test.assertIs
@@ -66,23 +64,13 @@ class GameStateRouteTest :
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player2, it) } eventHandler.handle(gameId) { NewPlayerEvent(gameId, player2, it) }
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player1, it) } eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player1, it) }
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player2, it) } eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player2, it) }
eventHandler.handle(gameId) { lastPlayedCard = eventually { stateRepo.getLast(gameId).playableCards(player1).first() }
GameStartedEvent.new(
gameId,
setOf(player1, player2),
it,
shuffleIsDisabled = true,
)
}
delay(100)
lastPlayedCard = stateRepo.getLast(gameId).playableCards(player1).first()
assertNotNull(lastPlayedCard) assertNotNull(lastPlayedCard)
.let { assertIs<Card.NumericCard>(lastPlayedCard) } .let { assertIs<Card.NumericCard>(lastPlayedCard) }
.let { .let {
it.number shouldBeEqual 0 it.number shouldBeEqual 0
it.color shouldBeEqual Card.Color.Red it.color shouldBeEqual Card.Color.Red
} }
delay(100)
eventHandler.handle(gameId) { eventHandler.handle(gameId) {
CardIsPlayedEvent( CardIsPlayedEvent(
gameId, gameId,
@@ -91,7 +79,6 @@ class GameStateRouteTest :
it, it,
) )
} }
delay(100)
} }
}) { }) {
eventually(1.seconds) { eventually(1.seconds) {
@@ -131,23 +118,13 @@ class GameStateRouteTest :
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player2, it) } eventHandler.handle(gameId) { NewPlayerEvent(gameId, player2, it) }
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player1, it) } eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player1, it) }
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player2, it) } eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player2, it) }
eventHandler.handle(gameId) { lastPlayedCard = eventually { stateRepo.getLast(gameId).playableCards(player1).first() }
GameStartedEvent.new(
gameId,
setOf(player1, player2),
it,
shuffleIsDisabled = true,
)
}
delay(100)
lastPlayedCard = stateRepo.getLast(gameId).playableCards(player1).first()
assertNotNull(lastPlayedCard) assertNotNull(lastPlayedCard)
.let { assertIs<Card.NumericCard>(lastPlayedCard) } .let { assertIs<Card.NumericCard>(lastPlayedCard) }
.let { .let {
it.number shouldBeEqual 0 it.number shouldBeEqual 0
it.color shouldBeEqual Card.Color.Red it.color shouldBeEqual Card.Color.Red
} }
delay(100)
eventHandler.handle(gameId) { eventHandler.handle(gameId) {
CardIsPlayedEvent( CardIsPlayedEvent(
gameId, gameId,
@@ -156,7 +133,6 @@ class GameStateRouteTest :
it, it,
) )
} }
delay(100)
} }
}) { }) {
eventually(1.seconds) { eventually(1.seconds) {

View File

@@ -158,7 +158,7 @@ class ProjectionSnapshotRepositoryTest :
val versionBuilder = VersionBuilderLocal() val versionBuilder = VersionBuilderLocal()
val aggregateId = IdTest() val aggregateId = IdTest()
fun buildEndSendEventX() { suspend fun buildEndSendEventX() {
EventXTest(num = 1, version = versionBuilder.buildNextVersion(aggregateId), aggregateId = aggregateId) EventXTest(num = 1, version = versionBuilder.buildNextVersion(aggregateId), aggregateId = aggregateId)
.also { eventStore.publish(it) } .also { eventStore.publish(it) }
.also { repo.applyAndPutToCache(it) } .also { repo.applyAndPutToCache(it) }