From 1ea68d82a7b937d11e339f98b5106e179deda645 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 18 Sep 2022 17:55:28 +0800 Subject: [PATCH] [mock] Fix serialization --- mirai-core-mock/src/internal/MockMiraiImpl.kt | 7 + mirai-core-mock/src/internal/contact/util.kt | 5 + .../src/internal/msgsrc/OnlineMsgSrc.kt | 134 ++++++++++++++++-- .../test/mock/MessageSerializationTest.kt | 123 ++++++++++++++++ 4 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 mirai-core-mock/test/mock/MessageSerializationTest.kt diff --git a/mirai-core-mock/src/internal/MockMiraiImpl.kt b/mirai-core-mock/src/internal/MockMiraiImpl.kt index 4dbc71269..1a6c29550 100644 --- a/mirai-core-mock/src/internal/MockMiraiImpl.kt +++ b/mirai-core-mock/src/internal/MockMiraiImpl.kt @@ -31,11 +31,18 @@ import net.mamoe.mirai.mock.internal.contact.AQQ_RECALL_FAILED_MESSAGE import net.mamoe.mirai.mock.internal.contact.MockFriendImpl import net.mamoe.mirai.mock.internal.contact.MockImage import net.mamoe.mirai.mock.internal.contact.MockStrangerImpl +import net.mamoe.mirai.mock.internal.msgsrc.registerMockMsgSerializers import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.mock.utils.simpleMemberInfo import net.mamoe.mirai.utils.currentTimeSeconds internal class MockMiraiImpl : MiraiImpl() { + companion object { + init { + registerMockMsgSerializers() + } + } + override suspend fun solveBotInvitedJoinGroupRequestEvent( bot: Bot, eventId: Long, diff --git a/mirai-core-mock/src/internal/contact/util.kt b/mirai-core-mock/src/internal/contact/util.kt index a7fc55be2..2821c8de8 100644 --- a/mirai-core-mock/src/internal/contact/util.kt +++ b/mirai-core-mock/src/internal/contact/util.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.mock.internal.contact +import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member @@ -95,6 +96,8 @@ internal suspend fun ExternalResource.mockImplUploadAudioAsOnline(bot: MockBot): ) } +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(MockImage.Serializer::class) internal class MockImage( override val imageId: String, private val urlPath: String, @@ -112,6 +115,8 @@ internal class MockImage( } } + object Serializer : Image.FallbackSerializer("MockImage") + private val _stringValue: String? by lazy(LazyThreadSafetyMode.NONE) { "[mirai:image:$imageId]" } override fun getUrl(bot: Bot): String { diff --git a/mirai-core-mock/src/internal/msgsrc/OnlineMsgSrc.kt b/mirai-core-mock/src/internal/msgsrc/OnlineMsgSrc.kt index 093755c2b..ec39c7733 100644 --- a/mirai-core-mock/src/internal/msgsrc/OnlineMsgSrc.kt +++ b/mirai-core-mock/src/internal/msgsrc/OnlineMsgSrc.kt @@ -7,16 +7,98 @@ * https://github.com/mamoe/mirai/blob/dev/LICENSE */ +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + package net.mamoe.mirai.mock.internal.msgsrc +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.MessageSourceKind -import net.mamoe.mirai.message.data.OnlineMessageSource +import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl +import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacadeImpl +import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer +import net.mamoe.mirai.message.MessageSerializers +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.internal.contact.AbstractMockContact +import net.mamoe.mirai.mock.internal.contact.MockImage import net.mamoe.mirai.utils.currentTimeSeconds +internal fun registerMockMsgSerializers() { + val serializers = mutableListOf>() + + MessageSerializer.superclassesScope(Image::class, MessageContent::class, SingleMessage::class) { + serializers.add( + MessageSerializer( + MockImage::class, + MockImage.serializer() + ) + ) + } + + MessageSerializer.superclassesScope(MessageSource::class, MessageMetadata::class, SingleMessage::class) { + + serializers.add( + MessageSerializer( + OnlineMsgSrcToGroup::class, + OnlineMsgSrcToGroup.serializer() + ) + ) + serializers.add( + MessageSerializer( + OnlineMsgSrcToFriend::class, + OnlineMsgSrcToFriend.serializer() + ) + ) + serializers.add( + MessageSerializer( + OnlineMsgSrcToStranger::class, + OnlineMsgSrcToStranger.serializer() + ) + ) + serializers.add( + MessageSerializer( + OnlineMsgSrcToTemp::class, + OnlineMsgSrcToTemp.serializer() + ) + ) + + + serializers.add( + MessageSerializer( + OnlineMsgSrcFromGroup::class, + OnlineMsgSrcFromGroup.serializer() + ) + ) + serializers.add( + MessageSerializer( + OnlineMsgSrcFromFriend::class, + OnlineMsgSrcFromFriend.serializer() + ) + ) + serializers.add( + MessageSerializer( + OnlineMsgSrcFromStranger::class, + OnlineMsgSrcFromStranger.serializer() + ) + ) + serializers.add( + MessageSerializer( + OnlineMsgSrcFromTemp::class, + OnlineMsgSrcFromTemp.serializer() + ) + ) + } + + val module = MessageProtocolFacadeImpl(listOf(), "").also { + it.serializers.addAll(serializers) + }.createSerializersModule() + + MessageSerializers.registerSerializers(module) +} + +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcToGroup.Serializer::class) internal class OnlineMsgSrcToGroup( override val ids: IntArray, override val internalIds: IntArray, @@ -27,8 +109,13 @@ internal class OnlineMsgSrcToGroup( override val target: Group ) : OnlineMessageSource.Outgoing.ToGroup() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceToGroup") + } +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcToFriend.Serializer::class) internal class OnlineMsgSrcToFriend( override val ids: IntArray, override val internalIds: IntArray, @@ -39,8 +126,14 @@ internal class OnlineMsgSrcToFriend( override val target: Friend ) : OnlineMessageSource.Outgoing.ToFriend() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceToFriend") + } + +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcToStranger.Serializer::class) internal class OnlineMsgSrcToStranger( override val ids: IntArray, override val internalIds: IntArray, @@ -51,8 +144,12 @@ internal class OnlineMsgSrcToStranger( override val target: Stranger ) : OnlineMessageSource.Outgoing.ToStranger() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceToStranger") } +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcToTemp.Serializer::class) internal class OnlineMsgSrcToTemp( override val ids: IntArray, override val internalIds: IntArray, @@ -63,19 +160,13 @@ internal class OnlineMsgSrcToTemp( override val target: Member ) : OnlineMessageSource.Outgoing.ToTemp() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceToTemp") } -internal class OnlineMsgFromGroup( - override val ids: IntArray, - override val internalIds: IntArray, - override val time: Int, - override val originalMessage: MessageChain, - override val bot: Bot, - override val sender: Member -) : OnlineMessageSource.Incoming.FromGroup() { - override val isOriginalMessageInitialized: Boolean get() = true -} +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcFromFriend.Serializer::class) internal class OnlineMsgSrcFromFriend( override val ids: IntArray, override val internalIds: IntArray, @@ -85,8 +176,12 @@ internal class OnlineMsgSrcFromFriend( override val sender: Friend ) : OnlineMessageSource.Incoming.FromFriend() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceFromFriend") } +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcFromStranger.Serializer::class) internal class OnlineMsgSrcFromStranger( override val ids: IntArray, override val internalIds: IntArray, @@ -96,8 +191,12 @@ internal class OnlineMsgSrcFromStranger( override val sender: Stranger ) : OnlineMessageSource.Incoming.FromStranger() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceFromStranger") } +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcFromTemp.Serializer::class) internal class OnlineMsgSrcFromTemp( override val ids: IntArray, override val internalIds: IntArray, @@ -107,8 +206,13 @@ internal class OnlineMsgSrcFromTemp( override val sender: Member ) : OnlineMessageSource.Incoming.FromTemp() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceFromTemp") + } +@Suppress("SERIALIZER_TYPE_INCOMPATIBLE") +@Serializable(OnlineMsgSrcFromGroup.Serializer::class) internal class OnlineMsgSrcFromGroup( override val ids: IntArray, override val internalIds: IntArray, @@ -118,6 +222,9 @@ internal class OnlineMsgSrcFromGroup( override val sender: Member ) : OnlineMessageSource.Incoming.FromGroup() { override val isOriginalMessageInitialized: Boolean get() = true + + object Serializer : KSerializer by MessageSourceSerializerImpl("OnlineMessageSourceFromGroup") + } internal typealias MsgSrcConstructor = ( @@ -141,6 +248,7 @@ internal inline fun AbstractMockContact.newMsgSrc( is Stranger, is Friend, -> this.id + else -> error("Invalid contact: $this") }, kind = when (this) { diff --git a/mirai-core-mock/test/mock/MessageSerializationTest.kt b/mirai-core-mock/test/mock/MessageSerializationTest.kt new file mode 100644 index 000000000..479d445b7 --- /dev/null +++ b/mirai-core-mock/test/mock/MessageSerializationTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2019-2022 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/dev/LICENSE + */ + +package net.mamoe.mirai.mock.test.mock + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializer +import net.mamoe.mirai.message.MessageSerializers +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.mock.test.MockBotTestBase +import net.mamoe.mirai.mock.utils.randomImageContent +import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse + +internal class MessageSerializationTest : MockBotTestBase() { + @Suppress("DEPRECATION_ERROR") + private val module + get() = MessageSerializers.serializersModule + private val format + get() = Json { + serializersModule = module + useArrayPolymorphism = false + ignoreUnknownKeys = true + } + + private inline fun T.serialize(serializer: KSerializer = module.serializer()): String { + return format.encodeToString(serializer, this) + } + + private inline fun String.deserialize(serializer: KSerializer = module.serializer()): T { + return format.decodeFromString(serializer, this) + } + + private inline fun testSerialization(t: T, serializer: KSerializer = module.serializer()) { + val deserialized = kotlin.runCatching { + println("Testing ${t::class.simpleName} with serializer $serializer") + val serialized = t.serialize(serializer) + println("Result: ${serializer.descriptor.serialName} $serialized") + serialized.deserialize(serializer) + }.getOrElse { + throw IllegalStateException("Failed to serialize $t", it) + } + + val msg = "serialized string: ${t.serialize(serializer)}\ndeserialized string: ${ + deserialized.serialize( + serializer + ) + }\n" + + + + assert1( + t, + deserialized, + msg + ) + } + + private fun assert1(t: Any, deserialized: Any, msg: String) { + if (deserialized is MessageSource && t is MessageSource) { + assertSource(t, deserialized, msg) + return + } + + if (t is MessageChain && deserialized is MessageChain) { + assertEquals(t.size, deserialized.size) + val iter1 = t.iterator() + val iter2 = deserialized.iterator() + + repeat(t.size) { + assert1(iter1.next(), iter2.next(), msg) + } + assertFalse(iter1.hasNext(), msg) + assertFalse(iter2.hasNext(), msg) + return + } + + assertEquals(t, deserialized, msg) + } + + private fun assertSource(t: MessageSource, deserialized: MessageSource, msg: String) { + assertEquals(t.kind, deserialized.kind, msg) + assertEquals(t.botId, deserialized.botId, msg) + assertEquals(t.fromId, deserialized.fromId, msg) + assertEquals(t.targetId, deserialized.targetId, msg) + assertEquals(t.time, deserialized.time, msg) + Assertions.assertArrayEquals(t.ids, deserialized.ids, msg) + Assertions.assertArrayEquals(t.internalIds, deserialized.internalIds, msg) + assertEquals(t.originalMessage, deserialized.originalMessage, msg) + } + + + @Test + fun testMockMessageSources() = runTest { + testSerialization(bot.addFriend(1, "").says("")) + testSerialization(bot.addStranger(2, "").says("")) + bot.addGroup(3, "").let { group -> + group.sendMessage("AWA").source.let { testSerialization(messageChainOf(it)) } + group.addMember(6, "").says("A").let { testSerialization(it) } + } + } + + @Test + fun testMockResources() = runTest { + testSerialization(bot.uploadMockImage(Image.randomImageContent().toExternalResource().toAutoCloseable())) + + "1".toByteArray().toExternalResource().use { data0 -> + testSerialization(bot.uploadOnlineAudio(data0)) + testSerialization(bot.asFriend.uploadAudio(data0)) + } + } + +}