From 33bcd7cd33ec0717e9745f36fe91b6301f81f4ba Mon Sep 17 00:00:00 2001 From: Diego Beraldin Date: Wed, 13 Mar 2024 07:46:58 +0100 Subject: [PATCH] chore: tests for :core:persistence (#592) --- README.md | 16 +- core/persistence/build.gradle.kts | 7 +- .../DefaultAccountRepositoryTest.kt | 193 +++++++++++ .../DefaultCommunitySortRepositoryTest.kt | 81 +++++ .../repository/DefaultDraftRepositoryTest.kt | 196 +++++++++++ .../DefaultFavoriteCommunityRepositoryTest.kt | 124 +++++++ .../DefaultInstanceSelectionRepositoryTest.kt | 86 +++++ .../DefaultMultiCommunityRepositoryTest.kt | 150 ++++++++ .../DefaultSettingsRepositoryTest.kt | 320 ++++++++++++++++++ 9 files changed, 1163 insertions(+), 10 deletions(-) create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultAccountRepositoryTest.kt create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultCommunitySortRepositoryTest.kt create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultDraftRepositoryTest.kt create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultFavoriteCommunityRepositoryTest.kt create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultInstanceSelectionRepositoryTest.kt create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultMultiCommunityRepositoryTest.kt create mode 100644 core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt diff --git a/README.md b/README.md index d5cf7c829..1f48bf0d8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ A Kotlin Multiplatform client for Lemmy (mainly Android).
- +
@@ -136,19 +136,19 @@ with a platform like Lemmy. This involves a high level of discretion and personal taste, I know, but this project _is_ all about experimenting and learning. -## Techical notes: +## Technical notes: The project uses the following technologies: - [Koin](https://github.com/InsertKoinIO/koin) for dependency injection - [Voyager](https://github.com/adrielcafe/voyager) for screen navigation -- [Ktor](https://github.com/ktorio/ktor) and [Ktorfit](https://github.com/Foso/Ktorfit) for - networking +- [Ktor](https://github.com/ktorio/ktor) and [Ktorfit](https://github.com/Foso/Ktorfit) for networking - [Lyricist](https://github.com/adrielcafe/lyricist) for l10n -- [Multiplatform settings](https://github.com/russhwolf/multiplatform-settings) for encrypted - preferences -- [SQLDelight](https://github.com/cashapp/sqldelight) - and [SQLCipher](https://github.com/sqlcipher/sqlcipher) for local persistence +- [Multiplatform settings](https://github.com/russhwolf/multiplatform-settings) for encrypted preferences +- [SQLDelight](https://github.com/cashapp/sqldelight) and [SQLCipher](https://github.com/sqlcipher/sqlcipher) for local + persistence +- [Multiplatform Markdown Renderer](https://github.com/mikepenz/multiplatform-markdown-renderer) for Markdown + rendering More info about the technologies used in the project can be found in the [CONTRIBUTING.md](https://github.com/diegoberaldin/RaccoonForLemmy/blob/master/CONTRIBUTING.md). diff --git a/core/persistence/build.gradle.kts b/core/persistence/build.gradle.kts index d4f2f33f0..f55a0eb41 100644 --- a/core/persistence/build.gradle.kts +++ b/core/persistence/build.gradle.kts @@ -55,9 +55,12 @@ kotlin { implementation(projects.core.utils) } } - val commonTest by getting { + val androidUnitTest by getting { dependencies { - implementation(kotlin("test")) + implementation(libs.kotlinx.coroutines.test) + implementation(kotlin("test-junit")) + implementation(libs.mockk) + implementation(projects.core.testutils) } } } diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultAccountRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultAccountRepositoryTest.kt new file mode 100644 index 000000000..1fe55191d --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultAccountRepositoryTest.kt @@ -0,0 +1,193 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import app.cash.sqldelight.Query +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.AccountEntity +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.AccountsQueries +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvider +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.AccountModel +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.entities.AppDatabase +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class DefaultAccountRepositoryTest { + + @get:Rule + val dispatcherRule = DispatcherTestRule() + + private val query = mockk>() + private val queries = mockk(relaxUnitFun = true) { + every { getAll() } returns query + every { getActive() } returns query + every { getBy(username = any(), instance = any()) } returns query + every { getActive() } returns query + } + private val provider = mockk { + every { getDatabase() } returns mockk { + every { accountsQueries } returns queries + } + } + + private val sut = DefaultAccountRepository( + provider = provider, + ) + + @Test + fun givenNoAccounts_whenGetAll_thenResultIsAsExpected() = runTest { + every { query.executeAsList() } returns listOf() + + val res = sut.getAll() + + assertTrue(res.isEmpty()) + verify { + queries.getAll() + } + } + + @Test + fun givenExitingAccounts_whenGetAll_thenResultIsAsExpected() = runTest { + val accounts = listOf(createFakeEntity()) + every { query.executeAsList() } returns accounts + + val res = sut.getAll() + + assertTrue(res.size == 1) + verify { + queries.getAll() + } + } + + + @Test + fun givenNoActiveAccount_whenGetActive_thenResultIsAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns null + + val res = sut.getActive() + + assertNull(res) + verify { + queries.getActive() + } + } + + @Test + fun givenActiveAccount_whenGetActive_thenResultIsAsExpected() = runTest { + val account = createFakeEntity(active = true) + every { query.executeAsOneOrNull() } returns account + + val res = sut.getActive() + + assertNotNull(res) + verify { + queries.getActive() + } + } + + @Test + fun givenNoAccount_whenGetBy_thenResultIsAsExpected() = runTest { + val username = "username" + val instance = "instance" + every { query.executeAsOneOrNull() } returns null + + val res = sut.getBy(username, instance) + + assertNull(res) + verify { + queries.getBy(username, instance) + } + } + + @Test + fun givenAccount_whenGetBy_thenResultIsAsExpected() = runTest { + val username = "username" + val instance = "instance" + val account = createFakeEntity(active = true, username = username, instance = instance) + every { query.executeAsOneOrNull() } returns account + + val res = sut.getBy(username, instance) + + assertNotNull(res) + verify { + queries.getBy(username, instance) + } + } + + @Test + fun whenCreate_thenIdIsReturned() = runTest { + val username = "username" + val instance = "instance" + val jwt = "jwt" + val account = AccountModel(username = username, instance = instance, jwt = jwt) + every { query.executeAsList() } returns listOf(createFakeEntity(id = 1, jwt = jwt)) + + val id = sut.createAccount(account) + + assertEquals(1, id) + + verify { + queries.create(username, instance, jwt, null) + } + } + + @Test + fun whenSetActive_thenResultIsAsExpected() = runTest { + sut.setActive(id = 1, active = true) + + verify { + queries.setActive(id = 1) + } + } + + @Test + fun whenSetInactive_thenInteractionsAreAsExpected() = runTest { + sut.setActive(id = 1, active = false) + + verify { + queries.setInactive(id = 1) + } + } + + @Test + fun whenUpdate_thenInteractionsAreAsExpected() = runTest { + val jwt = "jwt" + val avatar = "avatar" + sut.update(1, avatar = avatar, jwt = jwt) + + verify { + queries.update(jwt = jwt, avatar = avatar, id = 1) + } + } + + @Test + fun whenDelete_thenInteractionsAreAsExpected() = runTest { + sut.delete(1) + + verify { + queries.delete(1) + } + } + + private fun createFakeEntity( + id: Long = 0, + active: Boolean = false, + username: String = "", + instance: String = "", + jwt: String? = null, + avatar: String? = null, + ) = AccountEntity( + id = id, + active = if (active) 1 else 0, + username = username, + instance = instance, + jwt = jwt, + avatar = avatar, + ) +} diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultCommunitySortRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultCommunitySortRepositoryTest.kt new file mode 100644 index 000000000..456702bdd --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultCommunitySortRepositoryTest.kt @@ -0,0 +1,81 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import junit.framework.TestCase.assertNull +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals + +class DefaultCommunitySortRepositoryTest { + + @get:Rule + val dispatcherTestRule = DispatcherTestRule() + + private val keyStore = mockk(relaxUnitFun = true) + + private val sut = DefaultCommunitySortRepository(keyStore = keyStore) + + @Test + fun givenEmptyInitialState_whenSaveSort_thenValueIsStored() { + every { keyStore.get("communitySort", listOf()) } returns listOf() + + sut.saveSort("!raccoonforlemmy@lemmy.world", 1) + + verify { + keyStore.save("communitySort", listOf("!raccoonforlemmy@lemmy.world:1")) + } + } + + @Test + fun givenCommunityAlreadyExisting_whenSaveSort_thenValueIsStored() { + every { keyStore.get("communitySort", listOf()) } returns listOf("!raccoonforlemmy@lemmy.world:0") + + sut.saveSort("!raccoonforlemmy@lemmy.world", 1) + + verify { + keyStore.save("communitySort", listOf("!raccoonforlemmy@lemmy.world:1")) + } + } + + @Test + fun givenOtherCommunityAlreadyExisting_whenSaveSort_thenBothValuesAreStored() { + every { keyStore.get("communitySort", listOf()) } returns listOf("!test@lemmy.world:1") + + sut.saveSort("!raccoonforlemmy@lemmy.world", 1) + + verify { + keyStore.save("communitySort", listOf("!test@lemmy.world:1", "!raccoonforlemmy@lemmy.world:1")) + } + } + + @Test + fun givenEmptyInitialState_whenGet_thenResultIsAsExpected() { + every { keyStore.get("communitySort", listOf()) } returns listOf() + + val res = sut.getSort("!raccoonforlemmy@lemmy.world") + + assertNull(res) + } + + @Test + fun givenCommunityExists_whenGet_thenResultIsAsExpected() { + every { keyStore.get("communitySort", listOf()) } returns listOf("!raccoonforlemmy@lemmy.world:2") + + val res = sut.getSort("!raccoonforlemmy@lemmy.world") + + assertEquals(2, res) + } + + @Test + fun givenCommunityDoesNotExist_whenGet_thenResultIsAsExpected() { + every { keyStore.get("communitySort", listOf()) } returns listOf("!test@lemmy.world:2") + + val res = sut.getSort("!raccoonforlemmy@lemmy.world") + + assertNull(res) + } +} diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultDraftRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultDraftRepositoryTest.kt new file mode 100644 index 000000000..b3a7ee790 --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultDraftRepositoryTest.kt @@ -0,0 +1,196 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import app.cash.sqldelight.Query +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvider +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DraftEntity +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DraftsQueries +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.DraftModel +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.DraftType +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.entities.AppDatabase +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class DefaultDraftRepositoryTest { + + @get:Rule + val dispatcherTestRule = DispatcherTestRule() + + private val query = mockk>() + private val queries = mockk(relaxUnitFun = true) { + every { getBy(any()) } returns query + every { getAllBy(type = any(), account_id = any()) } returns query + } + private val provider = mockk { + every { getDatabase() } returns mockk { + every { draftsQueries } returns queries + } + } + + private val sut = DefaultDraftRepository(provider) + + @Test + fun givenNotExisting_whenGetBy_thenResultIsAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns null + + val res = sut.getBy(1) + + assertNull(res) + verify { + queries.getBy(1) + } + } + + @Test + fun givenExisting_whenGetBy_thenResultIsAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns createFakeDraftEntity(1) + + val res = sut.getBy(1) + + assertNotNull(res) + assertEquals(1, res.id) + verify { + queries.getBy(1) + } + } + + @Test + fun givenEmpty_whenGetAllByPosts_thenResultIsAsExpected() = runTest { + every { query.executeAsList() } returns emptyList() + + val res = sut.getAll(type = DraftType.Post, accountId = 1) + + assertTrue(res.isEmpty()) + verify { + queries.getAllBy(0, 1) + } + } + + @Test + fun givenEmpty_whenGetAllByComments_thenResultIsAsExpected() = runTest { + every { query.executeAsList() } returns emptyList() + + val res = sut.getAll(type = DraftType.Comment, accountId = 1) + + assertTrue(res.isEmpty()) + verify { + queries.getAllBy(1, 1) + } + } + + @Test + fun givenNotEmpty_whenGetAllByPosts_thenResultIsAsExpected() = runTest { + every { query.executeAsList() } returns listOf(createFakeDraftEntity(id = 2)) + + val res = sut.getAll(type = DraftType.Post, accountId = 1) + + assertTrue(res.isNotEmpty()) + assertEquals(2, res.first().id) + verify { + queries.getAllBy(0, 1) + } + } + + @Test + fun whenCreate_thenInteractionsAreAsExpected() = runTest { + val text = "text" + val model = DraftModel( + body = text, + type = DraftType.Post, + communityId = 3, + ) + + sut.create(model, 1) + + verify { + queries.create( + type = 0, + body = text, + title = null, + url = null, + postId = null, + parentId = null, + communityId = 3, + languageId = null, + nsfw = null, + date = null, + info = null, + account_id = 1, + ) + } + } + + @Test + fun whenUpdate_thenInteractionsAreAsExpected() = runTest { + val text = "text" + val model = DraftModel( + id = 1, + body = text, + type = DraftType.Post, + communityId = 3, + ) + + sut.update(model) + + verify { + queries.update( + id = 1, + body = text, + title = null, + url = null, + communityId = 3, + languageId = null, + nsfw = null, + date = null, + info = null, + ) + } + } + + @Test + fun whenDelete_thenInteractionsAreAsExpected() = runTest { + sut.delete(1) + + verify { + queries.delete(1) + } + } + + private fun createFakeDraftEntity( + id: Long = 0, + accountId: Long = 0, + type: DraftType = DraftType.Post, + title: String? = null, + body: String = "", + postId: Long? = null, + parentId: Long? = null, + communityId: Long? = null, + languageId: Long? = null, + url: String? = null, + nsfw: Boolean = false, + info: String? = null, + date: Long? = null, + ) = DraftEntity( + id = id, + title = title, + body = body, + type = if (type == DraftType.Post) 0 else 1, + postId = postId, + parentId = parentId, + account_id = accountId, + communityId = communityId, + languageId = languageId, + url = url, + nsfw = if (nsfw) 1 else 0, + info = info, + date = date, + ) +} diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultFavoriteCommunityRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultFavoriteCommunityRepositoryTest.kt new file mode 100644 index 000000000..8a23a4d84 --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultFavoriteCommunityRepositoryTest.kt @@ -0,0 +1,124 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import app.cash.sqldelight.Query +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvider +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.FavoriteCommunityEntity +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.FavoritecommunitiesQueries +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.FavoriteCommunityModel +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.entities.AppDatabase +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import junit.framework.TestCase.assertNull +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class DefaultFavoriteCommunityRepositoryTest { + + @get:Rule + val dispatcherTestRule = DispatcherTestRule() + + private val query = mockk>() + private val queries = mockk(relaxUnitFun = true) { + every { getAll(any()) } returns query + every { getBy(communityId = any(), account_id = any()) } returns query + } + private val provider = mockk { + every { getDatabase() } returns mockk { + every { favoritecommunitiesQueries } returns queries + } + } + + private val sut = DefaultFavoriteCommunityRepository(provider) + + @Test + fun givenEmpty_whenGetAll_thenResultsAreAsExpected() = runTest { + every { query.executeAsList() } returns emptyList() + + val res = sut.getAll(1) + + assertTrue(res.isEmpty()) + verify { + queries.getAll(1) + } + } + + @Test + fun givenNotEmpty_whenGetAll_thenResultsAreAsExpected() = runTest { + every { query.executeAsList() } returns listOf(createFakeFavoriteCommunityEntity(id = 1, accountId = 1)) + + val res = sut.getAll(1) + + assertTrue(res.isNotEmpty()) + assertEquals(1, res.first().id) + verify { + queries.getAll(1) + } + } + + @Test + fun givenEmpty_whenGetBy_thenResultsAreAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns null + + val res = sut.getBy(accountId = 1, communityId = 2) + + assertNull(res) + verify { + queries.getBy(communityId = 2, account_id = 1) + } + } + + @Test + fun givenNotEmpty_whenGetBy_thenResultsAreAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns createFakeFavoriteCommunityEntity(id = 2, accountId = 1) + + val res = sut.getBy(accountId = 1, communityId = 3) + + assertNotNull(res) + assertEquals(2, res.id) + verify { + queries.getBy(communityId = 3, account_id = 1) + } + } + + + @Test + fun whenCreate_thenInteractionsAreAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns createFakeFavoriteCommunityEntity(id = 2, accountId = 1) + + val model = FavoriteCommunityModel(communityId = 3) + val res = sut.create(model = model, accountId = 1) + + assertEquals(2, res) + verify { + queries.create(communityId = 3, account_id = 1) + } + } + + @Test + fun whenDelete_thenInteractionsAreAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns createFakeFavoriteCommunityEntity(id = 2, accountId = 1) + + val model = FavoriteCommunityModel(communityId = 3) + sut.delete(accountId = 1, model = model) + + verify { + queries.delete(2) + } + } + + private fun createFakeFavoriteCommunityEntity( + id: Long = 0, + communityId: Long = 0, + accountId: Long = 0, + ) = FavoriteCommunityEntity( + id = id, + communityId = communityId, + account_id = accountId, + ) +} diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultInstanceSelectionRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultInstanceSelectionRepositoryTest.kt new file mode 100644 index 000000000..219aa5be3 --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultInstanceSelectionRepositoryTest.kt @@ -0,0 +1,86 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class DefaultInstanceSelectionRepositoryTest { + + @get:Rule + val dispatcherTestRule = DispatcherTestRule() + + private val keyStore = mockk(relaxUnitFun = true) { + every { containsKey("customInstances") } returns true + } + + private val sut = DefaultInstanceSelectionRepository(keyStore) + + @Test + fun givenEmpty_whenGetAll_thenResultIsAsExpected() = runTest { + every { keyStore.get("customInstances", any>()) } returns listOf() + + val res = sut.getAll() + + assertTrue(res.isEmpty()) + } + + @Test + fun givenNotEmpty_whenGetAll_thenResultIsAsExpected() = runTest { + every { keyStore.get("customInstances", any>()) } returns listOf("lemmy.world") + + val res = sut.getAll() + + assertTrue(res.isNotEmpty()) + assertEquals("lemmy.world", res.first()) + } + + @Test + fun whenUpdateAll_thenResultIsAsExpected() = runTest { + val values = listOf("lemmy.world", "lemmy.ml") + sut.updateAll(values) + + verify { + keyStore.save("customInstances", values) + } + } + + @Test + fun givenNotAlreadyPresent_whenAdd_thenResultIsAsExpected() = runTest { + every { keyStore.get("customInstances", any>()) } returns listOf("lemmy.world") + + sut.add("lemmy.ml") + + verify { + keyStore.save("customInstances", listOf("lemmy.ml", "lemmy.world")) + } + } + + @Test + fun givenAlreadyPresent_whenAdd_thenResultIsAsExpected() = runTest { + every { keyStore.get("customInstances", any>()) } returns listOf("lemmy.world") + + sut.add("lemmy.world") + + verify { + keyStore.save("customInstances", listOf("lemmy.world")) + } + } + + @Test + fun whenRemove_thenResultIsAsExpected() = runTest { + every { keyStore.get("customInstances", any>()) } returns listOf("lemmy.world", "lemmy.ml") + + sut.remove("lemmy.ml") + + verify { + keyStore.save("customInstances", listOf("lemmy.world")) + } + } +} \ No newline at end of file diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultMultiCommunityRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultMultiCommunityRepositoryTest.kt new file mode 100644 index 000000000..280e2c15c --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultMultiCommunityRepositoryTest.kt @@ -0,0 +1,150 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import app.cash.sqldelight.Query +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvider +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.MultiCommunityEntity +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.MulticommunitiesQueries +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.entities.AppDatabase +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class DefaultMultiCommunityRepositoryTest { + + @get:Rule + val dispatcherTestRule = DispatcherTestRule() + + private val query = mockk>() + private val queries = mockk(relaxUnitFun = true) { + every { getAll(any()) } returns query + every { getBy(name = any(), account_id = any()) } returns query + every { getById(any()) } returns query + } + private val provider = mockk { + every { getDatabase() } returns mockk { + every { multicommunitiesQueries } returns queries + } + } + + private val sut = DefaultMultiCommunityRepository(provider) + + @Test + fun givenEmpty_whenGetAll_thenResultIsAsExpected() = runTest { + every { query.executeAsList() } returns listOf() + + val res = sut.getAll(1) + + assertTrue(res.isEmpty()) + verify { + queries.getAll(1) + } + } + + @Test + fun givenNotEmpty_whenGetAll_thenResultIsAsExpected() = runTest { + every { query.executeAsList() } returns listOf(createFakeMultiCommunityEntity(id = 2)) + + val res = sut.getAll(1) + + assertTrue(res.isNotEmpty()) + assertEquals(2, res.first().id) + verify { + queries.getAll(1) + } + } + + @Test + fun givenEmpty_whenGetById_thenResultIsAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns null + + val res = sut.getById(1) + + assertNull(res) + verify { + queries.getById(1) + } + } + + @Test + fun givenNotEmpty_whenGetById_thenResultIsAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns createFakeMultiCommunityEntity(id = 2) + + val res = sut.getById(2) + + assertNotNull(res) + assertEquals(2, res.id) + verify { + queries.getById(2) + } + } + + @Test + fun whenCreate_thenInteractionsAreAsExpected() = runTest { + val model = MultiCommunityModel(name = "test", communityIds = listOf(1, 2, 3)) + every { query.executeAsOneOrNull() } returns createFakeMultiCommunityEntity(id = 2) + + val res = sut.create(model = model, accountId = 1) + + assertEquals(2, res) + verify { + queries.create( + name = "test", + icon = null, + communityIds = "1,2,3", + account_id = 1 + ) + } + } + + @Test + fun whenUpdate_thenInteractionsAreAsExpected() = runTest { + val model = MultiCommunityModel(name = "test", id = 2, icon = "fake-icon", communityIds = listOf(1, 2, 3)) + every { query.executeAsOneOrNull() } returns createFakeMultiCommunityEntity(id = 2) + + sut.update(model = model) + + verify { + queries.update( + id = 2, + name = "test", + icon = "fake-icon", + communityIds = "1,2,3", + ) + } + } + + @Test + fun whenDelete_thenInteractionsAreAsExpected() = runTest { + val model = MultiCommunityModel(name = "test", id = 2) + every { query.executeAsOneOrNull() } returns createFakeMultiCommunityEntity(id = 2) + + sut.delete(model) + + verify { + queries.delete(id = 2) + } + } + + private fun createFakeMultiCommunityEntity( + id: Long, + name: String = "", + icon: String? = null, + communityIds: String = "", + accountId: Long? = null, + ) = MultiCommunityEntity( + id = id, + name = name, + icon = icon, + communityIds = communityIds, + account_id = accountId, + ) +} \ No newline at end of file diff --git a/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt new file mode 100644 index 000000000..b9209c37e --- /dev/null +++ b/core/persistence/src/androidUnitTest/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/persistence/repository/DefaultSettingsRepositoryTest.kt @@ -0,0 +1,320 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository + +import app.cash.sqldelight.Query +import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.DatabaseProvider +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.GetBy +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.SettingsQueries +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.ActionOnSwipe +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.SettingsModel +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.toInt +import com.github.diegoberaldin.raccoonforlemmy.core.persistence.entities.AppDatabase +import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore +import com.github.diegoberaldin.raccoonforlemmy.core.testutils.DispatcherTestRule +import io.mockk.Called +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.time.DurationUnit + +class DefaultSettingsRepositoryTest { + + @get:Rule + val dispatcherTestRule = DispatcherTestRule() + + private val query = mockk>() + private val queries = mockk(relaxUnitFun = true) { + every { getBy(any()) } returns query + } + private val provider = mockk { + every { getDatabase() } returns mockk { + every { settingsQueries } returns queries + } + } + private val keyStore = mockk(relaxUnitFun = true) + + private val sut = DefaultSettingsRepository( + provider = provider, + keyStore = keyStore, + ) + + @Test + fun givenAccount_whenGetSettings_thenResultIsAsExpected() = runTest { + every { query.executeAsOneOrNull() } returns createFake(id = 2) + + val res = sut.getSettings(1) + + assertEquals(2, res.id) + + verify { + queries.getBy(account_id = 1) + } + } + + @Test + fun givenNoAccount_whenGetSettings_thenResultIsAsExpected() = runTest { + every { keyStore[any(), any()] } returns 0 + every { keyStore[any(), any()] } returns 0 + every { keyStore[any(), any()] } returns 1f + every { keyStore[any(), any()] } returns "" + every { keyStore[any(), any()] } returns false + every { keyStore.containsKey(any()) } returns true + + val res = sut.getSettings(null) + + assertNotNull(res) + + verify { + queries wasNot Called + } + } + + @Test + fun whenChangeCurrentSettings_thenValueIsUpdated() = runTest { + val model = SettingsModel(defaultListingType = 1) + sut.changeCurrentSettings(model) + val value = sut.currentSettings.value + assertEquals(model, value) + } + + @Test + fun whenCreateSettings_thenResultIsAsExpected() = runTest { + val model = SettingsModel() + sut.createSettings(model, 1) + + verify { + queries.create( + theme = model.theme?.toLong(), + uiFontScale = model.uiFontScale.toDouble(), + uiFontFamily = model.uiFontFamily.toLong(), + titleFontScale = model.contentFontScale.title.toDouble(), + contentFontScale = model.contentFontScale.body.toDouble(), + commentFontScale = model.contentFontScale.comment.toDouble(), + ancillaryFontScale = model.contentFontScale.ancillary.toDouble(), + locale = model.locale, + defaultListingType = model.defaultListingType.toLong(), + defaultPostSortType = model.defaultPostSortType.toLong(), + defaultCommentSortType = model.defaultCommentSortType.toLong(), + defaultInboxType = model.defaultInboxType.toLong(), + includeNsfw = if (model.includeNsfw) 1 else 0, + blurNsfw = if (model.blurNsfw) 1 else 0, + navigationTitlesVisible = if (model.navigationTitlesVisible) 1 else 0, + dynamicColors = if (model.dynamicColors) 1 else 0, + openUrlsInExternalBrowser = if (model.openUrlsInExternalBrowser) 1 else 0, + enableSwipeActions = if (model.enableSwipeActions) 1 else 0, + enableDoubleTapAction = if (model.enableDoubleTapAction) 1 else 0, + customSeedColor = model.customSeedColor?.toLong(), + postLayout = model.postLayout.toLong(), + separateUpAndDownVotes = if (model.voteFormat == VoteFormat.Separated) 1 else 0, + autoLoadImages = if (model.autoLoadImages) 1 else 0, + autoExpandComments = if (model.autoExpandComments) 1 else 0, + fullHeightImages = if (model.fullHeightImages) 1 else 0, + upvoteColor = model.upVoteColor?.toLong(), + downvoteColor = model.downVoteColor?.toLong(), + hideNavigationBarWhileScrolling = if (model.hideNavigationBarWhileScrolling) 1 else 0, + zombieModeInterval = model.zombieModeInterval.toLong(DurationUnit.MILLISECONDS), + zombieModeScrollAmount = model.zombieModeScrollAmount.toDouble(), + markAsReadWhileScrolling = if (model.markAsReadWhileScrolling) 1 else 0, + commentBarTheme = model.commentBarTheme.toLong(), + replyColor = model.replyColor?.toLong(), + saveColor = model.saveColor?.toLong(), + searchPostTitleOnly = if (model.searchPostTitleOnly) 1 else 0, + contentFontFamily = model.contentFontFamily.toLong(), + edgeToEdge = if (model.edgeToEdge) 1 else 0, + postBodyMaxLines = model.postBodyMaxLines?.toLong(), + infiniteScrollEnabled = if (model.infiniteScrollEnabled) 1 else 0, + actionsOnSwipeToStartPosts = model.actionsOnSwipeToStartPosts.serialized(), + actionsOnSwipeToEndPosts = model.actionsOnSwipeToEndPosts.serialized(), + actionsOnSwipeToStartComments = model.actionsOnSwipeToStartComments.serialized(), + actionsOnSwipeToEndComments = model.actionsOnSwipeToEndComments.serialized(), + actionsOnSwipeToStartInbox = model.actionsOnSwipeToStartInbox.serialized(), + actionsOnSwipeToEndInbox = model.actionsOnSwipeToEndInbox.serialized(), + opaqueSystemBars = if (model.opaqueSystemBars) 1 else 0, + showScores = if (model.showScores) 1 else 0, + preferUserNicknames = if (model.preferUserNicknames) 1 else 0, + commentBarThickness = model.commentBarThickness.toLong(), + imageSourcePath = if (model.imageSourcePath) 1 else 0, + defaultExploreType = model.defaultExploreType.toLong(), + account_id = 1, + ) + } + } + + @Test + fun whenUpdate_thenResultIsAsExpected() = runTest { + val model = SettingsModel(defaultListingType = 1) + sut.updateSettings(model, 1) + + verify { + queries.update( + theme = model.theme?.toLong(), + uiFontScale = model.uiFontScale.toDouble(), + uiFontFamily = model.uiFontFamily.toLong(), + titleFontScale = model.contentFontScale.title.toDouble(), + contentFontScale = model.contentFontScale.body.toDouble(), + commentFontScale = model.contentFontScale.comment.toDouble(), + ancillaryFontScale = model.contentFontScale.ancillary.toDouble(), + locale = model.locale, + defaultListingType = model.defaultListingType.toLong(), + defaultPostSortType = model.defaultPostSortType.toLong(), + defaultCommentSortType = model.defaultCommentSortType.toLong(), + defaultInboxType = model.defaultInboxType.toLong(), + includeNsfw = if (model.includeNsfw) 1 else 0, + blurNsfw = if (model.blurNsfw) 1 else 0, + navigationTitlesVisible = if (model.navigationTitlesVisible) 1 else 0, + dynamicColors = if (model.dynamicColors) 1 else 0, + openUrlsInExternalBrowser = if (model.openUrlsInExternalBrowser) 1 else 0, + enableSwipeActions = if (model.enableSwipeActions) 1 else 0, + enableDoubleTapAction = if (model.enableDoubleTapAction) 1 else 0, + customSeedColor = model.customSeedColor?.toLong(), + postLayout = model.postLayout.toLong(), + separateUpAndDownVotes = if (model.voteFormat == VoteFormat.Separated) 1 else 0, + autoLoadImages = if (model.autoLoadImages) 1 else 0, + autoExpandComments = if (model.autoExpandComments) 1 else 0, + fullHeightImages = if (model.fullHeightImages) 1 else 0, + upvoteColor = model.upVoteColor?.toLong(), + downvoteColor = model.downVoteColor?.toLong(), + hideNavigationBarWhileScrolling = if (model.hideNavigationBarWhileScrolling) 1 else 0, + zombieModeInterval = model.zombieModeInterval.toLong(DurationUnit.MILLISECONDS), + zombieModeScrollAmount = model.zombieModeScrollAmount.toDouble(), + markAsReadWhileScrolling = if (model.markAsReadWhileScrolling) 1 else 0, + commentBarTheme = model.commentBarTheme.toLong(), + replyColor = model.replyColor?.toLong(), + saveColor = model.saveColor?.toLong(), + searchPostTitleOnly = if (model.searchPostTitleOnly) 1 else 0, + contentFontFamily = model.contentFontFamily.toLong(), + edgeToEdge = if (model.edgeToEdge) 1 else 0, + postBodyMaxLines = model.postBodyMaxLines?.toLong(), + infiniteScrollEnabled = if (model.infiniteScrollEnabled) 1 else 0, + actionsOnSwipeToStartPosts = model.actionsOnSwipeToStartPosts.serialized(), + actionsOnSwipeToEndPosts = model.actionsOnSwipeToEndPosts.serialized(), + actionsOnSwipeToStartComments = model.actionsOnSwipeToStartComments.serialized(), + actionsOnSwipeToEndComments = model.actionsOnSwipeToEndComments.serialized(), + actionsOnSwipeToStartInbox = model.actionsOnSwipeToStartInbox.serialized(), + actionsOnSwipeToEndInbox = model.actionsOnSwipeToEndInbox.serialized(), + opaqueSystemBars = if (model.opaqueSystemBars) 1 else 0, + showScores = if (model.showScores) 1 else 0, + preferUserNicknames = if (model.preferUserNicknames) 1 else 0, + commentBarThickness = model.commentBarThickness.toLong(), + imageSourcePath = if (model.imageSourcePath) 1 else 0, + defaultExploreType = model.defaultExploreType.toLong(), + account_id = 1, + ) + } + } + + private fun List.serialized(): String = map { it.toInt() }.joinToString(",") + + private fun createFake( + id: Long = 0, + theme: Long? = null, + uiFontFamily: Long = 0, + uiFontScale: Double = 1.0, + contentFontScale: Double = 1.0, + ancillaryFontScale: Double = 1.0, + commentFontScale: Double = 1.0, + titleFontScale: Double = 1.0, + contentFontFamily: Long = 0, + locale: String? = null, + defaultListingType: Long = 2, + defaultPostSortType: Long = 1, + defaultInboxType: Long = 0, + defaultCommentSortType: Long = 3, + defaultExploreType: Long = 2, + includeNsfw: Boolean = false, + blurNsfw: Boolean = true, + navigationTitlesVisible: Boolean = true, + dynamicColors: Boolean = false, + openUrlsInExternalBrowser: Boolean = true, + enableSwipeActions: Boolean = true, + enableDoubleTapAction: Boolean = false, + customSeedColor: Long? = null, + upVoteColor: Long? = null, + downVoteColor: Long? = null, + postLayout: Long = 0, + fullHeightImages: Boolean = true, + autoLoadImages: Boolean = true, + autoExpandComments: Boolean = true, + hideNavigationBarWhileScrolling: Boolean = true, + zombieModeInterval: Long = 1, + zombieModeScrollAmount: Double = 55.0, + markAsReadWhileScrolling: Boolean = false, + commentBarTheme: Long = 0, + replyColor: Long? = null, + saveColor: Long? = null, + searchPostTitleOnly: Boolean = false, + edgeToEdge: Boolean = true, + postBodyMaxLines: Long? = null, + infiniteScrollEnabled: Boolean = true, + actionsOnSwipeToStartPosts: String = "", + actionsOnSwipeToEndPosts: String = "", + actionsOnSwipeToStartComments: String = "", + actionsOnSwipeToEndComments: String = "", + actionsOnSwipeToStartInbox: String = "", + actionsOnSwipeToEndInbox: String = "", + opaqueSystemBars: Boolean = false, + showScores: Boolean = true, + preferUserNicknames: Boolean = true, + commentBarThickness: Long = 1, + imageSourcePath: Boolean = false, + separateUpAndDownVotes: Boolean = false, + ) = GetBy( + id = id, + theme = theme, + uiFontFamily = uiFontFamily, + uiFontScale = uiFontScale, + contentFontScale = contentFontScale, + contentFontFamily = contentFontFamily, + locale = locale, + defaultListingType = defaultListingType, + defaultPostSortType = defaultPostSortType, + defaultInboxType = defaultInboxType, + defaultCommentSortType = defaultCommentSortType, + defaultExploreType = defaultExploreType, + includeNsfw = if (includeNsfw) 1 else 0, + blurNsfw = if (blurNsfw) 1 else 0, + navigationTitlesVisible = if (navigationTitlesVisible) 1 else 0, + dynamicColors = if (dynamicColors) 1 else 0, + openUrlsInExternalBrowser = if (openUrlsInExternalBrowser) 1 else 0, + enableSwipeActions = if (enableSwipeActions) 1 else 0, + enableDoubleTapAction = if (enableDoubleTapAction) 1 else 0, + customSeedColor = customSeedColor, + upvoteColor = upVoteColor, + downvoteColor = downVoteColor, + postLayout = postLayout, + fullHeightImages = if (fullHeightImages) 1 else 0, + autoLoadImages = if (autoLoadImages) 1 else 0, + autoExpandComments = if (autoExpandComments) 1 else 0, + hideNavigationBarWhileScrolling = if (hideNavigationBarWhileScrolling) 1 else 0, + zombieModeInterval = zombieModeInterval, + zombieModeScrollAmount = zombieModeScrollAmount, + markAsReadWhileScrolling = if (markAsReadWhileScrolling) 1 else 0, + commentBarTheme = commentBarTheme, + replyColor = replyColor, + saveColor = saveColor, + searchPostTitleOnly = if (searchPostTitleOnly) 1 else 0, + edgeToEdge = if (edgeToEdge) 1 else 0, + postBodyMaxLines = postBodyMaxLines, + infiniteScrollEnabled = if (infiniteScrollEnabled) 1 else 0, + actionsOnSwipeToStartPosts = actionsOnSwipeToStartPosts, + actionsOnSwipeToEndPosts = actionsOnSwipeToEndPosts, + actionsOnSwipeToStartComments = actionsOnSwipeToStartComments, + actionsOnSwipeToEndComments = actionsOnSwipeToEndComments, + actionsOnSwipeToStartInbox = actionsOnSwipeToStartInbox, + actionsOnSwipeToEndInbox = actionsOnSwipeToEndInbox, + opaqueSystemBars = if (opaqueSystemBars) 1 else 0, + showScores = if (showScores) 1 else 0, + preferUserNicknames = if (preferUserNicknames) 1 else 0, + commentBarThickness = commentBarThickness, + imageSourcePath = if (imageSourcePath) 1 else 0, + ancillaryFontScale = ancillaryFontScale, + commentFontScale = commentFontScale, + titleFontScale = titleFontScale, + separateUpAndDownVotes = if (separateUpAndDownVotes) 1 else 0, + ) +}