Fix: close all connections after each tests

This commit is contained in:
2025-04-10 22:44:26 +02:00
parent aa9dac74e8
commit 0aa13b9299
14 changed files with 307 additions and 226 deletions

View File

@@ -1,5 +1,6 @@
package eventDemo
import com.zaxxer.hikari.HikariDataSource
import eventDemo.business.entity.Card
import eventDemo.business.entity.Deck
import eventDemo.configuration.business.configureGameListener
@@ -26,10 +27,12 @@ fun Deck.allCards(): Set<Card> =
suspend fun <T> testKoinApplicationWithConfig(block: suspend Koin.() -> T): T =
koinApplication { modules(appKoinModule(ApplicationConfig("application.conf").configuration())) }
.koin
.apply {
.run {
cleanDataTest()
configureGameListener()
}.block()
block()
.apply { get<HikariDataSource>().close() }
}
@KtorDsl
fun testApplicationWithConfig(
@@ -53,14 +56,14 @@ fun testApplicationWithConfig(
}
fun DataSource.cleanEventSource() {
this.connection
.prepareStatement(
"""
truncate event_stream;
""".trimIndent(),
).use {
it.execute()
}
this.connection.use {
it
.prepareStatement(
"""
truncate event_stream;
""".trimIndent(),
).execute()
}
}
fun UnifiedJedis.cleanProjections() {

View File

@@ -24,6 +24,7 @@ import eventDemo.business.notification.WelcomeToTheGameNotification
import eventDemo.libs.event.projection.ProjectionSnapshotRepositoryInMemory
import eventDemo.testKoinApplicationWithConfig
import io.kotest.assertions.nondeterministic.until
import io.kotest.core.NamedTag
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
@@ -43,6 +44,8 @@ import kotlin.time.Duration.Companion.seconds
@DelicateCoroutinesApi
class GameSimulationTest :
FunSpec({
tags(NamedTag("postgresql"))
test("Simulation of a game") {
withTimeout(2.seconds) {
disableShuffleDeck()
@@ -59,120 +62,120 @@ class GameSimulationTest :
var player1HasJoin = false
val player1Job =
launch {
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player1)).also { sendCommand ->
channelCommand1.send(sendCommand)
channelNotification1.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
player1HasJoin = true
channelNotification1.receive().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
}
channelNotification1.receive().let {
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
}
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player1)).also { sendCommand ->
channelCommand1.send(sendCommand)
channelNotification1.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
val player1Hand =
channelNotification1.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
playedCard1 = player1Hand.first()
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelNotification1.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
}
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player1, player1Hand.first())).also { sendCommand ->
channelCommand1.send(sendCommand)
channelNotification1.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
channelNotification1.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player2
card shouldBeEqual assertNotNull(playedCard2)
}
}
}
val player2Job =
launch {
until(1.seconds) { player1HasJoin }
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player2)).also { sendCommand ->
channelCommand2.send(sendCommand)
channelNotification2.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
channelNotification2.receive().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
}
channelNotification2.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
}
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player2)).also { sendCommand ->
channelCommand2.send(sendCommand)
channelNotification2.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
val player2Hand =
channelNotification2.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelNotification2.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player1
card shouldBeEqual assertNotNull(playedCard1)
}
}
playedCard2 = player2Hand.first()
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player2, player2Hand.first())).also { sendCommand ->
channelCommand2.send(sendCommand)
channelNotification2.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
}
testKoinApplicationWithConfig {
val player1Job =
launch {
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player1)).also { sendCommand ->
channelCommand1.send(sendCommand)
channelNotification1.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
player1HasJoin = true
channelNotification1.receive().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
}
channelNotification1.receive().let {
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
}
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player1)).also { sendCommand ->
channelCommand1.send(sendCommand)
channelNotification1.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
val player1Hand =
channelNotification1.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
playedCard1 = player1Hand.first()
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelNotification1.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
}
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player1, player1Hand.first())).also { sendCommand ->
channelCommand1.send(sendCommand)
channelNotification1.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
channelNotification1.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
channelNotification1.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player2
card shouldBeEqual assertNotNull(playedCard2)
}
}
}
val player2Job =
launch {
until(1.seconds) { player1HasJoin }
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player2)).also { sendCommand ->
channelCommand2.send(sendCommand)
channelNotification2.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
channelNotification2.receive().let {
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
}
channelNotification2.receive().let {
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
}
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player2)).also { sendCommand ->
channelCommand2.send(sendCommand)
channelNotification2.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
val player2Hand =
channelNotification2.receive().let {
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
}
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player1
}
}
channelNotification2.receive().let {
assertIs<PlayerAsPlayACardNotification>(it).apply {
player shouldBeEqual player1
card shouldBeEqual assertNotNull(playedCard1)
}
}
playedCard2 = player2Hand.first()
channelNotification2.receive().let {
assertIs<ItsTheTurnOfNotification>(it).apply {
player shouldBeEqual player2
}
}
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player2, player2Hand.first())).also { sendCommand ->
channelCommand2.send(sendCommand)
channelNotification2.receive().let {
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
}
}
}
val commandHandler by inject<GameCommandHandler>()
val eventStore by inject<GameEventStore>()
val playerNotificationListener by inject<PlayerNotificationListener>()

