From f927e7c86d19dbee1f9b1ed99ffed401dfeeba5c Mon Sep 17 00:00:00 2001 From: Tobias_Groza <304016-Tobias_Groza@noreply.gitlab.com> Date: Fri, 9 Aug 2024 13:42:11 +0200 Subject: [PATCH] Add test for AppMetadataFTS table migration --- .../FtsCaseInsensitiveMigrationTest.kt | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt diff --git a/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt new file mode 100644 index 000000000..d0b785e49 --- /dev/null +++ b/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt @@ -0,0 +1,187 @@ +package org.fdroid.database + +import android.content.ContentValues +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.room.Room +import androidx.room.testing.MigrationTestHelper +import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.runBlocking +import org.fdroid.database.TestUtils.getOrFail +import org.fdroid.test.TestUtils.getRandomString +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.random.Random +import kotlin.test.assertEquals + +private const val TEST_DB = "fts-test" + +@RunWith(AndroidJUnit4::class) +internal class FtsCaseInsensitiveMigrationTest { + + @get:Rule + val helper: MigrationTestHelper = MigrationTestHelper( + instrumentation = InstrumentationRegistry.getInstrumentation(), + databaseClass = FDroidDatabaseInt::class.java, + specs = emptyList(), + openFactory = FrameworkSQLiteOpenHelperFactory(), + ) + + @get:Rule + val instantExec = InstantTaskExecutorRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + private val repo = ContentValues().apply { + put("repoId", 1) + put("name", Converters.localizedTextV2toString(mapOf( + "de" to "a", "en-US" to "b"))) + put("address", getRandomString()) + put("certificate", "abcdef") + put("description", Converters.localizedTextV2toString(mapOf( + "de" to "aa", "en-US" to "bb"))) + put("version", Random.nextLong()) + put("timestamp", Random.nextLong()) + } + + private val repoPrefs = ContentValues().apply { + put("repoId", 1) + put("enabled", true) + put("weight", 1) + } + + private val oeffiMetadata = ContentValues().apply { + put("packageName", "de.schildbach.oeffi") + put("repoId", 1) + put("name", Converters.localizedTextV2toString(mapOf( + "de" to "Öffi", "en-US" to "Offi"))) + put("description", Converters.localizedTextV2toString(mapOf( + "de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"))) + put("license", "GPL-3.0") + put("summary", Converters.localizedTextV2toString(mapOf( + "de" to "Der König des Fahrplandschungels!", + "en-US" to " King of public transit planning!"))) + put("localizedName", "Öffi") + put("localizedSummary", "Der König des Fahrplandschungels!") + put("added", Random.nextLong()) + put("lastUpdated", Random.nextLong()) + put("isCompatible", true) + } + + private val oeffiVersion = ContentValues().apply { + put("repoId", 1) + put("packageName", "de.schildbach.oeffi") + put("versionId", 1) + put("added", 1000) + put("isCompatible", true) + put("file_name", "transportr.apk") + put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049") + put("manifest_versionName", "1.0.0") + put("manifest_versionCode", 1) + } + + private val transportrMetadata = ContentValues().apply { + put("packageName", "de.grobox.liberario") + put("repoId", 1) + put("name", Converters.localizedTextV2toString(mapOf( + "de" to "Transportr", "en-US" to "Transportr"))) + put("description", Converters.localizedTextV2toString(mapOf( + "de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"))) + put("license", "GPL-3.0") + put("summary", Converters.localizedTextV2toString(mapOf( + "de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking", + "en-US" to "Free Public Transport Assistant without Ads or Tracking"))) + put("localizedName", "Transportr") + put("localizedSummary", + "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking") + put("added", Random.nextLong()) + put("lastUpdated", Random.nextLong()) + put("isCompatible", true) + } + + private val transportrVersion = ContentValues().apply { + put("repoId", 1) + put("packageName", "de.grobox.liberario") + put("versionId", 1) + put("added", 1000) + put("isCompatible", true) + put("file_name", "transportr.apk") + put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049") + put("manifest_versionName", "1.0.0") + put("manifest_versionCode", 1) + } + + @Test + fun testMigration() = runBlocking { + + helper.createDatabase(TEST_DB, 5).use { db -> + // Database has schema version 5. Insert some data using SQL queries. + // We can't use DAO classes because they expect the latest schema. + db.insert(CoreRepository.TABLE, SQLiteDatabase.CONFLICT_FAIL, repo) + db.insert(RepositoryPreferences.TABLE, SQLiteDatabase.CONFLICT_FAIL, repoPrefs) + db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiMetadata) + db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, transportrMetadata) + db.insert(Version.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiVersion) + db.insert(Version.TABLE, SQLiteDatabase.CONFLICT_FAIL, transportrVersion) + + // Show that search is case sensitive for diacritics + assertSearch(db, "Öffi", 1) + assertSearch(db, "öffi", 0) + // using no diacritics does match any case + assertSearch(db, "Offi", 0) + assertSearch(db, "offi", 0) + // it's case sensitive so only "öffentlichen" from Transportr is found + assertSearch(db, "öff*", 1) + assertSearch(db, "König", 1) + + } + + helper.runMigrationsAndValidate(TEST_DB, 6, true, MIGRATION_5_6).close() + + // now get the Room DB, so we can use our DAOs for verifying the migration + Room.databaseBuilder(context, FDroidDatabaseInt::class.java, TEST_DB) + .allowMainThreadQueries() + .build().use { db -> + // assert that apps are still there + val metadata = db.getAppDao().getAppMetadata() + assertEquals(2, metadata.size) + // default search with no diacritics + assertGetAppListItems(db, "*Transport*", 1) + assertGetAppListItems(db, "Transportr", 1) + assertGetAppListItems(db, "*f*", 2) + // no or wrong diacritics + assertGetAppListItems(db, "Offi", 0) + assertGetAppListItems(db, "offi", 0) + assertGetAppListItems(db, "õffi", 0) + assertGetAppListItems(db, "*Offi*", 0) + assertGetAppListItems(db, "*offi*", 0) + assertGetAppListItems(db, "Konig", 0) + // correct diacritics + assertGetAppListItems(db, "*Öffi*", 1) + assertGetAppListItems(db, "*öffi*", 1) + assertGetAppListItems(db, "Öffi", 1) + assertGetAppListItems(db, "öffi", 1) + // both apps have "öff" in their name or summary + assertGetAppListItems(db, "*öff*", 2) + assertGetAppListItems(db, "König", 1) + } + } + + private fun assertSearch(db: SupportSQLiteDatabase, query: String, expected: Int) { + db.query("SELECT * FROM AppMetadataFts WHERE AppMetadataFts MATCH '$query'") + .use { cursor -> assertEquals(expected, cursor.count) } + } + + private fun assertGetAppListItems(db: FDroidDatabaseInt, query: String, expected: Int) { + db.getAppDao().getAppListItems(query).getOrFail().let { + assertEquals(expected, it.size) + } + } + +}