🔥 Remove setKnown() method from replies
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / database / memory / MemoryDatabaseTest.kt
1 /*
2  * Sone - MemoryDatabaseTest.kt - Copyright Â© 2013–2020 David 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.database.memory
19
20 import com.google.common.base.*
21 import com.google.common.base.Optional.*
22 import net.pterodactylus.sone.data.*
23 import net.pterodactylus.sone.data.impl.*
24 import net.pterodactylus.sone.test.*
25 import net.pterodactylus.sone.test.Matchers.*
26 import net.pterodactylus.util.config.*
27 import org.hamcrest.MatcherAssert.*
28 import org.hamcrest.Matchers.*
29 import org.mockito.ArgumentMatchers.anyString
30 import org.mockito.Mockito.*
31 import org.mockito.invocation.*
32 import java.util.Arrays.*
33 import java.util.UUID.*
34 import kotlin.test.*
35
36 /**
37  * Tests for [MemoryDatabase].
38  */
39 class MemoryDatabaseTest {
40
41         private val configuration = deepMock<Configuration>()
42         private val memoryDatabase = MemoryDatabase(configuration)
43         private val sone = mock<Sone>()
44
45         @BeforeTest
46         fun setupSone() {
47                 whenever(sone.id).thenReturn(SONE_ID)
48         }
49
50         @Test
51         fun `stored sone is made available`() {
52                 storeSone()
53                 assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", null))
54                 assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", RECIPIENT_ID))
55                 assertThat(memoryDatabase.getPost("post3"), nullValue())
56                 assertThat(memoryDatabase.getPostReply("reply1"), isPostReply("reply1", "post1", 3000L, "reply1"))
57                 assertThat(memoryDatabase.getPostReply("reply2"), isPostReply("reply2", "post2", 4000L, "reply2"))
58                 assertThat(memoryDatabase.getPostReply("reply3"), isPostReply("reply3", "post1", 5000L, "reply3"))
59                 assertThat(memoryDatabase.getPostReply("reply4"), nullValue())
60                 assertThat(memoryDatabase.getAlbum("album1"), isAlbum("album1", "root", "album1", "album-description1"))
61                 assertThat(memoryDatabase.getAlbum("album2"), isAlbum("album2", "root", "album2", "album-description2"))
62                 assertThat(memoryDatabase.getAlbum("album3"), isAlbum("album3", "album1", "album3", "album-description3"))
63                 assertThat(memoryDatabase.getAlbum("album4"), nullValue())
64                 assertThat(memoryDatabase.getImage("image1"), isImage("image1", 1000L, "KSK@image1", "image1", "image-description1", 16, 9))
65                 assertThat(memoryDatabase.getImage("image2"), isImage("image2", 2000L, "KSK@image2", "image2", "image-description2", 32, 18))
66                 assertThat(memoryDatabase.getImage("image3"), isImage("image3", 3000L, "KSK@image3", "image3", "image-description3", 48, 27))
67                 assertThat(memoryDatabase.getImage("image4"), nullValue())
68         }
69
70         private fun storeSone() {
71                 val firstPost = TestPostBuilder().withId("post1")
72                                 .from(SONE_ID)
73                                 .withTime(1000L)
74                                 .withText("post1")
75                                 .build()
76                 val secondPost = TestPostBuilder().withId("post2")
77                                 .from(SONE_ID)
78                                 .withTime(2000L)
79                                 .withText("post2")
80                                 .to(RECIPIENT_ID)
81                                 .build()
82                 val posts = asList(firstPost, secondPost)
83                 whenever(sone.posts).thenReturn(posts)
84                 val firstPostFirstReply = TestPostReplyBuilder().withId("reply1")
85                                 .from(SONE_ID)
86                                 .to(firstPost.id)
87                                 .withTime(3000L)
88                                 .withText("reply1")
89                                 .build()
90                 val firstPostSecondReply = TestPostReplyBuilder().withId("reply3")
91                                 .from(RECIPIENT_ID)
92                                 .to(firstPost.id)
93                                 .withTime(5000L)
94                                 .withText("reply3")
95                                 .build()
96                 val secondPostReply = TestPostReplyBuilder().withId("reply2")
97                                 .from(SONE_ID)
98                                 .to(secondPost.id)
99                                 .withTime(4000L)
100                                 .withText("reply2")
101                                 .build()
102                 val postReplies = setOf(firstPostFirstReply, firstPostSecondReply, secondPostReply)
103                 whenever(sone.replies).thenReturn(postReplies)
104                 val firstAlbum = TestAlbumBuilder().withId("album1")
105                                 .by(sone)
106                                 .build()
107                                 .modify()
108                                 .setTitle("album1")
109                                 .setDescription("album-description1")
110                                 .update()
111                 val secondAlbum = TestAlbumBuilder().withId("album2")
112                                 .by(sone)
113                                 .build()
114                                 .modify()
115                                 .setTitle("album2")
116                                 .setDescription("album-description2")
117                                 .update()
118                 val thirdAlbum = TestAlbumBuilder().withId("album3")
119                                 .by(sone)
120                                 .build()
121                                 .modify()
122                                 .setTitle("album3")
123                                 .setDescription("album-description3")
124                                 .update()
125                 firstAlbum.addAlbum(thirdAlbum)
126                 val rootAlbum = AlbumImpl(sone, "root").also {
127                         it.addAlbum(firstAlbum)
128                         it.addAlbum(secondAlbum)
129                 }
130                 whenever(sone.rootAlbum).thenReturn(rootAlbum)
131                 val firstImage = TestImageBuilder().withId("image1")
132                                 .build()
133                                 .modify()
134                                 .setSone(sone)
135                                 .setCreationTime(1000L)
136                                 .setKey("KSK@image1")
137                                 .setTitle("image1")
138                                 .setDescription("image-description1")
139                                 .setWidth(16)
140                                 .setHeight(9)
141                                 .update()
142                 val secondImage = TestImageBuilder().withId("image2")
143                                 .build()
144                                 .modify()
145                                 .setSone(sone)
146                                 .setCreationTime(2000L)
147                                 .setKey("KSK@image2")
148                                 .setTitle("image2")
149                                 .setDescription("image-description2")
150                                 .setWidth(32)
151                                 .setHeight(18)
152                                 .update()
153                 val thirdImage = TestImageBuilder().withId("image3")
154                                 .build()
155                                 .modify()
156                                 .setSone(sone)
157                                 .setCreationTime(3000L)
158                                 .setKey("KSK@image3")
159                                 .setTitle("image3")
160                                 .setDescription("image-description3")
161                                 .setWidth(48)
162                                 .setHeight(27)
163                                 .update()
164                 firstAlbum.addImage(firstImage)
165                 firstAlbum.addImage(thirdImage)
166                 secondAlbum.addImage(secondImage)
167                 memoryDatabase.storeSone(sone)
168         }
169
170         @Test
171         fun `stored and removed sone is not available`() {
172                 storeSone()
173                 memoryDatabase.removeSone(sone)
174                 assertThat(memoryDatabase.sones, empty())
175         }
176
177         @Test
178         fun `post recipients are detected correctly`() {
179                 val postWithRecipient = createPost(of(RECIPIENT_ID))
180                 memoryDatabase.storePost(postWithRecipient)
181                 val postWithoutRecipient = createPost(absent())
182                 memoryDatabase.storePost(postWithoutRecipient)
183                 assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), contains(postWithRecipient))
184         }
185
186         private fun createPost(recipient: Optional<String>): Post {
187                 val postWithRecipient = mock<Post>()
188                 whenever(postWithRecipient.id).thenReturn(randomUUID().toString())
189                 whenever(postWithRecipient.sone).thenReturn(sone)
190                 whenever(postWithRecipient.recipientId).thenReturn(recipient)
191                 return postWithRecipient
192         }
193
194         @Test
195         fun `post replies are managed correctly`() {
196                 val firstPost = createPost(absent())
197                 val firstPostFirstReply = createPostReply(firstPost, 1000L)
198                 val secondPost = createPost(absent())
199                 val secondPostFirstReply = createPostReply(secondPost, 1000L)
200                 val secondPostSecondReply = createPostReply(secondPost, 2000L)
201                 memoryDatabase.storePost(firstPost)
202                 memoryDatabase.storePost(secondPost)
203                 memoryDatabase.storePostReply(firstPostFirstReply)
204                 memoryDatabase.storePostReply(secondPostFirstReply)
205                 memoryDatabase.storePostReply(secondPostSecondReply)
206                 assertThat(memoryDatabase.getReplies(firstPost.id), contains(firstPostFirstReply))
207                 assertThat(memoryDatabase.getReplies(secondPost.id), contains(secondPostFirstReply, secondPostSecondReply))
208         }
209
210         private fun createPostReply(post: Post, time: Long): PostReply {
211                 val postReply = mock<PostReply>()
212                 whenever(postReply.id).thenReturn(randomUUID().toString())
213                 whenever(postReply.time).thenReturn(time)
214                 whenever(postReply.post).thenReturn(of(post))
215                 val postId = post.id
216                 whenever(postReply.postId).thenReturn(postId)
217                 return postReply
218         }
219
220         @Test
221         fun `test basic album functionality`() {
222                 val newAlbum = AlbumImpl(mock())
223                 assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue())
224                 memoryDatabase.storeAlbum(newAlbum)
225                 assertThat(memoryDatabase.getAlbum(newAlbum.id), equalTo<Album>(newAlbum))
226                 memoryDatabase.removeAlbum(newAlbum)
227                 assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue())
228         }
229
230         private fun initializeFriends() {
231                 whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/0/ID")).thenReturn(TestValue.from("Friend1"))
232                 whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/1/ID")).thenReturn(TestValue.from("Friend2"))
233                 whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/2/ID")).thenReturn(TestValue.from(null))
234         }
235
236         @Test
237         fun `friends are returned correctly`() {
238                 initializeFriends()
239                 whenever(sone.isLocal).thenReturn(true)
240                 val friends = memoryDatabase.getFriends(sone)
241                 assertThat(friends, containsInAnyOrder("Friend1", "Friend2"))
242         }
243
244         @Test
245         fun `friends are only loaded once from configuration`() {
246                 initializeFriends()
247                 whenever(sone.isLocal).thenReturn(true)
248                 memoryDatabase.getFriends(sone)
249                 memoryDatabase.getFriends(sone)
250                 verify(configuration, times(1)).getStringValue("Sone/$SONE_ID/Friends/0/ID")
251         }
252
253         @Test
254         fun `friends are only returned for local sones`() {
255                 val friends = memoryDatabase.getFriends(sone)
256                 assertThat(friends, emptyIterable<Any>())
257                 verify(configuration, never()).getStringValue("Sone/$SONE_ID/Friends/0/ID")
258         }
259
260         @Test
261         fun `checking for a friend returns true`() {
262                 initializeFriends()
263                 whenever(sone.isLocal).thenReturn(true)
264                 assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(true))
265         }
266
267         @Test
268         fun `checking for a friend that is not a friend returns false`() {
269                 initializeFriends()
270                 whenever(sone.isLocal).thenReturn(true)
271                 assertThat(memoryDatabase.isFriend(sone, "FriendX"), equalTo(false))
272         }
273
274         @Test
275         fun `checking for a friend of remote sone returns false`() {
276                 initializeFriends()
277                 assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(false))
278         }
279
280         private fun prepareConfigurationValues(): Map<String, Value<*>> =
281                         mutableMapOf<String, Value<*>>().also { configurationValues ->
282                                 whenever(configuration.getStringValue(anyString())).thenAnswer(createAndCacheValue(configurationValues))
283                                 whenever(configuration.getLongValue(anyString())).thenAnswer(createAndCacheValue(configurationValues))
284                         }
285
286         private fun createAndCacheValue(configurationValues: MutableMap<String, Value<*>>) =
287                         { invocation: InvocationOnMock ->
288                                 configurationValues[invocation[0]]
289                                                 ?: TestValue.from(null).also {
290                                                         configurationValues[invocation[0]] = it
291                                                 }
292                         }
293
294         @Test
295         fun `friend is added correctly to local sone`() {
296                 val configurationValues = prepareConfigurationValues()
297                 whenever(sone.isLocal).thenReturn(true)
298                 memoryDatabase.addFriend(sone, "Friend1")
299                 assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo<Value<*>>(TestValue.from("Friend1")))
300                 assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo<Value<*>>(TestValue.from(null)))
301         }
302
303         @Test
304         fun `friend is not added to remote sone`() {
305                 memoryDatabase.addFriend(sone, "Friend1")
306                 verify(configuration, never()).getStringValue(anyString())
307         }
308
309         @Test
310         fun `friend is removed correctly from local sone`() {
311                 val configurationValues = prepareConfigurationValues()
312                 whenever(sone.isLocal).thenReturn(true)
313                 memoryDatabase.addFriend(sone, "Friend1")
314                 memoryDatabase.removeFriend(sone, "Friend1")
315                 assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo<Value<*>>(TestValue.from(null)))
316                 assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo<Value<*>>(TestValue.from(null)))
317         }
318
319         @Test
320         fun `configuration is not written when a non-friend is removed`() {
321                 prepareConfigurationValues()
322                 whenever(sone.isLocal).thenReturn(true)
323                 memoryDatabase.removeFriend(sone, "Friend1")
324                 verify(configuration).getStringValue(anyString())
325         }
326
327         @Test
328         fun `sone following time is returned correctly`() {
329                 prepareConfigurationValues()
330                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "sone"
331                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
332                 assertThat(memoryDatabase.getFollowingTime("sone"), equalTo(1000L))
333         }
334
335         @Test
336         fun `null is returned when sone is not followed`() {
337                 prepareConfigurationValues()
338                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "otherSone"
339                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
340                 assertThat(memoryDatabase.getFollowingTime("sone"), nullValue())
341         }
342
343         @Test
344         fun `time is stored in configuration when a sone is followed`() {
345                 prepareConfigurationValues()
346                 whenever(sone.isLocal).thenReturn(true)
347                 memoryDatabase.addFriend(sone, "Friend")
348                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
349                 assertThat(System.currentTimeMillis() - configuration.getLongValue("SoneFollowingTimes/0/Time").value, lessThan(1000L))
350                 assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue())
351         }
352
353         @Test
354         fun `existing time is not overwritten when a sone is followed`() {
355                 prepareConfigurationValues()
356                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
357                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
358                 whenever(sone.isLocal).thenReturn(true)
359                 memoryDatabase.addFriend(sone, "Friend")
360                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
361                 assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L))
362                 assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue())
363         }
364
365         @Test
366         fun `unfollowing a sone removes the following time`() {
367                 prepareConfigurationValues()
368                 configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend"
369                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
370                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
371                 whenever(sone.isLocal).thenReturn(true)
372                 memoryDatabase.removeFriend(sone, "Friend")
373                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, nullValue())
374         }
375
376         @Test
377         fun `unfollowing a sone does not remove the following time if another local sone follows it`() {
378                 prepareConfigurationValues()
379                 configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend"
380                 configuration.getStringValue("Sone/other-sone/Friends/0/ID").value = "Friend"
381                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
382                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
383                 val otherSone = mock<Sone>()
384                 whenever(otherSone.isLocal).thenReturn(true)
385                 whenever(otherSone.id).thenReturn("other-sone")
386                 memoryDatabase.getFriends(otherSone)
387                 whenever(sone.isLocal).thenReturn(true)
388                 memoryDatabase.removeFriend(sone, "Friend")
389                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
390                 assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L))
391         }
392
393         @Test
394         fun `marking a post as known saves configuration`() {
395                 prepareConfigurationValues()
396                 val post = mock<Post>()
397                 whenever(post.id).thenReturn("post-id")
398                 memoryDatabase.setPostKnown(post, true)
399                 assertThat(configuration.getStringValue("KnownPosts/0/ID").value, equalTo("post-id"))
400                 assertThat(configuration.getStringValue("KnownPosts/1/ID").value, equalTo<Any>(null))
401         }
402
403         @Test
404         fun `marking a post reply as known saves configuration`() {
405                 prepareConfigurationValues()
406                 val postReply = mock<PostReply>()
407                 whenever(postReply.id).thenReturn("post-reply-id")
408                 memoryDatabase.setPostReplyKnown(postReply)
409                 assertThat(configuration.getStringValue("KnownReplies/0/ID").value, equalTo("post-reply-id"))
410                 assertThat(configuration.getStringValue("KnownReplies/1/ID").value, equalTo<Any>(null))
411         }
412
413         @Test
414         @Dirty("the rate limiter should be mocked")
415         fun `saving the database twice in a row only saves it once`() {
416                 memoryDatabase.save()
417                 memoryDatabase.save()
418                 verify(configuration.getStringValue("KnownPosts/0/ID"), times(1)).value = null
419         }
420
421         @Test
422         @Dirty("the rate limiter should be mocked")
423         fun `setting posts as knows twice in a row only saves the database once`() {
424                 prepareConfigurationValues()
425                 val post = mock<Post>()
426                 whenever(post.id).thenReturn("post-id")
427                 memoryDatabase.setPostKnown(post, true)
428                 memoryDatabase.setPostKnown(post, true)
429                 verify(configuration, times(1)).getStringValue("KnownPosts/1/ID")
430         }
431
432         @Test
433         @Dirty("the rate limiter should be mocked")
434         fun `setting post replies as knows twice in a row only saves the database once`() {
435                 prepareConfigurationValues()
436                 val postReply = mock<PostReply>()
437                 whenever(postReply.id).thenReturn("post-reply-id")
438                 memoryDatabase.setPostReplyKnown(postReply)
439                 memoryDatabase.setPostReplyKnown(postReply)
440                 verify(configuration, times(1)).getStringValue("KnownReplies/1/ID")
441         }
442
443 }
444
445 private const val SONE_ID = "sone"
446 private const val RECIPIENT_ID = "recipient"