From: David ‘Bombe’ Roden Date: Wed, 26 Jun 2019 14:50:03 +0000 (+0200) Subject: 🎨 Replace MemoryDatabaseTest with Kotlin version X-Git-Tag: v79^2~3 X-Git-Url: https://git.pterodactylus.net/?p=Sone.git;a=commitdiff_plain;h=a697ce78fc213bd83c696507cc66796610fd189c 🎨 Replace MemoryDatabaseTest with Kotlin version --- diff --git a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt index d7e84e5..4e960ec 100644 --- a/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt +++ b/src/main/java/net/pterodactylus/sone/database/memory/MemoryDatabase.kt @@ -281,7 +281,7 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio protected fun isPostKnown(post: Post) = readLock.withLock { post.id in knownPosts } - protected fun setPostKnown(post: Post, known: Boolean): Unit = + fun setPostKnown(post: Post, known: Boolean): Unit = writeLock.withLock { if (known) knownPosts.add(post.id) @@ -292,7 +292,7 @@ class MemoryDatabase @Inject constructor(private val configuration: Configuratio protected fun isPostReplyKnown(postReply: PostReply) = readLock.withLock { postReply.id in knownPostReplies } - protected fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit = + fun setPostReplyKnown(postReply: PostReply, known: Boolean): Unit = writeLock.withLock { if (known) knownPostReplies.add(postReply.id) diff --git a/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java b/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java deleted file mode 100644 index 60c8c53..0000000 --- a/src/test/java/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Sone - MemoryDatabaseTest.java - Copyright © 2013–2019 David Roden - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package net.pterodactylus.sone.database.memory; - -import static com.google.common.base.Optional.of; -import static java.util.Arrays.asList; -import static java.util.UUID.randomUUID; -import static net.pterodactylus.sone.test.Matchers.isAlbum; -import static net.pterodactylus.sone.test.Matchers.isImage; -import static net.pterodactylus.sone.test.Matchers.isPost; -import static net.pterodactylus.sone.test.Matchers.isPostReply; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.emptyIterable; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.nullValue; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import net.pterodactylus.sone.data.Album; -import net.pterodactylus.sone.data.Image; -import net.pterodactylus.sone.data.Post; -import net.pterodactylus.sone.data.PostReply; -import net.pterodactylus.sone.data.Sone; -import net.pterodactylus.sone.data.impl.AlbumImpl; -import net.pterodactylus.sone.test.TestAlbumBuilder; -import net.pterodactylus.sone.test.TestImageBuilder; -import net.pterodactylus.sone.test.TestPostBuilder; -import net.pterodactylus.sone.test.TestPostReplyBuilder; -import net.pterodactylus.sone.test.TestValue; -import net.pterodactylus.util.config.Configuration; -import net.pterodactylus.util.config.ConfigurationException; -import net.pterodactylus.util.config.Value; - -import com.google.common.base.Optional; -import org.hamcrest.CoreMatchers; -import org.junit.Before; -import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -/** - * Tests for {@link MemoryDatabase}. - */ -public class MemoryDatabaseTest { - - private static final String SONE_ID = "sone"; - private static final String RECIPIENT_ID = "recipient"; - private final Configuration configuration = mock(Configuration.class); - private final MemoryDatabase memoryDatabase = new MemoryDatabase(configuration); - private final Sone sone = mock(Sone.class); - - @Before - public void setupSone() { - when(sone.getId()).thenReturn(SONE_ID); - } - - @Test - public void storedSoneIsMadeAvailable() { - Post firstPost = new TestPostBuilder().withId("post1") - .from(SONE_ID) - .withTime(1000L) - .withText("post1") - .build(); - Post secondPost = new TestPostBuilder().withId("post2") - .from(SONE_ID) - .withTime(2000L) - .withText("post2") - .to(RECIPIENT_ID) - .build(); - List posts = asList(firstPost, secondPost); - when(sone.getPosts()).thenReturn(posts); - PostReply firstPostFirstReply = - new TestPostReplyBuilder().withId("reply1") - .from(SONE_ID) - .to(firstPost.getId()) - .withTime(3000L) - .withText("reply1") - .build(); - PostReply firstPostSecondReply = - new TestPostReplyBuilder().withId("reply3") - .from(RECIPIENT_ID) - .to(firstPost.getId()) - .withTime(5000L) - .withText("reply3") - .build(); - PostReply secondPostReply = - new TestPostReplyBuilder().withId("reply2") - .from(SONE_ID) - .to(secondPost.getId()) - .withTime(4000L) - .withText("reply2") - .build(); - Set postReplies = new HashSet<>( - asList(firstPostFirstReply, firstPostSecondReply, - secondPostReply)); - when(sone.getReplies()).thenReturn(postReplies); - Album firstAlbum = new TestAlbumBuilder().withId("album1") - .by(sone) - .build() - .modify() - .setTitle("album1") - .setDescription("album-description1") - .update(); - Album secondAlbum = new TestAlbumBuilder().withId("album2").by( - sone).build().modify().setTitle("album2").setDescription( - "album-description2").update(); - Album thirdAlbum = new TestAlbumBuilder().withId("album3").by( - sone).build().modify().setTitle("album3").setDescription( - "album-description3").update(); - firstAlbum.addAlbum(thirdAlbum); - Album rootAlbum = mock(Album.class); - when(rootAlbum.getId()).thenReturn("root"); - when(rootAlbum.getAlbums()).thenReturn( - asList(firstAlbum, secondAlbum)); - when(sone.getRootAlbum()).thenReturn(rootAlbum); - Image firstImage = new TestImageBuilder().withId("image1") - .build() - .modify() - .setSone(sone) - .setCreationTime(1000L) - .setKey("KSK@image1") - .setTitle("image1") - .setDescription("image-description1") - .setWidth(16) - .setHeight(9) - .update(); - Image secondImage = new TestImageBuilder().withId("image2") - .build() - .modify() - .setSone(sone) - .setCreationTime(2000L) - .setKey("KSK@image2") - .setTitle("image2") - .setDescription("image-description2") - .setWidth(32) - .setHeight(18) - .update(); - Image thirdImage = new TestImageBuilder().withId("image3") - .build() - .modify() - .setSone(sone) - .setCreationTime(3000L) - .setKey("KSK@image3") - .setTitle("image3") - .setDescription("image-description3") - .setWidth(48) - .setHeight(27) - .update(); - firstAlbum.addImage(firstImage); - firstAlbum.addImage(thirdImage); - secondAlbum.addImage(secondImage); - memoryDatabase.storeSone(sone); - assertThat(memoryDatabase.getPost("post1"), - isPost(firstPost.getId(), 1000L, "post1", - Optional.absent())); - assertThat(memoryDatabase.getPost("post2"), - isPost(secondPost.getId(), 2000L, "post2", of(RECIPIENT_ID))); - assertThat(memoryDatabase.getPost("post3"), nullValue()); - assertThat(memoryDatabase.getPostReply("reply1"), - isPostReply("reply1", "post1", 3000L, "reply1")); - assertThat(memoryDatabase.getPostReply("reply2"), - isPostReply("reply2", "post2", 4000L, "reply2")); - assertThat(memoryDatabase.getPostReply("reply3"), - isPostReply("reply3", "post1", 5000L, "reply3")); - assertThat(memoryDatabase.getPostReply("reply4"), nullValue()); - assertThat(memoryDatabase.getAlbum("album1"), - isAlbum("album1", null, "album1", "album-description1")); - assertThat(memoryDatabase.getAlbum("album2"), - isAlbum("album2", null, "album2", "album-description2")); - assertThat(memoryDatabase.getAlbum("album3"), - isAlbum("album3", "album1", "album3", "album-description3")); - assertThat(memoryDatabase.getAlbum("album4"), nullValue()); - assertThat(memoryDatabase.getImage("image1"), - isImage("image1", 1000L, "KSK@image1", "image1", - "image-description1", 16, 9)); - assertThat(memoryDatabase.getImage("image2"), - isImage("image2", 2000L, "KSK@image2", "image2", - "image-description2", 32, 18)); - assertThat(memoryDatabase.getImage("image3"), - isImage("image3", 3000L, "KSK@image3", "image3", - "image-description3", 48, 27)); - assertThat(memoryDatabase.getImage("image4"), nullValue()); - } - - @Test - public void storedAndRemovedSoneIsNotAvailable() { - storedSoneIsMadeAvailable(); - memoryDatabase.removeSone(sone); - assertThat(memoryDatabase.getSones(), empty()); - } - - @Test - public void postRecipientsAreDetectedCorrectly() { - Post postWithRecipient = createPost(of(RECIPIENT_ID)); - memoryDatabase.storePost(postWithRecipient); - Post postWithoutRecipient = createPost(Optional.absent()); - memoryDatabase.storePost(postWithoutRecipient); - assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), - contains(postWithRecipient)); - } - - private Post createPost(Optional recipient) { - Post postWithRecipient = mock(Post.class); - when(postWithRecipient.getId()).thenReturn(randomUUID().toString()); - when(postWithRecipient.getSone()).thenReturn(sone); - when(postWithRecipient.getRecipientId()).thenReturn(recipient); - return postWithRecipient; - } - - @Test - public void postRepliesAreManagedCorrectly() { - Post firstPost = createPost(Optional.absent()); - PostReply firstPostFirstReply = createPostReply(firstPost, 1000L); - Post secondPost = createPost(Optional.absent()); - PostReply secondPostFirstReply = createPostReply(secondPost, 1000L); - PostReply secondPostSecondReply = createPostReply(secondPost, 2000L); - memoryDatabase.storePost(firstPost); - memoryDatabase.storePost(secondPost); - memoryDatabase.storePostReply(firstPostFirstReply); - memoryDatabase.storePostReply(secondPostFirstReply); - memoryDatabase.storePostReply(secondPostSecondReply); - assertThat(memoryDatabase.getReplies(firstPost.getId()), - contains(firstPostFirstReply)); - assertThat(memoryDatabase.getReplies(secondPost.getId()), - contains(secondPostFirstReply, secondPostSecondReply)); - } - - private PostReply createPostReply(Post post, long time) { - PostReply postReply = mock(PostReply.class); - when(postReply.getId()).thenReturn(randomUUID().toString()); - when(postReply.getTime()).thenReturn(time); - when(postReply.getPost()).thenReturn(of(post)); - final String postId = post.getId(); - when(postReply.getPostId()).thenReturn(postId); - return postReply; - } - - @Test - public void testBasicAlbumFunctionality() { - Album newAlbum = new AlbumImpl(mock(Sone.class)); - assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue()); - memoryDatabase.storeAlbum(newAlbum); - assertThat(memoryDatabase.getAlbum(newAlbum.getId()), is(newAlbum)); - memoryDatabase.removeAlbum(newAlbum); - assertThat(memoryDatabase.getAlbum(newAlbum.getId()), nullValue()); - } - - private void initializeFriends() { - when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/0/ID")).thenReturn( - TestValue.from("Friend1")); - when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/1/ID")).thenReturn( - TestValue.from("Friend2")); - when(configuration.getStringValue("Sone/" + SONE_ID + "/Friends/2/ID")).thenReturn( - TestValue.from(null)); - } - - @Test - public void friendsAreReturnedCorrectly() { - initializeFriends(); - when(sone.isLocal()).thenReturn(true); - Collection friends = memoryDatabase.getFriends(sone); - assertThat(friends, containsInAnyOrder("Friend1", "Friend2")); - } - - @Test - public void friendsAreOnlyLoadedOnceFromConfiguration() { - friendsAreReturnedCorrectly(); - memoryDatabase.getFriends(sone); - verify(configuration).getStringValue("Sone/" + SONE_ID + "/Friends/0/ID"); - } - - @Test - public void friendsAreOnlyReturnedForLocalSones() { - Collection friends = memoryDatabase.getFriends(sone); - assertThat(friends, emptyIterable()); - verify(configuration, never()).getStringValue("Sone/" + SONE_ID + "/Friends/0/ID"); - } - - @Test - public void checkingForAFriendReturnsTrue() { - initializeFriends(); - when(sone.isLocal()).thenReturn(true); - assertThat(memoryDatabase.isFriend(sone, "Friend1"), is(true)); - } - - @Test - public void checkingForAFriendThatIsNotAFriendReturnsFalse() { - initializeFriends(); - when(sone.isLocal()).thenReturn(true); - assertThat(memoryDatabase.isFriend(sone, "FriendX"), is(false)); - } - - @Test - public void checkingForAFriendOfRemoteSoneReturnsFalse() { - initializeFriends(); - assertThat(memoryDatabase.isFriend(sone, "Friend1"), is(false)); - } - - private Map> prepareConfigurationValues() { - final Map> configurationValues = new HashMap<>(); - when(configuration.getStringValue(anyString())).thenAnswer(new Answer>() { - @Override - public Value answer(InvocationOnMock invocation) throws Throwable { - Value value = configurationValues.get(invocation.getArgument(0)); - if (value == null) { - value = TestValue.from(null); - configurationValues.put(invocation.getArgument(0), value); - } - return (Value) value; - } - }); - when(configuration.getLongValue(anyString())).thenAnswer(new Answer>() { - @Override - public Value answer(InvocationOnMock invocation) throws Throwable { - Value value = configurationValues.get(invocation.getArgument(0)); - if (value == null) { - value = TestValue.from(null); - configurationValues.put(invocation.getArgument(0), value); - } - return (Value) value; - } - }); - return configurationValues; - } - - @Test - public void friendIsAddedCorrectlyToLocalSone() { - Map> configurationValues = prepareConfigurationValues(); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.addFriend(sone, "Friend1"); - assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/0/ID"), - CoreMatchers.>is(TestValue.from("Friend1"))); - assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/1/ID"), - CoreMatchers.>is(TestValue.from(null))); - } - - @Test - public void friendIsNotAddedToRemoteSone() { - memoryDatabase.addFriend(sone, "Friend1"); - verify(configuration, never()).getStringValue(anyString()); - } - - @Test - public void friendIsRemovedCorrectlyFromLocalSone() { - Map> configurationValues = prepareConfigurationValues(); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.addFriend(sone, "Friend1"); - memoryDatabase.removeFriend(sone, "Friend1"); - assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/0/ID"), - CoreMatchers.>is(TestValue.from(null))); - assertThat(configurationValues.get("Sone/" + SONE_ID + "/Friends/1/ID"), - CoreMatchers.>is(TestValue.from(null))); - } - - @Test - public void configurationIsNotWrittenWhenANonFriendIsRemoved() { - prepareConfigurationValues(); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.removeFriend(sone, "Friend1"); - verify(configuration).getStringValue(anyString()); - } - - @Test - public void soneFollowingTimeIsReturnedCorrectly() throws ConfigurationException { - prepareConfigurationValues(); - configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("sone"); - configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L); - assertThat(memoryDatabase.getFollowingTime("sone"), equalTo(1000L)); - } - - @Test - public void nullisReturnedWhenSoneIsNotFollowed() throws ConfigurationException { - prepareConfigurationValues(); - configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("otherSone"); - configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L); - assertThat(memoryDatabase.getFollowingTime("sone"), nullValue()); - } - - @Test - public void timeIsStoredInConfigurationWhenASoneIsFollowed() throws ConfigurationException { - prepareConfigurationValues(); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.addFriend(sone, "Friend"); - assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), equalTo("Friend")); - assertThat(System.currentTimeMillis() - configuration.getLongValue("SoneFollowingTimes/0/Time").getValue(), lessThan(1000L)); - assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").getValue(), nullValue()); - } - - @Test - public void existingTimeIsNotOverwrittenWhenASoneIsFollowed() throws ConfigurationException { - prepareConfigurationValues(); - configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("Friend"); - configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.addFriend(sone, "Friend"); - assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), equalTo("Friend")); - assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").getValue(), equalTo(1000L)); - assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").getValue(), nullValue()); - } - - @Test - public void unfollowingASoneRemovesTheFollowingTime() throws ConfigurationException { - prepareConfigurationValues(); - configuration.getStringValue("Sone/sone/Friends/0/ID").setValue("Friend"); - configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("Friend"); - configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.removeFriend(sone, "Friend"); - assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), nullValue()); - } - - @Test - public void unfollowingASoneDoesNotRemoveTheFollowingTimeIfAnotherLocalSoneFollowsIt() throws ConfigurationException { - prepareConfigurationValues(); - configuration.getStringValue("Sone/sone/Friends/0/ID").setValue("Friend"); - configuration.getStringValue("Sone/other-sone/Friends/0/ID").setValue("Friend"); - configuration.getStringValue("SoneFollowingTimes/0/Sone").setValue("Friend"); - configuration.getLongValue("SoneFollowingTimes/0/Time").setValue(1000L); - Sone otherSone = mock(Sone.class); - when(otherSone.isLocal()).thenReturn(true); - when(otherSone.getId()).thenReturn("other-sone"); - memoryDatabase.getFriends(otherSone); - when(sone.isLocal()).thenReturn(true); - memoryDatabase.removeFriend(sone, "Friend"); - assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").getValue(), equalTo("Friend")); - assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").getValue(), equalTo(1000L)); - } - - @Test - public void markingAPostAsKnownSavesConfiguration() throws ConfigurationException { - prepareConfigurationValues(); - Post post = mock(Post.class); - when(post.getId()).thenReturn("post-id"); - memoryDatabase.setPostKnown(post, true); - assertThat(configuration.getStringValue("KnownPosts/0/ID").getValue(), equalTo("post-id")); - assertThat(configuration.getStringValue("KnownPosts/1/ID").getValue(), equalTo(null)); - } - - @Test - public void markingAPostReplyAsKnownSavesConfiguration() throws ConfigurationException { - prepareConfigurationValues(); - PostReply postReply = mock(PostReply.class); - when(postReply.getId()).thenReturn("post-reply-id"); - memoryDatabase.setPostReplyKnown(postReply, true); - assertThat(configuration.getStringValue("KnownReplies/0/ID").getValue(), equalTo("post-reply-id")); - assertThat(configuration.getStringValue("KnownReplies/1/ID").getValue(), equalTo(null)); - } - -} diff --git a/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt b/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt new file mode 100644 index 0000000..d02d162 --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/database/memory/MemoryDatabaseTest.kt @@ -0,0 +1,415 @@ +/* + * Sone - MemoryDatabaseTest.java - Copyright © 2013–2019 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.database.memory + +import com.google.common.base.* +import com.google.common.base.Optional.* +import net.pterodactylus.sone.data.* +import net.pterodactylus.sone.data.impl.* +import net.pterodactylus.sone.test.* +import net.pterodactylus.sone.test.Matchers.* +import net.pterodactylus.util.config.* +import org.hamcrest.MatcherAssert.* +import org.hamcrest.Matchers.* +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito.* +import org.mockito.invocation.* +import java.util.Arrays.* +import java.util.UUID.* +import kotlin.test.* + +/** + * Tests for [MemoryDatabase]. + */ +class MemoryDatabaseTest { + + private val configuration = mock() + private val memoryDatabase = MemoryDatabase(configuration) + private val sone = mock() + + @BeforeTest + fun setupSone() { + whenever(sone.id).thenReturn(SONE_ID) + } + + @Test + fun `stored sone is made available`() { + storeSone() + assertThat(memoryDatabase.getPost("post1"), isPost("post1", 1000L, "post1", absent())) + assertThat(memoryDatabase.getPost("post2"), isPost("post2", 2000L, "post2", of(RECIPIENT_ID))) + assertThat(memoryDatabase.getPost("post3"), nullValue()) + assertThat(memoryDatabase.getPostReply("reply1"), isPostReply("reply1", "post1", 3000L, "reply1")) + assertThat(memoryDatabase.getPostReply("reply2"), isPostReply("reply2", "post2", 4000L, "reply2")) + assertThat(memoryDatabase.getPostReply("reply3"), isPostReply("reply3", "post1", 5000L, "reply3")) + assertThat(memoryDatabase.getPostReply("reply4"), nullValue()) + assertThat(memoryDatabase.getAlbum("album1"), isAlbum("album1", null, "album1", "album-description1")) + assertThat(memoryDatabase.getAlbum("album2"), isAlbum("album2", null, "album2", "album-description2")) + assertThat(memoryDatabase.getAlbum("album3"), isAlbum("album3", "album1", "album3", "album-description3")) + assertThat(memoryDatabase.getAlbum("album4"), nullValue()) + assertThat(memoryDatabase.getImage("image1"), isImage("image1", 1000L, "KSK@image1", "image1", "image-description1", 16, 9)) + assertThat(memoryDatabase.getImage("image2"), isImage("image2", 2000L, "KSK@image2", "image2", "image-description2", 32, 18)) + assertThat(memoryDatabase.getImage("image3"), isImage("image3", 3000L, "KSK@image3", "image3", "image-description3", 48, 27)) + assertThat(memoryDatabase.getImage("image4"), nullValue()) + } + + private fun storeSone() { + val firstPost = TestPostBuilder().withId("post1") + .from(SONE_ID) + .withTime(1000L) + .withText("post1") + .build() + val secondPost = TestPostBuilder().withId("post2") + .from(SONE_ID) + .withTime(2000L) + .withText("post2") + .to(RECIPIENT_ID) + .build() + val posts = asList(firstPost, secondPost) + whenever(sone.posts).thenReturn(posts) + val firstPostFirstReply = TestPostReplyBuilder().withId("reply1") + .from(SONE_ID) + .to(firstPost.id) + .withTime(3000L) + .withText("reply1") + .build() + val firstPostSecondReply = TestPostReplyBuilder().withId("reply3") + .from(RECIPIENT_ID) + .to(firstPost.id) + .withTime(5000L) + .withText("reply3") + .build() + val secondPostReply = TestPostReplyBuilder().withId("reply2") + .from(SONE_ID) + .to(secondPost.id) + .withTime(4000L) + .withText("reply2") + .build() + val postReplies = setOf(firstPostFirstReply, firstPostSecondReply, secondPostReply) + whenever(sone.replies).thenReturn(postReplies) + val firstAlbum = TestAlbumBuilder().withId("album1") + .by(sone) + .build() + .modify() + .setTitle("album1") + .setDescription("album-description1") + .update() + val secondAlbum = TestAlbumBuilder().withId("album2") + .by(sone) + .build() + .modify() + .setTitle("album2") + .setDescription("album-description2") + .update() + val thirdAlbum = TestAlbumBuilder().withId("album3") + .by(sone) + .build() + .modify() + .setTitle("album3") + .setDescription("album-description3") + .update() + firstAlbum.addAlbum(thirdAlbum) + val rootAlbum = mock() + whenever(rootAlbum.id).thenReturn("root") + whenever(rootAlbum.albums).thenReturn(listOf(firstAlbum, secondAlbum)) + whenever(sone.rootAlbum).thenReturn(rootAlbum) + val firstImage = TestImageBuilder().withId("image1") + .build() + .modify() + .setSone(sone) + .setCreationTime(1000L) + .setKey("KSK@image1") + .setTitle("image1") + .setDescription("image-description1") + .setWidth(16) + .setHeight(9) + .update() + val secondImage = TestImageBuilder().withId("image2") + .build() + .modify() + .setSone(sone) + .setCreationTime(2000L) + .setKey("KSK@image2") + .setTitle("image2") + .setDescription("image-description2") + .setWidth(32) + .setHeight(18) + .update() + val thirdImage = TestImageBuilder().withId("image3") + .build() + .modify() + .setSone(sone) + .setCreationTime(3000L) + .setKey("KSK@image3") + .setTitle("image3") + .setDescription("image-description3") + .setWidth(48) + .setHeight(27) + .update() + firstAlbum.addImage(firstImage) + firstAlbum.addImage(thirdImage) + secondAlbum.addImage(secondImage) + memoryDatabase.storeSone(sone) + } + + @Test + fun `stored and removed sone is not available`() { + storeSone() + memoryDatabase.removeSone(sone) + assertThat(memoryDatabase.sones, empty()) + } + + @Test + fun `post recipients are detected correctly`() { + val postWithRecipient = createPost(of(RECIPIENT_ID)) + memoryDatabase.storePost(postWithRecipient) + val postWithoutRecipient = createPost(absent()) + memoryDatabase.storePost(postWithoutRecipient) + assertThat(memoryDatabase.getDirectedPosts(RECIPIENT_ID), contains(postWithRecipient)) + } + + private fun createPost(recipient: Optional): Post { + val postWithRecipient = mock() + whenever(postWithRecipient.id).thenReturn(randomUUID().toString()) + whenever(postWithRecipient.sone).thenReturn(sone) + whenever(postWithRecipient.recipientId).thenReturn(recipient) + return postWithRecipient + } + + @Test + fun `post replies are managed correctly`() { + val firstPost = createPost(absent()) + val firstPostFirstReply = createPostReply(firstPost, 1000L) + val secondPost = createPost(absent()) + val secondPostFirstReply = createPostReply(secondPost, 1000L) + val secondPostSecondReply = createPostReply(secondPost, 2000L) + memoryDatabase.storePost(firstPost) + memoryDatabase.storePost(secondPost) + memoryDatabase.storePostReply(firstPostFirstReply) + memoryDatabase.storePostReply(secondPostFirstReply) + memoryDatabase.storePostReply(secondPostSecondReply) + assertThat(memoryDatabase.getReplies(firstPost.id), contains(firstPostFirstReply)) + assertThat(memoryDatabase.getReplies(secondPost.id), contains(secondPostFirstReply, secondPostSecondReply)) + } + + private fun createPostReply(post: Post, time: Long): PostReply { + val postReply = mock() + whenever(postReply.id).thenReturn(randomUUID().toString()) + whenever(postReply.time).thenReturn(time) + whenever(postReply.post).thenReturn(of(post)) + val postId = post.id + whenever(postReply.postId).thenReturn(postId) + return postReply + } + + @Test + fun `test basic album functionality`() { + val newAlbum = AlbumImpl(mock()) + assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue()) + memoryDatabase.storeAlbum(newAlbum) + assertThat(memoryDatabase.getAlbum(newAlbum.id), equalTo(newAlbum)) + memoryDatabase.removeAlbum(newAlbum) + assertThat(memoryDatabase.getAlbum(newAlbum.id), nullValue()) + } + + private fun initializeFriends() { + whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/0/ID")).thenReturn(TestValue.from("Friend1")) + whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/1/ID")).thenReturn(TestValue.from("Friend2")) + whenever(configuration.getStringValue("Sone/$SONE_ID/Friends/2/ID")).thenReturn(TestValue.from(null)) + } + + @Test + fun `friends are returned correctly`() { + initializeFriends() + whenever(sone.isLocal).thenReturn(true) + val friends = memoryDatabase.getFriends(sone) + assertThat(friends, containsInAnyOrder("Friend1", "Friend2")) + } + + @Test + fun `friends are only loaded once from configuration`() { + initializeFriends() + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.getFriends(sone) + memoryDatabase.getFriends(sone) + verify(configuration, times(1)).getStringValue("Sone/$SONE_ID/Friends/0/ID") + } + + @Test + fun `friends are only returned for local sones`() { + val friends = memoryDatabase.getFriends(sone) + assertThat(friends, emptyIterable()) + verify(configuration, never()).getStringValue("Sone/$SONE_ID/Friends/0/ID") + } + + @Test + fun `checking for a friend returns true`() { + initializeFriends() + whenever(sone.isLocal).thenReturn(true) + assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(true)) + } + + @Test + fun `checking for a friend that is not a friend returns false`() { + initializeFriends() + whenever(sone.isLocal).thenReturn(true) + assertThat(memoryDatabase.isFriend(sone, "FriendX"), equalTo(false)) + } + + @Test + fun `checking for a friend of remote sone returns false`() { + initializeFriends() + assertThat(memoryDatabase.isFriend(sone, "Friend1"), equalTo(false)) + } + + private fun prepareConfigurationValues(): Map> = + mutableMapOf>().also { configurationValues -> + whenever(configuration.getStringValue(anyString())).thenAnswer(createAndCacheValue(configurationValues)) + whenever(configuration.getLongValue(anyString())).thenAnswer(createAndCacheValue(configurationValues)) + } + + private fun createAndCacheValue(configurationValues: MutableMap>) = + { invocation: InvocationOnMock -> + configurationValues[invocation[0]] + ?: TestValue.from(null).also { + configurationValues[invocation[0]] = it + } + } + + @Test + fun `friend is added correctly to local sone`() { + val configurationValues = prepareConfigurationValues() + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.addFriend(sone, "Friend1") + assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo>(TestValue.from("Friend1"))) + assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo>(TestValue.from(null))) + } + + @Test + fun `friend is not added to remote sone`() { + memoryDatabase.addFriend(sone, "Friend1") + verify(configuration, never()).getStringValue(anyString()) + } + + @Test + fun `friend is removed correctly from local sone`() { + val configurationValues = prepareConfigurationValues() + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.addFriend(sone, "Friend1") + memoryDatabase.removeFriend(sone, "Friend1") + assertThat(configurationValues["Sone/$SONE_ID/Friends/0/ID"], equalTo>(TestValue.from(null))) + assertThat(configurationValues["Sone/$SONE_ID/Friends/1/ID"], equalTo>(TestValue.from(null))) + } + + @Test + fun `configuration is not written when a non-friend is removed`() { + prepareConfigurationValues() + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.removeFriend(sone, "Friend1") + verify(configuration).getStringValue(anyString()) + } + + @Test + fun `sone following time is returned correctly`() { + prepareConfigurationValues() + configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "sone" + configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L + assertThat(memoryDatabase.getFollowingTime("sone"), equalTo(1000L)) + } + + @Test + fun `null is returned when sone is not followed`() { + prepareConfigurationValues() + configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "otherSone" + configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L + assertThat(memoryDatabase.getFollowingTime("sone"), nullValue()) + } + + @Test + fun `time is stored in configuration when a sone is followed`() { + prepareConfigurationValues() + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.addFriend(sone, "Friend") + assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend")) + assertThat(System.currentTimeMillis() - configuration.getLongValue("SoneFollowingTimes/0/Time").value, lessThan(1000L)) + assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue()) + } + + @Test + fun `existing time is not overwritten when a sone is followed`() { + prepareConfigurationValues() + configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend" + configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.addFriend(sone, "Friend") + assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend")) + assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L)) + assertThat(configuration.getStringValue("SoneFollowingTimes/1/Sone").value, nullValue()) + } + + @Test + fun `unfollowing a sone removes the following time`() { + prepareConfigurationValues() + configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend" + configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend" + configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.removeFriend(sone, "Friend") + assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, nullValue()) + } + + @Test + fun `unfollowing a sone does not remove the following time if another local sone follows it`() { + prepareConfigurationValues() + configuration.getStringValue("Sone/sone/Friends/0/ID").value = "Friend" + configuration.getStringValue("Sone/other-sone/Friends/0/ID").value = "Friend" + configuration.getStringValue("SoneFollowingTimes/0/Sone").value = "Friend" + configuration.getLongValue("SoneFollowingTimes/0/Time").value = 1000L + val otherSone = mock() + whenever(otherSone.isLocal).thenReturn(true) + whenever(otherSone.id).thenReturn("other-sone") + memoryDatabase.getFriends(otherSone) + whenever(sone.isLocal).thenReturn(true) + memoryDatabase.removeFriend(sone, "Friend") + assertThat(configuration.getStringValue("SoneFollowingTimes/0/Sone").value, equalTo("Friend")) + assertThat(configuration.getLongValue("SoneFollowingTimes/0/Time").value, equalTo(1000L)) + } + + @Test + fun `marking a post as known saves configuration`() { + prepareConfigurationValues() + val post = mock() + whenever(post.id).thenReturn("post-id") + memoryDatabase.setPostKnown(post, true) + assertThat(configuration.getStringValue("KnownPosts/0/ID").value, equalTo("post-id")) + assertThat(configuration.getStringValue("KnownPosts/1/ID").value, equalTo(null)) + } + + @Test + fun `marking a post reply as known saves configuration`() { + prepareConfigurationValues() + val postReply = mock() + whenever(postReply.id).thenReturn("post-reply-id") + memoryDatabase.setPostReplyKnown(postReply, true) + assertThat(configuration.getStringValue("KnownReplies/0/ID").value, equalTo("post-reply-id")) + assertThat(configuration.getStringValue("KnownReplies/1/ID").value, equalTo(null)) + } + +} + +private const val SONE_ID = "sone" +private const val RECIPIENT_ID = "recipient"