From 7a5fd177c8d4f7578ff45755f83c21981a41a0c5 Mon Sep 17 00:00:00 2001
From: Mikhail Astafyev <astafyev.mikhail@gmail.com>
Date: Mon, 4 Nov 2019 15:00:42 +0500
Subject: [PATCH] PSC-2 added test implementation for ContentAPI

---
 .../core/content/api/WriteContentAPI.kt       |  6 +-
 .../postssystem/core/utils/DateTimeUtils.kt   |  9 +++
 .../core/utils/pagination/Pagination.kt       |  9 +++
 .../core/api/InMemoryContentAPI.kt            | 67 +++++++++++++++++++
 4 files changed, 89 insertions(+), 2 deletions(-)
 create mode 100644 core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/DateTimeUtils.kt
 create mode 100644 core/src/test/kotlin/com/insanusmokrassar/postssystem/core/api/InMemoryContentAPI.kt

diff --git a/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/api/WriteContentAPI.kt b/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/api/WriteContentAPI.kt
index 3fbad021..acda8975 100644
--- a/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/api/WriteContentAPI.kt
+++ b/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/content/api/WriteContentAPI.kt
@@ -1,12 +1,14 @@
 package com.insanusmokrassar.postssystem.core.content.api
 
-import com.insanusmokrassar.postssystem.core.content.*
+import com.insanusmokrassar.postssystem.core.content.Content
+import com.insanusmokrassar.postssystem.core.content.ContentId
+import com.insanusmokrassar.postssystem.core.content.RegisteredContent
 import kotlinx.coroutines.flow.Flow
 
 interface WriteContentAPI {
     val contentCreatedFlow: Flow<RegisteredContent>
     val contentDeletedFlow: Flow<RegisteredContent>
 
-    suspend fun createContent(post: Content): RegisteredContent?
+    suspend fun createContent(content: Content): RegisteredContent?
     suspend fun deleteContent(id: ContentId): Boolean
 }
diff --git a/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/DateTimeUtils.kt b/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/DateTimeUtils.kt
new file mode 100644
index 00000000..3beef0cd
--- /dev/null
+++ b/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/DateTimeUtils.kt
@@ -0,0 +1,9 @@
+package com.insanusmokrassar.postssystem.core.utils
+
+import org.joda.time.DateTime
+
+fun DateTime?.orMin(): DateTime = this ?: MIN_DATE
+fun DateTime?.orMax(): DateTime = this ?: MAX_DATE
+
+private val MIN_DATE = DateTime(Long.MIN_VALUE)
+private val MAX_DATE = DateTime(Long.MAX_VALUE)
diff --git a/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/pagination/Pagination.kt b/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/pagination/Pagination.kt
index a8496b9d..c58d4953 100644
--- a/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/pagination/Pagination.kt
+++ b/core/src/main/kotlin/com/insanusmokrassar/postssystem/core/utils/pagination/Pagination.kt
@@ -1,5 +1,7 @@
 package com.insanusmokrassar.postssystem.core.utils.pagination
 
+import kotlin.math.ceil
+
 /**
  * Base interface of pagination
  *
@@ -33,3 +35,10 @@ val Pagination.firstIndex: Int
  */
 val Pagination.lastIndex: Int
     get() = firstIndex + size - 1
+
+/**
+ * Calculates pages count for given [datasetSize]
+ */
+fun Pagination.pagesCount(datasetSize: Int): Int {
+    return ceil(datasetSize.toDouble() / size).toInt()
+}
diff --git a/core/src/test/kotlin/com/insanusmokrassar/postssystem/core/api/InMemoryContentAPI.kt b/core/src/test/kotlin/com/insanusmokrassar/postssystem/core/api/InMemoryContentAPI.kt
new file mode 100644
index 00000000..795db98d
--- /dev/null
+++ b/core/src/test/kotlin/com/insanusmokrassar/postssystem/core/api/InMemoryContentAPI.kt
@@ -0,0 +1,67 @@
+package com.insanusmokrassar.postssystem.core.api
+
+import com.insanusmokrassar.postssystem.core.content.*
+import com.insanusmokrassar.postssystem.core.content.api.ContentAPI
+import com.insanusmokrassar.postssystem.core.utils.pagination.Pagination
+import com.insanusmokrassar.postssystem.core.utils.pagination.PaginationResult
+import com.insanusmokrassar.postssystem.core.utils.pagination.firstIndex
+import com.insanusmokrassar.postssystem.core.utils.pagination.pagesCount
+import kotlinx.coroutines.channels.BroadcastChannel
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import kotlinx.serialization.ImplicitReflectionSerializer
+import java.util.*
+
+@ImplicitReflectionSerializer
+private fun generateId(): ContentId = UUID.randomUUID().toString()
+
+@ImplicitReflectionSerializer
+class InMemoryContentAPI(
+    initialContent: List<RegisteredContent> = emptyList()
+): ContentAPI {
+
+    override val contentCreatedFlow: Flow<RegisteredContent>
+        get() = contentCreatedBroadcastChannel.asFlow()
+
+    override val contentDeletedFlow: Flow<RegisteredContent>
+        get() = contentDeletedBroadcastChannel.asFlow()
+
+    private val contents: MutableMap<ContentId, RegisteredContent> = initialContent
+        .associateBy(RegisteredContent::id)
+        .toMutableMap()
+
+    private val contentCreatedBroadcastChannel = BroadcastChannel<RegisteredContent>(Channel.BUFFERED)
+    private val contentDeletedBroadcastChannel = BroadcastChannel<RegisteredContent>(Channel.BUFFERED)
+
+    override suspend fun createContent(content: Content): RegisteredContent? {
+        return content.createRegisteredEntity(generateId())?.also { registeredContent ->
+            contents[registeredContent.id] = registeredContent
+            contentCreatedBroadcastChannel.send(registeredContent)
+        }
+    }
+
+    override suspend fun getContentById(id: ContentId): RegisteredContent? = contents[id]
+
+    override suspend fun getContentByPagination(pagination: Pagination): PaginationResult<out RegisteredContent> {
+        return PaginationResult(
+            page = pagination.page,
+            pagesNumber = pagination.pagesCount(contents.size),
+            results = contents.values.drop(pagination.firstIndex).take(pagination.size)
+        )
+    }
+
+    override suspend fun deleteContent(id: ContentId): Boolean {
+        return contents.remove(id)?.also { content ->
+            contentDeletedBroadcastChannel.send(content)
+        } != null
+    }
+
+    private fun Content.createRegisteredEntity(id: ContentId): RegisteredContent? {
+        return when(this) {
+            is TextContent -> SimpleTextRegisteredContent(id, text)
+            is SpecialContent -> SimpleTextRegisteredContent(id, internalId)
+            else -> null
+        }
+    }
+}
\ No newline at end of file