View File

@@ -9,6 +9,7 @@ import eventDemo.business.notification.CommandSuccessNotification
import eventDemo.business.notification.Notification
import eventDemo.business.notification.WelcomeToTheGameNotification
import eventDemo.testKoinApplicationWithConfig
import io.kotest.core.NamedTag
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.equals.shouldBeEqual
@@ -24,6 +25,8 @@ import kotlin.time.Duration.Companion.seconds
@OptIn(DelicateCoroutinesApi::class)
class GameCommandHandlerTest :
FunSpec({
tags(NamedTag("postgresql"))
test("handle a command should execute the command") {
withTimeout(5.seconds) {
testKoinApplicationWithConfig {

View File

@@ -9,6 +9,7 @@ import eventDemo.business.event.projection.gameState.GameStateRepository
import eventDemo.testKoinApplicationWithConfig
import io.kotest.assertions.nondeterministic.eventually
import io.kotest.assertions.nondeterministic.eventuallyConfig
import io.kotest.core.NamedTag
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
@@ -23,6 +24,8 @@ import kotlin.time.Duration.Companion.seconds
@OptIn(DelicateCoroutinesApi::class)
class GameStateRepositoryTest :
FunSpec({
tags(NamedTag("postgresql"))
val player1 = Player("Tesla")
val player2 = Player(name = "Einstein")

View File

@@ -1,4 +1,4 @@
package eventDemo.adapter.infrastructureLayer
package eventDemo.externalServices
import eventDemo.testKoinApplicationWithConfig
import io.kotest.core.NamedTag

View File

@@ -1,4 +1,4 @@
package eventDemo.adapter.infrastructureLayer
package eventDemo.externalServices
import io.kotest.core.NamedTag
import io.kotest.core.spec.style.FunSpec

View File

@@ -1,10 +1,13 @@
package eventDemo.libs.bus
import com.rabbitmq.client.ConnectionFactory
import io.kotest.assertions.nondeterministic.until
import io.kotest.assertions.nondeterministic.eventually
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.string.shouldStartWith
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlin.random.Random
import kotlin.time.Duration.Companion.seconds
@@ -19,7 +22,6 @@ class BusTest :
ConnectionFactory().apply {
host = "localhost"
port = 5672
virtualHost = virtualHost
username = "event-demo"
password = "changeit"
}
@@ -29,24 +31,24 @@ class BusTest :
BusInRabbitMQ::class.java.simpleName to
BusInRabbitMQ(
factory,
"testQueue",
"testExchange",
{ it.value },
{ ObjTest(it) },
),
)
withData(list) { bus ->
val value = "hello${Random.nextInt()}"
var isCalled = false
val spy = spyk(mockk<() -> Unit>())
bus.subscribe { obj ->
isCalled = true
obj.value shouldBeEqual value
spy()
obj.value shouldStartWith "testMessage"
}
bus.publish(ObjTest(value))
bus.publish(ObjTest("testMessage${Random.nextInt()}"))
bus.publish(ObjTest("testMessage${Random.nextInt()}"))
until(3.seconds) {
isCalled shouldBeEqual true
eventually(1.seconds) {
verify(exactly = 2) { spy() }
}
}
}

View File

@@ -1,6 +1,7 @@
package eventDemo.libs.event
import eventDemo.testKoinApplicationWithConfig
import io.kotest.core.NamedTag
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.matchers.collections.shouldHaveSize
@@ -12,11 +13,14 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.assertNull
import org.junit.jupiter.api.assertThrows
import org.koin.core.Koin
import kotlin.test.assertNotNull
@DelicateCoroutinesApi
class EventStreamTest :
FunSpec({
tags(NamedTag("postgresql"))
fun EventStream<EventXTest, IdTest>.with3Events(block: EventStream<EventXTest, IdTest>.(id: IdTest) -> Unit) =
also {
publish(EventXTest(aggregateId = aggregateId, version = 1, num = 1))
@@ -25,37 +29,39 @@ class EventStreamTest :
block(aggregateId)
}
suspend fun eventStreams(): List<EventStream<EventXTest, IdTest>> =
testKoinApplicationWithConfig {
listOf(
EventStreamInMemory(IdTest()),
EventStreamInPostgresql(
IdTest(),
dataSource = get(),
objectToString = { Json.encodeToString(it) },
stringToObject = { Json.decodeFromString(it) },
),
)
}
fun Koin.eventStreams(): List<EventStream<EventXTest, IdTest>> =
listOf(
EventStreamInMemory(IdTest()),
EventStreamInPostgresql(
IdTest(),
dataSource = get(),
objectToString = { Json.encodeToString(it) },
stringToObject = { Json.decodeFromString(it) },
),
)
context("readVersionBetween should only return the event of aggregate") {
withData(eventStreams()) { stream ->
stream.with3Events {
readVersionBetween(1..2) shouldHaveSize 2
readVersionBetween(1..1) shouldHaveSize 1
readVersionBetween(2..20) shouldHaveSize 2
readVersionBetween(4..20) shouldHaveSize 0
testKoinApplicationWithConfig {
withData(eventStreams()) { stream ->
stream.with3Events {
readVersionBetween(1..2) shouldHaveSize 2
readVersionBetween(1..1) shouldHaveSize 1
readVersionBetween(2..20) shouldHaveSize 2
readVersionBetween(4..20) shouldHaveSize 0
}
}
}
}
context("readAll should only return the event of aggregate") {
withData(eventStreams()) { stream ->
stream.with3Events {
readAll() shouldHaveSize 3
readAll().also {
it.forEachIndexed { i, event ->
event.version shouldBeEqual i + 1
testKoinApplicationWithConfig {
withData(eventStreams()) { stream ->
stream.with3Events {
readAll() shouldHaveSize 3
readAll().also {
it.forEachIndexed { i, event ->
event.version shouldBeEqual i + 1
}
}
}
}
@@ -63,50 +69,58 @@ class EventStreamTest :
}
context("getByVersion should only return the event with this version") {
withData(eventStreams()) { stream ->
stream.with3Events {
assertNotNull(getByVersion(1)).version shouldBeEqual 1
assertNotNull(getByVersion(2)).version shouldBeEqual 2
assertNotNull(getByVersion(3)).version shouldBeEqual 3
assertNull(getByVersion(4))
testKoinApplicationWithConfig {
withData(eventStreams()) { stream ->
stream.with3Events {
assertNotNull(getByVersion(1)).version shouldBeEqual 1
assertNotNull(getByVersion(2)).version shouldBeEqual 2
assertNotNull(getByVersion(3)).version shouldBeEqual 3
assertNull(getByVersion(4))
}
}
}
}
context("readGreaterOfVersion should only return the events with greater version") {
withData(eventStreams()) {
it.with3Events {
assertNotNull(readGreaterOfVersion(1)) shouldHaveSize 2
assertNotNull(readGreaterOfVersion(2)) shouldHaveSize 1
assertNotNull(readGreaterOfVersion(3)) shouldHaveSize 0
assertNotNull(readGreaterOfVersion(30)) shouldHaveSize 0
testKoinApplicationWithConfig {
withData(eventStreams()) {
it.with3Events {
assertNotNull(readGreaterOfVersion(1)) shouldHaveSize 2
assertNotNull(readGreaterOfVersion(2)) shouldHaveSize 1
assertNotNull(readGreaterOfVersion(3)) shouldHaveSize 0
assertNotNull(readGreaterOfVersion(30)) shouldHaveSize 0
}
}
}
}
context("publish should be throw error when publish another aggregate event") {
withData(eventStreams()) {
assertThrows<EventStreamPublishException> { it.publish(EventXTest(aggregateId = IdTest(), version = 1, num = 1)) }
testKoinApplicationWithConfig {
withData(eventStreams()) {
assertThrows<EventStreamPublishException> { it.publish(EventXTest(aggregateId = IdTest(), version = 1, num = 1)) }
}
}
}
context("publish should be concurrently secure") {
withData(eventStreams()) { stream ->
(0..9)
.map { i1 ->
GlobalScope.launch {
(1..10).forEach { i2 ->
stream.publish(
EventXTest(
aggregateId = stream.aggregateId,
version = (i1 * 10) + i2,
num = (i1 * 10) + i2,
),
)
testKoinApplicationWithConfig {
withData(eventStreams()) { stream ->
(0..9)
.map { i1 ->
GlobalScope.launch {
(1..10).forEach { i2 ->
stream.publish(
EventXTest(
aggregateId = stream.aggregateId,
version = (i1 * 10) + i2,
num = (i1 * 10) + i2,
),
)
}
}
}
}.joinAll()
stream.readAll() shouldHaveSize 100
}.joinAll()
stream.readAll() shouldHaveSize 100
}
}
}
})