[F] Fix import/export disparity, fix jackson3 export isSomething serialization bug, add tests (#237)

This commit is contained in:
Azalea
2026-08-06 03:21:20 +08:00
committed by GitHub
parent 30139cae8d
commit 3be81520ce
22 changed files with 419 additions and 241 deletions
+2 -5
View File
@@ -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")
+88 -30
View File
@@ -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<Boolean>() {
val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, object : ValueDeserializer<Boolean>() {
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<LocalDateTime>() {
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<LocalDateTime>() {
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<Int>()
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 <reified T> ObjectMapper.parse(str: Str) = readValue(str, T::class.java)
inline fun <reified T> 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
}
@@ -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<ExportModel: IExportClass<UserModel>, UserModel:
json.parseJackson(exportClass.java)
} catch (e: Exception) {
val jsonError = generateSequence<Throwable>(e) { it.cause }
.filterIsInstance<JsonProcessingException>()
.filterIsInstance<JacksonException>()
.firstOrNull()
?: throw e
@@ -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<List<Integer>>() {
val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : ValueDeserializer<List<Integer>>() {
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)
}
val JACKSON_ARTEMIS = JACKSON.rebuild().addModule(JSON_INT_LIST_STR).build()
@@ -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
@@ -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<LocalDateTime?>() {
class FlexibleDateTimeDeserializer : ValueDeserializer<LocalDateTime?>() {
@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>(
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,
)
}
@@ -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
@@ -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 <T> convert(map: JDict, toClass: Class<T>) = mapper.convertValue<T>(map, toClass)
@@ -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<LocalDateTime>? = null) : StdSerializer<LocalDateTime>(t) {
override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializerProvider) {
override fun serialize(value: LocalDateTime, gen: JsonGenerator, provider: SerializationContext) {
gen.writeString(DivaTime.getString(value))
}
}
@@ -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<String, Any>): Any?
}
@@ -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<String, Any>): Any? {
val upsertUserAll = mapper.convert(request, Mai2UpsertUserAll::class.java)
val userId = upsertUserAll.userId
@@ -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
@@ -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<LocalDateTime>() {
override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializerProvider) {
class MaimaiDateSerializer : ValueSerializer<LocalDateTime>() {
override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializationContext) {
j.writeString(v.format(MAIMAI_DATETIME))
}
}
@@ -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
@@ -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 <reified T> read(json: String): T = read(json, object : TypeReference<T>() {})
}
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>(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)
@@ -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
@@ -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<Card>? = null) : StdSerializer<Card>(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<Int>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = when (p.currentToken) {
class BooleanToIntegerDeserializer : ValueDeserializer<Int>() {
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<Int>() {
}
class BooleanNumberSerializer @JvmOverloads constructor(t: Class<Boolean>? = null) : StdSerializer<Boolean>(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<Boolean>() {
class BooleanNumberDeserializer : ValueDeserializer<Boolean>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = "0" != p.text
}
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime>() {
class ZonedDateTimeDeserializer : ValueDeserializer<ZonedDateTime>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext) = ZonedDateTime.parse(p.text)
}
+25 -1
View File
@@ -22,4 +22,28 @@ suspend fun registerUser(): Long {
println("User ID: $userId")
return userId
}
}
// ---------------------------------------------------------------------------
// 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)
+24 -3
View File
@@ -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<JsonMappingException> {
val error = shouldThrow<DatabindException> {
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"
}
})
@@ -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 <reified T : Any> assertJsonRoundTrip(value: T) {
val exportedJson = JACKSON.writeValueAsString(value)
val imported = exportedJson.parseJackson(T::class.java)
JACKSON.readTree(JACKSON.writeValueAsString(imported)) shouldBe JACKSON.readTree(exportedJson)
}
File diff suppressed because one or more lines are too long
+66 -66
View File
@@ -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
"[]"
}