extract projection snapshot logic
implement GameStateRepositoryTest
add lambda to the GameEventHandler.handle{} to set the version
add VersionBuilder
add version to the events
add creation date to the events
rename gameId to aggregateId
add EventHandler interface
This commit is contained in:
@@ -11,12 +11,14 @@ import eventDemo.app.notification.WelcomeToTheGameNotification
|
||||
import eventDemo.configuration.appKoinModule
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContain
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
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") {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 eventDemo.libs.event.VersionBuilderLocal
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import kotlin.test.assertIs
|
||||
@@ -17,34 +18,55 @@ 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(gameId, player1)
|
||||
val event =
|
||||
NewPlayerEvent(
|
||||
aggregateId = gameId,
|
||||
player = player1,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.isReady shouldBeEqual false
|
||||
state.isStarted shouldBeEqual false
|
||||
}
|
||||
}.run {
|
||||
val event = NewPlayerEvent(gameId, player2)
|
||||
val event =
|
||||
NewPlayerEvent(
|
||||
aggregateId = gameId,
|
||||
player = player2,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.players shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
}.run {
|
||||
val event = PlayerReadyEvent(gameId, player1)
|
||||
val event =
|
||||
PlayerReadyEvent(
|
||||
aggregateId = gameId,
|
||||
player = player1,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.readyPlayers shouldBeEqual setOf(player1)
|
||||
}
|
||||
}.run {
|
||||
val event = PlayerReadyEvent(gameId, player2)
|
||||
val event =
|
||||
PlayerReadyEvent(
|
||||
aggregateId = gameId,
|
||||
player = player2,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.readyPlayers shouldBeEqual setOf(player1, player2)
|
||||
state.isReady shouldBeEqual true
|
||||
state.isStarted shouldBeEqual false
|
||||
@@ -52,12 +74,13 @@ class GameStateBuilderTest :
|
||||
}.run {
|
||||
val event =
|
||||
GameStartedEvent.new(
|
||||
gameId,
|
||||
setOf(player1, player2),
|
||||
id = gameId,
|
||||
players = setOf(player1, player2),
|
||||
shuffleIsDisabled = true,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
state.isStarted shouldBeEqual true
|
||||
assertIs<Card.NumericCard>(state.deck.stack.first()).let {
|
||||
it.number shouldBeEqual 6
|
||||
@@ -66,9 +89,15 @@ class GameStateBuilderTest :
|
||||
}
|
||||
}.run {
|
||||
val playedCard = playableCards(player1)[0]
|
||||
val event = CardIsPlayedEvent(gameId, playedCard, player1)
|
||||
val event =
|
||||
CardIsPlayedEvent(
|
||||
aggregateId = gameId,
|
||||
card = playedCard,
|
||||
player = player1,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
assertNotNull(state.cardOnCurrentStack).card shouldBeEqual playedCard
|
||||
assertIs<Card.NumericCard>(playedCard).let {
|
||||
it.number shouldBeEqual 0
|
||||
@@ -77,9 +106,15 @@ class GameStateBuilderTest :
|
||||
}
|
||||
}.run {
|
||||
val playedCard = playableCards(player2)[0]
|
||||
val event = CardIsPlayedEvent(gameId, playedCard, player2)
|
||||
val event =
|
||||
CardIsPlayedEvent(
|
||||
aggregateId = gameId,
|
||||
card = playedCard,
|
||||
player = player2,
|
||||
version = versionBuilder.buildNextVersion(),
|
||||
)
|
||||
apply(event).also { state ->
|
||||
state.gameId shouldBeEqual gameId
|
||||
state.aggregateId shouldBeEqual gameId
|
||||
assertNotNull(state.cardOnCurrentStack).card shouldBeEqual playedCard
|
||||
assertIs<Card.NumericCard>(playedCard).let {
|
||||
it.number shouldBeEqual 7
|
||||
|
||||
@@ -1,16 +1,128 @@
|
||||
package eventDemo.app.event.projection
|
||||
|
||||
import eventDemo.app.entity.GameId
|
||||
import eventDemo.app.entity.Player
|
||||
import eventDemo.app.event.GameEventHandler
|
||||
import eventDemo.app.event.event.NewPlayerEvent
|
||||
import eventDemo.configuration.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({
|
||||
xtest("GameStateRepository should build the projection when a new event occurs") { }
|
||||
val player1 = Player("Tesla")
|
||||
val player2 = Player(name = "Einstein")
|
||||
|
||||
xtest("get should build the last version of the state") { }
|
||||
xtest("get should be concurrently secure") { }
|
||||
xtest("get should be concurrently secure") { }
|
||||
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 { 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()
|
||||
}
|
||||
|
||||
xtest("getUntil should build the state until the event") { }
|
||||
xtest("call getUntil twice should get the state from the cache") { }
|
||||
xtest("getUntil should be concurrently secure") { }
|
||||
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 { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
|
||||
.also {
|
||||
assertNotNull(repo.getLast(aggregateId)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
}
|
||||
|
||||
eventHandler
|
||||
.handle { 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 { NewPlayerEvent(aggregateId = aggregateId, player = player1, version = it) }
|
||||
.also { event1 ->
|
||||
assertNotNull(repo.getUntil(event1)).also {
|
||||
assertNotNull(it.players) shouldBeEqual setOf(player1)
|
||||
}
|
||||
}
|
||||
|
||||
eventHandler
|
||||
.handle { 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 {
|
||||
NewPlayerEvent(
|
||||
aggregateId = aggregateId,
|
||||
player = playerX,
|
||||
version = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
|
||||
repo.getLast(aggregateId).players shouldHaveSize 1000
|
||||
repo.getLast(aggregateId).lastEventVersion shouldBeEqual 1000
|
||||
}
|
||||
}
|
||||
|
||||
xtest("get should be concurrently secure") { }
|
||||
})
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package eventDemo.app.event.projection
|
||||
|
||||
import eventDemo.libs.event.AggregateId
|
||||
import eventDemo.libs.event.Event
|
||||
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 kotlin.test.assertNotNull
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class ProjectionSnapshotRepositoryInMemoryTest :
|
||||
FunSpec({
|
||||
|
||||
test("when call applyAndPutToCache, the getUntil method must be use the built projection cache") {
|
||||
repeat(10) {
|
||||
val repo = getRepoTest()
|
||||
val aggregateId = IdTest()
|
||||
|
||||
val eventOther = Event2Test(value2 = "valOther", version = 1, aggregateId = IdTest())
|
||||
repo.applyAndPutToCache(eventOther)
|
||||
assertNotNull(repo.getUntil(eventOther)).also {
|
||||
assertNotNull(it.value) shouldBeEqual "valOther"
|
||||
}
|
||||
|
||||
val event1 = Event1Test(value1 = "val1", version = 1, aggregateId = aggregateId)
|
||||
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)
|
||||
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 repo = getRepoTest(2000)
|
||||
val aggregateId = IdTest()
|
||||
(1..10)
|
||||
.map { r ->
|
||||
GlobalScope.launch {
|
||||
repeat(10) {
|
||||
val eventX = EventXTest(num = 1, version = r, aggregateId = aggregateId)
|
||||
repo.applyAndPutToCache(eventX)
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
assertNotNull(repo.getLast(aggregateId)).num shouldBeEqual 100
|
||||
}
|
||||
})
|
||||
|
||||
@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 getRepoTest(maxSnapshotCacheSize: Int = 2000): ProjectionSnapshotRepositoryInMemory<TestEvents, ProjectionTest, IdTest> =
|
||||
ProjectionSnapshotRepositoryInMemory(maxSnapshotCacheSize) { event ->
|
||||
(this ?: ProjectionTest(event.aggregateId)).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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class GameStateRouteTest :
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
val state = call.body<GameState>()
|
||||
id shouldBeEqual state.gameId
|
||||
id shouldBeEqual state.aggregateId
|
||||
state.players shouldHaveSize 0
|
||||
state.isStarted shouldBeEqual false
|
||||
}
|
||||
@@ -71,19 +71,20 @@ class GameStateRouteTest :
|
||||
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),
|
||||
eventHandler.handle { NewPlayerEvent(gameId, player1, it) }
|
||||
eventHandler.handle { NewPlayerEvent(gameId, player2, it) }
|
||||
eventHandler.handle { PlayerReadyEvent(gameId, player1, it) }
|
||||
eventHandler.handle { PlayerReadyEvent(gameId, player2, it) }
|
||||
eventHandler.handle {
|
||||
GameStartedEvent.new(
|
||||
gameId,
|
||||
setOf(player1, player2),
|
||||
shuffleIsDisabled = true,
|
||||
),
|
||||
)
|
||||
it,
|
||||
)
|
||||
}
|
||||
delay(100)
|
||||
lastPlayedCard = stateRepo.get(gameId).playableCards(player1).first()
|
||||
lastPlayedCard = stateRepo.getLast(gameId).playableCards(player1).first()
|
||||
assertNotNull(lastPlayedCard)
|
||||
.let { assertIs<Card.NumericCard>(lastPlayedCard) }
|
||||
.let {
|
||||
@@ -91,13 +92,14 @@ class GameStateRouteTest :
|
||||
it.color shouldBeEqual Card.Color.Red
|
||||
}
|
||||
delay(100)
|
||||
eventHandler.handle(
|
||||
eventHandler.handle {
|
||||
CardIsPlayedEvent(
|
||||
gameId,
|
||||
assertNotNull(lastPlayedCard),
|
||||
player1,
|
||||
),
|
||||
)
|
||||
it,
|
||||
)
|
||||
}
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,127 +32,131 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.joinAll
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.koin.dsl.koinApplication
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
class GameStateTest :
|
||||
FunSpec({
|
||||
test("Simulation of a game") {
|
||||
disableShuffleDeck()
|
||||
val id = 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)
|
||||
withTimeout(2.seconds) {
|
||||
disableShuffleDeck()
|
||||
val id = 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 playedCard1: Card? = null
|
||||
var playedCard2: Card? = null
|
||||
|
||||
val player1Job =
|
||||
launch {
|
||||
channelCommand1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)))
|
||||
channelNotification1.receive().let {
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
|
||||
}
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
channelCommand1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)))
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
val player1Hand =
|
||||
val player1Job =
|
||||
launch {
|
||||
channelCommand1.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player1)))
|
||||
channelNotification1.receive().let {
|
||||
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
|
||||
}
|
||||
playedCard1 = player1Hand.first()
|
||||
channelNotification1.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player1
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
}
|
||||
channelCommand1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())))
|
||||
channelCommand1.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player1)))
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
val player1Hand =
|
||||
channelNotification1.receive().let {
|
||||
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
|
||||
}
|
||||
playedCard1 = player1Hand.first()
|
||||
channelNotification1.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player1
|
||||
}
|
||||
}
|
||||
channelCommand1.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player1, player1Hand.first())))
|
||||
|
||||
channelNotification1.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
channelNotification1.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
}
|
||||
}
|
||||
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerAsPlayACardNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
card shouldBeEqual assertNotNull(playedCard2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerAsPlayACardNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
card shouldBeEqual assertNotNull(playedCard2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val player2Job =
|
||||
launch {
|
||||
delay(100)
|
||||
channelCommand2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)))
|
||||
channelNotification2.receive().let {
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
channelNotification2.receive().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
|
||||
}
|
||||
channelCommand2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)))
|
||||
val player2Hand =
|
||||
val player2Job =
|
||||
launch {
|
||||
delay(100)
|
||||
channelCommand2.send(IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(id, player2)))
|
||||
channelNotification2.receive().let {
|
||||
assertIs<TheGameWasStartedNotification>(it).hand shouldHaveSize 7
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
channelNotification2.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player1
|
||||
channelNotification2.receive().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
|
||||
}
|
||||
channelCommand2.send(IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(id, player2)))
|
||||
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
|
||||
}
|
||||
}
|
||||
channelCommand2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())))
|
||||
}
|
||||
channelNotification2.receive().let {
|
||||
assertIs<PlayerAsPlayACardNotification>(it).apply {
|
||||
player shouldBeEqual player1
|
||||
card shouldBeEqual assertNotNull(playedCard1)
|
||||
}
|
||||
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val commandHandler by inject<GameCommandHandler>()
|
||||
val eventStream by inject<GameEventStream>()
|
||||
val playerNotificationListener by inject<PlayerNotificationEventListener>()
|
||||
ReactionEventListener(get(), get(), get()).init()
|
||||
playerNotificationListener.startListening(channelNotification1, player1)
|
||||
playerNotificationListener.startListening(channelNotification2, player2)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player1, channelCommand1, channelNotification1)
|
||||
}
|
||||
playedCard2 = player2Hand.first()
|
||||
|
||||
channelNotification2.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player2, channelCommand2, channelNotification2)
|
||||
}
|
||||
channelCommand2.send(IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(id, player2, player2Hand.first())))
|
||||
|
||||
joinAll(player1Job, player2Job)
|
||||
|
||||
val state = id.buildStateFromEventStream(eventStream)
|
||||
|
||||
state.aggregateId 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.cardOnCurrentStack) shouldBeEqual GameState.LastCard(assertNotNull(playedCard2), player2)
|
||||
}
|
||||
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val commandHandler by inject<GameCommandHandler>()
|
||||
val eventStream by inject<GameEventStream>()
|
||||
val playerNotificationListener by inject<PlayerNotificationEventListener>()
|
||||
ReactionEventListener(get(), get(), get()).init()
|
||||
playerNotificationListener.startListening(channelNotification1, player1)
|
||||
playerNotificationListener.startListening(channelNotification2, player2)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player1, channelCommand1, channelNotification1)
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player2, channelCommand2, channelNotification2)
|
||||
}
|
||||
|
||||
joinAll(player1Job, player2Job)
|
||||
|
||||
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.cardOnCurrentStack) shouldBeEqual GameState.LastCard(assertNotNull(playedCard2), player2)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package eventDemo.libs.event
|
||||
|
||||
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
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class VersionBuilderLocalTest :
|
||||
FunSpec({
|
||||
|
||||
test("buildNextVersion") {
|
||||
VersionBuilderLocal().run {
|
||||
buildNextVersion() shouldBeEqual 1
|
||||
buildNextVersion() shouldBeEqual 2
|
||||
buildNextVersion() shouldBeEqual 3
|
||||
}
|
||||
}
|
||||
|
||||
test("buildNextVersion concurrently") {
|
||||
val versionBuilder = VersionBuilderLocal()
|
||||
(1..20)
|
||||
.map {
|
||||
GlobalScope.launch {
|
||||
(1..1000).map {
|
||||
versionBuilder.buildNextVersion()
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
versionBuilder.getLastVersion() shouldBeEqual 20 * 1000
|
||||
}
|
||||
|
||||
test("getLastVersion") {
|
||||
VersionBuilderLocal().run {
|
||||
getLastVersion() shouldBeEqual 0
|
||||
getLastVersion() shouldBeEqual 0
|
||||
getLastVersion() shouldBeEqual 0
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user