From 0613d36ab96793a5c6f020dec611f7b69f72c24e Mon Sep 17 00:00:00 2001 From: MohitMaliFtechiz Date: Wed, 29 May 2024 13:18:06 +0530 Subject: [PATCH] Added KiwixRoomDatabaseTest, ObjectBoxToRoomMigratorTest, RecentSearchRoomDaoTest for testing all the scenarios of migration and saving the recent searches in room database. * Improved the SaveSearchToRecentsTest. * Removed unused code from project. --- .../kiwixmobile/KiwixRoomDatabaseTest.kt | 80 +++++++++ .../ObjectBoxToRoomMigratorTest.kt | 168 ++++++++++++++++++ .../kiwixmobile/RecentSearchRoomDaoTest.kt | 108 +++++++++++ .../core/dao/RecentSearchRoomDao.kt | 17 -- .../effects/SaveSearchToRecentsTest.kt | 2 +- 5 files changed, 357 insertions(+), 18 deletions(-) create mode 100644 app/src/androidTest/java/org/kiwix/kiwixmobile/KiwixRoomDatabaseTest.kt create mode 100644 app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToRoomMigratorTest.kt create mode 100644 app/src/androidTest/java/org/kiwix/kiwixmobile/RecentSearchRoomDaoTest.kt diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/KiwixRoomDatabaseTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/KiwixRoomDatabaseTest.kt new file mode 100644 index 000000000..a1cc49bf2 --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/KiwixRoomDatabaseTest.kt @@ -0,0 +1,80 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * 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 org.kiwix.kiwixmobile + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao +import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity +import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase + +@RunWith(AndroidJUnit4::class) +class KiwixRoomDatabaseTest { + private lateinit var recentSearchRoomDao: RecentSearchRoomDao + private lateinit var db: KiwixRoomDatabase + + @After + fun teardown() { + db.close() + } + + @Test + fun testRecentSearchRoomDao() = runBlocking { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder(context, KiwixRoomDatabase::class.java) + .allowMainThreadQueries() + .build() + val zimId = "34388L" + val searchTerm = "title 1" + val searchTerm2 = "title 2" + val url = "" + recentSearchRoomDao = db.recentSearchRoomDao() + val recentSearch = RecentSearchRoomEntity(zimId = zimId, searchTerm = searchTerm, url = url) + val recentSearch1 = RecentSearchRoomEntity(zimId = zimId, searchTerm = searchTerm2, url = url) + + // test inserting into recent search database + recentSearchRoomDao.saveSearch(recentSearch.searchTerm, recentSearch.zimId, url = url) + var recentSearches = recentSearchRoomDao.search(zimId).first() + assertEquals(recentSearches.size, 1) + assertEquals(recentSearch.searchTerm, recentSearches.first().searchTerm) + assertEquals(recentSearch.zimId, recentSearches.first().zimId) + + // test deleting recent search + recentSearchRoomDao.deleteSearchString(searchTerm) + recentSearches = recentSearchRoomDao.search(searchTerm).first() + assertEquals(recentSearches.size, 0) + + // test deleting all recent search history + recentSearchRoomDao.saveSearch(recentSearch.searchTerm, recentSearch.zimId, url = url) + recentSearchRoomDao.saveSearch(recentSearch1.searchTerm, recentSearch1.zimId, url = url) + recentSearches = recentSearchRoomDao.search(zimId).first() + assertEquals(recentSearches.size, 2) + recentSearchRoomDao.deleteSearchHistory() + recentSearches = recentSearchRoomDao.search(searchTerm).first() + assertEquals(recentSearches.size, 0) + } +} diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToRoomMigratorTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToRoomMigratorTest.kt new file mode 100644 index 000000000..69bb50959 --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/ObjectBoxToRoomMigratorTest.kt @@ -0,0 +1,168 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * 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 org.kiwix.kiwixmobile + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.objectbox.Box +import io.objectbox.BoxStore +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity +import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase +import org.kiwix.kiwixmobile.core.data.remote.ObjectBoxToRoomMigrator +import org.kiwix.kiwixmobile.core.di.modules.DatabaseModule +import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil + +@RunWith(AndroidJUnit4::class) +class ObjectBoxToRoomMigratorTest { + private lateinit var context: Context + private lateinit var kiwixRoomDatabase: KiwixRoomDatabase + private lateinit var boxStore: BoxStore + private lateinit var objectBoxToRoomMigrator: ObjectBoxToRoomMigrator + + @Before + fun setup() { + context = ApplicationProvider.getApplicationContext() + kiwixRoomDatabase = Room.inMemoryDatabaseBuilder(context, KiwixRoomDatabase::class.java) + .allowMainThreadQueries() + .build() + boxStore = DatabaseModule.boxStore!! + objectBoxToRoomMigrator = ObjectBoxToRoomMigrator() + objectBoxToRoomMigrator.kiwixRoomDatabase = kiwixRoomDatabase + objectBoxToRoomMigrator.boxStore = boxStore + objectBoxToRoomMigrator.sharedPreferenceUtil = SharedPreferenceUtil(context) + } + + @After + fun cleanup() { + kiwixRoomDatabase.close() + boxStore.close() + } + + @Test + fun migrateRecentSearch_shouldInsertDataIntoRoomDatabase() = runBlocking { + val box = boxStore.boxFor(RecentSearchEntity::class.java) + // clear both databases for recent searches to test more edge cases + clearRecentSearchDatabases(box) + val expectedSearchTerm = "test search" + val expectedZimId = "8812214350305159407L" + val expectedUrl = "http://kiwix.app/mainPage" + val recentSearchEntity = + RecentSearchEntity(searchTerm = expectedSearchTerm, zimId = expectedZimId, url = expectedUrl) + // insert into object box + box.put(recentSearchEntity) + // migrate data into room database + objectBoxToRoomMigrator.migrateRecentSearch(box) + // check if data successfully migrated to room + val actual = kiwixRoomDatabase.recentSearchRoomDao().search(expectedZimId).first() + assertEquals(actual.size, 1) + assertEquals(actual[0].searchTerm, expectedSearchTerm) + assertEquals(actual[0].zimId, expectedZimId) + + // clear both databases for recent searches to test more edge cases + clearRecentSearchDatabases(box) + + // Migrate data from empty ObjectBox database + objectBoxToRoomMigrator.migrateRecentSearch(box) + val actualData = kiwixRoomDatabase.recentSearchRoomDao().fullSearch().first() + assertTrue(actualData.isEmpty()) + + // Test if data successfully migrated to Room and existing data is preserved + val existingSearchTerm = "existing search" + val existingZimId = "8812214350305159407L" + kiwixRoomDatabase.recentSearchRoomDao() + .saveSearch(existingSearchTerm, existingZimId, "$expectedUrl/1") + box.put(recentSearchEntity) + // Migrate data into Room database + objectBoxToRoomMigrator.migrateRecentSearch(box) + val actualDataAfterMigration = kiwixRoomDatabase.recentSearchRoomDao().fullSearch().first() + assertEquals(1, actual.size) + val existingItem = + actualDataAfterMigration.find { + it.searchTerm == existingSearchTerm && it.zimId == existingZimId + } + assertNotNull(existingItem) + val newItem = + actualDataAfterMigration.find { + it.searchTerm == expectedSearchTerm && it.zimId == expectedZimId + } + assertNotNull(newItem) + + clearRecentSearchDatabases(box) + + // Test migration if ObjectBox has null values + lateinit var undefinedSearchTerm: String + lateinit var undefinedZimId: String + lateinit var undefinedUrl: String + try { + val invalidSearchEntity = + RecentSearchEntity( + searchTerm = undefinedSearchTerm, + zimId = undefinedZimId, + url = undefinedUrl + ) + box.put(invalidSearchEntity) + // Migrate data into Room database + objectBoxToRoomMigrator.migrateRecentSearch(box) + } catch (_: Exception) { + } + // Ensure Room database remains empty or unaffected by the invalid data + val actualDataAfterInvalidMigration = + kiwixRoomDatabase.recentSearchRoomDao().fullSearch().first() + assertTrue(actualDataAfterInvalidMigration.isEmpty()) + + // Test large data migration for recent searches + val numEntities = 1000 + // Insert a large number of recent search entities into ObjectBox + for (i in 1..numEntities) { + val searchTerm = "search_$i" + val zimId = "$i" + val recentSearchEntity = + RecentSearchEntity(searchTerm = searchTerm, zimId = zimId, url = "$expectedUrl$i") + box.put(recentSearchEntity) + } + val startTime = System.currentTimeMillis() + // Migrate data into Room database + objectBoxToRoomMigrator.migrateRecentSearch(box) + val endTime = System.currentTimeMillis() + val migrationTime = endTime - startTime + // Check if data successfully migrated to Room + val actualDataAfterLargeMigration = + kiwixRoomDatabase.recentSearchRoomDao().fullSearch().first() + assertEquals(numEntities, actualDataAfterLargeMigration.size) + // Assert that the migration completes within a reasonable time frame + assertTrue("Migration took too long: $migrationTime ms", migrationTime < 5000) + } + + private fun clearRecentSearchDatabases(box: Box) { + // delete history for testing other edge cases + kiwixRoomDatabase.recentSearchRoomDao().deleteSearchHistory() + box.removeAll() + } +} diff --git a/app/src/androidTest/java/org/kiwix/kiwixmobile/RecentSearchRoomDaoTest.kt b/app/src/androidTest/java/org/kiwix/kiwixmobile/RecentSearchRoomDaoTest.kt new file mode 100644 index 000000000..e4b5cbe5e --- /dev/null +++ b/app/src/androidTest/java/org/kiwix/kiwixmobile/RecentSearchRoomDaoTest.kt @@ -0,0 +1,108 @@ +/* + * Kiwix Android + * Copyright (c) 2024 Kiwix + * 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 org.kiwix.kiwixmobile + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.core.IsEqual.equalTo +import org.junit.After +import org.junit.Test +import org.junit.runner.RunWith +import org.kiwix.kiwixmobile.core.dao.RecentSearchRoomDao +import org.kiwix.kiwixmobile.core.data.KiwixRoomDatabase + +@RunWith(AndroidJUnit4::class) +class RecentSearchRoomDaoTest { + + private lateinit var kiwixRoomDatabase: KiwixRoomDatabase + private lateinit var recentSearchRoomDao: RecentSearchRoomDao + + @After + fun tearDown() { + kiwixRoomDatabase.close() + } + + @Test + fun testRecentSearchRoomDao() = runBlocking { + val zimId = "8812214350305159407L" + val url = "http://kiwix.app/mainPage" + val context = ApplicationProvider.getApplicationContext() + kiwixRoomDatabase = Room.inMemoryDatabaseBuilder(context, KiwixRoomDatabase::class.java).build() + recentSearchRoomDao = kiwixRoomDatabase.recentSearchRoomDao() + // Save a recent search entity + val query = + "This is a long search term to test whether it will be saved into the room database." + recentSearchRoomDao.saveSearch(query, zimId, url) + // Search for recent search entities with a matching zimId + val result = getRecentSearchByZimId(zimId) + // Verify that the result contains the saved entity + assertThat(result.size, equalTo(1)) + assertThat(result[0].searchTerm, equalTo(query)) + assertThat(result[0].zimId, equalTo(zimId)) + + // Delete the saved entity by search term + recentSearchRoomDao.deleteSearchString(query) + // Verify that the result does not contain the deleted entity + assertThat(getRecentSearchByZimId(zimId).size, equalTo(0)) + + // Testing deleting all recent searched history + // Save two recent search entities + recentSearchRoomDao.saveSearch(query, zimId, url) + recentSearchRoomDao.saveSearch("query 2", zimId, url) + // Delete all recent search entities + recentSearchRoomDao.deleteSearchHistory() + // Verify that the result is empty + assertThat(getRecentSearchByZimId(zimId).size, equalTo(0)) + + // test to save empty values for recent search + val emptyQuery = "" + recentSearchRoomDao.saveSearch(emptyQuery, zimId, url) + // verify that the result is not empty + assertThat(getRecentSearchByZimId(zimId).size, equalTo(1)) + + // we are not saving undefined or null values into database. + // test to save undefined value for recent search. + lateinit var undefinedQuery: String + try { + recentSearchRoomDao.saveSearch(undefinedQuery, zimId, url) + assertThat( + "Undefined value was saved into database", + false + ) + } catch (e: Exception) { + assertThat("Undefined value was not saved, as expected.", true) + } + + // Delete all recent search entities for testing unicodes values + recentSearchRoomDao.deleteSearchHistory() + + // save unicode values into database + val unicodeQuery = "title \u03A3" // Unicode character for Greek capital letter Sigma + recentSearchRoomDao.saveSearch(unicodeQuery, zimId, url) + assertThat(getRecentSearchByZimId(zimId)[0].searchTerm, equalTo("title Σ")) + } + + private suspend fun getRecentSearchByZimId(zimId: String) = + recentSearchRoomDao.search(zimId).first() +} diff --git a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt index 0dd36db25..c1b516fbe 100644 --- a/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt +++ b/core/src/main/java/org/kiwix/kiwixmobile/core/dao/RecentSearchRoomDao.kt @@ -20,13 +20,8 @@ package org.kiwix.kiwixmobile.core.dao import androidx.room.Dao import androidx.room.Query -import io.objectbox.Box -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchEntity import org.kiwix.kiwixmobile.core.dao.entities.RecentSearchRoomEntity import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem @@ -80,18 +75,6 @@ abstract class RecentSearchRoomDao { @Query("DELETE FROM RecentSearchRoomEntity") abstract fun deleteSearchHistory() - fun migrationToRoomInsert( - box: Box - ) { - val searchRoomEntityList = box.all - searchRoomEntityList.forEachIndexed { _, recentSearchEntity -> - CoroutineScope(Dispatchers.IO).launch { - saveSearch(recentSearchEntity.searchTerm, recentSearchEntity.zimId, recentSearchEntity.url) - // Todo Should we remove object store data now? - } - } - } - companion object { private const val NUM_RECENT_RESULTS = 100 } diff --git a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecentsTest.kt b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecentsTest.kt index 4b80c9774..36a8e0c8a 100644 --- a/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecentsTest.kt +++ b/core/src/test/java/org/kiwix/kiwixmobile/core/search/viewmodel/effects/SaveSearchToRecentsTest.kt @@ -53,7 +53,7 @@ internal class SaveSearchToRecentsTest { @Test fun `invoke with non null Id saves search`() = runBlocking { - val id = "id" + val id = "8812214350305159407L" SaveSearchToRecents( newRecentSearchDao, searchListItem,