diff --git a/build.gradle.kts b/build.gradle.kts index 5cd073fd..bad643d7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -102,6 +102,7 @@ dependencies { // Serialization implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.22.1") + implementation("tools.jackson.module:jackson-module-kotlin:3.1.4") // Testing testImplementation("io.kotest:kotest-runner-junit5-jvm:6.2.2") diff --git a/src/main/java/ext/Json.kt b/src/main/java/ext/Json.kt index f35efcfe..4800599b 100644 --- a/src/main/java/ext/Json.kt +++ b/src/main/java/ext/Json.kt @@ -118,10 +118,10 @@ val JSON = Json { } // Bean for default jackson object mapper -//@Configuration -//class JacksonConfig { -// @Bean -// fun objectMapper(): ObjectMapper { -// return JACKSON -// } -//} +@org.springframework.context.annotation.Configuration +class JacksonConfig { + @org.springframework.context.annotation.Bean + fun objectMapper(): ObjectMapper { + return JACKSON + } +} diff --git a/src/test/kotlin/test/ReproduceIssueTest.kt b/src/test/kotlin/test/ReproduceIssueTest.kt new file mode 100644 index 00000000..cf7e60b7 --- /dev/null +++ b/src/test/kotlin/test/ReproduceIssueTest.kt @@ -0,0 +1,175 @@ +package test + +import com.fasterxml.jackson.databind.ObjectMapper +import ext.JACKSON +import icu.samnyan.aqua.net.games.chu3.Chu3DataExport +import icu.samnyan.aqua.net.games.mai2.Maimai2DataExport +import icu.samnyan.aqua.net.games.ongeki.OngekiDataExport +import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData +import icu.samnyan.aqua.sega.chusan.model.userdata.UserPlaylog as Chu3UserPlaylog +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMap +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog +import icu.samnyan.aqua.sega.ongeki.model.UserData as OngekiUserData +import icu.samnyan.aqua.sega.ongeki.model.UserPlaylog as OngekiUserPlaylog +import icu.samnyan.aqua.sega.util.BASIC_MAPPER +import icu.samnyan.aqua.sega.util.STRING_MAPPER +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import tools.jackson.databind.json.JsonMapper +import tools.jackson.module.kotlin.KotlinModule +import java.io.File + +class ReproduceIssueTest { + + @Test + fun testMai2ExportSerializationAgainstBaseline() { + val mai2Playlog = Mai2UserPlaylog().apply { + musicId = 11479 + level = 2 + achievement = 1000000 + deluxscore = 2000 + isClear = true + isTap = true + isHold = true + isSlide = true + isTouch = true + isBreak = true + isCriticalDisp = true + isFastLateDisp = true + isAchieveNewRecord = true + isDeluxscoreNewRecord = true + isPlayTutorial = true + isEventMode = false + isFreedomMode = false + isNewFree = true + } + + val mai2Map = Mai2UserMap().apply { + mapId = 1 + distance = 100 + isLock = false + isClear = true + isComplete = true + } + + val mai2Export = Maimai2DataExport().apply { + userData = Mai2UserDetail().apply { userName = "TestUser" } + userPlaylogList = listOf(mai2Playlog) + userMapList = listOf(mai2Map) + } + + val currentJson = JACKSON.writeValueAsString(mai2Export) + val baselineJson = File("/tmp/saved_jsons/mai2_export_web.json").readText() + + val currentTree = JACKSON.readTree(currentJson) + val baselineTree = JACKSON.readTree(baselineJson) + + val currentPlaylogNode = currentTree.at("/userPlaylogList/0") + val baselinePlaylogNode = baselineTree.at("/userPlaylogList/0") + + assertTrue(currentPlaylogNode.has("isClear"), "Current export must have 'isClear'") + assertTrue(currentPlaylogNode.has("isFullCombo"), "Current export must have 'isFullCombo'") + assertTrue(currentPlaylogNode.has("isAllPerfect"), "Current export must have 'isAllPerfect'") + + assertEquals(baselinePlaylogNode.get("isClear").asBoolean(), currentPlaylogNode.get("isClear").asBoolean()) + assertEquals(baselinePlaylogNode.get("isFullCombo").asBoolean(), currentPlaylogNode.get("isFullCombo").asBoolean()) + assertEquals(baselinePlaylogNode.get("isAllPerfect").asBoolean(), currentPlaylogNode.get("isAllPerfect").asBoolean()) + } + + @Test + fun testChu3ExportSerializationAgainstBaseline() { + val chu3Playlog = Chu3UserPlaylog().apply { + musicId = 100 + level = 1 + score = 1000000 + isClear = true + isFullCombo = true + isAllJustice = true + } + + val chu3Export = Chu3DataExport().apply { + userData = Chu3UserData().apply { userName = "ChuUser" } + userPlaylogList = listOf(chu3Playlog) + } + + val currentJson = JACKSON.writeValueAsString(chu3Export) + val baselineJson = File("/tmp/saved_jsons/chu3_export_web.json").readText() + + val currentTree = JACKSON.readTree(currentJson) + val baselineTree = JACKSON.readTree(baselineJson) + + val currentPlaylogNode = currentTree.at("/userPlaylogList/0") + val baselinePlaylogNode = baselineTree.at("/userPlaylogList/0") + + assertTrue(currentPlaylogNode.has("isClear"), "Current chu3 export must have 'isClear'") + assertTrue(currentPlaylogNode.has("isFullCombo"), "Current chu3 export must have 'isFullCombo'") + assertTrue(currentPlaylogNode.has("isAllJustice"), "Current chu3 export must have 'isAllJustice'") + assertTrue(currentPlaylogNode.has("isAllPerfect"), "Current chu3 export must have 'isAllPerfect'") + } + + @Test + fun testGameFacingMaimai2PlaylogSerialization() { + val mai2Playlog = Mai2UserPlaylog().apply { + musicId = 11479 + level = 2 + achievement = 1000000 + deluxscore = 2000 + isClear = true + isTap = true + isHold = true + isSlide = true + isTouch = true + isBreak = true + isCriticalDisp = true + isFastLateDisp = true + isAchieveNewRecord = true + isDeluxscoreNewRecord = true + isPlayTutorial = true + isEventMode = false + isFreedomMode = false + isNewFree = true + } + + val basicJson = BASIC_MAPPER.writeValueAsString(mai2Playlog) + val basicTree = BASIC_MAPPER.readTree(basicJson) + assertTrue(basicTree.has("isClear"), "BASIC_MAPPER playlog must have 'isClear'") + assertTrue(basicTree.has("isFullCombo"), "BASIC_MAPPER playlog must have 'isFullCombo'") + assertTrue(basicTree.has("isAllPerfect"), "BASIC_MAPPER playlog must have 'isAllPerfect'") + + val stringJson = STRING_MAPPER.writeValueAsString(mai2Playlog) + val stringTree = STRING_MAPPER.readTree(stringJson) + assertTrue(stringTree.has("isClear"), "STRING_MAPPER playlog must have 'isClear'") + assertTrue(stringTree.has("isFullCombo"), "STRING_MAPPER playlog must have 'isFullCombo'") + assertTrue(stringTree.has("isAllPerfect"), "STRING_MAPPER playlog must have 'isAllPerfect'") + } + + @Test + fun testJackson3JsonMapperWithKotlinModule() { + val mai2Playlog = Mai2UserPlaylog().apply { + musicId = 11479 + level = 2 + achievement = 1000000 + deluxscore = 2000 + isClear = true + } + + val mai2Export = Maimai2DataExport().apply { + userData = Mai2UserDetail().apply { userName = "TestUser" } + userPlaylogList = listOf(mai2Playlog) + } + + val j3Mapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .build() + + val j3Json = j3Mapper.writeValueAsString(mai2Export) + val j3Tree = j3Mapper.readTree(j3Json) + val j3PlaylogNode = j3Tree.at("/userPlaylogList/0") + + assertTrue(j3PlaylogNode.has("isClear"), "Jackson 3 JsonMapper with KotlinModule must have 'isClear'") + assertTrue(j3PlaylogNode.has("isFullCombo"), "Jackson 3 JsonMapper with KotlinModule must have 'isFullCombo'") + assertTrue(j3PlaylogNode.has("isAllPerfect"), "Jackson 3 JsonMapper with KotlinModule must have 'isAllPerfect'") + } +}