Move tests
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
package eventDemo.adapter.interfaceLayer.query
|
||||
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.configuration.ktor.makeJwt
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
import io.ktor.client.request.header
|
||||
|
||||
internal fun HttpRequestBuilder.withAuth(player: Player) {
|
||||
header("Authorization", "Bearer ${player.makeJwt("secret")}")
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package eventDemo.adapter.interfaceLayer.query
|
||||
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.event.GameEventHandler
|
||||
import eventDemo.business.event.event.GameStartedEvent
|
||||
import eventDemo.business.event.event.NewPlayerEvent
|
||||
import eventDemo.business.event.event.PlayerReadyEvent
|
||||
import eventDemo.business.event.projection.gameList.GameList
|
||||
import eventDemo.configuration.configure
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldContain
|
||||
import io.kotest.matchers.collections.shouldHaveSize
|
||||
import io.kotest.matchers.equals.shouldBeEqual
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.accept
|
||||
import io.ktor.client.request.get
|
||||
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.runBlocking
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.ktor.ext.inject
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class GameListRouteTest :
|
||||
FunSpec({
|
||||
test("/games with no game started") {
|
||||
testApplication {
|
||||
val player1 = Player(name = "Nikola")
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
}
|
||||
|
||||
httpClient()
|
||||
.get("/games") {
|
||||
withAuth(player1)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
val list = call.body<List<GameList>>()
|
||||
assertTrue(list.isEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("/games return a game with status OPENING") {
|
||||
testApplication {
|
||||
val gameId = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
|
||||
val eventHandler by inject<GameEventHandler>()
|
||||
runBlocking {
|
||||
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player1, it) }
|
||||
}
|
||||
}
|
||||
|
||||
httpClient()
|
||||
.get("/games") {
|
||||
withAuth(player1)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
call.body<List<GameList>>().first().let {
|
||||
it.status shouldBeEqual GameList.Status.OPENING
|
||||
it.players shouldHaveSize 1
|
||||
it.players shouldContain player1
|
||||
it.winners shouldHaveSize 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("/games return a game with status IS_STARTED") {
|
||||
testApplication {
|
||||
val gameId = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
val player2 = Player(name = "Einstein")
|
||||
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
|
||||
val eventHandler by inject<GameEventHandler>()
|
||||
runBlocking {
|
||||
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player1, it) }
|
||||
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player2, it) }
|
||||
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player1, it) }
|
||||
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player2, it) }
|
||||
eventHandler.handle(gameId) {
|
||||
GameStartedEvent.new(
|
||||
gameId,
|
||||
setOf(player1, player2),
|
||||
it,
|
||||
shuffleIsDisabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
httpClient()
|
||||
.get("/games") {
|
||||
withAuth(player1)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
call.body<List<GameList>>().first().let {
|
||||
it.status shouldBeEqual GameList.Status.IS_STARTED
|
||||
it.players shouldHaveSize 2
|
||||
it.players shouldContain player1
|
||||
it.players shouldContain player2
|
||||
it.winners shouldHaveSize 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,207 @@
|
||||
package eventDemo.adapter.interfaceLayer.query
|
||||
|
||||
import eventDemo.business.command.GameCommandHandler
|
||||
import eventDemo.business.command.command.GameCommand
|
||||
import eventDemo.business.command.command.IWantToJoinTheGameCommand
|
||||
import eventDemo.business.command.command.IWantToPlayCardCommand
|
||||
import eventDemo.business.command.command.IamReadyToPlayCommand
|
||||
import eventDemo.business.entity.Card
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.event.GameEventStore
|
||||
import eventDemo.business.event.event.disableShuffleDeck
|
||||
import eventDemo.business.event.projection.gameState.GameState
|
||||
import eventDemo.business.event.projection.gameState.apply
|
||||
import eventDemo.business.event.projection.projectionListener.PlayerNotificationListener
|
||||
import eventDemo.business.event.projection.projectionListener.ReactionListener
|
||||
import eventDemo.business.notification.CommandSuccessNotification
|
||||
import eventDemo.business.notification.ItsTheTurnOfNotification
|
||||
import eventDemo.business.notification.Notification
|
||||
import eventDemo.business.notification.PlayerAsJoinTheGameNotification
|
||||
import eventDemo.business.notification.PlayerAsPlayACardNotification
|
||||
import eventDemo.business.notification.PlayerWasReadyNotification
|
||||
import eventDemo.business.notification.TheGameWasStartedNotification
|
||||
import eventDemo.business.notification.WelcomeToTheGameNotification
|
||||
import eventDemo.configuration.injection.appKoinModule
|
||||
import eventDemo.libs.event.projection.ProjectionSnapshotRepositoryInMemory
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
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 GameSimulationTest :
|
||||
FunSpec({
|
||||
test("Simulation of a game") {
|
||||
withTimeout(2.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
|
||||
|
||||
val player1Job =
|
||||
launch {
|
||||
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player1)).also { sendCommand ->
|
||||
channelCommand1.send(sendCommand)
|
||||
channelNotification1.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
|
||||
channelNotification1.receive().let {
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1)
|
||||
}
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerAsJoinTheGameNotification>(it).player shouldBeEqual player2
|
||||
}
|
||||
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player1)).also { sendCommand ->
|
||||
channelCommand1.send(sendCommand)
|
||||
channelNotification1.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player1, player1Hand.first())).also { sendCommand ->
|
||||
channelCommand1.send(sendCommand)
|
||||
channelNotification1.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
|
||||
channelNotification1.receive().let {
|
||||
assertIs<ItsTheTurnOfNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
}
|
||||
}
|
||||
|
||||
channelNotification1.receive().let {
|
||||
assertIs<PlayerAsPlayACardNotification>(it).apply {
|
||||
player shouldBeEqual player2
|
||||
card shouldBeEqual assertNotNull(playedCard2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val player2Job =
|
||||
launch {
|
||||
delay(100)
|
||||
IWantToJoinTheGameCommand(IWantToJoinTheGameCommand.Payload(gameId, player2)).also { sendCommand ->
|
||||
channelCommand2.send(sendCommand)
|
||||
channelNotification2.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
|
||||
channelNotification2.receive().let {
|
||||
assertIs<WelcomeToTheGameNotification>(it).players shouldBeEqual setOf(player1, player2)
|
||||
}
|
||||
channelNotification2.receive().let {
|
||||
assertIs<PlayerWasReadyNotification>(it).player shouldBeEqual player1
|
||||
}
|
||||
|
||||
IamReadyToPlayCommand(IamReadyToPlayCommand.Payload(gameId, player2)).also { sendCommand ->
|
||||
channelCommand2.send(sendCommand)
|
||||
channelNotification2.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
IWantToPlayCardCommand(IWantToPlayCardCommand.Payload(gameId, player2, player2Hand.first())).also { sendCommand ->
|
||||
channelCommand2.send(sendCommand)
|
||||
channelNotification2.receive().let {
|
||||
assertIs<CommandSuccessNotification>(it).commandId shouldBeEqual sendCommand.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
koinApplication { modules(appKoinModule) }.koin.apply {
|
||||
val commandHandler by inject<GameCommandHandler>()
|
||||
val eventStore by inject<GameEventStore>()
|
||||
val playerNotificationListener by inject<PlayerNotificationListener>()
|
||||
ReactionListener(get(), get()).init()
|
||||
playerNotificationListener.startListening({ channelNotification1.trySendBlocking(it) }, player1, gameId)
|
||||
playerNotificationListener.startListening({ channelNotification2.trySendBlocking(it) }, player2, gameId)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player1, gameId, channelCommand1, channelNotification1)
|
||||
}
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
commandHandler.handle(player2, gameId, channelCommand2, channelNotification2)
|
||||
}
|
||||
|
||||
joinAll(player1Job, player2Job)
|
||||
|
||||
val state =
|
||||
ProjectionSnapshotRepositoryInMemory(
|
||||
eventStore = eventStore,
|
||||
initialStateBuilder = { aggregateId: GameId -> GameState(aggregateId) },
|
||||
applyToProjection = GameState::apply,
|
||||
).getLast(gameId)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,114 @@
|
||||
package eventDemo.adapter.interfaceLayer.query
|
||||
|
||||
import eventDemo.business.entity.Card
|
||||
import eventDemo.business.entity.GameId
|
||||
import eventDemo.business.entity.Player
|
||||
import eventDemo.business.event.GameEventHandler
|
||||
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.projection.gameState.GameState
|
||||
import eventDemo.business.event.projection.gameState.GameStateRepository
|
||||
import eventDemo.configuration.configure
|
||||
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.accept
|
||||
import io.ktor.client.request.get
|
||||
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({
|
||||
test("/games/{id}/state on empty game") {
|
||||
testApplication {
|
||||
val id = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
}
|
||||
|
||||
httpClient()
|
||||
.get("/games/$id/state") {
|
||||
withAuth(player1)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
val state = call.body<GameState>()
|
||||
id shouldBeEqual state.aggregateId
|
||||
state.players shouldHaveSize 0
|
||||
state.isStarted shouldBeEqual false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("/games/{id}/card/last") {
|
||||
testApplication {
|
||||
val gameId = GameId()
|
||||
val player1 = Player(name = "Nikola")
|
||||
val player2 = Player(name = "Einstein")
|
||||
var lastPlayedCard: Card? = null
|
||||
|
||||
application {
|
||||
stopKoin()
|
||||
configure()
|
||||
|
||||
val eventHandler by inject<GameEventHandler>()
|
||||
val stateRepo by inject<GameStateRepository>()
|
||||
runBlocking {
|
||||
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player1, it) }
|
||||
eventHandler.handle(gameId) { NewPlayerEvent(gameId, player2, it) }
|
||||
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player1, it) }
|
||||
eventHandler.handle(gameId) { PlayerReadyEvent(gameId, player2, it) }
|
||||
eventHandler.handle(gameId) {
|
||||
GameStartedEvent.new(
|
||||
gameId,
|
||||
setOf(player1, player2),
|
||||
it,
|
||||
shuffleIsDisabled = true,
|
||||
)
|
||||
}
|
||||
delay(100)
|
||||
lastPlayedCard = stateRepo.getLast(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(gameId) {
|
||||
CardIsPlayedEvent(
|
||||
gameId,
|
||||
assertNotNull(lastPlayedCard),
|
||||
player1,
|
||||
it,
|
||||
)
|
||||
}
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
|
||||
httpClient()
|
||||
.get("/games/$gameId/card/last") {
|
||||
withAuth(player1)
|
||||
accept(ContentType.Application.Json)
|
||||
}.apply {
|
||||
assertEquals(HttpStatusCode.OK, status, message = bodyAsText())
|
||||
assertEquals(assertNotNull(lastPlayedCard), call.body<Card>())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
package eventDemo.adapter.interfaceLayer.query
|
||||
import eventDemo.configuration.ktor.defaultJsonSerializer
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.server.testing.ApplicationTestBuilder
|
||||
|
||||
fun ApplicationTestBuilder.httpClient(): HttpClient =
|
||||
createClient {
|
||||
install(ContentNegotiation) {
|
||||
json(
|
||||
defaultJsonSerializer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user