diff --git a/build.gradle.kts b/build.gradle.kts index 5cd073fd8..0973ee75a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -97,11 +97,8 @@ dependencies { // Content validation implementation("org.apache.tika:tika-core:3.3.1") - // Import: DateTime Parsing - implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.22.1") - - // Serialization - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.22.1") + // Serialization (Jackson 3; JSR-310 support is built into jackson-databind 3.x) + implementation("tools.jackson.module:jackson-module-kotlin:3.2.1") // 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 78ceb88ed..3dc0af5e7 100644 --- a/src/main/java/ext/Json.kt +++ b/src/main/java/ext/Json.kt @@ -1,44 +1,95 @@ package ext import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.* -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNamingStrategy +import tools.jackson.core.JsonParser +import tools.jackson.core.JsonToken +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.PropertyNamingStrategies +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule +import tools.jackson.module.kotlin.KotlinModule import java.time.LocalDateTime import java.time.format.DateTimeFormatter // Jackson val ACCEPTABLE_FALSE = setOf("0", "false", "no", "off", "False", "None", "null") val ACCEPTABLE_TRUE = setOf("1", "true", "yes", "on", "True") -val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : JsonDeserializer() { +val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : ValueDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext) = when(parser.text) { in ACCEPTABLE_FALSE -> false in ACCEPTABLE_TRUE -> true else -> 400 - "Invalid boolean value ${parser.text}" } }) -val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer() { - override fun deserialize(parser: JsonParser, context: DeserializationContext) = - // First try standard formats via asDateTime() method - parser.text.takeIf { it.isNotEmpty() }?.run { asDateTime() ?: try { - // Try maimai2 format (yyyy-MM-dd HH:mm:ss.0) - LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) - } catch (e: Exception) { - 400 - "Invalid date time value ${parser.text}" - } } -}) -val JACKSON = jacksonObjectMapper().apply { - setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL) - findAndRegisterModules() - registerModule(JSON_FUZZY_BOOLEAN) - registerModule(JSON_DATETIME) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - propertyNamingStrategy = PropertyNamingStrategies.LOWER_CAMEL_CASE; +val JSON_DATETIME = SimpleModule() + .addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) + .addDeserializer(LocalDateTime::class.java, object : ValueDeserializer() { + override fun deserialize(parser: JsonParser, context: DeserializationContext): LocalDateTime? { + parser.readLocalDateTimeArray(context)?.let { return it } + + // First try standard formats via asDateTime() method + return parser.text.takeIf { it.isNotEmpty() }?.run { asDateTime() ?: try { + // Try maimai2 format (yyyy-MM-dd HH:mm:ss.0) + LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) + } catch (e: Exception) { + 400 - "Invalid date time value ${parser.text}" + } } + } + }) + +fun JsonParser.readLocalDateTimeArray(context: DeserializationContext): LocalDateTime? { + if (!isExpectedStartArrayToken) return null + + val parts = mutableListOf() + while (nextToken() != JsonToken.END_ARRAY) { + if (currentToken() != JsonToken.VALUE_NUMBER_INT) { + return context.reportInputMismatch( + LocalDateTime::class.java, + "Invalid legacy date-time array", + ) + } + parts += intValue + } + + if (parts.size !in 5..7) { + return context.reportInputMismatch( + LocalDateTime::class.java, + "Invalid legacy date-time array; expected 5 to 7 integer components", + ) + } + + return try { + LocalDateTime.of( + parts[0], parts[1], parts[2], parts[3], parts[4], + parts.getOrElse(5) { 0 }, parts.getOrElse(6) { 0 }, + ) + } catch (e: Exception) { + context.reportInputMismatch( + LocalDateTime::class.java, + "Invalid legacy date-time array: ${e.message}", + ) + } } + +val JACKSON: JsonMapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .findAndAddModules() + .addModule(JSON_FUZZY_BOOLEAN) + .addModule(JSON_DATETIME) + .changeDefaultPropertyInclusion { + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL) + } + .propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build() + inline fun ObjectMapper.parse(str: Str) = readValue(str, T::class.java) inline fun ObjectMapper.parse(map: Map<*, *>) = convertValue(map, T::class.java) // TODO: https://stackoverflow.com/q/78197784/7346633 @@ -73,11 +124,18 @@ val JSON = Json { coerceInputValues = true } -// Bean for default jackson object mapper -//@Configuration -//class JacksonConfig { -// @Bean -// fun objectMapper(): ObjectMapper { -// return JACKSON -// } -//} +/** + * Expose the shared Jackson 3 mapper as the application JsonMapper. + * + * Spring Boot 4 auto-configures a tools.jackson JsonMapper WITHOUT the Kotlin module + * (Jackson2ObjectMapperBuilder's well-known-module registration no longer exists), + * which silently renamed all `is*` boolean fields in @RestController responses + * (e.g. isClear -> clear) and ignored Jackson 2 databind annotations. + * Providing our own JsonMapper bean makes Boot's JacksonAutoConfiguration back off + * (@ConditionalOnMissingBean), so the HTTP message converters use this mapper. + */ +@org.springframework.context.annotation.Configuration +class JacksonConfig { + @org.springframework.context.annotation.Bean + fun jsonMapper(): JsonMapper = JACKSON +} diff --git a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt index 86079cb04..244df8f2d 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.net.games -import com.fasterxml.jackson.core.JsonProcessingException +import tools.jackson.core.JacksonException import ext.* import icu.samnyan.aqua.net.Fedy import icu.samnyan.aqua.net.db.AquaNetUser @@ -124,7 +124,7 @@ abstract class ImportController, UserModel: json.parseJackson(exportClass.java) } catch (e: Exception) { val jsonError = generateSequence(e) { it.cause } - .filterIsInstance() + .filterIsInstance() .firstOrNull() ?: throw e diff --git a/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt b/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt index 39b6651ad..15056d12c 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ImportHelper.kt @@ -1,9 +1,9 @@ package icu.samnyan.aqua.net.games -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.module.SimpleModule +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.module.SimpleModule import ext.JACKSON import ext.minus @@ -31,7 +31,7 @@ fun String.asSqlInsert(): SqlInsert { } @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") -val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : JsonDeserializer>() { +val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : ValueDeserializer>() { override fun deserialize(parser: JsonParser, context: DeserializationContext) = try { val text = parser.text.trim('[', ']') @@ -41,6 +41,4 @@ val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object 400 - "Invalid list value ${parser.text}: $e" } }) -val JACKSON_ARTEMIS = JACKSON.copy().apply { - registerModule(JSON_INT_LIST_STR) -} \ No newline at end of file +val JACKSON_ARTEMIS = JACKSON.rebuild().addModule(JSON_INT_LIST_STR).build() \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt b/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt index 17a882a04..2b8edfbe2 100644 --- a/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt +++ b/src/main/java/icu/samnyan/aqua/sega/cardmaker/CardMakerControllerAdvice.kt @@ -1,7 +1,7 @@ package icu.samnyan.aqua.sega.cardmaker -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.ObjectMapper +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.json.JsonMapper import ext.logger import jakarta.servlet.http.HttpServletRequest import org.springframework.web.bind.annotation.ModelAttribute @@ -14,7 +14,7 @@ import java.nio.charset.StandardCharsets @RestControllerAdvice(basePackages = ["icu.samnyan.aqua.sega.cardmaker"]) class CardMakerControllerAdvice { val logger = logger() - val mapper = ObjectMapper() + val mapper = JsonMapper() /** * Get the map object from json string diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt index f4943abd3..5745c3685 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/Chu3UserData.kt @@ -3,11 +3,12 @@ package icu.samnyan.aqua.sega.chusan.model.userdata import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.annotation.JsonDeserialize -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.core.JsonParser +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.annotation.JsonDeserialize +import tools.jackson.databind.annotation.JsonSerialize +import ext.readLocalDateTimeArray import icu.samnyan.aqua.net.games.BaseEntity import icu.samnyan.aqua.net.games.IUserData import icu.samnyan.aqua.sega.chusan.model.request.UserEmoney @@ -21,16 +22,18 @@ import java.time.format.DateTimeFormatterBuilder import java.time.format.ResolverStyle import java.time.temporal.ChronoField -class FlexibleDateTimeDeserializer : JsonDeserializer() { +class FlexibleDateTimeDeserializer : ValueDeserializer() { @Throws(IOException::class) public override fun deserialize(p: JsonParser, ctxt: DeserializationContext): LocalDateTime { + p.readLocalDateTimeArray(ctxt)?.let { return it } + val value = p.text return FORMATTERS.firstNotNullOfOrNull { formatter -> runCatching { LocalDateTime.parse(value, formatter) }.getOrNull() - } ?: throw ctxt.weirdStringException( - value, + } ?: ctxt.reportInputMismatch( LocalDateTime::class.java, - "Invalid date-time; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second", + "Invalid date-time '%s'; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second", + value, ) } diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt index edc5e5dee..aa7377c8e 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/userdata/UserMusicDetail.kt @@ -1,7 +1,7 @@ package icu.samnyan.aqua.sega.chusan.model.userdata import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonDeserialize +import tools.jackson.databind.annotation.JsonDeserialize import icu.samnyan.aqua.net.games.IGenericUserMusic import icu.samnyan.aqua.sega.util.BooleanToIntegerDeserializer import jakarta.persistence.Entity diff --git a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt index f4f5bec41..10dfcd639 100644 --- a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt +++ b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaMapper.kt @@ -1,12 +1,12 @@ package icu.samnyan.aqua.sega.diva.util -import com.fasterxml.jackson.core.json.JsonWriteFeature -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.json.JsonMapper -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer +import tools.jackson.core.json.JsonWriteFeature +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule import ext.JDict import icu.samnyan.aqua.sega.util.BooleanNumberDeserializer import icu.samnyan.aqua.sega.util.BooleanNumberSerializer @@ -18,19 +18,19 @@ import java.time.format.DateTimeFormatter @Component class DivaMapper { - private val mapper: ObjectMapper = JsonMapper.builder().enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) + private val mapper: ObjectMapper = JsonMapper.builder() + .addModule(SimpleModule().apply { + addSerializer(LocalDateTime::class.java, DivaDateTimeSerializer()) + addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))) + addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer()) + addSerializer(Boolean::class.java, BooleanNumberSerializer()) + addSerializer(Boolean::class.javaPrimitiveType, BooleanNumberSerializer()) + addDeserializer(Boolean::class.java, BooleanNumberDeserializer()) + addDeserializer(Boolean::class.javaPrimitiveType, BooleanNumberDeserializer()) + }) + .enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .build().apply { - registerModule(SimpleModule().apply { - addSerializer(LocalDateTime::class.java, DivaDateTimeSerializer()) - addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))) - addDeserializer(ZonedDateTime::class.java, ZonedDateTimeDeserializer()) - addSerializer(Boolean::class.java, BooleanNumberSerializer()) - addSerializer(Boolean::class.javaPrimitiveType, BooleanNumberSerializer()) - addDeserializer(Boolean::class.java, BooleanNumberDeserializer()) - addDeserializer(Boolean::class.javaPrimitiveType, BooleanNumberDeserializer()) - }) - } + .build() fun write(o: Any) = mapper.writeValueAsString(o) fun convert(map: JDict, toClass: Class) = mapper.convertValue(map, toClass) diff --git a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt index e2413dbb2..b4c3fc3dd 100644 --- a/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt +++ b/src/main/java/icu/samnyan/aqua/sega/diva/util/DivaUtils.kt @@ -1,8 +1,8 @@ package icu.samnyan.aqua.sega.diva.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.ser.std.StdSerializer +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ser.std.StdSerializer import icu.samnyan.aqua.sega.diva.PlayerPvRecordRepository import icu.samnyan.aqua.sega.diva.model.common.Edition import icu.samnyan.aqua.sega.diva.model.common.LevelInfo @@ -30,7 +30,7 @@ object DivaTime { } class DivaDateTimeSerializer(t: Class? = null) : StdSerializer(t) { - override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializationContext) { gen.writeString(DivaTime.getString(value)) } } diff --git a/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt b/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt index 34bd81f01..1b00e8a3f 100644 --- a/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/general/BaseHandler.kt @@ -1,13 +1,13 @@ package icu.samnyan.aqua.sega.general -import com.fasterxml.jackson.core.JsonProcessingException +import tools.jackson.core.JacksonException import ext.* import jakarta.servlet.http.HttpServletRequest import org.springframework.scheduling.annotation.Scheduled fun interface BaseHandler { - @Throws(JsonProcessingException::class) + @Throws(JacksonException::class) fun handle(request: Map): Any? } diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt index 5c5ad9441..5fec7aa54 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.sega.maimai2.handler -import com.fasterxml.jackson.core.JsonProcessingException +import tools.jackson.core.JacksonException import ext.mapApply import ext.minus import ext.unique @@ -28,7 +28,7 @@ class UpsertUserAllHandler( ) : BaseHandler { fun String.isValidUsername() = isNotBlank() && length <= 8 - @Throws(JsonProcessingException::class) + @Throws(JacksonException::class) override fun handle(request: Map): Any? { val upsertUserAll = mapper.convert(request, Mai2UpsertUserAll::class.java) val userId = upsertUserAll.userId diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt index 72e489b65..7cac85987 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/Mai2UserDetail.kt @@ -3,7 +3,7 @@ package icu.samnyan.aqua.sega.maimai2.model.userdata import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import icu.samnyan.aqua.net.games.BaseEntity import icu.samnyan.aqua.net.games.IUserData import icu.samnyan.aqua.sega.general.IntegerListConverter diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt index 1183c2cbf..83bfe3bb6 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/userdata/UserEntities.kt @@ -6,10 +6,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.databind.JsonSerializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.core.JsonGenerator +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ValueSerializer +import tools.jackson.databind.annotation.JsonSerialize import ext.toJson import icu.samnyan.aqua.net.games.BaseEntity import icu.samnyan.aqua.net.games.IGenericGamePlaylog @@ -563,8 +563,8 @@ class UserRegions : Mai2UserEntity() { } val MAIMAI_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0") -class MaimaiDateSerializer : JsonSerializer() { - override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializerProvider) { +class MaimaiDateSerializer : ValueSerializer() { + override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializationContext) { j.writeString(v.format(MAIMAI_DATETIME)) } } diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt index d200a5f7d..4e723e002 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/model/OngekiUserEntities.kt @@ -2,7 +2,7 @@ package icu.samnyan.aqua.sega.ongeki.model import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.databind.annotation.JsonSerialize +import tools.jackson.databind.annotation.JsonSerialize import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.util.AccessCodeSerializer diff --git a/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt b/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt index a5b9eb013..eeeee8f40 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/BasicMapper.kt @@ -1,17 +1,18 @@ package icu.samnyan.aqua.sega.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.json.JsonWriteFeature -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.module.SimpleModule -import com.fasterxml.jackson.databind.ser.std.StdSerializer -import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer -import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import tools.jackson.core.JsonGenerator +import tools.jackson.core.json.JsonWriteFeature +import tools.jackson.core.type.TypeReference +import tools.jackson.databind.DeserializationFeature +import tools.jackson.databind.ObjectMapper +import tools.jackson.databind.cfg.EnumFeature +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ext.javatime.deser.LocalDateTimeDeserializer +import tools.jackson.databind.ext.javatime.ser.LocalDateTimeSerializer +import tools.jackson.databind.json.JsonMapper +import tools.jackson.databind.module.SimpleModule +import tools.jackson.databind.ser.std.StdSerializer +import tools.jackson.module.kotlin.KotlinModule import ext.jsonArray import org.springframework.stereotype.Component import java.time.LocalDateTime @@ -28,11 +29,10 @@ open class IMapper(val mapper: ObjectMapper) { inline fun read(json: String): T = read(json, object : TypeReference() {}) } -val BASIC_MAPPER = jacksonObjectMapper().apply { - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - configure(SerializationFeature.WRITE_ENUMS_USING_INDEX, true) - findAndRegisterModules() - registerModule(SimpleModule().apply { +val BASIC_MAPPER: ObjectMapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .findAndAddModules() + .addModule(SimpleModule().apply { addSerializer( LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) @@ -42,7 +42,9 @@ val BASIC_MAPPER = jacksonObjectMapper().apply { LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) ) }) -} + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(EnumFeature.WRITE_ENUMS_USING_INDEX, true) + .build() @Component class BasicMapper: IMapper(BASIC_MAPPER) @@ -50,17 +52,15 @@ class BasicMapper: IMapper(BASIC_MAPPER) val BOOLEAN_SERIALIZER = object : StdSerializer(Boolean::class.java) { - override fun serialize(v: Boolean, gen: JsonGenerator, p: SerializerProvider) { + override fun serialize(v: Boolean, gen: JsonGenerator, p: SerializationContext) { gen.writeString(v.toString()) } } -var STRING_MAPPER = jacksonObjectMapper().apply { - enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.mappedFeature()) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - configure(SerializationFeature.WRITE_ENUMS_USING_INDEX, true) - findAndRegisterModules() - registerModule(SimpleModule().apply { +var STRING_MAPPER: ObjectMapper = JsonMapper.builder() + .addModule(KotlinModule.Builder().build()) + .findAndAddModules() + .addModule(SimpleModule().apply { addSerializer( LocalDateTime::class.java, LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) @@ -72,7 +72,10 @@ var STRING_MAPPER = jacksonObjectMapper().apply { addSerializer(Boolean::class.javaObjectType, BOOLEAN_SERIALIZER) addSerializer(Boolean::class.javaPrimitiveType, BOOLEAN_SERIALIZER) }) -} + .enable(JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(EnumFeature.WRITE_ENUMS_USING_INDEX, true) + .build() @Component class StringMapper: IMapper(STRING_MAPPER) diff --git a/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt b/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt index 285866d9d..ce830e973 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/GameDataService.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.sega.util -import com.fasterxml.jackson.core.type.TypeReference +import tools.jackson.core.type.TypeReference import ext.logger import ext.toJson import icu.samnyan.aqua.sega.general.model.GameEncryptionKey diff --git a/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt b/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt index 05a2ff7ac..7b84ed8b9 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/Serializers.kt @@ -1,24 +1,24 @@ package icu.samnyan.aqua.sega.util -import com.fasterxml.jackson.core.JsonGenerator -import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.JsonToken -import com.fasterxml.jackson.databind.DeserializationContext -import com.fasterxml.jackson.databind.JsonDeserializer -import com.fasterxml.jackson.databind.SerializerProvider -import com.fasterxml.jackson.databind.ser.std.StdSerializer +import tools.jackson.core.JsonGenerator +import tools.jackson.core.JsonParser +import tools.jackson.core.JsonToken +import tools.jackson.databind.DeserializationContext +import tools.jackson.databind.SerializationContext +import tools.jackson.databind.ValueDeserializer +import tools.jackson.databind.ser.std.StdSerializer import ext.int import icu.samnyan.aqua.sega.general.model.Card import java.time.ZonedDateTime class AccessCodeSerializer @JvmOverloads constructor(t: Class? = null) : StdSerializer(t) { - override fun serialize(value: Card, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: Card, gen: JsonGenerator, provider: SerializationContext) { gen.writeString(value.luid) } } -class BooleanToIntegerDeserializer : JsonDeserializer() { - override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = when (p.currentToken) { +class BooleanToIntegerDeserializer : ValueDeserializer() { + override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = when (p.currentToken()) { JsonToken.VALUE_STRING -> when (val str = p.valueAsString.lowercase()) { "true" -> 1 "false" -> 0 @@ -32,16 +32,16 @@ class BooleanToIntegerDeserializer : JsonDeserializer() { } class BooleanNumberSerializer @JvmOverloads constructor(t: Class? = null) : StdSerializer(t) { - override fun serialize(value: Boolean, gen: JsonGenerator, provider: SerializerProvider) { + override fun serialize(value: Boolean, gen: JsonGenerator, provider: SerializationContext) { gen.writeNumber(if (value) 1 else 0) } } -class BooleanNumberDeserializer : JsonDeserializer() { +class BooleanNumberDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = "0" != p.text } -class ZonedDateTimeDeserializer : JsonDeserializer() { +class ZonedDateTimeDeserializer : ValueDeserializer() { override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = ZonedDateTime.parse(p.text) } diff --git a/src/test/kotlin/ext/TestExt.kt b/src/test/kotlin/ext/TestExt.kt index aba1fc922..99ba37835 100644 --- a/src/test/kotlin/ext/TestExt.kt +++ b/src/test/kotlin/ext/TestExt.kt @@ -22,4 +22,28 @@ suspend fun registerUser(): Long { println("User ID: $userId") return userId -} \ No newline at end of file +} +// --------------------------------------------------------------------------- +// Live-server test gating +// --------------------------------------------------------------------------- + +/** + * Integration tests (Mai2Test, WaccaTest) talk to a real AquaDX server on + * localhost:80 backed by MariaDB. When no server is listening, tests + * registered via [liveTest] are marked as disabled instead of failing. + */ +val LIVE_SERVER_AVAILABLE: Boolean by lazy { + runCatching { + java.net.Socket().use { socket -> + socket.connect(java.net.InetSocketAddress("localhost", 80), 1000) + true + } + }.onFailure { println("No live server on localhost:80 - live-server tests will be skipped") } + .getOrDefault(false) +} + +/** Registers a test that only runs when [LIVE_SERVER_AVAILABLE] is true. */ +fun io.kotest.core.spec.style.scopes.StringSpecRootScope.liveTest( + name: String, + test: suspend io.kotest.core.test.TestScope.() -> Unit, +) = name.config(enabled = LIVE_SERVER_AVAILABLE, test = test) diff --git a/src/test/kotlin/test/Chu3ImportJsonTest.kt b/src/test/kotlin/test/Chu3ImportJsonTest.kt index 8e0ea1896..42f6f8e8e 100644 --- a/src/test/kotlin/test/Chu3ImportJsonTest.kt +++ b/src/test/kotlin/test/Chu3ImportJsonTest.kt @@ -1,8 +1,9 @@ package test -import com.fasterxml.jackson.databind.JsonMappingException +import tools.jackson.databind.DatabindException import ext.JACKSON import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData +import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge import icu.samnyan.aqua.sega.chusan.model.userdata.UserCMissionProgress import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.StringSpec @@ -44,8 +45,28 @@ class Chu3ImportJsonTest : StringSpec({ imported.lastPlayDate shouldBe LocalDateTime.of(2026, 8, 4, 1, 2, 3) } + "legacy backup date arrays remain importable" { + val userData = JACKSON.readValue( + """{ + "eventWatchedDate":[2026,8,4,1,2], + "firstPlayDate":[2026,8,4,1,2,3], + "lastPlayDate":[2026,8,4,1,2,3,123456000] + }""", + Chu3UserData::class.java, + ) + val charge = JACKSON.readValue( + """{"purchaseDate":[2026,8,4,1,2,3,123456000]}""", + UserCharge::class.java, + ) + + userData.eventWatchedDate shouldBe LocalDateTime.of(2026, 8, 4, 1, 2) + userData.firstPlayDate shouldBe LocalDateTime.of(2026, 8, 4, 1, 2, 3) + userData.lastPlayDate shouldBe LocalDateTime.of(2026, 8, 4, 1, 2, 3, 123456000) + charge.purchaseDate shouldBe LocalDateTime.of(2026, 8, 4, 1, 2, 3, 123456000) + } + "invalid CHUNITHM dates are rejected with a useful message" { - val error = shouldThrow { + val error = shouldThrow { JACKSON.readValue( """{"firstPlayDate":"573-573-573T22:49:23"}""", Chu3UserData::class.java, @@ -53,6 +74,6 @@ class Chu3ImportJsonTest : StringSpec({ } error.originalMessage shouldBe - "Cannot deserialize value of type `java.time.LocalDateTime` from String \"573-573-573T22:49:23\": Invalid date-time; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second" + "Invalid date-time '573-573-573T22:49:23'; expected yyyy-MM-dd'T'HH:mm:ss or yyyy-MM-dd HH:mm:ss with an optional fractional second" } }) diff --git a/src/test/kotlin/test/ImportExportParityTest.kt b/src/test/kotlin/test/ImportExportParityTest.kt new file mode 100644 index 000000000..b5709ad39 --- /dev/null +++ b/src/test/kotlin/test/ImportExportParityTest.kt @@ -0,0 +1,74 @@ +package test + +import ext.JACKSON +import ext.parseJackson +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.Chu3UserLinkedVerse +import icu.samnyan.aqua.sega.chusan.model.userdata.UserCharge +import icu.samnyan.aqua.sega.chusan.model.userdata.UserCourse +import icu.samnyan.aqua.sega.chusan.model.userdata.UserDuel +import icu.samnyan.aqua.sega.chusan.model.userdata.UserGacha +import icu.samnyan.aqua.sega.chusan.model.userdata.UserLoginBonus +import icu.samnyan.aqua.sega.chusan.model.userdata.UserPlaylog +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserKaleidx +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe +import java.time.LocalDateTime + +class ImportExportParityTest : StringSpec({ + val timestamp = LocalDateTime.of(2026, 8, 4, 1, 2, 3, 123_456_000) + + "CHUNITHM export JSON can be imported without changing data" { + val data = Chu3DataExport().apply { + userData.eventWatchedDate = timestamp + userData.firstPlayDate = timestamp + userData.lastPlayDate = timestamp + userChargeList = listOf(UserCharge().apply { + purchaseDate = timestamp + validDate = timestamp + paramDate = timestamp + }) + userCourseList = listOf(UserCourse().apply { lastPlayDate = timestamp }) + userDuelList = listOf(UserDuel().apply { lastPlayDate = timestamp }) + userPlaylogList = listOf(UserPlaylog().apply { + playDate = timestamp + userPlayDate = timestamp + }) + userGachaList = listOf(UserGacha().apply { dailyGachaDate = timestamp }) + userLinkedVerseList = listOf(Chu3UserLinkedVerse().apply { clearDate = timestamp }) + userLoginBonusList = listOf(UserLoginBonus(lastUpdateDate = timestamp)) + } + + assertJsonRoundTrip(data) + val tree = JACKSON.readTree(JACKSON.writeValueAsString(data)) + tree.at("/userData/firstPlayDate").isTextual shouldBe true + tree.at("/userChargeList/0/purchaseDate").isTextual shouldBe true + } + + "maimai DX export JSON can be imported without changing data" { + val data = Maimai2DataExport().apply { + userKaleidxScopeList = listOf(Mai2UserKaleidx().apply { + bestAchievementDate = timestamp + bestDeluxscoreDate = timestamp + clearDate = timestamp + lastPlayDate = timestamp + }) + } + + assertJsonRoundTrip(data) + JACKSON.readTree(JACKSON.writeValueAsString(data)) + .at("/userKaleidxScopeList/0/lastPlayDate").isTextual shouldBe true + } + + "ONGEKI export JSON can be imported without changing data" { + assertJsonRoundTrip(OngekiDataExport()) + } +}) + +private inline fun assertJsonRoundTrip(value: T) { + val exportedJson = JACKSON.writeValueAsString(value) + val imported = exportedJson.parseJackson(T::class.java) + JACKSON.readTree(JACKSON.writeValueAsString(imported)) shouldBe JACKSON.readTree(exportedJson) +} diff --git a/src/test/kotlin/test/Mai2Test.kt b/src/test/kotlin/test/Mai2Test.kt index 6f8d4624c..35fa74abf 100644 --- a/src/test/kotlin/test/Mai2Test.kt +++ b/src/test/kotlin/test/Mai2Test.kt @@ -28,7 +28,7 @@ class Mai2Test : StringSpec({ userId = registerUser() } - "GetGameSettingApi" { + liveTest("GetGameSettingApi") { println("UserID: $userId") post("GetGameSettingApi", """{"placeId":291,"clientId":"$CLIENT_ID"}""").let { (_, result) -> val gs = result["gameSetting"] as Map<*, *> @@ -37,86 +37,86 @@ class Mai2Test : StringSpec({ } } - "GetGameRankingApi" { + liveTest("GetGameRankingApi") { post("GetGameRankingApi", """{"type":1}""").let { (_, result) -> result["type"] shouldBe 1 assert(result["gameRankingList"] is List<*>) } } - "GetGameEventApi" { + liveTest("GetGameEventApi") { post("GetGameEventApi", """{"type":1,"isAllEvent":true}""").let { (_, result) -> result.keys shouldBe setOf("type", "gameEventList") ((result["gameEventList"] as List<*>).first() as Map<*, *>).keys shouldBe setOf("id", "type", "startDate", "endDate", "enable", "disableArea") } } - "GetGameTournamentInfoApi" { + liveTest("GetGameTournamentInfoApi") { post("GetGameTournamentInfoApi", """{}""").let { (_, result) -> result shouldBe """{"length":0,"gameTournamentInfoList":[]}""".jsonMap() } } - "GetGameChargeApi" { + liveTest("GetGameChargeApi") { post("GetGameChargeApi", """{"isAll":false}""").let { (_, result) -> result shouldBe """{"length":5,"gameChargeList":[{"chargeId":0,"orderId":0,"price":1,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":2,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":3,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":4,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"},{"chargeId":0,"orderId":0,"price":5,"startDate":"2019-01-01 00:00:00.000000","endDate":"2099-01-01 00:00:00.000000"}]}""".jsonMap() } } - "GetGameNgMusicIdApi" { + liveTest("GetGameNgMusicIdApi") { post("GetGameNgMusicIdApi", """{}""").let { (_, result) -> result shouldBe """{"length":0,"musicIdList":[],"ngMusicDataList":[]}""".jsonMap() } } - "UpsertClientSettingApi" { + liveTest("UpsertClientSettingApi") { post("UpsertClientSettingApi", """{"clientSetting":{"placeId":291,"clientId":"$CLIENT_ID","placeName":"","regionId":1,"regionName":"W","bordId":"ACAE01A9999","romVersion":100,"isDevelop":true,"isAou":true}}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UpsertClientSettingApi"}""".jsonMap() } } - "UpsertClientTestmodeApi" { + liveTest("UpsertClientTestmodeApi") { post("UpsertClientTestmodeApi", """{"clientTestmode":{"placeId":291,"clientId":"$CLIENT_ID","trackSingle":1,"trackMulti":0,"trackEvent":0,"totalMachine":4,"satelliteId":5,"cameraPosition":0}}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UpsertClientTestmodeApi"}""".jsonMap() } } - "UserLoginApi" { + liveTest("UserLoginApi") { post("UserLoginApi", """{"userId":$userId,"accessCode":"$ACCESS_CODE","regionId":1,"placeId":291,"clientId":"$CLIENT_ID","dateTime":1711485182}""").let { (_, result) -> result shouldBe """{"returnCode":1,"lastLoginDate":"2020-01-01 00:00:00.0","loginCount":1,"consecutiveLoginCount":0,"loginId":1,"Bearer":"meow","bearer":"meow"}""".jsonMap() } } - "UploadUserPlaylogApi" { + liveTest("UploadUserPlaylogApi") { post("UploadUserPlaylogApi", """{"userId":$userId,"userPlaylog":{"userId":0,"orderId":0,"playlogId":1,"version":1041000,"placeId":291,"placeName":"","loginDate":1711485915,"playDate":"2024-03-26","userPlayDate":"2024-03-27 05:49:38.0","type":0,"musicId":11479,"level":2,"trackNo":1,"vsMode":0,"vsUserName":"","vsStatus":0,"vsUserRating":0,"vsUserAchievement":0,"vsUserGradeRank":0,"vsRank":0,"playerNum":1,"playedUserId1":0,"playedUserName1":"","playedMusicLevel1":0,"playedUserId2":0,"playedUserName2":"","playedMusicLevel2":0,"playedUserId3":0,"playedUserName3":"","playedMusicLevel3":0,"characterId1":101,"characterLevel1":1,"characterAwakening1":0,"characterId2":400101,"characterLevel2":1,"characterAwakening2":0,"characterId3":105,"characterLevel3":1,"characterAwakening3":0,"characterId4":104,"characterLevel4":1,"characterAwakening4":0,"characterId5":103,"characterLevel5":1,"characterAwakening5":0,"achievement":997456,"deluxscore":1725,"scoreRank":11,"maxCombo":674,"totalCombo":674,"maxSync":674,"totalSync":0,"tapCriticalPerfect":276,"tapPerfect":194,"tapGreat":29,"tapGood":2,"tapMiss":0,"holdCriticalPerfect":56,"holdPerfect":23,"holdGreat":2,"holdGood":1,"holdMiss":0,"slideCriticalPerfect":42,"slidePerfect":0,"slideGreat":0,"slideGood":0,"slideMiss":0,"touchCriticalPerfect":32,"touchPerfect":0,"touchGreat":0,"touchGood":0,"touchMiss":0,"breakCriticalPerfect":9,"breakPerfect":7,"breakGreat":1,"breakGood":0,"breakMiss":0,"isTap":true,"isHold":true,"isSlide":true,"isTouch":true,"isBreak":true,"isCriticalDisp":true,"isFastLateDisp":true,"fastCount":24,"lateCount":11,"isAchieveNewRecord":true,"isDeluxscoreNewRecord":true,"comboStatus":1,"syncStatus":0,"isClear":true,"beforeRating":0,"afterRating":271,"beforeGrade":0,"afterGrade":0,"afterGradeRank":0,"beforeDeluxRating":0,"afterDeluxRating":271,"isPlayTutorial":true,"isEventMode":false,"isFreedomMode":false,"playMode":0,"isNewFree":true,"trialPlayAchievement":-1,"extNum1":0,"extNum2":0,"extNum4":106,"extBool1":false}}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UploadUserPlaylogApi"}""".jsonMap() } } - "UpsertUserAllApi" { + liveTest("UpsertUserAllApi") { post("UpsertUserAllApi", """{"userId":$userId,"playlogId":1,"isEventMode":false,"isFreePlay":true,"upsertUserAll":{"userData":[{"accessCode":"$ACCESS_CODE","userName":"AZA☆","isNetMember":0,"iconId":11,"plateId":1,"titleId":11,"partnerId":1,"frameId":1,"selectMapId":400001,"totalAwake":0,"gradeRating":0,"musicRating":271,"playerRating":271,"highestRating":271,"gradeRank":0,"classRank":0,"courseRank":0,"charaSlot":[101,400101,105,104,103],"charaLockSlot":[0,0,0,0,0],"contentBit":2169888,"playCount":1,"currentPlayCount":1,"renameCredit":0,"mapStock":0,"eventWatchedDate":"2024-03-27 05:45:15.0","lastGameId":"SDEZ","lastRomVersion":"1.41.00","lastDataVersion":"1.40.08","lastLoginDate":"2024-03-27 05:45:15.0","lastPlayDate":"2024-03-27 05:56:54.0","lastPlayCredit":1,"lastPlayMode":0,"lastPlaceId":291,"lastPlaceName":"","lastAllNetId":0,"lastRegionId":1,"lastRegionName":"W","lastClientId":"$CLIENT_ID","lastCountryCode":"JPN","lastSelectEMoney":0,"lastSelectTicket":0,"lastSelectCourse":0,"lastCountCourse":0,"firstGameId":"SDEZ","firstRomVersion":"1.41.00","firstDataVersion":"1.40.08","firstPlayDate":"2024-03-27 05:45:15.0","compatibleCmVersion":"1.40.00","dailyBonusDate":"1970-01-01 09:00:00.0","dailyCourseBonusDate":"1970-01-01 09:00:00.0","lastPairLoginDate":"1970-01-01 09:00:00.0","lastTrialPlayDate":"1970-01-01 09:00:00.0","playVsCount":0,"playSyncCount":0,"winCount":0,"helpCount":0,"comboCount":0,"totalDeluxscore":1725,"totalBasicDeluxscore":0,"totalAdvancedDeluxscore":0,"totalExpertDeluxscore":1725,"totalMasterDeluxscore":0,"totalReMasterDeluxscore":0,"totalSync":0,"totalBasicSync":0,"totalAdvancedSync":0,"totalExpertSync":0,"totalMasterSync":0,"totalReMasterSync":0,"totalAchievement":997456,"totalBasicAchievement":0,"totalAdvancedAchievement":0,"totalExpertAchievement":997456,"totalMasterAchievement":0,"totalReMasterAchievement":0,"playerOldRating":271,"playerNewRating":0,"banState":0,"dateTime":1711485182}],"userExtend":[{"selectMusicId":11479,"selectDifficultyId":2,"categoryIndex":106,"musicIndex":114,"extraFlag":529,"selectScoreType":1,"extendContentBit":1,"isPhotoAgree":false,"isGotoCodeRead":false,"selectResultDetails":false,"selectResultScoreViewType":0,"sortCategorySetting":0,"sortMusicSetting":0,"playStatusSetting":0,"selectedCardList":[],"encountMapNpcList":[]}],"userOption":[{"optionKind":3,"noteSpeed":24,"slideSpeed":10,"touchSpeed":21,"tapDesign":0,"holdDesign":0,"slideDesign":0,"starType":0,"outlineDesign":15,"noteSize":1,"slideSize":1,"touchSize":1,"starRotate":1,"dispCenter":1,"outFrameType":2,"dispChain":2,"dispRate":0,"dispBar":0,"touchEffect":0,"submonitorAnimation":2,"submonitorAchive":0,"submonitorAppeal":0,"matching":1,"trackSkip":0,"brightness":0,"mirrorMode":0,"dispJudge":8,"dispJudgePos":5,"dispJudgeTouchPos":1,"adjustTiming":34,"judgeTiming":34,"ansVolume":6,"tapHoldVolume":5,"criticalSe":0,"tapSe":0,"breakSe":0,"breakVolume":5,"exSe":0,"exVolume":5,"slideSe":0,"slideVolume":5,"breakSlideVolume":5,"touchVolume":5,"touchHoldVolume":5,"damageSeVolume":5,"headPhoneVolume":0,"sortTab":0,"sortMusic":0}],"userCharacterList":[{"characterId":101,"level":2,"awakening":0,"useCount":0},{"characterId":102,"level":1,"awakening":0,"useCount":0},{"characterId":103,"level":2,"awakening":0,"useCount":0},{"characterId":104,"level":2,"awakening":0,"useCount":0},{"characterId":105,"level":2,"awakening":0,"useCount":0},{"characterId":201,"level":1,"awakening":0,"useCount":0},{"characterId":202,"level":1,"awakening":0,"useCount":0},{"characterId":203,"level":1,"awakening":0,"useCount":0},{"characterId":204,"level":1,"awakening":0,"useCount":0},{"characterId":205,"level":1,"awakening":0,"useCount":0},{"characterId":301,"level":1,"awakening":0,"useCount":0},{"characterId":302,"level":1,"awakening":0,"useCount":0},{"characterId":303,"level":1,"awakening":0,"useCount":0},{"characterId":304,"level":1,"awakening":0,"useCount":0},{"characterId":305,"level":1,"awakening":0,"useCount":0},{"characterId":306,"level":1,"awakening":0,"useCount":0},{"characterId":392,"level":1,"awakening":0,"useCount":0},{"characterId":393,"level":1,"awakening":0,"useCount":0},{"characterId":394,"level":1,"awakening":0,"useCount":0},{"characterId":395,"level":1,"awakening":0,"useCount":0},{"characterId":401,"level":1,"awakening":0,"useCount":0},{"characterId":402,"level":1,"awakening":0,"useCount":0},{"characterId":403,"level":1,"awakening":0,"useCount":0},{"characterId":404,"level":1,"awakening":0,"useCount":0},{"characterId":405,"level":1,"awakening":0,"useCount":0},{"characterId":501,"level":1,"awakening":0,"useCount":0},{"characterId":502,"level":1,"awakening":0,"useCount":0},{"characterId":503,"level":1,"awakening":0,"useCount":0},{"characterId":504,"level":1,"awakening":0,"useCount":0},{"characterId":505,"level":1,"awakening":0,"useCount":0},{"characterId":601,"level":1,"awakening":0,"useCount":0},{"characterId":602,"level":1,"awakening":0,"useCount":0},{"characterId":603,"level":1,"awakening":0,"useCount":0},{"characterId":604,"level":1,"awakening":0,"useCount":0},{"characterId":605,"level":1,"awakening":0,"useCount":0},{"characterId":400101,"level":2,"awakening":0,"useCount":0}],"userGhost":[],"userMapList":[{"mapId":1,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":2,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":3,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":4,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":5,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":6,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150001,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150002,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150003,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150004,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150005,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350001,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350003,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350004,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350006,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350007,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400001,"distance":14000,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400002,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400003,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400004,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400005,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400006,"distance":0,"isLock":false,"isClear":false,"isComplete":false}],"userLoginBonusList":[{"bonusId":38,"point":1,"isCurrent":true,"isComplete":false}],"userRatingList":[{"rating":0,"ratingList":[{"musicId":11479,"level":2,"romVersion":23011,"achievement":997456}],"newRatingList":[],"nextRatingList":[],"nextNewRatingList":[],"udemae":{"rate":0,"maxRate":0,"classValue":0,"maxClassValue":0,"totalWinNum":0,"totalLoseNum":0,"maxWinNum":0,"maxLoseNum":0,"winNum":0,"loseNum":0,"npcTotalWinNum":0,"npcTotalLoseNum":0,"npcMaxWinNum":0,"npcMaxLoseNum":0,"npcWinNum":0,"npcLoseNum":0}}],"userItemList":[{"itemKind":1,"itemId":1,"stock":1,"isValid":true},{"itemKind":1,"itemId":11,"stock":1,"isValid":true},{"itemKind":2,"itemId":1,"stock":1,"isValid":true},{"itemKind":2,"itemId":11,"stock":1,"isValid":true},{"itemKind":2,"itemId":5023,"stock":1,"isValid":true},{"itemKind":3,"itemId":1,"stock":1,"isValid":true},{"itemKind":3,"itemId":10,"stock":1,"isValid":true},{"itemKind":3,"itemId":12,"stock":1,"isValid":true},{"itemKind":3,"itemId":11,"stock":1,"isValid":true},{"itemKind":10,"itemId":1,"stock":1,"isValid":true},{"itemKind":10,"itemId":17,"stock":1,"isValid":true},{"itemKind":11,"itemId":1,"stock":1,"isValid":true},{"itemKind":6,"itemId":11479,"stock":1,"isValid":true}],"userMusicDetailList":[{"musicId":11479,"level":2,"playCount":1,"achievement":997456,"comboStatus":1,"syncStatus":0,"deluxscoreMax":1725,"scoreRank":11,"extNum1":0}],"userCourseList":[],"userFriendSeasonRankingList":[],"userChargeList":[],"userFavoriteList":[],"userActivityList":[{"playList":[{"kind":1,"id":10,"sortNumber":1711485937,"param1":0,"param2":0,"param3":0,"param4":0},{"kind":1,"id":90,"sortNumber":1711486030,"param1":400005,"param2":0,"param3":0,"param4":0},{"kind":1,"id":30,"sortNumber":1711486333,"param1":11479,"param2":2,"param3":129,"param4":0},{"kind":1,"id":23,"sortNumber":1711486333,"param1":11479,"param2":2,"param3":129,"param4":0}],"musicList":[{"kind":2,"id":11479,"sortNumber":1711486333,"param1":0,"param2":0,"param3":0,"param4":0}]}],"userGamePlaylogList":[{"playlogId":1,"version":"1.41.00","playDate":"2024-03-27 05:56:54.0","playMode":0,"useTicketId":-1,"playCredit":1,"playTrack":1,"clientId":"$CLIENT_ID","isPlayTutorial":true,"isEventMode":false,"isNewFree":true,"playCount":189,"playSpecial":-1873749656,"playOtherUserId":0}],"user2pPlaylog":{"userId1":0,"userId2":0,"userName1":"","userName2":"","regionId":0,"placeId":0,"user2pPlaylogDetailList":[]},"isNewCharacterList":"111111111111111111111111111111111111","isNewMapList":"1111111111111111111111","isNewLoginBonusList":"1","isNewItemList":"1111111111111","isNewMusicDetailList":"1","isNewCourseList":"","isNewFavoriteList":"","isNewFriendSeasonRankingList":""}}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UpsertUserAllApi"}""".jsonMap() } } - "UserLogoutApi" { + liveTest("UserLogoutApi") { post("UserLogoutApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UserLogoutApi"}""".jsonMap() } } - "GetUserPreviewApi" { + liveTest("GetUserPreviewApi") { post("GetUserPreviewApi", """{"userId":$userId,"segaIdAuthKey":""}""").let { (_, result) -> result shouldBe """{"userId":$userId,"userName":"AZA☆","lastGameId":"SDEZ","lastDataVersion":"1.40.08","lastRomVersion":"1.41.00","lastLoginDate":"2024-03-27 05:56:54.0","lastPlayDate":"2024-03-27 05:56:54.0","playerRating":271,"nameplateId":1,"iconId":11,"trophyId":0,"partnerId":1,"frameId":1,"dispRate":0,"totalAwake":0,"isNetMember":1,"dailyBonusDate":"1970-01-01 09:00:00.0","headPhoneVolume":0,"banState":0,"isLogin":false,"isInherit":false}""".jsonMap() } } - "UserLoginApi2" { + liveTest("UserLoginApi2") { post("UserLoginApi", """{"userId":$userId,"accessCode":"$ACCESS_CODE","regionId":1,"placeId":291,"clientId":"$CLIENT_ID","dateTime":1711485182}""").let { (_, result) -> result shouldBe """{"returnCode":1,"lastLoginDate":"2020-01-01 00:00:00.0","loginCount":1,"consecutiveLoginCount":0,"loginId":1,"Bearer":"meow","bearer":"meow"}""".jsonMap() } } - "GetUserDataApi" { + liveTest("GetUserDataApi") { post("GetUserDataApi", """{"userId":$userId}""").let { (_, result) -> val userData = result["userData"] as Map<*, *> userData["userName"] shouldBe "AZA☆" @@ -125,13 +125,13 @@ class Mai2Test : StringSpec({ } } - "GetUserCardApi" { + liveTest("GetUserCardApi") { post("GetUserCardApi", """{"userId":$userId,"nextIndex":0,"maxCount":20}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":0,"userCardList":[]}""".jsonMap() } } - "GetUserCharacterApi" { + liveTest("GetUserCharacterApi") { post("GetUserCharacterApi", """{"userId":$userId}""").let { (_, result) -> result.keys shouldBe setOf("userId", "userCharacterList") result["userId"] shouldBe userId @@ -141,7 +141,7 @@ class Mai2Test : StringSpec({ } } - "GetUserItemApi" { + liveTest("GetUserItemApi") { infix fun Map.userItemEquals(exp: Map) { this["userId"] shouldBe userId this["nextIndex"] shouldBe exp["nextIndex"] @@ -195,19 +195,19 @@ class Mai2Test : StringSpec({ } } - "GetUserCourseApi" { + liveTest("GetUserCourseApi") { post("GetUserCourseApi", """{"userId":$userId,"nextIndex":0}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":0,"userCourseList":[]}""".jsonMap() } } - "GetUserChargeApi" { + liveTest("GetUserChargeApi") { post("GetUserChargeApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":0,"userChargeList":[]}""".jsonMap() } } - "GetUserFavoriteApi" { + liveTest("GetUserFavoriteApi") { post("GetUserFavoriteApi", """{"userId":$userId,"itemKind":1}""").let { (_, result) -> result shouldBe """{"userId":$userId}""".jsonMap() } @@ -233,73 +233,73 @@ class Mai2Test : StringSpec({ } } - "GetUserGhostApi" { + liveTest("GetUserGhostApi") { post("GetUserGhostApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":0,"userGhostList":[]}""".jsonMap() } } - "GetUserMapApi" { + liveTest("GetUserMapApi") { post("GetUserMapApi", """{"userId":$userId,"nextIndex":0,"maxCount":20}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":22,"userMapList":[{"mapId":1,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":2,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":3,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":4,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":5,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":6,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150001,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150002,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150003,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150004,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":150005,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350001,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350003,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350004,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350006,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":350007,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400001,"distance":14000,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400002,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400003,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400004,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400005,"distance":0,"isLock":false,"isClear":false,"isComplete":false},{"mapId":400006,"distance":0,"isLock":false,"isClear":false,"isComplete":false}]}""".jsonMap() } } - "GetUserLoginBonusApi" { + liveTest("GetUserLoginBonusApi") { post("GetUserLoginBonusApi", """{"userId":$userId,"nextIndex":0,"maxCount":20}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":1,"userLoginBonusList":[{"bonusId":38,"point":1,"isCurrent":false,"isComplete":false}]}""".jsonMap() } } - "GetUserRegionApi" { + liveTest("GetUserRegionApi") { post("GetUserRegionApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"length":1,"userRegionList":[{"regionId":1,"playCount":1}]}""".jsonMap() } } - "GetUserRecommendRateMusicApi" { + liveTest("GetUserRecommendRateMusicApi") { post("GetUserRecommendRateMusicApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"userRecommendRateMusicIdList":[]}""".jsonMap() } } - "GetUserRecommendSelectMusicApi" { + liveTest("GetUserRecommendSelectMusicApi") { post("GetUserRecommendSelectMusicApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"userRecommendSelectionMusicIdList":[]}""".jsonMap() } } - "GetUserOptionApi" { + liveTest("GetUserOptionApi") { post("GetUserOptionApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"userOption":{"optionKind":3,"noteSpeed":24,"slideSpeed":10,"touchSpeed":21,"tapDesign":0,"holdDesign":0,"slideDesign":0,"starType":0,"outlineDesign":15,"noteSize":1,"slideSize":1,"touchSize":1,"starRotate":1,"dispCenter":1,"dispChain":2,"dispRate":0,"dispBar":0,"touchEffect":0,"submonitorAnimation":2,"submonitorAchive":0,"submonitorAppeal":0,"matching":1,"trackSkip":0,"brightness":0,"mirrorMode":0,"dispJudge":8,"dispJudgePos":5,"dispJudgeTouchPos":1,"adjustTiming":34,"judgeTiming":34,"ansVolume":6,"tapHoldVolume":5,"criticalSe":0,"tapSe":0,"breakSe":0,"breakVolume":5,"exSe":0,"exVolume":5,"slideSe":0,"slideVolume":5,"touchHoldVolume":5,"damageSeVolume":5,"headPhoneVolume":0,"sortTab":0,"sortMusic":0,"outFrameType":2,"breakSlideVolume":5,"touchVolume":5}}""".jsonMap() } } - "GetUserExtendApi" { + liveTest("GetUserExtendApi") { post("GetUserExtendApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"userExtend":{"selectMusicId":11479,"selectDifficultyId":2,"categoryIndex":106,"musicIndex":114,"extraFlag":529,"selectScoreType":1,"extendContentBit":1,"isPhotoAgree":false,"isGotoCodeRead":false,"selectResultDetails":false,"sortCategorySetting":0,"sortMusicSetting":0,"playStatusSetting":0,"selectedCardList":[],"encountMapNpcList":[],"selectResultScoreViewType":0}}""".jsonMap() } } - "GetUserRatingApi" { + liveTest("GetUserRatingApi") { post("GetUserRatingApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"userId":$userId,"userRating":{"rating":271,"ratingList":[{"musicId":11479,"level":2,"romVersion":23011,"achievement":997456}],"newRatingList":[],"nextRatingList":[],"nextNewRatingList":[],"udemae":{"rate":0,"maxRate":0,"classValue":0,"maxClassValue":0,"totalWinNum":0,"totalLoseNum":0,"maxWinNum":0,"maxLoseNum":0,"winNum":0,"loseNum":0,"npcTotalWinNum":0,"npcTotalLoseNum":0,"npcMaxWinNum":0,"npcMaxLoseNum":0,"npcWinNum":0,"npcLoseNum":0}}}""".jsonMap() } } - "GetUserMusicApi" { + liveTest("GetUserMusicApi") { post("GetUserMusicApi", """{"userId":$userId,"nextIndex":0,"maxCount":50}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":1,"userMusicList":[{"userMusicDetailList":[{"musicId":11479,"level":2,"playCount":1,"achievement":997456,"comboStatus":1,"syncStatus":0,"deluxscoreMax":1725,"scoreRank":11,"extNum1":0}]}]}""".jsonMap() } } - "GetUserPortraitApi" { + liveTest("GetUserPortraitApi") { post("GetUserPortraitApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"length":0,"userPortraitList":[]}""".jsonMap() } } - "GetUserActivityApi" { + liveTest("GetUserActivityApi") { post("GetUserActivityApi", """{"userId":$userId}""").let { (_, result) -> result.keys shouldBe setOf("userActivity") ((result["userActivity"] as Map<*, *>)["playList"] as List<*>).toSet() shouldBe setOf( @@ -314,13 +314,13 @@ class Mai2Test : StringSpec({ } } - "GetUserFriendSeasonRankingApi" { + liveTest("GetUserFriendSeasonRankingApi") { post("GetUserFriendSeasonRankingApi", """{"userId":$userId,"nextIndex":0,"maxCount":20}""").let { (_, result) -> result shouldBe """{"userId":$userId,"nextIndex":0,"length":0,"userFriendSeasonRankingList":[]}""".jsonMap() } } - "GetUserFavoriteItemApi" { + liveTest("GetUserFavoriteItemApi") { // TODO: Check if this api should return string or int post("GetUserFavoriteItemApi", """{"userId":$userId,"kind":2,"nextIndex":0,"maxCount":100,"isAllFavoriteItem":false}""").let { (_, result) -> // result shouldBe """{"userId":"$USER_ID","kind":"2","length":"0","nextIndex":"0","userFavoriteItemList":[]}""".jsonMap() @@ -333,19 +333,19 @@ class Mai2Test : StringSpec({ } } - "UploadUserPlaylogApi2" { + liveTest("UploadUserPlaylogApi2") { post("UploadUserPlaylogApi", """{"userId":$userId,"userPlaylog":{"userId":0,"orderId":0,"playlogId":1,"version":1041000,"placeId":291,"placeName":"","loginDate":1711486636,"playDate":"2024-03-26","userPlayDate":"2024-03-27 05:59:00.0","type":0,"musicId":11176,"level":2,"trackNo":1,"vsMode":0,"vsUserName":"","vsStatus":0,"vsUserRating":0,"vsUserAchievement":0,"vsUserGradeRank":0,"vsRank":0,"playerNum":1,"playedUserId1":0,"playedUserName1":"","playedMusicLevel1":0,"playedUserId2":0,"playedUserName2":"","playedMusicLevel2":0,"playedUserId3":0,"playedUserName3":"","playedMusicLevel3":0,"characterId1":101,"characterLevel1":2,"characterAwakening1":0,"characterId2":102,"characterLevel2":1,"characterAwakening2":0,"characterId3":400101,"characterLevel3":2,"characterAwakening3":0,"characterId4":105,"characterLevel4":2,"characterAwakening4":0,"characterId5":104,"characterLevel5":2,"characterAwakening5":0,"achievement":999840,"deluxscore":2178,"scoreRank":11,"maxCombo":807,"totalCombo":807,"maxSync":807,"totalSync":0,"tapCriticalPerfect":411,"tapPerfect":159,"tapGreat":17,"tapGood":1,"tapMiss":0,"holdCriticalPerfect":61,"holdPerfect":27,"holdGreat":0,"holdGood":0,"holdMiss":0,"slideCriticalPerfect":58,"slidePerfect":0,"slideGreat":0,"slideGood":2,"slideMiss":0,"touchCriticalPerfect":49,"touchPerfect":0,"touchGreat":0,"touchGood":0,"touchMiss":0,"breakCriticalPerfect":9,"breakPerfect":12,"breakGreat":1,"breakGood":0,"breakMiss":0,"isTap":true,"isHold":true,"isSlide":true,"isTouch":true,"isBreak":true,"isCriticalDisp":true,"isFastLateDisp":true,"fastCount":8,"lateCount":13,"isAchieveNewRecord":true,"isDeluxscoreNewRecord":true,"comboStatus":1,"syncStatus":0,"isClear":true,"beforeRating":271,"afterRating":524,"beforeGrade":0,"afterGrade":0,"afterGradeRank":0,"beforeDeluxRating":271,"afterDeluxRating":524,"isPlayTutorial":true,"isEventMode":false,"isFreedomMode":false,"playMode":0,"isNewFree":false,"trialPlayAchievement":-1,"extNum1":0,"extNum2":0,"extNum4":106,"extBool1":false}}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UploadUserPlaylogApi"}""".jsonMap() } } - "UpsertUserAllApi2" { + liveTest("UpsertUserAllApi2") { post("UpsertUserAllApi", """{"userId":$userId,"playlogId":1,"isEventMode":false,"isFreePlay":true,"upsertUserAll":{"userData":[{"accessCode":"$ACCESS_CODE","userName":"AZA☆","isNetMember":1,"iconId":11,"plateId":1,"titleId":11,"partnerId":1,"frameId":1,"selectMapId":400001,"totalAwake":0,"gradeRating":0,"musicRating":524,"playerRating":524,"highestRating":524,"gradeRank":0,"classRank":0,"courseRank":0,"charaSlot":[101,102,400101,105,104],"charaLockSlot":[0,0,0,0,0],"contentBit":6364192,"playCount":2,"currentPlayCount":2,"renameCredit":0,"mapStock":5000,"eventWatchedDate":"2024-03-27 05:57:16.0","lastGameId":"SDEZ","lastRomVersion":"1.41.00","lastDataVersion":"1.40.08","lastLoginDate":"2024-03-27 05:57:16.0","lastPlayDate":"2024-03-27 06:02:48.0","lastPlayCredit":1,"lastPlayMode":0,"lastPlaceId":291,"lastPlaceName":"","lastAllNetId":0,"lastRegionId":1,"lastRegionName":"W","lastClientId":"$CLIENT_ID","lastCountryCode":"JPN","lastSelectEMoney":0,"lastSelectTicket":0,"lastSelectCourse":0,"lastCountCourse":0,"firstGameId":"SDEZ","firstRomVersion":"1.41.00","firstDataVersion":"1.40.08","firstPlayDate":"2024-03-27 05:45:15.0","compatibleCmVersion":"1.40.00","dailyBonusDate":"1970-01-01 09:00:00.0","dailyCourseBonusDate":"1970-01-01 09:00:00.0","lastPairLoginDate":"1970-01-01 09:00:00.0","lastTrialPlayDate":"1970-01-01 09:00:00.0","playVsCount":0,"playSyncCount":0,"winCount":0,"helpCount":0,"comboCount":0,"totalDeluxscore":3903,"totalBasicDeluxscore":0,"totalAdvancedDeluxscore":0,"totalExpertDeluxscore":3903,"totalMasterDeluxscore":0,"totalReMasterDeluxscore":0,"totalSync":0,"totalBasicSync":0,"totalAdvancedSync":0,"totalExpertSync":0,"totalMasterSync":0,"totalReMasterSync":0,"totalAchievement":1997296,"totalBasicAchievement":0,"totalAdvancedAchievement":0,"totalExpertAchievement":1997296,"totalMasterAchievement":0,"totalReMasterAchievement":0,"playerOldRating":524,"playerNewRating":0,"banState":0,"dateTime":1711485182}],"userExtend":[{"selectMusicId":11608,"selectDifficultyId":2,"categoryIndex":195,"musicIndex":96,"extraFlag":561,"selectScoreType":1,"extendContentBit":1,"isPhotoAgree":false,"isGotoCodeRead":false,"selectResultDetails":false,"selectResultScoreViewType":0,"sortCategorySetting":0,"sortMusicSetting":0,"playStatusSetting":0,"selectedCardList":[],"encountMapNpcList":[]}],"userOption":[{"optionKind":3,"noteSpeed":24,"slideSpeed":10,"touchSpeed":21,"tapDesign":0,"holdDesign":0,"slideDesign":0,"starType":0,"outlineDesign":15,"noteSize":1,"slideSize":1,"touchSize":1,"starRotate":1,"dispCenter":1,"outFrameType":2,"dispChain":2,"dispRate":0,"dispBar":0,"touchEffect":0,"submonitorAnimation":2,"submonitorAchive":0,"submonitorAppeal":0,"matching":1,"trackSkip":0,"brightness":0,"mirrorMode":0,"dispJudge":8,"dispJudgePos":5,"dispJudgeTouchPos":1,"adjustTiming":34,"judgeTiming":34,"ansVolume":6,"tapHoldVolume":5,"criticalSe":0,"tapSe":0,"breakSe":0,"breakVolume":5,"exSe":0,"exVolume":5,"slideSe":0,"slideVolume":5,"breakSlideVolume":5,"touchVolume":5,"touchHoldVolume":5,"damageSeVolume":5,"headPhoneVolume":0,"sortTab":0,"sortMusic":0}],"userCharacterList":[{"characterId":101,"level":4,"awakening":0,"useCount":0},{"characterId":102,"level":3,"awakening":0,"useCount":0},{"characterId":104,"level":4,"awakening":0,"useCount":0},{"characterId":105,"level":4,"awakening":0,"useCount":0},{"characterId":400101,"level":4,"awakening":0,"useCount":0},{"characterId":400102,"level":1,"awakening":0,"useCount":0}],"userGhost":[],"userMapList":[{"mapId":400001,"distance":30000,"isLock":false,"isClear":false,"isComplete":false}],"userLoginBonusList":[],"userRatingList":[{"rating":271,"ratingList":[{"musicId":11479,"level":2,"romVersion":23011,"achievement":997456},{"musicId":11176,"level":2,"romVersion":21007,"achievement":999840}],"newRatingList":[],"nextRatingList":[],"nextNewRatingList":[],"udemae":{"rate":0,"maxRate":0,"classValue":0,"maxClassValue":0,"totalWinNum":0,"totalLoseNum":0,"maxWinNum":0,"maxLoseNum":0,"winNum":0,"loseNum":0,"npcTotalWinNum":0,"npcTotalLoseNum":0,"npcMaxWinNum":0,"npcMaxLoseNum":0,"npcWinNum":0,"npcLoseNum":0}}],"userItemList":[{"itemKind":6,"itemId":11479,"stock":1,"isValid":true},{"itemKind":6,"itemId":11176,"stock":1,"isValid":true}],"userMusicDetailList":[{"musicId":11176,"level":2,"playCount":1,"achievement":999840,"comboStatus":1,"syncStatus":0,"deluxscoreMax":2178,"scoreRank":11,"extNum1":0}],"userCourseList":[],"userFriendSeasonRankingList":[{"seasonId":2015,"point":10,"rank":0,"rewardGet":false,"userName":"AZA☆","recordDate":"2024-03-27 06:02:34.0"}],"userChargeList":[],"userFavoriteList":[],"userActivityList":[{"playList":[{"kind":1,"id":30,"sortNumber":1711486333,"param1":11479,"param2":2,"param3":129,"param4":0},{"kind":1,"id":23,"sortNumber":1711486333,"param1":11479,"param2":2,"param3":129,"param4":0},{"kind":1,"id":90,"sortNumber":1711486030,"param1":400005,"param2":0,"param3":0,"param4":0},{"kind":1,"id":10,"sortNumber":1711485937,"param1":0,"param2":0,"param3":0,"param4":0}],"musicList":[{"kind":2,"id":11479,"sortNumber":1711486333,"param1":0,"param2":0,"param3":0,"param4":0},{"kind":2,"id":11176,"sortNumber":1711486905,"param1":0,"param2":0,"param3":0,"param4":0}]}],"userGamePlaylogList":[{"playlogId":1,"version":"1.41.00","playDate":"2024-03-27 06:02:48.0","playMode":0,"useTicketId":-1,"playCredit":1,"playTrack":1,"clientId":"$CLIENT_ID","isPlayTutorial":true,"isEventMode":false,"isNewFree":false,"playCount":190,"playSpecial":1642254544,"playOtherUserId":0}],"user2pPlaylog":{"userId1":0,"userId2":0,"userName1":"","userName2":"","regionId":0,"placeId":0,"user2pPlaylogDetailList":[]},"isNewCharacterList":"000001","isNewMapList":"0","isNewLoginBonusList":"","isNewItemList":"01","isNewMusicDetailList":"1","isNewCourseList":"","isNewFavoriteList":"","isNewFriendSeasonRankingList":"1"}}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UpsertUserAllApi"}""".jsonMap() } } - "UserLogoutApi2" { + liveTest("UserLogoutApi2") { post("UserLogoutApi", """{"userId":$userId}""").let { (_, result) -> result shouldBe """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.UserLogoutApi"}""".jsonMap() } diff --git a/src/test/kotlin/test/WaccaTest.kt b/src/test/kotlin/test/WaccaTest.kt index 101cd7ec4..ae0b362c7 100644 --- a/src/test/kotlin/test/WaccaTest.kt +++ b/src/test/kotlin/test/WaccaTest.kt @@ -67,333 +67,333 @@ class WaccaTest : StringSpec({ if (uid == 0L) uid = registerUser() } - "API Completion Status" { + liveTest("API Completion Status") { val ws = WaccaServer().apply { init() } val defined = (ws.handlerMap.keys + ws.cacheMap.keys).toSortedSet() val all = setOf("advertise/GetNews", "advertise/GetRanking", "competition/status/login", "competition/status/update", "housing/get", "housing/start", "user/goods/purchase", "user/info/getMyroom", "user/info/getRanking", "user/info/update", "user/mission/update", "user/music/unlock", "user/music/updateCoop", "user/music/updateTrial", "user/music/updateVersus", "user/music/update", "user/rating/update", "user/status/create", "user/status/getDetail", "user/status/get", "user/status/login", "user/status/logout", "user/status/update", "user/sugoroku/update", "user/trial/get", "user/trial/update", "user/vip/get", "user/vip/start").map { it.lowercase() }.toSortedSet() all shouldBe defined } - "housing/get #1" { + liveTest("housing/get #1") { post("housing/get", "[]").res exp "[39, 0]" } - "housing/start #1" { + liveTest("housing/start #1") { post("housing/start", """["", "2024/03/24 10:39:36, ApiUserStatusLogout,0\\n2024/03/24 10:51:06, ApiUserStatusLogout,0\\n2024/03/24 10:54:19, ApiUserStatusLogout,0\\n2024/03/24 10:59:33, ApiAdvertiseGetNews,0\\n2024/03/24 11:10:31, ApiAdvertiseGetNews,0\\n2024/03/24 11:11:04, ApiUserStatusLogout,0\\n2024/03/24 11:19:51, ,0\\n2024/03/24 11:20:14, ApiAdvertiseGetNews,0\\n", "", [[1, "SERVER"], [2, "JPN"]]]""").res exp "[1, [1269, 1007, 1270, 1002, 1020, 1003, 1008, 1211, 1018, 1092, 1056, 32, 1260, 1230, 1258, 1251, 2212, 1264, 1125, 1037, 2001, 1272, 1126, 1119, 1104, 1070, 1047, 1044, 1027, 1004, 1001, 24, 2068, 2062, 2021, 1275, 1249, 1207, 1203, 1107, 1021, 1009, 9, 4, 3, 23, 22, 2014, 13, 1276, 1247, 1240, 1237, 1128, 1114, 1110, 1109, 1102, 1045, 1043, 1036, 1035, 1030, 1023, 1015]]" } - "advertise/GetNews #1" { + liveTest("advertise/GetNews #1") { post("advertise/GetNews", "[]").res exp "[[], [], [], [], [], [], [], [], []]" } - "user/status/get #1" { + liveTest("user/status/get #1") { post("user/status/get", """["$uid"]""").res exp """[[0, "", 1, 0, 0, 0, 500, [0, 0, 0], 0, 0, 0, 0, 3376684800, 0, 0], 104001, 102001, 1, [2, "1.0.0"], []]""" } - "user/status/create #1" { + liveTest("user/status/create #1") { post("user/status/create", """["$uid", "AZA"]""").res exp """[[$uid, "AZA", 1, 0, 0, 0, 500, [0, 0, 0], 0, 0, 0, 0, 3376684800, 0, 0]]""" } - "user/status/login Guest" { + liveTest("user/status/login Guest") { post("user/status/login", "[0]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], 0, []]" } - "user/status/login #2" { + liveTest("user/status/login #2") { post("user/status/login", "[$uid]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], null, []]" } - "user/status/getDetail #1" { + liveTest("user/status/getDetail #1") { post("user/status/getDetail", "[$uid]").res expGetDetail """[[$uid, "AZA", 1, 0, 0, 0, 500, [0, 0, 0], 1, 1, 0, 1, 3376684800, 1, 0], [], [[3, 1, 0], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [0, 1, 1]], [[], [[104001, 1, null], [104002, 1, null], [104003, 1, null], [104005, 1, null]], [[102001, 1, 0, null], [102002, 1, 0, null]], [], [], [], [[103001, 1, null], [203001, 1, null]], [[105001, 1, null], [205005, 1, null]], [[210001, 1, null, 0, 0], [210002, 1, null, 0, 0], [210054, 1, null, 0, 0], [210055, 1, null, 0, 0], [210056, 1, null, 0, 0], [210057, 1, null, 0, 0], [210058, 1, null, 0, 0], [210059, 1, null, 0, 0], [210060, 1, null, 0, 0], [210061, 1, null, 0, 0], [310001, 1, null, 0, 0], [310002, 1, null, 0, 0]], [[211001, 1, null]], [[312000, 1, null], [312001, 1, null]]], [], [0, 1], [0, 500, 0, 0, 4, 2, 0, 2, 2, 1, 0], [[0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0]], null, [], [], [], [[1, 1, 1, 0, 0, 0, 0], [2, 1, 1, 0, 0, 0, 0], [3, 1, 1, 0, 0, 0, 0], [4, 1, 1, 0, 0, 0, 0], [5, 1, 1, 0, 0, 0, 0], [6, 1, 1, 0, 0, 0, 0], [7, 1, 1, 0, 0, 0, 0], [8, 1, 1, 0, 0, 0, 0], [9, 1, 1, 0, 0, 0, 0], [10, 1, 1, 0, 0, 0, 0], [11, 1, 1, 0, 0, 0, 0], [12, 1, 1, 0, 0, 0, 0], [13, 1, 1, 0, 0, 0, 0], [14, 1, 1, 0, 0, 0, 0], [15, 1, 1, 0, 0, 0, 0], [16, 1, 1, 0, 0, 0, 0], [17, 1, 1, 0, 0, 0, 0], [18, 1, 1, 0, 0, 0, 0], [19, 1, 1, 0, 0, 0, 0], [20, 1, 1, 0, 0, 0, 0], [21, 1, 1, 0, 0, 0, 0], [22, 1, 1, 0, 0, 0, 0], [23, 1, 1, 0, 0, 0, 0], [24, 1, 1, 0, 0, 0, 0]], [0, 0, 0, 0, 0], [[1, 0], [2, 0], [3, 0], [4, 0], [5, 0]], [], [], [0, []]]""" } - "user/sugoroku/update #1" { + liveTest("user/sugoroku/update #1") { post("user/sugoroku/update", "[$uid, 23, 2, 42, 0, 0, [[6, 302027, 1]], 292, 0]").res exp "[]" } - "user/mission/update #1" { + liveTest("user/mission/update #1") { post("user/mission/update", "[$uid, [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 0], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 812201]]], [], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]]]").res exp "[]" } - "user/music/update #4" { + liveTest("user/music/update #4") { post("user/music/update", "[$uid, 1, [1116, 2, 9.699999809265137, 812201, [252, 105, 28, 33], 56, 5, 1, 0, 0, 0, 0, 0, 11, 120, 1], [[2, 0, 936]]]").res exp "[[1116, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], 812201, 33, 0, 1, 0], [1116, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/sugoroku/update #2" { + liveTest("user/sugoroku/update #2") { post("user/sugoroku/update", "[$uid, 23, 3, 86, 0, 0, [[5, 304129, 1]], 324, 0]").res exp "[]" } - "user/mission/update #2" { + liveTest("user/mission/update #2") { post("user/mission/update", "[$uid, [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 0], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 1715547]]], [], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]]]").res exp "[]" } - "user/music/update #5" { + liveTest("user/music/update #5") { post("user/music/update", "[$uid, 2, [2074, 2, 9.100000381469727, 903346, [212, 40, 6, 11], 77, 7, 1, 0, 0, 0, 0, 0, 1, 45, 1], [[2, 0, 1224], [4, 2074, 3]]]").res exp "[[2074, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 903346, 11, 0, 1, 91], [2074, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/sugoroku/update #3" { + liveTest("user/sugoroku/update #3") { post("user/sugoroku/update", "[$uid, 23, 4, 90, 0, 0, [[6, 302030, 1]], 324, 0]").res exp "[]" } - "user/mission/update #3" { + liveTest("user/mission/update #3") { post("user/mission/update", "[$uid, [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 0], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]], [], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]]]").res exp "[]" } - "user/music/update #6" { + liveTest("user/music/update #6") { post("user/music/update", "[$uid, 3, [1111, 2, 10.899999618530273, 900776, [484, 128, 13, 19], 157, 7, 1, 0, 0, 0, 0, 0, 15, 126, 1], [[2, 0, 1224], [4, 1111, 3]]]").res exp "[[1111, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 900776, 19, 0, 1, 108], [1111, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/status/update #2" { + liveTest("user/status/update #2") { post("user/status/update", "[$uid, 1, [[1, 0, 100], [2, 0, 250], [5, 104078, 1], [5, 104125, 1], [7, 301001, 1000], [7, 301002, 300], [7, 301003, 100]], 0, 0, [], [1111, 2, 1, 5, 2]]").res exp "[]" } - "user/rating/update #1" { + liveTest("user/rating/update #1") { post("user/rating/update", "[$uid, 497, [[1116, 2, 97], [2074, 3, 0], [2074, 2, 182], [1111, 3, 0], [1111, 2, 218]]]").res exp "[]" } - "user/info/getMyroom #1" { + liveTest("user/info/getMyroom #1") { post("user/info/getMyroom", "[$uid]").res exp "[0, 0, 0, 0, 0, [], 0, 0, 0]" } - "user/info/update #1" { + liveTest("user/info/update #1") { post("user/info/update", "[$uid, [[1, 38], [2, 3], [108, 94]], [], [], [], []]").res exp "[]" } - "user/status/logout #2" { + liveTest("user/status/logout #2") { post("user/status/logout", "[$uid]").res exp "[]" } - "user/status/get #2" { + liveTest("user/status/get #2") { post("user/status/get", """["$uid"]""").res exp """[[$uid, "AZA", 1, 100, 0, 0, 4134, [0, 0, 0], 1, 1, 0, 1, 3376684800, 1, 497], 104001, 102001, 0, [0, "3.7.1"], [[1, 38], [2, 3], [108, 94]]]""" } - "user/status/login #3" { + liveTest("user/status/login #3") { post("user/status/login", "[$uid]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], null, []]" } - "user/status/getDetail #2" { + liveTest("user/status/getDetail #2") { post("user/status/getDetail", "[$uid]").res expGetDetail """[[$uid, "AZA", 1, 100, 0, 0, 4134, [0, 0, 0], 2, 1, 1, 1, 3376684800, 2, 497], [[1, 38], [2, 3], [108, 94]], [[3, 1, 1], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [0, 1, 1]], [[[1111, 1, 0, 1711419516], [1111, 2, 0, 1711419516], [1111, 3, 0, 1711419516], [2074, 1, 0, 1711419238], [2074, 2, 0, 1711419238], [2074, 3, 0, 1711419238]], [[104001, 1, 1711418627], [104002, 1, 1711418627], [104003, 1, 1711418627], [104005, 1, 1711418627], [304129, 1, 1711419172], [104078, 1, 1711419526], [104125, 1, 1711419526]], [[102001, 1, 1, 1711418627], [102002, 1, 0, 1711418627], [302027, 1, 0, 1711418927], [302030, 1, 0, 1711419486]], [[301001, 3, 1000, 0], [301002, 3, 300, 0], [301003, 3, 100, 0]], [], [], [[103001, 1, 1711418627], [203001, 1, 1711418627]], [[105001, 1, 1711418627], [205005, 1, 1711418627]], [[210001, 1, 1711418627, 0, 0], [210002, 1, 1711418627, 0, 0], [210054, 1, 1711418627, 0, 0], [210055, 1, 1711418627, 0, 0], [210056, 1, 1711418627, 0, 0], [210057, 1, 1711418627, 0, 0], [210058, 1, 1711418627, 0, 0], [210059, 1, 1711418627, 0, 0], [210060, 1, 1711418627, 0, 0], [210061, 1, 1711418627, 0, 0], [310001, 1, 1711418627, 1, 1], [310002, 1, 1711418627, 0, 0]], [[211001, 1, 1711418627]], [[312000, 1, 1711418627], [312001, 1, 1711418627]]], [[1116, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], 812201, 56, 33, 1, 97], [2074, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 903346, 77, 11, 1, 182], [1111, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 900776, 157, 19, 1, 218]], [1111, 1], [100, 4134, 0, 2616323, 7, 4, 0, 2, 2, 1, 324], [[0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0]], 1711422586, [], [], [], [[1, 1, 1, 0, 0, 0, 0], [2, 1, 1, 0, 0, 0, 0], [3, 1, 1, 0, 0, 0, 0], [4, 1, 1, 0, 0, 0, 0], [5, 1, 1, 0, 0, 0, 0], [6, 1, 1, 0, 0, 0, 0], [7, 1, 1, 0, 0, 0, 0], [8, 1, 1, 0, 0, 0, 0], [9, 1, 1, 0, 0, 0, 0], [10, 1, 1, 0, 0, 0, 0], [11, 1, 1, 0, 0, 0, 0], [12, 1, 1, 0, 0, 0, 0], [13, 1, 1, 0, 0, 0, 0], [14, 1, 1, 0, 0, 0, 0], [15, 1, 1, 0, 0, 0, 0], [16, 1, 1, 0, 0, 0, 0], [17, 1, 1, 0, 0, 0, 0], [18, 1, 1, 0, 0, 0, 0], [19, 1, 1, 0, 0, 0, 0], [20, 1, 1, 0, 0, 0, 0], [21, 1, 1, 0, 0, 0, 0], [22, 1, 1, 0, 0, 0, 0], [23, 1, 4, 90, 0, 1711418927, 0], [24, 1, 1, 0, 0, 0, 0]], [1111, 2, 1, 5, 2], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]], [], [], [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 0], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]]]""" } - "user/status/get #3" { + liveTest("user/status/get #3") { post("user/status/get", """["$uid"]""").res exp """[[$uid, "AZA", 1, 100, 0, 0, 4134, [0, 0, 0], 2, 1, 1, 1, 3376684800, 2, 497], 104001, 102001, 0, [0, "3.7.1"], [[1, 38], [2, 3], [108, 94]]]""" } - "user/status/login #4" { + liveTest("user/status/login #4") { post("user/status/login", "[$uid]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], null, []]" } - "user/status/getDetail #3" { + liveTest("user/status/getDetail #3") { post("user/status/getDetail", "[$uid]").res expGetDetail """[[$uid, "AZA", 1, 100, 0, 0, 4134, [0, 0, 0], 3, 1, 2, 1, 3376684800, 3, 497], [[1, 38], [2, 3], [108, 94]], [[3, 1, 1], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [0, 1, 1]], [[[1111, 1, 0, 1711419516], [1111, 2, 0, 1711419516], [1111, 3, 0, 1711419516], [2074, 1, 0, 1711419238], [2074, 2, 0, 1711419238], [2074, 3, 0, 1711419238]], [[104001, 1, 1711418627], [104002, 1, 1711418627], [104003, 1, 1711418627], [104005, 1, 1711418627], [304129, 1, 1711419172], [104078, 1, 1711419526], [104125, 1, 1711419526]], [[102001, 1, 1, 1711418627], [102002, 1, 0, 1711418627], [302027, 1, 0, 1711418927], [302030, 1, 0, 1711419486]], [[301001, 3, 1000, 0], [301002, 3, 300, 0], [301003, 3, 100, 0]], [], [], [[103001, 1, 1711418627], [203001, 1, 1711418627]], [[105001, 1, 1711418627], [205005, 1, 1711418627]], [[210001, 1, 1711418627, 0, 0], [210002, 1, 1711418627, 0, 0], [210054, 1, 1711418627, 0, 0], [210055, 1, 1711418627, 0, 0], [210056, 1, 1711418627, 0, 0], [210057, 1, 1711418627, 0, 0], [210058, 1, 1711418627, 0, 0], [210059, 1, 1711418627, 0, 0], [210060, 1, 1711418627, 0, 0], [210061, 1, 1711418627, 0, 0], [310001, 1, 1711418627, 1, 1], [310002, 1, 1711418627, 0, 0]], [[211001, 1, 1711418627]], [[312000, 1, 1711418627], [312001, 1, 1711418627]]], [[1116, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], 812201, 56, 33, 1, 97], [2074, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 903346, 77, 11, 1, 182], [1111, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 900776, 157, 19, 1, 218]], [1111, 1], [100, 4134, 0, 2616323, 7, 4, 0, 2, 2, 1, 324], [[0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0]], 1711422954, [], [], [], [[1, 1, 1, 0, 0, 0, 0], [2, 1, 1, 0, 0, 0, 0], [3, 1, 1, 0, 0, 0, 0], [4, 1, 1, 0, 0, 0, 0], [5, 1, 1, 0, 0, 0, 0], [6, 1, 1, 0, 0, 0, 0], [7, 1, 1, 0, 0, 0, 0], [8, 1, 1, 0, 0, 0, 0], [9, 1, 1, 0, 0, 0, 0], [10, 1, 1, 0, 0, 0, 0], [11, 1, 1, 0, 0, 0, 0], [12, 1, 1, 0, 0, 0, 0], [13, 1, 1, 0, 0, 0, 0], [14, 1, 1, 0, 0, 0, 0], [15, 1, 1, 0, 0, 0, 0], [16, 1, 1, 0, 0, 0, 0], [17, 1, 1, 0, 0, 0, 0], [18, 1, 1, 0, 0, 0, 0], [19, 1, 1, 0, 0, 0, 0], [20, 1, 1, 0, 0, 0, 0], [21, 1, 1, 0, 0, 0, 0], [22, 1, 1, 0, 0, 0, 0], [23, 1, 4, 90, 0, 1711418927, 0], [24, 1, 1, 0, 0, 0, 0]], [1111, 2, 1, 5, 2], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]], [], [], [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 0], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]]]""" } - "user/sugoroku/update #4" { + liveTest("user/sugoroku/update #4") { post("user/sugoroku/update", "[$uid, 23, 5, 79, 0, 0, [[5, 304132, 1]], 339, 0]").res exp "[]" } - "user/music/update #7" { + liveTest("user/music/update #7") { post("user/music/update", "[$uid, 1, [1111, 2, 10.899999618530273, 944410, [556, 71, 5, 12], 191, 11, 1, 0, 0, 0, 0, 0, 25, 51, 1], [[2, 0, 1224]]]").res exp "[[1111, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 944410, 12, 0, 1, 299], [1111, 2], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/music/unlock #1" { + liveTest("user/music/unlock #1") { post("user/music/unlock", "[$uid, 1085, 3, [[9, 106002, 1]]]").res exp "[5358, []]" } - "user/status/get #4" { + liveTest("user/status/get #4") { post("user/status/get", """["$uid"]""").res exp """[[$uid, "AZA", 1, 100, 0, 0, 5358, [0, 0, 0], 3, 1, 2, 1, 3376684800, 3, 497], 104001, 102001, 0, [0, "3.7.1"], [[1, 38], [2, 3], [108, 94]]]""" } - "user/status/login #5" { + liveTest("user/status/login #5") { post("user/status/login", "[$uid]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], 1711422954, []]" } - "user/status/getDetail #4" { + liveTest("user/status/getDetail #4") { post("user/status/getDetail", "[$uid]").res expGetDetail """[[$uid, "AZA", 1, 100, 0, 0, 5358, [0, 0, 0], 4, 1, 3, 1, 3376684800, 4, 497], [[1, 38], [2, 3], [108, 94]], [[3, 1, 1], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [0, 1, 1]], [[[2074, 1, 0, 1711419238], [2074, 2, 0, 1711419238], [2074, 3, 0, 1711419238], [1111, 1, 0, 1711419516], [1111, 2, 0, 1711419516], [1111, 3, 0, 1711419516], [1085, 1, 0, 1711423274], [1085, 2, 0, 1711423274], [1085, 3, 0, 1711423274]], [[104001, 1, 1711418627], [104002, 1, 1711418627], [104003, 1, 1711418627], [104005, 1, 1711418627], [304129, 1, 1711419172], [104078, 1, 1711419526], [104125, 1, 1711419526], [304132, 1, 1711423159]], [[102001, 1, 1, 1711418627], [102002, 1, 0, 1711418627], [302027, 1, 0, 1711418927], [302030, 1, 0, 1711419486]], [[301001, 3, 1000, 0], [301002, 3, 300, 0], [301003, 3, 100, 0]], [], [], [[103001, 1, 1711418627], [203001, 1, 1711418627]], [[105001, 1, 1711418627], [205005, 1, 1711418627]], [[210001, 1, 1711418627, 0, 0], [210002, 1, 1711418627, 0, 0], [210054, 1, 1711418627, 0, 0], [210055, 1, 1711418627, 0, 0], [210056, 1, 1711418627, 0, 0], [210057, 1, 1711418627, 0, 0], [210058, 1, 1711418627, 0, 0], [210059, 1, 1711418627, 0, 0], [210060, 1, 1711418627, 0, 0], [210061, 1, 1711418627, 0, 0], [310001, 1, 1711418627, 1, 1], [310002, 1, 1711418627, 0, 0]], [[211001, 1, 1711418627]], [[312000, 1, 1711418627], [312001, 1, 1711418627]]], [[1116, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], 812201, 56, 33, 1, 97], [2074, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 903346, 77, 11, 1, 182], [1111, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 944410, 191, 12, 1, 218]], [1111, 1], [100, 5358, 0, 2659957, 8, 4, 0, 2, 2, 1, 339], [[0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0]], 1711423417, [], [], [], [[1, 1, 1, 0, 0, 0, 0], [2, 1, 1, 0, 0, 0, 0], [3, 1, 1, 0, 0, 0, 0], [4, 1, 1, 0, 0, 0, 0], [5, 1, 1, 0, 0, 0, 0], [6, 1, 1, 0, 0, 0, 0], [7, 1, 1, 0, 0, 0, 0], [8, 1, 1, 0, 0, 0, 0], [9, 1, 1, 0, 0, 0, 0], [10, 1, 1, 0, 0, 0, 0], [11, 1, 1, 0, 0, 0, 0], [12, 1, 1, 0, 0, 0, 0], [13, 1, 1, 0, 0, 0, 0], [14, 1, 1, 0, 0, 0, 0], [15, 1, 1, 0, 0, 0, 0], [16, 1, 1, 0, 0, 0, 0], [17, 1, 1, 0, 0, 0, 0], [18, 1, 1, 0, 0, 0, 0], [19, 1, 1, 0, 0, 0, 0], [20, 1, 1, 0, 0, 0, 0], [21, 1, 1, 0, 0, 0, 0], [22, 1, 1, 0, 0, 0, 0], [23, 1, 5, 79, 0, 1711418927, 0], [24, 1, 1, 0, 0, 0, 0]], [1111, 2, 1, 5, 2], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]], [], [], [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 0], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]]]""" } - "user/music/unlock #2" { + liveTest("user/music/unlock #2") { post("user/music/unlock", "[$uid, 3011, 3, [[9, 106002, 1]]]").res exp "[5358, []]" } - "user/music/unlock #3" { + liveTest("user/music/unlock #3") { post("user/music/unlock", "[$uid, 2009, 3, [[9, 106002, 1]]]").res exp "[5358, []]" } - "user/sugoroku/update #5" { + liveTest("user/sugoroku/update #5") { post("user/sugoroku/update", "[$uid, 23, 5, 313, 0, 0, [], 234, 0]").res exp "[]" } - "user/mission/update #4" { + liveTest("user/mission/update #4") { post("user/mission/update", "[$uid, [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 1], [4, 5, 0], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]], [], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]]]").res exp "[]" } - "user/music/update #8" { + liveTest("user/music/update #8") { post("user/music/update", "[$uid, 1, [2009, 3, 13.699999809265137, 657762, [464, 225, 62, 241], 48, 3, 0, 0, 0, 0, 0, 0, 171, 108, 1], [[2, 0, 252]]]").res exp "[[2009, 3, [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 657762, 241, 0, 1, 0], [2009, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/sugoroku/update #6" { + liveTest("user/sugoroku/update #6") { post("user/sugoroku/update", "[$uid, 23, 6, 279, 0, 0, [[6, 302033, 1]], 346, 0]").res exp "[]" } - "user/mission/update #5" { + liveTest("user/mission/update #5") { post("user/mission/update", "[$uid, [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 1], [4, 5, 1], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]], [[2, 0, 100]], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]]]").res exp "[]" } - "user/music/update #9" { + liveTest("user/music/update #9") { post("user/music/update", "[$uid, 2, [4, 2, 10.899999618530273, 967339, [464, 19, 5, 8], 246, 8, 1, 0, 0, 0, 0, 0, 16, 8, 1], [[2, 0, 1296], [4, 4, 3]]]").res exp "[[4, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], 967339, 8, 0, 1, 354], [4, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/sugoroku/update #7" { + liveTest("user/sugoroku/update #7") { post("user/sugoroku/update", "[$uid, 23, 7, 205, 0, 0, [[5, 304135, 1]], 346, 0]").res exp "[]" } - "user/mission/update #6" { + liveTest("user/mission/update #6") { post("user/mission/update", "[$uid, [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 1], [4, 5, 1], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]], [], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]]]").res exp "[]" } - "user/music/update #10" { + liveTest("user/music/update #10") { post("user/music/update", "[$uid, 3, [4, 2, 10.899999618530273, 962702, [451, 35, 4, 6], 199, 8, 1, 0, 0, 0, 0, 0, 35, 4, 0], [[2, 0, 1296]]]").res exp "[[4, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], 967339, 6, 0, 1, 354], [4, 2], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/status/update #3" { + liveTest("user/status/update #3") { post("user/status/update", "[$uid, 1, [[1, 0, 100], [2, 0, 250], [6, 102023, 1], [7, 301001, 2333], [7, 301002, 700], [7, 301003, 233], [7, 301007, 1000], [7, 301008, 200]], 0, 0, [], [4, 2, 1, 7, 5]]").res exp "[]" } - "user/rating/update #2" { + liveTest("user/rating/update #2") { post("user/rating/update", "[$uid, 1028, [[1111, 2, 299], [3011, 3, 0], [2009, 3, 95], [4, 3, 0], [4, 2, 354]]]").res exp "[]" } - "user/info/update #2" { + liveTest("user/info/update #2") { post("user/info/update", "[$uid, [[1, 40], [108, 100]], [], [], [], []]").res exp "[]" } - "user/status/get #5" { + liveTest("user/status/get #5") { post("user/status/get", """["$uid"]""").res exp """[[$uid, "AZA", 1, 200, 0, 0, 8552, [0, 0, 0], 4, 1, 3, 1, 3376684800, 4, 1028], 104001, 102001, 0, [0, "3.7.1"], [[1, 40], [2, 3], [108, 100]]]""" } - "user/status/login #6" { + liveTest("user/status/login #6") { post("user/status/login", "[$uid]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], 1711423417, []]" } - "user/status/getDetail #5" { + liveTest("user/status/getDetail #5") { post("user/status/getDetail", "[$uid]").res expGetDetail """[[$uid, "AZA", 1, 200, 0, 0, 8552, [0, 0, 0], 5, 1, 4, 1, 3376684800, 5, 1028], [[1, 40], [2, 3], [108, 100]], [[3, 1, 2], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [0, 1, 1]], [[[2074, 1, 0, 1711419238], [2074, 2, 0, 1711419238], [2074, 3, 0, 1711419238], [1111, 1, 0, 1711419516], [1111, 2, 0, 1711419516], [1111, 3, 0, 1711419516], [1085, 1, 0, 1711423274], [1085, 2, 0, 1711423274], [1085, 3, 0, 1711423274], [3011, 1, 0, 1711423444], [3011, 2, 0, 1711423444], [3011, 3, 0, 1711423444], [2009, 1, 0, 1711423495], [2009, 2, 0, 1711423495], [2009, 3, 0, 1711423495], [4, 1, 0, 1711423942], [4, 2, 0, 1711423942], [4, 3, 0, 1711423942]], [[104001, 1, 1711418627], [104002, 1, 1711418627], [104003, 1, 1711418627], [104005, 1, 1711418627], [304129, 1, 1711419172], [104078, 1, 1711419526], [104125, 1, 1711419526], [304132, 1, 1711423159], [304135, 1, 1711424222]], [[102001, 1, 2, 1711418627], [102002, 1, 0, 1711418627], [302027, 1, 0, 1711418927], [302030, 1, 0, 1711419486], [302033, 1, 0, 1711423919], [102023, 1, 0, 1711424255]], [[301001, 3, 2333, 0], [301002, 3, 700, 0], [301003, 3, 233, 0], [301007, 3, 1000, 0], [301008, 3, 200, 0]], [], [], [[103001, 1, 1711418627], [203001, 1, 1711418627]], [[105001, 1, 1711418627], [205005, 1, 1711418627]], [[210001, 1, 1711418627, 0, 0], [210002, 1, 1711418627, 0, 0], [210054, 1, 1711418627, 0, 0], [210055, 1, 1711418627, 0, 0], [210056, 1, 1711418627, 0, 0], [210057, 1, 1711418627, 0, 0], [210058, 1, 1711418627, 0, 0], [210059, 1, 1711418627, 0, 0], [210060, 1, 1711418627, 0, 0], [210061, 1, 1711418627, 0, 0], [310001, 1, 1711418627, 2, 2], [310002, 1, 1711418627, 0, 0]], [[211001, 1, 1711418627]], [[312000, 1, 1711418627], [312001, 1, 1711418627]]], [[1116, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], 812201, 56, 33, 1, 97], [2074, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 903346, 77, 11, 1, 182], [1111, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 944410, 191, 12, 1, 299], [2009, 3, [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 657762, 48, 0, 1, 95], [4, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], 967339, 246, 6, 1, 354]], [4, 1], [200, 8552, 0, 4285058, 9, 6, 0, 2, 2, 1, 346], [[0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0]], 1711425683, [], [], [], [[1, 1, 1, 0, 0, 0, 0], [2, 1, 1, 0, 0, 0, 0], [3, 1, 1, 0, 0, 0, 0], [4, 1, 1, 0, 0, 0, 0], [5, 1, 1, 0, 0, 0, 0], [6, 1, 1, 0, 0, 0, 0], [7, 1, 1, 0, 0, 0, 0], [8, 1, 1, 0, 0, 0, 0], [9, 1, 1, 0, 0, 0, 0], [10, 1, 1, 0, 0, 0, 0], [11, 1, 1, 0, 0, 0, 0], [12, 1, 1, 0, 0, 0, 0], [13, 1, 1, 0, 0, 0, 0], [14, 1, 1, 0, 0, 0, 0], [15, 1, 1, 0, 0, 0, 0], [16, 1, 1, 0, 0, 0, 0], [17, 1, 1, 0, 0, 0, 0], [18, 1, 1, 0, 0, 0, 0], [19, 1, 1, 0, 0, 0, 0], [20, 1, 1, 0, 0, 0, 0], [21, 1, 1, 0, 0, 0, 0], [22, 1, 1, 0, 0, 0, 0], [23, 1, 7, 205, 0, 1711418927, 0], [24, 1, 1, 0, 0, 0, 0]], [4, 2, 1, 7, 5], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]], [], [], [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 1], [4, 5, 1], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]]]""" } - "user/sugoroku/update #8" { + liveTest("user/sugoroku/update #8") { post("user/sugoroku/update", "[$uid, 23, 7, 205, 0, 0, [], 0, 0]").res exp "[]" } - "user/music/update #11" { + liveTest("user/music/update #11") { post("user/music/update", "[$uid, 1, [4, 3, 13.300000190734863, 8353, [5, 1, 3, 853], 1, 2, 0, 0, 0, 0, 0, 0, 0, 2, 1], [[2, 0, 180]]]").res exp "[[4, 3, [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8353, 853, 0, 1, 0], [4, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/status/get #6" { + liveTest("user/status/get #6") { post("user/status/get", """["$uid"]""").res exp """[[$uid, "AZA", 1, 200, 0, 0, 8732, [0, 0, 0], 5, 1, 4, 1, 3376684800, 5, 1028], 104001, 102001, 0, [0, "3.7.1"], [[1, 40], [2, 3], [108, 100]]]""" } - "user/status/login #7" { + liveTest("user/status/login #7") { post("user/status/login", "[$uid]").res exp "[[], [], [], 0, [2077, 1, 1, 1, [], []], 1711425683, []]" } - "user/status/getDetail #6" { + liveTest("user/status/getDetail #6") { post("user/status/getDetail", "[$uid]").res expGetDetail """[[$uid, "AZA", 1, 200, 0, 0, 8732, [0, 0, 0], 6, 1, 5, 1, 3376684800, 6, 1028], [[1, 40], [2, 3], [108, 100]], [[3, 1, 2], [3, 2, 0], [3, 3, 0], [3, 4, 0], [3, 5, 0], [0, 1, 1]], [[[2074, 1, 0, 1711419238], [2074, 2, 0, 1711419238], [2074, 3, 0, 1711419238], [1111, 1, 0, 1711419516], [1111, 2, 0, 1711419516], [1111, 3, 0, 1711419516], [1085, 1, 0, 1711423274], [1085, 2, 0, 1711423274], [1085, 3, 0, 1711423274], [3011, 1, 0, 1711423444], [3011, 2, 0, 1711423444], [3011, 3, 0, 1711423444], [2009, 1, 0, 1711423495], [2009, 2, 0, 1711423495], [2009, 3, 0, 1711423495], [4, 1, 0, 1711423942], [4, 2, 0, 1711423942], [4, 3, 0, 1711423942]], [[104001, 1, 1711418627], [104002, 1, 1711418627], [104003, 1, 1711418627], [104005, 1, 1711418627], [304129, 1, 1711419172], [104078, 1, 1711419526], [104125, 1, 1711419526], [304132, 1, 1711423159], [304135, 1, 1711424222]], [[102001, 1, 2, 1711418627], [102002, 1, 0, 1711418627], [302027, 1, 0, 1711418927], [302030, 1, 0, 1711419486], [302033, 1, 0, 1711423919], [102023, 1, 0, 1711424255]], [[301001, 3, 2333, 0], [301002, 3, 700, 0], [301003, 3, 233, 0], [301007, 3, 1000, 0], [301008, 3, 200, 0]], [], [], [[103001, 1, 1711418627], [203001, 1, 1711418627]], [[105001, 1, 1711418627], [205005, 1, 1711418627]], [[210001, 1, 1711418627, 0, 0], [210002, 1, 1711418627, 0, 0], [210054, 1, 1711418627, 0, 0], [210055, 1, 1711418627, 0, 0], [210056, 1, 1711418627, 0, 0], [210057, 1, 1711418627, 0, 0], [210058, 1, 1711418627, 0, 0], [210059, 1, 1711418627, 0, 0], [210060, 1, 1711418627, 0, 0], [210061, 1, 1711418627, 0, 0], [310001, 1, 1711418627, 2, 2], [310002, 1, 1711418627, 0, 0]], [[211001, 1, 1711418627]], [[312000, 1, 1711418627], [312001, 1, 1711418627]]], [[1116, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], 812201, 56, 33, 1, 97], [2074, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 903346, 77, 11, 1, 182], [1111, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], 944410, 191, 12, 1, 299], [2009, 3, [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 657762, 48, 0, 1, 95], [4, 3, [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8353, 1, 0, 1, 0], [4, 2, [2, 2, 0, 0, 0], [2, 2, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], 967339, 246, 6, 1, 354]], [4, 1], [200, 8232, 0, 4293411, 9, 6, 0, 2, 2, 1, 0], [[0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0]], 1711426091, [], [], [], [[1, 1, 1, 0, 0, 0, 0], [2, 1, 1, 0, 0, 0, 0], [3, 1, 1, 0, 0, 0, 0], [4, 1, 1, 0, 0, 0, 0], [5, 1, 1, 0, 0, 0, 0], [6, 1, 1, 0, 0, 0, 0], [7, 1, 1, 0, 0, 0, 0], [8, 1, 1, 0, 0, 0, 0], [9, 1, 1, 0, 0, 0, 0], [10, 1, 1, 0, 0, 0, 0], [11, 1, 1, 0, 0, 0, 0], [12, 1, 1, 0, 0, 0, 0], [13, 1, 1, 0, 0, 0, 0], [14, 1, 1, 0, 0, 0, 0], [15, 1, 1, 0, 0, 0, 0], [16, 1, 1, 0, 0, 0, 0], [17, 1, 1, 0, 0, 0, 0], [18, 1, 1, 0, 0, 0, 0], [19, 1, 1, 0, 0, 0, 0], [20, 1, 1, 0, 0, 0, 0], [21, 1, 1, 0, 0, 0, 0], [22, 1, 1, 0, 0, 0, 0], [23, 1, 7, 205, 0, 1711418927, 0], [24, 1, 1, 0, 0, 0, 0]], [4, 2, 1, 7, 5], [[1, 1], [2, 1], [3, 1], [4, 1], [5, 0]], [], [], [1, [[0, 1, 1], [1, 2, 0], [2, 3, 0], [3, 4, 1], [4, 5, 1], [5, 6, 0], [6, 7, 0], [7, 8, 800], [8, 9, 2000000]]]]""" } - "user/sugoroku/update #9" { + liveTest("user/sugoroku/update #9") { post("user/sugoroku/update", "[$uid, 23, 8, 87, 0, 0, [[6, 302028, 1]], 332, 0]").res exp "[]" } - "user/music/update #12" { + liveTest("user/music/update #12") { post("user/music/update", "[$uid, 1, [1056, 2, 10.600000381469727, 928060, [455, 49, 7, 20], 133, 7, 1, 0, 0, 0, 0, 0, 15, 41, 1], [[2, 0, 1224], [4, 1056, 3]]]").res exp "[[1056, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], 928060, 20, 0, 1, 238], [1056, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/music/unlock #4" { + liveTest("user/music/unlock #4") { post("user/music/unlock", "[$uid, 2235, 0, [[2, 0, 5000]]]").res exp "[4956, []]" } - "user/sugoroku/update #10" { + liveTest("user/sugoroku/update #10") { post("user/sugoroku/update", "[$uid, 23, 8, 393, 0, 0, [], 306, 0]").res exp "[]" } - "user/music/update #13" { + liveTest("user/music/update #13") { post("user/music/update", "[$uid, 2, [2235, 2, 10.5, 850311, [307, 94, 21, 24], 111, 6, 1, 0, 0, 0, 0, 0, 30, 81, 1], [[2, 0, 1008]]]").res exp "[[2235, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], 850311, 24, 0, 1, 0], [2235, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/sugoroku/update #11" { + liveTest("user/sugoroku/update #11") { post("user/sugoroku/update", "[$uid, 23, 9, 220, 0, 0, [[5, 304130, 1]], 317, 0]").res exp "[]" } - "user/music/update #14" { + liveTest("user/music/update #14") { post("user/music/update", "[$uid, 3, [1068, 2, 11, 884696, [423, 106, 23, 23], 115, 6, 1, 0, 0, 0, 0, 0, 16, 113, 1], [[2, 0, 1008]]]").res exp "[[1068, 2, [1, 1, 0, 0, 0], [1, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], 884696, 23, 0, 1, 0], [1068, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []]" } - "user/status/update #4" { + liveTest("user/status/update #4") { post("user/status/update", "[$uid, 1, [[1, 0, 100], [2, 0, 250], [6, 102054, 1], [11, 105002, 1], [7, 301001, 3666], [7, 301002, 1100], [7, 301003, 366]], 0, 0, [], [1068, 2, 1, 7, 5]]").res exp "[]" } - "user/rating/update #3" { + liveTest("user/rating/update #3") { post("user/rating/update", "[$uid, 1629, [[4, 3, 13], [1056, 3, 0], [1056, 2, 265], [2235, 2, 157], [1068, 2, 165]]]").res exp "[]" }