From 471004d860e843ca1c1a3dd50f1090569bff86e0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Sat, 29 Apr 2023 21:39:49 +0200 Subject: [PATCH] =?utf8?q?=E2=9C=A8=20Only=20send=20an=20email=20if=20prev?= =?utf8?q?ious=20state=20was=20not=20a=20failure,=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../rhynodge/engine/ReactionRunner.java | 9 +- .../rhynodge/engine/ReactionRunnerTest.kt | 116 +++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/test/kotlin/net/pterodactylus/rhynodge/engine/ReactionRunnerTest.kt diff --git a/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java b/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java index 0538a0f..45642ce 100644 --- a/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java +++ b/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java @@ -49,8 +49,11 @@ public class ReactionRunner implements Runnable { state = runStateThroughFilters(state); if (!state.success()) { logger.info(format("Reaction %s failed in %s.", reaction.name(), state)); + Optional lastState = reactionState.loadLastState(); saveStateWithIncreasedFailCount(state); - errorEmailAction.execute(createErrorOutput(reaction, state)); + if (thisFailureIsTheFirstFailure(lastState)) { + errorEmailAction.execute(createErrorOutput(reaction, state)); + } return; } Optional lastSuccessfulState = reactionState.loadLastSuccessfulState(); @@ -69,6 +72,10 @@ public class ReactionRunner implements Runnable { logger.info(format("Reaction %s finished.", reaction.name())); } + private static boolean thisFailureIsTheFirstFailure(Optional lastState) { + return lastState.map(State::success).orElse(true); + } + private void saveStateWithIncreasedFailCount(State state) { Optional lastState = reactionState.loadLastState(); state.setFailCount(lastState.map(State::failCount).orElse(0) + 1); diff --git a/src/test/kotlin/net/pterodactylus/rhynodge/engine/ReactionRunnerTest.kt b/src/test/kotlin/net/pterodactylus/rhynodge/engine/ReactionRunnerTest.kt new file mode 100644 index 0000000..4dfd891 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/rhynodge/engine/ReactionRunnerTest.kt @@ -0,0 +1,116 @@ +package net.pterodactylus.rhynodge.engine + +import net.pterodactylus.rhynodge.Query +import net.pterodactylus.rhynodge.Reaction +import net.pterodactylus.rhynodge.State +import net.pterodactylus.rhynodge.actions.EmailAction +import net.pterodactylus.rhynodge.output.Output +import net.pterodactylus.rhynodge.states.AbstractState +import net.pterodactylus.rhynodge.states.FailedState +import net.pterodactylus.rhynodge.states.StateManager +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.hamcrest.TypeSafeDiagnosingMatcher +import org.junit.Test +import java.util.Optional +import java.util.Optional.empty +import java.util.Optional.of +import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference + +/** + * Unit test for [ReactionRunner]. + */ +class ReactionRunnerTest { + + @Test + fun `reaction runner sends email if reaction fails and there is no last state`() { + val emailSent = AtomicBoolean(false) + val errorEmailAction = createEmailAction { emailSent.set(true) } + val reactionRunner = ReactionRunner(failingReaction, reactionState, errorEmailAction) + reactionRunner.run() + assertThat(emailSent.get(), equalTo(true)) + } + + @Test + fun `reaction runner sends email if reaction fails and the last state is successful`() { + val reactionState = createReactionState({ + of(object : AbstractState(true) { + override fun plainText() = "success" + }) + }) + val emailSent = AtomicBoolean(false) + val errorEmailAction = createEmailAction { emailSent.set(true) } + val reactionRunner = ReactionRunner(failingReaction, reactionState, errorEmailAction) + reactionRunner.run() + assertThat(emailSent.get(), equalTo(true)) + } + + @Test + fun `reaction runner does not send email if reaction fails and the last state is failed`() { + val reactionState = createReactionState({ of(FailedState()) }) + val emailSent = AtomicBoolean(false) + val errorEmailAction = createEmailAction { emailSent.set(true) } + val reactionRunner = ReactionRunner(failingReaction, reactionState, errorEmailAction) + reactionRunner.run() + assertThat(emailSent.get(), equalTo(false)) + } + + @Test + fun `reaction runner saves state when unsuccessful and last state does not exist`() { + val savedState = AtomicReference() + val reactionState = createReactionState(saveState = { _, state -> savedState.set(state) }) + val reactionRunner = ReactionRunner(failingReaction, reactionState, nullEmailAction) + reactionRunner.run() + assertThat(savedState.get(), isState(success = equalTo(false), failCount = equalTo(1))) + } + + @Test + fun `reaction runner saves state when unsuccessful and last state is failed`() { + val savedState = AtomicReference() + val failedState = FailedState().apply { setFailCount(12) } + val reactionState = createReactionState(lastState = { of(failedState) }, saveState = { _, state -> savedState.set(state) }) + val reactionRunner = ReactionRunner(failingReaction, reactionState, nullEmailAction) + reactionRunner.run() + assertThat(savedState.get(), isState(success = equalTo(false), failCount = equalTo(13))) + } + + private fun createReactionState(lastState: (reactionName: String) -> Optional = { empty() }, lastSuccessfulState: (reactionName: String) -> Optional = { empty() }, saveState: (reactionName: String, State) -> Unit = { reactionName, state -> }) = + ReactionState(object : StateManager(StateDirectory.of("")) { + override fun loadLastState(reactionName: String) = lastState(reactionName) + override fun loadLastSuccessfulState(reactionName: String) = lastSuccessfulState(reactionName) + override fun saveState(reactionName: String, state: State) = saveState(reactionName, state) + }, "Test Reaction") + + private fun createEmailAction(action: () -> Unit = {}) = object : EmailAction("test.test", "sender@test.test", "recipient@test.test") { + override fun execute(output: Output?) = action() + } + + private val reactionState = createReactionState() + private val failingReaction = object : Reaction("Test", null, null, null) { + override fun query() = Query { FailedState() } + } + private val nullEmailAction = createEmailAction { } + +} + +private fun isState(success: Matcher = equalTo(true), failCount: Matcher = equalTo(0)) = object : TypeSafeDiagnosingMatcher(State::class.java) { + override fun matchesSafely(item: State, mismatchDescription: Description): Boolean { + if (!success.matches(item.success())) { + mismatchDescription.appendText("success is ").appendValue(item.success()) + return false + } + if (!failCount.matches(item.failCount())) { + mismatchDescription.appendText("failCount is ").appendValue(item.failCount()) + return false + } + return true + } + + override fun describeTo(description: Description) { + description.appendText("state with success ").appendValue(success) + .appendText(" and failCount ").appendValue(failCount) + } +} -- 2.7.4