🐛 Fix NPE when post isn’t loaded
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / text / SoneMentionDetectorTest.kt
1 /**
2  * Sone - SoneMentionDetectorTest.kt - Copyright © 2019 David ‘Bombe’ Roden
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 package net.pterodactylus.sone.text
19
20 import com.google.common.eventbus.*
21 import net.pterodactylus.sone.core.event.*
22 import net.pterodactylus.sone.data.*
23 import net.pterodactylus.sone.database.*
24 import net.pterodactylus.sone.test.*
25 import org.hamcrest.MatcherAssert.*
26 import org.hamcrest.Matchers.*
27 import kotlin.test.*
28
29 /**
30  * Unit test for [SoneMentionDetector].
31  */
32 @Suppress("UnstableApiUsage")
33 class SoneMentionDetectorTest {
34
35         private val caughtExceptions = mutableListOf<Throwable>()
36         private val eventBus = EventBus { exception, _ -> caughtExceptions += exception }
37         private val soneProvider = TestSoneProvider()
38         private val postProvider = TestPostProvider()
39         private val soneTextParser = SoneTextParser(soneProvider, postProvider)
40         private val capturedFoundEvents = mutableListOf<MentionOfLocalSoneFoundEvent>()
41         private val capturedRemovedEvents = mutableListOf<MentionOfLocalSoneRemovedEvent>()
42         private val postReplyProvider = TestPostReplyProvider()
43
44         init {
45                 eventBus.register(SoneMentionDetector(eventBus, soneTextParser, postReplyProvider))
46                 eventBus.register(object : Any() {
47                         @Subscribe
48                         fun captureFoundEvent(mentionOfLocalSoneFoundEvent: MentionOfLocalSoneFoundEvent) {
49                                 capturedFoundEvents += mentionOfLocalSoneFoundEvent
50                         }
51
52                         @Subscribe
53                         fun captureRemovedEvent(event: MentionOfLocalSoneRemovedEvent) {
54                                 capturedRemovedEvents += event
55                         }
56                 })
57         }
58
59         @Test
60         fun `detector does not emit event on post that does not contain any sones`() {
61                 val post = createPost()
62                 eventBus.post(NewPostFoundEvent(post))
63                 assertThat(capturedFoundEvents, emptyIterable())
64         }
65
66         @Test
67         fun `detector does not emit event on post that does contain two remote sones`() {
68                 val post = createPost("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.")
69                 eventBus.post(NewPostFoundEvent(post))
70                 assertThat(capturedFoundEvents, emptyIterable())
71         }
72
73         @Test
74         fun `detector emits event on post that contains links to a remote and a local sone`() {
75                 val post = createPost("text mentions sone://${localSone1.id} and sone://${remoteSone2.id}.")
76                 eventBus.post(NewPostFoundEvent(post))
77                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
78         }
79
80         @Test
81         fun `detector emits one event on post that contains two links to the same local sone`() {
82                 val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone1.id}.")
83                 eventBus.post(NewPostFoundEvent(post))
84                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
85         }
86
87         @Test
88         fun `detector emits one event on post that contains links to two local sones`() {
89                 val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.")
90                 eventBus.post(NewPostFoundEvent(post))
91                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
92         }
93
94         @Test
95         fun `detector does not emit event for post by local sone`() {
96                 val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", localSone1)
97                 eventBus.post(NewPostFoundEvent(post))
98                 assertThat(capturedFoundEvents, emptyIterable())
99         }
100
101         @Test
102         fun `detector does not emit event for reply that contains no sones`() {
103                 val reply = emptyPostReply()
104                 eventBus.post(NewPostReplyFoundEvent(reply))
105                 assertThat(capturedFoundEvents, emptyIterable())
106         }
107
108         @Test
109         fun `detector does not emit event for reply that contains two links to remote sones`() {
110                 val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.")
111                 eventBus.post(NewPostReplyFoundEvent(reply))
112                 assertThat(capturedFoundEvents, emptyIterable())
113         }
114
115         @Test
116         fun `detector emits event on reply that contains links to a remote and a local sone`() {
117                 val post = createPost()
118                 val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${localSone1.id}.", post)
119                 eventBus.post(NewPostReplyFoundEvent(reply))
120                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
121         }
122
123         @Test
124         fun `detector emits one event on reply that contains two links to the same local sone`() {
125                 val post = createPost()
126                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone1.id}.", post)
127                 eventBus.post(NewPostReplyFoundEvent(reply))
128                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
129         }
130
131         @Test
132         fun `detector emits one event on reply that contains two links to local sones`() {
133                 val post = createPost()
134                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post)
135                 eventBus.post(NewPostReplyFoundEvent(reply))
136                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
137         }
138
139         @Test
140         fun `detector does not emit event for reply by local sone`() {
141                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", sone = localSone1)
142                 eventBus.post(NewPostReplyFoundEvent(reply))
143                 assertThat(capturedFoundEvents, emptyIterable())
144         }
145
146         @Test
147         fun `detector does not emit event for reply without post`() {
148                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post = null)
149                 eventBus.post(NewPostReplyFoundEvent(reply))
150                 assertThat(caughtExceptions, emptyIterable())
151                 assertThat(capturedFoundEvents, emptyIterable())
152         }
153
154         @Test
155         fun `detector does not emit removed event when a post without mention is removed`() {
156                 val post = createPost()
157                 eventBus.post(PostRemovedEvent(post))
158                 assertThat(capturedRemovedEvents, emptyIterable())
159         }
160
161         @Test
162         fun `detector does emit removed event when post with mention is removed`() {
163                 val post = createPost("sone://${localSone1.id}")
164                 eventBus.post(NewPostFoundEvent(post))
165                 eventBus.post(PostRemovedEvent(post))
166                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
167         }
168
169         @Test
170         fun `detector does not emit removed event when a post without mention is marked as known`() {
171                 val post = createPost()
172                 eventBus.post(MarkPostKnownEvent(post))
173                 assertThat(capturedRemovedEvents, emptyIterable())
174         }
175
176         @Test
177         fun `detector does emit removed event when post with mention is marked as known`() {
178                 val post = createPost("sone://${localSone1.id}")
179                 eventBus.post(NewPostFoundEvent(post))
180                 eventBus.post(MarkPostKnownEvent(post))
181                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
182         }
183
184         @Test
185         fun `detector does emit removed event when reply with mention is removed and no more mentions in that post exist`() {
186                 val post = createPost()
187                 val reply = emptyPostReply("sone://${localSone1.id}", post)
188                 postReplyProvider.postReplies[post.id] = listOf(reply)
189                 eventBus.post(NewPostReplyFoundEvent(reply))
190                 eventBus.post(PostReplyRemovedEvent(reply))
191                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
192         }
193
194         @Test
195         fun `detector does not emit removed event when reply with mention is removed and post mentions local sone`() {
196                 val post = createPost("sone://${localSone1.id}")
197                 val reply = emptyPostReply("sone://${localSone1.id}", post)
198                 eventBus.post(NewPostReplyFoundEvent(reply))
199                 eventBus.post(PostReplyRemovedEvent(reply))
200                 assertThat(capturedRemovedEvents, emptyIterable())
201         }
202
203         @Test
204         fun `detector does emit removed event when reply with mention is removed and post mentions local sone but is known`() {
205                 val post = createPost("sone://${localSone1.id}", known = true)
206                 val reply = emptyPostReply("sone://${localSone1.id}", post)
207                 eventBus.post(NewPostReplyFoundEvent(reply))
208                 eventBus.post(PostReplyRemovedEvent(reply))
209                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
210         }
211
212         @Test
213         fun `detector does not emit removed event when reply with mention is removed and post has other replies with mentions`() {
214                 val post = createPost()
215                 val reply1 = emptyPostReply("sone://${localSone1.id}", post)
216                 val reply2 = emptyPostReply("sone://${localSone1.id}", post)
217                 postReplyProvider.postReplies[post.id] = listOf(reply1, reply2)
218                 eventBus.post(NewPostReplyFoundEvent(reply1))
219                 eventBus.post(PostReplyRemovedEvent(reply1))
220                 assertThat(capturedRemovedEvents, emptyIterable())
221         }
222
223         @Test
224         fun `detector does emit removed event when reply with mention is removed and post has other replies with mentions which are known`() {
225                 val post = createPost()
226                 val reply1 = emptyPostReply("sone://${localSone1.id}", post)
227                 val reply2 = emptyPostReply("sone://${localSone1.id}", post, known = true)
228                 postReplyProvider.postReplies[post.id] = listOf(reply1, reply2)
229                 eventBus.post(NewPostReplyFoundEvent(reply1))
230                 eventBus.post(PostReplyRemovedEvent(reply1))
231                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
232         }
233
234 }
235
236 private class TestSoneProvider : SoneProvider {
237
238         override val sones: Collection<Sone> get() = remoteSones + localSones
239         override val localSones: Collection<Sone> get() = setOf(localSone1, localSone2)
240         override val remoteSones: Collection<Sone> get() = setOf(remoteSone1, remoteSone2)
241         override val soneLoader: (String) -> Sone? get() = this::getSone
242         override fun getSone(soneId: String): Sone? =
243                         localSones.firstOrNull { it.id == soneId } ?: remoteSones.firstOrNull { it.id == soneId }
244
245 }
246
247 private class TestPostProvider : PostProvider {
248
249         override fun getPost(postId: String): Post? = null
250         override fun getPosts(soneId: String): Collection<Post> = emptyList()
251         override fun getDirectedPosts(recipientId: String): Collection<Post> = emptyList()
252
253 }
254
255 private class TestPostReplyProvider : PostReplyProvider {
256
257         val replies = mutableMapOf<String, PostReply>()
258         val postReplies = mutableMapOf<String, List<PostReply>>()
259
260         override fun getPostReply(id: String) = replies[id]
261         override fun getReplies(postId: String) = postReplies[postId] ?: emptyList()
262
263 }