mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-06-18 04:39:45 -04:00
Clean up app gradle modules
This commit is contained in:
@@ -1,290 +0,0 @@
|
||||
package org.fdroid.fdroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyOrder
|
||||
import org.fdroid.database.AppManifest
|
||||
import org.fdroid.database.AppVersion
|
||||
import org.fdroid.database.DbUpdateChecker
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.database.UpdatableApp
|
||||
import org.fdroid.download.Downloader
|
||||
import org.fdroid.fdroid.data.App
|
||||
import org.fdroid.fdroid.installer.InstallManagerService
|
||||
import org.fdroid.fdroid.installer.Installer
|
||||
import org.fdroid.fdroid.installer.InstallerFactory
|
||||
import org.fdroid.index.RepoManager
|
||||
import org.fdroid.index.v2.FileV1
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class AppUpdateManagerTest {
|
||||
|
||||
private val context: Context = mockk()
|
||||
private val repoManager: RepoManager = mockk()
|
||||
private val updateChecker: DbUpdateChecker = mockk()
|
||||
private val preferences: Preferences = mockk()
|
||||
private val downloaderFactory: org.fdroid.download.DownloaderFactory = mockk()
|
||||
private val statusManager: AppUpdateStatusManager = mockk()
|
||||
|
||||
private val appUpdateManager =
|
||||
AppUpdateManager(context, repoManager, updateChecker, downloaderFactory, statusManager)
|
||||
|
||||
private val downloader: Downloader = mockk()
|
||||
private val installer: Installer = mockk()
|
||||
private val packageManager: PackageManager = mockk()
|
||||
private val installManagerService: InstallManagerService = mockk(relaxed = true)
|
||||
private val repoUri: Uri = mockk(relaxed = true)
|
||||
|
||||
private val app1 = App().apply {
|
||||
packageName = "org.example.1"
|
||||
name = "One"
|
||||
repoId = 42
|
||||
}
|
||||
private val app2 = App().apply {
|
||||
packageName = "org.example.2"
|
||||
name = "Two"
|
||||
repoId = 42
|
||||
}
|
||||
private val repo: Repository = mockk(relaxed = true) {
|
||||
every { repoId } returns app1.repoId
|
||||
every { address } returns "https://example.org/repo"
|
||||
}
|
||||
private val file1 = FileV1("one", "one")
|
||||
private val file2 = FileV1("two", "one")
|
||||
private val version1: AppVersion = mockk<AppVersion>(relaxed = true) {
|
||||
every { repoId } returns app1.repoId
|
||||
every { packageName } returns app1.packageName
|
||||
every { manifest } returns AppManifest("1", 1)
|
||||
every { file } returns file1
|
||||
}
|
||||
private val version2: AppVersion = mockk<AppVersion>(relaxed = true) {
|
||||
every { repoId } returns app2.repoId
|
||||
every { packageName } returns app2.packageName
|
||||
every { manifest } returns AppManifest("2", 2)
|
||||
every { file } returns file2
|
||||
}
|
||||
|
||||
// need to mock apps as their constructor is internal
|
||||
private val updatableApp1: UpdatableApp = mockk(relaxed = true) {
|
||||
every { packageName } returns app1.packageName
|
||||
every { name } returns app1.name
|
||||
every { summary } returns app1.summary
|
||||
every { repoId } returns app1.repoId
|
||||
every { update } returns version1
|
||||
every { update.file } returns file1
|
||||
every { update.manifest } returns AppManifest("1", 1)
|
||||
every { update.repoId } returns app1.repoId
|
||||
every { update.packageName } returns app1.packageName
|
||||
every { installedVersionCode } returns app1.installedVersionCode
|
||||
}
|
||||
private val updatableApp2: UpdatableApp = mockk(relaxed = true) {
|
||||
every { packageName } returns app2.packageName
|
||||
every { name } returns app2.name
|
||||
every { summary } returns app2.summary
|
||||
every { repoId } returns app2.repoId
|
||||
every { update } returns version2
|
||||
every { update.repoId } returns app2.repoId
|
||||
every { update.packageName } returns app2.packageName
|
||||
every { update.file } returns file2
|
||||
every { update.manifest } returns AppManifest("2", 2)
|
||||
every { installedVersionCode } returns app2.installedVersionCode
|
||||
}
|
||||
|
||||
init {
|
||||
mockkStatic(Preferences::get)
|
||||
every { Preferences.get() } returns preferences
|
||||
|
||||
mockkStatic(InstallManagerService::getInstance)
|
||||
every { InstallManagerService.getInstance(any()) } returns installManagerService
|
||||
|
||||
mockkStatic(Uri::parse)
|
||||
every { Uri.parse(any()) } returns repoUri
|
||||
|
||||
mockkStatic(InstallerFactory::create)
|
||||
every { InstallerFactory.create(any(), any(), any()) } returns installer
|
||||
|
||||
every { context.packageManager } returns packageManager
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoUpdates() {
|
||||
val updates = emptyList<UpdatableApp>()
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
|
||||
assertTrue(appUpdateManager.updateApps())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSomeUpdates() {
|
||||
val updates = listOf(updatableApp1, updatableApp2)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns null
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { repoManager.getRepository(app2.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(any<String>(), any<Int>())
|
||||
} returns getPackageInfo(0)
|
||||
every { context.cacheDir } returns File("/tmp/fdroid-app-update-test")
|
||||
every { downloaderFactory.create(repo, any(), file1, any()) } returns downloader
|
||||
every { downloaderFactory.create(repo, any(), file2, any()) } returns downloader
|
||||
every { downloader.setListener(any()) } just Runs
|
||||
every { downloader.download() } just Runs
|
||||
every { installer.installPackage(any(), any()) } just Runs
|
||||
|
||||
assertTrue(appUpdateManager.updateApps())
|
||||
|
||||
verify(exactly = 2) {
|
||||
installManagerService.onDownloadComplete(any())
|
||||
installer.installPackage(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVersionAlreadyInstalled() {
|
||||
val updates = listOf(updatableApp1, updatableApp2)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns null
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { repoManager.getRepository(app2.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(app1.packageName, any<Int>())
|
||||
} returns getPackageInfo(1)
|
||||
every {
|
||||
packageManager.getPackageInfo(app2.packageName, any<Int>())
|
||||
} returns getPackageInfo(2)
|
||||
|
||||
assertTrue(appUpdateManager.updateApps())
|
||||
|
||||
verify(exactly = 0) {
|
||||
installManagerService.onDownloadComplete(any())
|
||||
installer.installPackage(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOurOwnAppIsUpdatedLast() {
|
||||
val installer1: Installer = mockk()
|
||||
val installer2: Installer = mockk()
|
||||
val updates = listOf(updatableApp1, updatableApp2)
|
||||
val updatesSorted = listOf(updatableApp2, updatableApp1)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns app1.packageName // we are app1
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { repoManager.getRepository(app2.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updatesSorted, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(any<String>(), any<Int>())
|
||||
} returns getPackageInfo(0)
|
||||
every { context.cacheDir } returns File("/tmp/fdroid-app-update-test")
|
||||
every { downloaderFactory.create(repo, any(), file1, any()) } returns downloader
|
||||
every { downloaderFactory.create(repo, any(), file2, any()) } returns downloader
|
||||
every { downloader.setListener(any()) } just Runs
|
||||
every { downloader.download() } just Runs
|
||||
every {
|
||||
InstallerFactory.create(any(), match { it.packageName == app1.packageName }, any())
|
||||
} returns installer1
|
||||
every {
|
||||
InstallerFactory.create(any(), match { it.packageName == app2.packageName }, any())
|
||||
} returns installer2
|
||||
every { installer1.installPackage(any(), any()) } just Runs
|
||||
every { installer2.installPackage(any(), any()) } just Runs
|
||||
|
||||
assertTrue(appUpdateManager.updateApps())
|
||||
|
||||
verifyOrder {
|
||||
// app1 gets installed last, because it is us and updating us kills us
|
||||
installer2.installPackage(any(), any())
|
||||
installer1.installPackage(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFailedDownloadSkipsUpdate() {
|
||||
val updates = listOf(updatableApp1)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns null
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(any<String>(), any<Int>())
|
||||
} returns getPackageInfo(0)
|
||||
every { context.cacheDir } returns File("/tmp/fdroid-app-update-test")
|
||||
every { downloaderFactory.create(repo, any(), file1, any()) } returns downloader
|
||||
every { downloader.setListener(any()) } just Runs
|
||||
every { downloader.download() } throws IOException("foo bar")
|
||||
|
||||
assertFalse(appUpdateManager.updateApps())
|
||||
|
||||
verify(exactly = 0) {
|
||||
installer.installPackage(any(), any())
|
||||
}
|
||||
verify {
|
||||
installManagerService.onDownloadFailed(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageInfo(versionCode: Int) = PackageInfo().apply {
|
||||
@Suppress("DEPRECATION") // longVersionCode doesn't work
|
||||
this.versionCode = versionCode
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Senecto Limited
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class PreferencesTest {
|
||||
private static final String TAG = "PreferencesTest";
|
||||
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private SharedPreferences defaults;
|
||||
|
||||
/**
|
||||
* Manually read the {@code preferences.xml} defaults to a separate
|
||||
* instance. Clear the preference state before each test so that each
|
||||
* test starts as if it was a first time install.
|
||||
*/
|
||||
@Before
|
||||
public void setup() {
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
defaults = getSharedPreferences(CONTEXT);
|
||||
assertTrue(defaults.getAll().size() > 0);
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(CONTEXT);
|
||||
Log.d(TAG, "Clearing DefaultSharedPreferences containing: " + sharedPreferences.getAll().size());
|
||||
sharedPreferences.edit().clear().commit();
|
||||
assertEquals(0, sharedPreferences.getAll().size());
|
||||
|
||||
SharedPreferences defaultValueSp = CONTEXT.getSharedPreferences(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES,
|
||||
Context.MODE_PRIVATE);
|
||||
defaultValueSp.edit().remove(PreferenceManager.KEY_HAS_SET_DEFAULT_VALUES).commit();
|
||||
}
|
||||
|
||||
public static SharedPreferences getSharedPreferences(Context context) {
|
||||
String sharedPreferencesName = context.getPackageName() + "_preferences_defaults";
|
||||
PreferenceManager pm = new PreferenceManager(context);
|
||||
pm.setSharedPreferencesName(sharedPreferencesName);
|
||||
pm.setSharedPreferencesMode(Context.MODE_PRIVATE);
|
||||
pm.inflateFromResource(context, R.xml.preferences, null);
|
||||
return pm.getSharedPreferences();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the defaults are being set when using
|
||||
* {@link PreferenceManager#getDefaultSharedPreferences(Context)}, and that
|
||||
* the values match. {@link Preferences#Preferences(Context)} sets the
|
||||
* values of {@link Preferences#PREF_LOCAL_REPO_NAME} and
|
||||
* {@link Preferences#PREF_AUTO_DOWNLOAD_INSTALL_UPDATES} dynamically, so
|
||||
* there are two more preferences.
|
||||
*/
|
||||
@Test
|
||||
public void testSetDefaultValues() {
|
||||
Preferences.setupForTests(CONTEXT);
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(CONTEXT);
|
||||
assertEquals(defaults.getAll().size() + 2, sharedPreferences.getAll().size());
|
||||
assertTrue(sharedPreferences.contains(Preferences.PREF_LOCAL_REPO_NAME));
|
||||
assertTrue(sharedPreferences.contains(Preferences.PREF_AUTO_DOWNLOAD_INSTALL_UPDATES));
|
||||
|
||||
Map<String, ?> entries = sharedPreferences.getAll();
|
||||
for (Map.Entry<String, ?> entry : defaults.getAll().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
assertTrue(sharedPreferences.contains(entry.getKey()));
|
||||
assertEquals(entries.get(key), value);
|
||||
}
|
||||
|
||||
String key = Preferences.PREF_EXPERT;
|
||||
boolean defaultValue = defaults.getBoolean(key, false);
|
||||
sharedPreferences.edit().putBoolean(key, !defaultValue).commit();
|
||||
assertNotEquals(defaultValue, sharedPreferences.getBoolean(key, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodsUseDefaults() {
|
||||
Preferences.setupForTests(CONTEXT);
|
||||
Preferences preferences = Preferences.get();
|
||||
|
||||
assertEquals(defaults.getBoolean(Preferences.PREF_EXPERT, false),
|
||||
preferences.expertMode());
|
||||
assertEquals(defaults.getBoolean(Preferences.PREF_FORCE_TOUCH_APPS, false),
|
||||
preferences.isForceOldIndexEnabled());
|
||||
assertEquals(defaults.getBoolean(Preferences.PREF_FORCE_OLD_INDEX, false),
|
||||
preferences.isForceOldIndexEnabled());
|
||||
assertEquals(defaults.getBoolean(Preferences.PREF_PREVENT_SCREENSHOTS, false),
|
||||
preferences.preventScreenshots());
|
||||
assertEquals(defaults.getStringSet(Preferences.PREF_SHOW_ANTI_FEATURES, null),
|
||||
preferences.showAppsWithAntiFeatures());
|
||||
assertEquals(defaults.getBoolean(Preferences.PREF_SHOW_INCOMPAT_VERSIONS, false),
|
||||
preferences.showIncompatibleVersions());
|
||||
assertEquals(defaults.getBoolean(Preferences.PREF_UPDATE_NOTIFICATION_ENABLED, false),
|
||||
preferences.isUpdateNotificationEnabled());
|
||||
|
||||
assertEquals(Long.parseLong(defaults.getString(Preferences.PREF_KEEP_CACHE_TIME, null)),
|
||||
preferences.getKeepCacheTime());
|
||||
|
||||
assertEquals(Preferences.Theme.valueOf(defaults.getString(Preferences.PREF_THEME, null)),
|
||||
preferences.getTheme());
|
||||
|
||||
// now test setting the prefs
|
||||
boolean defaultValue = defaults.getBoolean(Preferences.PREF_EXPERT, false);
|
||||
preferences.setExpertMode(!defaultValue);
|
||||
assertNotEquals(defaultValue, preferences.expertMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* When {@link Preferences#Preferences(Context)} calls
|
||||
* {@link PreferenceManager#setDefaultValues(Context, int, boolean)}, any
|
||||
* existing preference values should not be overridden.
|
||||
*/
|
||||
@Test
|
||||
public void testMigrationWithSetDefaultValues() {
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(CONTEXT);
|
||||
long testValue = (long) (Math.random() * Long.MAX_VALUE);
|
||||
String testValueString = String.valueOf(testValue);
|
||||
assertNotEquals(testValueString, defaults.getString(Preferences.PREF_KEEP_CACHE_TIME, "not a long"));
|
||||
sharedPreferences.edit().putString(Preferences.PREF_KEEP_CACHE_TIME, testValueString).commit();
|
||||
|
||||
Preferences.setupForTests(CONTEXT);
|
||||
assertEquals(testValue, Preferences.get().getKeepCacheTime());
|
||||
assertNotEquals(Long.parseLong(defaults.getString(Preferences.PREF_KEEP_CACHE_TIME, null)),
|
||||
Preferences.get().getKeepCacheTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMirrorErrorMethods() {
|
||||
Preferences.setupForTests(CONTEXT);
|
||||
Preferences preferences = Preferences.get();
|
||||
// serialize an empty map
|
||||
preferences.setMirrorErrorData(new HashMap<>(0));
|
||||
Map<String, Integer> result = preferences.getMirrorErrorData();
|
||||
// deserializing should return an empty map without throwing any exceptions
|
||||
assertNotNull(result);
|
||||
assertEquals(result.size(), 0);
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package org.fdroid.fdroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Looper
|
||||
import app.cash.turbine.test
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.verify
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertFalse
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.fdroid.CompatibilityChecker
|
||||
import org.fdroid.database.FDroidDatabase
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.database.RepositoryDao
|
||||
import org.fdroid.fdroid.work.RepoUpdateWorker
|
||||
import org.fdroid.index.IndexUpdateResult
|
||||
import org.fdroid.index.RepoManager
|
||||
import org.fdroid.index.RepoUpdater
|
||||
import org.fdroid.index.v1.IndexV1Updater
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
|
||||
class RepoUpdateManagerTest {
|
||||
|
||||
private val context: Context = mockk()
|
||||
private val db: FDroidDatabase = mockk()
|
||||
private val repoManager: RepoManager = mockk()
|
||||
private val notificationManager: NotificationManager = mockk()
|
||||
private val compatibilityChecker: CompatibilityChecker = mockk()
|
||||
private val repoUpdater: RepoUpdater = mockk()
|
||||
private val indexV1Updater: IndexV1Updater = mockk()
|
||||
|
||||
private val packageManager: PackageManager = mockk()
|
||||
private val preferences: Preferences = mockk()
|
||||
private val repositoryDao: RepositoryDao = mockk()
|
||||
|
||||
init {
|
||||
// needed for Flow#asLiveData()
|
||||
mockkStatic(Looper::class)
|
||||
every { Looper.getMainLooper() } returns mockk<Looper> {
|
||||
every { thread } returns Thread.currentThread()
|
||||
}
|
||||
// avoids having to deal with WorkManager here
|
||||
mockkObject(RepoUpdateWorker)
|
||||
every { RepoUpdateWorker.getAutoUpdateWorkInfo(any()) } returns mockk()
|
||||
|
||||
mockkStatic(Preferences::get)
|
||||
every { Preferences.get() } returns preferences
|
||||
|
||||
every { context.packageManager } returns packageManager
|
||||
every { context.getString(any(), any()) } returns "foo bar"
|
||||
every { db.getRepositoryDao() } returns repositoryDao
|
||||
}
|
||||
|
||||
private val repoUpdateManager = RepoUpdateManager(
|
||||
context = context,
|
||||
db = db,
|
||||
repoManager = repoManager,
|
||||
notificationManager = notificationManager,
|
||||
compatibilityChecker = compatibilityChecker,
|
||||
repoUpdater = repoUpdater,
|
||||
indexV1Updater = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testUpdateSingleRepo() {
|
||||
val repo: Repository = mockk(relaxed = true)
|
||||
|
||||
every { repoManager.getRepository(1L) } returns repo
|
||||
every { preferences.isUpdateNotificationEnabled } returns true
|
||||
every { notificationManager.showUpdateRepoNotification(any(), false, null) } just Runs
|
||||
every { repoUpdater.update(repo) } returns IndexUpdateResult.Processed
|
||||
every { notificationManager.cancelUpdateRepoNotification() } just Runs
|
||||
every { repositoryDao.walCheckpoint() } just Runs
|
||||
|
||||
runBlocking {
|
||||
repoUpdateManager.isUpdating.test {
|
||||
assertFalse(awaitItem()) // not updating
|
||||
// do the update now
|
||||
assertEquals(IndexUpdateResult.Processed, repoUpdateManager.updateRepo(1L))
|
||||
assertTrue(awaitItem()) // now updating
|
||||
assertFalse(awaitItem()) // at the end again not updating
|
||||
}
|
||||
}
|
||||
|
||||
verify {
|
||||
notificationManager.cancelUpdateRepoNotification()
|
||||
repositoryDao.walCheckpoint()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateSingleForcedV1Repo() {
|
||||
val repoUpdateManager = RepoUpdateManager(
|
||||
context = context,
|
||||
db = db,
|
||||
repoManager = repoManager,
|
||||
notificationManager = notificationManager,
|
||||
compatibilityChecker = compatibilityChecker,
|
||||
repoUpdater = repoUpdater,
|
||||
indexV1Updater = indexV1Updater,
|
||||
)
|
||||
val repo: Repository = mockk(relaxed = true)
|
||||
|
||||
every { repoManager.getRepository(1L) } returns repo
|
||||
every { preferences.isUpdateNotificationEnabled } returns true
|
||||
every { notificationManager.showUpdateRepoNotification(any(), false, null) } just Runs
|
||||
every { indexV1Updater.update(repo) } returns IndexUpdateResult.Unchanged
|
||||
every { notificationManager.cancelUpdateRepoNotification() } just Runs
|
||||
every { repositoryDao.walCheckpoint() } just Runs
|
||||
|
||||
runBlocking {
|
||||
repoUpdateManager.isUpdating.test {
|
||||
assertFalse(awaitItem()) // not updating
|
||||
// do the update now and expect unchanged result
|
||||
assertEquals(IndexUpdateResult.Unchanged, repoUpdateManager.updateRepo(1L))
|
||||
assertTrue(awaitItem()) // now updating
|
||||
assertFalse(awaitItem()) // at the end again not updating
|
||||
}
|
||||
}
|
||||
|
||||
verify {
|
||||
notificationManager.cancelUpdateRepoNotification()
|
||||
repositoryDao.walCheckpoint()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateSingleRepoCleansUpException() {
|
||||
val repo: Repository = mockk(relaxed = true)
|
||||
|
||||
every { repoManager.getRepository(1L) } returns repo
|
||||
every { preferences.isUpdateNotificationEnabled } returns true
|
||||
every { notificationManager.showUpdateRepoNotification(any(), false, null) } just Runs
|
||||
every { repoUpdater.update(repo) } throws IOException()
|
||||
every { notificationManager.cancelUpdateRepoNotification() } just Runs
|
||||
every { repositoryDao.walCheckpoint() } just Runs
|
||||
|
||||
runBlocking {
|
||||
repoUpdateManager.isUpdating.test {
|
||||
assertFalse(awaitItem()) // not updating
|
||||
// do the update now
|
||||
assertThrows(IOException::class.java) {
|
||||
repoUpdateManager.updateRepo(1L)
|
||||
}
|
||||
assertTrue(awaitItem()) // now updating
|
||||
assertFalse(awaitItem()) // at the end again not updating
|
||||
}
|
||||
}
|
||||
|
||||
verify {
|
||||
notificationManager.cancelUpdateRepoNotification()
|
||||
repositoryDao.walCheckpoint()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateReposDoesntDoQuickRecheck() {
|
||||
// we did a check just now
|
||||
every { preferences.lastUpdateCheck } returns System.currentTimeMillis() - 500
|
||||
|
||||
repoUpdateManager.updateRepos()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateThreeReposOneDisabled() {
|
||||
val repo1: Repository = mockk(relaxed = true) {
|
||||
every { enabled } returns true
|
||||
}
|
||||
val repo2: Repository = mockk(relaxed = true) {
|
||||
every { enabled } returns false
|
||||
}
|
||||
val repo3: Repository = mockk(relaxed = true) {
|
||||
every { enabled } returns true
|
||||
}
|
||||
|
||||
every { preferences.lastUpdateCheck } returns 1337
|
||||
every { repositoryDao.getRepositories() } returns listOf(repo1, repo2)
|
||||
every { preferences.isUpdateNotificationEnabled } returns true
|
||||
every { notificationManager.showUpdateRepoNotification(any(), false, null) } just Runs
|
||||
every { repoUpdater.update(repo1) } returns IndexUpdateResult.Unchanged
|
||||
every { repoUpdater.update(repo3) } returns IndexUpdateResult.Processed
|
||||
|
||||
every { notificationManager.cancelUpdateRepoNotification() } just Runs
|
||||
every { repositoryDao.walCheckpoint() } just Runs
|
||||
every { preferences.lastUpdateCheck = any() } just Runs
|
||||
|
||||
runBlocking {
|
||||
repoUpdateManager.isUpdating.test {
|
||||
assertFalse(awaitItem()) // not updating
|
||||
repoUpdateManager.updateRepos()
|
||||
assertTrue(awaitItem()) // now updating
|
||||
assertFalse(awaitItem()) // at the end again not updating
|
||||
}
|
||||
}
|
||||
|
||||
verify(exactly = 1) {
|
||||
notificationManager.cancelUpdateRepoNotification()
|
||||
repositoryDao.walCheckpoint()
|
||||
}
|
||||
// repo2 is disabled and should not get updated
|
||||
verify(exactly = 0) { repoUpdater.update(repo2) }
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Angus Gratton
|
||||
* Copyright (C) 2018 Senecto Limited
|
||||
*
|
||||
* 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.index.v1.IndexV1UpdaterKt;
|
||||
import org.fdroid.index.v2.FileV1;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class RepoUrlsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
/**
|
||||
* Private class describing a repository URL we're going to test, and
|
||||
* the file pattern for any files within that URL.
|
||||
*/
|
||||
private static class TestRepo {
|
||||
// Repo URL for the test case
|
||||
final String repoUrl;
|
||||
// String format pattern for generating file URLs, should contain a single %s for the filename
|
||||
final String fileUrlPattern;
|
||||
|
||||
TestRepo(String repoUrl, String fileUrlPattern) {
|
||||
this.repoUrl = repoUrl;
|
||||
this.fileUrlPattern = fileUrlPattern;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String APK_NAME = "test-v1.apk";
|
||||
|
||||
private static final TestRepo[] REPOS = {
|
||||
new TestRepo(
|
||||
"https://microg.org/fdroid/repo",
|
||||
"https://microg.org/fdroid/repo/%s"),
|
||||
new TestRepo(
|
||||
"http://bdf2wcxujkg6qqff.onion/fdroid/repo",
|
||||
"http://bdf2wcxujkg6qqff.onion/fdroid/repo/%s"),
|
||||
new TestRepo(
|
||||
"http://lysator7eknrfl47rlyxvgeamrv7ucefgrrlhk7rouv3sna25asetwid.onion/pub/fdroid/repo",
|
||||
"http://lysator7eknrfl47rlyxvgeamrv7ucefgrrlhk7rouv3sna25asetwid.onion/pub/fdroid/repo/%s"),
|
||||
new TestRepo(
|
||||
"https://mirrors.nju.edu.cn/fdroid/repo?fingerprint=43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB",
|
||||
"https://mirrors.nju.edu.cn/fdroid/repo/%s?fingerprint=43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB"),
|
||||
new TestRepo(
|
||||
"https://raw.githubusercontent.com/guardianproject/fdroid-repo/master/fdroid/repo",
|
||||
"https://raw.githubusercontent.com/guardianproject/fdroid-repo/master/fdroid/repo/%s"),
|
||||
new TestRepo(
|
||||
"content://com.android.externalstorage.documents/tree/1AFB-2402%3A/document/1AFB-2402%3Atesty.at.or.at%2Ffdroid%2Frepo",
|
||||
// note: to have a URL-escaped path in a format string pattern, we need to
|
||||
// %-escape all URL %
|
||||
"content://com.android.externalstorage.documents/tree/1AFB-2402%%3A/document/1AFB-2402%%3Atesty.at.or.at%%2Ffdroid%%2Frepo%%2F%s"),
|
||||
new TestRepo(
|
||||
"content://authority/tree/313E-1F1C%3A/document/313E-1F1C%3Aguardianproject.info%2Ffdroid%2Frepo",
|
||||
// note: to have a URL-escaped path in a format string pattern, we need to
|
||||
// %-escape all URL %
|
||||
"content://authority/tree/313E-1F1C%%3A/document/313E-1F1C%%3Aguardianproject.info%%2Ffdroid%%2Frepo%%2F%s"),
|
||||
new TestRepo(
|
||||
"http://10.20.31.244:8888/fdroid/repo?FINGERPRINT=35521D88285A9D06FBE33D35FB8B4BB872D753666CF981728E2249FEE6D2D0F2&SWAP=1&BSSID=FE:EE:DA:45:2D:4E",
|
||||
"http://10.20.31.244:8888/fdroid/repo/%s?FINGERPRINT=35521D88285A9D06FBE33D35FB8B4BB872D753666CF981728E2249FEE6D2D0F2&SWAP=1&BSSID=FE:EE:DA:45:2D:4E"),
|
||||
new TestRepo(
|
||||
"fdroidrepos://briarproject.org/fdroid/repo?fingerprint=1FB874BEE7276D28ECB2C9B06E8A122EC4BCB4008161436CE474C257CBF49BD6",
|
||||
"fdroidrepos://briarproject.org/fdroid/repo/%s?fingerprint=1FB874BEE7276D28ECB2C9B06E8A122EC4BCB4008161436CE474C257CBF49BD6"),
|
||||
};
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Preferences.setupForTests(context);
|
||||
}
|
||||
|
||||
interface GetFileFromRepo {
|
||||
String get(TestRepo tr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility test function - go through the list of test repos,
|
||||
* using the useOfRepo interface to instantiate a repo from the URL
|
||||
* and return a file of some kind (Apk, index, etc.) and check that
|
||||
* it matches the test repo's expected URL format.
|
||||
*
|
||||
* @param fileName File that 'useOfRepo' will return in the repo, when called
|
||||
* @param useOfRepo Instance of the function that uses the repo to build a file URL
|
||||
*/
|
||||
private void testReposWithFile(String fileName, GetFileFromRepo useOfRepo) {
|
||||
for (TestRepo tr : REPOS) {
|
||||
String expectedUrl = String.format(tr.fileUrlPattern, fileName);
|
||||
System.out.println("Testing URL " + expectedUrl);
|
||||
String actualUrl = useOfRepo.get(tr);
|
||||
assertEquals(expectedUrl, actualUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexUrls() {
|
||||
testReposWithFile("index.jar", tr ->
|
||||
Utils.getUri(tr.repoUrl, "index.jar").toString()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexV1Urls() {
|
||||
testReposWithFile(IndexV1UpdaterKt.SIGNED_FILE_NAME, tr ->
|
||||
Utils.getUri(tr.repoUrl, IndexV1UpdaterKt.SIGNED_FILE_NAME).toString()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApkUrls() {
|
||||
testReposWithFile(APK_NAME, tr -> {
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1(APK_NAME, "hash", null, null);
|
||||
apk.versionCode = 1;
|
||||
apk.repoAddress = tr.repoUrl;
|
||||
apk.canonicalRepoAddress = tr.repoUrl;
|
||||
return apk.getCanonicalUrl();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
/**
|
||||
* Due to there being so much static initialization in the main FDroidApp, it becomes hard to reset
|
||||
* that state between Robolectric test runs. Therefore, robolectric tests will default to this
|
||||
* {@link Application} instead of {@link FDroidApp}. It intentionally doesn't extends {@link FDroidApp}
|
||||
* so that the static initialization in {@link FDroidApp#onCreate()} is not executed.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class TestFDroidApp extends Application {
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.index.v2.FileV1;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Collections;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "TestUtils"; // NOPMD
|
||||
|
||||
/**
|
||||
* These signers are used to sign the Bitcoin Wallet in f-droid.org/repo.
|
||||
*/
|
||||
public static final String FDROID_SIGNER = "bcb4b93636bb10c7ddaf61aa9604ff795dfdb05fad4d9412b335682f0b612e32";
|
||||
public static final String UPSTREAM_SIGNER = "58dcd8a0edf2a590683ba022d22a8dca5659aabf4728741a5c07af738d53db38";
|
||||
|
||||
public static Apk getApk(int versionCode) {
|
||||
return getApk(versionCode, "signature", null);
|
||||
}
|
||||
|
||||
public static Apk getApk(int versionCode, String signer, String releaseChannel) {
|
||||
Apk apk = new Apk();
|
||||
apk.repoAddress = "http://www.example.com/fdroid/repo";
|
||||
apk.canonicalRepoAddress = "http://www.example.com/fdroid/repo";
|
||||
apk.versionCode = versionCode;
|
||||
apk.repoId = 1;
|
||||
apk.versionName = "The good one";
|
||||
apk.apkFile = new FileV1("Test Apk", "hash", null, null);
|
||||
apk.size = 10000;
|
||||
apk.compatible = true;
|
||||
apk.signer = signer;
|
||||
apk.releaseChannels = releaseChannel == null ?
|
||||
null : Collections.singletonList(releaseChannel);
|
||||
return apk;
|
||||
}
|
||||
|
||||
public static App getApp() {
|
||||
App app = new App();
|
||||
app.packageName = "com.example.app";
|
||||
app.name = "Test App";
|
||||
app.repoId = 1;
|
||||
app.summary = "test summary";
|
||||
app.description = "test description";
|
||||
app.license = "GPL?";
|
||||
app.compatible = true;
|
||||
return app;
|
||||
}
|
||||
|
||||
public static File copyResourceToTempFile(String resourceName) {
|
||||
File tempFile = null;
|
||||
InputStream input = null;
|
||||
OutputStream output = null;
|
||||
try {
|
||||
tempFile = File.createTempFile(resourceName + "-", ".testasset");
|
||||
input = TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
|
||||
output = new FileOutputStream(tempFile);
|
||||
Utils.copy(input, output);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
if (tempFile != null && tempFile.exists()) {
|
||||
assertTrue(tempFile.delete());
|
||||
}
|
||||
fail();
|
||||
return null;
|
||||
} finally {
|
||||
Utils.closeQuietly(output);
|
||||
Utils.closeQuietly(input);
|
||||
}
|
||||
return tempFile;
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package org.fdroid.fdroid;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static vendored.org.apache.commons.codec.digest.MessageDigestAlgorithms.SHA_256;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.encode.Contents;
|
||||
import com.google.zxing.encode.QRCodeEncoder;
|
||||
|
||||
import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @see <a href="https://gitlab.com/fdroid/fdroidclient/-/merge_requests/1089#note_822501322">forced to vendor Apache Commons Codec</a>
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@SuppressWarnings("LineLength")
|
||||
public class UtilsTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Preferences.setupForTests(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trailingNewLines() {
|
||||
CharSequence threeParagraphs = AppDetailsRecyclerViewAdapter.trimTrailingNewlines("Paragraph One\n\nParagraph Two\n\nParagraph Three\n\n");
|
||||
assertEquals("Paragraph One\n\nParagraph Two\n\nParagraph Three", threeParagraphs);
|
||||
|
||||
CharSequence leadingAndExtraTrailing = AppDetailsRecyclerViewAdapter.trimTrailingNewlines("\n\n\nA\n\n\n");
|
||||
assertEquals("\n\n\nA", leadingAndExtraTrailing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatFingerprint() {
|
||||
Context context = ApplicationProvider.getApplicationContext();
|
||||
String badResult = Utils.formatFingerprint(context, "");
|
||||
// real fingerprints
|
||||
String formatted;
|
||||
String fdroidFingerprint =
|
||||
"43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB";
|
||||
formatted = Utils.formatFingerprint(context, fdroidFingerprint);
|
||||
assertNotEquals(formatted, badResult);
|
||||
assertTrue(formatted.matches("[A-Z0-9][A-Z0-9] [A-Z0-9 ]+"));
|
||||
String gpRepoFingerprint =
|
||||
"59050C8155DCA377F23D5A15B77D3713400CDBD8B42FBFBE0E3F38096E68CECE";
|
||||
formatted = Utils.formatFingerprint(context, gpRepoFingerprint);
|
||||
assertNotEquals(formatted, badResult);
|
||||
assertTrue(formatted.matches("[A-Z0-9][A-Z0-9] [A-Z0-9 ]+"));
|
||||
String gpTest1Fingerprint =
|
||||
"C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C50";
|
||||
formatted = Utils.formatFingerprint(context, gpTest1Fingerprint);
|
||||
assertNotEquals(formatted, badResult);
|
||||
assertTrue(formatted.matches("[A-Z0-9][A-Z0-9] [A-Z0-9 ]+"));
|
||||
// random garbage
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "234k2lk3jljwlk4j2lk3jlkmqwekljrlkj34lk2jlk2j34lkjl2k3j4lk2j34lja"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "g000000000000000000000000000000000000000000000000000000000000000"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "98273498723948728934789237489273p1928731982731982739182739817238"));
|
||||
// too short
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C5"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "C63AED1"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "f"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, ""));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, null));
|
||||
// real digits but too long
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "C63AED1AC79D37C7B0474472AC6EFA6C3AB2B11A767A4F42CF360FA5496E3C50F"));
|
||||
assertEquals(
|
||||
badResult,
|
||||
Utils.formatFingerprint(context, "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsFileMatchingHash() {
|
||||
Utils.isFileMatchingHash(null, null, null);
|
||||
Utils.isFileMatchingHash(new File("/"), "", null);
|
||||
|
||||
assertFalse(Utils.isFileMatchingHash(null, null, ""));
|
||||
assertFalse(Utils.isFileMatchingHash(null, null, SHA_256));
|
||||
assertFalse(Utils.isFileMatchingHash(new File("/"), null, SHA_256));
|
||||
assertFalse(Utils.isFileMatchingHash(new File("/"), "", SHA_256));
|
||||
|
||||
assertTrue(Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("Norway_bouvet_europe_2.obf.zip"),
|
||||
"6e8a584e004c6cd26d3822a04b0591e355dc5d07b5a3d0f8e309443f47ad1208", SHA_256));
|
||||
assertTrue(Utils.isFileMatchingHash(TestUtils.copyResourceToTempFile("install_history_all"),
|
||||
"4ad118d4a600dcc104834635d248a89e337fc91b173163d646996b9c54d77372", SHA_256));
|
||||
|
||||
File f = TestUtils.copyResourceToTempFile("additional_repos.xml");
|
||||
assertTrue(Utils.isFileMatchingHash(f,
|
||||
"47ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab7", SHA_256));
|
||||
assertFalse("uppercase fails",
|
||||
Utils.isFileMatchingHash(f,
|
||||
"47AD2284D3042373E6280012CC10E9B82F75352DB6D6D9BAB1E06934B7B1DAB7", SHA_256));
|
||||
assertFalse("one uppercase digit fails",
|
||||
Utils.isFileMatchingHash(f,
|
||||
"47Ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab7", SHA_256));
|
||||
assertFalse("missing digit fails",
|
||||
Utils.isFileMatchingHash(f,
|
||||
"47ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab", SHA_256));
|
||||
assertFalse("extra digit fails",
|
||||
Utils.isFileMatchingHash(f,
|
||||
"47ad2284d3042373e6280012cc10e9b82f75352db6d6d9bab1e06934b7b1dab71", SHA_256));
|
||||
assertFalse("all zeros fails",
|
||||
Utils.isFileMatchingHash(f,
|
||||
"0000000000000000000000000000000000000000000000000000000000000000", SHA_256));
|
||||
assertFalse("null fails",
|
||||
Utils.isFileMatchingHash(f, null, SHA_256));
|
||||
assertFalse("empty string fails",
|
||||
Utils.isFileMatchingHash(f, "", SHA_256));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetFileHexDigest() throws IOException {
|
||||
// zero size file should have a stable hex digest file
|
||||
assertEquals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
||||
Utils.getFileHexDigest(File.createTempFile("asdf", "asdf"), SHA_256));
|
||||
|
||||
assertNull(Utils.getFileHexDigest(new File("/kasdfkjasdhflkjasd"), SHA_256));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetFileHexDigestBadAlgo() {
|
||||
File f = TestUtils.copyResourceToTempFile("additional_repos.xml");
|
||||
assertNull(Utils.getFileHexDigest(f, "FAKE"));
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testGetFileHexDigestNullFile() {
|
||||
assertNull(Utils.getFileHexDigest(null, SHA_256));
|
||||
}
|
||||
|
||||
// TODO write tests that work with a Certificate
|
||||
|
||||
@Test
|
||||
public void testFormatLastUpdated() {
|
||||
Resources res = context.getResources();
|
||||
long now = System.currentTimeMillis();
|
||||
long offset = DateUtils.MINUTE_IN_MILLIS * 10;
|
||||
assertEquals(
|
||||
"Updated today",
|
||||
Utils.formatLastUpdated(res, now - offset)
|
||||
);
|
||||
assertEquals(
|
||||
"Updated today",
|
||||
Utils.formatLastUpdated(res, now - DateUtils.DAY_IN_MILLIS / 2 + offset)
|
||||
);
|
||||
assertEquals(
|
||||
"Updated 1 day ago",
|
||||
Utils.formatLastUpdated(res, now - DateUtils.DAY_IN_MILLIS / 2 - offset)
|
||||
);
|
||||
assertEquals(
|
||||
"Updated 1 day ago",
|
||||
Utils.formatLastUpdated(res, now - DateUtils.DAY_IN_MILLIS - offset)
|
||||
);
|
||||
assertEquals(
|
||||
"Updated 3 days ago",
|
||||
Utils.formatLastUpdated(res, now - 234834870L)
|
||||
);
|
||||
assertEquals(
|
||||
"Updated 13 days ago",
|
||||
Utils.formatLastUpdated(res, now - DateUtils.DAY_IN_MILLIS * 13 - offset)
|
||||
);
|
||||
assertEquals(
|
||||
"Updated 7 months ago",
|
||||
Utils.formatLastUpdated(res, now - DateUtils.DAY_IN_MILLIS * 30 * 7 + offset)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQrCodeEncoding() throws Exception {
|
||||
String text1 = "http://192.168.3.159:8888/fdroid/repo?FINGERPRINT=" +
|
||||
"BA29D02E303B2604D00C91189600E868B26FA0B248DC39D75C5C0F4349CA5FA9" +
|
||||
"&SWAP=1&BSSID=44:FE:3B:7F:7F:EE";
|
||||
int bytesNum1 = new QRCodeEncoder(text1, null, Contents.Type.TEXT,
|
||||
BarcodeFormat.QR_CODE.toString(), 500).encodeAsBitmap().getByteCount();
|
||||
|
||||
String text2 = "http://192.168.3.159:8888/fdroid/repo?fingerprint=" +
|
||||
"ba29d02e303b2604d00c91189600e868b26fa0b248dc39d75c5c0f4349ca5fa9" +
|
||||
"&swap=1&bssid=44:fe:3b:7f:7f:ee";
|
||||
int bytesNum2 = new QRCodeEncoder(text2, null, Contents.Type.TEXT,
|
||||
BarcodeFormat.QR_CODE.toString(), 500).encodeAsBitmap().getByteCount();
|
||||
assertEquals(bytesNum1, bytesNum2);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.ContextWrapper;
|
||||
import android.os.Environment;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.installer.ApkCache;
|
||||
import org.fdroid.fdroid.nearby.PublicSourceDirProvider;
|
||||
import org.fdroid.index.v2.FileV1;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.Shadows;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.robolectric.shadows.ShadowMimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ApkTest {
|
||||
|
||||
private final ContextWrapper context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public final void setUp() {
|
||||
ShadowMimeTypeMap mimeTypeMap = Shadows.shadowOf(MimeTypeMap.getSingleton());
|
||||
mimeTypeMap.addExtensionMimeTypeMapping("apk", "application/vnd.android.package-archive");
|
||||
mimeTypeMap.addExtensionMimeTypeMapping("obf", "application/octet-stream");
|
||||
mimeTypeMap.addExtensionMimeTypeMapping("zip", PublicSourceDirProvider.SHARE_APK_MIME_TYPE);
|
||||
ShadowLog.stream = System.out;
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetMediaInstallPathWithApk() {
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1("test.apk", "hash", null, null);
|
||||
apk.repoAddress = "https://example.com/fdroid/repo";
|
||||
apk.canonicalRepoAddress = "https://example.com/fdroid/repo";
|
||||
assertTrue(apk.isApk());
|
||||
apk.getMediaInstallPath(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMediaInstallPathWithOta() throws IOException {
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1("org.fdroid.fdroid.privileged.ota_2110.zip", "hash", null, null);
|
||||
apk.repoAddress = "https://example.com/fdroid/repo";
|
||||
apk.canonicalRepoAddress = "https://example.com/fdroid/repo";
|
||||
assertFalse(apk.isApk());
|
||||
copyResourceFileToCache(apk);
|
||||
File path = apk.getMediaInstallPath(context);
|
||||
assertEquals(new File(context.getApplicationInfo().dataDir + "/ota"), path);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMediaInstallPathWithObf() {
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1("Norway_bouvet_europe_2.obf", "hash", null, null);
|
||||
apk.repoAddress = "https://example.com/fdroid/repo";
|
||||
apk.canonicalRepoAddress = "https://example.com/fdroid/repo";
|
||||
assertFalse(apk.isApk());
|
||||
File path = apk.getMediaInstallPath(context);
|
||||
assertEquals(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), path);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMediaInstallPathWithObfZip() throws IOException {
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1("Norway_bouvet_europe_2.obf.zip", "hash", null, null);
|
||||
apk.repoAddress = "https://example.com/fdroid/repo";
|
||||
apk.canonicalRepoAddress = "https://example.com/fdroid/repo";
|
||||
assertFalse(apk.isApk());
|
||||
copyResourceFileToCache(apk);
|
||||
File path = apk.getMediaInstallPath(context);
|
||||
assertEquals(context.getCacheDir(), path);
|
||||
}
|
||||
|
||||
private void copyResourceFileToCache(Apk apk) throws IOException {
|
||||
URL res = getClass().getClassLoader().getResource(apk.apkFile.getName());
|
||||
FileUtils.copyInputStreamToFile(res.openStream(),
|
||||
ApkCache.getApkDownloadPath(context, apk.getCanonicalUrl()));
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.fdroid.database.FDroidDatabase;
|
||||
import org.fdroid.database.InitialRepository;
|
||||
import org.fdroid.database.RepositoryDao;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.TestUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DBHelperTest {
|
||||
|
||||
private static final String TAG = "DBHelperTest";
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
private List<String> getReposFromXml(String xml) throws IOException, XmlPullParserException {
|
||||
File additionalReposXml = File.createTempFile("." + context.getPackageName() + "-DBHelperTest_",
|
||||
"_additional_repos.xml");
|
||||
Log.i(TAG, "additionalReposXml: " + additionalReposXml);
|
||||
|
||||
FileOutputStream outputStream = new FileOutputStream(additionalReposXml);
|
||||
outputStream.write(xml.getBytes());
|
||||
outputStream.close();
|
||||
|
||||
// Now parse that xml file
|
||||
return DBHelper.parseAdditionalReposXml(additionalReposXml);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultReposAddedToDb() {
|
||||
FDroidDatabase db = mock(FDroidDatabase.class);
|
||||
RepositoryDao repoDao = mock(RepositoryDao.class);
|
||||
when(db.getRepositoryDao()).thenReturn(repoDao);
|
||||
|
||||
// pre-populate the DB
|
||||
DBHelper.prePopulateDb(context, db);
|
||||
|
||||
// verify that all default repos were added to DB
|
||||
int numRepos = getDefaultRepoCount();
|
||||
verify(repoDao, times(numRepos)).insert(ArgumentMatchers.any(InitialRepository.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of repos in app/src/main/res/default_repo.xml
|
||||
*/
|
||||
private int getDefaultRepoCount() {
|
||||
int itemCount = context.getResources().getStringArray(R.array.default_repos).length;
|
||||
return itemCount / DBHelper.REPO_XML_ITEM_COUNT;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAdditionalReposXmlAllOneLineTest() throws IOException, XmlPullParserException {
|
||||
String oneRepoXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
|
||||
"<resources>" +
|
||||
"<string-array name=\"default_repos\">\n" +
|
||||
"<!-- name -->" +
|
||||
"<item>F-Droid</item>" +
|
||||
"<!-- address -->" +
|
||||
"<item>https://f-droid.org/repo</item>" +
|
||||
"<!-- description -->" +
|
||||
"<item>The official F-Droid repository. Applications in this repository are mostly built" +
|
||||
"directory from the source code. Some are official binaries built by the original" +
|
||||
"application developers - these will be replaced by source-built versions over time." +
|
||||
"</item>" +
|
||||
"<!-- version -->" +
|
||||
"<item>13</item>" +
|
||||
"<!-- enabled -->" +
|
||||
"<item>1</item>" +
|
||||
"<!-- push requests -->" +
|
||||
"<item>ignore</item>" +
|
||||
"<!-- pubkey -->" +
|
||||
"<item>" +
|
||||
"3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b30090603550406130255" +
|
||||
"</item>" +
|
||||
"</string-array>" +
|
||||
"</resources>";
|
||||
List<String> repos = getReposFromXml(oneRepoXml);
|
||||
assertEquals("Should contain one repo's worth of items", DBHelper.REPO_XML_ITEM_COUNT, repos.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAdditionalReposXmlIncludedPriorityTest() throws IOException, XmlPullParserException {
|
||||
String wrongXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
|
||||
"<resources>" +
|
||||
"<string-array name=\"default_repos\">\n" +
|
||||
"<!-- name -->" +
|
||||
"<item>F-Droid</item>" +
|
||||
"<!-- address -->" +
|
||||
"<item>https://f-droid.org/repo</item>" +
|
||||
"<!-- description -->" +
|
||||
"<item>The official F-Droid repository. Applications in this repository are mostly built" +
|
||||
"directory from the source code. Some are official binaries built by the original" +
|
||||
"application developers - these will be replaced by source-built versions over time." +
|
||||
"</item>" +
|
||||
"<!-- version -->" +
|
||||
"<item>13</item>" +
|
||||
"<!-- enabled -->" +
|
||||
"<item>1</item>" +
|
||||
"<!-- priority -->" +
|
||||
"<item>1</item>" +
|
||||
"<!-- push requests -->" +
|
||||
"<item>ignore</item>" +
|
||||
"<!-- pubkey -->" +
|
||||
"<item>" +
|
||||
"3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b3009060355040613025" +
|
||||
"</item>" +
|
||||
"</string-array>" +
|
||||
"</resources>";
|
||||
getReposFromXml(wrongXml);
|
||||
List<String> repos = getReposFromXml(wrongXml);
|
||||
assertEquals("Should be empty", 0, repos.size());
|
||||
}
|
||||
|
||||
@Test(expected = XmlPullParserException.class)
|
||||
public void parseAdditionalReposXmlDoubleTagTest() throws IOException, XmlPullParserException {
|
||||
String wrongXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<resources>"
|
||||
+ "<string-array name=\"additional_repos\">"
|
||||
+ "<!-- address -->"
|
||||
+ "<item><item>https://www.oem0.com/yeah/repo</item>"
|
||||
+ "<!-- description -->"
|
||||
+ "<item>I'm the first oem repo.</item>"
|
||||
+ "<!-- version -->"
|
||||
+ "<item>22</item>"
|
||||
+ "<!-- enabled -->"
|
||||
+ "<item>1</item>"
|
||||
+ "<!-- push requests -->"
|
||||
+ "<item>ignore</item>"
|
||||
+ "<!-- pubkey -->"
|
||||
+ "<item>fffff2313aaaaabcccc111</item>"
|
||||
+ "</string-array>"
|
||||
+ "</resources>";
|
||||
getReposFromXml(wrongXml);
|
||||
fail("Invalid xml read successfully --> Wrong");
|
||||
}
|
||||
|
||||
@Test(expected = XmlPullParserException.class)
|
||||
public void parseAdditionalReposXmlMissingStartTagTest() throws IOException, XmlPullParserException {
|
||||
String wrongXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<item>https://www.oem0.com/yeah/repo</item>"
|
||||
+ "<!-- description -->"
|
||||
+ "<item>I'm the first oem repo.</item>"
|
||||
+ "<!-- version -->"
|
||||
+ "<item>22</item>"
|
||||
+ "<!-- enabled -->"
|
||||
+ "<item>1</item>"
|
||||
+ "<!-- push requests -->"
|
||||
+ "<item>ignore</item>"
|
||||
+ "<!-- pubkey -->"
|
||||
+ "<item>fffff2313aaaaabcccc111</item>"
|
||||
+ "</string-array>"
|
||||
+ "</resources>";
|
||||
getReposFromXml(wrongXml);
|
||||
fail("Invalid xml read successfully --> Wrong");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAdditionalReposXmlWrongCountTest() throws IOException, XmlPullParserException {
|
||||
String wrongXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<resources>"
|
||||
+ "<string-array name=\"default_repos\"><item>foo</item></string-array>"
|
||||
+ "</resources>";
|
||||
List<String> repos = getReposFromXml(wrongXml);
|
||||
assertEquals("Should be empty", 0, repos.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse valid xml but make sure that only <item> tags are parsed
|
||||
*/
|
||||
@Test
|
||||
public void parseAdditionalReposXmlSloppyTest() throws IOException, XmlPullParserException {
|
||||
InputStream input = TestUtils.class.getClassLoader().getResourceAsStream("ugly_additional_repos.xml");
|
||||
String validXml = IOUtils.toString(input, StandardCharsets.UTF_8);
|
||||
|
||||
List<String> repos = getReposFromXml(validXml);
|
||||
assertEquals(2 * DBHelper.REPO_XML_ITEM_COUNT, repos.size());
|
||||
assertEquals("Repo Name", repos.get(7));
|
||||
assertEquals("https://www.oem0.com/yeah/repo", repos.get(8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseAdditionalReposXmlPositiveTest() throws IOException {
|
||||
InputStream input = TestUtils.class.getClassLoader().getResourceAsStream("additional_repos.xml");
|
||||
String reposXmlContent = IOUtils.toString(input, StandardCharsets.UTF_8);
|
||||
|
||||
List<String> additionalRepos;
|
||||
try {
|
||||
additionalRepos = getReposFromXml(reposXmlContent);
|
||||
} catch (IOException io) {
|
||||
fail("IOException. Failed parsing xml string into repos.");
|
||||
return;
|
||||
} catch (XmlPullParserException xppe) {
|
||||
fail("XmlPullParserException. Failed parsing xml string into repos.");
|
||||
return;
|
||||
}
|
||||
|
||||
// We should have loaded these repos
|
||||
List<String> oem0 = Arrays.asList(
|
||||
"oem0Name",
|
||||
"https://www.oem0.com/yeah/repo",
|
||||
"I'm the first oem repo.",
|
||||
"22",
|
||||
"1",
|
||||
"ignore",
|
||||
"fffff2313aaaaabcccc111");
|
||||
List<String> oem1 = Arrays.asList(
|
||||
"oem1MyNameIs",
|
||||
"https://www.mynameis.com/rapper/repo",
|
||||
"Who is the first repo?",
|
||||
"22",
|
||||
"0",
|
||||
"ignore",
|
||||
"ddddddd2313aaaaabcccc111");
|
||||
List<String> shouldBeRepos = new LinkedList<>();
|
||||
shouldBeRepos.addAll(oem0);
|
||||
shouldBeRepos.addAll(oem1);
|
||||
|
||||
assertEquals(additionalRepos.size(), shouldBeRepos.size());
|
||||
for (int i = 0; i < additionalRepos.size(); i++) {
|
||||
assertEquals(shouldBeRepos.get(i), additionalRepos.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("LineLength")
|
||||
@Test
|
||||
public void canAddAdditionalRepos() throws IOException {
|
||||
File oemEtcDir = new File("/oem/etc");
|
||||
File oemEtcPackageDir = new File(oemEtcDir, context.getPackageName());
|
||||
if (!oemEtcPackageDir.canWrite() || !oemEtcDir.canWrite()) {
|
||||
if (oemEtcDir.canWrite() || new File("/").canWrite()) {
|
||||
oemEtcPackageDir.mkdirs();
|
||||
}
|
||||
assumeTrue(oemEtcPackageDir.isDirectory());
|
||||
if (TextUtils.isEmpty(System.getenv("CI")) && !oemEtcPackageDir.isDirectory()) {
|
||||
Log.e(TAG, "Cannot create " + oemEtcDir + ", skipping test!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File additionalReposXmlFile = new File(oemEtcPackageDir, "additional_repos.xml");
|
||||
FileOutputStream outputStream = new FileOutputStream(additionalReposXmlFile);
|
||||
outputStream.write(("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
|
||||
+ "<resources>"
|
||||
+ "<string-array name=\"default_repos\">"
|
||||
+ "<!-- name -->"
|
||||
+ "<item>oem0Name</item>"
|
||||
+ "<!-- address -->"
|
||||
+ "<item>https://www.oem0.com/yeah/repo</item>"
|
||||
+ "<!-- description -->"
|
||||
+ "<item>I'm the first oem repo.</item>"
|
||||
+ "<!-- version -->"
|
||||
+ "<item>22</item>"
|
||||
+ "<!-- enabled -->"
|
||||
+ "<item>1</item>"
|
||||
+ "<!-- push requests -->"
|
||||
+ "<item>ignore</item>"
|
||||
+ "<!-- pubkey -->"
|
||||
+ "<item>fffff2313aaaaabcccc111</item>"
|
||||
|
||||
+ "<!-- name -->"
|
||||
+ "<item>oem1MyNameIs</item>"
|
||||
+ "<!-- address -->"
|
||||
+ "<item>https://www.mynameis.com/rapper/repo</item>"
|
||||
+ "<!-- description -->"
|
||||
+ "<item>Who is the first repo?</item>"
|
||||
+ "<!-- version -->"
|
||||
+ "<item>22</item>"
|
||||
+ "<!-- enabled -->"
|
||||
+ "<item>0</item>"
|
||||
+ "<!-- push requests -->"
|
||||
+ "<item>ignore</item>"
|
||||
+ "<!-- pubkey -->"
|
||||
+ "<item>ddddddd2313aaaaabcccc111</item>"
|
||||
+ "</string-array>"
|
||||
+ "</resources>").getBytes());
|
||||
outputStream.close();
|
||||
|
||||
try {
|
||||
List<String> initialRepos = DBHelper.loadInitialRepos(context);
|
||||
|
||||
// Construct the repos that we should have loaded
|
||||
List<String> oem0 = Arrays.asList("oem0Name", "https://www.oem0.com/yeah/repo", "I'm the first oem repo.",
|
||||
"22", "1", "ignore", "fffff2313aaaaabcccc111");
|
||||
List<String> oem1 = Arrays.asList("oem1MyNameIs", "https://www.mynameis.com/rapper/repo", "Who is the first repo?",
|
||||
"22", "0", "ignore", "ddddddd2313aaaaabcccc111");
|
||||
|
||||
String[] defaultRepos = context.getResources().getStringArray(R.array.default_repos);
|
||||
|
||||
List<String> shouldBeRepos = new LinkedList<>();
|
||||
shouldBeRepos.addAll(Arrays.asList(defaultRepos));
|
||||
shouldBeRepos.addAll(oem0);
|
||||
shouldBeRepos.addAll(oem1);
|
||||
|
||||
// Normalize whitespace in descriptions, just like DBHelper does.
|
||||
final int descriptionIndex = 2;
|
||||
for (int i = descriptionIndex; i < shouldBeRepos.size(); i += DBHelper.REPO_XML_ITEM_COUNT) {
|
||||
String description = shouldBeRepos.get(i);
|
||||
shouldBeRepos.set(i, description.replaceAll("\\s+", " "));
|
||||
}
|
||||
|
||||
for (int i = 0; i < initialRepos.size(); i++) {
|
||||
assertEquals(shouldBeRepos.get(i), initialRepos.get(i));
|
||||
}
|
||||
} finally {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
additionalReposXmlFile.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class SanitizedFileTest {
|
||||
|
||||
@Test
|
||||
public void testSanitizedFile() {
|
||||
assumeTrue("/".equals(System.getProperty("file.separator")));
|
||||
|
||||
File directory = new File("/tmp/blah");
|
||||
|
||||
String safeFile = "safe";
|
||||
String nonEvilFile = "$%^safe-and_bleh.boo*@~";
|
||||
String evilFile = ";rm /etc/shadow;";
|
||||
|
||||
File safeNotSanitized = new File(directory, safeFile);
|
||||
File nonEvilNotSanitized = new File(directory, nonEvilFile);
|
||||
File evilNotSanitized = new File(directory, evilFile);
|
||||
|
||||
assertEquals("/tmp/blah/safe", safeNotSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/$%^safe-and_bleh.boo*@~", nonEvilNotSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/;rm /etc/shadow;", evilNotSanitized.getAbsolutePath());
|
||||
|
||||
assertEquals("safe", safeNotSanitized.getName());
|
||||
assertEquals("$%^safe-and_bleh.boo*@~", nonEvilNotSanitized.getName());
|
||||
assertEquals("shadow;", evilNotSanitized.getName());
|
||||
|
||||
SanitizedFile safeSanitized = new SanitizedFile(directory, safeFile);
|
||||
SanitizedFile nonEvilSanitized = new SanitizedFile(directory, nonEvilFile);
|
||||
SanitizedFile evilSanitized = new SanitizedFile(directory, evilFile);
|
||||
|
||||
assertEquals("/tmp/blah/safe", safeSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/safe-and_bleh.boo", nonEvilSanitized.getAbsolutePath());
|
||||
assertEquals("/tmp/blah/rm etcshadow", evilSanitized.getAbsolutePath());
|
||||
|
||||
assertEquals("safe", safeSanitized.getName());
|
||||
assertEquals("safe-and_bleh.boo", nonEvilSanitized.getName());
|
||||
assertEquals("rm etcshadow", evilSanitized.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSanitizeFileName() {
|
||||
for (String valid : new String[]{"An.stop", "a.0", "packageName", "com.this-and-that", "A_.o"}) {
|
||||
assertEquals(valid, SanitizedFile.sanitizeFileName(valid));
|
||||
}
|
||||
for (String invalid : new String[]{"'--;DROP", "a.0)", "packageName\n"}) {
|
||||
assertNotEquals(invalid, SanitizedFile.sanitizeFileName(invalid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package org.fdroid.fdroid.data;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.fdroid.database.AppPrefs;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.TestUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Config(application = Application.class)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class SuggestedVersionTest {
|
||||
|
||||
private final Context context = ApplicationProvider.getApplicationContext();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Preferences.setupForTests(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleRepoSingleSig() {
|
||||
App singleApp = TestUtils.getApp();
|
||||
singleApp.installedVersionCode = 1;
|
||||
singleApp.installedSigner = TestUtils.FDROID_SIGNER;
|
||||
Apk apk1 = TestUtils.getApk(1, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk2 = TestUtils.getApk(2, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk3 = TestUtils.getApk(3, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_BETA);
|
||||
List<Apk> apks = new ArrayList<>();
|
||||
apks.add(apk3);
|
||||
apks.add(apk2);
|
||||
apks.add(apk1);
|
||||
assertSuggested(singleApp, apks, 2, Apk.RELEASE_CHANNEL_STABLE);
|
||||
|
||||
// By enabling the beta channel we should suggest the latest version (3).
|
||||
Preferences.get().setUnstableUpdates(true);
|
||||
assertSuggested(singleApp, apks, 3, Apk.RELEASE_CHANNEL_BETA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleRepoMultiSigner() {
|
||||
App singleApp = TestUtils.getApp();
|
||||
singleApp.installedVersionCode = 0;
|
||||
|
||||
Apk apk1 = TestUtils.getApk(1, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk2 = TestUtils.getApk(2, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk3 = TestUtils.getApk(3, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk4 = TestUtils.getApk(4, TestUtils.UPSTREAM_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk5 = TestUtils.getApk(5, TestUtils.UPSTREAM_SIGNER, Apk.RELEASE_CHANNEL_BETA);
|
||||
List<Apk> apks = new ArrayList<>();
|
||||
apks.add(apk5);
|
||||
apks.add(apk4);
|
||||
apks.add(apk3);
|
||||
apks.add(apk2);
|
||||
apks.add(apk1);
|
||||
|
||||
// Given we aren't installed yet, we don't care which signature.
|
||||
// Just get as close to suggestedVersionCode as possible.
|
||||
assertSuggested(singleApp, apks, 4, Apk.RELEASE_CHANNEL_STABLE, false);
|
||||
|
||||
// Now install v1 with the f-droid signature. In response, we should only suggest
|
||||
// apps with that sig in the future. That is, version 4 from upstream is not considered.
|
||||
singleApp.installedSigner = TestUtils.FDROID_SIGNER;
|
||||
singleApp.installedVersionCode = 1;
|
||||
assertSuggested(singleApp, apks, 3, Apk.RELEASE_CHANNEL_STABLE);
|
||||
|
||||
// This adds the "suggestedVersionCode" version of the app, but signed by f-droid.
|
||||
Apk apk4f = TestUtils.getApk(4, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk5f = TestUtils.getApk(5, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_BETA);
|
||||
apks.clear();
|
||||
apks.add(apk5);
|
||||
apks.add(apk5f);
|
||||
apks.add(apk4);
|
||||
apks.add(apk4f);
|
||||
apks.add(apk3);
|
||||
apks.add(apk2);
|
||||
apks.add(apk1);
|
||||
assertSuggested(singleApp, apks, 4, Apk.RELEASE_CHANNEL_STABLE);
|
||||
|
||||
// Version 5 from F-Droid is not the "suggestedVersionCode", but with beta updates it should
|
||||
// still become the suggested version now.
|
||||
assertSuggested(singleApp, apks, 5, Apk.RELEASE_CHANNEL_BETA);
|
||||
}
|
||||
|
||||
public void assertSuggested(App app, List<Apk> apks, int suggestedVersion,
|
||||
String releaseChannel) {
|
||||
assertSuggested(app, apks, suggestedVersion, releaseChannel, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncompatibleWithBeta() {
|
||||
App singleApp = TestUtils.getApp();
|
||||
singleApp.installedVersionCode = 1;
|
||||
singleApp.installedSigner = TestUtils.FDROID_SIGNER;
|
||||
Apk apk1 = TestUtils.getApk(1, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk2 = TestUtils.getApk(2, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
Apk apk3 = TestUtils.getApk(3, TestUtils.FDROID_SIGNER, Apk.RELEASE_CHANNEL_STABLE);
|
||||
apk3.compatible = false;
|
||||
List<Apk> apks = new ArrayList<>();
|
||||
apks.add(apk3);
|
||||
apks.add(apk2);
|
||||
apks.add(apk1);
|
||||
assertSuggested(singleApp, apks, 2, Apk.RELEASE_CHANNEL_BETA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the app exists, that its suggested version code is correct, and that the apk which is "suggested"
|
||||
* has the correct signature.
|
||||
* <p>
|
||||
* If {@param installedSigner} is null then {@param installedVersion} is
|
||||
* ignored and the signature of the suggested APK is not checked.
|
||||
*/
|
||||
public void assertSuggested(App app, List<Apk> apks, int suggestedVersion,
|
||||
String releaseChannel, boolean hasUpdates) {
|
||||
Apk suggestedApk = app.findSuggestedApk(apks, releaseChannel);
|
||||
assertNotNull(suggestedApk);
|
||||
assertEquals("Suggested version on App", suggestedVersion, suggestedApk.versionCode);
|
||||
|
||||
if (app.installedSigner != null) {
|
||||
assertEquals("Installed signature on Apk", app.installedSigner, suggestedApk.signer);
|
||||
}
|
||||
assertTrue(app.canAndWantToUpdate(suggestedApk));
|
||||
AppPrefs appPrefs = new AppPrefs(app.packageName, 0, null, Collections.singletonList(releaseChannel));
|
||||
assertEquals(hasUpdates, app.hasUpdates(apks, appPrefs));
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.ContextWrapper;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class ApkCacheTest {
|
||||
private static final String TAG = "ApkCacheTest";
|
||||
|
||||
private ContextWrapper context;
|
||||
private File cacheDir;
|
||||
|
||||
@Before
|
||||
public final void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
cacheDir = ApkCache.getApkCacheDir(context);
|
||||
ShadowLog.stream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetApkCacheDir() {
|
||||
Log.i(TAG, "path: " + cacheDir);
|
||||
assertTrue("Must be full path", cacheDir.isAbsolute());
|
||||
assertTrue("Must be a directory", cacheDir.isDirectory());
|
||||
assertTrue("Must be writable", cacheDir.canWrite());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetApkDownloadPath() {
|
||||
assertEquals("Should be in folder based on repo hostname",
|
||||
new File(cacheDir, "f-droid.org--1/org.fdroid.fdroid_1008000.apk"),
|
||||
ApkCache.getApkDownloadPath(context,
|
||||
"https://f-droid.org/repo/org.fdroid.fdroid_1008000.apk"));
|
||||
assertEquals("Should be in folder based on repo hostname with port number",
|
||||
new File(cacheDir, "192.168.234.12-8888/sun.bob.leela_2.apk"),
|
||||
ApkCache.getApkDownloadPath(context,
|
||||
"http://192.168.234.12:8888/fdroid/repo/sun.bob.leela_2.apk"));
|
||||
assertEquals("Should work for OTA files also",
|
||||
new File(cacheDir, "f-droid.org--1/org.fdroid.fdroid.privileged.ota_2110.zip"),
|
||||
ApkCache.getApkDownloadPath(context,
|
||||
"http://f-droid.org/fdroid/repo/org.fdroid.fdroid.privileged.ota_2110.zip"));
|
||||
assertEquals("Should work for ZIP files also",
|
||||
new File(cacheDir, "example.com--1/Norway_bouvet_europe_2.obf.zip"),
|
||||
ApkCache.getApkDownloadPath(context,
|
||||
"https://example.com/fdroid/repo/Norway_bouvet_europe_2.obf.zip"));
|
||||
assertEquals("Should work for OBF files also",
|
||||
new File(cacheDir, "example.com--1/Norway_bouvet_europe_2.obf"),
|
||||
ApkCache.getApkDownloadPath(context,
|
||||
"https://example.com/fdroid/repo/Norway_bouvet_europe_2.obf"));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import android.content.ContextWrapper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.index.v2.FileV1;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class FileInstallerTest {
|
||||
|
||||
private ContextWrapper context;
|
||||
|
||||
@Before
|
||||
public final void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
Preferences.setupForTests(context);
|
||||
ShadowLog.stream = System.out;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstallOtaZip() {
|
||||
App app = new App();
|
||||
app.packageName = "org.fdroid.fdroid.privileged.ota";
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1("org.fdroid.fdroid.privileged.ota_2010.zip", "hash", null, null);
|
||||
apk.packageName = "org.fdroid.fdroid.privileged.ota";
|
||||
apk.versionCode = 2010;
|
||||
assertFalse(apk.isApk());
|
||||
Installer installer = InstallerFactory.create(context, app, apk);
|
||||
assertEquals("should be a FileInstaller",
|
||||
FileInstaller.class,
|
||||
installer.getClass());
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.content.ContextWrapper;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.index.v2.FileV1;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class InstallerFactoryTest {
|
||||
|
||||
private ContextWrapper context;
|
||||
|
||||
@Before
|
||||
public final void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
Preferences.setupForTests(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApkInstallerInstance() {
|
||||
for (String filename : new String[]{"test.apk", "A.APK", "b.ApK"}) {
|
||||
App app = new App();
|
||||
app.packageName = "test";
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1(filename, "hash", null, null);
|
||||
apk.packageName = "test";
|
||||
Installer installer = InstallerFactory.create(context, app, apk);
|
||||
assertEquals(filename + " should use a DefaultInstaller",
|
||||
DefaultInstaller.class,
|
||||
installer.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileInstallerInstance() {
|
||||
for (String filename : new String[]{"org.fdroid.fdroid.privileged.ota_2110.zip", "test.ZIP"}) {
|
||||
App app = new App();
|
||||
app.packageName = "cafe0088";
|
||||
Apk apk = new Apk();
|
||||
apk.apkFile = new FileV1(filename, "hash", null, null);
|
||||
apk.packageName = "cafe0088";
|
||||
Installer installer = InstallerFactory.create(context, app, apk);
|
||||
assertEquals("should be a FileInstaller",
|
||||
FileInstaller.class,
|
||||
installer.getClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
package org.fdroid.fdroid.views;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.appcompat.view.ContextThemeWrapper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.fdroid.database.AppPrefs;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.TestUtils;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.index.v2.FileV2;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Config(application = Application.class)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AppDetailsAdapterTest {
|
||||
|
||||
private final AppPrefs appPrefs = new AppPrefs("com.example.app", 0, null, null);
|
||||
private Context context;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// Must manually set the theme again here other than in AndroidManifest,xml
|
||||
// https://github.com/mozilla-mobile/fenix/pull/15646#issuecomment-707345798
|
||||
ApplicationProvider.getApplicationContext().setTheme(R.style.Theme_App);
|
||||
context = new ContextThemeWrapper(ApplicationProvider.getApplicationContext(), R.style.Theme_App);
|
||||
|
||||
Preferences.setupForTests(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appWithNoVersionsOrScreenshots() {
|
||||
App app = TestUtils.getApp();
|
||||
AppDetailsRecyclerViewAdapter adapter = new AppDetailsRecyclerViewAdapter(context, app, dummyCallbacks);
|
||||
adapter.updateItems(TestUtils.getApp(), Collections.emptyList(), appPrefs);
|
||||
populateViewHolders(adapter);
|
||||
|
||||
assertEquals(3, adapter.getItemCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appWithScreenshots() {
|
||||
App app = TestUtils.getApp();
|
||||
app.phoneScreenshots = new ArrayList<>(2);
|
||||
app.phoneScreenshots.add(FileV2.fromPath("screenshot1.png"));
|
||||
app.phoneScreenshots.add(FileV2.fromPath("screenshot2.png"));
|
||||
|
||||
AppDetailsRecyclerViewAdapter adapter = new AppDetailsRecyclerViewAdapter(context, app, dummyCallbacks);
|
||||
adapter.updateItems(app, Collections.emptyList(), appPrefs);
|
||||
populateViewHolders(adapter);
|
||||
|
||||
assertEquals(4, adapter.getItemCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void appWithVersions() {
|
||||
App app = TestUtils.getApp();
|
||||
app.preferredSigner = "eaa1d713b9c2a0475234a86d6539f910";
|
||||
List<Apk> apks = new ArrayList<>();
|
||||
apks.add(TestUtils.getApk(1));
|
||||
apks.add(TestUtils.getApk(2));
|
||||
apks.add(TestUtils.getApk(3));
|
||||
app.installedApk = apks.get(0);
|
||||
|
||||
AppDetailsRecyclerViewAdapter adapter = new AppDetailsRecyclerViewAdapter(context, app, dummyCallbacks);
|
||||
adapter.updateItems(app, apks, appPrefs);
|
||||
populateViewHolders(adapter);
|
||||
|
||||
// Starts collapsed, not showing versions at all. (also showing permissions)
|
||||
assertEquals(4, adapter.getItemCount());
|
||||
|
||||
adapter.setShowVersions(true);
|
||||
assertEquals(7, adapter.getItemCount());
|
||||
|
||||
adapter.setShowVersions(false);
|
||||
assertEquals(4, adapter.getItemCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that every single item in the adapter gets its view holder created and bound.
|
||||
* Doesn't care about what type of holder it should be, the adapter is able to figure all that
|
||||
* out for us .
|
||||
*/
|
||||
private void populateViewHolders(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
|
||||
ViewGroup parent = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.app_details2_links, null);
|
||||
for (int i = 0; i < adapter.getItemCount(); i++) {
|
||||
RecyclerView.ViewHolder viewHolder = adapter.createViewHolder(parent, adapter.getItemViewType(i));
|
||||
adapter.bindViewHolder(viewHolder, i);
|
||||
}
|
||||
}
|
||||
|
||||
private final AppDetailsRecyclerViewAdapter.AppDetailsRecyclerViewAdapterCallbacks dummyCallbacks
|
||||
= new AppDetailsRecyclerViewAdapter.AppDetailsRecyclerViewAdapterCallbacks() {
|
||||
@Override
|
||||
public boolean isAppDownloading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openUrl(String url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installApk(Apk apk) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallApk() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installCancel() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchApk() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepoChanged(long repoId) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPreferredRepoChanged(long repoId) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.fdroid.fdroid.views.main;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MainActivityTest {
|
||||
|
||||
@Test
|
||||
public void testSanitizeSearchTerms() {
|
||||
for (String valid : new String[]{"private browser", "πÇÇ", "现代 通用字", "български", "عربي"}) {
|
||||
assertEquals(valid, MainActivity.sanitizeSearchTerms(valid));
|
||||
}
|
||||
for (String invalid : new String[]{
|
||||
"Robert'); DROP TABLE Students; --",
|
||||
"xxx') OR 1 = 1 -- ]",
|
||||
"105 OR 1=1;",
|
||||
"\" OR \"=\"",
|
||||
}) {
|
||||
assertNotEquals(invalid, MainActivity.sanitizeSearchTerms(invalid));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package org.fdroid.fdroid.work;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.fdroid.fdroid.BuildConfig;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.nearby.LocalRepoManager;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Test non-time-based cache deletion methods. Robolectric is lacking full
|
||||
* support for getting time from files, so methods that rely on time must be
|
||||
* tested in {@code CleanCacheWorkerTest} in {@code androidTest}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class CleanCacheWorkerTest {
|
||||
public static final String TAG = "CleanCacheWorkerTest";
|
||||
|
||||
private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
|
||||
private static final File FILES_DIR = CONTEXT.getFilesDir();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ShadowLog.stream = System.out;
|
||||
Preferences.setupForTests(CONTEXT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteOldInstallerFiles() throws IOException {
|
||||
Assume.assumeTrue(BuildConfig.FLAVOR.startsWith("full"));
|
||||
ArrayList<File> webRootAssetFiles = new ArrayList<>();
|
||||
File indexHtml = new File(FILES_DIR, "index.html");
|
||||
assertTrue(indexHtml.createNewFile());
|
||||
webRootAssetFiles.add(indexHtml);
|
||||
for (String name : LocalRepoManager.WEB_ROOT_ASSET_FILES) {
|
||||
File f = new File(FILES_DIR, name);
|
||||
assertTrue(f.createNewFile());
|
||||
webRootAssetFiles.add(f);
|
||||
}
|
||||
File apk = new File(FILES_DIR, "fake.apk");
|
||||
assertTrue(apk.createNewFile());
|
||||
File giantblob = new File(FILES_DIR, "giantblob");
|
||||
assertTrue(giantblob.createNewFile());
|
||||
File obf = new File(FILES_DIR, "fake.obf");
|
||||
assertTrue(obf.createNewFile());
|
||||
File zip = new File(FILES_DIR, "fake.zip");
|
||||
assertTrue(zip.createNewFile());
|
||||
CleanCacheWorker.deleteOldInstallerFiles(CONTEXT);
|
||||
assertFalse(apk.exists());
|
||||
assertFalse(giantblob.exists());
|
||||
assertFalse(obf.exists());
|
||||
assertFalse(zip.exists());
|
||||
for (File f : webRootAssetFiles) {
|
||||
assertTrue(f.exists());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure smoke check, Robolectric does not support file times fully.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteExpiredApksFromCache() {
|
||||
CleanCacheWorker.deleteExpiredApksFromCache(CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure smoke check, Robolectric does not support file times fully.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteStrayIndexFiles() {
|
||||
CleanCacheWorker.deleteStrayIndexFiles(CONTEXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure smoke check, Robolectric does not support file times fully.
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteOldIcons() {
|
||||
CleanCacheWorker.deleteOldIcons(CONTEXT);
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package org.fdroid.fdroid.work;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContextWrapper;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.fdroid.fdroid.Preferences;
|
||||
import org.fdroid.fdroid.TestUtils;
|
||||
import org.fdroid.fdroid.installer.InstallHistoryService;
|
||||
import org.fdroid.fdroid.work.FDroidMetricsWorker.MatomoEvent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
|
||||
@Config(application = Application.class)
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class FDroidMetricsWorkerTest {
|
||||
protected ContextWrapper context;
|
||||
|
||||
@Before
|
||||
public final void setUp() {
|
||||
context = ApplicationProvider.getApplicationContext();
|
||||
Preferences.setupForTests(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalizeTimestampToWeek() {
|
||||
long startTime = 1610038865743L;
|
||||
long endTime = 1610037631519L;
|
||||
|
||||
long normalizedStart = FDroidMetricsWorker.toCleanInsightsTimestamp(startTime);
|
||||
long normalizedEnd = FDroidMetricsWorker.toCleanInsightsTimestamp(endTime);
|
||||
assertEquals(normalizedStart, normalizedEnd);
|
||||
|
||||
long normalizedRelativeEnd = FDroidMetricsWorker.toCleanInsightsTimestamp(startTime, endTime);
|
||||
assertEquals(1609976365L, normalizedRelativeEnd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateReport() throws IOException {
|
||||
String json = FDroidMetricsWorker.generateReport(context);
|
||||
System.out.println(json);
|
||||
File downloads = new File(System.getenv("HOME"), "Downloads");
|
||||
if (downloads.exists()) {
|
||||
File output = new File(downloads, getClass().getName() + ".testGenerateReport.json");
|
||||
FileUtils.writeStringToFile(output, json);
|
||||
}
|
||||
// TODO validate against the schema
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseInstallHistory() throws IOException {
|
||||
FileUtils.copyFile(TestUtils.copyResourceToTempFile("install_history_all"),
|
||||
InstallHistoryService.getInstallHistoryFile(context));
|
||||
long weekStart = FDroidMetricsWorker.getReportingWeekStart(1611268892206L + DateUtils.WEEK_IN_MILLIS);
|
||||
Collection<? extends MatomoEvent> events = FDroidMetricsWorker.parseInstallHistoryCsv(context,
|
||||
weekStart);
|
||||
assertEquals(3, events.size());
|
||||
for (MatomoEvent event : events) {
|
||||
assertEquals(event.name, "com.termux");
|
||||
}
|
||||
|
||||
Collection<? extends MatomoEvent> oneWeekAgo = FDroidMetricsWorker.parseInstallHistoryCsv(context,
|
||||
weekStart - DateUtils.WEEK_IN_MILLIS);
|
||||
assertEquals(11, oneWeekAgo.size());
|
||||
|
||||
Collection<? extends MatomoEvent> twoWeeksAgo = FDroidMetricsWorker.parseInstallHistoryCsv(context,
|
||||
weekStart - (2 * DateUtils.WEEK_IN_MILLIS));
|
||||
assertEquals(0, twoWeeksAgo.size());
|
||||
|
||||
Collection<? extends MatomoEvent> threeWeeksAgo = FDroidMetricsWorker.parseInstallHistoryCsv(context,
|
||||
weekStart - (3 * DateUtils.WEEK_IN_MILLIS));
|
||||
assertEquals(9, threeWeeksAgo.size());
|
||||
assertNotEquals(oneWeekAgo, threeWeeksAgo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetReportingWeekStart() throws ParseException {
|
||||
long now = System.currentTimeMillis();
|
||||
long start = FDroidMetricsWorker.getReportingWeekStart(now);
|
||||
assertTrue((now - DateUtils.WEEK_IN_MILLIS) > start);
|
||||
assertTrue((now - DateUtils.WEEK_IN_MILLIS) < (start + DateUtils.WEEK_IN_MILLIS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.fdroid.ui.details
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* Tests modifications to app details descriptions done in [getHtmlDescription].
|
||||
*/
|
||||
class HtmlDescriptionTest {
|
||||
@Test
|
||||
fun testLinks() {
|
||||
val description = """
|
||||
2. If you have experience with Java and the Android SDK, then we look forward to your contributions! More info: https://mediawiki.org/wiki/Wikimedia_Apps/Team/Android/App_hacking
|
||||
|
||||
3. Explanation of permissions needed by the app: https://mediawiki.org/wiki/Wikimedia_Apps/Android_FAQ#Security_and_Permissions
|
||||
"""
|
||||
val expectedDescription = """<br>
|
||||
2. If you have experience with Java and the Android SDK, then we look forward to your contributions! More info: <a href="https://mediawiki.org/wiki/Wikimedia_Apps/Team/Android/App_hacking">https://mediawiki.org/wiki/Wikimedia_Apps/Team/Android/App_hacking</a><br>
|
||||
<br>
|
||||
3. Explanation of permissions needed by the app: <a href="https://mediawiki.org/wiki/Wikimedia_Apps/Android_FAQ#Security_and_Permissions">https://mediawiki.org/wiki/Wikimedia_Apps/Android_FAQ#Security_and_Permissions</a><br>
|
||||
"""
|
||||
assertEquals(expectedDescription, getHtmlDescription(description))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLinkAtTheVeryEnd() {
|
||||
val description = """
|
||||
Project page: https://github.com/lukaspieper/Gcam-Services-Provider"""
|
||||
val expectedDescription = """<br>
|
||||
Project page: <a href="https://github.com/lukaspieper/Gcam-Services-Provider">https://github.com/lukaspieper/Gcam-Services-Provider</a>"""
|
||||
assertEquals(expectedDescription, getHtmlDescription(description))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLinkWithDotAtTheEnd() {
|
||||
val description = """please visit our website: https://wikimediafoundation.org/."""
|
||||
|
||||
@Suppress("ktlint:standard:max-line-length")
|
||||
val expectedDescription = """please visit our website: <a href="https://wikimediafoundation.org/">https://wikimediafoundation.org/</a>."""
|
||||
assertEquals(expectedDescription, getHtmlDescription(description))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLinkInRoundBrackets() {
|
||||
val description = """our link (https://wikimediafoundation.org/)."""
|
||||
|
||||
@Suppress("ktlint:standard:max-line-length")
|
||||
val expectedDescription = """our link (<a href="https://wikimediafoundation.org/">https://wikimediafoundation.org/</a>)."""
|
||||
assertEquals(expectedDescription, getHtmlDescription(description))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHeadlineRemoval() {
|
||||
val description = """<h1>SimpleX - the first messaging platform that has no user identifiers, not even random numbers!</h1>
|
||||
<p><a href="https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html" target="_blank">Security assessment</a> was done by Trail of Bits in November 2022.</p>
|
||||
<p>SimpleX Chat features:</p>
|
||||
<ul>
|
||||
<li>end-to-end encrypted messages, with editing, replies and deletion of messages.</li>
|
||||
<li>sending end-to-end encrypted images and files.</li>"""
|
||||
|
||||
val expectedDescription = """SimpleX - the first messaging platform that has no user identifiers, not even random numbers!<br>
|
||||
<p><a href="https://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html" target="_blank">Security assessment</a> was done by Trail of Bits in November 2022.</p>
|
||||
<p>SimpleX Chat features:</p>
|
||||
<ul>
|
||||
<li>end-to-end encrypted messages, with editing, replies and deletion of messages.</li>
|
||||
<li>sending end-to-end encrypted images and files.</li>"""
|
||||
assertEquals(expectedDescription, getHtmlDescription(description))
|
||||
}
|
||||
}
|
||||
109
app/src/test/java/org/fdroid/ui/navigation/IntentRouterTest.kt
Normal file
109
app/src/test/java/org/fdroid/ui/navigation/IntentRouterTest.kt
Normal file
@@ -0,0 +1,109 @@
|
||||
package org.fdroid.ui.navigation
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_SHOW_APP_INFO
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.content.Intent.CATEGORY_BROWSABLE
|
||||
import android.content.Intent.EXTRA_PACKAGE_NAME
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation3.runtime.NavBackStack
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@Config(sdk = [24]) // needed for ACTION_SHOW_APP_INFO
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class IntentRouterTest {
|
||||
|
||||
val navigationState = NavigationState(
|
||||
startRoute = NavigationKey.Discover,
|
||||
topLevelRoute = mutableStateOf(NavigationKey.Discover),
|
||||
backStacks = topLevelRoutes.associateWith { key ->
|
||||
NavBackStack(key)
|
||||
}
|
||||
)
|
||||
val navigator = Navigator(navigationState)
|
||||
private val intentRouter = IntentRouter(navigator)
|
||||
|
||||
@Test
|
||||
fun testBrowserUris() {
|
||||
val packageName = "org.fdroid.fdroid"
|
||||
listOf(
|
||||
"https://f-droid.org/packages/$packageName/",
|
||||
"https://f-droid.org/de/packages/$packageName/",
|
||||
"https://f-droid.org/en-US/packages/$packageName",
|
||||
"https://cloudflare.f-droid.org/zh_Hans/packages/$packageName",
|
||||
"market://details?id=$packageName",
|
||||
).forEach { url ->
|
||||
val i = Intent().apply {
|
||||
action = ACTION_VIEW
|
||||
addCategory(CATEGORY_BROWSABLE)
|
||||
data = url.toUri()
|
||||
}
|
||||
intentRouter.accept(i)
|
||||
assertEquals(NavigationKey.AppDetails(packageName), navigator.last)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMalformedBrowserUris() {
|
||||
val packageName = "fdroid') DROP TABLE apps; --"
|
||||
listOf(
|
||||
"https://f-droid.org/packages/$packageName/",
|
||||
"https://f-droid.org/de/packages/$packageName/",
|
||||
"https://f-droid.org/en-US/packages/$packageName",
|
||||
"https://cloudflare.f-droid.org/zh_Hans/packages/$packageName",
|
||||
"market://details?id=$packageName",
|
||||
).forEach { url ->
|
||||
val i = Intent().apply {
|
||||
action = ACTION_VIEW
|
||||
addCategory(CATEGORY_BROWSABLE)
|
||||
data = url.toUri()
|
||||
}
|
||||
intentRouter.accept(i)
|
||||
assertEquals(NavigationKey.Discover, navigator.last)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowAppInfo() {
|
||||
val packageName = "org.fdroid.fdroid"
|
||||
val i = Intent().apply {
|
||||
action = ACTION_SHOW_APP_INFO
|
||||
putExtra(EXTRA_PACKAGE_NAME, packageName)
|
||||
}
|
||||
intentRouter.accept(i)
|
||||
assertEquals(NavigationKey.AppDetails(packageName), navigator.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowAppInfoMalformed() {
|
||||
val packageName = "fdroid') DROP TABLE apps; --"
|
||||
val i = Intent().apply {
|
||||
action = ACTION_SHOW_APP_INFO
|
||||
putExtra(EXTRA_PACKAGE_NAME, packageName)
|
||||
}
|
||||
intentRouter.accept(i)
|
||||
assertEquals(NavigationKey.Discover, navigator.last)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRepoUris() {
|
||||
listOf(
|
||||
"fdroidrepos://example.org/repo",
|
||||
"FDROIDREPOS://example.org/repo",
|
||||
"https://fdroid.link/#repo=https://f-droid.org/repo",
|
||||
"https://fdroid.link/#foo/bar",
|
||||
).forEach { uri ->
|
||||
val i = Intent(ACTION_VIEW).apply {
|
||||
data = uri.toUri()
|
||||
}
|
||||
intentRouter.accept(i)
|
||||
assertEquals(NavigationKey.AddRepo(uri), navigator.last)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user