197 lines
8.8 KiB
Kotlin
197 lines
8.8 KiB
Kotlin
package eventDemo.adapter.presenter.query
|
|
|
|
import eventDemo.Tag
|
|
import eventDemo.domain.command.GameCommandHandler
|
|
import eventDemo.domain.command.command.GameCommand
|
|
import eventDemo.domain.command.command.IWantToJoinTheGameCommand
|
|
import eventDemo.domain.command.command.IWantToPlayCardCommand
|
|
import eventDemo.domain.command.command.IamReadyToPlayCommand
|
|
import eventDemo.domain.entity.Card
|
|
import eventDemo.domain.entity.GameId
|
|
import eventDemo.domain.entity.Player
|
|
import eventDemo.domain.event.event.disableShuffleDeck
|
|
import eventDemo.domain.event.projection.GameState
|
|
import eventDemo.domain.event.projection.GameStateRepository
|
|
import eventDemo.domain.event.projection.projectionListener.PlayerNotificationListener
|
|
import eventDemo.domain.notification.CommandSuccessNotification
|
|
import eventDemo.domain.notification.ItsTheTurnOfNotification
|
|
import eventDemo.domain.notification.Notification
|
|
import eventDemo.domain.notification.PlayerAsJoinTheGameNotification
|
|
import eventDemo.domain.notification.PlayerAsPlayACardNotification
|
|
import eventDemo.domain.notification.PlayerWasReadyNotification
|
|
import eventDemo.domain.notification.TheGameWasStartedNotification
|
|
import eventDemo.domain.notification.WelcomeToTheGameNotification
|
|
import eventDemo.testKoinApplicationWithConfig
|
|
import io.kotest.assertions.nondeterministic.eventually
|
|
import io.kotest.assertions.nondeterministic.until
|
|
import io.kotest.core.spec.style.FunSpec
|
|
import io.kotest.matchers.equals.shouldBeEqual
|
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.GlobalScope
|
|
import kotlinx.coroutines.channels.Channel
|
|
import kotlinx.coroutines.channels.trySendBlocking
|
|
import kotlinx.coroutines.joinAll
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withTimeout
|
|
import kotlin.test.assertNotNull
|
|
import kotlin.test.assertTrue
|
|
import kotlin.time.Duration.Companion.seconds
|
|
|
|
@DelicateCoroutinesApi
|
|
class GameSimulationTest :
|
|
FunSpec({
|
|
tags(Tag.Postgresql)
|
|
|
|
test("Simulation of a game") {
|
|
withTimeout(10.seconds) {
|
|
disableShuffleDeck()
|
|
val gameId = GameId()
|
|
val player1 = Player(name = "Nikola")
|
|
val player2 = Player(name = "Einstein")
|
|
val channelCommand1 = Channel<GameCommand>(Channel.BUFFERED)
|
|
val channelCommand2 = Channel<GameCommand>(Channel.BUFFERED)
|
|
val channelNotification1 = Channel<Notification>(Channel.BUFFERED)
|
|
val channelNotification2 = Channel<Notification>(Channel.BUFFERED)
|
|
|
|
var playedCard1: Card? = null
|
|
var playedCard2: Card? = null
|
|
|
|
var player1HasJoin = false
|
|
|
|
testKoinApplicationWithConfig {
|
|
val commandHandler = get<GameCommandHandler>()
|
|
val playerNotificationListener = get<PlayerNotificationListener>()
|
|
val gameStateRepository = get<GameStateRepository>()
|
|
|
|
// Run command handler
|
|
// In the normal process, these handlers is invoque players connect to the websocket
|
|
run {
|
|
GlobalScope.launch(Dispatchers.IO) {
|
|
commandHandler.handleIncomingPlayerCommands(player1, gameId, channelCommand1, channelNotification1)
|
|
}
|
|
GlobalScope.launch(Dispatchers.IO) {
|
|
commandHandler.handleIncomingPlayerCommands(player2, gameId, channelCommand2, channelNotification2)
|
|
}
|
|
}
|
|
|
|
// Consume etch notification of players, and put theses in a list.
|
|
// Is used later to control when other players can execute the next action
|
|
val player1Notifications = mutableListOf<Notification>()
|
|
val player2Notifications = mutableListOf<Notification>()
|
|
run {
|
|
GlobalScope.launch {
|
|
for (notification in channelNotification1) {
|
|
player1Notifications.add(notification)
|
|
}
|
|
}
|
|
|
|
GlobalScope.launch {
|
|
for (notification in channelNotification2) {
|
|
player2Notifications.add(notification)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Player 1 actions
|
|
val player1Job =
|
|
launch {
|
|
playerNotificationListener.startListening(player1, gameId) {
|
|
channelNotification1.trySendBlocking(it)
|
|
}
|
|
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player1)).also { sendCommand ->
|
|
channelCommand1.send(sendCommand)
|
|
player1Notifications.waitNotification<CommandSuccessNotification> { commandId == sendCommand.id }
|
|
}
|
|
|
|
player1HasJoin = true
|
|
|
|
player1Notifications.waitNotification<WelcomeToTheGameNotification> { players == setOf(player1) }
|
|
player1Notifications.waitNotification<PlayerAsJoinTheGameNotification> { player == player2 }
|
|
|
|
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player1)).also { sendCommand ->
|
|
channelCommand1.send(sendCommand)
|
|
player1Notifications.waitNotification<CommandSuccessNotification> { commandId == sendCommand.id }
|
|
}
|
|
player1Notifications.waitNotification<PlayerWasReadyNotification> { player == player2 }
|
|
val player1Hand = player1Notifications.waitNotification<TheGameWasStartedNotification> { hand.size == 7 }.hand
|
|
|
|
playedCard1 = player1Hand.first()
|
|
player1Notifications.waitNotification<ItsTheTurnOfNotification> { player == player1 }
|
|
|
|
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player1, player1Hand.first())).also { sendCommand ->
|
|
channelCommand1.send(sendCommand)
|
|
player1Notifications.waitNotification<CommandSuccessNotification> { commandId == sendCommand.id }
|
|
}
|
|
|
|
player1Notifications.waitNotification<ItsTheTurnOfNotification> { player == player2 }
|
|
|
|
player1Notifications.waitNotification<PlayerAsPlayACardNotification> {
|
|
player == player2 && card == playedCard2
|
|
}
|
|
}
|
|
|
|
// Player 2 actions
|
|
val player2Job =
|
|
launch {
|
|
// wait player 1 has joined the game
|
|
until(3.seconds) { player1HasJoin }
|
|
|
|
playerNotificationListener.startListening(player2, gameId) {
|
|
channelNotification2.trySendBlocking(it)
|
|
}
|
|
|
|
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player2)).also { sendCommand ->
|
|
channelCommand2.send(sendCommand)
|
|
player2Notifications.waitNotification<CommandSuccessNotification> { commandId == sendCommand.id }
|
|
}
|
|
|
|
player2Notifications.waitNotification<WelcomeToTheGameNotification> { players == setOf(player1, player2) }
|
|
player2Notifications.waitNotification<PlayerWasReadyNotification> { player == player1 }
|
|
|
|
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player2)).also { sendCommand ->
|
|
channelCommand2.send(sendCommand)
|
|
player2Notifications.waitNotification<CommandSuccessNotification> { commandId == sendCommand.id }
|
|
}
|
|
|
|
val player2Hand =
|
|
player2Notifications.waitNotification<TheGameWasStartedNotification> { hand.size == 7 }.hand
|
|
|
|
player2Notifications.waitNotification<ItsTheTurnOfNotification> { player == player1 }
|
|
player2Notifications.waitNotification<PlayerAsPlayACardNotification> {
|
|
player == player1 && card == playedCard1
|
|
}
|
|
playedCard2 = player2Hand.first()
|
|
|
|
player2Notifications.waitNotification<ItsTheTurnOfNotification> { player == player2 }
|
|
|
|
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player2, player2Hand.first())).also { sendCommand ->
|
|
channelCommand2.send(sendCommand)
|
|
player2Notifications.waitNotification<CommandSuccessNotification> { commandId == sendCommand.id }
|
|
}
|
|
}
|
|
|
|
// Wait the end of the game
|
|
joinAll(player1Job, player2Job)
|
|
|
|
// Build the last state from the event store
|
|
val state = gameStateRepository.get(gameId)
|
|
|
|
// Check if the state is correct
|
|
state.aggregateId shouldBeEqual gameId
|
|
assertTrue(state.isStarted)
|
|
state.players shouldBeEqual setOf(player1, player2)
|
|
state.readyPlayers shouldBeEqual setOf(player1, player2)
|
|
state.direction shouldBeEqual GameState.Direction.CLOCKWISE
|
|
assertNotNull(state.lastCardPlayer) shouldBeEqual player2
|
|
assertNotNull(state.cardOnCurrentStack) shouldBeEqual assertNotNull(playedCard2)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
private suspend inline fun <reified T : Notification> MutableList<Notification>.waitNotification(crossinline block: T.() -> Boolean): T =
|
|
eventually(3.seconds) {
|
|
filterIsInstance<T>().first { block(it) }
|
|
}
|