✨ Only send an email if previous state was not a failure, too
[rhynodge.git] / src / test / kotlin / net / pterodactylus / rhynodge / engine / ReactionRunnerTest.kt
1 package net.pterodactylus.rhynodge.engine
2
3 import net.pterodactylus.rhynodge.Query
4 import net.pterodactylus.rhynodge.Reaction
5 import net.pterodactylus.rhynodge.State
6 import net.pterodactylus.rhynodge.actions.EmailAction
7 import net.pterodactylus.rhynodge.output.Output
8 import net.pterodactylus.rhynodge.states.AbstractState
9 import net.pterodactylus.rhynodge.states.FailedState
10 import net.pterodactylus.rhynodge.states.StateManager
11 import org.hamcrest.Description
12 import org.hamcrest.Matcher
13 import org.hamcrest.MatcherAssert.assertThat
14 import org.hamcrest.Matchers.equalTo
15 import org.hamcrest.TypeSafeDiagnosingMatcher
16 import org.junit.Test
17 import java.util.Optional
18 import java.util.Optional.empty
19 import java.util.Optional.of
20 import java.util.concurrent.atomic.AtomicBoolean
21 import java.util.concurrent.atomic.AtomicReference
22
23 /**
24  * Unit test for [ReactionRunner].
25  */
26 class ReactionRunnerTest {
27
28         @Test
29         fun `reaction runner sends email if reaction fails and there is no last state`() {
30                 val emailSent = AtomicBoolean(false)
31                 val errorEmailAction = createEmailAction { emailSent.set(true) }
32                 val reactionRunner = ReactionRunner(failingReaction, reactionState, errorEmailAction)
33                 reactionRunner.run()
34                 assertThat(emailSent.get(), equalTo(true))
35         }
36
37         @Test
38         fun `reaction runner sends email if reaction fails and the last state is successful`() {
39                 val reactionState = createReactionState({
40                         of<State>(object : AbstractState(true) {
41                                 override fun plainText() = "success"
42                         })
43                 })
44                 val emailSent = AtomicBoolean(false)
45                 val errorEmailAction = createEmailAction { emailSent.set(true) }
46                 val reactionRunner = ReactionRunner(failingReaction, reactionState, errorEmailAction)
47                 reactionRunner.run()
48                 assertThat(emailSent.get(), equalTo(true))
49         }
50
51         @Test
52         fun `reaction runner does not send email if reaction fails and the last state is failed`() {
53                 val reactionState = createReactionState({ of<State>(FailedState()) })
54                 val emailSent = AtomicBoolean(false)
55                 val errorEmailAction = createEmailAction { emailSent.set(true) }
56                 val reactionRunner = ReactionRunner(failingReaction, reactionState, errorEmailAction)
57                 reactionRunner.run()
58                 assertThat(emailSent.get(), equalTo(false))
59         }
60
61         @Test
62         fun `reaction runner saves state when unsuccessful and last state does not exist`() {
63                 val savedState = AtomicReference<State>()
64                 val reactionState = createReactionState(saveState = { _, state -> savedState.set(state) })
65                 val reactionRunner = ReactionRunner(failingReaction, reactionState, nullEmailAction)
66                 reactionRunner.run()
67                 assertThat(savedState.get(), isState(success = equalTo(false), failCount = equalTo(1)))
68         }
69
70         @Test
71         fun `reaction runner saves state when unsuccessful and last state is failed`() {
72                 val savedState = AtomicReference<State>()
73                 val failedState = FailedState().apply { setFailCount(12) }
74                 val reactionState = createReactionState(lastState = { of<State>(failedState) }, saveState = { _, state -> savedState.set(state) })
75                 val reactionRunner = ReactionRunner(failingReaction, reactionState, nullEmailAction)
76                 reactionRunner.run()
77                 assertThat(savedState.get(), isState(success = equalTo(false), failCount = equalTo(13)))
78         }
79
80         private fun createReactionState(lastState: (reactionName: String) -> Optional<State> = { empty<State>() }, lastSuccessfulState: (reactionName: String) -> Optional<State> = { empty<State>() }, saveState: (reactionName: String, State) -> Unit = { reactionName, state -> }) =
81                 ReactionState(object : StateManager(StateDirectory.of("")) {
82                         override fun loadLastState(reactionName: String) = lastState(reactionName)
83                         override fun loadLastSuccessfulState(reactionName: String) = lastSuccessfulState(reactionName)
84                         override fun saveState(reactionName: String, state: State) = saveState(reactionName, state)
85                 }, "Test Reaction")
86
87         private fun createEmailAction(action: () -> Unit = {}) = object : EmailAction("test.test", "sender@test.test", "recipient@test.test") {
88                 override fun execute(output: Output?) = action()
89         }
90
91         private val reactionState = createReactionState()
92         private val failingReaction = object : Reaction("Test", null, null, null) {
93                 override fun query() = Query { FailedState() }
94         }
95         private val nullEmailAction = createEmailAction { }
96
97 }
98
99 private fun isState(success: Matcher<Boolean> = equalTo(true), failCount: Matcher<Int> = equalTo(0)) = object : TypeSafeDiagnosingMatcher<State>(State::class.java) {
100         override fun matchesSafely(item: State, mismatchDescription: Description): Boolean {
101                 if (!success.matches(item.success())) {
102                         mismatchDescription.appendText("success is ").appendValue(item.success())
103                         return false
104                 }
105                 if (!failCount.matches(item.failCount())) {
106                         mismatchDescription.appendText("failCount is ").appendValue(item.failCount())
107                         return false
108                 }
109                 return true
110         }
111
112         override fun describeTo(description: Description) {
113                 description.appendText("state with success ").appendValue(success)
114                         .appendText(" and failCount ").appendValue(failCount)
115         }
116 }