Add Sone change collector
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Mon, 26 Feb 2018 18:45:20 +0000 (19:45 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 2 Mar 2018 20:59:53 +0000 (21:59 +0100)
This wraps the Sone change detector in order to allow firing off events
for new/removed items.

src/main/kotlin/net/pterodactylus/sone/core/SoneChangeCollector.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/SoneChangeCollectorTest.kt [new file with mode: 0644]

diff --git a/src/main/kotlin/net/pterodactylus/sone/core/SoneChangeCollector.kt b/src/main/kotlin/net/pterodactylus/sone/core/SoneChangeCollector.kt
new file mode 100644 (file)
index 0000000..609a55f
--- /dev/null
@@ -0,0 +1,49 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.data.Sone
+
+/**
+ * Wrapper around a [SoneChangeDetector] that can turn changed elements into
+ * different elements which are then being returned. This can be used to turn
+ * changed elements into events for further processing.
+ */
+class SoneChangeCollector(private val oldSone: Sone) {
+
+       private val newPostEventCreators = mutableListOf<(Post) -> Any?>()
+       private val removedPostEventCreators = mutableListOf<(Post) -> Any?>()
+       private val newPostReplyEventCreators = mutableListOf<(PostReply) -> Any?>()
+       private val removedPostReplyEventCreators = mutableListOf<(PostReply) -> Any?>()
+
+       fun onNewPost(postProcessor: (Post) -> Unit) =
+                       newPostEventCreators.add { postProcessor(it).let { null } }.let { this }
+
+       fun newPostEvent(postEventCreator: (Post) -> Any?) =
+                       newPostEventCreators.add(postEventCreator).let { this }
+
+       fun onNewPostReply(postReplyProcessor: (PostReply) -> Unit) =
+                       newPostReplyEventCreators.add { postReplyProcessor(it).let { null } }.let { this }
+
+       fun newPostReplyEvent(postReplyEventCreator: (PostReply) -> Any?) =
+                       newPostReplyEventCreators.add(postReplyEventCreator).let { this }
+
+       fun removedPostEvent(postEventCreator: (Post) -> Any?) =
+                       removedPostEventCreators.add(postEventCreator).let { this }
+
+       fun onRemovedPostReply(postReplyEventCreator: (PostReply) -> Any?) =
+                       removedPostReplyEventCreators.add(postReplyEventCreator).let { this }
+
+       fun detectChanges(newSone: Sone): List<Any> {
+               val events = mutableListOf<Any>()
+               SoneChangeDetector(oldSone).apply {
+                       onNewPosts { post -> newPostEventCreators.mapNotNull { it(post) }.forEach { events.add(it) } }
+                       onRemovedPosts { post -> removedPostEventCreators.mapNotNull { it(post) }.forEach { events.add(it) } }
+                       onNewPostReplies { reply -> newPostReplyEventCreators.mapNotNull { it(reply) }.forEach { events.add(it) } }
+                       onRemovedPostReplies { reply -> removedPostReplyEventCreators.mapNotNull { it(reply) }.forEach { events.add(it) } }
+                       detectChanges(newSone)
+               }
+               return events
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneChangeCollectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneChangeCollectorTest.kt
new file mode 100644 (file)
index 0000000..3b82b49
--- /dev/null
@@ -0,0 +1,78 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.data.Post
+import net.pterodactylus.sone.data.PostReply
+import net.pterodactylus.sone.data.Sone
+import net.pterodactylus.sone.test.mock
+import net.pterodactylus.sone.test.whenever
+import org.hamcrest.MatcherAssert.assertThat
+import org.hamcrest.Matchers.containsInAnyOrder
+import org.hamcrest.Matchers.emptyIterable
+import org.hamcrest.Matchers.equalTo
+import org.junit.Test
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * Unit test for [SoneChangeCollectorTest].
+ */
+class SoneChangeCollectorTest {
+
+       private val oldSone = mock<Sone>()
+       private val newSone = mock<Sone>()
+       private val changeCollector = SoneChangeCollector(oldSone)
+
+       @Test
+       fun `new posts are correctly turned into events`() {
+               val posts = listOf(mock<Post>(), mock(), mock())
+               whenever(newSone.posts).thenReturn(posts)
+               changeCollector.newPostEvent { it.takeIf { it != posts[1] } }
+               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(posts[0], posts[2]))
+       }
+
+       @Test
+       fun `actions can be performed on new post without being returned`() {
+               val posts = listOf(mock<Post>(), mock(), mock())
+               val counter = AtomicInteger(0)
+               whenever(newSone.posts).thenReturn(posts.slice(0..2))
+               whenever(oldSone.posts).thenReturn(posts.slice(2..2))
+               changeCollector.onNewPost { counter.incrementAndGet() }
+               assertThat(changeCollector.detectChanges(newSone), emptyIterable())
+               assertThat(counter.get(), equalTo(2))
+       }
+
+       @Test
+       fun `removed posts are correctly turned into events`() {
+               val posts = listOf(mock<Post>(), mock(), mock())
+               whenever(oldSone.posts).thenReturn(posts)
+               changeCollector.removedPostEvent { it.takeIf { it != posts[1] } }
+               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(posts[0], posts[2]))
+       }
+
+       @Test
+       fun `new post replies are correctly turned into events`() {
+               val postReplies = listOf(mock<PostReply>(), mock(), mock())
+               whenever(newSone.replies).thenReturn(postReplies.toSet())
+               changeCollector.newPostReplyEvent { it.takeIf { it != postReplies[1] } }
+               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(postReplies[0], postReplies[2]))
+       }
+
+       @Test
+       fun `actions can be performed on new replies without being returned`() {
+               val replies = listOf(mock<PostReply>(), mock(), mock())
+               val counter = AtomicInteger(0)
+               whenever(newSone.replies).thenReturn(replies.slice(0..2).toSet())
+               whenever(oldSone.replies).thenReturn(replies.slice(2..2).toSet())
+               changeCollector.onNewPostReply { counter.incrementAndGet() }
+               assertThat(changeCollector.detectChanges(newSone), emptyIterable())
+               assertThat(counter.get(), equalTo(2))
+       }
+
+       @Test
+       fun `removed post replies are correctly turned into events`() {
+               val postReplies = listOf(mock<PostReply>(), mock(), mock())
+               whenever(oldSone.replies).thenReturn(postReplies.toSet())
+               changeCollector.onRemovedPostReply { it.takeIf { it != postReplies[1] } }
+               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(postReplies[0], postReplies[2]))
+       }
+
+}