From 5d8665149b71fd9c2916be309be842a137561ecc Mon Sep 17 00:00:00 2001 From: ccornu <ccornu@takima.fr> Date: Mon, 10 Feb 2025 16:55:07 +0100 Subject: [PATCH] feat: correction of integration tests with serialization --- build.gradle.kts | 3 +- .../kotlin/configuration/ApiConfiguration.kt | 7 +- src/main/kotlin/player/PlayerRepository.kt | 1 - .../kotlin/player/dtos/PlayerCreationDTO.kt | 2 + src/main/kotlin/player/dtos/PlayerInfoDTO.kt | 2 + .../kotlin/player/dtos/PlayerUpdateDTO.kt | 2 + .../kotlin/player/PlayerIntegrationTest.kt | 9 +- src/test/kotlin/player/PlayerServiceTest.kt | 92 ++++++++++++++++++- src/test/resources/application-test.yaml | 2 +- 9 files changed, 106 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4bd559e..f445208 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ val localstack_version: String by project plugins { kotlin("jvm") version "2.1.10" id("io.ktor.plugin") version "2.3.13" + kotlin("plugin.serialization") version "1.9.0" } group = "betclic.test" @@ -43,7 +44,7 @@ dependencies { // Content negociation implementation("io.ktor:ktor-server-content-negotiation") - implementation("io.ktor:ktor-serialization-jackson") + implementation("io.ktor:ktor-serialization-kotlinx-json") implementation("ch.qos.logback:logback-classic:$logback_version") // Database diff --git a/src/main/kotlin/configuration/ApiConfiguration.kt b/src/main/kotlin/configuration/ApiConfiguration.kt index 27f2a9c..7e4ac30 100644 --- a/src/main/kotlin/configuration/ApiConfiguration.kt +++ b/src/main/kotlin/configuration/ApiConfiguration.kt @@ -1,14 +1,11 @@ package betclic.test.configuration -import com.fasterxml.jackson.databind.SerializationFeature -import io.ktor.serialization.jackson.* +import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* import io.ktor.server.plugins.contentnegotiation.* fun Application.configureSerialization() { install(ContentNegotiation) { - jackson { - enable(SerializationFeature.INDENT_OUTPUT) - } + json() } } diff --git a/src/main/kotlin/player/PlayerRepository.kt b/src/main/kotlin/player/PlayerRepository.kt index 4360500..8aae634 100644 --- a/src/main/kotlin/player/PlayerRepository.kt +++ b/src/main/kotlin/player/PlayerRepository.kt @@ -25,7 +25,6 @@ class PlayerRepository(dynamoDbEnhancedClient: DynamoDbEnhancedAsyncClient) { } suspend fun updatePlayer(player: Player): Player { - val updatedPlayer = table.updateItem(player.toPlayerEntity()).await() return updatedPlayer.toPlayer() } diff --git a/src/main/kotlin/player/dtos/PlayerCreationDTO.kt b/src/main/kotlin/player/dtos/PlayerCreationDTO.kt index 46568cf..eb150a1 100644 --- a/src/main/kotlin/player/dtos/PlayerCreationDTO.kt +++ b/src/main/kotlin/player/dtos/PlayerCreationDTO.kt @@ -1,7 +1,9 @@ package betclic.test.player.dtos import betclic.test.player.Player +import kotlinx.serialization.Serializable +@Serializable data class PlayerCreationDTO( val pseudo: String, ) diff --git a/src/main/kotlin/player/dtos/PlayerInfoDTO.kt b/src/main/kotlin/player/dtos/PlayerInfoDTO.kt index abeff58..16b8490 100644 --- a/src/main/kotlin/player/dtos/PlayerInfoDTO.kt +++ b/src/main/kotlin/player/dtos/PlayerInfoDTO.kt @@ -1,7 +1,9 @@ package betclic.test.player.dtos import betclic.test.player.Player +import kotlinx.serialization.Serializable +@Serializable data class PlayerInfoDTO( val pseudo: String, val pointsNumber: Int, diff --git a/src/main/kotlin/player/dtos/PlayerUpdateDTO.kt b/src/main/kotlin/player/dtos/PlayerUpdateDTO.kt index b209e4c..ca5f03e 100644 --- a/src/main/kotlin/player/dtos/PlayerUpdateDTO.kt +++ b/src/main/kotlin/player/dtos/PlayerUpdateDTO.kt @@ -1,7 +1,9 @@ package betclic.test.player.dtos import betclic.test.player.Player +import kotlinx.serialization.Serializable +@Serializable data class PlayerUpdateDTO( val pseudo: String, val pointsNumber: Int, diff --git a/src/test/kotlin/player/PlayerIntegrationTest.kt b/src/test/kotlin/player/PlayerIntegrationTest.kt index ea7d9bb..4585d90 100644 --- a/src/test/kotlin/player/PlayerIntegrationTest.kt +++ b/src/test/kotlin/player/PlayerIntegrationTest.kt @@ -2,8 +2,11 @@ package player import BaseIntegrationTest import betclic.test.player.PlayerRepository +import betclic.test.player.dtos.PlayerCreationDTO import io.ktor.client.request.* import io.ktor.http.* +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import org.assertj.core.api.Assertions.assertThat import org.junit.Test import org.koin.test.inject @@ -17,8 +20,10 @@ class PlayerIntegrationTest : BaseIntegrationTest() { @Test fun `When calling player creation, a player should be saved in DB`() = iTest { val response = client.post("/players") { - header(HttpHeaders.ContentType, ContentType.Text.Plain) - setBody(PLAYER_NAME) + val player = PlayerCreationDTO(pseudo = PLAYER_NAME) + val json = Json.encodeToString(player) + header(HttpHeaders.ContentType, ContentType.Application.Json) + setBody(json) } assertEquals(HttpStatusCode.Created, response.status) diff --git a/src/test/kotlin/player/PlayerServiceTest.kt b/src/test/kotlin/player/PlayerServiceTest.kt index 9671150..c0a592f 100644 --- a/src/test/kotlin/player/PlayerServiceTest.kt +++ b/src/test/kotlin/player/PlayerServiceTest.kt @@ -1,11 +1,20 @@ -package betclic.test.player +package player +import betclic.test.player.Player +import betclic.test.player.PlayerRepository +import betclic.test.player.PlayerServiceImpl +import betclic.test.player.dtos.PlayerCreationDTO +import betclic.test.player.dtos.PlayerUpdateDTO +import betclic.test.player.exceptions.AlreadyExistingPlayerException +import io.ktor.server.plugins.* import io.mockk.coEvery import io.mockk.coVerify import io.mockk.just import io.mockk.mockk import io.mockk.runs import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Assert.assertThrows import org.junit.Test class PlayerServiceTest { @@ -15,13 +24,88 @@ class PlayerServiceTest { private val playerService: PlayerServiceImpl = PlayerServiceImpl(playerRepository) private val john = "John" + private val doe = "Doe" private val player1 = Player(pseudo = john) + private val playerToUpdate = Player(pseudo = john, pointsNumber = 10) + private val player2 = Player(pseudo = doe, pointsNumber = 20) @Test - fun `should create a new player in database`() { - coEvery { playerRepository.createNewPlayer(player1) } just runs - runBlocking { playerService.createNewPlayer(john) } + fun `when creating player, should call the repository once`() { + coEvery { playerRepository.createNewPlayer(player1) } returns player1 + coEvery { playerRepository.findPlayerByPseudo(john) } returns null + runBlocking { playerService.createNewPlayer(PlayerCreationDTO(pseudo = john)) } coVerify(exactly = 1) { playerRepository.createNewPlayer(player1) } } + @Test + fun `when creating player that exists, should throw on existing player`() { + coEvery { playerRepository.findPlayerByPseudo(john) } returns player1 + assertThrows(AlreadyExistingPlayerException::class.java) { + runBlocking { playerService.createNewPlayer(PlayerCreationDTO(pseudo = john)) } + } + } + + @Test + fun `when updating player, should call the repository once`() { + coEvery { playerRepository.findPlayerByPseudo(pseudo = john) } returns player1 + coEvery { playerRepository.updatePlayer(playerToUpdate) } returns playerToUpdate + runBlocking { playerService.updatePlayer(PlayerUpdateDTO(pseudo = john, pointsNumber = 10)) } + coVerify(exactly = 1) { playerRepository.updatePlayer(playerToUpdate) } + } + + @Test + fun `when updating player that doesn't exist, should throw on not found player`() { + coEvery { playerRepository.findPlayerByPseudo(pseudo = john) } returns null + assertThrows(NotFoundException::class.java) { + runBlocking { playerService.updatePlayer(PlayerUpdateDTO(pseudo = john, pointsNumber = 10)) } + } + } + + @Test + fun `when finding user by pseudo, should call the repository once`() { + coEvery { playerRepository.findPlayerByPseudo(pseudo = john) } returns player1 + runBlocking { playerService.findPlayerByPseudo(pseudo = john) } + coVerify(exactly = 1) { playerRepository.findPlayerByPseudo(john) } + } + + @Test + fun `when finding by pseudo, should throw on not found player`() { + coEvery { playerRepository.findPlayerByPseudo(pseudo = john) } returns null + assertThrows(NotFoundException::class.java) { + runBlocking { playerService.findPlayerByPseudo(pseudo = john) } + } + } + + @Test + fun `when getting player info, should return player with his rank`() { + coEvery { playerRepository.findPlayerByPseudo(pseudo = john) } returns player1 + coEvery { playerRepository.getRank(player1) } returns 2 + val result = runBlocking { playerService.getPlayerInfoByPseudo(pseudo = john) } + assertThat(result.pseudo).isEqualTo(john) + assertThat(result.ranking).isEqualTo(2) + } + + @Test + fun `when getting player info on non-existing player, should throw on not found player`() { + coEvery { playerRepository.findPlayerByPseudo(pseudo = john) } returns null + coEvery { playerRepository.getRank(player1) } returns 2 + assertThrows(NotFoundException::class.java) { + runBlocking { playerService.getPlayerInfoByPseudo(pseudo = john) } + } + } + + @Test + fun `when getting all players ranking, should return players sorted by rank`() { + coEvery { playerRepository.findAll() } returns listOf(player1, player2) + val result = runBlocking { playerService.getPlayersRanked() } + assertThat(result.first().ranking).isEqualTo(1) + assertThat(result.last().ranking).isEqualTo(2) + } + + @Test + fun `when deleting all players, should call the repository once`() { + coEvery { playerRepository.deleteAllPlayers() } just runs + runBlocking { playerService.deleteAllPlayers() } + coVerify(exactly = 1) { playerRepository.deleteAllPlayers() } + } } \ No newline at end of file diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index 2e745a4..00ca02f 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -1,7 +1,7 @@ ktor: application: modules: - - betclic.test.TestApplicationKt.module + - TestApplicationKt.module deployment: port: 8081 database: -- GitLab