24d2335face1dad1949bb9a6684031ecd7abf31c
[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.base.*
21 import com.google.common.base.Optional.*
22 import com.google.common.eventbus.*
23 import net.pterodactylus.sone.core.event.*
24 import net.pterodactylus.sone.data.*
25 import net.pterodactylus.sone.database.*
26 import net.pterodactylus.sone.test.*
27 import org.hamcrest.MatcherAssert.*
28 import org.hamcrest.Matchers.*
29 import kotlin.test.*
30
31 /**
32  * Unit test for [SoneMentionDetector].
33  */
34 @Suppress("UnstableApiUsage")
35 class SoneMentionDetectorTest {
36
37         private val eventBus = EventBus()
38         private val soneProvider = TestSoneProvider()
39         private val postProvider = TestPostProvider()
40         private val soneTextParser = SoneTextParser(soneProvider, postProvider)
41         private val capturedFoundEvents = mutableListOf<MentionOfLocalSoneFoundEvent>()
42         private val capturedRemovedEvents = mutableListOf<MentionOfLocalSoneRemovedEvent>()
43         private val postReplyProvider = TestPostReplyProvider()
44
45         init {
46                 eventBus.register(SoneMentionDetector(eventBus, soneTextParser, postReplyProvider))
47                 eventBus.register(object : Any() {
48                         @Subscribe
49                         fun captureFoundEvent(mentionOfLocalSoneFoundEvent: MentionOfLocalSoneFoundEvent) {
50                                 capturedFoundEvents += mentionOfLocalSoneFoundEvent
51                         }
52
53                         @Subscribe
54                         fun captureRemovedEvent(event: MentionOfLocalSoneRemovedEvent) {
55                                 capturedRemovedEvents += event
56                         }
57                 })
58         }
59
60         @Test
61         fun `detector does not emit event on post that does not contain any sones`() {
62                 val post = createPost()
63                 eventBus.post(NewPostFoundEvent(post))
64                 assertThat(capturedFoundEvents, emptyIterable())
65         }
66
67         @Test
68         fun `detector does not emit event on post that does contain two remote sones`() {
69                 val post = createPost("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.")
70                 eventBus.post(NewPostFoundEvent(post))
71                 assertThat(capturedFoundEvents, emptyIterable())
72         }
73
74         @Test
75         fun `detector emits event on post that contains links to a remote and a local sone`() {
76                 val post = createPost("text mentions sone://${localSone1.id} and sone://${remoteSone2.id}.")
77                 eventBus.post(NewPostFoundEvent(post))
78                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
79         }
80
81         @Test
82         fun `detector emits one event on post that contains two links to the same local sone`() {
83                 val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone1.id}.")
84                 eventBus.post(NewPostFoundEvent(post))
85                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
86         }
87
88         @Test
89         fun `detector emits one event on post that contains links to two local sones`() {
90                 val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.")
91                 eventBus.post(NewPostFoundEvent(post))
92                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
93         }
94
95         @Test
96         fun `detector does not emit event for post by local sone`() {
97                 val post = createPost("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", localSone1)
98                 eventBus.post(NewPostFoundEvent(post))
99                 assertThat(capturedFoundEvents, emptyIterable())
100         }
101
102         @Test
103         fun `detector does not emit event for reply that contains no sones`() {
104                 val reply = emptyPostReply()
105                 eventBus.post(NewPostReplyFoundEvent(reply))
106                 assertThat(capturedFoundEvents, emptyIterable())
107         }
108
109         @Test
110         fun `detector does not emit event for reply that contains two links to remote sones`() {
111                 val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${remoteSone2.id}.")
112                 eventBus.post(NewPostReplyFoundEvent(reply))
113                 assertThat(capturedFoundEvents, emptyIterable())
114         }
115
116         @Test
117         fun `detector emits event on reply that contains links to a remote and a local sone`() {
118                 val post = createPost()
119                 val reply = emptyPostReply("text mentions sone://${remoteSone1.id} and sone://${localSone1.id}.", post)
120                 eventBus.post(NewPostReplyFoundEvent(reply))
121                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
122         }
123
124         @Test
125         fun `detector emits one event on reply that contains two links to the same local sone`() {
126                 val post = createPost()
127                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone1.id}.", post)
128                 eventBus.post(NewPostReplyFoundEvent(reply))
129                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
130         }
131
132         @Test
133         fun `detector emits one event on reply that contains two links to local sones`() {
134                 val post = createPost()
135                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", post)
136                 eventBus.post(NewPostReplyFoundEvent(reply))
137                 assertThat(capturedFoundEvents, contains(MentionOfLocalSoneFoundEvent(post)))
138         }
139
140         @Test
141         fun `detector does not emit event for reply by local sone`() {
142                 val reply = emptyPostReply("text mentions sone://${localSone1.id} and sone://${localSone2.id}.", sone = localSone1)
143                 eventBus.post(NewPostReplyFoundEvent(reply))
144                 assertThat(capturedFoundEvents, emptyIterable())
145         }
146
147         @Test
148         fun `detector does not emit removed event when a post without mention is removed`() {
149                 val post = createPost()
150                 eventBus.post(PostRemovedEvent(post))
151                 assertThat(capturedRemovedEvents, emptyIterable())
152         }
153
154         @Test
155         fun `detector does emit removed event when post with mention is removed`() {
156                 val post = createPost("sone://${localSone1.id}")
157                 eventBus.post(NewPostFoundEvent(post))
158                 eventBus.post(PostRemovedEvent(post))
159                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
160         }
161
162         @Test
163         fun `detector does not emit removed event when a post without mention is marked as known`() {
164                 val post = createPost()
165                 eventBus.post(MarkPostKnownEvent(post))
166                 assertThat(capturedRemovedEvents, emptyIterable())
167         }
168
169         @Test
170         fun `detector does emit removed event when post with mention is marked as known`() {
171                 val post = createPost("sone://${localSone1.id}")
172                 eventBus.post(NewPostFoundEvent(post))
173                 eventBus.post(MarkPostKnownEvent(post))
174                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
175         }
176
177         @Test
178         fun `detector does emit removed event when reply with mention is removed and no more mentions in that post exist`() {
179                 val post = createPost()
180                 val reply = emptyPostReply("sone://${localSone1.id}", post)
181                 postReplyProvider.postReplies[post.id] = listOf(reply)
182                 eventBus.post(NewPostReplyFoundEvent(reply))
183                 eventBus.post(PostReplyRemovedEvent(reply))
184                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
185         }
186
187         @Test
188         fun `detector does not emit removed event when reply with mention is removed and post mentions local sone`() {
189                 val post = createPost("sone://${localSone1.id}")
190                 val reply = emptyPostReply("sone://${localSone1.id}", post)
191                 eventBus.post(NewPostReplyFoundEvent(reply))
192                 eventBus.post(PostReplyRemovedEvent(reply))
193                 assertThat(capturedRemovedEvents, emptyIterable())
194         }
195
196         @Test
197         fun `detector does emit removed event when reply with mention is removed and post mentions local sone but is known`() {
198                 val post = createPost("sone://${localSone1.id}", known = true)
199                 val reply = emptyPostReply("sone://${localSone1.id}", post)
200                 eventBus.post(NewPostReplyFoundEvent(reply))
201                 eventBus.post(PostReplyRemovedEvent(reply))
202                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
203         }
204
205         @Test
206         fun `detector does not emit removed event when reply with mention is removed and post has other replies with mentions`() {
207                 val post = createPost()
208                 val reply1 = emptyPostReply("sone://${localSone1.id}", post)
209                 val reply2 = emptyPostReply("sone://${localSone1.id}", post)
210                 postReplyProvider.postReplies[post.id] = listOf(reply1, reply2)
211                 eventBus.post(NewPostReplyFoundEvent(reply1))
212                 eventBus.post(PostReplyRemovedEvent(reply1))
213                 assertThat(capturedRemovedEvents, emptyIterable())
214         }
215
216         @Test
217         fun `detector does emit removed event when reply with mention is removed and post has other replies with mentions which are known`() {
218                 val post = createPost()
219                 val reply1 = emptyPostReply("sone://${localSone1.id}", post)
220                 val reply2 = emptyPostReply("sone://${localSone1.id}", post, known = true)
221                 postReplyProvider.postReplies[post.id] = listOf(reply1, reply2)
222                 eventBus.post(NewPostReplyFoundEvent(reply1))
223                 eventBus.post(PostReplyRemovedEvent(reply1))
224                 assertThat(capturedRemovedEvents, contains(MentionOfLocalSoneRemovedEvent(post)))
225         }
226
227 }
228
229 private val remoteSone1 = createRemoteSone()
230 private val remoteSone2 = createRemoteSone()
231
232 private val localSone1 = createLocalSone()
233 private val localSone2 = createLocalSone()
234
235 private fun createPost(text: String = "", sone: Sone = remoteSone1, known: Boolean = false): Post.EmptyPost {
236         return object : Post.EmptyPost("post-id") {
237                 override fun getSone() = sone
238                 override fun getText() = text
239                 override fun isKnown() = known
240         }
241 }
242
243 private class TestSoneProvider : SoneProvider {
244
245         override val sones: Collection<Sone> get() = remoteSones + localSones
246         override val localSones: Collection<Sone> get() = setOf(localSone1, localSone2)
247         override val remoteSones: Collection<Sone> get() = setOf(remoteSone1, remoteSone2)
248         override val soneLoader: (String) -> Sone? get() = this::getSone
249         override fun getSone(soneId: String): Sone? =
250                         localSones.firstOrNull { it.id == soneId } ?: remoteSones.firstOrNull { it.id == soneId }
251
252 }
253
254 private class TestPostProvider : PostProvider {
255
256         override fun getPost(postId: String): Post? = null
257         override fun getPosts(soneId: String): Collection<Post> = emptyList()
258         override fun getDirectedPosts(recipientId: String): Collection<Post> = emptyList()
259
260 }
261
262 private class TestPostReplyProvider : PostReplyProvider {
263
264         val replies = mutableMapOf<String, PostReply>()
265         val postReplies = mutableMapOf<String, List<PostReply>>()
266
267         override fun getPostReply(id: String) = replies[id]
268         override fun getReplies(postId: String) = postReplies[postId] ?: emptyList()
269
270 }
271
272 private fun emptyPostReply(text: String = "", post: Post = createPost(), sone: Sone = remoteSone1, known: Boolean = false) = object : PostReply {
273         override val id = "reply-id"
274         override fun getSone() = sone
275         override fun getPostId() = post.id
276         override fun getPost(): Optional<Post> = of(post)
277         override fun getTime() = 1L
278         override fun getText() = text
279         override fun isKnown() = known
280         override fun setKnown(known: Boolean): PostReply = this
281 }