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