MessageRefineTest.kt 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. * Copyright 2019-2022 Mamoe Technologies and contributors.
  3. *
  4. * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  5. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  6. *
  7. * https://github.com/mamoe/mirai/blob/dev/LICENSE
  8. */
  9. package net.mamoe.mirai.internal.message.data
  10. import kotlinx.serialization.decodeFromHexString
  11. import kotlinx.serialization.protobuf.ProtoBuf
  12. import net.mamoe.mirai.Bot
  13. import net.mamoe.mirai.internal.AbstractTestWithMiraiImpl
  14. import net.mamoe.mirai.internal.MockBot
  15. import net.mamoe.mirai.internal.getMiraiImpl
  16. import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
  17. import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
  18. import net.mamoe.mirai.internal.message.RefinableMessage
  19. import net.mamoe.mirai.internal.message.RefineContext
  20. import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade
  21. import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData
  22. import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
  23. import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
  24. import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit
  25. import net.mamoe.mirai.internal.test.runBlockingUnit
  26. import net.mamoe.mirai.internal.utils.io.serialization.loadAs
  27. import net.mamoe.mirai.message.data.*
  28. import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
  29. import net.mamoe.mirai.utils.hexToBytes
  30. import net.mamoe.mirai.utils.isSameType
  31. import kotlin.random.Random
  32. import kotlin.test.Test
  33. import kotlin.test.assertEquals
  34. open class TM(private val name: String = Random.nextInt().toString()) : SingleMessage {
  35. override fun toString(): String = name
  36. override fun contentToString(): String = name
  37. override fun equals(other: Any?): Boolean {
  38. if (this === other) return true
  39. if (!isSameType(this, other)) return false
  40. if (name != other.name) return false
  41. return true
  42. }
  43. override fun hashCode(): Int = name.hashCode()
  44. }
  45. private val bot = MockBot()
  46. private suspend fun testRefineAll(
  47. before: Message,
  48. after: MessageChain,
  49. ) {
  50. testRefineLight(before, after)
  51. testRefineDeep(before, after)
  52. }
  53. private suspend fun testRefineDeep(
  54. before: Message,
  55. after: MessageChain
  56. ) = assertEquals(after.toMessageChain(), before.toMessageChain().refineDeep(bot))
  57. private fun testRefineLight(
  58. before: Message,
  59. after: MessageChain
  60. ) = assertEquals(after.toMessageChain(), before.toMessageChain().refineLight(bot))
  61. @Suppress("TestFunctionName")
  62. private fun RefinableMessage0(
  63. refine: () -> Message?
  64. ): RefinableMessage {
  65. return object : RefinableMessage, TM() {
  66. override fun tryRefine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? {
  67. return refine()
  68. }
  69. }
  70. }
  71. internal class MessageRefineTest : AbstractTestWithMiraiImpl() {
  72. @Test
  73. fun `can remove self`() = runBlockingUnit {
  74. testRefineAll(
  75. RefinableMessage0 { null },
  76. messageChainOf()
  77. )
  78. }
  79. @Test
  80. fun `can replace`() = runBlockingUnit {
  81. testRefineAll(
  82. RefinableMessage0 { TM("1") },
  83. messageChainOf(TM("1"))
  84. )
  85. }
  86. @Test
  87. fun `ignore non-refinable`() = runBlockingUnit {
  88. testRefineAll(
  89. TM("1"),
  90. messageChainOf(TM("1"))
  91. )
  92. }
  93. @Test
  94. fun `can replace flatten`() = runBlockingUnit {
  95. testRefineAll(
  96. buildMessageChain {
  97. +RefinableMessage0 { TM("1") + TM("2") }
  98. +TM("3")
  99. +RefinableMessage0 { TM("4") + TM("5") }
  100. },
  101. messageChainOf(TM("1"), TM("2"), TM("3"), TM("4"), TM("5"))
  102. )
  103. }
  104. private val testCases = object {
  105. /**
  106. * 单个 quote 包含 at 和 plain
  107. */
  108. val simpleQuote =
  109. decodeProto("087aea027708a2fc1010d285d8cc0418f9e7b4830620012a0d0a0b0a09e999a4e99d9e363438420a18aedd90f380808080014a480a2d08d285d8cc0410d285d8cc04185228a2fc1030f9e7b4830638aedd90f380808080014a0608d285d8cc04e001011a170a15120d0a0b0a09e999a4e99d9e36343812044a0208591a0a180a0740e9bb84e889b21a0d00010000000300499602d20000050a030a01201a0a180a0740e9bb84e889b21a0d00010000000300499602d20000070a050a032073624baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021002900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803")
  110. /**
  111. * 一个引用另一个 quote
  112. */
  113. val nestedQuote2 =
  114. decodeProto("0631ea022e08a4fc1010d285d8cc041885e8b4830620012a0e0a0c0a0a40e9bb84e889b2207362420a1896fee2d386808080011b0a190a0840616161746573741a0d00010000000800499602d20000080a060a04207878784baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803")
  115. /**
  116. * quote -> quote -> quote[at + plain]
  117. */
  118. val nestedQuote3 =
  119. decodeProto("062aea022708a6fc1010d285d8cc0418b0e8b4830620012a070a050a03787878420a18b584a7ca80808080011b0a190a0840616161746573741a0d00010000000800499602d200000a0a080a062061616161614baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803")
  120. /**
  121. * [Dice.value] 4
  122. */
  123. val dice4 =
  124. decodeProto("056432620a0e5be99a8fe69cbae9aab0e5ad905d1006180122104823d3adb15df08014ce5d6796b76ee128c85930033a103430396532613639623136393138663950c80158c8016211727363547970653f313b76616c75653d336a0a0a0608c80110c8014001120a100a0e5be99a8fe69cbae9aab0e5ad905d4baa02489a014508017800900101c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803")
  125. /**
  126. * quote -> dice4
  127. */
  128. val quoteDice4 =
  129. decodeProto("06e601ea02e20108adfc1010d285d8cc04188feab4830620012a120a100a0e5be99a8fe69cbae9aab0e5ad905d420a1894bbc6f481808080014aad010a2d08d285d8cc0410d285d8cc04185228adfc10308feab483063894bbc6f481808080014a0608d285d8cc04e001011a7c0a7a125e325c0a0e5be99a8fe69cbae9aab0e5ad905d1006180122104823d3adb15df08014ce5d6796b76ee128c85930033a1034303965326136396231363931386639480050c80158c8016211727363547970653f313b76616c75653d336a02400112120a100a0e5be99a8fe69cbae9aab0e5ad905d12044a0208001b0a190a0840616161746573741a0d00010000000800499602d20000080a060a04206162634baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803")
  130. /**
  131. * forward[quote + dice + forward]
  132. */
  133. val complexForward =
  134. "044b0a3a08d285d8cc041852288f831130dfffb6830638a7c4dfde87808080014a0e08d285d8cc042206e7b289e889b2a2010b10b0f083f7fbffffffff011a0d0a0b12050a030a016112024a00dc010a3a08d285d8cc0418522891831130e4ffb68306388fc594dd85808080014a0e08d285d8cc042206e7b289e889b2a2010b10b1f083f7fbffffffff011a9d010a9a011270ea026d088f831110d285d8cc0418dfffb6830620012a050a030a0161420a18a7c4dfde87808080014a400a2d08d285d8cc0410d285d8cc041852288f831130dfffb6830638a7c4dfde87808080014a0608d285d8cc04e001011a0f0a0d12050a030a016112044a02080050d285d8cc04121a0a180a0740e7b289e889b21a0d00010000000300499602d2000012060a040a02207212024a00520a3a08d285d8cc0418522892831130f2ffb6830638a8e4d1eb80808080014a0e08d285d8cc042206e7b289e889b2a2010b10b2f083f7fbffffffff011a140a12120c0a0a0a085be9aab0e5ad905d12024a00700a3a08d285d8cc04185228978311308d80b7830638a0e8bfa582808080014a0e08d285d8cc042206e7b289e889b2a2010b10b3f083f7fbffffffff011a320a30122a0a280a265be59088e5b9b6e8bdace58f915de8afb7e58d87e7baa7e696b0e78988e69cace69fa5e79c8b12024a00"
  135. .let { s ->
  136. ProtoBuf.decodeFromHexString<List<MsgComm.Msg>>(s).flatMap { it.msgBody.richText.elems }
  137. }
  138. val nestedForward =
  139. "0a790a3008d285d8cc041852200028f9263084f99c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a450a430a0618d5f0fbdf0412390a370a315be59088e5b9b6e8bdace58f915de8afb7e4bdbfe794a8e6898be69cba5151e69c80e696b0e78988e69cace69fa5e79c8b1a005a000a620a3f08d285d8cc041852200028fb2630f5f99c83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a1f0a1d0a0618df98a9a10d12090a070a01351a005a0012084a060890e60260000a97010a3f08d285d8cc0418522000288a2730ef999d83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a540a520a0618a19bc4fe09122fea022c08fb2610d285d8cc0418f5f99c830620012a050a030a01353001420a188fefa5ae8a8080800150d285d8cc04120d0a0b0a0561736566661a005a0012084a060890e602600012fe060a084d756c74694d736712f1060af0040a3008d285d8cc041852200028f9263084f99c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011abb040ab8040a0618d5f0fbdf0412ad0462aa040aa30401789cbd535d6f1241147df7574cc6676497a5c036bb3408bbb608550a02d234cdb2ccc2da1db6ccccb2edbe359af8fdf5604c6cad8969134d8d9a98544d2c0ffe1617f0c9bfe080561b5f7c30f14e723373ef9c73ef99dc51e636b003fa8850dbedaa503c234080baa6dbb2bb6d157acc8aa4e05cfa14e0a660da064d62234b85cbe3ad5be1decbf1ebb7e1d1a31508f0aa653b68d1c0488545cf617691b6571389999ca8c96224a38952249ee43b5917e488248852327936abcb991c0486c9a6a5fb36f28fa110b0b287551883c0720cde88040145a46f9b6821c74f33c715cb76c02b8a02ef1aa48162338481636cba1ee351c823cc660e02a6ebb84485a785a971ae294c8a430e1a0df6b898d1936b272501253a45fe85219698300c770f460fde7d7eff75fbc5f8dec75950a15cc23f3164caf99a96af674dcbcad44c400d622d87db83d1cdeb2bffb933fe3ae1eed57067f0edd39de1e3c32f1fee723f7cba3f7e7ed0366741f55c3568cc2fe1a5b546bd12d3a592b0982b89eb976b58971af54ea715bbe417af14e58b55d6aaf17ca3baae558235bfd835850b755d0a7a3d9f90423320fd3c08923579a3d9d419958c8542b613b082ee5332df4f56b4780a8bbee9457b74be10b43cdfa71ea3e7b576aee4aabf0501a543d24a943ba0500f63836cfe52971226eb8ff779b63fdab93d3e7a15de7f383cbc31dc7ac329a23f919c233a19a8b4425d8f980874a7e37d724c202ff62339b9ccff47fa3b18253b9f10231a000a620a3f08d285d8cc041852200028fb2630f5f99c83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a1f0a1d0a0618df98a9a10d12090a070a01351a005a0012084a060890e60260000a97010a3f08d285d8cc0418522000288a2730ef999d83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a540a520a0618a19bc4fe09122fea022c08fb2610d285d8cc0418f5f99c830620012a050a030a01353001420a188fefa5ae8a8080800150d285d8cc04120d0a0b0a0561736566661a005a0012084a060890e602600012f3040a2d4d756c74694d73675f36363544314539312d414531332d343739312d394630392d33303133373742434639414412c1040a4e0a3108d285d8cc041852200028ffb20130fef89c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a190a170a061881f9d1ae02120d0a0b0a0554734d73671a005a000ab9010a3108d285d8cc041852200028ffb20130fff89c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a83010a80010a0618e3fde8a20b121b0a190a1341534a57454a58436366664157630a736172661a005a00125942571220463042383130354243463033374133323733413644343133453941333043393338b0bd858e0940b787ace70b485050005a0060006a10f0b8105bcf037a3273a6d413e9a30c93a00100b001ac02b8018302c801b85c0a4e0a3108d285d8cc041852200028ffb2013080f99c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a190a170a0618d1afffda02120d0a0b0a0554734d73671a005a000ae2010a4008d285d8cc041852200028ffb2013081f99c83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a9d010a9a010a0618bfb1ebfb0f128f010a8c010a85015647567a5a48526d526b5a585432463351304e4451317059576d46335a586868643255774d6a4d3950567464573246335a567045547a6b774d6e63304f5846337a71717772724c627a72764a0a7a3757397862624674733361494c43687a744c46777372487637544534386d317763752f7173484c7a64757777737574734b456744516f3d1a005a00"
  140. .hexToBytes().loadAs(MsgTransmit.PbMultiMsgTransmit.serializer())
  141. private fun decodeProto(p: String) = ProtoBuf.decodeFromHexString<List<ImMsgBody.Elem>>(p)
  142. }
  143. // override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> {
  144. // return super.downloadForwardMessage(bot, resourceId)
  145. // }
  146. /**
  147. * We cannot test LongMessage and MusicShare in unit tests (for now), but these tests will be sufficient
  148. */
  149. @Test
  150. fun `recursive refinement`() = runBlockingUnit {
  151. val map = listOf(
  152. RefineTest(testCases.simpleQuote) {
  153. expected {
  154. +QuoteReply(sourceStub(buildMessageChain {
  155. +"除非648"
  156. }))
  157. +At(1234567890) // sent by official client, redundant At?
  158. +" "
  159. +At(1234567890)
  160. +" sb"
  161. }
  162. light()
  163. deep()
  164. },
  165. RefineTest(testCases.nestedQuote2) {
  166. expected {
  167. +QuoteReply(sourceStub(buildMessageChain {
  168. +"@黄色 sb" // this is sent by official client.
  169. }))
  170. +At(1234567890) // mentions self
  171. +" xxx"
  172. }
  173. light()
  174. deep()
  175. },
  176. RefineTest(testCases.nestedQuote3) {
  177. expected {
  178. +QuoteReply(sourceStub(buildMessageChain {
  179. +"xxx" // official client does not handle nested quotes.
  180. }))
  181. +At(1234567890) // mentions self
  182. +" aaaaa"
  183. }
  184. light()
  185. deep()
  186. },
  187. RefineTest(testCases.dice4) {
  188. expected {
  189. +Dice(4)
  190. }
  191. light()
  192. deep()
  193. },
  194. RefineTest(testCases.quoteDice4) {
  195. expected {
  196. +QuoteReply(sourceStub(PlainText("[随机骰子]")))
  197. +At(1234567890)
  198. +" abc"
  199. }
  200. light()
  201. deep()
  202. },
  203. RefineTest(testCases.complexForward) {
  204. expected {
  205. +"a"
  206. +QuoteReply(sourceStub(PlainText("a")))
  207. +At(1234567890)
  208. +PlainText(" r")
  209. +PlainText("[骰子]") // client does not support
  210. +PlainText("[合并转发]请升级新版本查看") // client support but mirai does not.
  211. }
  212. deep() // deep only
  213. }
  214. )
  215. for (test in map) {
  216. if (test.testLight) {
  217. testRecursiveRefine(test.list, test.expected, true)
  218. }
  219. if (test.testDeep) {
  220. testRecursiveRefine(test.list, test.expected, false)
  221. }
  222. }
  223. }
  224. @Test
  225. fun `test nested forward refinement`() = runBlockingUnit {
  226. val redefined = getMiraiImpl().run { testCases.nestedForward.toForwardMessageNodes(bot) }
  227. assertNodesEquals(
  228. listOf(
  229. ForwardMessage.Node(
  230. senderId = 1234567890,
  231. time = 1617378436,
  232. senderName = "枫琳·雨莹",
  233. messageChain = buildMessageChain {
  234. +redefined[0].messageChain[MessageOrigin]!!
  235. val rf = redefined[0].messageChain[ForwardMessage]!!
  236. +ForwardMessage(
  237. preview = rf.preview,
  238. title = rf.title,
  239. brief = rf.brief,
  240. source = rf.source,
  241. summary = rf.summary,
  242. nodeList = listOf(
  243. ForwardMessage.Node(1234567890, 1617378430, "枫琳·雨莹", PlainText("TsMsg")),
  244. ForwardMessage.Node(
  245. 1234567890,
  246. 1617378431,
  247. "枫琳·雨莹",
  248. PlainText("ASJWEJXCcffAWc\nsarf") + Image("{F0B8105B-CF03-7A32-73A6-D413E9A30C93}.mirai")
  249. ),
  250. ForwardMessage.Node(1234567890, 1617378432, "枫琳·雨莹", PlainText("TsMsg")),
  251. ForwardMessage.Node(
  252. 1234567890,
  253. 1617378433,
  254. "群垃圾,时不时来被gc",
  255. PlainText("VGVzZHRmRkZXT2F3Q0NDQ1pYWmF3ZXhhd2UwMjM9PVtdW2F3ZVpETzkwMnc0OXF3zqqwrrLbzrvJ\nz7W9xbbFts3aILChztLFwsrHv7TE48m1wcu/qsHLzduwwsutsKEgDQo=")
  256. ),
  257. )
  258. )
  259. }
  260. ),
  261. ForwardMessage.Node(
  262. 1234567890, 1617378549, "群垃圾,时不时来被gc", PlainText("5")
  263. ),
  264. ForwardMessage.Node(
  265. 1234567890,
  266. 1617382639,
  267. "群垃圾,时不时来被gc",
  268. redefined[2].messageChain[QuoteReply]!! + PlainText("aseff")
  269. ),
  270. ),
  271. redefined,
  272. )
  273. }
  274. }
  275. private fun sourceStub(
  276. originalMessage: Message
  277. ): OfflineMessageSourceImplData {
  278. return OfflineMessageSourceImplData(
  279. MessageSourceKind.GROUP, intArrayOf(), bot.id, 0, 0, 0, originalMessage.toMessageChain(), intArrayOf()
  280. )
  281. }
  282. private suspend fun testRecursiveRefine(list: List<ImMsgBody.Elem>, expected: MessageChain, isLight: Boolean) {
  283. val actual = buildMessageChain {
  284. MessageProtocolFacade.decode(list, 0, MessageSourceKind.GROUP, bot, this, null)
  285. }.let { c ->
  286. if (isLight) {
  287. c.refineLight(bot)
  288. } else {
  289. c.refineDeep(bot)
  290. }
  291. }
  292. assertMessageChainEquals(expected, actual)
  293. }
  294. private fun assertNodesEquals(excepted: List<ForwardMessage.Node>, actual: List<ForwardMessage.Node>) {
  295. assertEquals(excepted.size, actual.size, "Length not match")
  296. for (i in excepted.indices) {
  297. val en = excepted[i]
  298. val an = actual[i]
  299. assertEquals(en.senderId, an.senderId)
  300. assertEquals(en.time, an.time)
  301. assertEquals(en.senderName, an.senderName)
  302. assertMessageChainEquals(en.messageChain, an.messageChain)
  303. }
  304. }
  305. @Suppress("unused")
  306. private object Color {
  307. const val RESET = "\u001b[0m"
  308. const val WHITE = "\u001b[97m"
  309. const val RED = "\u001b[31m"
  310. const val EMERALD_GREEN = "\u001b[32m"
  311. const val GOLD = "\u001b[33m"
  312. const val BLUE = "\u001b[34m"
  313. const val PURPLE = "\u001b[35m"
  314. const val GREEN = "\u001b[36m"
  315. const val GRAY = "\u001b[90m"
  316. const val LIGHT_RED = "\u001b[91m"
  317. const val LIGHT_GREEN = "\u001b[92m"
  318. const val LIGHT_YELLOW = "\u001b[93m"
  319. const val LIGHT_BLUE = "\u001b[94m"
  320. const val LIGHT_PURPLE = "\u001b[95m"
  321. const val LIGHT_CYAN = "\u001b[96m"
  322. }
  323. private fun assertMessageChainEquals(expected: MessageChain, actual: MessageChain) {
  324. val color = object {
  325. val yellow get() = Color.LIGHT_YELLOW
  326. val green get() = Color.LIGHT_GREEN
  327. val reset get() = Color.RESET
  328. }
  329. fun compare(expected: MessageChain, actual: MessageChain): Boolean {
  330. if (expected.size != actual.size) return false
  331. for ((e, a) in expected.zip(actual)) {
  332. when (e) {
  333. is QuoteReply -> {
  334. if (a !is QuoteReply) return false
  335. if (!compare(e.source.originalMessage, a.source.originalMessage)) return false
  336. }
  337. is MessageSource -> {
  338. if (a !is MessageSource) return false
  339. if (!compare(e.originalMessage, a.originalMessage)) return false
  340. }
  341. is ForwardMessage -> {
  342. if (a !is ForwardMessage) return false
  343. if (e.brief != a.brief) return false
  344. if (e.summary != a.summary) return false
  345. if (e.source != a.source) return false
  346. if (e.title != a.title) return false
  347. if (e.preview != a.preview) return false
  348. assertNodesEquals(e.nodeList, a.nodeList)
  349. }
  350. is Image -> {
  351. if (a !is Image) return false
  352. if (e.imageId != a.imageId) return false
  353. }
  354. else -> {
  355. if (e != a) return false
  356. }
  357. }
  358. }
  359. return true
  360. }
  361. if (!compare(expected, actual))
  362. throw AssertionError(
  363. "\n" + """
  364. Expected str:${color.green}${expected}${color.reset}
  365. Actual str:${color.green}${actual}${color.reset}
  366. Expected json:${color.yellow}${expected.serializeToJsonString()}${color.reset}
  367. Actual json:${color.yellow}${actual.serializeToJsonString()}${color.reset}
  368. """.trimIndent() + "\n"
  369. )
  370. }
  371. private class RefineTest(
  372. val list: List<ImMsgBody.Elem>,
  373. ) {
  374. lateinit var expected: MessageChain
  375. fun expected(chain: MessageChainBuilder.() -> Unit) {
  376. expected = buildMessageChain(chain)
  377. }
  378. var testLight: Boolean = false
  379. var testDeep: Boolean = false
  380. fun deep() {
  381. testDeep = true
  382. }
  383. fun light() {
  384. testLight = true
  385. }
  386. }
  387. @Suppress("TestFunctionName")
  388. private fun RefineTest(list: List<ImMsgBody.Elem>, action: RefineTest.() -> Unit): RefineTest {
  389. return RefineTest(list).apply(action)
  390. }