Create test for complete game
create notifications for reply the players implement notifications on GameEventPlayerNotificationListener add priority to the eventbus.subscribe() improve JWT creation update libs koin + ktor remove output of GameCommandStream improve logs create a function disableShuffleDeck to disable the shuffle of the deck (for tests)
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
package eventDemo
|
||||
|
||||
import eventDemo.app.command.command.GameCommand
|
||||
import eventDemo.app.entity.Card
|
||||
import eventDemo.app.entity.Deck
|
||||
import io.ktor.websocket.Frame
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
fun Deck.allCardCount(): Int = stack.size + discard.size + playersHands.values.flatten().size
|
||||
|
||||
fun Deck.allCards(): Set<Card> = stack + discard + playersHands.values.flatten()
|
||||
|
||||
suspend fun SendChannel<Frame>.send(command: GameCommand) = send(Frame.Text(Json.encodeToString(command)))
|
||||
|
||||
@@ -55,9 +55,9 @@ class DeckTest :
|
||||
modifiedDeck.discard.size shouldBeExactly 0
|
||||
modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1
|
||||
modifiedDeck.playersHands.size shouldBeExactly playerNumbers
|
||||
assertNotNull(modifiedDeck.playersHands[firstPlayer]).size shouldBeExactly 7 + 1
|
||||
assertNotNull(modifiedDeck.playersHands.getHand(firstPlayer)).size shouldBeExactly 7 + 1
|
||||
modifiedDeck.playersHands
|
||||
.filterKeys { it != firstPlayer }
|
||||
.filterKeys { it != firstPlayer.id }
|
||||
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
|
||||
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
}
|
||||
@@ -70,16 +70,16 @@ class DeckTest :
|
||||
val firstPlayer = players.first()
|
||||
|
||||
// When
|
||||
val card = deck.playersHands[firstPlayer]!!.first()
|
||||
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[firstPlayer]).size shouldBeExactly 6
|
||||
assertNotNull(modifiedDeck.playersHands.getHand(firstPlayer)).size shouldBeExactly 6
|
||||
modifiedDeck.playersHands
|
||||
.filterKeys { it != firstPlayer }
|
||||
.filterKeys { it != firstPlayer.id }
|
||||
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
|
||||
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ class PlayerHandKtTest :
|
||||
// When
|
||||
val newHands: PlayersHands = playersHands.addCards(firstPlayer, listOf(card))
|
||||
|
||||
assertNotNull(newHands[firstPlayer]).size shouldBeExactly 1
|
||||
assertNotNull(newHands[players.last()]).size shouldBeExactly 0
|
||||
assertNotNull(newHands.getHand(firstPlayer)).size shouldBeExactly 1
|
||||
assertNotNull(newHands.getHand(players.last())).size shouldBeExactly 0
|
||||
}
|
||||
|
||||
test("removeCard") {
|
||||
@@ -35,7 +35,7 @@ class PlayerHandKtTest :
|
||||
// When
|
||||
val newHands: PlayersHands = playersHands.removeCard(firstPlayer, card1)
|
||||
|
||||
assertNotNull(newHands[firstPlayer]).size shouldBeExactly 1
|
||||
assertNotNull(newHands[players.last()]).size shouldBeExactly 0
|
||||
assertNotNull(newHands.getHand(firstPlayer)).size shouldBeExactly 1
|
||||
assertNotNull(newHands.getHand(players.last())).size shouldBeExactly 0
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,49 +1,50 @@
|
||||
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.event.CardIsPlayedEvent
|
||||
import eventDemo.configuration.configure
|
||||
import eventDemo.configuration.makeJwt
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.accept
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType.Application.Json
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.server.testing.testApplication
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.java.KoinJavaComponent.getKoin
|
||||
import org.koin.ktor.ext.inject
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CardTest :
|
||||
class GameStateRouteTest :
|
||||
FunSpec({
|
||||
test("/game/{id}/card") {
|
||||
test("/game/{id}/state on empty game") {
|
||||
testApplication {
|
||||
val id = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
}
|
||||
|
||||
val id = GameId()
|
||||
val card: Card = Card.NumericCard(1, Card.Color.Blue)
|
||||
val player = Player(name = "Nikola")
|
||||
httpClient()
|
||||
.post("/game/$id/card") {
|
||||
contentType(Json)
|
||||
accept(Json)
|
||||
setBody(card)
|
||||
.get("/game/$id/state") {
|
||||
withAuth(player1)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
|
||||
val eventStream = getKoin().get<GameEventStream>()
|
||||
assertEquals(CardIsPlayedEvent(id, card, player), eventStream.readLast(id))
|
||||
val state = call.body<GameState>()
|
||||
assertEquals(id, state.gameId)
|
||||
state.players shouldHaveSize 0
|
||||
state.isStarted shouldBeEqual false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,12 +53,13 @@ class CardTest :
|
||||
testApplication {
|
||||
val id = GameId()
|
||||
val card: Card = Card.NumericCard(1, Card.Color.Blue)
|
||||
val player = Player(name = "Nikola")
|
||||
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
|
||||
val eventStream by inject<GameEventStream>()
|
||||
val player = Player(name = "Nikola")
|
||||
eventStream.publish(
|
||||
CardIsPlayedEvent(id, Card.NumericCard(2, Card.Color.Yellow), player),
|
||||
CardIsPlayedEvent(id, card, player),
|
||||
@@ -66,10 +68,18 @@ class CardTest :
|
||||
)
|
||||
}
|
||||
|
||||
httpClient().get("/game/$id/card/last").apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
assertEquals(card, call.body<Card>())
|
||||
}
|
||||
httpClient()
|
||||
.get("/game/$id/card/last") {
|
||||
withAuth(player)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
assertEquals(card, call.body<Card>())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun HttpRequestBuilder.withAuth(player: Player) {
|
||||
header("Authorization", "Bearer ${player.makeJwt()}")
|
||||
}
|
||||
133
src/test/kotlin/eventDemo/app/query/GameStateTest.kt
Normal file
133
src/test/kotlin/eventDemo/app/query/GameStateTest.kt
Normal file
@@ -0,0 +1,133 @@
|
||||
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
|
||||
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.eventListener.GameEventPlayerNotificationListener
|
||||
import eventDemo.app.eventListener.GameEventReactionListener
|
||||
import eventDemo.app.notification.PlayerAsJoinTheGameNotification
|
||||
import eventDemo.app.notification.PlayerAsPlayACardNotification
|
||||
import eventDemo.app.notification.PlayerWasReadyNotification
|
||||
import eventDemo.app.notification.TheGameWasStartedNotification
|
||||
import eventDemo.app.notification.WelcomeToTheGameNotification
|
||||
import eventDemo.configuration.appKoinModule
|
||||
import eventDemo.send
|
||||
import eventDemo.shared.toNotification
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import io.ktor.websocket.Frame
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.dsl.koinApplication
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
class GameStateTest :
|
||||
FunSpec({
|
||||
test("Simulation of a game") {
|
||||
disableShuffleDeck()
|
||||
val id = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
val player2 = Player(name = "Einstein")
|
||||
val channelIn1 = Channel<Frame>(Channel.BUFFERED)
|
||||
val channelIn2 = Channel<Frame>(Channel.BUFFERED)
|
||||
val channelOut1 = Channel<Frame>(Channel.BUFFERED)
|
||||
val channelOut2 = Channel<Frame>(Channel.BUFFERED)
|
||||
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val commandHandler by inject<GameCommandHandler>()
|
||||
val playerNotificationListener by inject<GameEventPlayerNotificationListener>()
|
||||
val eventStream by inject<GameEventStream>()
|
||||
GameEventReactionListener(get(), get()).init()
|
||||
playerNotificationListener.startListening(channelOut1, player1)
|
||||
playerNotificationListener.startListening(channelOut2, player2)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player1, channelIn1, channelOut1)
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player2, channelIn2, channelOut2)
|
||||
}
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
channelIn1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)))
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
delay(200)
|
||||
channelIn2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)))
|
||||
}
|
||||
channelOut1.receive().toNotification().let {
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
|
||||
}
|
||||
|
||||
channelOut2.receive().toNotification().let {
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
channelOut1.receive().toNotification().let {
|
||||
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
channelIn1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)))
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
channelIn2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)))
|
||||
}
|
||||
|
||||
channelOut1.receive().toNotification().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
channelOut2.receive().toNotification().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
|
||||
}
|
||||
|
||||
val player1Hand =
|
||||
channelOut1.receive().toNotification().let {
|
||||
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
|
||||
}
|
||||
val player2Hand =
|
||||
channelOut2.receive().toNotification().let {
|
||||
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
|
||||
}
|
||||
|
||||
launch {
|
||||
channelIn1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())))
|
||||
}
|
||||
channelOut2.receive().toNotification().let {
|
||||
assertIs<PlayerAsPlayACardNotification>(it).player shouldBeEqual player1
|
||||
assertIs<PlayerAsPlayACardNotification>(it).card shouldBeEqual player1Hand.first()
|
||||
}
|
||||
|
||||
launch {
|
||||
channelIn2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())))
|
||||
}
|
||||
channelOut1.receive().toNotification().let {
|
||||
assertIs<PlayerAsPlayACardNotification>(it).player shouldBeEqual player2
|
||||
assertIs<PlayerAsPlayACardNotification>(it).card shouldBeEqual player2Hand.first()
|
||||
}
|
||||
|
||||
val state = id.buildStateFromEventStream(eventStream)
|
||||
|
||||
state.gameId shouldBeEqual id
|
||||
assertTrue(state.isStarted)
|
||||
state.players shouldBeEqual setOf(player1, player2)
|
||||
state.readyPlayers shouldBeEqual setOf(player1, player2)
|
||||
state.direction shouldBeEqual GameState.Direction.CLOCKWISE
|
||||
assertNotNull(state.lastCard) shouldBeEqual GameState.LastCard(player2Hand.first(), player2)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
package eventDemo.app.query
|
||||
|
||||
import eventDemo.app.entity.GameId
|
||||
import eventDemo.shared.GameIdSerializer
|
||||
import eventDemo.shared.UUIDSerializer
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
@@ -17,6 +18,7 @@ fun ApplicationTestBuilder.httpClient(): HttpClient =
|
||||
serializersModule =
|
||||
SerializersModule {
|
||||
contextual(UUID::class) { UUIDSerializer }
|
||||
contextual(GameId::class) { GameIdSerializer }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -5,7 +5,10 @@ import io.ktor.websocket.Frame
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@Serializable
|
||||
class CommandTest(
|
||||
override val id: CommandId,
|
||||
) : Command
|
||||
@@ -15,16 +18,12 @@ class CommandStreamChannelTest :
|
||||
|
||||
test("send and receive") {
|
||||
val command = CommandTest(CommandId())
|
||||
val command2 = CommandTest(CommandId())
|
||||
val command3 = CommandTest(CommandId())
|
||||
|
||||
val channel = Channel<Frame>()
|
||||
val stream =
|
||||
CommandStreamChannel<CommandTest>(
|
||||
incoming = channel,
|
||||
outgoing = channel,
|
||||
serializer = { it.id.toString() },
|
||||
deserializer = { CommandTest(CommandId(it)) },
|
||||
deserializer = { Json.decodeFromString(it) },
|
||||
)
|
||||
|
||||
val spyCall: () -> Unit = mockk(relaxed = true)
|
||||
@@ -33,8 +32,7 @@ class CommandStreamChannelTest :
|
||||
println("In action ${it.id}")
|
||||
spyCall()
|
||||
}
|
||||
stream.send(command, command2)
|
||||
stream.send(command3)
|
||||
verify(exactly = 3) { spyCall() }
|
||||
channel.send(Frame.Text(Json.encodeToString(command)))
|
||||
verify(exactly = 1) { spyCall() }
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user