Create GameStateRepository

Use GameState.apply() to build Projection
Create GameEventHandler
Add PlayerWinEvent
This commit is contained in:
2025-03-09 03:43:31 +01:00
parent 3080e515d6
commit 19e425d684
22 changed files with 371 additions and 81 deletions

View File

@@ -0,0 +1,91 @@
package eventDemo.app.event.projection
import eventDemo.app.entity.Card
import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player
import eventDemo.app.event.event.CardIsPlayedEvent
import eventDemo.app.event.event.GameStartedEvent
import eventDemo.app.event.event.NewPlayerEvent
import eventDemo.app.event.event.PlayerReadyEvent
import eventDemo.app.event.event.disableShuffleDeck
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 gameId = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
GameState(gameId)
.run {
val event = NewPlayerEvent(gameId, player1)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.isReady shouldBeEqual false
state.isStarted shouldBeEqual false
}
}.run {
val event = NewPlayerEvent(gameId, player2)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.players shouldBeEqual setOf(player1, player2)
}
}.run {
val event = PlayerReadyEvent(gameId, player1)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.readyPlayers shouldBeEqual setOf(player1)
}
}.run {
val event = PlayerReadyEvent(gameId, player2)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
state.readyPlayers shouldBeEqual setOf(player1, player2)
state.isReady shouldBeEqual true
state.isStarted shouldBeEqual false
}
}.run {
val event =
GameStartedEvent.new(
gameId,
setOf(player1, player2),
shuffleIsDisabled = true,
)
apply(event).also { state ->
state.gameId 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(gameId, playedCard, player1)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
assertNotNull(state.lastCard).card 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(gameId, playedCard, player2)
apply(event).also { state ->
state.gameId shouldBeEqual gameId
assertNotNull(state.lastCard).card shouldBeEqual playedCard
assertIs<Card.NumericCard>(playedCard).let {
it.number shouldBeEqual 7
it.color shouldBeEqual Card.Color.Red
}
}
}
}
})

View File

@@ -1,11 +1,15 @@
package eventDemo.app.query
import eventDemo.app.GameState
import eventDemo.app.entity.Card
import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventStream
import eventDemo.app.event.GameEventHandler
import eventDemo.app.event.event.CardIsPlayedEvent
import eventDemo.app.event.event.GameStartedEvent
import eventDemo.app.event.event.NewPlayerEvent
import eventDemo.app.event.event.PlayerReadyEvent
import eventDemo.app.event.projection.GameState
import eventDemo.app.event.projection.GameStateRepository
import eventDemo.configuration.configure
import eventDemo.configuration.makeJwt
import io.kotest.core.spec.style.FunSpec
@@ -20,9 +24,13 @@ import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.testing.testApplication
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.koin.core.context.stopKoin
import org.koin.ktor.ext.inject
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotNull
class GameStateRouteTest :
FunSpec({
@@ -51,30 +59,56 @@ class GameStateRouteTest :
test("/game/{id}/card/last") {
testApplication {
val id = GameId()
val card: Card = Card.NumericCard(1, Card.Color.Blue)
val player = Player(name = "Nikola")
val gameId = GameId()
val player1 = Player(name = "Nikola")
val player2 = Player(name = "Einstein")
var lastPlayedCard: Card? = null
application {
stopKoin()
configure()
val eventStream by inject<GameEventStream>()
eventStream.publish(
CardIsPlayedEvent(id, Card.NumericCard(2, Card.Color.Yellow), player),
CardIsPlayedEvent(id, card, player),
// Other game
CardIsPlayedEvent(GameId(), Card.NumericCard(2, Card.Color.Yellow), player),
)
val eventHandler by inject<GameEventHandler>()
val stateRepo by inject<GameStateRepository>()
runBlocking {
eventHandler.handle(
NewPlayerEvent(gameId, player1),
NewPlayerEvent(gameId, player2),
PlayerReadyEvent(gameId, player1),
PlayerReadyEvent(gameId, player2),
GameStartedEvent.new(
gameId,
setOf(player1, player2),
shuffleIsDisabled = true,
),
)
delay(100)
lastPlayedCard = stateRepo.get(gameId).playableCards(player1).first()
assertNotNull(lastPlayedCard)
.let { assertIs<Card.NumericCard>(lastPlayedCard) }
.let {
it.number shouldBeEqual 0
it.color shouldBeEqual Card.Color.Red
}
delay(100)
eventHandler.handle(
CardIsPlayedEvent(
gameId,
assertNotNull(lastPlayedCard),
player1,
),
)
delay(100)
}
}
httpClient()
.get("/game/$id/card/last") {
withAuth(player)
.get("/game/$gameId/card/last") {
withAuth(player1)
accept(ContentType.Application.Json)
}.apply {
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
assertEquals(card, call.body<Card>())
assertEquals(assertNotNull(lastPlayedCard), call.body<Card>())
}
}
}

View File

@@ -1,6 +1,5 @@
package eventDemo.app.query
import eventDemo.app.GameState
import eventDemo.app.command.GameCommandHandler
import eventDemo.app.command.command.IWantToJoinTheGameCommand
import eventDemo.app.command.command.IWantToPlayCardCommand
@@ -8,8 +7,9 @@ import eventDemo.app.command.command.IamReadyToPlayCommand
import eventDemo.app.entity.GameId
import eventDemo.app.entity.Player
import eventDemo.app.event.GameEventStream
import eventDemo.app.event.buildStateFromEventStream
import eventDemo.app.event.event.disableShuffleDeck
import eventDemo.app.event.projection.GameState
import eventDemo.app.event.projection.buildStateFromEventStream
import eventDemo.app.eventListener.GameEventPlayerNotificationListener
import eventDemo.app.eventListener.GameEventReactionListener
import eventDemo.app.notification.PlayerAsJoinTheGameNotification
@@ -52,7 +52,7 @@ class GameStateTest :
val commandHandler by inject<GameCommandHandler>()
val playerNotificationListener by inject<GameEventPlayerNotificationListener>()
val eventStream by inject<GameEventStream>()
GameEventReactionListener(get(), get()).init()
GameEventReactionListener(get(), get(), get()).init()
playerNotificationListener.startListening(channelOut1, player1)
playerNotificationListener.startListening(channelOut2, player2)