From 48441188c601ad05c54e63b4d8ae28c08fee55f1 Mon Sep 17 00:00:00 2001 From: Azalea Date: Tue, 4 Aug 2026 21:08:13 +0000 Subject: [PATCH] [O] Recursive test --- src/test/kotlin/test/ReproduceIssueTest.kt | 111 +++++++++++++++------ 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/src/test/kotlin/test/ReproduceIssueTest.kt b/src/test/kotlin/test/ReproduceIssueTest.kt index 1582e670..dfcd1370 100644 --- a/src/test/kotlin/test/ReproduceIssueTest.kt +++ b/src/test/kotlin/test/ReproduceIssueTest.kt @@ -1,6 +1,7 @@ package test import com.fasterxml.jackson.databind.JsonNode +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 @@ -14,9 +15,11 @@ 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.assertFalse 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 class ReproduceIssueTest { @@ -25,16 +28,28 @@ class ReproduceIssueTest { ?: error("Test resource $path not found") } - private fun checkFieldNamesMatch(baselineNode: JsonNode, currentNode: JsonNode, path: String) { - val baselineKeys = baselineNode.fieldNames().asSequence().toSet() - val currentKeys = currentNode.fieldNames().asSequence().toSet() + private fun checkFieldNamesMatchRecursive(baselineNode: JsonNode, currentNode: JsonNode, path: String = "$") { + if (baselineNode.isObject && currentNode.isObject) { + val baselineKeys = baselineNode.fieldNames().asSequence().toSet() + val currentKeys = currentNode.fieldNames().asSequence().toSet() - val missingKeys = baselineKeys - currentKeys - assertTrue(missingKeys.isEmpty(), "Missing keys at $path: $missingKeys. Current keys: $currentKeys") + val missingKeys = baselineKeys - currentKeys + assertTrue(missingKeys.isEmpty(), "Missing keys at path '$path': $missingKeys. Current keys present: $currentKeys") + + for (key in baselineKeys) { + checkFieldNamesMatchRecursive(baselineNode.get(key), currentNode.get(key), "$path.$key") + } + } else if (baselineNode.isArray && currentNode.isArray) { + for (i in 0 until baselineNode.size()) { + if (i < currentNode.size()) { + checkFieldNamesMatchRecursive(baselineNode.get(i), currentNode.get(i), "$path[$i]") + } + } + } } @Test - fun testMai2UserPlaylogAllFieldNamesMatchBaseline() { + fun testObjectMapperWithoutKotlinModule() { val mai2Playlog = Mai2UserPlaylog().apply { musicId = 11479 level = 2 @@ -56,15 +71,34 @@ class ReproduceIssueTest { isNewFree = true } - val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(mai2Playlog)) - val baselineTree = JACKSON.readTree(loadResourceJson("/json/mai2_playlog_jackson.json")) + val mai2Map = Mai2UserMap().apply { + mapId = 1 + distance = 100 + isLock = false + isClear = true + isComplete = true + } - checkFieldNamesMatch(baselineTree, currentTree, "Mai2UserPlaylog") - assertEquals(baselineTree.fieldNames().asSequence().toSet(), currentTree.fieldNames().asSequence().toSet()) + val mai2Export = Maimai2DataExport().apply { + userData = Mai2UserDetail().apply { userName = "TestUser" } + userPlaylogList = listOf(mai2Playlog) + userMapList = listOf(mai2Map) + } + + // Standard Jackson 2 ObjectMapper() without KotlinModule strips 'is' getters: + val plainObjectMapper = ObjectMapper() + val plainJson = plainObjectMapper.writeValueAsString(mai2Export) + val plainTree = plainObjectMapper.readTree(plainJson) + val plainPlaylogNode = plainTree.at("/userPlaylogList/0") + + assertTrue(plainPlaylogNode.has("clear"), "Plain ObjectMapper has 'clear' instead of 'isClear'") + assertTrue(plainPlaylogNode.has("fullCombo"), "Plain ObjectMapper has 'fullCombo' instead of 'isFullCombo'") + assertTrue(plainPlaylogNode.has("allPerfect"), "Plain ObjectMapper has 'allPerfect' instead of 'isAllPerfect'") + assertFalse(plainPlaylogNode.has("isClear"), "Plain ObjectMapper is missing 'isClear'") } @Test - fun testMai2ExportEntityFieldNamesMatchBaseline() { + fun testMai2ExportEntityFieldNamesMatchBaselineRecursively() { val mai2Playlog = Mai2UserPlaylog().apply { musicId = 11479 level = 2 @@ -103,16 +137,11 @@ class ReproduceIssueTest { val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(mai2Export)) val baselineTree = JACKSON.readTree(loadResourceJson("/json/mai2_export_web.json")) - checkFieldNamesMatch(baselineTree.at("/userData"), currentTree.at("/userData"), "/userData") - checkFieldNamesMatch(baselineTree.at("/userExtend"), currentTree.at("/userExtend"), "/userExtend") - checkFieldNamesMatch(baselineTree.at("/userOption"), currentTree.at("/userOption"), "/userOption") - checkFieldNamesMatch(baselineTree.at("/userUdemae"), currentTree.at("/userUdemae"), "/userUdemae") - checkFieldNamesMatch(baselineTree.at("/userMapList/0"), currentTree.at("/userMapList/0"), "/userMapList/0") - checkFieldNamesMatch(baselineTree.at("/userPlaylogList/0"), currentTree.at("/userPlaylogList/0"), "/userPlaylogList/0") + checkFieldNamesMatchRecursive(baselineTree, currentTree) } @Test - fun testChu3ExportEntityFieldNamesMatchBaseline() { + fun testChu3ExportEntityFieldNamesMatchBaselineRecursively() { val chu3Playlog = Chu3UserPlaylog().apply { musicId = 100 level = 1 @@ -130,13 +159,11 @@ class ReproduceIssueTest { val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(chu3Export)) val baselineTree = JACKSON.readTree(loadResourceJson("/json/chu3_export_web.json")) - checkFieldNamesMatch(baselineTree.at("/userData"), currentTree.at("/userData"), "/userData") - checkFieldNamesMatch(baselineTree.at("/userGameOption"), currentTree.at("/userGameOption"), "/userGameOption") - checkFieldNamesMatch(baselineTree.at("/userPlaylogList/0"), currentTree.at("/userPlaylogList/0"), "/userPlaylogList/0") + checkFieldNamesMatchRecursive(baselineTree, currentTree) } @Test - fun testOngekiExportEntityFieldNamesMatchBaseline() { + fun testOngekiExportEntityFieldNamesMatchBaselineRecursively() { val ongekiPlaylog = OngekiUserPlaylog().apply { musicId = 200 level = 1 @@ -154,13 +181,11 @@ class ReproduceIssueTest { val currentTree = JACKSON.readTree(JACKSON.writeValueAsString(ongekiExport)) val baselineTree = JACKSON.readTree(loadResourceJson("/json/ongeki_export_web.json")) - checkFieldNamesMatch(baselineTree.at("/userData"), currentTree.at("/userData"), "/userData") - checkFieldNamesMatch(baselineTree.at("/userOption"), currentTree.at("/userOption"), "/userOption") - checkFieldNamesMatch(baselineTree.at("/userPlaylogList/0"), currentTree.at("/userPlaylogList/0"), "/userPlaylogList/0") + checkFieldNamesMatchRecursive(baselineTree, currentTree) } @Test - fun testGameFacingMaimai2PlaylogFieldNamesMatchBaseline() { + fun testGameFacingMaimai2PlaylogFieldNamesMatchBaselineRecursively() { val mai2Playlog = Mai2UserPlaylog().apply { musicId = 11479 level = 2 @@ -184,10 +209,38 @@ class ReproduceIssueTest { val currentBasicTree = BASIC_MAPPER.readTree(BASIC_MAPPER.writeValueAsString(mai2Playlog)) val baselineBasicTree = BASIC_MAPPER.readTree(loadResourceJson("/json/mai2_playlog_basic.json")) - checkFieldNamesMatch(baselineBasicTree, currentBasicTree, "Mai2UserPlaylog (BASIC_MAPPER)") + checkFieldNamesMatchRecursive(baselineBasicTree, currentBasicTree, "Mai2UserPlaylog (BASIC_MAPPER)") val currentStringTree = STRING_MAPPER.readTree(STRING_MAPPER.writeValueAsString(mai2Playlog)) val baselineStringTree = STRING_MAPPER.readTree(loadResourceJson("/json/mai2_playlog_string.json")) - checkFieldNamesMatch(baselineStringTree, currentStringTree, "Mai2UserPlaylog (STRING_MAPPER)") + checkFieldNamesMatchRecursive(baselineStringTree, currentStringTree, "Mai2UserPlaylog (STRING_MAPPER)") + } + + @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'") } }