Move tests
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package eventDemo.business.command
|
||||
|
||||
import eventDemo.business.command.command.GameCommand
|
||||
import eventDemo.business.command.command.IWantToJoinTheGameCommand
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.event.projection.projectionListener.PlayerNotificationListener
|
||||
import eventDemo.business.event.projection.projectionListener.ReactionListener
|
||||
import eventDemo.business.notification.CommandSuccessNotification
|
||||
import eventDemo.business.notification.Notification
|
||||
import eventDemo.business.notification.WelcomeToTheGameNotification
|
||||
import eventDemo.configuration.injection.appKoinModule
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContain
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.dsl.koinApplication
|
||||
import kotlin.test.assertIs
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class GameCommandHandlerTest :
|
||||
FunSpec({
|
||||
test("handle a command should execute the command") {
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val commandHandler by inject<GameCommandHandler>()
|
||||
val notificationListener by inject<PlayerNotificationListener>()
|
||||
val gameId = GameId()
|
||||
val player = Player("Tesla")
|
||||
val channelCommand = Channel<GameCommand>(Channel.BUFFERED)
|
||||
val channelNotification = Channel<Notification>(Channel.BUFFERED)
|
||||
ReactionListener(get(), get()).init()
|
||||
notificationListener.startListening(
|
||||
{ channelNotification.trySendBlocking(it) },
|
||||
player,
|
||||
gameId,
|
||||
)
|
||||
|
||||
GlobalScope.launch {
|
||||
commandHandler.handle(
|
||||
player,
|
||||
gameId,
|
||||
channelCommand,
|
||||
channelNotification,
|
||||
)
|
||||
}
|
||||
|
||||
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player)).also { sendCommand ->
|
||||
channelCommand.send(sendCommand)
|
||||
channelNotification.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
assertIs<WelcomeToTheGameNotification>(channelNotification.receive()).let {
|
||||
it.players shouldContain player
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,8 @@
|
||||
package eventDemo.business.command
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class GameCommandRunnerTest :
|
||||
FunSpec({
|
||||
test("run should run the correct command") { }
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
package eventDemo.business.command.command
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class ICantPlayCommandTest :
|
||||
FunSpec({
|
||||
|
||||
xtest("run should publish the event") { }
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
package eventDemo.business.command.command
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class IWantToJoinTheGameCommandTest :
|
||||
FunSpec({
|
||||
|
||||
xtest("run should publish the event") { }
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
package eventDemo.business.command.command
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class IWantToPlayCardCommandTest :
|
||||
FunSpec({
|
||||
|
||||
xtest("run should publish the event") { }
|
||||
})
|
||||
@@ -0,0 +1,9 @@
|
||||
package eventDemo.business.command.command
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class IamReadyToPlayCommandTest :
|
||||
FunSpec({
|
||||
|
||||
xtest("run should publish the event") { }
|
||||
})
|
||||
104
src/test/kotlin/eventDemo/business/entity/DeckTest.kt
Normal file
104
src/test/kotlin/eventDemo/business/entity/DeckTest.kt
Normal file
@@ -0,0 +1,104 @@
|
||||
package eventDemo.business.entity
|
||||
|
||||
import eventDemo.allCardCount
|
||||
import eventDemo.allCards
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeUnique
|
||||
import io.kotest.matchers.ints.shouldBeExactly
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class DeckTest :
|
||||
FunSpec({
|
||||
val totalCardsNumber = 104
|
||||
test("newWithoutPlayers") {
|
||||
// When
|
||||
val deck = Deck.newWithoutPlayers()
|
||||
|
||||
// Then
|
||||
deck.stack.size shouldBeExactly totalCardsNumber
|
||||
deck.discard.size shouldBeExactly 0
|
||||
deck.playersHands.size shouldBeExactly 0
|
||||
|
||||
deck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
deck.allCards().shouldBeUnique()
|
||||
deck.allCards().map { it.id }.shouldBeUnique()
|
||||
}
|
||||
|
||||
test("initHands should be generate the hands of all players from the stack") {
|
||||
// Given
|
||||
val playerNumbers = 4
|
||||
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
|
||||
val deck = Deck.newWithoutPlayers()
|
||||
|
||||
// When
|
||||
val initDeck = deck.initHands(players)
|
||||
|
||||
// Then
|
||||
initDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7)
|
||||
initDeck.discard.size shouldBeExactly 0
|
||||
initDeck.playersHands.size shouldBeExactly playerNumbers
|
||||
initDeck.playersHands.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
|
||||
initDeck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
}
|
||||
|
||||
test("takeOneCardFromStackTo player") {
|
||||
// Given
|
||||
val playerNumbers = 4
|
||||
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
|
||||
val deck = Deck.newWithoutPlayers().initHands(players)
|
||||
val firstPlayer = players.first()
|
||||
|
||||
// When
|
||||
val modifiedDeck = deck.takeOneCardFromStackTo(firstPlayer)
|
||||
|
||||
// Then
|
||||
modifiedDeck.discard.size shouldBeExactly 0
|
||||
modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1
|
||||
modifiedDeck.playersHands.size shouldBeExactly playerNumbers
|
||||
assertNotNull(modifiedDeck.playersHands.getHand(firstPlayer)).size shouldBeExactly 7 + 1
|
||||
modifiedDeck.playersHands
|
||||
.filterKeys { it != firstPlayer.id }
|
||||
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
|
||||
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
}
|
||||
|
||||
test("putOneCardFromHand") {
|
||||
// Given
|
||||
val playerNumbers = 4
|
||||
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
|
||||
val deck = Deck.newWithoutPlayers().initHands(players)
|
||||
val firstPlayer = players.first()
|
||||
|
||||
// When
|
||||
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.getHand(firstPlayer)).size shouldBeExactly 6
|
||||
modifiedDeck.playersHands
|
||||
.filterKeys { it != firstPlayer.id }
|
||||
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
|
||||
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
}
|
||||
|
||||
test("placeFirstCardOnDiscard") {
|
||||
// Given
|
||||
val playerNumbers = 4
|
||||
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
|
||||
val deck = Deck.newWithoutPlayers().initHands(players)
|
||||
|
||||
// When
|
||||
val modifiedDeck = deck.placeFirstCardOnDiscard()
|
||||
|
||||
// Then
|
||||
modifiedDeck.discard.size shouldBeExactly 1
|
||||
modifiedDeck.stack.size shouldBeExactly totalCardsNumber - (playerNumbers * 7) - 1
|
||||
modifiedDeck.playersHands.size shouldBeExactly playerNumbers
|
||||
modifiedDeck.playersHands
|
||||
.forEach { (_, cards) -> cards.size shouldBeExactly 7 }
|
||||
modifiedDeck.allCardCount() shouldBeExactly totalCardsNumber
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,44 @@
|
||||
package eventDemo.business.entity
|
||||
|
||||
import eventDemo.business.entity.Card
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.entity.PlayersHands
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.ints.shouldBeExactly
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class PlayerHandKtTest :
|
||||
FunSpec({
|
||||
test("addCards") {
|
||||
// Given
|
||||
val playerNumbers = 4
|
||||
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
|
||||
val firstPlayer = players.first()
|
||||
val playersHands = PlayersHands(players)
|
||||
val card = Card.NumericCard(0, Card.Color.Red)
|
||||
|
||||
// When
|
||||
val newHands: PlayersHands = playersHands.addCards(firstPlayer, listOf(card))
|
||||
|
||||
assertNotNull(newHands.getHand(firstPlayer)).size shouldBeExactly 1
|
||||
assertNotNull(newHands.getHand(players.last())).size shouldBeExactly 0
|
||||
}
|
||||
|
||||
test("removeCard") {
|
||||
// Given
|
||||
val playerNumbers = 4
|
||||
val players = (1..playerNumbers).map { Player(name = "name $it") }.toSet()
|
||||
val firstPlayer = players.first()
|
||||
val card1 = Card.NumericCard(1, Card.Color.Red)
|
||||
val card2 = Card.NumericCard(2, Card.Color.Red)
|
||||
val playersHands: PlayersHands =
|
||||
PlayersHands(players)
|
||||
.addCards(firstPlayer, listOf(card1, card2))
|
||||
|
||||
// When
|
||||
val newHands: PlayersHands = playersHands.removeCard(firstPlayer, card1)
|
||||
|
||||
assertNotNull(newHands.getHand(firstPlayer)).size shouldBeExactly 1
|
||||
assertNotNull(newHands.getHand(players.last())).size shouldBeExactly 0
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
package eventDemo.business.entity
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class PlayersHandsTest :
|
||||
FunSpec({
|
||||
|
||||
xtest("getHand should return the hand of the player") { }
|
||||
|
||||
xtest("removeCard should remove the card") { }
|
||||
|
||||
xtest("addCard should add the card to the correct hand") { }
|
||||
|
||||
xtest("toPlayersHands should build object from map") { }
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
package eventDemo.business.event
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
|
||||
class GameEventHandlerTest :
|
||||
FunSpec({
|
||||
xtest("handle event should publish the event to the stream") { }
|
||||
xtest("handle event should build the registered projection") { }
|
||||
xtest("handle event should publish the event to the bus") { }
|
||||
})
|
||||
@@ -0,0 +1,128 @@
|
||||
package eventDemo.business.event.projection
|
||||
|
||||
import eventDemo.business.entity.Card
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.event.event.CardIsPlayedEvent
|
||||
import eventDemo.business.event.event.GameStartedEvent
|
||||
import eventDemo.business.event.event.NewPlayerEvent
|
||||
import eventDemo.business.event.event.PlayerReadyEvent
|
||||
import eventDemo.business.event.event.disableShuffleDeck
|
||||
import eventDemo.business.event.projection.gameState.GameState
|
||||
import eventDemo.business.event.projection.gameState.apply
|
||||
import eventDemo.libs.event.VersionBuilderLocal
|
||||
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 versionBuilder = VersionBuilderLocal()
|
||||
val gameId = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
val player2 = Player(name = "Einstein")
|
||||
|
||||
GameState(gameId)
|
||||
.run {
|
||||
val event =
|
||||
NewPlayerEvent(
|
||||
aggregateId = gameId,
|
||||
player = player1,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.isReady shouldBeEqual false
|
||||
state.isStarted shouldBeEqual false
|
||||
}
|
||||
}.run {
|
||||
val event =
|
||||
NewPlayerEvent(
|
||||
aggregateId = gameId,
|
||||
player = player2,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.players shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
}.run {
|
||||
val event =
|
||||
PlayerReadyEvent(
|
||||
aggregateId = gameId,
|
||||
player = player1,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.readyPlayers shouldBeEqual setOf(player1)
|
||||
}
|
||||
}.run {
|
||||
val event =
|
||||
PlayerReadyEvent(
|
||||
aggregateId = gameId,
|
||||
player = player2,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.readyPlayers shouldBeEqual setOf(player1, player2)
|
||||
state.isReady shouldBeEqual true
|
||||
state.isStarted shouldBeEqual false
|
||||
}
|
||||
}.run {
|
||||
val event =
|
||||
GameStartedEvent.new(
|
||||
id = gameId,
|
||||
players = setOf(player1, player2),
|
||||
shuffleIsDisabled = true,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId 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(
|
||||
aggregateId = gameId,
|
||||
card = playedCard,
|
||||
player = player1,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
assertNotNull(state.cardOnCurrentStack) 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(
|
||||
aggregateId = gameId,
|
||||
card = playedCard,
|
||||
player = player2,
|
||||
version = versionBuilder.buildNextVersion(gameId),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
assertNotNull(state.cardOnCurrentStack) shouldBeEqual playedCard
|
||||
assertIs<Card.NumericCard>(playedCard).let {
|
||||
it.number shouldBeEqual 7
|
||||
it.color shouldBeEqual Card.Color.Red
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,131 @@
|
||||
package eventDemo.business.event.projection
|
||||
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.event.GameEventHandler
|
||||
import eventDemo.business.event.event.NewPlayerEvent
|
||||
import eventDemo.business.event.projection.gameState.GameStateRepository
|
||||
import eventDemo.configuration.injection.appKoinModule
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.koinApplication
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class GameStateRepositoryTest :
|
||||
FunSpec({
|
||||
val player1 = Player("Tesla")
|
||||
val player2 = Player(name = "Einstein")
|
||||
|
||||
test("GameStateRepository should build the projection when a new event occurs") {
|
||||
val aggregateId = GameId()
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val repo = get<GameStateRepository>()
|
||||
val eventHandler = get<GameEventHandler>()
|
||||
eventHandler
|
||||
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
|
||||
.also { event ->
|
||||
assertNotNull(repo.getUntil(event)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
assertNotNull(repo.getLast(aggregateId)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
}
|
||||
}
|
||||
stopKoin()
|
||||
}
|
||||
|
||||
test("get should build the last version of the state") {
|
||||
val aggregateId = GameId()
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val repo = get<GameStateRepository>()
|
||||
val eventHandler = get<GameEventHandler>()
|
||||
|
||||
eventHandler
|
||||
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
|
||||
.also {
|
||||
assertNotNull(repo.getLast(aggregateId)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
}
|
||||
|
||||
eventHandler
|
||||
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player2, version = it) }
|
||||
.also {
|
||||
assertNotNull(repo.getLast(aggregateId)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("getUntil should build the state until the event") {
|
||||
repeat(10) {
|
||||
val aggregateId = GameId()
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val repo = get<GameStateRepository>()
|
||||
val eventHandler = get<GameEventHandler>()
|
||||
|
||||
val event1 =
|
||||
eventHandler
|
||||
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
|
||||
.also { event1 ->
|
||||
assertNotNull(repo.getUntil(event1)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
}
|
||||
|
||||
eventHandler
|
||||
.handle(aggregateId) { NewPlayerEvent(aggregateId = aggregateId, player = player2, version = it) }
|
||||
.also { event2 ->
|
||||
assertNotNull(repo.getUntil(event2)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
assertNotNull(repo.getUntil(event1)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("getUntil should be concurrently secure") {
|
||||
val aggregateId = GameId()
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val repo = get<GameStateRepository>()
|
||||
val eventHandler = get<GameEventHandler>()
|
||||
|
||||
(1..10)
|
||||
.map { r ->
|
||||
GlobalScope
|
||||
.launch {
|
||||
repeat(100) { r2 ->
|
||||
val playerX = Player("player$r$r2")
|
||||
eventHandler
|
||||
.handle(aggregateId) {
|
||||
NewPlayerEvent(
|
||||
aggregateId = aggregateId,
|
||||
player = playerX,
|
||||
version = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
|
||||
repo.getLast(aggregateId).run {
|
||||
lastEventVersion shouldBeEqual 1000
|
||||
players shouldHaveSize 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xtest("get should be concurrently secure") { }
|
||||
})
|
||||
@@ -0,0 +1,159 @@
|
||||
package eventDemo.business.event.projection
|
||||
|
||||
import eventDemo.business.entity.Card
|
||||
import eventDemo.business.entity.Deck
|
||||
import eventDemo.business.entity.Discard
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.entity.PlayersHands
|
||||
import eventDemo.business.event.projection.gameState.GameState
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
|
||||
class GameStateTest :
|
||||
FunSpec({
|
||||
val player1 = Player("Tesla")
|
||||
val player2 = Player("Einstein")
|
||||
|
||||
test("isReady return false when all players in the game is not ready") {
|
||||
GameState(
|
||||
aggregateId = GameId(),
|
||||
players = setOf(player1, player2),
|
||||
readyPlayers = setOf(player1),
|
||||
).isReady shouldBeEqual false
|
||||
}
|
||||
|
||||
test("isReady return true when all players in the game is ready") {
|
||||
GameState(
|
||||
aggregateId = GameId(),
|
||||
players = setOf(player1, player2),
|
||||
readyPlayers = setOf(player1, player2),
|
||||
).isReady shouldBeEqual true
|
||||
}
|
||||
|
||||
xtest("nextPlayerTurn") { }
|
||||
xtest("playerDiffIndex") { }
|
||||
xtest("cardOnBoardIsForYou") { }
|
||||
xtest("playableCards") { }
|
||||
xtest("playerHasNoCardLeft") { }
|
||||
|
||||
test("canBePlayThisCard return true when a card can be played on the current game") {
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.NumericCard(5, Card.Color.Red),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.NumericCard(0, Card.Color.Blue),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.NumericCard(0, Card.Color.Green),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.Plus2Card(Card.Color.Red),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.Plus2Card(Card.Color.Red),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.Plus4Card(),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.Plus4Card(),
|
||||
playedCard = Card.Plus4Card(),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.Plus4Card(),
|
||||
playedCard = Card.Plus4Card(),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.Plus2Card(Card.Color.Red),
|
||||
playedCard = Card.Plus2Card(Card.Color.Blue),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.ChangeColorCard(),
|
||||
) shouldBeEqual true
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.ReverseCard(Card.Color.Red),
|
||||
playedCard = Card.NumericCard(0, Card.Color.Red),
|
||||
) shouldBeEqual true
|
||||
}
|
||||
|
||||
test("canBePlayThisCard return false when a card cannot be played on the current game") {
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.NumericCard(9, Card.Color.Blue),
|
||||
) shouldBeEqual false
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.Plus2Card(Card.Color.Blue),
|
||||
) shouldBeEqual false
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.NumericCard(0, Card.Color.Red),
|
||||
playedCard = Card.Plus2Card(Card.Color.Blue),
|
||||
) shouldBeEqual false
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.Plus2Card(Card.Color.Red),
|
||||
playedCard = Card.Plus4Card(),
|
||||
) shouldBeEqual false
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.Plus2Card(Card.Color.Red),
|
||||
playedCard = Card.ChangeColorCard(),
|
||||
) shouldBeEqual false
|
||||
|
||||
canBePlayThisCard(
|
||||
onTheDeck = Card.Plus2Card(Card.Color.Red),
|
||||
playedCard = Card.NumericCard(0, Card.Color.Red),
|
||||
) shouldBeEqual false
|
||||
}
|
||||
})
|
||||
|
||||
private fun canBePlayThisCard(
|
||||
onTheDeck: Card,
|
||||
playedCard: Card,
|
||||
): Boolean {
|
||||
val player1 = Player("Tesla")
|
||||
return gameStateWithCard(
|
||||
player = player1,
|
||||
onTheDeck = onTheDeck,
|
||||
playerHand = listOf(playedCard),
|
||||
).canBePlayThisCard(player1, playedCard)
|
||||
}
|
||||
|
||||
private fun gameStateWithCard(
|
||||
player: Player,
|
||||
onTheDeck: Card,
|
||||
playerHand: List<Card>,
|
||||
): GameState {
|
||||
val player2 = Player("Einstein")
|
||||
return GameState(
|
||||
aggregateId = GameId(),
|
||||
players = setOf(player, player2),
|
||||
readyPlayers = setOf(player, player2),
|
||||
lastCardPlayer = player2,
|
||||
deck =
|
||||
Deck(
|
||||
discard = Discard(setOf(onTheDeck)),
|
||||
playersHands = PlayersHands(mapOf(player.id to playerHand)),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package eventDemo.business.event.projection
|
||||
|
||||
import eventDemo.libs.event.AggregateId
|
||||
import eventDemo.libs.event.Event
|
||||
import eventDemo.libs.event.EventStore
|
||||
import eventDemo.libs.event.EventStoreInMemory
|
||||
import eventDemo.libs.event.VersionBuilderLocal
|
||||
import eventDemo.libs.event.projection.Projection
|
||||
import eventDemo.libs.event.projection.ProjectionSnapshotRepositoryInMemory
|
||||
import eventDemo.libs.event.projection.SnapshotConfig
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class ProjectionSnapshotRepositoryInMemoryTest :
|
||||
FunSpec({
|
||||
|
||||
test("when call applyAndPutToCache, the getUntil method must be use the built projection cache") {
|
||||
val eventStore: EventStore<TestEvents, IdTest> = EventStoreInMemory()
|
||||
val repo = getSnapshotRepoTest(eventStore)
|
||||
val aggregateId = IdTest()
|
||||
|
||||
val eventOther = Event2Test(value2 = "valOther", version = 1, aggregateId = IdTest())
|
||||
eventStore.publish(eventOther)
|
||||
repo.applyAndPutToCache(eventOther)
|
||||
assertNotNull(repo.getUntil(eventOther)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "valOther"
|
||||
}
|
||||
|
||||
val event1 = Event1Test(value1 = "val1", version = 1, aggregateId = aggregateId)
|
||||
eventStore.publish(event1)
|
||||
repo.applyAndPutToCache(event1)
|
||||
assertNotNull(repo.getLast(event1.aggregateId)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "val1"
|
||||
}
|
||||
assertNotNull(repo.getUntil(event1)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "val1"
|
||||
}
|
||||
|
||||
val event2 = Event2Test(value2 = "val2", version = 2, aggregateId = aggregateId)
|
||||
eventStore.publish(event2)
|
||||
repo.applyAndPutToCache(event2)
|
||||
assertNotNull(repo.getLast(event2.aggregateId)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "val1val2"
|
||||
}
|
||||
assertNotNull(repo.getUntil(event1)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "val1"
|
||||
}
|
||||
assertNotNull(repo.getUntil(event2)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "val1val2"
|
||||
}
|
||||
}
|
||||
|
||||
test("ProjectionSnapshotRepositoryInMemory should be thread safe") {
|
||||
val eventStore: EventStore<TestEvents, IdTest> = EventStoreInMemory()
|
||||
val repo = getSnapshotRepoTest(eventStore)
|
||||
val aggregateId = IdTest()
|
||||
val versionBuilder = VersionBuilderLocal()
|
||||
val lock = ReentrantLock()
|
||||
(0..9)
|
||||
.map {
|
||||
GlobalScope.launch {
|
||||
(1..10).map {
|
||||
val eventX =
|
||||
lock.withLock {
|
||||
EventXTest(num = 1, version = versionBuilder.buildNextVersion(aggregateId), aggregateId = aggregateId)
|
||||
.also { eventStore.publish(it) }
|
||||
}
|
||||
repo.applyAndPutToCache(eventX)
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
assertNotNull(repo.getLast(aggregateId)).num shouldBeEqual 100
|
||||
}
|
||||
|
||||
test("removeOldSnapshot") {
|
||||
val versionBuilder = VersionBuilderLocal()
|
||||
val eventStore: EventStore<TestEvents, IdTest> = EventStoreInMemory()
|
||||
val repo = getSnapshotRepoTest(eventStore, SnapshotConfig(2))
|
||||
val aggregateId = IdTest()
|
||||
|
||||
fun buildEndSendEventX() {
|
||||
EventXTest(num = 1, version = versionBuilder.buildNextVersion(aggregateId), aggregateId = aggregateId)
|
||||
.also { eventStore.publish(it) }
|
||||
.also { repo.applyAndPutToCache(it) }
|
||||
}
|
||||
|
||||
buildEndSendEventX()
|
||||
repo.getLast(aggregateId).num shouldBeEqual 1
|
||||
buildEndSendEventX()
|
||||
repo.getLast(aggregateId).num shouldBeEqual 2
|
||||
buildEndSendEventX()
|
||||
repo.getLast(aggregateId).num shouldBeEqual 3
|
||||
buildEndSendEventX()
|
||||
repo.getLast(aggregateId).num shouldBeEqual 4
|
||||
}
|
||||
})
|
||||
|
||||
@JvmInline
|
||||
private value class IdTest(
|
||||
override val id: UUID = UUID.randomUUID(),
|
||||
) : AggregateId
|
||||
|
||||
private data class ProjectionTest(
|
||||
override val aggregateId: IdTest,
|
||||
override val lastEventVersion: Int = 0,
|
||||
var value: String? = null,
|
||||
var num: Int = 0,
|
||||
) : Projection<IdTest>
|
||||
|
||||
private sealed interface TestEvents : Event<IdTest>
|
||||
|
||||
private data class Event1Test(
|
||||
override val eventId: UUID = UUID.randomUUID(),
|
||||
override val aggregateId: IdTest,
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val version: Int,
|
||||
val value1: String,
|
||||
) : TestEvents
|
||||
|
||||
private data class Event2Test(
|
||||
override val eventId: UUID = UUID.randomUUID(),
|
||||
override val aggregateId: IdTest,
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val version: Int,
|
||||
val value2: String,
|
||||
) : TestEvents
|
||||
|
||||
private data class EventXTest(
|
||||
override val eventId: UUID = UUID.randomUUID(),
|
||||
override val aggregateId: IdTest,
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val version: Int,
|
||||
val num: Int,
|
||||
) : TestEvents
|
||||
|
||||
private fun getSnapshotRepoTest(
|
||||
eventStore: EventStore<TestEvents, IdTest>,
|
||||
snapshotConfig: SnapshotConfig = SnapshotConfig(2000),
|
||||
): ProjectionSnapshotRepositoryInMemory<TestEvents, ProjectionTest, IdTest> =
|
||||
ProjectionSnapshotRepositoryInMemory(
|
||||
eventStore = eventStore,
|
||||
initialStateBuilder = { aggregateId: IdTest -> ProjectionTest(aggregateId) },
|
||||
snapshotCacheConfig = snapshotConfig,
|
||||
) { event ->
|
||||
this.let { projection ->
|
||||
when (event) {
|
||||
is Event1Test -> {
|
||||
projection.copy(value = (projection.value ?: "") + event.value1)
|
||||
}
|
||||
|
||||
is Event2Test -> {
|
||||
projection.copy(value = (projection.value ?: "") + event.value2)
|
||||
}
|
||||
|
||||
is EventXTest -> {
|
||||
projection.copy(num = projection.num + event.num)
|
||||
}
|
||||
}.copy(
|
||||
lastEventVersion = event.version,
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user