📄 Update copyright lines with correct filenames
[Sone.git] / src / test / kotlin / net / pterodactylus / sone / database / memory / MemoryDatabaseTest.kt
1 /*
2  * Sone - MemoryDatabaseTest.kt - Copyright Â© 2013–2019 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 = mock<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", absent()))
54                 assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", of(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", null, "album1", "album-description1"))
61                 assertThat(memoryDatabase.getAlbum("album2"), isAlbum("album2", null, "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 = mock<Album>()
127                 whenever(rootAlbum.id).thenReturn("root")
128                 whenever(rootAlbum.albums).thenReturn(listOf(firstAlbum, secondAlbum))
129                 whenever(sone.rootAlbum).thenReturn(rootAlbum)
130                 val firstImage = TestImageBuilder().withId("image1")
131                                 .build()
132                                 .modify()
133                                 .setSone(sone)
134                                 .setCreationTime(1000L)
135                                 .setKey("KSK@image1")
136                                 .setTitle("image1")
137                                 .setDescription("image-description1")
138                                 .setWidth(16)
139                                 .setHeight(9)
140                                 .update()
141                 val secondImage = TestImageBuilder().withId("image2")
142                                 .build()
143                                 .modify()
144                                 .setSone(sone)
145                                 .setCreationTime(2000L)
146                                 .setKey("KSK@image2")
147                                 .setTitle("image2")
148                                 .setDescription("image-description2")
149                                 .setWidth(32)
150                                 .setHeight(18)
151                                 .update()
152                 val thirdImage = TestImageBuilder().withId("image3")
153                                 .build()
154                                 .modify()
155                                 .setSone(sone)
156                                 .setCreationTime(3000L)
157                                 .setKey("KSK@image3")
158                                 .setTitle("image3")
159                                 .setDescription("image-description3")
160                                 .setWidth(48)
161                                 .setHeight(27)
162                                 .update()
163                 firstAlbum.addImage(firstImage)
164                 firstAlbum.addImage(thirdImage)
165                 secondAlbum.addImage(secondImage)
166                 memoryDatabase.storeSone(sone)
167         }
168
169         @Test
170         fun `stored and removed sone is not available`() {
171                 storeSone()
172                 memoryDatabase.removeSone(sone)
173                 assertThat(memoryDatabase.sones, empty())
174         }
175
176         @Test
177         fun `post recipients are detected correctly`() {
178                 val postWithRecipient = createPost(of(RECIPIENT_ID))
179                 memoryDatabase.storePost(postWithRecipient)
180                 val postWithoutRecipient = createPost(absent())
181                 memoryDatabase.storePost(postWithoutRecipient)
182                 assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), contains(postWithRecipient))
183         }
184
185         private fun createPost(recipient: Optional<String>): Post {
186                 val postWithRecipient = mock<Post>()
187                 whenever(postWithRecipient.id).thenReturn(randomUUID().toString())
188                 whenever(postWithRecipient.sone).thenReturn(sone)
189                 whenever(postWithRecipient.recipientId).thenReturn(recipient)
190                 return postWithRecipient
191         }
192
193         @Test
194         fun `post replies are managed correctly`() {
195                 val firstPost = createPost(absent())
196                 val firstPostFirstReply = createPostReply(firstPost, 1000L)
197                 val secondPost = createPost(absent())
198                 val secondPostFirstReply = createPostReply(secondPost, 1000L)
199                 val secondPostSecondReply = createPostReply(secondPost, 2000L)
200                 memoryDatabase.storePost(firstPost)
201                 memoryDatabase.storePost(secondPost)
202                 memoryDatabase.storePostReply(firstPostFirstReply)
203                 memoryDatabase.storePostReply(secondPostFirstReply)
204                 memoryDatabase.storePostReply(secondPostSecondReply)
205                 assertThat(memoryDatabase.getReplies(firstPost.id), contains(firstPostFirstReply))
206                 assertThat(memoryDatabase.getReplies(secondPost.id), contains(secondPostFirstReply, secondPostSecondReply))
207         }
208
209         private fun createPostReply(post: Post, time: Long): PostReply {
210                 val postReply = mock<PostReply>()
211                 whenever(postReply.id).thenReturn(randomUUID().toString())
212                 whenever(postReply.time).thenReturn(time)
213                 whenever(postReply.post).thenReturn(of(post))
214                 val postId = post.id
215                 whenever(postReply.postId).thenReturn(postId)
216                 return postReply
217         }
218
219         @Test
220         fun `test basic album functionality`() {
221                 val newAlbum = AlbumImpl(mock())
222                 assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue())
223                 memoryDatabase.storeAlbum(newAlbum)
224                 assertThat(memoryDatabase.getAlbum(newAlbum.id), equalTo<Album>(newAlbum))
225                 memoryDatabase.removeAlbum(newAlbum)
226                 assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue())
227         }
228
229         private fun initializeFriends() {
230                 whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/0/ID")).thenReturn(TestValue.from("Friend1"))
231                 whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/1/ID")).thenReturn(TestValue.from("Friend2"))
232                 whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/2/ID")).thenReturn(TestValue.from(null))
233         }
234
235         @Test
236         fun `friends are returned correctly`() {
237                 initializeFriends()
238                 whenever(sone.isLocal).thenReturn(true)
239                 val friends = memoryDatabase.getFriends(sone)
240                 assertThat(friends, containsInAnyOrder("Friend1", "Friend2"))
241         }
242
243         @Test
244         fun `friends are only loaded once from configuration`() {
245                 initializeFriends()
246                 whenever(sone.isLocal).thenReturn(true)
247                 memoryDatabase.getFriends(sone)
248                 memoryDatabase.getFriends(sone)
249                 verify(configuration, times(1)).getStringValue("Sone/$SONE_ID/Friends/0/ID")
250         }
251
252         @Test
253         fun `friends are only returned for local sones`() {
254                 val friends = memoryDatabase.getFriends(sone)
255                 assertThat(friends, emptyIterable<Any>())
256                 verify(configuration, never()).getStringValue("Sone/$SONE_ID/Friends/0/ID")
257         }
258
259         @Test
260         fun `checking for a friend returns true`() {
261                 initializeFriends()
262                 whenever(sone.isLocal).thenReturn(true)
263                 assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(true))
264         }
265
266         @Test
267         fun `checking for a friend that is not a friend returns false`() {
268                 initializeFriends()
269                 whenever(sone.isLocal).thenReturn(true)
270                 assertThat(memoryDatabase.isFriend(sone, "FriendX"), equalTo(false))
271         }
272
273         @Test
274         fun `checking for a friend of remote sone returns false`() {
275                 initializeFriends()
276                 assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(false))
277         }
278
279         private fun prepareConfigurationValues(): Map<String, Value<*>> =
280                         mutableMapOf<String, Value<*>>().also { configurationValues ->
281                                 whenever(configuration.getStringValue(anyString())).thenAnswer(createAndCacheValue(configurationValues))
282                                 whenever(configuration.getLongValue(anyString())).thenAnswer(createAndCacheValue(configurationValues))
283                         }
284
285         private fun createAndCacheValue(configurationValues: MutableMap<String, Value<*>>) =
286                         { invocation: InvocationOnMock ->
287                                 configurationValues[invocation[0]]
288                                                 ?: TestValue.from(null).also {
289                                                         configurationValues[invocation[0]] = it
290                                                 }
291                         }
292
293         @Test
294         fun `friend is added correctly to local sone`() {
295                 val configurationValues = prepareConfigurationValues()
296                 whenever(sone.isLocal).thenReturn(true)
297                 memoryDatabase.addFriend(sone, "Friend1")
298                 assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo<Value<*>>(TestValue.from("Friend1")))
299                 assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo<Value<*>>(TestValue.from(null)))
300         }
301
302         @Test
303         fun `friend is not added to remote sone`() {
304                 memoryDatabase.addFriend(sone, "Friend1")
305                 verify(configuration, never()).getStringValue(anyString())
306         }
307
308         @Test
309         fun `friend is removed correctly from local sone`() {
310                 val configurationValues = prepareConfigurationValues()
311                 whenever(sone.isLocal).thenReturn(true)
312                 memoryDatabase.addFriend(sone, "Friend1")
313                 memoryDatabase.removeFriend(sone, "Friend1")
314                 assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo<Value<*>>(TestValue.from(null)))
315                 assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo<Value<*>>(TestValue.from(null)))
316         }
317
318         @Test
319         fun `configuration is not written when a non-friend is removed`() {
320                 prepareConfigurationValues()
321                 whenever(sone.isLocal).thenReturn(true)
322                 memoryDatabase.removeFriend(sone, "Friend1")
323                 verify(configuration).getStringValue(anyString())
324         }
325
326         @Test
327         fun `sone following time is returned correctly`() {
328                 prepareConfigurationValues()
329                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "sone"
330                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
331                 assertThat(memoryDatabase.getFollowingTime("sone"), equalTo(1000L))
332         }
333
334         @Test
335         fun `null is returned when sone is not followed`() {
336                 prepareConfigurationValues()
337                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "otherSone"
338                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
339                 assertThat(memoryDatabase.getFollowingTime("sone"), nullValue())
340         }
341
342         @Test
343         fun `time is stored in configuration when a sone is followed`() {
344                 prepareConfigurationValues()
345                 whenever(sone.isLocal).thenReturn(true)
346                 memoryDatabase.addFriend(sone, "Friend")
347                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
348                 assertThat(System.currentTimeMillis() - configuration.getLongValue("SoneFollowingTimes/0/Time").value, lessThan(1000L))
349                 assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue())
350         }
351
352         @Test
353         fun `existing time is not overwritten when a sone is followed`() {
354                 prepareConfigurationValues()
355                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
356                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
357                 whenever(sone.isLocal).thenReturn(true)
358                 memoryDatabase.addFriend(sone, "Friend")
359                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
360                 assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L))
361                 assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue())
362         }
363
364         @Test
365         fun `unfollowing a sone removes the following time`() {
366                 prepareConfigurationValues()
367                 configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend"
368                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
369                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
370                 whenever(sone.isLocal).thenReturn(true)
371                 memoryDatabase.removeFriend(sone, "Friend")
372                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, nullValue())
373         }
374
375         @Test
376         fun `unfollowing a sone does not remove the following time if another local sone follows it`() {
377                 prepareConfigurationValues()
378                 configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend"
379                 configuration.getStringValue("Sone/other-sone/Friends/0/ID").value = "Friend"
380                 configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend"
381                 configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L
382                 val otherSone = mock<Sone>()
383                 whenever(otherSone.isLocal).thenReturn(true)
384                 whenever(otherSone.id).thenReturn("other-sone")
385                 memoryDatabase.getFriends(otherSone)
386                 whenever(sone.isLocal).thenReturn(true)
387                 memoryDatabase.removeFriend(sone, "Friend")
388                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend"))
389                 assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L))
390         }
391
392         @Test
393         fun `marking a post as known saves configuration`() {
394                 prepareConfigurationValues()
395                 val post = mock<Post>()
396                 whenever(post.id).thenReturn("post-id")
397                 memoryDatabase.setPostKnown(post, true)
398                 assertThat(configuration.getStringValue("KnownPosts/0/ID").value, equalTo("post-id"))
399                 assertThat(configuration.getStringValue("KnownPosts/1/ID").value, equalTo<Any>(null))
400         }
401
402         @Test
403         fun `marking a post reply as known saves configuration`() {
404                 prepareConfigurationValues()
405                 val postReply = mock<PostReply>()
406                 whenever(postReply.id).thenReturn("post-reply-id")
407                 memoryDatabase.setPostReplyKnown(postReply, true)
408                 assertThat(configuration.getStringValue("KnownReplies/0/ID").value, equalTo("post-reply-id"))
409                 assertThat(configuration.getStringValue("KnownReplies/1/ID").value, equalTo<Any>(null))
410         }
411
412 }
413
414 private const val SONE_ID = "sone"
415 private const val RECIPIENT_ID = "recipient"