From 668ef44930befd55879666d77387cb355319ae62 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Mon, 24 Jun 2024 01:48:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(spring):=20=E4=BF=AE=E5=A4=8D=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=A4=9A=E4=B8=AAFilter=E6=97=B6=E4=BC=9A=E5=A4=B1?= =?UTF-8?q?=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../build.gradle.kts | 1 + .../KFunctionEventListenerProcessor.kt | 2 +- .../apptest/FindRepeatableFilterV2Tests.kt | 213 ++++++++++++++++++ .../build.gradle.kts | 2 + .../KFunctionEventListenerProcessor.kt | 2 +- .../spring/test/FindRepeatableFilterTests.kt | 213 ++++++++++++++++++ 6 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 simbot-cores/simbot-core-spring-boot-starter-v2/src/test/kotlin/love/forte/simbot/spring2/test/apptest/FindRepeatableFilterV2Tests.kt create mode 100644 simbot-cores/simbot-core-spring-boot-starter/src/test/kotlin/love/forte/simbot/spring/test/FindRepeatableFilterTests.kt diff --git a/simbot-cores/simbot-core-spring-boot-starter-v2/build.gradle.kts b/simbot-cores/simbot-core-spring-boot-starter-v2/build.gradle.kts index baf19ce6b..23be0d96c 100644 --- a/simbot-cores/simbot-core-spring-boot-starter-v2/build.gradle.kts +++ b/simbot-cores/simbot-core-spring-boot-starter-v2/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { testImplementation(libs.spring.boot.v2.configuration.processor) testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.spring.boot.v2.logging) + testImplementation(libs.mockk) } tasks.test { diff --git a/simbot-cores/simbot-core-spring-boot-starter-v2/src/main/kotlin/love/forte/simbot/spring2/configuration/listener/KFunctionEventListenerProcessor.kt b/simbot-cores/simbot-core-spring-boot-starter-v2/src/main/kotlin/love/forte/simbot/spring2/configuration/listener/KFunctionEventListenerProcessor.kt index 376717a22..516a750b8 100644 --- a/simbot-cores/simbot-core-spring-boot-starter-v2/src/main/kotlin/love/forte/simbot/spring2/configuration/listener/KFunctionEventListenerProcessor.kt +++ b/simbot-cores/simbot-core-spring-boot-starter-v2/src/main/kotlin/love/forte/simbot/spring2/configuration/listener/KFunctionEventListenerProcessor.kt @@ -320,7 +320,7 @@ internal class KFunctionEventListenerProcessor { }?.also { filterMatchers -> when { filterMatchers.size == 1 -> onFilter(filterMatchers.first()) - filterMatchers.size > 1 -> merge(matchType, filterMatchers) + filterMatchers.size > 1 -> onFilter(merge(matchType, filterMatchers)) } } diff --git a/simbot-cores/simbot-core-spring-boot-starter-v2/src/test/kotlin/love/forte/simbot/spring2/test/apptest/FindRepeatableFilterV2Tests.kt b/simbot-cores/simbot-core-spring-boot-starter-v2/src/test/kotlin/love/forte/simbot/spring2/test/apptest/FindRepeatableFilterV2Tests.kt new file mode 100644 index 000000000..bb1023a92 --- /dev/null +++ b/simbot-cores/simbot-core-spring-boot-starter-v2/src/test/kotlin/love/forte/simbot/spring2/test/apptest/FindRepeatableFilterV2Tests.kt @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * Project https://github.com/simple-robot/simpler-robot + * Email ForteScarlet@163.com + * + * This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + * + */ + +package love.forte.simbot.spring2.test.apptest + +import io.mockk.mockk +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.test.runTest +import love.forte.simbot.ability.DeleteOption +import love.forte.simbot.annotations.ExperimentalSimbotAPI +import love.forte.simbot.bot.Bot +import love.forte.simbot.common.PriorityConstant +import love.forte.simbot.common.id.ID +import love.forte.simbot.common.id.IntID.Companion.ID +import love.forte.simbot.common.id.UUID +import love.forte.simbot.common.time.Timestamp +import love.forte.simbot.core.application.launchSimpleApplication +import love.forte.simbot.event.Event +import love.forte.simbot.event.MessageEvent +import love.forte.simbot.event.StandardEventResult +import love.forte.simbot.message.* +import love.forte.simbot.quantcat.common.annotations.Filter +import love.forte.simbot.quantcat.common.annotations.FilterValue +import love.forte.simbot.quantcat.common.annotations.Listener +import love.forte.simbot.quantcat.common.binder.ParameterBinderFactory +import love.forte.simbot.quantcat.common.binder.SimpleBinderManager +import love.forte.simbot.spring2.configuration.binder.DefaultBinderManagerProvidersConfiguration +import love.forte.simbot.spring2.configuration.listener.KFunctionEventListenerProcessor +import love.forte.simbot.spring2.utils.findRepeatableMergedAnnotationSafely +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringBootConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.ApplicationContext +import org.springframework.stereotype.Component +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.reflect.jvm.javaMethod +import kotlin.test.* + + +/** + * + * @author ForteScarlet + */ +@SpringBootTest( + classes = [ + FindRepeatableFilterV2Tests.TestHd::class, + DefaultBinderManagerProvidersConfiguration::class, + ] +) +@SpringBootConfiguration +open class FindRepeatableFilterV2Tests { + + @Test + fun findRepeatableFilterTest() { + val filters = TestHd::func.javaMethod!!.findRepeatableMergedAnnotationSafely() + assertNotNull(filters) + assertEquals(2, filters.size) + } + + @OptIn(ExperimentalSimbotAPI::class) + @Test + fun processorTest( + @Autowired context: ApplicationContext, + @Autowired binders: List, + ) = runTest { + val processor = KFunctionEventListenerProcessor() + val manager = SimpleBinderManager( + globalBinderFactories = binders + ) + + val processedResolver = processor.process( + "test_bean_hd", + TestHd::func, + Listener("", PriorityConstant.DEFAULT), + null, + context, + manager + ) + + val app = launchSimpleApplication { } + processedResolver.resolve(app) + val listeners = app.eventDispatcher.listeners.toList() + assertEquals(1, listeners.size) + + val list = app.eventDispatcher.push(NopEvent()).toList() + list.forEach { + assertIs(it) + } + + val resume1: String = suspendCancellableCoroutine { continuation -> + launch { + val results = app.eventDispatcher.push(TestEvent("name1 Hello", continuation)).toList() + results.forEach { + assertIs(it) + } + assertTrue(continuation.isCompleted) + } + } + + assertEquals("Hello", resume1) + + val resume2: String = suspendCancellableCoroutine { continuation -> + launch { + val results = app.eventDispatcher.push(TestEvent("name2 hi", continuation)).toList() + results.forEach { + assertIs(it) + } + assertTrue(continuation.isCompleted) + } + } + + assertEquals("hi", resume2) + + val resume3: String = suspendCancellableCoroutine { continuation -> + launch { + val results = app.eventDispatcher.push(TestEvent("name1", continuation)).toList() + results.forEach { + assertIs(it) + } + + assertTrue(continuation.isActive) + assertFalse(continuation.isCancelled) + assertFalse(continuation.isCompleted) + continuation.resume("NOTHING") + } + } + + assertEquals("NOTHING", resume3) + } + + + @Component("test_bean_hd") + class TestHd { + @Listener + @Filter("name1 {{name}}") + @Filter("name2 {{name}}") + fun func(event: TestEvent, @FilterValue("name") name: String) { + event.continuation.resume(name) + } + } + + + class NopEvent : Event { + override val id: ID = UUID.random() + + @OptIn(ExperimentalSimbotAPI::class) + override val time: Timestamp = Timestamp.now() + } + + class TestEvent( + val plain: String, + val continuation: Continuation + ) : MessageEvent { + + override suspend fun reply(text: String): MessageReceipt = object : MessageReceipt { + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + + override val authorId: ID = 42.ID + override val messageContent: MessageContent = object : MessageContent { + override val id: ID = 0.ID + override val messages: Messages = listOf(plain.toText()).toMessages() + override val plainText: String = plain + + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + override val bot: Bot = mockk() + + override suspend fun reply(message: Message): MessageReceipt = object : MessageReceipt { + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + + override suspend fun reply(messageContent: MessageContent): MessageReceipt = object : MessageReceipt { + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + + override val id: ID = 4.ID + + @OptIn(ExperimentalSimbotAPI::class) + override val time: Timestamp = Timestamp.now() + } +} diff --git a/simbot-cores/simbot-core-spring-boot-starter/build.gradle.kts b/simbot-cores/simbot-core-spring-boot-starter/build.gradle.kts index eeba8712c..0d2da68cb 100644 --- a/simbot-cores/simbot-core-spring-boot-starter/build.gradle.kts +++ b/simbot-cores/simbot-core-spring-boot-starter/build.gradle.kts @@ -61,6 +61,7 @@ dependencies { compileOnly(libs.javax.annotation.api) + testImplementation(kotlin("test")) testImplementation(project(":simbot-commons:simbot-common-annotations")) testImplementation(project(":simbot-test")) testImplementation(libs.spring.boot.v3.test) @@ -68,6 +69,7 @@ dependencies { testImplementation(libs.spring.boot.v3.autoconfigure) testImplementation(libs.spring.boot.v3.configuration.processor) testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.mockk) } tasks.withType { diff --git a/simbot-cores/simbot-core-spring-boot-starter/src/main/kotlin/love/forte/simbot/spring/configuration/listener/KFunctionEventListenerProcessor.kt b/simbot-cores/simbot-core-spring-boot-starter/src/main/kotlin/love/forte/simbot/spring/configuration/listener/KFunctionEventListenerProcessor.kt index d13da715d..a8df8bf90 100644 --- a/simbot-cores/simbot-core-spring-boot-starter/src/main/kotlin/love/forte/simbot/spring/configuration/listener/KFunctionEventListenerProcessor.kt +++ b/simbot-cores/simbot-core-spring-boot-starter/src/main/kotlin/love/forte/simbot/spring/configuration/listener/KFunctionEventListenerProcessor.kt @@ -320,7 +320,7 @@ internal class KFunctionEventListenerProcessor { }?.also { filterMatchers -> when { filterMatchers.size == 1 -> onFilter(filterMatchers.first()) - filterMatchers.size > 1 -> merge(matchType, filterMatchers) + filterMatchers.size > 1 -> onFilter(merge(matchType, filterMatchers)) } } diff --git a/simbot-cores/simbot-core-spring-boot-starter/src/test/kotlin/love/forte/simbot/spring/test/FindRepeatableFilterTests.kt b/simbot-cores/simbot-core-spring-boot-starter/src/test/kotlin/love/forte/simbot/spring/test/FindRepeatableFilterTests.kt new file mode 100644 index 000000000..c273f77f2 --- /dev/null +++ b/simbot-cores/simbot-core-spring-boot-starter/src/test/kotlin/love/forte/simbot/spring/test/FindRepeatableFilterTests.kt @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * Project https://github.com/simple-robot/simpler-robot + * Email ForteScarlet@163.com + * + * This file is part of the Simple Robot Library (Alias: simple-robot, simbot, etc.). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Lesser GNU General Public License for more details. + * + * You should have received a copy of the Lesser GNU General Public License + * along with this program. If not, see . + * + */ + +package love.forte.simbot.spring.test + +import io.mockk.mockk +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.test.runTest +import love.forte.simbot.ability.DeleteOption +import love.forte.simbot.annotations.ExperimentalSimbotAPI +import love.forte.simbot.bot.Bot +import love.forte.simbot.common.PriorityConstant +import love.forte.simbot.common.id.ID +import love.forte.simbot.common.id.IntID.Companion.ID +import love.forte.simbot.common.id.UUID +import love.forte.simbot.common.time.Timestamp +import love.forte.simbot.core.application.launchSimpleApplication +import love.forte.simbot.event.Event +import love.forte.simbot.event.MessageEvent +import love.forte.simbot.event.StandardEventResult +import love.forte.simbot.message.* +import love.forte.simbot.quantcat.common.annotations.Filter +import love.forte.simbot.quantcat.common.annotations.FilterValue +import love.forte.simbot.quantcat.common.annotations.Listener +import love.forte.simbot.quantcat.common.binder.ParameterBinderFactory +import love.forte.simbot.quantcat.common.binder.SimpleBinderManager +import love.forte.simbot.spring.configuration.binder.DefaultBinderManagerProvidersConfiguration +import love.forte.simbot.spring.configuration.listener.KFunctionEventListenerProcessor +import love.forte.simbot.spring.utils.findRepeatableMergedAnnotationSafely +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.SpringBootConfiguration +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.ApplicationContext +import org.springframework.stereotype.Component +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.reflect.jvm.javaMethod +import kotlin.test.* + + +/** + * + * @author ForteScarlet + */ +@SpringBootTest( + classes = [ + FindRepeatableFilterTests.TestHd::class, + DefaultBinderManagerProvidersConfiguration::class, + ] +) +@SpringBootConfiguration +open class FindRepeatableFilterTests { + + @Test + fun findRepeatableFilterTest() { + val filters = TestHd::func.javaMethod!!.findRepeatableMergedAnnotationSafely() + assertNotNull(filters) + assertEquals(2, filters.size) + } + + @OptIn(ExperimentalSimbotAPI::class) + @Test + fun processorTest( + @Autowired context: ApplicationContext, + @Autowired binders: List, + ) = runTest { + val processor = KFunctionEventListenerProcessor() + val manager = SimpleBinderManager( + globalBinderFactories = binders + ) + + val processedResolver = processor.process( + "test_bean_hd", + TestHd::func, + Listener("", PriorityConstant.DEFAULT), + null, + context, + manager + ) + + val app = launchSimpleApplication { } + processedResolver.resolve(app) + val listeners = app.eventDispatcher.listeners.toList() + assertEquals(1, listeners.size) + + val list = app.eventDispatcher.push(NopEvent()).toList() + list.forEach { + assertIs(it) + } + + val resume1: String = suspendCancellableCoroutine { continuation -> + launch { + val results = app.eventDispatcher.push(TestEvent("name1 Hello", continuation)).toList() + results.forEach { + assertIs(it) + } + assertTrue(continuation.isCompleted) + } + } + + assertEquals("Hello", resume1) + + val resume2: String = suspendCancellableCoroutine { continuation -> + launch { + val results = app.eventDispatcher.push(TestEvent("name2 hi", continuation)).toList() + results.forEach { + assertIs(it) + } + assertTrue(continuation.isCompleted) + } + } + + assertEquals("hi", resume2) + + val resume3: String = suspendCancellableCoroutine { continuation -> + launch { + val results = app.eventDispatcher.push(TestEvent("name1", continuation)).toList() + results.forEach { + assertIs(it) + } + + assertTrue(continuation.isActive) + assertFalse(continuation.isCancelled) + assertFalse(continuation.isCompleted) + continuation.resume("NOTHING") + } + } + + assertEquals("NOTHING", resume3) + } + + + @Component("test_bean_hd") + class TestHd { + @Listener + @Filter("name1 {{name}}") + @Filter("name2 {{name}}") + fun func(event: TestEvent, @FilterValue("name") name: String) { + event.continuation.resume(name) + } + } + + + class NopEvent : Event { + override val id: ID = UUID.random() + + @OptIn(ExperimentalSimbotAPI::class) + override val time: Timestamp = Timestamp.now() + } + + class TestEvent( + val plain: String, + val continuation: Continuation + ) : MessageEvent { + + override suspend fun reply(text: String): MessageReceipt = object : MessageReceipt { + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + + override val authorId: ID = 42.ID + override val messageContent: MessageContent = object : MessageContent { + override val id: ID = 0.ID + override val messages: Messages = listOf(plain.toText()).toMessages() + override val plainText: String = plain + + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + override val bot: Bot = mockk() + + override suspend fun reply(message: Message): MessageReceipt = object : MessageReceipt { + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + + override suspend fun reply(messageContent: MessageContent): MessageReceipt = object : MessageReceipt { + override suspend fun delete(vararg options: DeleteOption) { + // Nothing. + } + } + + override val id: ID = 4.ID + + @OptIn(ExperimentalSimbotAPI::class) + override val time: Timestamp = Timestamp.now() + } +}