From: David ‘Bombe’ Roden Date: Wed, 13 Nov 2024 21:43:03 +0000 (+0100) Subject: 🚸 Allow specifying “EmailAction” to use a single email action for everything X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=44337b9ac6a641284cd51b5bdb5543d2aa3ea8eb;p=rhynodge.git 🚸 Allow specifying “EmailAction” to use a single email action for everything --- diff --git a/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java b/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java index e132b08..963997b 100644 --- a/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java +++ b/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java @@ -18,10 +18,12 @@ package net.pterodactylus.rhynodge.engine; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.inject.Singleton; import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; +import net.pterodactylus.rhynodge.Action; import net.pterodactylus.rhynodge.Reaction; import net.pterodactylus.rhynodge.actions.EmailAction; import net.pterodactylus.rhynodge.states.StateManager; @@ -46,10 +48,10 @@ public class Engine { private final StateManager stateManager; private final ScheduledExecutorService executorService; private final Map> scheduledFutures = new ConcurrentHashMap<>(); - private final EmailAction errorEmailAction; + private final Action errorEmailAction; @Inject - public Engine(StateManager stateManager, EmailAction errorEmailAction) { + public Engine(StateManager stateManager, @Named("errorEmail") Action errorEmailAction) { this.stateManager = stateManager; this.errorEmailAction = errorEmailAction; executorService = new ScheduledThreadPoolExecutor(1); diff --git a/src/main/java/net/pterodactylus/rhynodge/loader/ReactionLoader.java b/src/main/java/net/pterodactylus/rhynodge/loader/ReactionLoader.java index d358c23..44fa217 100644 --- a/src/main/java/net/pterodactylus/rhynodge/loader/ReactionLoader.java +++ b/src/main/java/net/pterodactylus/rhynodge/loader/ReactionLoader.java @@ -42,10 +42,8 @@ import net.pterodactylus.rhynodge.Merger; public class ReactionLoader { @Inject - public ReactionLoader(@Named("smtpHostname") String smtpHostname, @Named("emailSender") String emailSender, @Named("emailRecipient") String emailRecipient) { - this.smtpHostname = smtpHostname; - this.emailSender = emailSender; - this.emailRecipient = emailRecipient; + public ReactionLoader(@Named("email") Action emailAction) { + this.emailAction = emailAction; } /** @@ -146,6 +144,10 @@ public class ReactionLoader { @SuppressWarnings("unchecked") private T createObject(String className, String packageName, List parameters) throws LoaderException { + if (className.equals("EmailAction")) { + return (T) emailAction; + } + /* try to load class without package name. */ Class objectClass = null; try { @@ -163,13 +165,11 @@ public class ReactionLoader { } } - var effectiveParameters = overrideParameters(className, parameters); - /* locate an eligible constructor. */ Constructor wantedConstructor = null; for (Constructor constructor : objectClass.getConstructors()) { Class[] parameterTypes = constructor.getParameterTypes(); - if (parameterTypes.length != effectiveParameters.size()) { + if (parameterTypes.length != parameters.size()) { continue; } boolean compatibleTypes = true; @@ -190,7 +190,7 @@ public class ReactionLoader { } try { - return (T) wantedConstructor.newInstance(effectiveParameters.toArray()); + return (T) wantedConstructor.newInstance(parameters.toArray()); } catch (IllegalArgumentException iae1) { throw new LoaderException("Could not invoke constructor.", iae1); } catch (InstantiationException ie1) { @@ -203,15 +203,6 @@ public class ReactionLoader { } - private List overrideParameters(String className, List parameters) { - if (className.equals("EmailAction")) { - return List.of(smtpHostname, emailSender, emailRecipient); - } - return parameters; - } - - private final String smtpHostname; - private final String emailSender; - private final String emailRecipient; + private final Action emailAction; } diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/loader/ReactionLoaderTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/loader/ReactionLoaderTest.kt index c20a362..e177d09 100644 --- a/src/test/kotlin/net/pterodactylus/rhynodge/loader/ReactionLoaderTest.kt +++ b/src/test/kotlin/net/pterodactylus/rhynodge/loader/ReactionLoaderTest.kt @@ -1,19 +1,164 @@ package net.pterodactylus.rhynodge.loader -import com.google.inject.Guice +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.google.inject.Guice.createInjector +import net.pterodactylus.rhynodge.Action +import net.pterodactylus.rhynodge.Filter +import net.pterodactylus.rhynodge.Merger +import net.pterodactylus.rhynodge.Query +import net.pterodactylus.rhynodge.State +import net.pterodactylus.rhynodge.Watcher +import net.pterodactylus.rhynodge.actions.EmailAction +import net.pterodactylus.rhynodge.output.Output +import net.pterodactylus.rhynodge.states.AbstractState import net.pterodactylus.util.inject.ObjectBinding +import org.hamcrest.Matcher +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.contains +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.instanceOf +import org.hamcrest.Matchers.sameInstance import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import java.util.concurrent.TimeUnit class ReactionLoaderTest { @Test fun `reaction loader can be created by guice`() { - val injector = Guice.createInjector( - ObjectBinding.forClass(String::class.java).named("smtpHostname").`is`("host"), - ObjectBinding.forClass(String::class.java).named("emailSender").`is`("sender"), - ObjectBinding.forClass(String::class.java).named("emailRecipient").`is`("recipient"), + val injector = createInjector( + ObjectBinding.forClass(Action::class.java).named("email").shallBe(emailAction) ) injector.getInstance(ReactionLoader::class.java) } + @Test + fun `loader refuses to load disabled chains`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("disabled-chain.json")!!) + assertThrows { reactionLoader.loadReaction(chain) } + } + + @Test + fun `loader can load chain without watcher`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-without-watcher.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat(reaction.name(), equalTo("Test Reaction")) + assertThat(reaction.updateInterval(), equalTo(TimeUnit.SECONDS.toMillis(3600))) + assertThat(reaction.query(), instanceOf()) + assertThat(reaction.filters(), contains(instanceOf())) + assertThat(reaction.merger(), instanceOf()) + assertThat(reaction.action(), instanceOf()) + } + + @Test + fun `loader can load chain with watcher`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-watcher.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat(reaction.name(), equalTo("Test Watcher")) + assertThat(reaction.updateInterval(), equalTo(TimeUnit.SECONDS.toMillis(7200))) + assertThat(reaction.query(), instanceOf()) + assertThat(reaction.filters(), contains(instanceOf())) + assertThat(reaction.merger(), instanceOf()) + assertThat(reaction.action(), instanceOf()) + } + + @Test + fun `loader can instantiate query with multiple string parameters`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-query-parameters.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat((reaction.query() as TestQuery2).parameter1, equalTo("value1")) + assertThat((reaction.query() as TestQuery2).parameter2, equalTo("value2")) + } + + @Test + fun `loader can instantiate filters with multiple string parameters`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-filter-parameters.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat(reaction.filters(), contains(instanceOf(), instanceOf())) + assertThat((reaction.filters().toList()[0] as TestFilter1).parameter, equalTo("value")) + assertThat((reaction.filters().toList()[1] as TestFilter2).first, equalTo("one")) + assertThat((reaction.filters().toList()[1] as TestFilter2).second, equalTo("two")) + } + + @Test + fun `loader can instantiate merger with multiple parameters`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-merger-parameters.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat((reaction.merger() as TestMerger3).one, equalTo("1")) + assertThat((reaction.merger() as TestMerger3).two, equalTo("2")) + assertThat((reaction.merger() as TestMerger3).three, equalTo("3")) + } + + @Test + fun `loader can instantiate action with multiple parameters`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-action-parameters.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat((reaction.action() as TestAction2).foo, equalTo("1")) + assertThat((reaction.action() as TestAction2).bar, equalTo("2")) + } + + @Test + fun `loader can instantiate watcher with multiple parameters`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-watcher-parameters.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat((reaction.query() as TestQuery1).parameter1, equalTo("query")) + assertThat((reaction.filters().single() as TestFilter1).parameter, equalTo("filter")) + assertThat((reaction.merger() as TestMerger1).one, equalTo("merger")) + } + + @Test + fun `loader replaces email actions with instance given in constructor`() { + val chain = objectMapper.readValue(javaClass.getResourceAsStream("chain-with-email-action.json")!!) + val reaction = reactionLoader.loadReaction(chain) + assertThat(reaction.action(), sameInstance(emailAction)) + } + + private val emailAction = EmailAction("local.host", "send@r", "recipi@nt") + private val reactionLoader = ReactionLoader(emailAction) + +} + +private inline fun instanceOf(): Matcher = instanceOf(T::class.java) + +open class TestQuery0 : Query { + override fun state(): State = object : AbstractState() { + override fun plainText() = "" + } +} + +class TestQuery1(val parameter1: String) : TestQuery0() +class TestQuery2(val parameter1: String, val parameter2: String) : TestQuery0() + +open class TestFilter0 : Filter { + override fun filter(state: State) = state } + +class TestFilter1(val parameter: String): TestFilter0() +class TestFilter2(val first: String, val second: String): TestFilter0() + +open class TestMerger0 : Merger { + override fun mergeStates(previousState: State, currentState: State) = currentState.apply { trigger() } +} +class TestMerger1(val one: String) : TestMerger0() +class TestMerger3(val one: String, val two: String, val three: String) : TestMerger0() + +open class TestAction0 : Action { + override fun execute(output: Output) = Unit +} + +class TestAction2(val foo: String, val bar: String) : TestAction0() + +class TestWatcher0 : Watcher { + override fun query() = TestQuery0() + override fun filters() = listOf(TestFilter0()) + override fun merger() = TestMerger0() +} + +class TestWatcher3(val one: String, val two: String, val three: String) : Watcher { + override fun query() = TestQuery1(one) + override fun filters() = listOf(TestFilter1(two)) + override fun merger() = TestMerger1(three) +} + +private val objectMapper = ObjectMapper() diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-action-parameters.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-action-parameters.json new file mode 100644 index 0000000..d9cc940 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-action-parameters.json @@ -0,0 +1,29 @@ +{ + "enabled": true, + "name": "Test Watcher", + "query": { + "class": "net.pterodactylus.rhynodge.loader.TestQuery0" + }, + "filters": [ + { + "class": "net.pterodactylus.rhynodge.loader.TestFilter0" + } + ], + "merger": { + "class": "net.pterodactylus.rhynodge.loader.TestMerger0" + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction2", + "parameters": [ + { + "name": "one", + "value": "1" + }, + { + "name": "two", + "value": "2" + } + ] + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-email-action.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-email-action.json new file mode 100644 index 0000000..dc27653 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-email-action.json @@ -0,0 +1,11 @@ +{ + "enabled": true, + "name": "Test Watcher", + "watcher": { + "class": "net.pterodactylus.rhynodge.loader.TestWatcher0" + }, + "action": { + "class": "EmailAction" + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-filter-parameters.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-filter-parameters.json new file mode 100644 index 0000000..4a92a12 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-filter-parameters.json @@ -0,0 +1,38 @@ +{ + "enabled": true, + "name": "Test Watcher", + "query": { + "class": "net.pterodactylus.rhynodge.loader.TestQuery0" + }, + "filters": [ + { + "class": "net.pterodactylus.rhynodge.loader.TestFilter1", + "parameters": [ + { + "name": "parameter", + "value": "value" + } + ] + }, + { + "class": "net.pterodactylus.rhynodge.loader.TestFilter2", + "parameters": [ + { + "name": "first", + "value": "one" + }, + { + "name": "second", + "value": "two" + } + ] + } + ], + "merger": { + "class": "net.pterodactylus.rhynodge.loader.TestMerger0" + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction0" + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-merger-parameters.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-merger-parameters.json new file mode 100644 index 0000000..da68d00 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-merger-parameters.json @@ -0,0 +1,33 @@ +{ + "enabled": true, + "name": "Test Watcher", + "query": { + "class": "net.pterodactylus.rhynodge.loader.TestQuery0" + }, + "filters": [ + { + "class": "net.pterodactylus.rhynodge.loader.TestFilter0" + } + ], + "merger": { + "class": "net.pterodactylus.rhynodge.loader.TestMerger3", + "parameters": [ + { + "name": "one", + "value": "1" + }, + { + "name": "two", + "value": "2" + }, + { + "name": "param2", + "value": "3" + } + ] + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction0" + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-query-parameters.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-query-parameters.json new file mode 100644 index 0000000..10cd121 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-query-parameters.json @@ -0,0 +1,29 @@ +{ + "enabled": true, + "name": "Test Watcher", + "query": { + "class": "net.pterodactylus.rhynodge.loader.TestQuery2", + "parameters": [ + { + "name": "param1", + "value": "value1" + }, + { + "name": "param2", + "value": "value2" + } + ] + }, + "filters": [ + { + "class": "net.pterodactylus.rhynodge.loader.TestFilter0" + } + ], + "merger": { + "class": "net.pterodactylus.rhynodge.loader.TestMerger0" + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction0" + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-watcher-parameters.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-watcher-parameters.json new file mode 100644 index 0000000..8b138ea --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-watcher-parameters.json @@ -0,0 +1,25 @@ +{ + "enabled": true, + "name": "Test Watcher", + "watcher": { + "class": "net.pterodactylus.rhynodge.loader.TestWatcher3", + "parameters": [ + { + "name": "1", + "value": "query" + }, + { + "name": "2", + "value": "filter" + }, + { + "name": "3", + "value": "merger" + } + ] + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction0" + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-watcher.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-watcher.json new file mode 100644 index 0000000..51444a0 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-with-watcher.json @@ -0,0 +1,11 @@ +{ + "enabled": true, + "name": "Test Watcher", + "watcher": { + "class": "net.pterodactylus.rhynodge.loader.TestWatcher0" + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction0" + }, + "updateInterval": 7200 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/chain-without-watcher.json b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-without-watcher.json new file mode 100644 index 0000000..9788b16 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/chain-without-watcher.json @@ -0,0 +1,19 @@ +{ + "enabled": true, + "name": "Test Reaction", + "query": { + "class": "net.pterodactylus.rhynodge.loader.TestQuery0" + }, + "filters": [ + { + "class": "net.pterodactylus.rhynodge.loader.TestFilter0" + } + ], + "merger": { + "class": "net.pterodactylus.rhynodge.loader.TestMerger0" + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction0" + }, + "updateInterval": 3600 +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/loader/disabled-chain.json b/src/test/resources/net/pterodactylus/rhynodge/loader/disabled-chain.json new file mode 100644 index 0000000..bf6928a --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/loader/disabled-chain.json @@ -0,0 +1,17 @@ +{ + "enabled": false, + "name": "Test Watcher", + "watcher": { + "class": "net.pterodactylus.rhynodge.loader.TestWatcher" + }, + "action": { + "class": "net.pterodactylus.rhynodge.loader.TestAction", + "parameters": [ + { + "name": "parameter", + "value": "value" + } + ] + }, + "updateInterval": 7200 +}