Add unit test for get post feed command, add likes to replies
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 13 Jan 2017 06:52:29 +0000 (07:52 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 13 Jan 2017 06:52:29 +0000 (07:52 +0100)
src/main/java/net/pterodactylus/sone/fcp/AbstractSoneCommand.java
src/test/java/net/pterodactylus/sone/OneByOneMatcher.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/GetPostFeedCommandTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/fcp/SoneCommandTest.kt

index 1723b69..33e9233 100644 (file)
@@ -355,7 +355,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
         *            {@code null})
         * @return The simple field set containing the replies
         */
-       protected static SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
+       protected SimpleFieldSet encodeReplies(Collection<? extends PostReply> replies, String prefix) {
                SimpleFieldSetBuilder replyBuilder = new SimpleFieldSetBuilder();
 
                int replyIndex = 0;
@@ -366,6 +366,7 @@ public abstract class AbstractSoneCommand extends AbstractCommand {
                        replyBuilder.put(replyPrefix + "Sone", reply.getSone().getId());
                        replyBuilder.put(replyPrefix + "Time", reply.getTime());
                        replyBuilder.put(replyPrefix + "Text", encodeString(reply.getText()));
+                       replyBuilder.put(encodeLikes(core.getLikes(reply), replyPrefix + "Likes."));
                }
 
                return replyBuilder.get();
diff --git a/src/test/java/net/pterodactylus/sone/OneByOneMatcher.kt b/src/test/java/net/pterodactylus/sone/OneByOneMatcher.kt
new file mode 100644 (file)
index 0000000..109add2
--- /dev/null
@@ -0,0 +1,33 @@
+package net.pterodactylus.sone
+
+import org.hamcrest.Description
+import org.hamcrest.TypeSafeDiagnosingMatcher
+
+class OneByOneMatcher<A> : TypeSafeDiagnosingMatcher<A>() {
+       private data class Matcher<in A, out V>(val expected: V, val actual: (A) -> V, val description: String)
+
+       private val matchers = mutableListOf<Matcher<A, *>>()
+
+       fun <V> expect(description: String, expected: V, actual: (A) -> V) {
+               matchers += Matcher<A, V>(expected, actual, description)
+       }
+
+       override fun describeTo(description: Description) {
+               matchers.forEachIndexed { index, matcher ->
+                       if (index > 0) {
+                               description.appendText(", ")
+                       }
+                       description.appendText("${matcher.description} is ").appendValue(matcher.expected)
+               }
+       }
+
+       override fun matchesSafely(item: A, mismatchDescription: Description) =
+                       matchers.all {
+                               if (it.expected != it.actual(item)) {
+                                       mismatchDescription.appendText("${it.description} is ").appendValue(it.actual(item))
+                                       false
+                               } else {
+                                       true
+                               }
+                       }
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/fcp/GetPostFeedCommandTest.kt b/src/test/kotlin/net/pterodactylus/sone/fcp/GetPostFeedCommandTest.kt
new file mode 100644 (file)
index 0000000..c2f0a7a
--- /dev/null
@@ -0,0 +1,179 @@
+package net.pterodactylus.sone.fcp
+
+import freenet.support.SimpleFieldSet
+import net.pterodactylus.sone.core.Core
+import net.pterodactylus.sone.test.asOptional
+import net.pterodactylus.sone.test.whenever
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.containsInAnyOrder
+import org.hamcrest.Matchers.equalTo
+import org.junit.Test
+
+/**
+ * Unit test for [GetPostFeedCommand].
+ */
+class GetPostFeedCommandTest : SoneCommandTest() {
+
+       private val sone1 = createSone("Sone1", "Sone 1", "Sone", "#1", 1000)
+       private val sone2 = createSone("Sone2", "Sone 2", "Sone", "#2", 2000)
+       private val sone3 = createSone("Sone3", "Sone 3", "Sone", "#3", 3000)
+       private val sone4 = createSone("Sone4", "Sone 4", "Sone", "#4", 4000)
+       private val friend1 = createSone("Friend1", "Friend 1", "Friend", "#1", 5000)
+       private val post1 = createPost("Post1", sone1, null, 1000, "Post 1")
+       private val post1Reply1 = createReply("Post1Reply1", sone1, post1, 10000, "Post 1, Reply 1")
+       private val post1Reply2 = createReply("Post1Reply2", sone2, post1, 20000, "Post 1, Reply 2")
+       private val post2 = createPost("Post2", sone2, "Recipient 2", 2000, "Post 2")
+       private val post2Reply1 = createReply("Post2Reply1", sone3, post2, 30000, "Post 2, Reply 1")
+       private val post2Reply2 = createReply("Post2Reply2", sone4, post2, 40000, "Post 2, Reply 2")
+       private val friendPost1 = createPost("FriendPost1", friend1, null, 1500, "Friend Post 1")
+       private val directedPost = createPost("DirectedPost1", sone3, "ValidSoneId", 500, "Hey!")
+
+       override fun createCommand(core: Core) = GetPostFeedCommand(core)
+
+       @Test
+       fun `command does not require write access`() {
+               assertThat(command.requiresWriteAccess(), equalTo(false))
+       }
+
+       @Test
+       fun `request without any parameters results in fcp exception`() {
+               requestWithoutAnyParameterResultsInFcpException()
+       }
+
+       @Test
+       fun `request with empty Sone parameter results in fcp exception`() {
+               requestWithEmptySoneParameterResultsInFcpException()
+       }
+
+       @Test
+       fun `request with invalid Sone parameter results in fcp exception`() {
+               requestWithInvalidSoneParameterResultsInFcpException()
+       }
+
+       @Test
+       fun `request with valid remote Sone parameter results in fcp exception`() {
+               requestWithValidRemoteSoneParameterResultsInFcpException()
+       }
+
+       private fun setupAllPostsAndReplies() {
+               parameters.putSingle("Sone", "ValidSoneId")
+               whenever(localSone.id).thenReturn("ValidSoneId")
+               whenever(core.getSone("ValidSoneId")).thenReturn(localSone.asOptional())
+               whenever(core.getSone("Friend1")).thenReturn(friend1.asOptional())
+               whenever(core.getLikes(post1)).thenReturn(setOf(sone3, sone4))
+               whenever(core.getLikes(post1Reply1)).thenReturn(setOf(sone2, sone3))
+               whenever(core.getLikes(post1Reply2)).thenReturn(setOf(sone3))
+               whenever(core.getReplies("Post1")).thenReturn(listOf(post1Reply1, post1Reply2))
+               whenever(core.getLikes(post2)).thenReturn(setOf(sone1, sone2))
+               whenever(core.getLikes(post2Reply1)).thenReturn(setOf(sone4, sone1))
+               whenever(core.getLikes(post2Reply2)).thenReturn(setOf(sone1, sone2, sone3))
+               whenever(core.getReplies("Post2")).thenReturn(listOf(post2Reply1, post2Reply2))
+               whenever(localSone.posts).thenReturn(listOf(post2, post1))
+               whenever(core.getLikes(friendPost1)).thenReturn(setOf(sone1, friend1))
+               whenever(friend1.posts).thenReturn(listOf(friendPost1))
+               whenever(localSone.friends).thenReturn(setOf("Friend1", "Friend2"))
+               whenever(core.getDirectedPosts("ValidSoneId")).thenReturn(setOf(directedPost))
+               whenever(core.getLikes(directedPost)).thenReturn(setOf(sone2, sone4))
+       }
+
+       private fun verifyFirstPost(replyParameters: SimpleFieldSet) {
+               assertThat(replyParameters.parsePost("Posts.0."), matchesPost(post2))
+               assertThat(replyParameters["Posts.0.Replies.Count"], equalTo("2"))
+               assertThat(replyParameters.parseReply("Posts.0.Replies.0."), matchesReply(post2Reply1))
+               assertThat(replyParameters["Posts.0.Replies.0.Likes.Count"], equalTo("2"))
+               assertThat((0..1).map { replyParameters["Posts.0.Replies.0.Likes.$it.ID"] }, containsInAnyOrder("Sone1", "Sone4"))
+               assertThat(replyParameters.parseReply("Posts.0.Replies.1."), matchesReply(post2Reply2))
+               assertThat(replyParameters["Posts.0.Replies.1.Likes.Count"], equalTo("3"))
+               assertThat((0..2).map { replyParameters["Posts.0.Replies.1.Likes.$it.ID"] }, containsInAnyOrder("Sone1", "Sone2", "Sone3"))
+               assertThat(replyParameters["Posts.0.Likes.Count"], equalTo("2"))
+               assertThat((0..1).map { replyParameters["Posts.0.Likes.$it.ID"] }, containsInAnyOrder("Sone1", "Sone2"))
+       }
+
+       private fun verifySecondPost(replyParameters: SimpleFieldSet, index: Int = 1) {
+               assertThat(replyParameters.parsePost("Posts.$index."), matchesPost(friendPost1))
+               assertThat(replyParameters["Posts.$index.Replies.Count"], equalTo("0"))
+               assertThat(replyParameters["Posts.$index.Likes.Count"], equalTo("2"))
+               assertThat((0..1).map { replyParameters["Posts.$index.Likes.$it.ID"] }, containsInAnyOrder("Sone1", "Friend1"))
+       }
+
+       private fun verifyThirdPost(replyParameters: SimpleFieldSet, index: Int = 2) {
+               assertThat(replyParameters.parsePost("Posts.$index."), matchesPost(post1))
+               assertThat(replyParameters.parseReply("Posts.$index.Replies.0."), matchesReply(post1Reply1))
+               assertThat(replyParameters.parseReply("Posts.$index.Replies.1."), matchesReply(post1Reply2))
+               assertThat(replyParameters["Posts.$index.Replies.0.Likes.Count"], equalTo("2"))
+               assertThat((0..1).map { replyParameters["Posts.$index.Replies.0.Likes.$it.ID"] }, containsInAnyOrder("Sone2", "Sone3"))
+               assertThat(replyParameters["Posts.$index.Replies.1.Likes.Count"], equalTo("1"))
+               assertThat(replyParameters["Posts.$index.Replies.1.Likes.0.ID"], equalTo("Sone3"))
+               assertThat(replyParameters["Posts.$index.Likes.Count"], equalTo("2"))
+               assertThat((0..1).map { replyParameters["Posts.$index.Likes.$it.ID"] }, containsInAnyOrder("Sone3", "Sone4"))
+       }
+
+       private fun verifyFourthPost(replyParameters: SimpleFieldSet, index: Int = 3) {
+               assertThat(replyParameters.parsePost("Posts.$index."), matchesPost(directedPost))
+               assertThat(replyParameters["Posts.$index.Replies.Count"], equalTo("0"))
+               assertThat(replyParameters["Posts.$index.Likes.Count"], equalTo("2"))
+               assertThat((0..1).map { replyParameters["Posts.$index.Likes.$it.ID"] }, containsInAnyOrder("Sone2", "Sone4"))
+       }
+
+       @Test
+       fun `request with valid local Sone parameter results in the post feed with all required posts`() {
+               setupAllPostsAndReplies()
+
+               val replyParameters = command.execute(parameters).replyParameters
+
+               assertThat(replyParameters["Message"], equalTo("PostFeed"))
+               assertThat(replyParameters["Posts.Count"], equalTo("4"))
+
+               verifyFirstPost(replyParameters)
+               verifySecondPost(replyParameters)
+               verifyThirdPost(replyParameters)
+               verifyFourthPost(replyParameters)
+       }
+
+       @Test
+       fun `request with larger start than number of posts returns empty feed`() {
+               setupAllPostsAndReplies()
+               parameters.putSingle("StartPost", "20")
+               val replyParameters = command.execute(parameters).replyParameters
+               assertThat(replyParameters["Message"], equalTo("PostFeed"))
+               assertThat(replyParameters["Posts.Count"], equalTo("0"))
+       }
+
+       @Test
+       fun `request with max posts of 2 returns the first two posts`() {
+               setupAllPostsAndReplies()
+               parameters.putSingle("MaxPosts", "2")
+               val replyParameters = command.execute(parameters).replyParameters
+               assertThat(replyParameters["Message"], equalTo("PostFeed"))
+               assertThat(replyParameters["Posts.Count"], equalTo("2"))
+
+               verifyFirstPost(replyParameters)
+               verifySecondPost(replyParameters)
+       }
+
+       @Test
+       fun `request with max posts of 2 and start post of 1 returns the center two posts`() {
+               setupAllPostsAndReplies()
+               parameters.putSingle("StartPost", "1")
+               parameters.putSingle("MaxPosts", "2")
+               val replyParameters = command.execute(parameters).replyParameters
+               assertThat(replyParameters["Message"], equalTo("PostFeed"))
+               assertThat(replyParameters["Posts.Count"], equalTo("2"))
+
+               verifySecondPost(replyParameters, 0)
+               verifyThirdPost(replyParameters, 1)
+       }
+
+       @Test
+       fun `request with max posts of 2 and start post of 3 returns the last post`() {
+               setupAllPostsAndReplies()
+               parameters.putSingle("StartPost", "3")
+               parameters.putSingle("MaxPosts", "2")
+               val replyParameters = command.execute(parameters).replyParameters
+               assertThat(replyParameters["Message"], equalTo("PostFeed"))
+               assertThat(replyParameters["Posts.Count"], equalTo("1"))
+
+               verifyFourthPost(replyParameters, 0)
+       }
+
+}
index 8344531..3bdcb4a 100644 (file)
@@ -3,10 +3,14 @@ package net.pterodactylus.sone.fcp
 import com.google.common.base.Optional
 import com.google.common.base.Optional.absent
 import freenet.support.SimpleFieldSet
+import net.pterodactylus.sone.OneByOneMatcher
 import net.pterodactylus.sone.core.Core
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.PostReply
 import net.pterodactylus.sone.data.Profile
 import net.pterodactylus.sone.data.Sone
 import net.pterodactylus.sone.freenet.fcp.FcpException
+import net.pterodactylus.sone.test.asOptional
 import net.pterodactylus.sone.test.mock
 import net.pterodactylus.sone.test.whenever
 import org.junit.Before
@@ -49,6 +53,22 @@ abstract class SoneCommandTest {
                whenever(this.time).thenReturn(time)
        }
 
+       protected fun createPost(id: String, sone: Sone, recipientId: String?, time: Long, text: String) = mock<Post>().apply {
+               whenever(this.id).thenReturn(id)
+               whenever(this.sone).thenReturn(sone)
+               whenever(this.recipientId).thenReturn(recipientId.asOptional())
+               whenever(this.time).thenReturn(time)
+               whenever(this.text).thenReturn(text)
+       }
+
+       protected fun createReply(id: String, sone: Sone, post: Post, time: Long, text: String) = mock<PostReply>().apply {
+               whenever(this.id).thenReturn(id)
+               whenever(this.sone).thenReturn(sone)
+               whenever(this.post).thenReturn(post.asOptional())
+               whenever(this.time).thenReturn(time)
+               whenever(this.text).thenReturn(text)
+       }
+
        protected fun executeCommandAndExpectFcpException() {
                expectedException.expect(FcpException::class.java)
                command.execute(parameters)
@@ -75,4 +95,26 @@ abstract class SoneCommandTest {
                executeCommandAndExpectFcpException()
        }
 
+       protected fun SimpleFieldSet.parsePost(prefix: String) = parseFromSimpleFieldSet(prefix, "ID", "Sone", "Recipient", "Time", "Text")
+       protected fun SimpleFieldSet.parseReply(prefix: String) = parseFromSimpleFieldSet(prefix, "ID", "Sone", "Time", "Text")
+
+       private fun SimpleFieldSet.parseFromSimpleFieldSet(prefix: String, vararg fields: String) = listOf(*fields)
+                       .map { it to (get(prefix + it) as String?) }
+                       .toMap()
+
+       protected fun matchesPost(post: Post) = OneByOneMatcher<Map<String, String?>>().apply {
+               expect("ID", post.id) { it["ID"] }
+               expect("Sone", post.sone.id) { it["Sone"] }
+               expect("recipient", post.recipientId.orNull()) { it["Recipient"] }
+               expect("time", post.time.toString()) { it["Time"] }
+               expect("text", post.text) { it["Text"] }
+       }
+
+       protected fun matchesReply(reply: PostReply) = OneByOneMatcher<Map<String, String?>>().apply {
+               expect("ID", reply.id) { it["ID"] }
+               expect("Sone", reply.sone.id) { it["Sone"] }
+               expect("time", reply.time.toString()) { it["Time"] }
+               expect("text", reply.text) { it["Text"] }
+       }
+
 }