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.
This commit is contained in:
MohitMaliFtechiz 2024-05-29 13:18:06 +05:30 committed by MohitMaliFtechiz
parent 106e83c704
commit 0613d36ab9
5 changed files with 357 additions and 18 deletions

View File

@ -0,0 +1,80 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<Context>()
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)
}
}

View File

@ -0,0 +1,168 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<RecentSearchEntity>) {
// delete history for testing other edge cases
kiwixRoomDatabase.recentSearchRoomDao().deleteSearchHistory()
box.removeAll()
}
}

View File

@ -0,0 +1,108 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* 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 <http://www.gnu.org/licenses/>.
*
*/
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<Context>()
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()
}

View File

@ -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<RecentSearchEntity>
) {
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
}

View File

@ -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,