diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 510fcf5..c7189e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,6 +35,5 @@ jobs: name: codecov-umbrella fail_ci_if_error: false verbose: true - java-version: 17 token: ${{ secrets.CODECOV_TOKEN }} if: github.ref == 'refs/heads/main' diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..ae8dfbe --- /dev/null +++ b/codecov.yml @@ -0,0 +1,2 @@ +ignore: + - "**/testFixtures/**" diff --git a/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/Registrar.kt b/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/Registrar.kt index 1be01f7..2879ee3 100644 --- a/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/Registrar.kt +++ b/projects/kediatr-core/src/main/kotlin/com/trendyol/kediatr/Registrar.kt @@ -65,7 +65,14 @@ abstract class Registrar { protected fun extractParameter(genericInterface: ParameterizedType): Class<*> = when (val typeArgument = genericInterface.actualTypeArguments[0]) { is ParameterizedType -> typeArgument.rawType as Class<*> - is TypeVariable<*> -> extractParameter((genericInterface.rawType as Class<*>).genericInterfaces[0] as ParameterizedType) + is TypeVariable<*> -> { + val rawType = (genericInterface.rawType as Class<*>) + when { + rawType.genericInterfaces.any() -> extractParameter(rawType.genericInterfaces[0] as ParameterizedType) + else -> rawType + } + } + else -> typeArgument as Class<*> } } diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/CommandHandlerTest.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/CommandHandlerTest.kt deleted file mode 100644 index e66f746..0000000 --- a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/CommandHandlerTest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.trendyol.kediatr - -import com.trendyol.kediatr.coreUseCases.CommandHandlerUseCases - -class CommandHandlerTest : CommandHandlerUseCases() diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/CommandWithResultHandlerTest.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/CommandWithResultHandlerTest.kt deleted file mode 100644 index 12e2687..0000000 --- a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/CommandWithResultHandlerTest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.trendyol.kediatr - -import com.trendyol.kediatr.coreUseCases.CommandWithResultHandlerUseCases - -class CommandWithResultHandlerTest : CommandWithResultHandlerUseCases() diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt new file mode 100644 index 0000000..8245da3 --- /dev/null +++ b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/MediatorTests.kt @@ -0,0 +1,50 @@ +package com.trendyol.kediatr + +import com.trendyol.kediatr.testing.* +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class MediatorTests : MediatorUseCases() { + override fun provideMediator(): Mediator = createMediator( + types = listOf( + TestCommandHandler(mediator = { testMediator }), + TestPipelineCommandHandler(mediator = { testMediator }), + TestCommandWithResultCommandHandler(mediator = { testMediator }), + TestNotificationHandler(mediator = { testMediator }), + TestQueryHandler(mediator = { testMediator }), + TestCommandHandlerWithoutInjection(), + TestInheritedCommandHandlerForSpecificCommand(), + TestCommandHandlerForTypeLimitedInheritance(), + ParameterizedCommandHandler(), + ParameterizedCommandHandlerForInheritance(), + ParameterizedCommandWithResultHandler(), + ParameterizedCommandWithResultHandlerOfInheritedHandler(), + APingHandler(), + AnotherPingHandler(), + Handler1ForNotificationOfMultipleHandlers(), + Handler2ForNotificationOfMultipleHandlers(), + InheritedNotificationHandler(), + ParameterizedNotificationHandler(), + ParameterizedNotificationHandlerForInheritance(), + TestPipelineCommandHandlerWithoutInjection(), + TestPipelineCommandHandlerThatFails(), + ExceptionPipelineBehavior(), + LoggingPipelineBehavior(), + InheritedPipelineBehaviour(), + ParameterizedQueryHandler() + ) + ) + + @Test + fun `when a publish strategy is defined it should be set`() { + listOf( + ContinueOnExceptionPublishStrategy(), + ParallelNoWaitPublishStrategy(), + ParallelWhenAllPublishStrategy(), + StopOnExceptionPublishStrategy() + ).forEach { + val builder = MediatorBuilder(MappingDependencyProvider(hashMapOf())).withPublishStrategy(it) + builder.defaultPublishStrategy shouldBe it + } + } +} diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/NotificationHandlerTest.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/NotificationHandlerTest.kt deleted file mode 100644 index d4dc5d8..0000000 --- a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/NotificationHandlerTest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.trendyol.kediatr - -import com.trendyol.kediatr.coreUseCases.NotificationHandlerUseCases - -class NotificationHandlerTest : NotificationHandlerUseCases() diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/PipelineBehaviorTest.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/PipelineBehaviorTest.kt deleted file mode 100644 index a9cc2e6..0000000 --- a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/PipelineBehaviorTest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.trendyol.kediatr - -import com.trendyol.kediatr.coreUseCases.PipelineBehaviorUseCases - -class PipelineBehaviorTest : PipelineBehaviorUseCases() diff --git a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/QueryHandlerTest.kt b/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/QueryHandlerTest.kt deleted file mode 100644 index 11f0b48..0000000 --- a/projects/kediatr-core/src/test/kotlin/com/trendyol/kediatr/QueryHandlerTest.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.trendyol.kediatr - -import com.trendyol.kediatr.coreUseCases.QueryHandlerUseCases - -class QueryHandlerTest : QueryHandlerUseCases() diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/CommandHandlerUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/CommandHandlerUseCases.kt deleted file mode 100644 index 3cbee3c..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/CommandHandlerUseCases.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.Command -import com.trendyol.kediatr.CommandHandler -import com.trendyol.kediatr.HandlerNotFoundException -import com.trendyol.kediatr.Mediator -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test - -abstract class CommandHandlerUseCases : MediatorTestConvention() { - @Test - fun async_commandHandler_should_be_fired() = runTest { - var invocationCount = 0 - - class MyAsyncCommand : Command - - class MyCommandHandler : CommandHandler { - override suspend fun handle(command: MyAsyncCommand) { - invocationCount++ - } - } - - val bus: Mediator = newMediator(handlers = listOf(MyCommandHandler())) - bus.send(MyAsyncCommand()) - - invocationCount shouldBe 1 - } - - @Test - fun `should throw exception if given async command has not been registered before`() = runTest { - class NonExistCommand : Command - - val bus: Mediator = newMediator() - - val exception = shouldThrow { - bus.send(NonExistCommand()) - } - - exception.message shouldBe "handler could not be found for ${NonExistCommand::class.java.typeName}" - } - - @Test - fun inheritance_should_work() = runTest { - var invocationCount = 0 - - class MyCommandForInheritance : Command - - abstract class MyCommandHandlerFor : CommandHandler - - class MyInheritedCommandHandler : MyCommandHandlerFor() { - override suspend fun handle(command: MyCommandForInheritance) { - invocationCount++ - } - } - - val handler = MyInheritedCommandHandler() - val bus: Mediator = newMediator(handlers = listOf(handler)) - bus.send(MyCommandForInheritance()) - - invocationCount shouldBe 1 - } - - @Test - fun inheritance_but_not_parameterized_should_work() = runTest { - var invocationCount = 0 - - class MyCommandForInheritance : Command - - abstract class MyCommandHandlerBaseForSpecificCommand : CommandHandler - - class MyInheritedCommandHandlerForSpecificCommand : MyCommandHandlerBaseForSpecificCommand() { - override suspend fun handle(command: MyCommandForInheritance) { - invocationCount++ - } - } - - val handler = MyInheritedCommandHandlerForSpecificCommand() - val bus: Mediator = newMediator(handlers = listOf(handler)) - bus.send(MyCommandForInheritance()) - - invocationCount shouldBe 1 - } - - @Test - fun async_command_should_be_fired() = runTest { - var invocationCount = 0 - - class ParameterizedCommand(val param: T) : Command - - class ParameterizedCommandHandler : CommandHandler> { - override suspend fun handle(command: ParameterizedCommand) { - command.param shouldBe "MyParam" - invocationCount++ - } - } - - // given - val handler = ParameterizedCommandHandler>() - val bus: Mediator = newMediator(handlers = listOf(handler)) - - // when - bus.send(ParameterizedCommand("MyParam")) - - // then - invocationCount shouldBe 1 - } - - @Test - fun async_commandHandler_with_inheritance_should_be_fired() = runTest { - var invocationCount = 0 - - class ParameterizedCommand(val param: T) : Command - - abstract class ParameterizedCommandHandlerBase : CommandHandler> - - class ParameterizedCommandHandler : ParameterizedCommandHandlerBase() { - override suspend fun handle(command: ParameterizedCommand) { - command.param shouldBe "MyParam" - invocationCount++ - } - } - - // given - val handler = ParameterizedCommandHandler>() - val bus: Mediator = newMediator(handlers = listOf(handler)) - - // when - bus.send(ParameterizedCommand("MyParam")) - - // then - invocationCount shouldBe 1 - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/CommandWithResultHandlerUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/CommandWithResultHandlerUseCases.kt deleted file mode 100644 index de80ec3..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/CommandWithResultHandlerUseCases.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.* -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.delay -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.* - -private var counter = 0 -private var asyncTestCounter = 0 - -abstract class CommandWithResultHandlerUseCases : MediatorTestConvention() { - @BeforeEach - fun beforeEach() { - counter = 0 - asyncTestCounter = 0 - } - - @Test - fun `async commandHandler should be fired`() = runTest { - val handler = AsyncMyCommandRHandler() - val bus: Mediator = newMediator(handlers = listOf(handler)) - bus.send(MyAsyncCommandR()) - asyncTestCounter shouldBe 1 - } - - @Test - fun `should throw exception if given async command has not been registered before`() = runTest { - val bus: Mediator = newMediator() - val exception = shouldThrow { - bus.send(NonExistCommandR()) - } - - exception.message shouldBe "handler could not be found for ${NonExistCommandR::class.java.typeName}" - } - - @Test - fun inheritance_should_work_with_command_with_result() = runTest { - var invocationCount = 0 - - class MyAsyncCommand : CommandWithResult - - class AsyncMyCommandHandler : CommandWithResultHandler { - override suspend fun handle(command: MyAsyncCommand): Result { - invocationCount++ - return Result() - } - } - - val handler = AsyncMyCommandHandler() - val bus: Mediator = newMediator(handlers = listOf(handler)) - bus.send(MyAsyncCommand()) - - invocationCount shouldBe 1 - } - - inner class ParameterizedCommandWithResult(val param: TParam) : CommandWithResult - - inner class ParameterizedAsyncCommandWithResultHandler : - CommandWithResultHandler, String> { - override suspend fun handle(command: ParameterizedCommandWithResult): String { - counter++ - return command.param.toString() - } - } - - @Test - fun `async commandWithResult should be fired and return result`() = runTest { - // given - val handler = ParameterizedAsyncCommandWithResultHandler>() - val bus: Mediator = newMediator(handlers = listOf(handler)) - - // when - val result = bus.send(ParameterizedCommandWithResult(61L)) - - // then - counter shouldBe 1 - result shouldBe "61" - } - - @Test - fun inheritance_should_work_with_command_with_result_and_parameter() = runTest { - var invocationCount = 0 - - class ParameterizedCommandWithResult(val param: TParam) : CommandWithResult - - abstract class ParameterizedCommandWithResultHandlerBase> : - CommandWithResultHandler - - class Handler : ParameterizedCommandWithResultHandlerBase>() { - override suspend fun handle(command: ParameterizedCommandWithResult): String { - invocationCount++ - return command.param.toString() - } - } - - val handler = Handler>() - val bus: Mediator = newMediator(handlers = listOf(handler)) - - // when - val result = bus.send(ParameterizedCommandWithResult("invoked")) - - // then - invocationCount shouldBe 1 - result shouldBe "invoked" - } -} - -private class Result - -private class NonExistCommandR : Command - -private class MyAsyncCommandR : CommandWithResult - -private class AsyncMyCommandRHandler : CommandWithResultHandler { - override suspend fun handle(command: MyAsyncCommandR): Result { - delay(500) - asyncTestCounter++ - - return Result() - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/NotificationHandlerUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/NotificationHandlerUseCases.kt deleted file mode 100644 index 7f15fe8..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/NotificationHandlerUseCases.kt +++ /dev/null @@ -1,131 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.Mediator -import com.trendyol.kediatr.Notification -import com.trendyol.kediatr.NotificationHandler -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.* -import java.util.concurrent.CountDownLatch - -private var asyncCountDownLatch = CountDownLatch(1) - -abstract class NotificationHandlerUseCases : MediatorTestConvention() { - @BeforeEach - fun beforeEach() { - asyncCountDownLatch = CountDownLatch(1) - } - - @Test - fun `async notification handler should be called`() = runTest { - val pingHandler = APingHandler() - val anotherPingHandler = AnotherPingHandler() - val bus: Mediator = newMediator(handlers = listOf(pingHandler, anotherPingHandler)) - bus.publish(ExtendedPing()) - - asyncCountDownLatch.count shouldBe 0 - } - - @Test - fun multiple_handlers_for_a_notification_should_be_dispatched() = runTest { - var invocationCount = 0 - - class MyNotification : Notification - - class Handler1 : NotificationHandler { - override suspend fun handle(notification: MyNotification) { - invocationCount++ - } - } - - class Handler2 : NotificationHandler { - override suspend fun handle(notification: MyNotification) { - invocationCount++ - } - } - - val bus: Mediator = newMediator(handlers = listOf(Handler1(), Handler2())) - bus.publish(MyNotification()) - invocationCount shouldBe 2 - } - - @Test - fun inherited_notification_handler_should_be_called() = runTest { - class PingForInherited : Notification - - abstract class NotificationHandlerBase : NotificationHandler - - class InheritedNotificationHandler : NotificationHandlerBase() { - override suspend fun handle(notification: PingForInherited) { - asyncCountDownLatch.countDown() - } - } - - val bus: Mediator = newMediator(handlers = listOf(InheritedNotificationHandler())) - bus.publish(PingForInherited()) - - asyncCountDownLatch.count shouldBe 0 - } - - inner class ParameterizedNotification(val param: T) : Notification - - inner class ParameterizedNotificationHandler : NotificationHandler> { - override suspend fun handle(notification: ParameterizedNotification) { - notification.param shouldBe "MyParam" - asyncCountDownLatch.countDown() - } - } - - @Test - fun `async notification should be fired`() = runTest { - // given - val handler = ParameterizedNotificationHandler>() - val bus: Mediator = newMediator(handlers = listOf(handler)) - - // when - bus.publish(ParameterizedNotification("MyParam")) - - // then - asyncCountDownLatch.count shouldBe 0 - } - - @Test - fun inherited_notification_handler_should_be_called_with_param() = runTest { - var invocationCount = 0 - var parameter = "" - - class ParameterizedNotification(val param: T) : Notification - - abstract class NotificationHandlerBase : NotificationHandler - - class ParameterizedNotificationHandler : NotificationHandlerBase>() { - override suspend fun handle(notification: ParameterizedNotification) { - parameter = notification.param.toString() - invocationCount++ - } - } - - val nHandler = ParameterizedNotificationHandler>() - val bus: Mediator = newMediator(handlers = listOf(nHandler)) - bus.publish(ParameterizedNotification("invoked")) - - invocationCount shouldBe 1 - parameter shouldBe "invoked" - } -} - -private open class Ping : Notification - -private class ExtendedPing : Ping() - -private class APingHandler : NotificationHandler { - override suspend fun handle(notification: ExtendedPing) { - asyncCountDownLatch.countDown() - } -} - -private class AnotherPingHandler : NotificationHandler { - override suspend fun handle(notification: Ping) { - asyncCountDownLatch.countDown() - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/PipelineBehaviorUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/PipelineBehaviorUseCases.kt deleted file mode 100644 index 5e51717..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/PipelineBehaviorUseCases.kt +++ /dev/null @@ -1,138 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.* -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.delay -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.* - -var exceptionPipelineBehaviorHandleCounter = 0 -var exceptionPipelineBehaviorHandleCatchCounter = 0 -var loggingPipelineBehaviorHandleBeforeNextCounter = 0 -var loggingPipelineBehaviorHandleAfterNextCounter = 0 -var inheritedPipelineBehaviourHandleCounter = 0 -var commandTestCounter = 0 - -abstract class PipelineBehaviorUseCases : MediatorTestConvention() { - @BeforeEach - fun beforeEach() { - exceptionPipelineBehaviorHandleCounter = 0 - exceptionPipelineBehaviorHandleCatchCounter = 0 - loggingPipelineBehaviorHandleBeforeNextCounter = 0 - loggingPipelineBehaviorHandleAfterNextCounter = 0 - inheritedPipelineBehaviourHandleCounter = 0 - commandTestCounter = 0 - } - - private class MyCommand : Command - - private class MyCommandHandler : CommandHandler { - override suspend fun handle(command: MyCommand) { - commandTestCounter++ - delay(500) - } - } - - @Test - fun `should process command without async pipeline`() = runTest { - val handler = MyCommandHandler() - val mediator: Mediator = newMediator(handlers = listOf(handler)) - mediator.send(MyCommand()) - - commandTestCounter shouldBe 1 - exceptionPipelineBehaviorHandleCatchCounter shouldBe 0 - exceptionPipelineBehaviorHandleCounter shouldBe 0 - loggingPipelineBehaviorHandleBeforeNextCounter shouldBe 0 - loggingPipelineBehaviorHandleAfterNextCounter shouldBe 0 - } - - @Test - fun `should process command with async pipeline`() = runTest { - val handler = MyCommandHandler() - val exceptionPipeline = ExceptionPipelineBehavior() - val loggingPipeline = LoggingPipelineBehavior() - val bus: Mediator = newMediator(handlers = listOf(exceptionPipeline, loggingPipeline) + listOf(handler)) - - bus.send(MyCommand()) - - commandTestCounter shouldBe 1 - exceptionPipelineBehaviorHandleCatchCounter shouldBe 0 - exceptionPipelineBehaviorHandleCounter shouldBe 1 - loggingPipelineBehaviorHandleBeforeNextCounter shouldBe 1 - loggingPipelineBehaviorHandleAfterNextCounter shouldBe 1 - } - - @Test - fun `should process exception in async handler`() = runTest { - val handler = MyBrokenHandler() - val exceptionPipeline = ExceptionPipelineBehavior() - val loggingPipeline = LoggingPipelineBehavior() - val bus: Mediator = newMediator(handlers = listOf(handler) + listOf(exceptionPipeline, loggingPipeline)) - val act = suspend { bus.send(MyBrokenCommand()) } - - shouldThrow { act() } - commandTestCounter shouldBe 0 - exceptionPipelineBehaviorHandleCatchCounter shouldBe 1 - exceptionPipelineBehaviorHandleCounter shouldBe 1 - loggingPipelineBehaviorHandleBeforeNextCounter shouldBe 1 - loggingPipelineBehaviorHandleAfterNextCounter shouldBe 0 - } - - @Test - fun `should process command with inherited pipeline`() = runTest { - val handler = MyCommandHandler() - val pipeline = InheritedPipelineBehaviour() - val bus: Mediator = newMediator(handlers = listOf(handler) + listOf(pipeline)) - bus.send(MyCommand()) - - inheritedPipelineBehaviourHandleCounter shouldBe 1 - } -} - -private abstract class MyBasePipelineBehaviour : PipelineBehavior - -private class InheritedPipelineBehaviour : MyBasePipelineBehaviour() { - override suspend fun handle( - request: TRequest, - next: RequestHandlerDelegate - ): TResponse { - inheritedPipelineBehaviourHandleCounter++ - return next(request) - } -} - -private class MyBrokenCommand : Command - -private class MyBrokenHandler : CommandHandler { - override suspend fun handle(command: MyBrokenCommand) { - throw Exception() - } -} - -private class ExceptionPipelineBehavior : PipelineBehavior { - override suspend fun handle( - request: TRequest, - next: RequestHandlerDelegate - ): TResponse { - try { - exceptionPipelineBehaviorHandleCounter++ - return next(request) - } catch (ex: Exception) { - exceptionPipelineBehaviorHandleCatchCounter++ - throw ex - } - } -} - -private class LoggingPipelineBehavior : PipelineBehavior { - override suspend fun handle( - request: TRequest, - next: RequestHandlerDelegate - ): TResponse { - loggingPipelineBehaviorHandleBeforeNextCounter++ - val result = next(request) - loggingPipelineBehaviorHandleAfterNextCounter++ - return result - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/PublishStrategyTests.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/PublishStrategyTests.kt deleted file mode 100644 index 2ad7170..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/PublishStrategyTests.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.* -import io.kotest.matchers.shouldBe -import org.junit.jupiter.api.Test - -class PublishStrategyTests { - @Test - fun `When a publish strategy is defined it should be set`() { - listOf( - ContinueOnExceptionPublishStrategy(), - ParallelNoWaitPublishStrategy(), - ParallelWhenAllPublishStrategy(), - StopOnExceptionPublishStrategy() - ).forEach { - val builder = MediatorBuilder(MappingDependencyProvider(hashMapOf())) - .withPublishStrategy(it) - - // Assert - builder.defaultPublishStrategy shouldBe it - } - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/QueryHandlerUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/QueryHandlerUseCases.kt deleted file mode 100644 index b269592..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/QueryHandlerUseCases.kt +++ /dev/null @@ -1,62 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.HandlerNotFoundException -import com.trendyol.kediatr.Mediator -import com.trendyol.kediatr.Query -import com.trendyol.kediatr.QueryHandler -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test - -abstract class QueryHandlerUseCases : MediatorTestConvention() { - @Test - fun async_queryHandler_should_retrieve_result() = runTest { - class TestQuery(val id: Int) : Query - - class TestQueryHandler : QueryHandler { - override suspend fun handle(query: TestQuery): String { - return "hello " + query.id - } - } - - val handler = TestQueryHandler() - val bus: Mediator = newMediator(handlers = listOf(handler)) - val result = bus.send(TestQuery(1)) - - result shouldBe "hello 1" - } - - @Test - fun should_throw_exception_if_given_async_query_has_not_been_registered_before() = runTest { - class NonExistQuery : Query - - val bus: Mediator = newMediator() - val exception = shouldThrow { - bus.send(NonExistQuery()) - } - - exception.message shouldBe "handler could not be found for ${NonExistQuery::class.java.typeName}" - } - - inner class ParameterizedQuery(val param: TParam) : Query - - inner class ParameterizedQueryHandler : QueryHandler, String> { - override suspend fun handle(query: ParameterizedQuery): String { - return query.param.toString() - } - } - - @Test - fun async_query_should_be_fired_and_return_result() = runTest { - // given - val handler = ParameterizedQueryHandler>() - val bus: Mediator = newMediator(handlers = listOf(handler)) - - // when - val result = bus.send(ParameterizedQuery(61L)) - - // then - result shouldBe "61" - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/convention.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/convention.kt deleted file mode 100644 index b2fc1f4..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/convention.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.trendyol.kediatr.coreUseCases - -import com.trendyol.kediatr.Mediator -import com.trendyol.kediatr.MediatorBuilder - -@Suppress("UNCHECKED_CAST") -abstract class MediatorTestConvention { - protected fun newMediator(handlers: List = emptyList()): Mediator = createMediator(handlers) - - private fun createMediator( - handlers: List = emptyList() - ): Mediator { - val provider = MappingDependencyProvider(handlers.associateBy { it.javaClass } as HashMap, Any>) - return MediatorBuilder(provider).build() - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/MediatorUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/MediatorUseCases.kt deleted file mode 100644 index 7c5f2f5..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/MediatorUseCases.kt +++ /dev/null @@ -1,89 +0,0 @@ -package com.trendyol.kediatr.framewokUseCases - -import com.trendyol.kediatr.HandlerNotFoundException -import io.kotest.assertions.throwables.shouldThrow -import io.kotest.matchers.shouldBe -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -abstract class MediatorUseCases : MediatorDIConvention { - @Test - fun command() = runTest { - val count = 0 - val result = testMediator.send(TestCommandWithResult(count)) - result.value shouldBe count + 1 - } - - @Test - fun commandWithoutAHandler() = runTest { - val exception = shouldThrow { - testMediator.send(TestNonExistCommand()) - } - - exception.message shouldBe "handler could not be found for ${TestNonExistCommand::class.java.typeName}" - } - - @Test - fun commandWithResult() = runTest { - val count = 0 - val command = TestCommandWithResult(count) - val result = testMediator.send(command) - result.value shouldBe count + 1 - command.invocationCount() shouldBe 1 - } - - @Test - fun commandWithResultWithoutAHandler() = runTest { - val exception = shouldThrow { - testMediator.send(NonExistCommandWithResult()) - } - - exception.message shouldBe "handler could not be found for ${NonExistCommandWithResult::class.java.typeName}" - } - - @Test - fun notification() = runTest { - val notification = TestNotification() - testMediator.publish(notification) - notification.invocationCount() shouldBe 1 - } - - @Test - fun pipeline() = runTest { - val command = TestPipelineCommand() - testMediator.send(command) - command.visitedPipelines() shouldBe setOf( - ExceptionPipelineBehavior::class.simpleName, - LoggingPipelineBehavior::class.simpleName - ) - } - - @Test - fun command_throws_exception() = runTest { - val act = suspend { testMediator.send(TestBrokenCommand()) } - assertThrows { act() } - } - - @Test - fun query_without_handler() = runTest { - val exception = shouldThrow { - testMediator.send(NonExistQuery()) - } - - exception.message shouldBe "handler could not be found for ${NonExistQuery::class.java.typeName}" - } - - @Test - fun query() = runTest { - val result = testMediator.send(TestQuery(1)) - result shouldBe "hello 1" - } - - @Test - fun inheritance_for_command_handler() = runTest { - val command = TestCommandForInheritance() - testMediator.send(command) - command.invocationCount() shouldBe 1 - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/convention.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/convention.kt deleted file mode 100644 index af4b6fe..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/convention.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.trendyol.kediatr.framewokUseCases - -import com.trendyol.kediatr.Mediator - -interface MediatorDIConvention { - fun provideMediator(): Mediator - - val testMediator get() = provideMediator() -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/models.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/models.kt deleted file mode 100644 index 17224c0..0000000 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/framewokUseCases/models.kt +++ /dev/null @@ -1,151 +0,0 @@ -@file:Suppress("UNCHECKED_CAST") - -package com.trendyol.kediatr.framewokUseCases - -import com.trendyol.kediatr.* -import io.kotest.matchers.shouldNotBe - -class TestNonExistCommand : Command - -class TestCommand : Command, EnrichedWithMetadata() - -class TestCommandHandler( - private val mediator: Mediator -) : CommandHandler { - override suspend fun handle(command: TestCommand) { - mediator shouldNotBe null - command.incrementInvocationCount() - } -} - -class Result( - val value: Int = 0 -) - -class NonExistCommandWithResult : CommandWithResult - -data class TestCommandWithResult( - val invoked: Int = 0 -) : CommandWithResult, EnrichedWithMetadata() - -class TestCommandWithResultCommandHandler( - val mediator: Mediator -) : CommandWithResultHandler { - override suspend fun handle( - command: TestCommandWithResult - ): Result = Result(command.invoked + 1).also { command.incrementInvocationCount() } -} - -class TestNotification : Notification, EnrichedWithMetadata() - -class TestNotificationHandler( - private val mediator: Mediator -) : NotificationHandler { - override suspend fun handle(notification: TestNotification) { - mediator shouldNotBe null - notification.incrementInvocationCount() - } -} - -class TestBrokenCommand : Command, EnrichedWithMetadata() - -class TestPipelineCommand : Command, EnrichedWithMetadata() - -abstract class EnrichedWithMetadata { - private val metadata = mutableMapOf() - - internal fun incrementInvocationCount() { - val invocationCount = invocationCount() - addMetadata(INVOCATION_COUNT, invocationCount + 1) - } - - fun invocationCount(): Int = getMetadata(INVOCATION_COUNT) as? Int ?: 0 - - internal fun visitedPipeline(pipeline: String) { - val visitedPipelines = visitedPipelines().toMutableSet() - visitedPipelines.add(pipeline) - addMetadata(VISITED_PIPELINES, visitedPipelines) - } - - fun visitedPipelines(): Set = getMetadata(VISITED_PIPELINES) as? Set ?: emptySet() - - private fun addMetadata(key: String, value: Any) { - metadata[key] = value - } - - private fun getMetadata(key: String): Any? = metadata[key] - - companion object { - private const val INVOCATION_COUNT = "invocationCount" - private const val VISITED_PIPELINES = "visitedPipelines" - } -} - -class TestPipelineCommandHandler( - val mediator: Mediator -) : CommandHandler { - override suspend fun handle(command: TestPipelineCommand) { - mediator shouldNotBe null - command.incrementInvocationCount() - } -} - -class TestBrokenCommandHandler( - private val mediator: Mediator -) : CommandHandler { - override suspend fun handle(command: TestBrokenCommand) { - mediator shouldNotBe null - command.incrementInvocationCount() - throw Exception() - } -} - -class ExceptionPipelineBehavior : PipelineBehavior { - override suspend fun handle( - request: TRequest, - next: RequestHandlerDelegate - ): TResponse = try { - when (request) { - is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) - } - next(request) - } catch (ex: Exception) { - throw ex - } -} - -class LoggingPipelineBehavior : PipelineBehavior { - override suspend fun handle( - request: TRequest, - next: RequestHandlerDelegate - ): TResponse { - when (request) { - is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) - } - return next(request) - } -} - -class NonExistQuery : Query - -class TestQuery(val id: Int) : Query, EnrichedWithMetadata() - -class TestQueryHandler( - private val mediator: Mediator -) : QueryHandler { - override suspend fun handle(query: TestQuery): String { - mediator shouldNotBe null - query.incrementInvocationCount() - return "hello " + query.id - } -} - -class TestCommandForInheritance : Command, EnrichedWithMetadata() - -abstract class MyCommandHandlerBaseForSpecificCommand : CommandHandler - -class TestInheritedCommandHandlerForSpecificCommand : MyCommandHandlerBaseForSpecificCommand() { - override suspend fun handle(command: TestCommandForInheritance) { - command.incrementInvocationCount() - } -} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/MappingDependencyProvider.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MappingDependencyProvider.kt similarity index 87% rename from projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/MappingDependencyProvider.kt rename to projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MappingDependencyProvider.kt index 4ccd1f0..fcd1fc4 100644 --- a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/coreUseCases/MappingDependencyProvider.kt +++ b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MappingDependencyProvider.kt @@ -1,6 +1,6 @@ @file:Suppress("UNCHECKED_CAST") -package com.trendyol.kediatr.coreUseCases +package com.trendyol.kediatr.testing import com.trendyol.kediatr.DependencyProvider import java.lang.reflect.ParameterizedType @@ -8,15 +8,11 @@ import java.lang.reflect.ParameterizedType class MappingDependencyProvider( private val handlerMap: HashMap, Any> ) : DependencyProvider { - override fun getSingleInstanceOf(clazz: Class): T { - return handlerMap[clazz] as T - } + override fun getSingleInstanceOf(clazz: Class): T = handlerMap[clazz] as T override fun getSubTypesOf(clazz: Class): Collection> = handlerMap .filter { filterInternal(it.key, clazz) } - .map { - it.key as Class - } + .map { it.key as Class } private fun filterInternal( handler: Class<*>, diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt new file mode 100644 index 0000000..deaad3c --- /dev/null +++ b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/MediatorUseCases.kt @@ -0,0 +1,211 @@ +package com.trendyol.kediatr.testing + +import com.trendyol.kediatr.HandlerNotFoundException +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.* + +abstract class MediatorUseCases : MediatorTestConvention() { + @Test + fun command_should_be_routed_to_its_handler() = runTest { + val command = TestCommand() + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun command_with_result_should_be_routed_to_its_handler() = runTest { + val count = 0 + val result = testMediator.send(TestCommandWithResult(count)) + result.value shouldBe count + 1 + } + + @Test + fun command_without_a_handler_should_throw_HandlerNotFoundException() = runTest { + val exception = shouldThrow { + testMediator.send(TestNonExistCommand()) + } + + exception.message shouldBe "handler could not be found for ${TestNonExistCommand::class.java.typeName}" + } + + @Test + fun command_with_result_that_does_not_have_a_handler_should_throw_HandlerNotFoundException() = runTest { + val exception = shouldThrow { + testMediator.send(NonExistCommandWithResult()) + } + + exception.message shouldBe "handler could not be found for ${NonExistCommandWithResult::class.java.typeName}" + } + + @Test + fun notification_should_be_routed() = runTest { + val notification = TestNotification() + testMediator.publish(notification) + notification.invocationCount() shouldBe 1 + } + + @Test + fun command_that_has_pipeline_behaviours_should_pass_through_the_pipeline() = runTest { + val command = CommandThatPassesThroughPipelineBehaviours() + testMediator.send(command) + command.visitedPipelines() shouldBe setOf( + ExceptionPipelineBehavior::class.simpleName, + LoggingPipelineBehavior::class.simpleName, + InheritedPipelineBehaviour::class.simpleName + ) + } + + @Test + fun command_that_fails_should_throw_related_exception_when_it_is_routed() = runTest { + val act = suspend { testMediator.send(TestCommandThatFailsWithException()) } + assertThrows { act() } + } + + @Test + fun query_without_a_handler_should_throw_HandlerNotFoundException() = runTest { + val exception = shouldThrow { + testMediator.send(NonExistQuery()) + } + + exception.message shouldBe "handler could not be found for ${NonExistQuery::class.java.typeName}" + } + + @Test + fun query_should_be_routed_to_its_handler() = runTest { + val result = testMediator.send(TestQuery(1)) + result shouldBe "hello 1" + } + + @Test + fun inherited_command_handlers_should_work() = runTest { + val command = TestCommandForInheritance() + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun command_is_routed_to_its_handler() = runTest { + val command = TestCommandForWithoutInjection() + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun throws_exception_for_command_that_does_not_have_a_handler() = runTest { + val exception = shouldThrow { + testMediator.send(TestNonExistCommand()) + } + + exception.message shouldBe "handler could not be found for ${TestNonExistCommand::class.java.typeName}" + } + + @Test + fun inherited_type_limited_commandHandlers_should_handle_the_commands() = runTest { + val command = TestCommandForTypeLimitedInheritance() + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun inherited_commandHandlers_should_handle_the_commands() = runTest { + val command = TestCommandForInheritance() + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun generic_parameterized_command_class_should_be_routed() = runTest { + val command = ParameterizedCommand("MyParam") + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun generic_parameterized_command_class_should_be_routed_to_inherited_handler() = runTest { + val command = ParameterizedCommandForInheritedCommandHandler("Netherlands") + testMediator.send(command) + command.invocationCount() shouldBe 1 + } + + @Test + fun generic_parameterized_commandWithResult_should_be_handled_and_return_result() = runTest { + val command = ParameterizedCommandWithResult(10) { + (it + 1).toString() + } + val result = testMediator.send(command) + command.invocationCount() shouldBe 1 + result shouldBe "11" + } + + @Test + fun generic_parameterized_inheritance_should_work_with_command_with_result_and_parameter() = runTest { + val command = ParameterizedCommandWithResultForInheritance("invoked") + val result = testMediator.send(command) + command.invocationCount() shouldBe 1 + result shouldBe "invoked" + } + + @Test + fun inherited_notification_should_be_called_for_its_direct_handler_and_indirect_handler() = runTest { + val notification = ExtendedPing() + testMediator.publish(notification) + notification.invocationCount() shouldBe 2 + } + + @Test + fun notification_that_is_handled_in_multiple_handlers_should_be_dispatched() = runTest { + val notification = NotificationForMultipleHandlers() + testMediator.publish(notification) + notification.invocationCount() shouldBe 2 + } + + @Test + fun inherited_notification_handler_should_be_called() = runTest { + val notification = PingForInherited() + testMediator.publish(notification) + notification.invocationCount() shouldBe 1 + } + + @Test + fun generic_parameterized_notification_should_be_routed() = runTest { + val notification = ParameterizedNotification("My Param") + testMediator.publish(notification) + notification.invocationCount() shouldBe 1 + } + + @Test + fun generic_parameterized_inherited_notification_handler_should_be_called_with_param() = runTest { + val notification = ParameterizedNotificationForInheritance("My Param") + testMediator.publish(notification) + notification.invocationCount() shouldBe 1 + } + + @Test + fun should_process_exception_in_handler() = runTest { + val act = suspend { testMediator.send(CommandThatFailsWhilePassingThroughPipelineBehaviours()) } + shouldThrow { act() } + } + + @Test + fun should_process_command_with_inherited_pipeline() = runTest { + val command = CommandForWithoutInjectionThatPassesThroughPipelineBehaviours() + testMediator.send(command) + command.visitedPipelines() shouldBe setOf( + ExceptionPipelineBehavior::class.simpleName, + LoggingPipelineBehavior::class.simpleName, + InheritedPipelineBehaviour::class.simpleName + ) + } + + @Test + fun generic_parameterized_query_should_be_routed_to_its_handler() = runTest { + val query = ParameterizedQuery(10L) { + (it * 6).toString() + } + val result = testMediator.send(query) + result shouldBe "60" + query.invocationCount() shouldBe 1 + } +} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/convention.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/convention.kt new file mode 100644 index 0000000..213adcb --- /dev/null +++ b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/convention.kt @@ -0,0 +1,16 @@ +package com.trendyol.kediatr.testing + +import com.trendyol.kediatr.* + +@Suppress("UNCHECKED_CAST") +abstract class MediatorTestConvention { + abstract fun provideMediator(): Mediator + + val testMediator by lazy { provideMediator() } + + protected fun createMediator(types: List = emptyList()): Mediator { + val provider = MappingDependencyProvider(types.associateBy { it.javaClass } as HashMap, Any>) + val mediator = MediatorBuilder(provider).build() + return mediator + } +} diff --git a/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt new file mode 100644 index 0000000..b1a8a0d --- /dev/null +++ b/projects/kediatr-core/src/testFixtures/kotlin/com/trendyol/kediatr/testing/models.kt @@ -0,0 +1,335 @@ +@file:Suppress("UNCHECKED_CAST") + +package com.trendyol.kediatr.testing + +import com.trendyol.kediatr.* +import io.kotest.matchers.shouldNotBe + +typealias MediatorAccessor = () -> Mediator + +abstract class EnrichedWithMetadata { + private val metadata = mutableMapOf() + + internal fun incrementInvocationCount() { + val invocationCount = invocationCount() + addMetadata(INVOCATION_COUNT, invocationCount + 1) + } + + fun invocationCount(): Int = getMetadata(INVOCATION_COUNT) as? Int ?: 0 + + internal fun visitedPipeline(pipeline: String) { + val visitedPipelines = visitedPipelines().toMutableSet() + visitedPipelines.add(pipeline) + addMetadata(VISITED_PIPELINES, visitedPipelines) + } + + fun visitedPipelines(): Set = getMetadata(VISITED_PIPELINES) as? Set ?: emptySet() + + private fun addMetadata(key: String, value: Any) { + metadata[key] = value + } + + private fun getMetadata(key: String): Any? = metadata[key] + + companion object { + private const val INVOCATION_COUNT = "invocationCount" + private const val VISITED_PIPELINES = "visitedPipelines" + } +} + +class Result(val value: Int = 0) + +class TestNonExistCommand : Command + +class NonExistCommandWithResult : CommandWithResult + +class NonExistQuery : Query + +/** + * Notifications + */ + +class TestNotification : Notification, EnrichedWithMetadata() + +class TestNotificationHandler( + private val mediator: MediatorAccessor +) : NotificationHandler { + override suspend fun handle(notification: TestNotification) { + mediator shouldNotBe null + notification.incrementInvocationCount() + } +} + +open class Ping : Notification, EnrichedWithMetadata() + +class ExtendedPing : Ping() + +class APingHandler : NotificationHandler { + override suspend fun handle(notification: ExtendedPing) { + notification.incrementInvocationCount() + } +} + +class AnotherPingHandler : NotificationHandler { + override suspend fun handle(notification: Ping) { + notification.incrementInvocationCount() + } +} + +class NotificationForMultipleHandlers : Notification, EnrichedWithMetadata() + +class Handler1ForNotificationOfMultipleHandlers : NotificationHandler { + override suspend fun handle(notification: NotificationForMultipleHandlers) { + notification.incrementInvocationCount() + } +} + +class Handler2ForNotificationOfMultipleHandlers : NotificationHandler { + override suspend fun handle(notification: NotificationForMultipleHandlers) { + notification.incrementInvocationCount() + } +} + +class PingForInherited : Notification, EnrichedWithMetadata() + +abstract class NotificationHandlerBase : NotificationHandler + +class InheritedNotificationHandler : NotificationHandlerBase() { + override suspend fun handle(notification: PingForInherited) { + notification.incrementInvocationCount() + } +} + +class ParameterizedNotification(val param: T) : Notification, EnrichedWithMetadata() + +class ParameterizedNotificationHandler : NotificationHandler> { + override suspend fun handle(notification: ParameterizedNotification) { + notification.param shouldNotBe null + notification.incrementInvocationCount() + } +} + +class ParameterizedNotificationForInheritance(val param: T) : Notification, EnrichedWithMetadata() + +class ParameterizedNotificationHandlerForInheritance : NotificationHandlerBase>() { + override suspend fun handle(notification: ParameterizedNotificationForInheritance) { + notification.param shouldNotBe null + notification.incrementInvocationCount() + } +} + +/** + * Commands + */ + +class TestCommandForWithoutInjection : Command, EnrichedWithMetadata() + +class TestCommandHandlerWithoutInjection : CommandHandler { + override suspend fun handle(command: TestCommandForWithoutInjection) { + command.incrementInvocationCount() + } +} + +class TestCommand : Command, EnrichedWithMetadata() + +class TestCommandHandler( + private val mediator: MediatorAccessor +) : CommandHandler { + override suspend fun handle(command: TestCommand) { + mediator() shouldNotBe null + command.incrementInvocationCount() + } +} + +data class TestCommandWithResult(val invoked: Int = 0) : CommandWithResult, EnrichedWithMetadata() + +class TestCommandWithResultCommandHandler(val mediator: MediatorAccessor) : CommandWithResultHandler { + override suspend fun handle( + command: TestCommandWithResult + ): Result = Result(command.invoked + 1).also { + mediator() shouldNotBe null + command.incrementInvocationCount() + } +} + +class CommandThatPassesThroughPipelineBehaviours : Command, EnrichedWithMetadata() + +class TestPipelineCommandHandler( + private val mediator: MediatorAccessor +) : CommandHandler { + override suspend fun handle(command: CommandThatPassesThroughPipelineBehaviours) { + mediator shouldNotBe null + command.incrementInvocationCount() + } +} + +class CommandForWithoutInjectionThatPassesThroughPipelineBehaviours : Command, EnrichedWithMetadata() + +class TestPipelineCommandHandlerWithoutInjection : CommandHandler { + override suspend fun handle(command: CommandForWithoutInjectionThatPassesThroughPipelineBehaviours) { + command.incrementInvocationCount() + } +} + +class CommandThatFailsWhilePassingThroughPipelineBehaviours : Command, EnrichedWithMetadata() + +class TestPipelineCommandHandlerThatFails : CommandHandler { + override suspend fun handle(command: CommandThatFailsWhilePassingThroughPipelineBehaviours) { + command.incrementInvocationCount() + throw Exception() + } +} + +class TestCommandThatFailsWithException : Command, EnrichedWithMetadata() + +class TestBrokenCommandHandler( + private val mediator: MediatorAccessor +) : CommandHandler { + override suspend fun handle(command: TestCommandThatFailsWithException) { + mediator shouldNotBe null + command.incrementInvocationCount() + throw Exception() + } +} + +class TestCommandForInheritance : Command, EnrichedWithMetadata() + +abstract class MyCommandHandlerBaseForSpecificCommand : CommandHandler + +class TestInheritedCommandHandlerForSpecificCommand : MyCommandHandlerBaseForSpecificCommand() { + override suspend fun handle(command: TestCommandForInheritance) { + command.incrementInvocationCount() + } +} + +class TestCommandForTypeLimitedInheritance : Command, EnrichedWithMetadata() + +abstract class TestBaseCommandHandlerForTypeLimitedInheritance : CommandHandler + +class TestCommandHandlerForTypeLimitedInheritance : + TestBaseCommandHandlerForTypeLimitedInheritance() { + override suspend fun handle(command: TestCommandForTypeLimitedInheritance) { + command.incrementInvocationCount() + } +} + +class ParameterizedCommand(val param: T) : Command, EnrichedWithMetadata() + +class ParameterizedCommandHandler : CommandHandler> { + override suspend fun handle(command: ParameterizedCommand) { + command.param shouldNotBe null + command.incrementInvocationCount() + } +} + +class ParameterizedCommandForInheritedCommandHandler(val param: T) : Command, EnrichedWithMetadata() + +abstract class ParameterizedCommandHandlerBaseForInheritedCommandHandler : + CommandHandler> + +class ParameterizedCommandHandlerForInheritance : ParameterizedCommandHandlerBaseForInheritedCommandHandler() { + override suspend fun handle(command: ParameterizedCommandForInheritedCommandHandler) { + command.param shouldNotBe null + command.incrementInvocationCount() + } +} + +class ParameterizedCommandWithResult( + val param: TParam, + val retFn: suspend (TParam) -> TReturn +) : CommandWithResult, EnrichedWithMetadata() + +class ParameterizedCommandWithResultHandler : + CommandWithResultHandler, TReturn> { + override suspend fun handle(command: ParameterizedCommandWithResult): TReturn { + command.param shouldNotBe null + command.incrementInvocationCount() + return command.retFn(command.param) + } +} + +data class ParameterizedCommandWithResultForInheritance(val param: TParam) : CommandWithResult, EnrichedWithMetadata() + +abstract class ParameterizedCommandWithResultHandlerBase> : CommandWithResultHandler + +class ParameterizedCommandWithResultHandlerOfInheritedHandler : + ParameterizedCommandWithResultHandlerBase>() { + override suspend fun handle(command: ParameterizedCommandWithResultForInheritance): String { + command.param shouldNotBe null + command.incrementInvocationCount() + return command.param.toString() + } +} + +/** + * Queries + */ + +class TestQuery(val id: Int) : Query, EnrichedWithMetadata() + +class TestQueryHandler( + private val mediator: MediatorAccessor +) : QueryHandler { + override suspend fun handle(query: TestQuery): String { + mediator shouldNotBe null + query.incrementInvocationCount() + return "hello " + query.id + } +} + +class ParameterizedQuery( + val param: TParam, + val retFn: suspend (TParam) -> TResponse +) : Query, EnrichedWithMetadata() + +class ParameterizedQueryHandler : QueryHandler, TResponse> { + override suspend fun handle(query: ParameterizedQuery): TResponse { + query.param shouldNotBe null + query.incrementInvocationCount() + return query.retFn(query.param) + } +} + +/** + * Pipeline Behaviors + */ + +class ExceptionPipelineBehavior : PipelineBehavior { + override suspend fun handle( + request: TRequest, + next: RequestHandlerDelegate + ): TResponse = try { + when (request) { + is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) + } + next(request) + } catch (ex: Exception) { + throw ex + } +} + +class LoggingPipelineBehavior : PipelineBehavior { + override suspend fun handle( + request: TRequest, + next: RequestHandlerDelegate + ): TResponse { + when (request) { + is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) + } + return next(request) + } +} + +abstract class MyBasePipelineBehaviour : PipelineBehavior + +class InheritedPipelineBehaviour : MyBasePipelineBehaviour() { + override suspend fun handle( + request: TRequest, + next: RequestHandlerDelegate + ): TResponse { + when (request) { + is EnrichedWithMetadata -> request.visitedPipeline(this::class.java.simpleName) + } + return next(request) + } +} diff --git a/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt b/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt index 0829d76..f7c0a31 100644 --- a/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt +++ b/projects/kediatr-koin-starter/src/test/kotlin/com/trendyol/kediatr/koin/MediatorTests.kt @@ -1,12 +1,10 @@ package com.trendyol.kediatr.koin import com.trendyol.kediatr.* -import com.trendyol.kediatr.framewokUseCases.* +import com.trendyol.kediatr.testing.* import org.junit.jupiter.api.extension.RegisterExtension -import org.koin.dsl.bind -import org.koin.dsl.module -import org.koin.test.KoinTest -import org.koin.test.inject +import org.koin.dsl.* +import org.koin.test.* import org.koin.test.junit5.KoinTestExtension class MediatorTests : KoinTest, MediatorUseCases() { @@ -16,6 +14,7 @@ class MediatorTests : KoinTest, MediatorUseCases() { modules( module { single { KediatRKoin.getMediator() } + single { InheritedPipelineBehaviour() } single { ExceptionPipelineBehavior() } single { LoggingPipelineBehavior() } single { TestCommandHandler(get()) } @@ -25,6 +24,23 @@ class MediatorTests : KoinTest, MediatorUseCases() { single { TestBrokenCommandHandler(get()) } bind CommandHandler::class single { TestPipelineCommandHandler(get()) } bind CommandHandler::class single { TestInheritedCommandHandlerForSpecificCommand() } bind CommandHandler::class + single { TestCommandHandlerWithoutInjection() } bind CommandHandler::class + single { TestCommandHandlerForTypeLimitedInheritance() } bind CommandHandler::class + single { ParameterizedCommandHandler() } bind CommandHandler::class + single { ParameterizedCommandHandlerForInheritance() } bind CommandHandler::class + single { ParameterizedCommandWithResultHandler() } bind CommandWithResultHandler::class + single { ParameterizedCommandWithResultHandlerOfInheritedHandler() } bind CommandWithResultHandler::class + single { APingHandler() } bind NotificationHandler::class + single { AnotherPingHandler() } bind NotificationHandler::class + single { Handler1ForNotificationOfMultipleHandlers() } bind NotificationHandler::class + single { Handler2ForNotificationOfMultipleHandlers() } bind NotificationHandler::class + single { InheritedNotificationHandler() } bind NotificationHandler::class + single { ParameterizedNotificationHandler() } bind NotificationHandler::class + single { ParameterizedNotificationHandlerForInheritance() } bind NotificationHandler::class + single { TestPipelineCommandHandlerWithoutInjection() } bind CommandHandler::class + single { TestPipelineCommandHandlerThatFails() } bind CommandHandler::class + single { ParameterizedQueryHandler() } bind QueryHandler::class + single { { get() } } } ) } diff --git a/projects/kediatr-quarkus-starter/src/main/kotlin/com/trendyol/kediatr/quarkus/QuarkusTypeResolver.kt b/projects/kediatr-quarkus-starter/src/main/kotlin/com/trendyol/kediatr/quarkus/QuarkusTypeResolver.kt index 2dc7821..836449b 100644 --- a/projects/kediatr-quarkus-starter/src/main/kotlin/com/trendyol/kediatr/quarkus/QuarkusTypeResolver.kt +++ b/projects/kediatr-quarkus-starter/src/main/kotlin/com/trendyol/kediatr/quarkus/QuarkusTypeResolver.kt @@ -5,28 +5,32 @@ package com.trendyol.kediatr.quarkus import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.inject.spi.* import java.lang.reflect.* +import java.util.concurrent.ConcurrentHashMap @ApplicationScoped class QuarkusTypeResolver( private val beanManager: BeanManager ) { - fun resolveOrThrow(clazz: Class): T { - val beans = beanManager.getBeans(clazz) - val bean = beans.firstOrNull() ?: error("No bean found for class $clazz") - val ctx = beanManager.createCreationalContext(bean) - return beanManager.getReference(bean, clazz, ctx) as T - } - - fun resolveTypesOrEmpty(clazz: Class): Collection> = beanManager.getBeans(Any::class.java) + private val resolveCache = ConcurrentHashMap, Any>() + private val typesCache = ConcurrentHashMap, Collection>>() + private val beanTypes = beanManager.getBeans(Any::class.java) .asSequence() - .filterNot(::quarkusThings) + .filterNot(::quarkusPackage) .flatMap { it.types } - .mapNotNull { type -> mapRelevantOrNull(type, clazz) } - .filter { it != clazz } - .distinct() - .toList() - private fun quarkusThings(it: Bean<*>) = it.beanClass.packageName.contains("io.quarkus") + fun resolveOrThrow(clazz: Class): T = resolveCache.computeIfAbsent(clazz) { + val bean = beanManager.getBeans(clazz).singleOrNull() ?: error("No bean found for $clazz") + val ctx = beanManager.createCreationalContext(bean) + beanManager.getReference(bean, clazz, ctx) + } as T + + fun resolveTypesOrEmpty(clazz: Class): Collection> = typesCache.computeIfAbsent(clazz) { + beanTypes + .mapNotNull { type -> mapRelevantOrNull(type, clazz) } + .filter { it != clazz } + .distinct() + .toList() + } as Collection> private fun mapRelevantOrNull( type: Type?, @@ -40,10 +44,17 @@ class QuarkusTypeResolver( private fun getMatchingGenericArgument( type: ParameterizedType, clazz: Class - ) = type.actualTypeArguments.firstNotNullOfOrNull { arg -> (arg as? Class<*>)?.let { getMatchingClass(it, clazz) } } - - private fun getMatchingClass(type: Class<*>, targetClass: Class): Class? = when { + ): Class? = type.actualTypeArguments + .filterIsInstance>() + .firstNotNullOfOrNull { getMatchingClass(it, clazz) } + + private fun getMatchingClass( + type: Class<*>, + targetClass: Class + ): Class? = when { targetClass.isAssignableFrom(type) -> type as Class else -> null } + + private fun quarkusPackage(it: Bean<*>) = it.beanClass.packageName.contains("io.quarkus") } diff --git a/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt b/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt index 0eecc6b..4ca5c11 100644 --- a/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt +++ b/projects/kediatr-quarkus-starter/src/test/kotlin/com/trendyol/kediatr/quarkus/MediatorTests.kt @@ -1,7 +1,7 @@ package com.trendyol.kediatr.quarkus import com.trendyol.kediatr.Mediator -import com.trendyol.kediatr.framewokUseCases.* +import com.trendyol.kediatr.testing.* import io.quarkus.test.junit.QuarkusTest import jakarta.enterprise.inject.Produces import jakarta.inject.Inject @@ -14,23 +14,22 @@ class MediatorTests : MediatorUseCases() { override fun provideMediator(): Mediator = mediator @Produces - fun handler1(mediator: Mediator) = TestCommandHandler(mediator) + fun handler1(mediator: Mediator) = TestCommandHandler({ mediator }) @Produces - fun handler2(mediator: Mediator) = TestCommandWithResultCommandHandler(mediator) + fun handler2(mediator: Mediator) = TestCommandWithResultCommandHandler({ mediator }) @Produces - fun handler3(mediator: Mediator): TestBrokenCommandHandler = TestBrokenCommandHandler(mediator) + fun handler3(mediator: Mediator) = TestBrokenCommandHandler({ mediator }) @Produces - fun handler4(mediator: Mediator): TestPipelineCommandHandler = TestPipelineCommandHandler(mediator) + fun handler4(mediator: Mediator) = TestPipelineCommandHandler({ mediator }) @Produces - fun handler5(): TestInheritedCommandHandlerForSpecificCommand = - TestInheritedCommandHandlerForSpecificCommand() + fun handler5() = TestInheritedCommandHandlerForSpecificCommand() @Produces - fun notificationHandler(mediator: Mediator) = TestNotificationHandler(mediator) + fun notificationHandler(mediator: Mediator) = TestNotificationHandler({ mediator }) @Produces fun pipeline1() = ExceptionPipelineBehavior() @@ -39,5 +38,56 @@ class MediatorTests : MediatorUseCases() { fun pipeline2() = LoggingPipelineBehavior() @Produces - fun provideQueryHandler(mediator: Mediator) = TestQueryHandler(mediator) + fun pipeline3() = InheritedPipelineBehaviour() + + @Produces + fun queryHandler(mediator: Mediator) = TestQueryHandler({ mediator }) + + @Produces + fun handler6() = TestCommandHandlerWithoutInjection() + + @Produces + fun handler7() = TestCommandHandlerForTypeLimitedInheritance() + + @Produces + fun handler8(): ParameterizedCommandHandler = ParameterizedCommandHandler() + + @Produces + fun handler9() = ParameterizedCommandHandlerForInheritance() + + @Produces + fun handler10() = ParameterizedCommandWithResultHandler() + + @Produces + fun handler11() = ParameterizedCommandWithResultHandlerOfInheritedHandler() + + @Produces + fun handler12() = APingHandler() + + @Produces + fun handler13() = AnotherPingHandler() + + @Produces + fun handler14() = Handler1ForNotificationOfMultipleHandlers() + + @Produces + fun handler15() = Handler2ForNotificationOfMultipleHandlers() + + @Produces + fun handler16() = InheritedNotificationHandler() + + @Produces + fun handler17() = ParameterizedNotificationHandler() + + @Produces + fun handler18() = ParameterizedNotificationHandlerForInheritance() + + @Produces + fun handler19() = TestPipelineCommandHandlerWithoutInjection() + + @Produces + fun handler20() = TestPipelineCommandHandlerThatFails() + + @Produces + fun handler22() = ParameterizedQueryHandler() } diff --git a/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt b/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt index f8cf9e8..4c224e5 100644 --- a/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt +++ b/projects/kediatr-spring-boot-2x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt @@ -1,13 +1,15 @@ package com.trendyol.kediatr.spring import com.trendyol.kediatr.Mediator -import com.trendyol.kediatr.framewokUseCases.* +import com.trendyol.kediatr.testing.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.* @SpringBootTest( classes = [ KediatRAutoConfiguration::class, + MediatorTests.TestConfiguration::class, TestCommandHandler::class, ExceptionPipelineBehavior::class, LoggingPipelineBehavior::class, @@ -16,7 +18,24 @@ import org.springframework.boot.test.context.SpringBootTest TestBrokenCommandHandler::class, TestPipelineCommandHandler::class, TestCommandWithResultCommandHandler::class, - TestInheritedCommandHandlerForSpecificCommand::class + TestInheritedCommandHandlerForSpecificCommand::class, + TestCommandHandlerWithoutInjection::class, + TestCommandHandlerForTypeLimitedInheritance::class, + ParameterizedCommandHandler::class, + ParameterizedCommandHandlerForInheritance::class, + ParameterizedCommandWithResultHandler::class, + ParameterizedCommandWithResultHandlerOfInheritedHandler::class, + APingHandler::class, + AnotherPingHandler::class, + Handler1ForNotificationOfMultipleHandlers::class, + Handler2ForNotificationOfMultipleHandlers::class, + InheritedNotificationHandler::class, + ParameterizedNotificationHandler::class, + ParameterizedNotificationHandlerForInheritance::class, + TestPipelineCommandHandlerWithoutInjection::class, + TestPipelineCommandHandlerThatFails::class, + InheritedPipelineBehaviour::class, + ParameterizedQueryHandler::class ] ) class MediatorTests : MediatorUseCases() { @@ -24,4 +43,12 @@ class MediatorTests : MediatorUseCases() { lateinit var mediator: Mediator override fun provideMediator(): Mediator = mediator + + @Configuration + open class TestConfiguration { + @Bean + open fun mediatorAccessor(mediator: Mediator): MediatorAccessor { + return { mediator } + } + } } diff --git a/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt b/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt index f8cf9e8..4c224e5 100644 --- a/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt +++ b/projects/kediatr-spring-boot-3x-starter/src/test/kotlin/com/trendyol/kediatr/spring/MediatorTests.kt @@ -1,13 +1,15 @@ package com.trendyol.kediatr.spring import com.trendyol.kediatr.Mediator -import com.trendyol.kediatr.framewokUseCases.* +import com.trendyol.kediatr.testing.* import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest +import org.springframework.context.annotation.* @SpringBootTest( classes = [ KediatRAutoConfiguration::class, + MediatorTests.TestConfiguration::class, TestCommandHandler::class, ExceptionPipelineBehavior::class, LoggingPipelineBehavior::class, @@ -16,7 +18,24 @@ import org.springframework.boot.test.context.SpringBootTest TestBrokenCommandHandler::class, TestPipelineCommandHandler::class, TestCommandWithResultCommandHandler::class, - TestInheritedCommandHandlerForSpecificCommand::class + TestInheritedCommandHandlerForSpecificCommand::class, + TestCommandHandlerWithoutInjection::class, + TestCommandHandlerForTypeLimitedInheritance::class, + ParameterizedCommandHandler::class, + ParameterizedCommandHandlerForInheritance::class, + ParameterizedCommandWithResultHandler::class, + ParameterizedCommandWithResultHandlerOfInheritedHandler::class, + APingHandler::class, + AnotherPingHandler::class, + Handler1ForNotificationOfMultipleHandlers::class, + Handler2ForNotificationOfMultipleHandlers::class, + InheritedNotificationHandler::class, + ParameterizedNotificationHandler::class, + ParameterizedNotificationHandlerForInheritance::class, + TestPipelineCommandHandlerWithoutInjection::class, + TestPipelineCommandHandlerThatFails::class, + InheritedPipelineBehaviour::class, + ParameterizedQueryHandler::class ] ) class MediatorTests : MediatorUseCases() { @@ -24,4 +43,12 @@ class MediatorTests : MediatorUseCases() { lateinit var mediator: Mediator override fun provideMediator(): Mediator = mediator + + @Configuration + open class TestConfiguration { + @Bean + open fun mediatorAccessor(mediator: Mediator): MediatorAccessor { + return { mediator } + } + } }