Add test for storing IDs on known post replies
[Sone.git] / src / test / java / net / pterodactylus / sone / database / memory / MemoryDatabaseTest.java
1 /*
2  * Sone - MemoryDatabaseTest.java - 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 static com.google.common.base.Optional.of;
21 import static java.util.Arrays.asList;
22 import static java.util.UUID.randomUUID;
23 import static net.pterodactylus.sone.test.Matchers.isAlbum;
24 import static net.pterodactylus.sone.test.Matchers.isImage;
25 import static net.pterodactylus.sone.test.Matchers.isPost;
26 import static net.pterodactylus.sone.test.Matchers.isPostReply;
27 import static org.hamcrest.CoreMatchers.is;
28 import static org.hamcrest.MatcherAssert.assertThat;
29 import static org.hamcrest.Matchers.contains;
30 import static org.hamcrest.Matchers.containsInAnyOrder;
31 import static org.hamcrest.Matchers.empty;
32 import static org.hamcrest.Matchers.emptyIterable;
33 import static org.hamcrest.Matchers.equalTo;
34 import static org.hamcrest.Matchers.lessThan;
35 import static org.hamcrest.Matchers.nullValue;
36 import static org.mockito.ArgumentMatchers.anyString;
37 import static org.mockito.Mockito.mock;
38 import static org.mockito.Mockito.never;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41
42 import java.util.Collection;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48
49 import net.pterodactylus.sone.data.Album;
50 import net.pterodactylus.sone.data.Image;
51 import net.pterodactylus.sone.data.Post;
52 import net.pterodactylus.sone.data.PostReply;
53 import net.pterodactylus.sone.data.Sone;
54 import net.pterodactylus.sone.data.impl.AlbumImpl;
55 import net.pterodactylus.sone.test.TestAlbumBuilder;
56 import net.pterodactylus.sone.test.TestImageBuilder;
57 import net.pterodactylus.sone.test.TestPostBuilder;
58 import net.pterodactylus.sone.test.TestPostReplyBuilder;
59 import net.pterodactylus.sone.test.TestValue;
60 import net.pterodactylus.util.config.Configuration;
61 import net.pterodactylus.util.config.ConfigurationException;
62 import net.pterodactylus.util.config.Value;
63
64 import com.google.common.base.Optional;
65 import org.hamcrest.CoreMatchers;
66 import org.junit.Before;
67 import org.junit.Test;
68 import org.mockito.invocation.InvocationOnMock;
69 import org.mockito.stubbing.Answer;
70
71 /**
72  * Tests for {@link MemoryDatabase}.
73  */
74 public class MemoryDatabaseTest {
75
76         private static final String SONE_ID = "sone";
77         private static final String RECIPIENT_ID = "recipient";
78         private final Configuration configuration = mock(Configuration.class);
79         private final MemoryDatabase memoryDatabase = new MemoryDatabase(configuration);
80         private final Sone sone = mock(Sone.class);
81
82         @Before
83         public void setupSone() {
84                 when(sone.getId()).thenReturn(SONE_ID);
85         }
86
87         @Test
88         public void storedSoneIsMadeAvailable() {
89                 Post firstPost = new TestPostBuilder().withId("post1")
90                                 .from(SONE_ID)
91                                 .withTime(1000L)
92                                 .withText("post1")
93                                 .build();
94                 Post secondPost = new TestPostBuilder().withId("post2")
95                                 .from(SONE_ID)
96                                 .withTime(2000L)
97                                 .withText("post2")
98                                 .to(RECIPIENT_ID)
99                                 .build();
100                 List<Post> posts = asList(firstPost, secondPost);
101                 when(sone.getPosts()).thenReturn(posts);
102                 PostReply firstPostFirstReply =
103                                 new TestPostReplyBuilder().withId("reply1")
104                                                 .from(SONE_ID)
105                                                 .to(firstPost.getId())
106                                                 .withTime(3000L)
107                                                 .withText("reply1")
108                                                 .build();
109                 PostReply firstPostSecondReply =
110                                 new TestPostReplyBuilder().withId("reply3")
111                                                 .from(RECIPIENT_ID)
112                                                 .to(firstPost.getId())
113                                                 .withTime(5000L)
114                                                 .withText("reply3")
115                                                 .build();
116                 PostReply secondPostReply =
117                                 new TestPostReplyBuilder().withId("reply2")
118                                                 .from(SONE_ID)
119                                                 .to(secondPost.getId())
120                                                 .withTime(4000L)
121                                                 .withText("reply2")
122                                                 .build();
123                 Set<PostReply> postReplies = new HashSet<>(
124                                 asList(firstPostFirstReply, firstPostSecondReply,
125                                                 secondPostReply));
126                 when(sone.getReplies()).thenReturn(postReplies);
127                 Album firstAlbum = new TestAlbumBuilder().withId("album1")
128                                 .by(sone)
129                                 .build()
130                                 .modify()
131                                 .setTitle("album1")
132                                 .setDescription("album-description1")
133                                 .update();
134                 Album secondAlbum = new TestAlbumBuilder().withId("album2").by(
135                                 sone).build().modify().setTitle("album2").setDescription(
136                                 "album-description2").update();
137                 Album thirdAlbum = new TestAlbumBuilder().withId("album3").by(
138                                 sone).build().modify().setTitle("album3").setDescription(
139                                 "album-description3").update();
140                 firstAlbum.addAlbum(thirdAlbum);
141                 Album rootAlbum = mock(Album.class);
142                 when(rootAlbum.getId()).thenReturn("root");
143                 when(rootAlbum.getAlbums()).thenReturn(
144                                 asList(firstAlbum, secondAlbum));
145                 when(sone.getRootAlbum()).thenReturn(rootAlbum);
146                 Image firstImage = new TestImageBuilder().withId("image1")
147                                 .build()
148                                 .modify()
149                                 .setSone(sone)
150                                 .setCreationTime(1000L)
151                                 .setKey("KSK@image1")
152                                 .setTitle("image1")
153                                 .setDescription("image-description1")
154                                 .setWidth(16)
155                                 .setHeight(9)
156                                 .update();
157                 Image secondImage = new TestImageBuilder().withId("image2")
158                                 .build()
159                                 .modify()
160                                 .setSone(sone)
161                                 .setCreationTime(2000L)
162                                 .setKey("KSK@image2")
163                                 .setTitle("image2")
164                                 .setDescription("image-description2")
165                                 .setWidth(32)
166                                 .setHeight(18)
167                                 .update();
168                 Image thirdImage = new TestImageBuilder().withId("image3")
169                                 .build()
170                                 .modify()
171                                 .setSone(sone)
172                                 .setCreationTime(3000L)
173                                 .setKey("KSK@image3")
174                                 .setTitle("image3")
175                                 .setDescription("image-description3")
176                                 .setWidth(48)
177                                 .setHeight(27)
178                                 .update();
179                 firstAlbum.addImage(firstImage);
180                 firstAlbum.addImage(thirdImage);
181                 secondAlbum.addImage(secondImage);
182                 memoryDatabase.storeSone(sone);
183                 assertThat(memoryDatabase.getPost("post1"),
184                                 isPost(firstPost.getId(), 1000L, "post1",
185                                                 Optional.<String>absent()));
186                 assertThat(memoryDatabase.getPost("post2"),
187                                 isPost(secondPost.getId(), 2000L, "post2", of(RECIPIENT_ID)));
188                 assertThat(memoryDatabase.getPost("post3"), nullValue());
189                 assertThat(memoryDatabase.getPostReply("reply1"),
190                                 isPostReply("reply1", "post1", 3000L, "reply1"));
191                 assertThat(memoryDatabase.getPostReply("reply2"),
192                                 isPostReply("reply2", "post2", 4000L, "reply2"));
193                 assertThat(memoryDatabase.getPostReply("reply3"),
194                                 isPostReply("reply3", "post1", 5000L, "reply3"));
195                 assertThat(memoryDatabase.getPostReply("reply4"), nullValue());
196                 assertThat(memoryDatabase.getAlbum("album1"),
197                                 isAlbum("album1", null, "album1", "album-description1"));
198                 assertThat(memoryDatabase.getAlbum("album2"),
199                                 isAlbum("album2", null, "album2", "album-description2"));
200                 assertThat(memoryDatabase.getAlbum("album3"),
201                                 isAlbum("album3", "album1", "album3", "album-description3"));
202                 assertThat(memoryDatabase.getAlbum("album4"), nullValue());
203                 assertThat(memoryDatabase.getImage("image1"),
204                                 isImage("image1", 1000L, "KSK@image1", "image1",
205                                                 "image-description1", 16, 9));
206                 assertThat(memoryDatabase.getImage("image2"),
207                                 isImage("image2", 2000L, "KSK@image2", "image2",
208                                                 "image-description2", 32, 18));
209                 assertThat(memoryDatabase.getImage("image3"),
210                                 isImage("image3", 3000L, "KSK@image3", "image3",
211                                                 "image-description3", 48, 27));
212                 assertThat(memoryDatabase.getImage("image4"), nullValue());
213         }
214
215         @Test
216         public void storedAndRemovedSoneIsNotAvailable() {
217                 storedSoneIsMadeAvailable();
218                 memoryDatabase.removeSone(sone);
219                 assertThat(memoryDatabase.getSones(), empty());
220         }
221
222         @Test
223         public void postRecipientsAreDetectedCorrectly() {
224                 Post postWithRecipient = createPost(of(RECIPIENT_ID));
225                 memoryDatabase.storePost(postWithRecipient);
226                 Post postWithoutRecipient = createPost(Optional.<String>absent());
227                 memoryDatabase.storePost(postWithoutRecipient);
228                 assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID),
229                                 contains(postWithRecipient));
230         }
231
232         private Post createPost(Optional<String> recipient) {
233                 Post postWithRecipient = mock(Post.class);
234                 when(postWithRecipient.getId()).thenReturn(randomUUID().toString());
235                 when(postWithRecipient.getSone()).thenReturn(sone);
236                 when(postWithRecipient.getRecipientId()).thenReturn(recipient);
237                 return postWithRecipient;
238         }
239
240         @Test
241         public void postRepliesAreManagedCorrectly() {
242                 Post firstPost = createPost(Optional.<String>absent());
243                 PostReply firstPostFirstReply = createPostReply(firstPost, 1000L);
244                 Post secondPost = createPost(Optional.<String>absent());
245                 PostReply secondPostFirstReply = createPostReply(secondPost, 1000L);
246                 PostReply secondPostSecondReply = createPostReply(secondPost, 2000L);
247                 memoryDatabase.storePost(firstPost);
248                 memoryDatabase.storePost(secondPost);
249                 memoryDatabase.storePostReply(firstPostFirstReply);
250                 memoryDatabase.storePostReply(secondPostFirstReply);
251                 memoryDatabase.storePostReply(secondPostSecondReply);
252                 assertThat(memoryDatabase.getReplies(firstPost.getId()),
253                                 contains(firstPostFirstReply));
254                 assertThat(memoryDatabase.getReplies(secondPost.getId()),
255                                 contains(secondPostFirstReply, secondPostSecondReply));
256         }
257
258         private PostReply createPostReply(Post post, long time) {
259                 PostReply postReply = mock(PostReply.class);
260                 when(postReply.getId()).thenReturn(randomUUID().toString());
261                 when(postReply.getTime()).thenReturn(time);
262                 when(postReply.getPost()).thenReturn(of(post));
263                 final String postId = post.getId();
264                 when(postReply.getPostId()).thenReturn(postId);
265                 return postReply;
266         }
267
268         @Test
269         public void testBasicAlbumFunctionality() {
270                 Album newAlbum = new AlbumImpl(mock(Sone.class));
271                 assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue());
272                 memoryDatabase.storeAlbum(newAlbum);
273                 assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(newAlbum));
274                 memoryDatabase.removeAlbum(newAlbum);
275                 assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue());
276         }
277
278         private void initializeFriends() {
279                 when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/0/ID")).thenReturn(
280                                 TestValue.from("Friend1"));
281                 when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/1/ID")).thenReturn(
282                                 TestValue.from("Friend2"));
283                 when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/2/ID")).thenReturn(
284                                 TestValue.<String>from(null));
285         }
286
287         @Test
288         public void friendsAreReturnedCorrectly() {
289                 initializeFriends();
290                 when(sone.isLocal()).thenReturn(true);
291                 Collection<String> friends = memoryDatabase.getFriends(sone);
292                 assertThat(friends, containsInAnyOrder("Friend1", "Friend2"));
293         }
294
295         @Test
296         public void friendsAreOnlyLoadedOnceFromConfiguration() {
297                 friendsAreReturnedCorrectly();
298                 memoryDatabase.getFriends(sone);
299                 verify(configuration).getStringValue("Sone/" + SONE_ID + "/Friends/0/ID");
300         }
301
302         @Test
303         public void friendsAreOnlyReturnedForLocalSones() {
304                 Collection<String> friends = memoryDatabase.getFriends(sone);
305                 assertThat(friends, emptyIterable());
306                 verify(configuration, never()).getStringValue("Sone/" + SONE_ID + "/Friends/0/ID");
307         }
308
309         @Test
310         public void checkingForAFriendReturnsTrue() {
311                 initializeFriends();
312                 when(sone.isLocal()).thenReturn(true);
313                 assertThat(memoryDatabase.isFriend(sone, "Friend1"), is(true));
314         }
315
316         @Test
317         public void checkingForAFriendThatIsNotAFriendReturnsFalse() {
318                 initializeFriends();
319                 when(sone.isLocal()).thenReturn(true);
320                 assertThat(memoryDatabase.isFriend(sone, "FriendX"), is(false));
321         }
322
323         @Test
324         public void checkingForAFriendOfRemoteSoneReturnsFalse() {
325                 initializeFriends();
326                 assertThat(memoryDatabase.isFriend(sone, "Friend1"), is(false));
327         }
328
329         private Map<String, Value<?>> prepareConfigurationValues() {
330                 final Map<String, Value<?>> configurationValues = new HashMap<>();
331                 when(configuration.getStringValue(anyString())).thenAnswer(new Answer<Value<String>>() {
332                         @Override
333                         public Value<String> answer(InvocationOnMock invocation) throws Throwable {
334                                 Value<?> value = configurationValues.get(invocation.<String>getArgument(0));
335                                 if (value == null) {
336                                         value = TestValue.from(null);
337                                         configurationValues.put(invocation.<String>getArgument(0), value);
338                                 }
339                                 return (Value<String>) value;
340                         }
341                 });
342                 when(configuration.getLongValue(anyString())).thenAnswer(new Answer<Value<Long>>() {
343                         @Override
344                         public Value<Long> answer(InvocationOnMock invocation) throws Throwable {
345                                 Value<?> value = configurationValues.get(invocation.<String>getArgument(0));
346                                 if (value == null) {
347                                         value = TestValue.from(null);
348                                         configurationValues.put(invocation.<String>getArgument(0), value);
349                                 }
350                                 return (Value<Long>) value;
351                         }
352                 });
353                 return configurationValues;
354         }
355
356         @Test
357         public void friendIsAddedCorrectlyToLocalSone() {
358                 Map<String, Value<?>> configurationValues = prepareConfigurationValues();
359                 when(sone.isLocal()).thenReturn(true);
360                 memoryDatabase.addFriend(sone, "Friend1");
361                 assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/0/ID"),
362                                 CoreMatchers.<Value<?>>is(TestValue.from("Friend1")));
363                 assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/1/ID"),
364                                 CoreMatchers.<Value<?>>is(TestValue.<String>from(null)));
365         }
366
367         @Test
368         public void friendIsNotAddedToRemoteSone() {
369                 memoryDatabase.addFriend(sone, "Friend1");
370                 verify(configuration, never()).getStringValue(anyString());
371         }
372
373         @Test
374         public void friendIsRemovedCorrectlyFromLocalSone() {
375                 Map<String, Value<?>> configurationValues = prepareConfigurationValues();
376                 when(sone.isLocal()).thenReturn(true);
377                 memoryDatabase.addFriend(sone, "Friend1");
378                 memoryDatabase.removeFriend(sone, "Friend1");
379                 assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/0/ID"),
380                                 CoreMatchers.<Value<?>>is(TestValue.<String>from(null)));
381                 assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/1/ID"),
382                                 CoreMatchers.<Value<?>>is(TestValue.<String>from(null)));
383         }
384
385         @Test
386         public void configurationIsNotWrittenWhenANonFriendIsRemoved() {
387                 prepareConfigurationValues();
388                 when(sone.isLocal()).thenReturn(true);
389                 memoryDatabase.removeFriend(sone, "Friend1");
390                 verify(configuration).getStringValue(anyString());
391         }
392
393         @Test
394         public void soneFollowingTimeIsReturnedCorrectly() throws ConfigurationException {
395                 prepareConfigurationValues();
396                 configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("sone");
397                 configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L);
398                 assertThat(memoryDatabase.getFollowingTime("sone"), equalTo(1000L));
399         }
400
401         @Test
402         public void nullisReturnedWhenSoneIsNotFollowed() throws ConfigurationException {
403                 prepareConfigurationValues();
404                 configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("otherSone");
405                 configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L);
406                 assertThat(memoryDatabase.getFollowingTime("sone"), nullValue());
407         }
408
409         @Test
410         public void timeIsStoredInConfigurationWhenASoneIsFollowed() throws ConfigurationException {
411                 prepareConfigurationValues();
412                 when(sone.isLocal()).thenReturn(true);
413                 memoryDatabase.addFriend(sone, "Friend");
414                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), equalTo("Friend"));
415                 assertThat(System.currentTimeMillis() - configuration.getLongValue("SoneFollowingTimes/0/Time").getValue(), lessThan(1000L));
416                 assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").getValue(), nullValue());
417         }
418
419         @Test
420         public void existingTimeIsNotOverwrittenWhenASoneIsFollowed() throws ConfigurationException {
421                 prepareConfigurationValues();
422                 configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("Friend");
423                 configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L);
424                 when(sone.isLocal()).thenReturn(true);
425                 memoryDatabase.addFriend(sone, "Friend");
426                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), equalTo("Friend"));
427                 assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").getValue(), equalTo(1000L));
428                 assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").getValue(), nullValue());
429         }
430
431         @Test
432         public void unfollowingASoneRemovesTheFollowingTime() throws ConfigurationException {
433                 prepareConfigurationValues();
434                 configuration.getStringValue("Sone/sone/Friends/0/ID").setValue("Friend");
435                 configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("Friend");
436                 configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L);
437                 when(sone.isLocal()).thenReturn(true);
438                 memoryDatabase.removeFriend(sone, "Friend");
439                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), nullValue());
440         }
441
442         @Test
443         public void unfollowingASoneDoesNotRemoveTheFollowingTimeIfAnotherLocalSoneFollowsIt() throws ConfigurationException {
444                 prepareConfigurationValues();
445                 configuration.getStringValue("Sone/sone/Friends/0/ID").setValue("Friend");
446                 configuration.getStringValue("Sone/other-sone/Friends/0/ID").setValue("Friend");
447                 configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("Friend");
448                 configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L);
449                 Sone otherSone = mock(Sone.class);
450                 when(otherSone.isLocal()).thenReturn(true);
451                 when(otherSone.getId()).thenReturn("other-sone");
452                 memoryDatabase.getFriends(otherSone);
453                 when(sone.isLocal()).thenReturn(true);
454                 memoryDatabase.removeFriend(sone, "Friend");
455                 assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), equalTo("Friend"));
456                 assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").getValue(), equalTo(1000L));
457         }
458
459         @Test
460         public void markingAPostAsKnownSavesConfiguration() throws ConfigurationException {
461                 prepareConfigurationValues();
462                 Post post = mock(Post.class);
463                 when(post.getId()).thenReturn("post-id");
464                 memoryDatabase.setPostKnown(post, true);
465                 assertThat(configuration.getStringValue("KnownPosts/0/ID").getValue(), equalTo("post-id"));
466                 assertThat(configuration.getStringValue("KnownPosts/1/ID").getValue(), equalTo(null));
467         }
468
469         @Test
470         public void markingAPostReplyAsKnownSavesConfiguration() throws ConfigurationException {
471                 prepareConfigurationValues();
472                 PostReply postReply = mock(PostReply.class);
473                 when(postReply.getId()).thenReturn("post-reply-id");
474                 memoryDatabase.setPostReplyKnown(postReply, true);
475                 assertThat(configuration.getStringValue("KnownReplies/0/ID").getValue(), equalTo("post-reply-id"));
476                 assertThat(configuration.getStringValue("KnownReplies/1/ID").getValue(), equalTo(null));
477         }
478
479 }