diff --git a/app/build.gradle b/app/build.gradle index 8fd669d5d..a9bf227f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,11 @@ dependencies { testCompile 'junit:junit:4.12' + // 3.1-rc1 is required because it is the first to implements API v23. + testCompile "org.robolectric:robolectric:3.1-rc1" + + testCompile "org.mockito:mockito-core:1.10.19" + androidTestCompile 'com.android.support:support-annotations:23.4.0' androidTestCompile 'com.android.support.test:runner:0.5' androidTestCompile 'com.android.support.test:rules:0.5' @@ -225,13 +230,22 @@ pmd { consoleOutput = true } -task pmd(type: Pmd, dependsOn: assembleDebug) { - ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml") +task pmdMain(type: Pmd, dependsOn: assembleDebug) { + ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-main.xml") ruleSets = [] // otherwise defaults clash with the list in rules.xml - source 'src/main/java', 'src/test/java', 'src/androidTest/java' + source 'src/main/java' include '**/*.java' } +task pmdTest(type: Pmd, dependsOn: assembleDebug) { + ruleSetFiles = files("${project.rootDir}/config/pmd/rules.xml", "${project.rootDir}/config/pmd/rules-test.xml") + ruleSets = [] // otherwise defaults clash with the list in rules.xml + source 'src/test/java', 'src/androidTest/java' + include '**/*.java' +} + +task pmd(dependsOn: [pmdMain, pmdTest]) {} + // This person took the example code below from another blogpost online, however // I lost the reference to it: // http://stackoverflow.com/questions/23297562/gradle-javadoc-and-android-documentation diff --git a/app/src/androidTest/assets/simpleIndex.jar b/app/src/androidTest/assets/simpleIndex.jar index 1c173ceb3..e69de29bb 100644 Binary files a/app/src/androidTest/assets/simpleIndex.jar and b/app/src/androidTest/assets/simpleIndex.jar differ diff --git a/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java b/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java deleted file mode 100644 index 405902649..000000000 --- a/app/src/androidTest/java/android/test/ProviderTestCase2MockContext.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.test; - -import android.annotation.TargetApi; -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.res.Resources; -import android.database.DatabaseUtils; -import android.os.Build; -import android.test.mock.MockContentResolver; -import android.test.mock.MockContext; - -import org.junit.After; -import org.junit.Before; - -import java.io.File; - -/** - * This test case class provides a framework for testing a single - * {@link ContentProvider} and for testing your app code with an - * isolated content provider. Instead of using the system map of - * providers that is based on the manifests of other applications, the test - * case creates its own internal map. It then uses this map to resolve providers - * given an authority. This allows you to inject test providers and to null out - * providers that you do not want to use. - *

- * This test case also sets up the following mock objects: - *

- * - *

- * This framework is set up automatically by the base class' {@link #setUp()} method. If you - * override this method, you must call the super method as the first statement in - * your override. - *

- *

- * In order for their tests to be run, concrete subclasses must provide their own - * constructor with no arguments. This constructor must call - * {@link #ProviderTestCase2MockContext(Class, String)} as its first operation. - *

- * For more information on content provider testing, please see - * Content Provider Testing. - */ -public abstract class ProviderTestCase2MockContext extends AndroidTestCase { - - Class mProviderClass; - String mProviderAuthority; - - private IsolatedContext mProviderContext; - private MockContentResolver mResolver; - - private class MockContext2 extends MockContext { - - @Override - public Resources getResources() { - return getContext().getResources(); - } - - @Override - public File getDir(String name, int mode) { - // name the directory so the directory will be separated from - // one created through the regular Context - return getContext().getDir("mockcontext2_" + name, mode); - } - - @Override - public Context getApplicationContext() { - return this; - } - - @Override - public String getPackageName() { - return "org.fdroid.fdroid"; - } - } - - /** - * Constructor. - * - * @param providerClass The class name of the provider under test - * @param providerAuthority The provider's authority string - */ - public ProviderTestCase2MockContext(Class providerClass, String providerAuthority) { - mProviderClass = providerClass; - mProviderAuthority = providerAuthority; - } - - private T mProvider; - - /** - * Returns the content provider created by this class in the {@link #setUp()} method. - * @return T An instance of the provider class given as a parameter to the test case class. - */ - public T getProvider() { - return mProvider; - } - - protected abstract Context createMockContext(Context delegate); - - /** - * Sets up the environment for the test fixture. - *

- * Creates a new - * {@link android.test.mock.MockContentResolver}, a new IsolatedContext - * that isolates the provider's file operations, and a new instance of - * the provider under test within the isolated environment. - *

- * - * @throws Exception - */ - @Override - @Before - protected void setUp() throws Exception { - super.setUp(); - - mResolver = new MockContentResolver(); - final String filenamePrefix = "test."; - final RenamingDelegatingContext targetContextWrapper = new - RenamingDelegatingContext( - createMockContext(new MockContext2()), // The context that most methods are delegated to - getContext(), // The context that file methods are delegated to - filenamePrefix); - - mProviderContext = new IsolatedContext(mResolver, new ContextWrapper(targetContextWrapper) { - // The FDroidProvider class needs access to an application context in order to initialize - // the singleton DBHelper instance. - @Override - public Context getApplicationContext() { - return targetContextWrapper; - } - }); - - mProvider = mProviderClass.newInstance(); - mProvider.attachInfo(mProviderContext, null); - assertNotNull(mProvider); - mResolver.addProvider(mProviderAuthority, getProvider()); - } - - /** - * Tears down the environment for the test fixture. - *

- * Calls {@link android.content.ContentProvider#shutdown()} on the - * {@link android.content.ContentProvider} represented by mProvider. - */ - @Override - @After - protected void tearDown() throws Exception { - shutdownProvider(); - super.tearDown(); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - private void shutdownProvider() { - if (Build.VERSION.SDK_INT >= 11) { - mProvider.shutdown(); - } - } - - /** - * Gets the {@link MockContentResolver} created by this class during initialization. You - * must use the methods of this resolver to access the provider under test. - * - * @return A {@link MockContentResolver} instance. - */ - public MockContentResolver getMockContentResolver() { - return mResolver; - } - - /** - * Gets the {@link IsolatedContext} created by this class during initialization. - * @return The {@link IsolatedContext} instance - */ - public IsolatedContext getMockContext() { - return mProviderContext; - } - - /** - *

- * Creates a new content provider of the same type as that passed to the test case class, - * with an authority name set to the authority parameter, and using an SQLite database as - * the underlying data source. The SQL statement parameter is used to create the database. - * This method also creates a new {@link MockContentResolver} and adds the provider to it. - *

- *

- * Both the new provider and the new resolver are put into an {@link IsolatedContext} - * that uses the targetContext parameter for file operations and a {@link MockContext} - * for everything else. The IsolatedContext prepends the filenamePrefix parameter to - * file, database, and directory names. - *

- *

- * This is a convenience method for creating a "mock" provider that can contain test data. - *

- * - * @param targetContext The context to use as the basis of the IsolatedContext - * @param filenamePrefix A string that is prepended to file, database, and directory names - * @param providerClass The type of the provider being tested - * @param authority The authority string to associated with the test provider - * @param databaseName The name assigned to the database - * @param databaseVersion The version assigned to the database - * @param sql A string containing the SQL statements that are needed to create the desired - * database and its tables. The format is the same as that generated by the - * sqlite3 tool's .dump command. - * @return ContentResolver A new {@link MockContentResolver} linked to the provider - * - * @throws IllegalAccessException - * @throws InstantiationException - */ - public static ContentResolver newResolverWithContentProviderFromSql( - Context targetContext, String filenamePrefix, Class providerClass, String authority, - String databaseName, int databaseVersion, String sql) - throws IllegalAccessException, InstantiationException { - MockContentResolver resolver = new MockContentResolver(); - RenamingDelegatingContext targetContextWrapper = new RenamingDelegatingContext( - new MockContext(), // The context that most methods are delegated to - targetContext, // The context that file methods are delegated to - filenamePrefix); - Context context = new IsolatedContext(resolver, targetContextWrapper); - DatabaseUtils.createDbFromSqlStatements(context, databaseName, databaseVersion, sql); - - T provider = providerClass.newInstance(); - provider.attachInfo(context, null); - resolver.addProvider(authority, provider); - - return resolver; - } -} diff --git a/app/src/androidTest/java/mock/MockApplicationInfo.java b/app/src/androidTest/java/mock/MockApplicationInfo.java deleted file mode 100644 index 1ab4592a4..000000000 --- a/app/src/androidTest/java/mock/MockApplicationInfo.java +++ /dev/null @@ -1,29 +0,0 @@ -package mock; - -import android.annotation.SuppressLint; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -import java.io.File; -import java.io.IOException; - -@SuppressLint("ParcelCreator") -public class MockApplicationInfo extends ApplicationInfo { - - private final PackageInfo info; - - public MockApplicationInfo(PackageInfo info) { - this.info = info; - try { - this.publicSourceDir = File.createTempFile(info.packageName, "apk").getAbsolutePath(); - } catch (IOException e) { - this.publicSourceDir = "/data/app/" + info.packageName + "-4.apk"; - } - } - - @Override - public CharSequence loadLabel(PackageManager pm) { - return "Mock app: " + info.packageName; - } -} diff --git a/app/src/androidTest/java/mock/MockCategoryResources.java b/app/src/androidTest/java/mock/MockCategoryResources.java deleted file mode 100644 index 0f88ade9f..000000000 --- a/app/src/androidTest/java/mock/MockCategoryResources.java +++ /dev/null @@ -1,27 +0,0 @@ -package mock; - -import android.content.Context; - -import org.fdroid.fdroid.R; - -public class MockCategoryResources extends MockFDroidResources { - - public MockCategoryResources(Context getStringDelegatingContext) { - super(getStringDelegatingContext); - } - - @Override - public String getString(int id) { - switch (id) { - case R.string.category_All: - return "All"; - case R.string.category_Recently_Updated: - return "Recently Updated"; - case R.string.category_Whats_New: - return "Whats New"; - default: - return ""; - } - } - -} diff --git a/app/src/androidTest/java/mock/MockContextEmptyComponents.java b/app/src/androidTest/java/mock/MockContextEmptyComponents.java deleted file mode 100644 index eb962bbe9..000000000 --- a/app/src/androidTest/java/mock/MockContextEmptyComponents.java +++ /dev/null @@ -1,14 +0,0 @@ -package mock; - -/** - * As more components are required to test different parts of F-Droid, we can - * create them and add them here (and accessors to the parent class). - */ -public class MockContextEmptyComponents extends MockContextSwappableComponents { - - public MockContextEmptyComponents() { - setPackageManager(new MockEmptyPackageManager()); - setResources(new MockEmptyResources()); - } - -} diff --git a/app/src/androidTest/java/mock/MockContextSwappableComponents.java b/app/src/androidTest/java/mock/MockContextSwappableComponents.java deleted file mode 100644 index 5d94967f2..000000000 --- a/app/src/androidTest/java/mock/MockContextSwappableComponents.java +++ /dev/null @@ -1,43 +0,0 @@ -package mock; - -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.test.mock.MockContentResolver; -import android.test.mock.MockContext; - -public class MockContextSwappableComponents extends MockContext { - - private PackageManager packageManager; - private Resources resources; - private MockContentResolver contentResolver; - - public MockContextSwappableComponents setPackageManager(PackageManager pm) { - packageManager = pm; - return this; - } - - public MockContextSwappableComponents setResources(Resources resources) { - this.resources = resources; - return this; - } - - public MockContextSwappableComponents setContentResolver(MockContentResolver contentResolver) { - this.contentResolver = contentResolver; - return this; - } - - @Override - public PackageManager getPackageManager() { - return packageManager; - } - - @Override - public Resources getResources() { - return resources; - } - - @Override - public MockContentResolver getContentResolver() { - return contentResolver; - } -} diff --git a/app/src/androidTest/java/mock/MockEmptyPackageManager.java b/app/src/androidTest/java/mock/MockEmptyPackageManager.java deleted file mode 100644 index 463864179..000000000 --- a/app/src/androidTest/java/mock/MockEmptyPackageManager.java +++ /dev/null @@ -1,16 +0,0 @@ -package mock; - -import android.content.pm.PackageInfo; -import android.test.mock.MockPackageManager; - -import java.util.ArrayList; -import java.util.List; - -public class MockEmptyPackageManager extends MockPackageManager { - - @Override - public List getInstalledPackages(int flags) { - return new ArrayList<>(); - } - -} diff --git a/app/src/androidTest/java/mock/MockEmptyResources.java b/app/src/androidTest/java/mock/MockEmptyResources.java deleted file mode 100644 index fdc06e47f..000000000 --- a/app/src/androidTest/java/mock/MockEmptyResources.java +++ /dev/null @@ -1,12 +0,0 @@ -package mock; - -import android.test.mock.MockResources; - -public class MockEmptyResources extends MockResources { - - @Override - public String getString(int id) { - return ""; - } - -} diff --git a/app/src/androidTest/java/mock/MockFDroidResources.java b/app/src/androidTest/java/mock/MockFDroidResources.java deleted file mode 100644 index 5cb33798c..000000000 --- a/app/src/androidTest/java/mock/MockFDroidResources.java +++ /dev/null @@ -1,37 +0,0 @@ -package mock; - -import android.content.Context; -import android.test.mock.MockResources; - -import org.fdroid.fdroid.R; - -public class MockFDroidResources extends MockResources { - - private final Context getStringDelegatingContext; - - public MockFDroidResources(Context getStringDelegatingContext) { - this.getStringDelegatingContext = getStringDelegatingContext; - } - - @Override - public String getString(int id) { - return getStringDelegatingContext.getString(id); - } - - @Override - public int getInteger(int id) { - switch (id) { - case R.integer.fdroid_repo_inuse: - return 1; - case R.integer.fdroid_archive_inuse: - return 0; - case R.integer.fdroid_repo_priority: - return 10; - case R.integer.fdroid_archive_priority: - return 20; - default: - return 0; - } - } - -} diff --git a/app/src/androidTest/java/mock/MockInstallablePackageManager.java b/app/src/androidTest/java/mock/MockInstallablePackageManager.java deleted file mode 100644 index 1fcef1e86..000000000 --- a/app/src/androidTest/java/mock/MockInstallablePackageManager.java +++ /dev/null @@ -1,61 +0,0 @@ -package mock; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.test.mock.MockPackageManager; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -public class MockInstallablePackageManager extends MockPackageManager { - - private final List info = new ArrayList<>(); - - @Override - public List getInstalledPackages(int flags) { - return info; - } - - @Override - public PackageInfo getPackageInfo(String id, int flags) { - for (PackageInfo i : info) { - if (i.packageName.equals(id)) { - return i; - } - } - return null; - } - - public void install(String id, int version, String versionName) { - PackageInfo existing = getPackageInfo(id, 0); - if (existing != null) { - existing.versionCode = version; - existing.versionName = versionName; - } else { - PackageInfo p = new PackageInfo(); - p.packageName = id; - p.versionCode = version; - p.versionName = versionName; - p.applicationInfo = new MockApplicationInfo(p); - p.lastUpdateTime = System.currentTimeMillis(); - info.add(p); - } - } - - @Override - public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException { - return new MockApplicationInfo(getPackageInfo(packageName, 0)); - } - - public void remove(String id) { - for (Iterator it = info.iterator(); it.hasNext();) { - PackageInfo info = it.next(); - if (info.packageName.equals(id)) { - it.remove(); - return; - } - } - } - -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java b/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java index 819d8539b..18456f749 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/FileCompatTest.java @@ -1,7 +1,10 @@ package org.fdroid.fdroid; import android.app.Instrumentation; +import android.content.Context; import android.os.Build; +import android.os.Environment; +import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.util.Log; @@ -14,10 +17,15 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.UUID; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; /** @@ -36,8 +44,8 @@ public class FileCompatTest { @Before public void setUp() { Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - File dir = TestUtils.getWriteableDir(instrumentation); - sourceFile = SanitizedFile.knownSanitized(TestUtils.copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir)); + File dir = getWriteableDir(instrumentation); + sourceFile = SanitizedFile.knownSanitized(copyAssetToDir(instrumentation.getContext(), "simpleIndex.jar", dir)); destFile = new SanitizedFile(dir, "dest-" + UUID.randomUUID() + ".testproduct"); assertFalse(destFile.exists()); assertTrue(sourceFile.getAbsolutePath() + " should exist.", sourceFile.exists()); @@ -62,24 +70,70 @@ public class FileCompatTest { @Test public void testSymlinkLibcore() { - - if (Build.VERSION.SDK_INT >= 19) { - FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile); - assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists()); - } else { - Log.w(TAG, "Cannot test symlink-libcore on this device. Requires android-19, but this has android-" + Build.VERSION.SDK_INT); - } + assumeTrue(Build.VERSION.SDK_INT >= 19); + FileCompatForTest.symlinkLibcoreTest(sourceFile, destFile); + assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists()); } @Test public void testSymlinkOs() { - - if (Build.VERSION.SDK_INT >= 21) { - FileCompatForTest.symlinkOsTest(sourceFile, destFile); - assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists()); - } else { - Log.w(TAG, "Cannot test symlink-os on this device. Requires android-21, but only has android-" + Build.VERSION.SDK_INT); - } + assumeTrue(Build.VERSION.SDK_INT >= 21); + FileCompatForTest.symlinkOsTest(sourceFile, destFile); + assertTrue(destFile.getAbsolutePath() + " should exist after symlinking", destFile.exists()); } + @Nullable + private static File copyAssetToDir(Context context, String assetName, File directory) { + File tempFile; + InputStream input = null; + OutputStream output = null; + try { + tempFile = File.createTempFile(assetName + "-", ".testasset", directory); + Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory); + input = context.getAssets().open(assetName); + output = new FileOutputStream(tempFile); + Utils.copy(input, output); + } catch (IOException e) { + e.printStackTrace(); + return null; + } finally { + Utils.closeQuietly(output); + Utils.closeQuietly(input); + } + return tempFile; + } + + /** + * Prefer internal over external storage, because external tends to be FAT filesystems, + * which don't support symlinks (which we test using this method). + */ + private static File getWriteableDir(Instrumentation instrumentation) { + Context context = instrumentation.getContext(); + Context targetContext = instrumentation.getTargetContext(); + + File[] dirsToTry = new File[]{ + context.getCacheDir(), + context.getFilesDir(), + targetContext.getCacheDir(), + targetContext.getFilesDir(), + context.getExternalCacheDir(), + context.getExternalFilesDir(null), + targetContext.getExternalCacheDir(), + targetContext.getExternalFilesDir(null), + Environment.getExternalStorageDirectory(), + }; + + return getWriteableDir(dirsToTry); + } + + private static File getWriteableDir(File[] dirsToTry) { + + for (File dir : dirsToTry) { + if (dir != null && dir.canWrite()) { + return dir; + } + } + + return null; + } } diff --git a/app/src/androidTest/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/androidTest/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java deleted file mode 100644 index 9fc9c9443..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java +++ /dev/null @@ -1,446 +0,0 @@ - -package org.fdroid.fdroid; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.support.annotation.NonNull; -import android.test.InstrumentationTestCase; -import android.test.RenamingDelegatingContext; -import android.test.mock.MockContentResolver; -import android.text.TextUtils; -import android.util.Log; - -import org.fdroid.fdroid.RepoUpdater.UpdateException; -import org.fdroid.fdroid.data.Apk; -import org.fdroid.fdroid.data.ApkProvider; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.FDroidProvider; -import org.fdroid.fdroid.data.Repo; -import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.data.TempApkProvider; -import org.fdroid.fdroid.data.TempAppProvider; - -import java.io.File; -import java.util.List; -import java.util.UUID; - -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public class MultiRepoUpdaterTest extends InstrumentationTestCase { - private static final String TAG = "MultiRepoUpdaterTest"; - - private static final String REPO_MAIN = "Test F-Droid repo"; - private static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)"; - private static final String REPO_CONFLICTING = "Test F-Droid repo with different apps"; - - private Context context; - private RepoUpdater conflictingRepoUpdater; - private RepoUpdater mainRepoUpdater; - private RepoUpdater archiveRepoUpdater; - private File testFilesDir; - - private static final String PUB_KEY = - "3082050b308202f3a003020102020420d8f212300d06092a864886f70d01010b050030363110300e0603" + - "55040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e7365727779" + - "6c6f2e636f6d301e170d3135303931323233313632315a170d3433303132383233313632315a30363110" + - "300e060355040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e73" + - "657277796c6f2e636f6d30820222300d06092a864886f70d01010105000382020f003082020a02820201" + - "00b21fe72b84ce721967851364bd20511088d117bc3034e4bb4d3c1a06af2a308fdffdaf63b12e0926b9" + - "0545134b9ff570646cbcad89d9e86dcc8eb9977dd394240c75bccf5e8ddc3c5ef91b4f16eca5f36c36f1" + - "92463ff2c9257d3053b7c9ecdd1661bd01ec3fe70ee34a7e6b92ddba04f258a32d0cfb1b0ce85d047180" + - "97fc4bdfb54541b430dfcfc1c84458f9eb5627e0ec5341d561c3f15f228379a1282d241329198f31a7ac" + - "cd51ab2bbb881a1da55001123483512f77275f8990c872601198065b4e0137ddd1482e4fdefc73b857d4" + - "be324ca96c268ceb725398f8cc38a0dc6aa2c277f8686724e8c7ff3f320a05791fccacc6caa956cf23a9" + - "de2dc7070b262c0e35d90d17e90773bb11e875e79a8dfd958e359d5d5ad903a7cbc2955102502bd0134c" + - "a1ff7a0bbbbb57302e4a251e40724dcaa8ad024f4b3a71b8fceaac664c0dcc1995a1c4cf42676edad8bc" + - "b03ba255ab796677f18fff2298e1aaa5b134254b44d08a4d934c9859af7bbaf078c37b7f628db0e2cffb" + - "0493a669d5f4770d35d71284550ce06d6f6811cd2a31585085716257a4ba08ad968b0a2bf88f34ca2f2c" + - "73af1c042ab147597faccfb6516ef4468cfa0c5ab3c8120eaa7bac1080e4d2310f717db20815d0e1ee26" + - "bd4e47eed8d790892017ae9595365992efa1b7fd1bc1963f018264b2b3749b8f7b1907bb0843f1e7fc2d" + - "3f3b02284cd4bae0ab0203010001a321301f301d0603551d0e0416041456110e4fed863ab1df9448bfd9" + - "e10a8bc32ffe08300d06092a864886f70d01010b050003820201008082572ae930ebc55ecf1110f4bb72" + - "ad2a952c8ac6e65bd933706beb4a310e23deabb8ef6a7e93eea8217ab1f3f57b1f477f95f1d62eccb563" + - "67a4d70dfa6fcd2aace2bb00b90af39412a9441a9fae2396ff8b93de1df3d9837c599b1f80b7d75285cb" + - "df4539d7dd9612f54b45ca59bc3041c9b92fac12753fac154d12f31df360079ab69a2d20db9f6a7277a8" + - "259035e93de95e8cbc80351bc83dd24256183ea5e3e1db2a51ea314cdbc120c064b77e2eb3a731530511" + - "1e1dabed6996eb339b7cb948d05c1a84d63094b4a4c6d11389b2a7b5f2d7ecc9a149dda6c33705ef2249" + - "58afdfa1d98cf646dcf8857cd8342b1e07d62cb4313f35ad209046a4a42ff73f38cc740b1e695eeda49d" + - "5ea0384ad32f9e3ae54f6a48a558dbc7cccabd4e2b2286dc9c804c840bd02b9937841a0e48db00be9e3c" + - "d7120cf0f8648ce4ed63923f0352a2a7b3b97fc55ba67a7a218b8c0b3cda4a45861280a622e0a59cc9fb" + - "ca1117568126c581afa4408b0f5c50293c212c406b8ab8f50aad5ed0f038cfca580ef3aba7df25464d9e" + - "495ffb629922cfb511d45e6294c045041132452f1ed0f20ac3ab4792f610de1734e4c8b71d743c4b0101" + - "98f848e0dbfce5a0f2da0198c47e6935a47fda12c518ef45adfb66ddf5aebaab13948a66c004b8592d22" + - "e8af60597c4ae2977977cf61dc715a572e241ae717cafdb4f71781943945ac52e0f50b"; - - public class TestContext extends RenamingDelegatingContext { - - private MockContentResolver resolver; - - public TestContext() { - super(getInstrumentation().getTargetContext(), "test."); - - resolver = new MockContentResolver(); - resolver.addProvider(AppProvider.getAuthority(), prepareProvider(new AppProvider())); - resolver.addProvider(ApkProvider.getAuthority(), prepareProvider(new ApkProvider())); - resolver.addProvider(RepoProvider.getAuthority(), prepareProvider(new RepoProvider())); - resolver.addProvider(TempAppProvider.getAuthority(), prepareProvider(new TempAppProvider())); - resolver.addProvider(TempApkProvider.getAuthority(), prepareProvider(new TempApkProvider())); - } - - private ContentProvider prepareProvider(ContentProvider provider) { - provider.attachInfo(this, null); - provider.onCreate(); - return provider; - } - - @Override - public File getFilesDir() { - return getInstrumentation().getTargetContext().getFilesDir(); - } - - /** - * String resources used during testing (e.g. when bootstraping the database) are from - * the real org.fdroid.fdroid app, not the test org.fdroid.fdroid.test app. - */ - @Override - public Resources getResources() { - return getInstrumentation().getTargetContext().getResources(); - } - - @Override - public ContentResolver getContentResolver() { - return resolver; - } - - @Override - public AssetManager getAssets() { - return getInstrumentation().getContext().getAssets(); - } - - @Override - public File getDatabasePath(String name) { - return new File(getInstrumentation().getContext().getFilesDir(), "fdroid_test.db"); - } - - @Override - public Context getApplicationContext() { - // Used by the DBHelper singleton instance. - return this; - } - } - - @Override - public void setUp() throws Exception { - super.setUp(); - - FDroidProvider.clearDbHelperSingleton(); - - context = new TestContext(); - - testFilesDir = TestUtils.getWriteableDir(getInstrumentation()); - - // On a fresh database install, there will be F-Droid + GP repos, including their Archive - // repos that we are not interested in. - RepoProvider.Helper.remove(context, 1); - RepoProvider.Helper.remove(context, 2); - RepoProvider.Helper.remove(context, 3); - RepoProvider.Helper.remove(context, 4); - - conflictingRepoUpdater = createUpdater(REPO_CONFLICTING, context); - mainRepoUpdater = createUpdater(REPO_MAIN, context); - archiveRepoUpdater = createUpdater(REPO_ARCHIVE, context); - } - - /** - * Check that all of the expected apps and apk versions are available in the database. This - * check will take into account the repository the apks came from, to ensure that each - * repository indeed contains the apks that it said it would provide. - */ - private void assertExpected() { - Log.i(TAG, "Asserting all versions of each .apk are in index."); - List repos = RepoProvider.Helper.all(context); - assertEquals("Repos", 3, repos.size()); - - assertMainRepo(repos); - assertMainArchiveRepo(repos); - assertConflictingRepo(repos); - } - - /** - * - */ - private void assertSomewhatAcceptable() { - Log.i(TAG, "Asserting at least one versions of each .apk is in index."); - List repos = RepoProvider.Helper.all(context); - assertEquals("Repos", 3, repos.size()); - - assertApp2048(); - assertAppAdaway(); - assertAppAdbWireless(); - assertAppIcsImport(); - } - - private void assertApp(String packageName, int[] versionCodes) { - List apks = ApkProvider.Helper.findByPackageName(context, packageName, ApkProvider.DataColumns.ALL); - assertApksExist(apks, packageName, versionCodes); - } - - private void assertApp2048() { - assertApp("com.uberspot.a2048", new int[]{19, 18}); - } - - private void assertAppAdaway() { - assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35}); - } - - private void assertAppAdbWireless() { - assertApp("siir.es.adbWireless", new int[]{12}); - } - - private void assertAppIcsImport() { - assertApp("org.dgtale.icsimport", new int[]{3, 2}); - } - - /** - * + 2048 (com.uberspot.a2048) - * - Version 1.96 (19) - * - Version 1.95 (18) - * + AdAway (org.adaway) - * - Version 3.0.2 (54) - * - Version 3.0.1 (53) - * - Version 3.0 (52) - * + adbWireless (siir.es.adbWireless) - * - Version 1.5.4 (12) - */ - private void assertMainRepo(List allRepos) { - Repo repo = findRepo(REPO_MAIN, allRepos); - - List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL); - assertEquals("Apks for main repo", apks.size(), 6); - assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19}); - assertApksExist(apks, "org.adaway", new int[]{52, 53, 54}); - assertApksExist(apks, "siir.es.adbWireless", new int[]{12}); - } - - /** - * + AdAway (org.adaway) - * - Version 2.9.2 (51) - * - Version 2.9.1 (50) - * - Version 2.9 (49) - * - Version 2.8.1 (48) - * - Version 2.8 (47) - * - Version 2.7 (46) - * - Version 2.6 (45) - * - Version 2.3 (42) - * - Version 2.1 (40) - * - Version 1.37 (38) - * - Version 1.36 (37) - * - Version 1.35 (36) - * - Version 1.34 (35) - */ - private void assertMainArchiveRepo(List allRepos) { - Repo repo = findRepo(REPO_ARCHIVE, allRepos); - - List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL); - assertEquals("Apks for main archive repo", 13, apks.size()); - assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51}); - } - - /** - * + AdAway (org.adaway) - * - Version 3.0.1 (53) * - * - Version 3.0 (52) * - * - Version 2.9.2 (51) * - * - Version 2.2.1 (50) * - * + Add to calendar (org.dgtale.icsimport) - * - Version 1.2 (3) - * - Version 1.1 (2) - */ - private void assertConflictingRepo(List allRepos) { - Repo repo = findRepo(REPO_CONFLICTING, allRepos); - - List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL); - assertEquals("Apks for main repo", 6, apks.size()); - assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53}); - assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3}); - } - - @NonNull - private Repo findRepo(@NonNull String name, List allRepos) { - Repo repo = null; - for (Repo r : allRepos) { - if (TextUtils.equals(name, r.getName())) { - repo = r; - break; - } - } - - assertNotNull("Repo " + allRepos, repo); - return repo; - } - - /** - * Checks that each version of appId as specified in versionCodes is present in apksToCheck. - */ - private void assertApksExist(List apksToCheck, String appId, int[] versionCodes) { - for (int versionCode : versionCodes) { - boolean found = false; - for (Apk apk : apksToCheck) { - if (apk.versionCode == versionCode && apk.packageName.equals(appId)) { - found = true; - break; - } - } - - assertTrue("Couldn't find app " + appId + ", v" + versionCode, found); - } - } - - private void assertEmpty() { - assertEquals("No apps present", 0, AppProvider.Helper.all(context.getContentResolver()).size()); - - String[] packages = { - "com.uberspot.a2048", - "org.adaway", - "siir.es.adbWireless", - }; - - for (String id : packages) { - assertEquals("No apks for " + id, 0, ApkProvider.Helper.findByPackageName(context, id).size()); - } - } - - /* At time fo writing, the following tests did not pass. This is because the multi-repo support - in F-Droid was not sufficient. When working on proper multi repo support than this should be - ucommented and all these tests should pass: - - public void testCorrectConflictingThenMainThenArchive() throws UpdateException { - assertEmpty(); - if (updateConflicting() && updateMain() && updateArchive()) { - assertExpected(); - } - } - - public void testCorrectConflictingThenArchiveThenMain() throws UpdateException { - assertEmpty(); - if (updateConflicting() && updateArchive() && updateMain()) { - assertExpected(); - } - } - - public void testCorrectArchiveThenMainThenConflicting() throws UpdateException { - assertEmpty(); - if (updateArchive() && updateMain() && updateConflicting()) { - assertExpected(); - } - } - - public void testCorrectArchiveThenConflictingThenMain() throws UpdateException { - assertEmpty(); - if (updateArchive() && updateConflicting() && updateMain()) { - assertExpected(); - } - } - - public void testCorrectMainThenArchiveThenConflicting() throws UpdateException { - assertEmpty(); - if (updateMain() && updateArchive() && updateConflicting()) { - assertExpected(); - } - } - - public void testCorrectMainThenConflictingThenArchive() throws UpdateException { - assertEmpty(); - if (updateMain() && updateConflicting() && updateArchive()) { - assertExpected(); - } - } - - */ - - public void testAcceptableConflictingThenMainThenArchive() throws UpdateException { - assertEmpty(); - if (updateConflicting() && updateMain() && updateArchive()) { - assertSomewhatAcceptable(); - } - } - - public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException { - assertEmpty(); - if (updateConflicting() && updateArchive() && updateMain()) { - assertSomewhatAcceptable(); - } - } - - public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException { - assertEmpty(); - if (updateArchive() && updateMain() && updateConflicting()) { - assertSomewhatAcceptable(); - } - } - - public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException { - assertEmpty(); - if (updateArchive() && updateConflicting() && updateMain()) { - assertSomewhatAcceptable(); - } - } - - public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException { - assertEmpty(); - if (updateMain() && updateArchive() && updateConflicting()) { - assertSomewhatAcceptable(); - } - } - - public void testAcceptableMainThenConflictingThenArchive() throws UpdateException { - assertEmpty(); - if (updateMain() && updateConflicting() && updateArchive()) { - assertSomewhatAcceptable(); - } - } - - private RepoUpdater createUpdater(String name, Context context) { - Repo repo = new Repo(); - repo.signingCertificate = PUB_KEY; - repo.address = "https://fake.url/" + UUID.randomUUID().toString() + "/fdroid/repo"; - repo.name = name; - - ContentValues values = new ContentValues(2); - values.put(RepoProvider.DataColumns.SIGNING_CERT, repo.signingCertificate); - values.put(RepoProvider.DataColumns.ADDRESS, repo.address); - values.put(RepoProvider.DataColumns.NAME, repo.name); - - RepoProvider.Helper.insert(context, values); - - // Need to reload the repo based on address so that it includes the primary key from - // the database. - return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address)); - } - - private boolean updateConflicting() throws UpdateException { - return updateRepo(conflictingRepoUpdater, "multiRepo.conflicting.jar"); - } - - private boolean updateMain() throws UpdateException { - return updateRepo(mainRepoUpdater, "multiRepo.normal.jar"); - } - - private boolean updateArchive() throws UpdateException { - return updateRepo(archiveRepoUpdater, "multiRepo.archive.jar"); - } - - private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { - if (!testFilesDir.canWrite()) { - return false; - } - - File indexJar = TestUtils.copyAssetToDir(context, indexJarPath, testFilesDir); - updater.processDownloadedFile(indexJar); - return true; - } - -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java b/app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java deleted file mode 100644 index ac77cbd60..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/RepoUpdaterTest.java +++ /dev/null @@ -1,175 +0,0 @@ - -package org.fdroid.fdroid; - -import android.app.Instrumentation; -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; -import android.util.Log; - -import org.fdroid.fdroid.RepoUpdater.UpdateException; -import org.fdroid.fdroid.data.Repo; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; - -@RunWith(AndroidJUnit4.class) -public class RepoUpdaterTest { - public static final String TAG = "RepoUpdaterTest"; - - private Context context; - private Repo repo; - private RepoUpdater repoUpdater; - private File testFilesDir; - - String simpleIndexSigningCert = "308201ee30820157a0030201020204300d845b300d06092a864886f70d01010b0500302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e301e170d3134303432373030303633315a170d3431303931323030303633315a302a3110300e060355040b1307462d44726f6964311630140603550403130d70616c6174736368696e6b656e30819f300d06092a864886f70d010101050003818d0030818902818100a439472e4b6d01141bfc94ecfe131c7c728fdda670bb14c57ca60bd1c38a8b8bc0879d22a0a2d0bc0d6fdd4cb98d1d607c2caefbe250a0bd0322aedeb365caf9b236992fac13e6675d3184a6c7c6f07f73410209e399a9da8d5d7512bbd870508eebacff8b57c3852457419434d34701ccbf692267cbc3f42f1c5d1e23762d790203010001a321301f301d0603551d0e041604140b1840691dab909746fde4bfe28207d1cae15786300d06092a864886f70d01010b05000381810062424c928ffd1b6fd419b44daafef01ca982e09341f7077fb865905087aeac882534b3bd679b51fdfb98892cef38b63131c567ed26c9d5d9163afc775ac98ad88c405d211d6187bde0b0d236381cc574ba06ef9080721a92ae5a103a7301b2c397eecc141cc850dd3e123813ebc41c59d31ddbcb6e984168280c53272f6a442b"; - - /** - * Getting a writeable dir during the tests seems to be a flaky prospect. - */ - private boolean canWrite() { - if (testFilesDir.canWrite()) { - return true; - } else { - Log.e(TAG, "ERROR: " + testFilesDir + " is not writable, skipping test"); - return false; - } - } - - @Before - public void setUp() { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - context = instrumentation.getContext(); - testFilesDir = TestUtils.getWriteableDir(instrumentation); - repo = new Repo(); - repo.address = "https://fake.url/fdroid/repo"; - repo.signingCertificate = this.simpleIndexSigningCert; - } - - @Test - public void testExtractIndexFromJar() { - assumeTrue(canWrite()); - File simpleIndexJar = TestUtils.copyAssetToDir(context, "simpleIndex.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - - // these are supposed to succeed - try { - repoUpdater.processDownloadedFile(simpleIndexJar); - } catch (UpdateException e) { - e.printStackTrace(); - fail(); - } - } - - @Test(expected = UpdateException.class) - public void testExtractIndexFromOutdatedJar() throws UpdateException { - assumeTrue(canWrite()); - File simpleIndexJar = TestUtils.copyAssetToDir(context, "simpleIndex.jar", testFilesDir); - repo.version = 10; - repo.timestamp = System.currentTimeMillis() / 1000L; - repoUpdater = new RepoUpdater(context, repo); - - // these are supposed to fail - repoUpdater.processDownloadedFile(simpleIndexJar); - fail(); - } - - @Test(expected = UpdateException.class) - public void testExtractIndexFromJarWithoutSignatureJar() throws UpdateException { - assumeTrue(canWrite()); - // this is supposed to fail - File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithoutSignature.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - repoUpdater.processDownloadedFile(jarFile); - fail(); - } - - @Test - public void testExtractIndexFromJarWithCorruptedManifestJar() { - assumeTrue(canWrite()); - // this is supposed to fail - try { - File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedManifest.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - repoUpdater.processDownloadedFile(jarFile); - fail(); - } catch (UpdateException e) { - e.printStackTrace(); - fail(); - } catch (SecurityException e) { - // success! - } - } - - @Test - public void testExtractIndexFromJarWithCorruptedSignature() { - assumeTrue(canWrite()); - // this is supposed to fail - try { - File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedSignature.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - repoUpdater.processDownloadedFile(jarFile); - fail(); - } catch (UpdateException e) { - e.printStackTrace(); - fail(); - } catch (SecurityException e) { - // success! - } - } - - @Test - public void testExtractIndexFromJarWithCorruptedCertificate() { - assumeTrue(canWrite()); - // this is supposed to fail - try { - File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedCertificate.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - repoUpdater.processDownloadedFile(jarFile); - fail(); - } catch (UpdateException e) { - e.printStackTrace(); - fail(); - } catch (SecurityException e) { - // success! - } - } - - @Test - public void testExtractIndexFromJarWithCorruptedEverything() { - assumeTrue(canWrite()); - // this is supposed to fail - try { - File jarFile = TestUtils.copyAssetToDir(context, "simpleIndexWithCorruptedEverything.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - repoUpdater.processDownloadedFile(jarFile); - fail(); - } catch (UpdateException e) { - e.printStackTrace(); - fail(); - } catch (SecurityException e) { - // success! - } - } - - @Test - public void testExtractIndexFromMasterKeyIndexJar() { - assumeTrue(canWrite()); - // this is supposed to fail - try { - File jarFile = TestUtils.copyAssetToDir(context, "masterKeyIndex.jar", testFilesDir); - repoUpdater = new RepoUpdater(context, repo); - repoUpdater.processDownloadedFile(jarFile); - fail(); //NOPMD - } catch (UpdateException e) { - // success! - } catch (SecurityException e) { - // success! - } - } -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java b/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java deleted file mode 100644 index 8b01eb630..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.fdroid.fdroid; - -import android.app.Instrumentation; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.net.Uri; -import android.os.Environment; -import android.support.annotation.Nullable; -import android.util.Log; - -import junit.framework.AssertionFailedError; - -import org.fdroid.fdroid.data.ApkProvider; -import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.FDroidProviderTest; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class TestUtils { - - private static final String TAG = "TestUtils"; - - public static void assertContainsOnly(List actualList, T[] expectedArray) { - List expectedList = new ArrayList<>(expectedArray.length); - Collections.addAll(expectedList, expectedArray); - assertContainsOnly(actualList, expectedList); - } - - public static void assertContainsOnly(T[] actualArray, List expectedList) { - List actualList = new ArrayList<>(actualArray.length); - Collections.addAll(actualList, actualArray); - assertContainsOnly(actualList, expectedList); - } - - public static void assertContainsOnly(T[] actualArray, T[] expectedArray) { - List expectedList = new ArrayList<>(expectedArray.length); - Collections.addAll(expectedList, expectedArray); - assertContainsOnly(actualArray, expectedList); - } - - public static String listToString(List list) { - String string = "["; - for (int i = 0; i < list.size(); i++) { - if (i > 0) { - string += ", "; - } - string += "'" + list.get(i) + "'"; - } - string += "]"; - return string; - } - - public static void assertContainsOnly(List actualList, List expectedContains) { - if (actualList.size() != expectedContains.size()) { - String message = - "List sizes don't match.\n" + - "Expected: " + - listToString(expectedContains) + "\n" + - "Actual: " + - listToString(actualList); - throw new AssertionFailedError(message); - } - for (T required : expectedContains) { - boolean containsRequired = false; - for (T itemInList : actualList) { - if (required.equals(itemInList)) { - containsRequired = true; - break; - } - } - if (!containsRequired) { - String message = - "List doesn't contain \"" + required + "\".\n" + - "Expected: " + - listToString(expectedContains) + "\n" + - "Actual: " + - listToString(actualList); - throw new AssertionFailedError(message); - } - } - } - - public static void insertApp(ContentResolver resolver, String appId, String name) { - insertApp(resolver, appId, name, new ContentValues()); - } - - public static void insertApp(ContentResolver resolver, String id, String name, ContentValues additionalValues) { - - ContentValues values = new ContentValues(); - values.put(AppProvider.DataColumns.PACKAGE_NAME, id); - values.put(AppProvider.DataColumns.NAME, name); - - // Required fields (NOT NULL in the database). - values.put(AppProvider.DataColumns.SUMMARY, "test summary"); - values.put(AppProvider.DataColumns.DESCRIPTION, "test description"); - values.put(AppProvider.DataColumns.LICENSE, "GPL?"); - values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1); - values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0); - values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0); - - values.putAll(additionalValues); - - Uri uri = AppProvider.getContentUri(); - - resolver.insert(uri, values); - } - - public static Uri insertApk(FDroidProviderTest providerTest, String id, int versionCode) { - return insertApk(providerTest, id, versionCode, new ContentValues()); - } - - public static Uri insertApk(FDroidProviderTest providerTest, String id, int versionCode, ContentValues additionalValues) { - - ContentValues values = new ContentValues(); - - values.put(ApkProvider.DataColumns.PACKAGE_NAME, id); - values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode); - - // Required fields (NOT NULL in the database). - values.put(ApkProvider.DataColumns.REPO_ID, 1); - values.put(ApkProvider.DataColumns.VERSION_NAME, "The good one"); - values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa"); - values.put(ApkProvider.DataColumns.NAME, "Test Apk"); - values.put(ApkProvider.DataColumns.SIZE, 10000); - values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1); - - values.putAll(additionalValues); - - Uri uri = ApkProvider.getContentUri(); - - return providerTest.getMockContentResolver().insert(uri, values); - } - - @Nullable - public static File copyAssetToDir(Context context, String assetName, File directory) { - File tempFile; - InputStream input = null; - OutputStream output = null; - try { - tempFile = File.createTempFile(assetName + "-", ".testasset", directory); - Log.i(TAG, "Copying asset file " + assetName + " to directory " + directory); - input = context.getAssets().open(assetName); - output = new FileOutputStream(tempFile); - Utils.copy(input, output); - } catch (IOException e) { - e.printStackTrace(); - return null; - } finally { - Utils.closeQuietly(output); - Utils.closeQuietly(input); - } - return tempFile; - } - - /** - * Prefer internal over external storage, because external tends to be FAT filesystems, - * which don't support symlinks (which we test using this method). - */ - public static File getWriteableDir(Instrumentation instrumentation) { - Context context = instrumentation.getContext(); - Context targetContext = instrumentation.getTargetContext(); - - File[] dirsToTry = new File[]{ - context.getCacheDir(), - context.getFilesDir(), - targetContext.getCacheDir(), - targetContext.getFilesDir(), - context.getExternalCacheDir(), - context.getExternalFilesDir(null), - targetContext.getExternalCacheDir(), - targetContext.getExternalFilesDir(null), - Environment.getExternalStorageDirectory(), - }; - - return getWriteableDir(dirsToTry); - } - - private static File getWriteableDir(File[] dirsToTry) { - - for (File dir : dirsToTry) { - if (dir != null && dir.canWrite()) { - return dir; - } - } - - return null; - } -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderHelperTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderHelperTest.java deleted file mode 100644 index 307655a72..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderHelperTest.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; - -import org.fdroid.fdroid.TestUtils; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.mock.MockApk; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; - -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public class ApkProviderHelperTest extends BaseApkProviderTest { - - public void testKnownApks() { - - for (int i = 0; i < 7; i++) { - TestUtils.insertApk(this, "org.fdroid.fdroid", i); - } - - for (int i = 0; i < 9; i++) { - TestUtils.insertApk(this, "org.example", i); - } - - for (int i = 0; i < 3; i++) { - TestUtils.insertApk(this, "com.example", i); - } - - TestUtils.insertApk(this, "com.apk.thingo", 1); - - Apk[] known = { - new MockApk("org.fdroid.fdroid", 1), - new MockApk("org.fdroid.fdroid", 3), - new MockApk("org.fdroid.fdroid", 5), - - new MockApk("com.example", 1), - new MockApk("com.example", 2), - }; - - Apk[] unknown = { - new MockApk("org.fdroid.fdroid", 7), - new MockApk("org.fdroid.fdroid", 9), - new MockApk("org.fdroid.fdroid", 11), - new MockApk("org.fdroid.fdroid", 13), - - new MockApk("com.example", 3), - new MockApk("com.example", 4), - new MockApk("com.example", 5), - - new MockApk("info.example", 1), - new MockApk("info.example", 2), - }; - - List apksToCheck = new ArrayList<>(known.length + unknown.length); - Collections.addAll(apksToCheck, known); - Collections.addAll(apksToCheck, unknown); - - String[] projection = { - ApkProvider.DataColumns.PACKAGE_NAME, - ApkProvider.DataColumns.VERSION_CODE, - }; - - List knownApks = ApkProvider.Helper.knownApks(getMockContext(), apksToCheck, projection); - - assertResultCount(known.length, knownApks); - - for (Apk knownApk : knownApks) { - assertContains(knownApks, knownApk); - } - } - - public void testFindByApp() { - - for (int i = 0; i < 7; i++) { - TestUtils.insertApk(this, "org.fdroid.fdroid", i); - } - - for (int i = 0; i < 9; i++) { - TestUtils.insertApk(this, "org.example", i); - } - - for (int i = 0; i < 3; i++) { - TestUtils.insertApk(this, "com.example", i); - } - - TestUtils.insertApk(this, "com.apk.thingo", 1); - - assertTotalApkCount(7 + 9 + 3 + 1); - - List fdroidApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.fdroid.fdroid"); - assertResultCount(7, fdroidApks); - assertBelongsToApp(fdroidApks, "org.fdroid.fdroid"); - - List exampleApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.example"); - assertResultCount(9, exampleApks); - assertBelongsToApp(exampleApks, "org.example"); - - List exampleApks2 = ApkProvider.Helper.findByPackageName(getMockContext(), "com.example"); - assertResultCount(3, exampleApks2); - assertBelongsToApp(exampleApks2, "com.example"); - - List thingoApks = ApkProvider.Helper.findByPackageName(getMockContext(), "com.apk.thingo"); - assertResultCount(1, thingoApks); - assertBelongsToApp(thingoApks, "com.apk.thingo"); - } - - public void testUpdate() { - - Uri apkUri = TestUtils.insertApk(this, "com.example", 10); - - String[] allFields = ApkProvider.DataColumns.ALL; - Cursor cursor = getMockContentResolver().query(apkUri, allFields, null, null, null); - assertResultCount(1, cursor); - - cursor.moveToFirst(); - Apk apk = new Apk(cursor); - cursor.close(); - - assertEquals("com.example", apk.packageName); - assertEquals(10, apk.versionCode); - - assertNull(apk.features); - assertNull(apk.added); - assertNull(apk.hashType); - - apk.features = Utils.CommaSeparatedList.make("one,two,three"); - long dateTimestamp = System.currentTimeMillis(); - apk.added = new Date(dateTimestamp); - apk.hashType = "i'm a hash type"; - - ApkProvider.Helper.update(getMockContext(), apk); - - // Should not have inserted anything else, just updated the already existing apk. - Cursor allCursor = getMockContentResolver().query(ApkProvider.getContentUri(), allFields, null, null, null); - assertResultCount(1, allCursor); - allCursor.close(); - - Cursor updatedCursor = getMockContentResolver().query(apkUri, allFields, null, null, null); - assertResultCount(1, updatedCursor); - - updatedCursor.moveToFirst(); - Apk updatedApk = new Apk(updatedCursor); - updatedCursor.close(); - - assertEquals("com.example", updatedApk.packageName); - assertEquals(10, updatedApk.versionCode); - - assertNotNull(updatedApk.features); - assertNotNull(updatedApk.added); - assertNotNull(updatedApk.hashType); - - assertEquals("one,two,three", updatedApk.features.toString()); - assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear()); - assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth()); - assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay()); - assertEquals("i'm a hash type", updatedApk.hashType); - } - - public void testFind() { - - // Insert some random apks either side of the "com.example", so that - // the Helper.find() method doesn't stumble upon the app we are interested - // in by shear dumb luck... - for (int i = 0; i < 10; i++) { - TestUtils.insertApk(this, "org.fdroid.apk." + i, i); - } - - ContentValues values = new ContentValues(); - values.put(ApkProvider.DataColumns.VERSION_NAME, "v1.1"); - values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy"); - values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type"); - TestUtils.insertApk(this, "com.example", 11, values); - - // ...and a few more for good measure... - for (int i = 15; i < 20; i++) { - TestUtils.insertApk(this, "com.other.thing." + i, i); - } - - Apk apk = ApkProvider.Helper.find(getMockContext(), "com.example", 11); - - assertNotNull(apk); - - // The find() method populates ALL fields if you don't specify any, - // so we expect to find each of the ones we inserted above... - assertEquals("com.example", apk.packageName); - assertEquals(11, apk.versionCode); - assertEquals("v1.1", apk.versionName); - assertEquals("xxxxyyyy", apk.hash); - assertEquals("a hash type", apk.hashType); - - String[] projection = { - ApkProvider.DataColumns.PACKAGE_NAME, - ApkProvider.DataColumns.HASH, - }; - - Apk apkLessFields = ApkProvider.Helper.find(getMockContext(), "com.example", 11, projection); - - assertNotNull(apkLessFields); - - assertEquals("com.example", apkLessFields.packageName); - assertEquals("xxxxyyyy", apkLessFields.hash); - - // Didn't ask for these fields, so should be their default values... - assertNull(apkLessFields.hashType); - assertNull(apkLessFields.versionName); - assertEquals(0, apkLessFields.versionCode); - - Apk notFound = ApkProvider.Helper.find(getMockContext(), "com.doesnt.exist", 1000); - assertNull(notFound); - } - -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderTest.java deleted file mode 100644 index 4dced204f..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/ApkProviderTest.java +++ /dev/null @@ -1,334 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; - -import org.fdroid.fdroid.TestUtils; -import org.fdroid.fdroid.mock.MockApk; -import org.fdroid.fdroid.mock.MockApp; -import org.fdroid.fdroid.mock.MockRepo; - -import java.util.ArrayList; -import java.util.List; - -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public class ApkProviderTest extends BaseApkProviderTest { - - /** - * I want to test the protected {@link org.fdroid.fdroid.data.ApkProvider#getContentUri(java.util.List)} - * method, but don't want to make it public. This exposes it. - */ - private static class PublicApkProvider extends ApkProvider { - - public static final int MAX_APKS_TO_QUERY = ApkProvider.MAX_APKS_TO_QUERY; - - public static Uri getContentUri(List apks) { - return ApkProvider.getContentUri(apks); - } - } - - public void testUris() { - assertInvalidUri(ApkProvider.getAuthority()); - assertInvalidUri(RepoProvider.getContentUri()); - - List apks = new ArrayList<>(3); - for (int i = 0; i < 10; i++) { - apks.add(new MockApk("com.example." + i, i)); - } - - assertValidUri(ApkProvider.getContentUri()); - assertValidUri(ApkProvider.getAppUri("org.fdroid.fdroid")); - assertValidUri(ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100))); - assertValidUri(ApkProvider.getContentUri()); - assertValidUri(PublicApkProvider.getContentUri(apks)); - assertValidUri(ApkProvider.getContentUri("org.fdroid.fdroid", 100)); - assertValidUri(ApkProvider.getRepoUri(1000)); - - List manyApks = new ArrayList<>(PublicApkProvider.MAX_APKS_TO_QUERY - 5); - for (int i = 0; i < PublicApkProvider.MAX_APKS_TO_QUERY - 1; i++) { - manyApks.add(new MockApk("com.example." + i, i)); - } - assertValidUri(PublicApkProvider.getContentUri(manyApks)); - - manyApks.add(new MockApk("org.fdroid.fdroid.1", 1)); - manyApks.add(new MockApk("org.fdroid.fdroid.2", 2)); - try { - // Technically, it is a valid URI, because it doesn't - // throw an UnsupportedOperationException. However it - // is still not okay (we run out of bindable parameters - // in the sqlite query. - assertValidUri(PublicApkProvider.getContentUri(manyApks)); - fail(); - } catch (IllegalArgumentException e) { - // This is the expected error behaviour. - } catch (Exception e) { - fail(); - } - } - - public void testAppApks() { - for (int i = 1; i <= 10; i++) { - TestUtils.insertApk(this, "org.fdroid.fdroid", i); - TestUtils.insertApk(this, "com.example", i); - } - - assertTotalApkCount(20); - - Cursor fdroidApks = getMockContentResolver().query( - ApkProvider.getAppUri("org.fdroid.fdroid"), - getMinimalProjection(), - null, null, null); - assertResultCount(10, fdroidApks); - assertBelongsToApp(fdroidApks, "org.fdroid.fdroid"); - fdroidApks.close(); - - Cursor exampleApks = getMockContentResolver().query( - ApkProvider.getAppUri("com.example"), - getMinimalProjection(), - null, null, null); - assertResultCount(10, exampleApks); - assertBelongsToApp(exampleApks, "com.example"); - exampleApks.close(); - - ApkProvider.Helper.deleteApksByApp(getMockContext(), new MockApp("com.example")); - - Cursor all = queryAllApks(); - assertResultCount(10, all); - assertBelongsToApp(all, "org.fdroid.fdroid"); - all.close(); - } - - public void testInvalidUpdateUris() { - Apk apk = new MockApk("org.fdroid.fdroid", 10); - - List apks = new ArrayList<>(); - apks.add(apk); - - assertCantUpdate(ApkProvider.getContentUri()); - assertCantUpdate(ApkProvider.getAppUri("org.fdroid.fdroid")); - assertCantUpdate(ApkProvider.getRepoUri(1)); - assertCantUpdate(PublicApkProvider.getContentUri(apks)); - assertCantUpdate(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path")); - - // The only valid ones are: - // ApkProvider.getContentUri(apk) - // ApkProvider.getContentUri(id, version) - // which are tested elsewhere. - } - - public void testDeleteArbitraryApks() { - Apk one = insertApkForRepo("com.example.one", 1, 10); - Apk two = insertApkForRepo("com.example.two", 1, 10); - Apk three = insertApkForRepo("com.example.three", 1, 10); - Apk four = insertApkForRepo("com.example.four", 1, 10); - Apk five = insertApkForRepo("com.example.five", 1, 10); - - assertTotalApkCount(5); - - assertEquals("com.example.one", one.packageName); - assertEquals("com.example.two", two.packageName); - assertEquals("com.example.five", five.packageName); - - String[] expectedIds = { - "com.example.one", - "com.example.two", - "com.example.three", - "com.example.four", - "com.example.five", - }; - - List all = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL); - List actualIds = new ArrayList<>(); - for (Apk apk : all) { - actualIds.add(apk.packageName); - } - - TestUtils.assertContainsOnly(actualIds, expectedIds); - - List toDelete = new ArrayList<>(3); - toDelete.add(two); - toDelete.add(three); - toDelete.add(four); - ApkProvider.Helper.deleteApks(getSwappableContext(), toDelete); - - assertTotalApkCount(2); - - List allRemaining = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL); - List actualRemainingIds = new ArrayList<>(); - for (Apk apk : allRemaining) { - actualRemainingIds.add(apk.packageName); - } - - String[] expectedRemainingIds = { - "com.example.one", - "com.example.five", - }; - - TestUtils.assertContainsOnly(actualRemainingIds, expectedRemainingIds); - } - - public void testInvalidDeleteUris() { - Apk apk = new MockApk("org.fdroid.fdroid", 10); - - assertCantDelete(ApkProvider.getContentUri()); - assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10)); - assertCantDelete(ApkProvider.getContentUri(apk)); - assertCantDelete(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path")); - } - - private static final long REPO_KEEP = 1; - private static final long REPO_DELETE = 2; - - public void testRepoApks() { - - // Insert apks into two repos, one of which we will later purge the - // the apks from. - for (int i = 1; i <= 5; i++) { - insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP); - insertApkForRepo("com.example." + i, 1, REPO_DELETE); - } - for (int i = 6; i <= 10; i++) { - insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE); - insertApkForRepo("com.example." + i, 1, REPO_KEEP); - } - - assertTotalApkCount(20); - - Cursor cursor = getMockContentResolver().query( - ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null); - assertResultCount(10, cursor); - assertBelongsToRepo(cursor, REPO_DELETE); - cursor.close(); - - int count = ApkProvider.Helper.deleteApksByRepo(getMockContext(), new MockRepo(REPO_DELETE)); - assertEquals(10, count); - - assertTotalApkCount(10); - cursor = getMockContentResolver().query( - ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null); - assertResultCount(0, cursor); - cursor.close(); - - // The only remaining apks should be those from REPO_KEEP. - assertBelongsToRepo(queryAllApks(), REPO_KEEP); - } - - public void testQuery() { - Cursor cursor = queryAllApks(); - assertNotNull(cursor); - cursor.close(); - } - - public void testInsert() { - - // Start with an empty database... - Cursor cursor = queryAllApks(); - assertNotNull(cursor); - assertEquals(0, cursor.getCount()); - cursor.close(); - - Apk apk = new MockApk("org.fdroid.fdroid", 13); - - // Insert a new record... - Uri newUri = TestUtils.insertApk(this, apk.packageName, apk.versionCode); - assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString()); - cursor = queryAllApks(); - assertNotNull(cursor); - assertEquals(1, cursor.getCount()); - - // We intentionally throw an IllegalArgumentException if you haven't - // yet called cursor.move*()... - try { - new Apk(cursor); - fail(); - } catch (IllegalArgumentException e) { - // Success! - } catch (Exception e) { - fail(); - } - - // And now we should be able to recover these values from the apk - // value object (because the queryAllApks() helper asks for VERSION_CODE and - // PACKAGE_NAME. - cursor.moveToFirst(); - Apk toCheck = new Apk(cursor); - cursor.close(); - assertEquals("org.fdroid.fdroid", toCheck.packageName); - assertEquals(13, toCheck.versionCode); - } - - public void testCount() { - String[] projectionFields = getMinimalProjection(); - String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT}; - - for (int i = 0; i < 13; i++) { - TestUtils.insertApk(this, "com.example", i); - } - - Uri all = ApkProvider.getContentUri(); - Cursor allWithFields = getMockContentResolver().query(all, projectionFields, null, null, null); - Cursor allWithCount = getMockContentResolver().query(all, projectionCount, null, null, null); - - assertResultCount(13, allWithFields); - allWithFields.close(); - assertResultCount(1, allWithCount); - - allWithCount.moveToFirst(); - int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT); - assertEquals(13, allWithCount.getInt(countColumn)); - allWithCount.close(); - } - - public void testInsertWithExtraFields() { - - assertResultCount(0, queryAllApks()); - - String[] repoFields = new String[] { - RepoProvider.DataColumns.DESCRIPTION, - RepoProvider.DataColumns.ADDRESS, - RepoProvider.DataColumns.FINGERPRINT, - RepoProvider.DataColumns.NAME, - RepoProvider.DataColumns.SIGNING_CERT, - }; - - for (String field : repoFields) { - ContentValues invalidRepo = new ContentValues(); - invalidRepo.put(field, "Test data"); - try { - TestUtils.insertApk(this, "org.fdroid.fdroid", 10, invalidRepo); - fail(); - } catch (IllegalArgumentException e) { - } catch (Exception e) { - fail(); - } - assertResultCount(0, queryAllApks()); - } - - ContentValues values = new ContentValues(); - values.put(ApkProvider.DataColumns.REPO_ID, 10); - values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com"); - values.put(ApkProvider.DataColumns.REPO_VERSION, 3); - values.put(ApkProvider.DataColumns.FEATURES, "Some features"); - Uri uri = TestUtils.insertApk(this, "com.example.com", 1, values); - - assertResultCount(1, queryAllApks()); - - String[] projections = ApkProvider.DataColumns.ALL; - Cursor cursor = getMockContentResolver().query(uri, projections, null, null, null); - cursor.moveToFirst(); - Apk apk = new Apk(cursor); - cursor.close(); - - // These should have quietly been dropped when we tried to save them, - // because the provider only knows how to query them (not update them). - assertEquals(null, apk.repoAddress); - assertEquals(0, apk.repoVersion); - - // But this should have saved correctly... - assertEquals("Some features", apk.features.toString()); - assertEquals("com.example.com", apk.packageName); - assertEquals(1, apk.versionCode); - assertEquals(10, apk.repo); - } -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java deleted file mode 100644 index 454759c0e..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/AppProviderTest.java +++ /dev/null @@ -1,389 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.pm.PackageInfo; -import android.content.res.Resources; -import android.database.Cursor; - -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.TestUtils; - -import java.util.ArrayList; -import java.util.List; - -import mock.MockCategoryResources; -import mock.MockContextSwappableComponents; -import mock.MockInstallablePackageManager; - -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public class AppProviderTest extends FDroidProviderTest { - - public AppProviderTest() { - super(AppProvider.class, AppProvider.getAuthority()); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - getSwappableContext().setResources(new MockCategoryResources(getContext())); - } - - @Override - protected Resources getMockResources() { - return new MockCategoryResources(getContext()); - } - - @Override - protected String[] getMinimalProjection() { - return new String[] { - AppProvider.DataColumns.PACKAGE_NAME, - AppProvider.DataColumns.NAME, - }; - } - - /** - * Although this doesn't directly relate to the AppProvider, it is here because - * the AppProvider used to stumble across this bug when asking for installed apps, - * and the device had over 1000 apps installed. - */ - public void testMaxSqliteParams() { - - MockInstallablePackageManager pm = new MockInstallablePackageManager(); - getSwappableContext().setPackageManager(pm); - - insertApp("com.example.app1", "App 1"); - insertApp("com.example.app100", "App 100"); - insertApp("com.example.app1000", "App 1000"); - - for (int i = 0; i < 50; i++) { - String packageName = "com.example.app" + i; - pm.install(packageName, 1, "v" + 1); - PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); - InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo); - } - assertResultCount(1, AppProvider.getInstalledUri()); - - for (int i = 50; i < 500; i++) { - String packageName = "com.example.app" + i; - pm.install(packageName, 1, "v" + 1); - PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); - InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo); - } - assertResultCount(2, AppProvider.getInstalledUri()); - - for (int i = 500; i < 1100; i++) { - String packageName = "com.example.app" + i; - pm.install(packageName, 1, "v" + 1); - PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); - InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo); - } - assertResultCount(3, AppProvider.getInstalledUri()); - } - - public void testCantFindApp() { - assertNull(AppProvider.Helper.findByPackageName(getMockContentResolver(), "com.example.doesnt-exist")); - } - - public void testUris() { - assertInvalidUri(AppProvider.getAuthority()); - assertInvalidUri(ApkProvider.getContentUri()); - - assertValidUri(AppProvider.getContentUri(), "content://org.fdroid.fdroid.data.AppProvider"); - assertValidUri(AppProvider.getSearchUri("'searching!'"), "content://org.fdroid.fdroid.data.AppProvider/search/'searching!'"); - assertValidUri(AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F"); - assertValidUri(AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider"); - assertValidUri(AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider"); - assertValidUri(AppProvider.getNoApksUri()); - assertValidUri(AppProvider.getInstalledUri()); - assertValidUri(AppProvider.getCanUpdateUri()); - - App app = new App(); - app.packageName = "org.fdroid.fdroid"; - - List apps = new ArrayList<>(1); - apps.add(app); - - assertValidUri(AppProvider.getContentUri(app)); - assertValidUri(AppProvider.getContentUri(apps)); - assertValidUri(AppProvider.getContentUri("org.fdroid.fdroid")); - } - - public void testQuery() { - Cursor cursor = queryAllApps(); - assertNotNull(cursor); - cursor.close(); - } - - private void insertApps(int count) { - for (int i = 0; i < count; i++) { - insertApp("com.example.test." + i, "Test app " + i); - } - } - - private void insertAndInstallApp( - MockInstallablePackageManager packageManager, - String id, int installedVercode, int suggestedVercode, - boolean ignoreAll, int ignoreVercode) { - ContentValues values = new ContentValues(3); - values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode); - values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAll); - values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode); - insertApp(id, "App: " + id, values); - - InstalledAppProviderTest.install(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode); - } - - public void testCanUpdate() { - - MockContextSwappableComponents c = getSwappableContext(); - - MockInstallablePackageManager pm = new MockInstallablePackageManager(); - c.setPackageManager(pm); - - insertApp("not installed", "not installed"); - insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0); - insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0); - insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0); - insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10); - insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5); - insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0); - insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0); - insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10); - insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8); - - ContentResolver r = getMockContentResolver(); - - // Can't "update", although can "install"... - App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed"); - assertFalse(notInstalled.canAndWantToUpdate()); - - App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available"); - App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore"); - App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all"); - App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest"); - App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old"); - - assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate()); - assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate()); - assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate()); - assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate()); - assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate()); - - App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore"); - App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all"); - App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest"); - App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest"); - - assertTrue(installedOldNoIgnore.canAndWantToUpdate()); - assertFalse(installedOldIgnoreAll.canAndWantToUpdate()); - assertFalse(installedOldIgnoreLatest.canAndWantToUpdate()); - assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate()); - - Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null); - canUpdateCursor.moveToFirst(); - List canUpdateIds = new ArrayList<>(canUpdateCursor.getCount()); - while (!canUpdateCursor.isAfterLast()) { - canUpdateIds.add(new App(canUpdateCursor).packageName); - canUpdateCursor.moveToNext(); - } - canUpdateCursor.close(); - - String[] expectedUpdateableIds = { - "installed, old version, no ignore", - "installed, old version, ignore newer, but not latest", - }; - - TestUtils.assertContainsOnly(expectedUpdateableIds, canUpdateIds); - } - - public void testIgnored() { - - MockInstallablePackageManager pm = new MockInstallablePackageManager(); - getSwappableContext().setPackageManager(pm); - - insertApp("not installed", "not installed"); - insertAndInstallApp(pm, "installed, only one version available", 1, 1, false, 0); - insertAndInstallApp(pm, "installed, already latest, no ignore", 10, 10, false, 0); - insertAndInstallApp(pm, "installed, already latest, ignore all", 10, 10, true, 0); - insertAndInstallApp(pm, "installed, already latest, ignore latest", 10, 10, false, 10); - insertAndInstallApp(pm, "installed, already latest, ignore old", 10, 10, false, 5); - insertAndInstallApp(pm, "installed, old version, no ignore", 5, 10, false, 0); - insertAndInstallApp(pm, "installed, old version, ignore all", 5, 10, true, 0); - insertAndInstallApp(pm, "installed, old version, ignore latest", 5, 10, false, 10); - insertAndInstallApp(pm, "installed, old version, ignore newer, but not latest", 5, 10, false, 8); - - assertResultCount(10, AppProvider.getContentUri()); - - String[] projection = {AppProvider.DataColumns.PACKAGE_NAME}; - List ignoredApps = AppProvider.Helper.findIgnored(getMockContext(), projection); - - String[] expectedIgnored = { - "installed, already latest, ignore all", - "installed, already latest, ignore latest", - // NOT "installed, already latest, ignore old" - because it - // is should only ignore if "ignored version" is >= suggested - - "installed, old version, ignore all", - "installed, old version, ignore latest", - // NOT "installed, old version, ignore newer, but not latest" - // for the same reason as above. - }; - - assertContainsOnlyIds(ignoredApps, expectedIgnored); - } - - private void assertContainsOnlyIds(List actualApps, String[] expectedIds) { - List actualIds = new ArrayList<>(actualApps.size()); - for (App app : actualApps) { - actualIds.add(app.packageName); - } - TestUtils.assertContainsOnly(actualIds, expectedIds); - } - - public void testInstalled() { - MockInstallablePackageManager pm = new MockInstallablePackageManager(); - getSwappableContext().setPackageManager(pm); - - insertApps(100); - - assertResultCount(100, AppProvider.getContentUri()); - assertResultCount(0, AppProvider.getInstalledUri()); - - for (int i = 10; i < 20; i++) { - InstalledAppProviderTest.install(getSwappableContext(), pm, "com.example.test." + i, i, "v1"); - } - - assertResultCount(10, AppProvider.getInstalledUri()); - } - - public void testInsert() { - - // Start with an empty database... - Cursor cursor = queryAllApps(); - assertNotNull(cursor); - assertEquals(0, cursor.getCount()); - cursor.close(); - - // Insert a new record... - insertApp("org.fdroid.fdroid", "F-Droid"); - cursor = queryAllApps(); - assertNotNull(cursor); - assertEquals(1, cursor.getCount()); - - // We intentionally throw an IllegalArgumentException if you haven't - // yet called cursor.move*()... - try { - new App(cursor); - fail(); - } catch (IllegalArgumentException e) { - // Success! - } catch (Exception e) { - fail(); - } - - // And now we should be able to recover these values from the app - // value object (because the queryAllApps() helper asks for NAME and - // PACKAGE_NAME. - cursor.moveToFirst(); - App app = new App(cursor); - cursor.close(); - assertEquals("org.fdroid.fdroid", app.packageName); - assertEquals("F-Droid", app.name); - } - - private Cursor queryAllApps() { - return getMockContentResolver().query(AppProvider.getContentUri(), getMinimalProjection(), null, null, null); - } - - // ======================================================================== - // "Categories" - // (at this point) not an additional table, but we treat them sort of - // like they are. That means that if we change the implementation to - // use a separate table in the future, these should still pass. - // ======================================================================== - - public void testCategoriesSingle() { - insertAppWithCategory("com.dog", "Dog", "Animal"); - insertAppWithCategory("com.rock", "Rock", "Mineral"); - insertAppWithCategory("com.banana", "Banana", "Vegetable"); - - List categories = AppProvider.Helper.categories(getMockContext()); - String[] expected = new String[] { - getMockContext().getResources().getString(R.string.category_Whats_New), - getMockContext().getResources().getString(R.string.category_Recently_Updated), - getMockContext().getResources().getString(R.string.category_All), - "Animal", - "Mineral", - "Vegetable", - }; - TestUtils.assertContainsOnly(categories, expected); - } - - public void testCategoriesMultiple() { - insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal"); - insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable"); - insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable"); - - List categories = AppProvider.Helper.categories(getMockContext()); - String[] expected = new String[] { - getMockContext().getResources().getString(R.string.category_Whats_New), - getMockContext().getResources().getString(R.string.category_Recently_Updated), - getMockContext().getResources().getString(R.string.category_All), - - "Animal", - "Mineral", - "Vegetable", - }; - TestUtils.assertContainsOnly(categories, expected); - - insertAppWithCategory("com.example.game", "Game", - "Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," + - "The quick brown fox jumps over the lazy dog,With apostrophe's"); - - List categoriesLonger = AppProvider.Helper.categories(getMockContext()); - String[] expectedLonger = new String[] { - getMockContext().getResources().getString(R.string.category_Whats_New), - getMockContext().getResources().getString(R.string.category_Recently_Updated), - getMockContext().getResources().getString(R.string.category_All), - - "Animal", - "Mineral", - "Vegetable", - - "Running", - "Shooting", - "Jumping", - "Bleh", - "Sneh", - "Pleh", - "Blah", - "Test category", - "The quick brown fox jumps over the lazy dog", - "With apostrophe's", - }; - - TestUtils.assertContainsOnly(categoriesLonger, expectedLonger); - } - - // ======================================================================= - // Misc helper functions - // (to be used by any tests in this suite) - // ======================================================================= - - private void insertApp(String id, String name) { - insertApp(id, name, new ContentValues()); - } - - private void insertAppWithCategory(String id, String name, String categories) { - ContentValues values = new ContentValues(1); - values.put(AppProvider.DataColumns.CATEGORIES, categories); - insertApp(id, name, values); - } - - private void insertApp(String id, String name, - ContentValues additionalValues) { - TestUtils.insertApp(getMockContentResolver(), id, name, additionalValues); - } - -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java deleted file mode 100644 index f5a73197c..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentValues; -import android.database.Cursor; -import android.net.Uri; - -import org.fdroid.fdroid.TestUtils; - -import java.util.List; - -/** - * Provides helper methods that can be used by both Helper and plain old - * Provider tests. Allows the test classes to contain only test methods, - * hopefully making them easier to understand. - * - * This should not contain any test methods, or else they get executed - * once for every concrete subclass. - */ -abstract class BaseApkProviderTest extends FDroidProviderTest { - - BaseApkProviderTest() { - super(ApkProvider.class, ApkProvider.getAuthority()); - } - - @Override - protected String[] getMinimalProjection() { - return new String[] { - ApkProvider.DataColumns.PACKAGE_NAME, - ApkProvider.DataColumns.VERSION_CODE, - ApkProvider.DataColumns.NAME, - ApkProvider.DataColumns.REPO_ID, - }; - } - - protected final Cursor queryAllApks() { - return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null); - } - - protected void assertContains(List apks, Apk apk) { - boolean found = false; - for (Apk a : apks) { - if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) { - found = true; - break; - } - } - if (!found) { - fail("Apk [" + apk + "] not found in " + TestUtils.listToString(apks)); - } - } - - protected void assertBelongsToApp(Cursor apks, String appId) { - assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId); - } - - protected void assertBelongsToApp(List apks, String appId) { - for (Apk apk : apks) { - assertEquals(appId, apk.packageName); - } - } - - protected void assertTotalApkCount(int expected) { - assertResultCount(expected, queryAllApks()); - } - - protected void assertBelongsToRepo(Cursor apkCursor, long repoId) { - for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) { - assertEquals(repoId, apk.repo); - } - } - - protected Apk insertApkForRepo(String id, int versionCode, long repoId) { - ContentValues additionalValues = new ContentValues(); - additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId); - Uri uri = TestUtils.insertApk(this, id, versionCode, additionalValues); - return ApkProvider.Helper.get(getSwappableContext(), uri); - } -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java deleted file mode 100644 index 40e81fd28..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentValues; -import android.content.Context; -import android.content.res.Resources; -import android.database.Cursor; -import android.net.Uri; -import android.provider.ContactsContract; -import android.test.ProviderTestCase2MockContext; - -import java.util.List; - -import mock.MockContextEmptyComponents; -import mock.MockContextSwappableComponents; -import mock.MockFDroidResources; - -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public abstract class FDroidProviderTest extends ProviderTestCase2MockContext { - - private FDroidProvider[] allProviders = { - new AppProvider(), - new RepoProvider(), - new ApkProvider(), - new InstalledAppProvider(), - }; - - private MockContextSwappableComponents swappableContext; - - public FDroidProviderTest(Class providerClass, String providerAuthority) { - super(providerClass, providerAuthority); - } - - protected Resources getMockResources() { - return new MockFDroidResources(getContext()); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - - FDroidProvider.clearDbHelperSingleton(); - - // Instantiate all providers other than the one which was already created by the base class. - // This is because F-Droid providers tend to perform joins onto tables managed by other - // providers, and so we need to be able to insert into those other providers for these - // joins to be tested correctly. - for (FDroidProvider provider : allProviders) { - if (!provider.getName().equals(getProvider().getName())) { - provider.attachInfo(getMockContext(), null); - getMockContentResolver().addProvider(provider.getName(), provider); - } - } - - getSwappableContext().setResources(getMockResources()); - - // The *Provider.Helper.* functions tend to take a Context as their - // first parameter. This context is used to connect to the relevant - // content provider. Thus, we need a context that is able to connect - // to the mock content resolver, in order to reach the provider - // under test. - getSwappableContext().setContentResolver(getMockContentResolver()); - - } - - public void testObviouslyInvalidUris() { - assertInvalidUri("http://www.google.com"); - assertInvalidUri(ContactsContract.AUTHORITY_URI); - assertInvalidUri("junk"); - } - - @Override - protected Context createMockContext(Context delegate) { - swappableContext = new MockContextEmptyComponents(); - return swappableContext; - } - - public MockContextSwappableComponents getSwappableContext() { - return swappableContext; - } - - protected void assertCantDelete(Uri uri) { - try { - getMockContentResolver().delete(uri, null, null); - fail(); - } catch (UnsupportedOperationException e) { - } catch (Exception e) { - fail(); - } - } - - protected void assertCantUpdate(Uri uri) { - try { - getMockContentResolver().update(uri, new ContentValues(), null, null); - fail(); - } catch (UnsupportedOperationException e) { - } catch (Exception e) { - fail(); - } - } - - protected void assertInvalidUri(String uri) { - assertInvalidUri(Uri.parse(uri)); - } - - protected void assertValidUri(String uri) { - assertValidUri(Uri.parse(uri)); - } - - protected void assertInvalidUri(Uri uri) { - try { - // Use getProvdider instead of getContentResolver, because the mock - // content resolver wont result in the provider we are testing, and - // hence we don't get to see how our provider responds to invalid - // uris. - getProvider().query(uri, getMinimalProjection(), null, null, null); - fail(); - } catch (UnsupportedOperationException e) { } - } - - protected void assertValidUri(Uri uri) { - Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null); - assertNotNull(cursor); - cursor.close(); - } - - protected void assertValidUri(Uri actualUri, String expectedUri) { - assertValidUri(actualUri); - assertEquals(expectedUri, actualUri.toString()); - } - - /** - * Many queries need at least some sort of projection in order to produce - * valid SQL. As such, we also need to know about that, so we can provide - * helper functions that revolve around the contnet provider under test. - */ - protected abstract String[] getMinimalProjection(); - - protected void assertResultCount(int expectedCount, Uri uri) { - Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null); - assertResultCount(expectedCount, cursor); - cursor.close(); - } - - protected void assertResultCount(int expectedCount, List items) { - assertNotNull(items); - assertEquals(expectedCount, items.size()); - } - - protected void assertResultCount(int expectedCount, Cursor result) { - assertNotNull(result); - assertEquals(expectedCount, result.getCount()); - } - - protected void assertIsInstalledVersionInDb(String appId, int versionCode, String versionName) { - Uri uri = InstalledAppProvider.getAppUri(appId); - - String[] projection = { - InstalledAppProvider.DataColumns.PACKAGE_NAME, - InstalledAppProvider.DataColumns.VERSION_CODE, - InstalledAppProvider.DataColumns.VERSION_NAME, - InstalledAppProvider.DataColumns.APPLICATION_LABEL, - }; - - Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null); - - assertNotNull(cursor); - assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount()); - - cursor.moveToFirst(); - - assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME))); - assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE))); - assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME))); - cursor.close(); - } - -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java deleted file mode 100644 index cb63f631a..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.fdroid.fdroid.data; - -import mock.MockInstallablePackageManager; - -/** - * Tests the ability of the {@link InstalledAppCacheUpdater} to stay in sync with - * the {@link android.content.pm.PackageManager}. - * For practical reasons, it extends FDroidProviderTest, although there is also a - * separate test for the InstalledAppProvider which tests the CRUD operations in more detail. - */ -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public class InstalledAppCacheTest extends FDroidProviderTest { - - private MockInstallablePackageManager packageManager; - - public InstalledAppCacheTest() { - super(InstalledAppProvider.class, InstalledAppProvider.getAuthority()); - } - - @Override - public void setUp() throws Exception { - super.setUp(); - packageManager = new MockInstallablePackageManager(); - getSwappableContext().setPackageManager(packageManager); - } - - @Override - protected String[] getMinimalProjection() { - return new String[] { - InstalledAppProvider.DataColumns.PACKAGE_NAME, - }; - } - - public void install(String appId, int versionCode, String versionName) { - packageManager.install(appId, versionCode, versionName); - } - - public void remove(String appId) { - packageManager.remove(appId); - } - -/* TODO fix me - public void testFromEmptyCache() { - assertResultCount(0, InstalledAppProvider.getContentUri()); - for (int i = 1; i <= 15; i ++) { - install("com.example.app" + i, 200, "2.0"); - } - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - String[] expectedInstalledIds = { - "com.example.app1", - "com.example.app2", - "com.example.app3", - "com.example.app4", - "com.example.app5", - "com.example.app6", - "com.example.app7", - "com.example.app8", - "com.example.app9", - "com.example.app10", - "com.example.app11", - "com.example.app12", - "com.example.app13", - "com.example.app14", - "com.example.app15", - }; - - TestUtils.assertContainsOnly(getInstalledAppIdsFromProvider(), expectedInstalledIds); - } - - private String[] getInstalledAppIdsFromProvider() { - Uri uri = InstalledAppProvider.getContentUri(); - String[] projection = { InstalledAppProvider.DataColumns.PACKAGE_NAME }; - Cursor result = getMockContext().getContentResolver().query(uri, projection, null, null, null); - if (result == null) { - return new String[0]; - } - - String[] installedAppIds = new String[result.getCount()]; - result.moveToFirst(); - int i = 0; - while (!result.isAfterLast()) { - installedAppIds[i] = result.getString(result.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME)); - result.moveToNext(); - i ++; - } - result.close(); - return installedAppIds; - } - - public void testAppsAdded() { - assertResultCount(0, InstalledAppProvider.getContentUri()); - - install("com.example.app1", 1, "v1"); - install("com.example.app2", 1, "v1"); - install("com.example.app3", 1, "v1"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(3, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app2", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app3", 1, "v1"); - - install("com.example.app10", 1, "v1"); - install("com.example.app11", 1, "v1"); - install("com.example.app12", 1, "v1"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(6, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app10", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app11", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app12", 1, "v1"); - } - - public void testAppsRemoved() { - install("com.example.app1", 1, "v1"); - install("com.example.app2", 1, "v1"); - install("com.example.app3", 1, "v1"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(3, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app2", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app3", 1, "v1"); - - remove("com.example.app2"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(2, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app3", 1, "v1"); - } - - public void testAppsUpdated() { - install("com.example.app1", 1, "v1"); - install("com.example.app2", 1, "v1"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(2, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app2", 1, "v1"); - - install("com.example.app2", 20, "v2.0"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(2, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app2", 20, "v2.0"); - } - - public void testAppsAddedRemovedAndUpdated() { - install("com.example.app1", 1, "v1"); - install("com.example.app2", 1, "v1"); - install("com.example.app3", 1, "v1"); - install("com.example.app4", 1, "v1"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(4, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app2", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app3", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app4", 1, "v1"); - - install("com.example.app1", 13, "v1.3"); - remove("com.example.app2"); - remove("com.example.app3"); - install("com.example.app10", 1, "v1"); - InstalledAppCacheUpdater.updateInForeground(getMockContext()); - - assertResultCount(3, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app1", 13, "v1.3"); - assertIsInstalledVersionInDb("com.example.app4", 1, "v1"); - assertIsInstalledVersionInDb("com.example.app10", 1, "v1"); - - } -*/ -} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java deleted file mode 100644 index 7da03fcd0..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.ContentValues; -import android.content.pm.PackageInfo; -import android.database.Cursor; -import android.net.Uri; - -import mock.MockContextSwappableComponents; -import mock.MockInstallablePackageManager; - -@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public class InstalledAppProviderTest extends FDroidProviderTest { - - public InstalledAppProviderTest() { - super(InstalledAppProvider.class, InstalledAppProvider.getAuthority()); - } - - public void testUris() { - assertInvalidUri(InstalledAppProvider.getAuthority()); - assertInvalidUri(RepoProvider.getContentUri()); - assertInvalidUri(AppProvider.getContentUri()); - assertInvalidUri(ApkProvider.getContentUri()); - assertInvalidUri("blah"); - - assertValidUri(InstalledAppProvider.getContentUri()); - assertValidUri(InstalledAppProvider.getAppUri("com.example.com")); - assertValidUri(InstalledAppProvider.getAppUri("blah")); - } - - public void testInsert() { - - assertResultCount(0, InstalledAppProvider.getContentUri()); - - insertInstalledApp("com.example.com1", 1, "v1"); - insertInstalledApp("com.example.com2", 2, "v2"); - insertInstalledApp("com.example.com3", 3, "v3"); - - assertResultCount(3, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.com1", 1, "v1"); - assertIsInstalledVersionInDb("com.example.com2", 2, "v2"); - assertIsInstalledVersionInDb("com.example.com3", 3, "v3"); - } - - public void testUpdate() { - - insertInstalledApp("com.example.app1", 10, "1.0"); - insertInstalledApp("com.example.app2", 10, "1.0"); - - assertResultCount(2, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app2", 10, "1.0"); - - try { - getMockContentResolver().update( - InstalledAppProvider.getAppUri("com.example.app2"), - createContentValues(11, "1.1"), - null, null - ); - fail(); - } catch (UnsupportedOperationException e) { - // We expect this to happen, because we should be using insert() instead. - } - - getMockContentResolver().insert( - InstalledAppProvider.getContentUri(), - createContentValues("com.example.app2", 11, "1.1") - ); - - assertResultCount(2, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app2", 11, "1.1"); - - } - - public void testLastUpdateTime() { - String packageName = "com.example.app"; - - insertInstalledApp(packageName, 10, "1.0"); - assertResultCount(1, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb(packageName, 10, "1.0"); - - Uri uri = InstalledAppProvider.getAppUri(packageName); - - String[] projection = { - InstalledAppProvider.DataColumns.PACKAGE_NAME, - InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, - }; - - Cursor cursor = getMockContentResolver().query(uri, projection, null, null, null); - assertNotNull(cursor); - assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount()); - cursor.moveToFirst(); - assertEquals(packageName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME))); - long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME)); - assertTrue(lastUpdateTime > 0); - assertTrue(lastUpdateTime < System.currentTimeMillis()); - cursor.close(); - - insertInstalledApp(packageName, 11, "1.1"); - cursor = getMockContentResolver().query(uri, projection, null, null, null); - assertNotNull(cursor); - assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount()); - cursor.moveToFirst(); - assertTrue(lastUpdateTime < cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME))); - cursor.close(); - } - - public void testDelete() { - - insertInstalledApp("com.example.app1", 10, "1.0"); - insertInstalledApp("com.example.app2", 10, "1.0"); - - assertResultCount(2, InstalledAppProvider.getContentUri()); - - getMockContentResolver().delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null); - - assertResultCount(1, InstalledAppProvider.getContentUri()); - assertIsInstalledVersionInDb("com.example.app2", 10, "1.0"); - - } - - @Override - protected String[] getMinimalProjection() { - return new String[]{ - InstalledAppProvider.DataColumns.PACKAGE_NAME, - InstalledAppProvider.DataColumns.VERSION_CODE, - InstalledAppProvider.DataColumns.VERSION_NAME, - }; - } - - private ContentValues createContentValues(int versionCode, String versionNumber) { - return createContentValues(null, versionCode, versionNumber); - } - - private ContentValues createContentValues(String appId, int versionCode, String versionNumber) { - ContentValues values = new ContentValues(3); - if (appId != null) { - values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, appId); - } - values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId); - values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode); - values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber); - values.put(InstalledAppProvider.DataColumns.SIGNATURE, ""); - values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, System.currentTimeMillis()); - values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "sha256"); - values.put(InstalledAppProvider.DataColumns.HASH, "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe"); - return values; - } - - private void insertInstalledApp(String appId, int versionCode, String versionNumber) { - ContentValues values = createContentValues(appId, versionCode, versionNumber); - getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values); - } - - /** - * Will tell {@code pm} that we are installing {@code packageName}, and then update the - * "installed apps" table in the database. - */ - public static void install(MockContextSwappableComponents context, - MockInstallablePackageManager pm, String packageName, - int versionCode, String versionName) { - - context.setPackageManager(pm); - pm.install(packageName, versionCode, versionName); - PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); - InstalledAppProviderService.insertAppIntoDb(context, packageName, packageInfo); - } - -} diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index 20144be5f..ff521293e 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -372,6 +372,17 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh private static Preferences instance; + /** + * Should only be used for unit testing, whereby separate tests are required to invoke `setup()`. + * The reason we don't instead ask for the singleton to be lazily loaded in the {@link Preferences#get()} + * method is because that would require each call to that method to require a {@link Context}. + * While it is likely that most places asking for preferences have access to a {@link Context}, + * it is a minor convenience to be able to ask for preferences without. + */ + public static void clearSingletonForTesting() { + instance = null; + } + /** * Needs to be setup before anything else tries to access it. */ diff --git a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java index 683f39235..d72f83210 100644 --- a/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/RepoUpdater.java @@ -190,7 +190,9 @@ public class RepoUpdater { processXmlProgressListener, new URL(repo.address), (int) indexEntry.getSize()); // Process the index... - final SAXParser parser = SAXParserFactory.newInstance().newSAXParser(); + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + final SAXParser parser = factory.newSAXParser(); final XMLReader reader = parser.getXMLReader(); final RepoXMLHandler repoXMLHandler = new RepoXMLHandler(repo, createIndexReceiver()); reader.setContentHandler(repoXMLHandler); diff --git a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java index e1395f92d..70b43b739 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java +++ b/app/src/main/java/org/fdroid/fdroid/data/ApkProvider.java @@ -26,7 +26,7 @@ public class ApkProvider extends FDroidProvider { * we may want to add additional constraints, so we give our self some * room by saying only 450 apks can be queried at once. */ - protected static final int MAX_APKS_TO_QUERY = 450; + static final int MAX_APKS_TO_QUERY = 450; public static final class Helper { @@ -319,7 +319,7 @@ public class ApkProvider extends FDroidProvider { * this directly, think about using * {@link org.fdroid.fdroid.data.ApkProvider.Helper#knownApks(android.content.Context, java.util.List, String[])} */ - protected static Uri getContentUri(List apks) { + static Uri getContentUri(List apks) { return getContentUri().buildUpon() .appendPath(PATH_APKS) .appendPath(buildApkString(apks)) diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java index f1959596f..3c54f5519 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -156,7 +156,10 @@ public class InstalledAppProviderService extends IntentService { String packageName = intent.getData().getSchemeSpecificPart(); final String action = intent.getAction(); if (ACTION_INSERT.equals(action)) { - insertAppIntoDb(this, packageName, (PackageInfo) intent.getParcelableExtra(EXTRA_PACKAGE_INFO)); + PackageInfo packageInfo = intent.getParcelableExtra(EXTRA_PACKAGE_INFO); + String hashType = "sha256"; + String hash = Utils.getBinaryHash(new File(packageInfo.applicationInfo.publicSourceDir), hashType); + insertAppIntoDb(this, packageName, packageInfo, hashType, hash); } else if (ACTION_DELETE.equals(action)) { deleteAppFromDb(this, packageName); } @@ -164,7 +167,13 @@ public class InstalledAppProviderService extends IntentService { } } - static void insertAppIntoDb(Context context, String packageName, PackageInfo packageInfo) { + /** + * @param hash Although the has could be calculated within this function, it is helpful to inject + * the hash so as to be able to use this method during testing. Otherwise, the + * hashing method will try to hash a non-existent .apk file and try to insert NULL + * into the database when under test. + */ + static void insertAppIntoDb(Context context, String packageName, PackageInfo packageInfo, String hashType, String hash) { if (packageInfo == null) { try { packageInfo = context.getPackageManager().getPackageInfo(packageName, @@ -185,8 +194,6 @@ public class InstalledAppProviderService extends IntentService { contentValues.put(InstalledAppProvider.DataColumns.SIGNATURE, getPackageSig(packageInfo)); contentValues.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, packageInfo.lastUpdateTime); - String hashType = "sha256"; - String hash = Utils.getBinaryHash(new File(packageInfo.applicationInfo.publicSourceDir), hashType); contentValues.put(InstalledAppProvider.DataColumns.HASH_TYPE, hashType); contentValues.put(InstalledAppProvider.DataColumns.HASH, hash); diff --git a/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java new file mode 100644 index 000000000..d159c6018 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/AcceptableMultiRepoUpdaterTest.java @@ -0,0 +1,82 @@ + +package org.fdroid.fdroid; + +import android.util.Log; + +import org.fdroid.fdroid.RepoUpdater.UpdateException; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +@Config(constants = BuildConfig.class) +@RunWith(RobolectricGradleTestRunner.class) +public class AcceptableMultiRepoUpdaterTest extends MultiRepoUpdaterTest { + private static final String TAG = "AcceptableMultiRepoTest"; + + private void assertSomewhatAcceptable() { + Log.i(TAG, "Asserting at least one versions of each .apk is in index."); + List repos = RepoProvider.Helper.all(context); + assertEquals("Repos", 3, repos.size()); + + assertApp2048(); + assertAppAdaway(); + assertAppAdbWireless(); + assertAppIcsImport(); + } + + @Test + public void testAcceptableConflictingThenMainThenArchive() throws UpdateException { + assertEmpty(); + if (updateConflicting() && updateMain() && updateArchive()) { + assertSomewhatAcceptable(); + } + } + + @Test + public void testAcceptableConflictingThenArchiveThenMain() throws UpdateException { + assertEmpty(); + if (updateConflicting() && updateArchive() && updateMain()) { + assertSomewhatAcceptable(); + } + } + + @Test + public void testAcceptableArchiveThenMainThenConflicting() throws UpdateException { + assertEmpty(); + if (updateArchive() && updateMain() && updateConflicting()) { + assertSomewhatAcceptable(); + } + } + + @Test + public void testAcceptableArchiveThenConflictingThenMain() throws UpdateException { + assertEmpty(); + if (updateArchive() && updateConflicting() && updateMain()) { + assertSomewhatAcceptable(); + } + } + + @Test + public void testAcceptableMainThenArchiveThenConflicting() throws UpdateException { + assertEmpty(); + if (updateMain() && updateArchive() && updateConflicting()) { + assertSomewhatAcceptable(); + } + } + + @Test + public void testAcceptableMainThenConflictingThenArchive() throws UpdateException { + assertEmpty(); + if (updateMain() && updateConflicting() && updateArchive()) { + assertSomewhatAcceptable(); + } + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/Assert.java b/app/src/test/java/org/fdroid/fdroid/Assert.java new file mode 100644 index 000000000..1296e0f9c --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/Assert.java @@ -0,0 +1,225 @@ +package org.fdroid.fdroid; + +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import junit.framework.AssertionFailedError; + +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.data.InstalledAppProvider; +import org.robolectric.shadows.ShadowContentResolver; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +public class Assert { + + public static void assertContainsOnly(List actualList, T[] expectedArray) { + List expectedList = new ArrayList<>(expectedArray.length); + Collections.addAll(expectedList, expectedArray); + assertContainsOnly(actualList, expectedList); + } + + public static void assertContainsOnly(T[] actualArray, List expectedList) { + List actualList = new ArrayList<>(actualArray.length); + Collections.addAll(actualList, actualArray); + assertContainsOnly(actualList, expectedList); + } + + public static void assertContainsOnly(T[] actualArray, T[] expectedArray) { + List expectedList = new ArrayList<>(expectedArray.length); + Collections.addAll(expectedList, expectedArray); + assertContainsOnly(actualArray, expectedList); + } + + public static String listToString(List list) { + String string = "["; + for (int i = 0; i < list.size(); i++) { + if (i > 0) { + string += ", "; + } + string += "'" + list.get(i) + "'"; + } + string += "]"; + return string; + } + + public static void assertContainsOnly(List actualList, List expectedContains) { + if (actualList.size() != expectedContains.size()) { + String message = + "List sizes don't match.\n" + + "Expected: " + + listToString(expectedContains) + "\n" + + "Actual: " + + listToString(actualList); + throw new AssertionFailedError(message); + } + for (T required : expectedContains) { + boolean containsRequired = false; + for (T itemInList : actualList) { + if (required.equals(itemInList)) { + containsRequired = true; + break; + } + } + if (!containsRequired) { + String message = + "List doesn't contain \"" + required + "\".\n" + + "Expected: " + + listToString(expectedContains) + "\n" + + "Actual: " + + listToString(actualList); + throw new AssertionFailedError(message); + } + } + } + + public static void assertCantDelete(ShadowContentResolver resolver, Uri uri) { + try { + resolver.delete(uri, null, null); + fail(); + } catch (UnsupportedOperationException e) { + // Successful condition + } catch (Exception e) { + fail(); + } + } + + public static void assertCantUpdate(ShadowContentResolver resolver, Uri uri) { + try { + resolver.update(uri, new ContentValues(), null, null); + fail(); + } catch (UnsupportedOperationException e) { + // Successful condition + } catch (Exception e) { + fail(); + } + } + + public static void assertInvalidUri(ShadowContentResolver resolver, String uri) { + assertInvalidUri(resolver, Uri.parse(uri)); + } + + public static void assertValidUri(ShadowContentResolver resolver, String uri, String[] projection) { + assertValidUri(resolver, Uri.parse(uri), projection); + } + + public static void assertInvalidUri(ShadowContentResolver resolver, Uri uri) { + Cursor cursor = resolver.query(uri, new String[] {}, null, null, null); + assertNull(cursor); + } + + public static void assertValidUri(ShadowContentResolver resolver, Uri uri, String[] projection) { + Cursor cursor = resolver.query(uri, projection, null, null, null); + assertNotNull(cursor); + cursor.close(); + } + + public static void assertValidUri(ShadowContentResolver resolver, Uri actualUri, String expectedUri, String[] projection) { + assertValidUri(resolver, actualUri, projection); + assertEquals(expectedUri, actualUri.toString()); + } + + public static void assertResultCount(ShadowContentResolver resolver, int expectedCount, Uri uri) { + assertResultCount(resolver, expectedCount, uri, new String[] {}); + } + + public static void assertResultCount(ShadowContentResolver resolver, int expectedCount, Uri uri, String[] projection) { + Cursor cursor = resolver.query(uri, projection, null, null, null); + assertResultCount(expectedCount, cursor); + cursor.close(); + } + + public static void assertResultCount(int expectedCount, List items) { + assertNotNull(items); + assertEquals(expectedCount, items.size()); + } + + public static void assertResultCount(int expectedCount, Cursor result) { + assertNotNull(result); + assertEquals(expectedCount, result.getCount()); + } + + public static void assertIsInstalledVersionInDb(ShadowContentResolver resolver, String appId, int versionCode, String versionName) { + Uri uri = InstalledAppProvider.getAppUri(appId); + + String[] projection = { + InstalledAppProvider.DataColumns.PACKAGE_NAME, + InstalledAppProvider.DataColumns.VERSION_CODE, + InstalledAppProvider.DataColumns.VERSION_NAME, + InstalledAppProvider.DataColumns.APPLICATION_LABEL, + }; + + Cursor cursor = resolver.query(uri, projection, null, null, null); + + assertNotNull(cursor); + assertEquals("App \"" + appId + "\" not installed", 1, cursor.getCount()); + + cursor.moveToFirst(); + + assertEquals(appId, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME))); + assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE))); + assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME))); + cursor.close(); + } + + public static void insertApp(ShadowContentResolver resolver, String appId, String name) { + insertApp(resolver, appId, name, new ContentValues()); + } + + public static void insertApp(ShadowContentResolver resolver, String id, String name, ContentValues additionalValues) { + + ContentValues values = new ContentValues(); + values.put(AppProvider.DataColumns.PACKAGE_NAME, id); + values.put(AppProvider.DataColumns.NAME, name); + + // Required fields (NOT NULL in the database). + values.put(AppProvider.DataColumns.SUMMARY, "test summary"); + values.put(AppProvider.DataColumns.DESCRIPTION, "test description"); + values.put(AppProvider.DataColumns.LICENSE, "GPL?"); + values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1); + values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0); + values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0); + + values.putAll(additionalValues); + + Uri uri = AppProvider.getContentUri(); + + resolver.insert(uri, values); + } + + public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode) { + return insertApk(resolver, id, versionCode, new ContentValues()); + } + + public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode, ContentValues additionalValues) { + + ContentValues values = new ContentValues(); + + values.put(ApkProvider.DataColumns.PACKAGE_NAME, id); + values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode); + + // Required fields (NOT NULL in the database). + values.put(ApkProvider.DataColumns.REPO_ID, 1); + values.put(ApkProvider.DataColumns.VERSION_NAME, "The good one"); + values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa"); + values.put(ApkProvider.DataColumns.NAME, "Test Apk"); + values.put(ApkProvider.DataColumns.SIZE, 10000); + values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1); + + values.putAll(additionalValues); + + Uri uri = ApkProvider.getContentUri(); + + return resolver.insert(uri, values); + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java new file mode 100644 index 000000000..2da14d8eb --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/MultiRepoUpdaterTest.java @@ -0,0 +1,201 @@ + +package org.fdroid.fdroid; + +import android.content.ContentValues; +import android.content.Context; +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import org.fdroid.fdroid.RepoUpdater.UpdateException; +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.data.FDroidProviderTest; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; +import org.junit.After; +import org.junit.Before; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public abstract class MultiRepoUpdaterTest extends FDroidProviderTest { + @SuppressWarnings("unused") + private static final String TAG = "AcceptableMultiRepoUpdaterTest"; // NOPMD + + protected static final String REPO_MAIN = "Test F-Droid repo"; + protected static final String REPO_ARCHIVE = "Test F-Droid repo (Archive)"; + protected static final String REPO_CONFLICTING = "Test F-Droid repo with different apps"; + + protected RepoUpdater conflictingRepoUpdater; + protected RepoUpdater mainRepoUpdater; + protected RepoUpdater archiveRepoUpdater; + + private static final String PUB_KEY = + "3082050b308202f3a003020102020420d8f212300d06092a864886f70d01010b050030363110300e0603" + + "55040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e7365727779" + + "6c6f2e636f6d301e170d3135303931323233313632315a170d3433303132383233313632315a30363110" + + "300e060355040b1307462d44726f69643122302006035504031319657073696c6f6e2e70657465722e73" + + "657277796c6f2e636f6d30820222300d06092a864886f70d01010105000382020f003082020a02820201" + + "00b21fe72b84ce721967851364bd20511088d117bc3034e4bb4d3c1a06af2a308fdffdaf63b12e0926b9" + + "0545134b9ff570646cbcad89d9e86dcc8eb9977dd394240c75bccf5e8ddc3c5ef91b4f16eca5f36c36f1" + + "92463ff2c9257d3053b7c9ecdd1661bd01ec3fe70ee34a7e6b92ddba04f258a32d0cfb1b0ce85d047180" + + "97fc4bdfb54541b430dfcfc1c84458f9eb5627e0ec5341d561c3f15f228379a1282d241329198f31a7ac" + + "cd51ab2bbb881a1da55001123483512f77275f8990c872601198065b4e0137ddd1482e4fdefc73b857d4" + + "be324ca96c268ceb725398f8cc38a0dc6aa2c277f8686724e8c7ff3f320a05791fccacc6caa956cf23a9" + + "de2dc7070b262c0e35d90d17e90773bb11e875e79a8dfd958e359d5d5ad903a7cbc2955102502bd0134c" + + "a1ff7a0bbbbb57302e4a251e40724dcaa8ad024f4b3a71b8fceaac664c0dcc1995a1c4cf42676edad8bc" + + "b03ba255ab796677f18fff2298e1aaa5b134254b44d08a4d934c9859af7bbaf078c37b7f628db0e2cffb" + + "0493a669d5f4770d35d71284550ce06d6f6811cd2a31585085716257a4ba08ad968b0a2bf88f34ca2f2c" + + "73af1c042ab147597faccfb6516ef4468cfa0c5ab3c8120eaa7bac1080e4d2310f717db20815d0e1ee26" + + "bd4e47eed8d790892017ae9595365992efa1b7fd1bc1963f018264b2b3749b8f7b1907bb0843f1e7fc2d" + + "3f3b02284cd4bae0ab0203010001a321301f301d0603551d0e0416041456110e4fed863ab1df9448bfd9" + + "e10a8bc32ffe08300d06092a864886f70d01010b050003820201008082572ae930ebc55ecf1110f4bb72" + + "ad2a952c8ac6e65bd933706beb4a310e23deabb8ef6a7e93eea8217ab1f3f57b1f477f95f1d62eccb563" + + "67a4d70dfa6fcd2aace2bb00b90af39412a9441a9fae2396ff8b93de1df3d9837c599b1f80b7d75285cb" + + "df4539d7dd9612f54b45ca59bc3041c9b92fac12753fac154d12f31df360079ab69a2d20db9f6a7277a8" + + "259035e93de95e8cbc80351bc83dd24256183ea5e3e1db2a51ea314cdbc120c064b77e2eb3a731530511" + + "1e1dabed6996eb339b7cb948d05c1a84d63094b4a4c6d11389b2a7b5f2d7ecc9a149dda6c33705ef2249" + + "58afdfa1d98cf646dcf8857cd8342b1e07d62cb4313f35ad209046a4a42ff73f38cc740b1e695eeda49d" + + "5ea0384ad32f9e3ae54f6a48a558dbc7cccabd4e2b2286dc9c804c840bd02b9937841a0e48db00be9e3c" + + "d7120cf0f8648ce4ed63923f0352a2a7b3b97fc55ba67a7a218b8c0b3cda4a45861280a622e0a59cc9fb" + + "ca1117568126c581afa4408b0f5c50293c212c406b8ab8f50aad5ed0f038cfca580ef3aba7df25464d9e" + + "495ffb629922cfb511d45e6294c045041132452f1ed0f20ac3ab4792f610de1734e4c8b71d743c4b0101" + + "98f848e0dbfce5a0f2da0198c47e6935a47fda12c518ef45adfb66ddf5aebaab13948a66c004b8592d22" + + "e8af60597c4ae2977977cf61dc715a572e241ae717cafdb4f71781943945ac52e0f50b"; + + @Before + public final void setupMultiRepo() throws Exception { + // On a fresh database install, there will be F-Droid + GP repos, including their Archive + // repos that we are not interested in. + RepoProvider.Helper.remove(context, 1); + RepoProvider.Helper.remove(context, 2); + RepoProvider.Helper.remove(context, 3); + RepoProvider.Helper.remove(context, 4); + + conflictingRepoUpdater = createUpdater(REPO_CONFLICTING, context); + mainRepoUpdater = createUpdater(REPO_MAIN, context); + archiveRepoUpdater = createUpdater(REPO_ARCHIVE, context); + + Preferences.setup(context); + } + + @After + public final void tearDownMultiRepo() { + Preferences.clearSingletonForTesting(); + } + + protected void assertApp(String packageName, int[] versionCodes) { + List apks = ApkProvider.Helper.findByPackageName(context, packageName, ApkProvider.DataColumns.ALL); + assertApksExist(apks, packageName, versionCodes); + } + + protected void assertApp2048() { + assertApp("com.uberspot.a2048", new int[]{19, 18}); + } + + protected void assertAppAdaway() { + assertApp("org.adaway", new int[]{54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 42, 40, 38, 37, 36, 35}); + } + + protected void assertAppAdbWireless() { + assertApp("siir.es.adbWireless", new int[]{12}); + } + + protected void assertAppIcsImport() { + assertApp("org.dgtale.icsimport", new int[]{3, 2}); + } + + @NonNull + protected Repo findRepo(@NonNull String name, List allRepos) { + Repo repo = null; + for (Repo r : allRepos) { + if (TextUtils.equals(name, r.getName())) { + repo = r; + break; + } + } + + assertNotNull("Repo " + allRepos, repo); + return repo; + } + + /** + * Checks that each version of appId as specified in versionCodes is present in apksToCheck. + */ + protected void assertApksExist(List apksToCheck, String appId, int[] versionCodes) { + for (int versionCode : versionCodes) { + boolean found = false; + for (Apk apk : apksToCheck) { + if (apk.versionCode == versionCode && apk.packageName.equals(appId)) { + found = true; + break; + } + } + + assertTrue("Couldn't find app " + appId + ", v" + versionCode, found); + } + } + + protected void assertEmpty() { + assertEquals("No apps present", 0, AppProvider.Helper.all(context.getContentResolver()).size()); + + String[] packages = { + "com.uberspot.a2048", + "org.adaway", + "siir.es.adbWireless", + }; + + for (String id : packages) { + assertEquals("No apks for " + id, 0, ApkProvider.Helper.findByPackageName(context, id).size()); + } + } + + private RepoUpdater createUpdater(String name, Context context) { + Repo repo = new Repo(); + repo.signingCertificate = PUB_KEY; + repo.address = "https://fake.url/" + UUID.randomUUID().toString() + "/fdroid/repo"; + repo.name = name; + + ContentValues values = new ContentValues(2); + values.put(RepoProvider.DataColumns.SIGNING_CERT, repo.signingCertificate); + values.put(RepoProvider.DataColumns.ADDRESS, repo.address); + values.put(RepoProvider.DataColumns.NAME, repo.name); + + RepoProvider.Helper.insert(context, values); + + // Need to reload the repo based on address so that it includes the primary key from + // the database. + return new RepoUpdater(context, RepoProvider.Helper.findByAddress(context, repo.address)); + } + + protected boolean updateConflicting() throws UpdateException { + return updateRepo(conflictingRepoUpdater, "multiRepo.conflicting.jar"); + } + + protected boolean updateMain() throws UpdateException { + return updateRepo(mainRepoUpdater, "multiRepo.normal.jar"); + } + + protected boolean updateArchive() throws UpdateException { + return updateRepo(archiveRepoUpdater, "multiRepo.archive.jar"); + } + + private boolean updateRepo(RepoUpdater updater, String indexJarPath) throws UpdateException { + File indexJar = TestUtils.copyResourceToTempFile(indexJarPath); + try { + updater.processDownloadedFile(indexJar); + } finally { + if (indexJar != null && indexJar.exists()) { + assertTrue(indexJar.delete()); + } + } + return true; + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java b/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java new file mode 100644 index 000000000..b0e992854 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/ProperMultiRepoUpdaterTest.java @@ -0,0 +1,154 @@ + +package org.fdroid.fdroid; + +import android.util.Log; + +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.Repo; +import org.fdroid.fdroid.data.RepoProvider; + +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/* +At time fo writing, the following tests did not pass. This is because the multi-repo support +in F-Droid was not sufficient. When working on proper multi repo support than this should be +uncommented and all these tests will be required to pass: + +@Config(constants = BuildConfig.class) +@RunWith(RobolectricGradleTestRunner.class) +*/ +@SuppressWarnings("unused") +public class ProperMultiRepoUpdaterTest extends MultiRepoUpdaterTest { + private static final String TAG = "ProperMultiRepoSupport"; + + /*@Test + public void testCorrectConflictingThenMainThenArchive() throws UpdateException { + assertEmpty(); + if (updateConflicting() && updateMain() && updateArchive()) { + assertExpected(); + } + } + + @Test + public void testCorrectConflictingThenArchiveThenMain() throws UpdateException { + assertEmpty(); + if (updateConflicting() && updateArchive() && updateMain()) { + assertExpected(); + } + } + + @Test + public void testCorrectArchiveThenMainThenConflicting() throws UpdateException { + assertEmpty(); + if (updateArchive() && updateMain() && updateConflicting()) { + assertExpected(); + } + } + + @Test + public void testCorrectArchiveThenConflictingThenMain() throws UpdateException { + assertEmpty(); + if (updateArchive() && updateConflicting() && updateMain()) { + assertExpected(); + } + } + + @Test + public void testCorrectMainThenArchiveThenConflicting() throws UpdateException { + assertEmpty(); + if (updateMain() && updateArchive() && updateConflicting()) { + assertExpected(); + } + } + + @Test + public void testCorrectMainThenConflictingThenArchive() throws UpdateException { + assertEmpty(); + if (updateMain() && updateConflicting() && updateArchive()) { + assertExpected(); + } + }*/ + + /** + * Check that all of the expected apps and apk versions are available in the database. This + * check will take into account the repository the apks came from, to ensure that each + * repository indeed contains the apks that it said it would provide. + */ + private void assertExpected() { + Log.i(TAG, "Asserting all versions of each .apk are in index."); + List repos = RepoProvider.Helper.all(context); + assertEquals("Repos", 3, repos.size()); + + assertMainRepo(repos); + assertMainArchiveRepo(repos); + assertConflictingRepo(repos); + } + + /** + * + 2048 (com.uberspot.a2048) + * - Version 1.96 (19) + * - Version 1.95 (18) + * + AdAway (org.adaway) + * - Version 3.0.2 (54) + * - Version 3.0.1 (53) + * - Version 3.0 (52) + * + adbWireless (siir.es.adbWireless) + * - Version 1.5.4 (12) + */ + private void assertMainRepo(List allRepos) { + Repo repo = findRepo(REPO_MAIN, allRepos); + + List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL); + assertEquals("Apks for main repo", apks.size(), 6); + assertApksExist(apks, "com.uberspot.a2048", new int[]{18, 19}); + assertApksExist(apks, "org.adaway", new int[]{52, 53, 54}); + assertApksExist(apks, "siir.es.adbWireless", new int[]{12}); + } + + /** + * + AdAway (org.adaway) + * - Version 2.9.2 (51) + * - Version 2.9.1 (50) + * - Version 2.9 (49) + * - Version 2.8.1 (48) + * - Version 2.8 (47) + * - Version 2.7 (46) + * - Version 2.6 (45) + * - Version 2.3 (42) + * - Version 2.1 (40) + * - Version 1.37 (38) + * - Version 1.36 (37) + * - Version 1.35 (36) + * - Version 1.34 (35) + */ + private void assertMainArchiveRepo(List allRepos) { + Repo repo = findRepo(REPO_ARCHIVE, allRepos); + + List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL); + assertEquals("Apks for main archive repo", 13, apks.size()); + assertApksExist(apks, "org.adaway", new int[]{35, 36, 37, 38, 40, 42, 45, 46, 47, 48, 49, 50, 51}); + } + + /** + * + AdAway (org.adaway) + * - Version 3.0.1 (53) * + * - Version 3.0 (52) * + * - Version 2.9.2 (51) * + * - Version 2.2.1 (50) * + * + Add to calendar (org.dgtale.icsimport) + * - Version 1.2 (3) + * - Version 1.1 (2) + */ + private void assertConflictingRepo(List allRepos) { + Repo repo = findRepo(REPO_CONFLICTING, allRepos); + + List apks = ApkProvider.Helper.findByRepo(context, repo, ApkProvider.DataColumns.ALL); + assertEquals("Apks for main repo", 6, apks.size()); + assertApksExist(apks, "org.adaway", new int[]{50, 51, 52, 53}); + assertApksExist(apks, "org.dgtale.icsimport", new int[]{2, 3}); + } + +} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/RepoXMLHandlerTest.java b/app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java similarity index 99% rename from app/src/androidTest/java/org/fdroid/fdroid/RepoXMLHandlerTest.java rename to app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java index 8b2b7f6f1..e354320e8 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/RepoXMLHandlerTest.java +++ b/app/src/test/java/org/fdroid/fdroid/RepoXMLHandlerTest.java @@ -2,7 +2,6 @@ package org.fdroid.fdroid; import android.support.annotation.NonNull; -import android.support.test.runner.AndroidJUnit4; import android.text.TextUtils; import android.util.Log; @@ -12,6 +11,8 @@ import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.mock.MockRepo; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; @@ -31,7 +32,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; -@RunWith(AndroidJUnit4.class) +@Config(constants = BuildConfig.class) +@RunWith(RobolectricGradleTestRunner.class) public class RepoXMLHandlerTest { private static final String TAG = "RepoXMLHandlerTest"; @@ -673,16 +675,16 @@ public class RepoXMLHandlerTest { @NonNull private RepoDetails getFromFile(String indexFilename) { - SAXParser parser; try { - parser = SAXParserFactory.newInstance().newSAXParser(); + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); RepoDetails repoDetails = new RepoDetails(); RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(100), repoDetails); reader.setContentHandler(handler); - String resName = "assets/" + indexFilename; - Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName)); - InputStream input = getClass().getClassLoader().getResourceAsStream(resName); + Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(indexFilename)); + InputStream input = getClass().getClassLoader().getResourceAsStream(indexFilename); InputSource is = new InputSource(new BufferedInputStream(input)); reader.parse(is); return repoDetails; diff --git a/app/src/test/java/org/fdroid/fdroid/TestFDroidApp.java b/app/src/test/java/org/fdroid/fdroid/TestFDroidApp.java new file mode 100644 index 000000000..15bef648f --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/TestFDroidApp.java @@ -0,0 +1,13 @@ +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. + */ +public class TestFDroidApp extends Application { + +} diff --git a/app/src/test/java/org/fdroid/fdroid/TestUtils.java b/app/src/test/java/org/fdroid/fdroid/TestUtils.java new file mode 100644 index 000000000..2ced7a025 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/TestUtils.java @@ -0,0 +1,40 @@ +package org.fdroid.fdroid; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class TestUtils { + + @SuppressWarnings("unused") + private static final String TAG = "TestUtils"; // NOPMD + + 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; + } + +} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java b/app/src/test/java/org/fdroid/fdroid/UtilsTest.java similarity index 97% rename from app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java rename to app/src/test/java/org/fdroid/fdroid/UtilsTest.java index 96f701328..7ec649ca3 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/UtilsTest.java +++ b/app/src/test/java/org/fdroid/fdroid/UtilsTest.java @@ -1,14 +1,14 @@ package org.fdroid.fdroid; -import android.app.Instrumentation; import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; import org.apache.commons.io.FileUtils; import org.junit.Test; import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; import java.io.File; import java.io.IOException; @@ -17,7 +17,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -@RunWith(AndroidJUnit4.class) +@Config(constants = BuildConfig.class) +@RunWith(RobolectricGradleTestRunner.class) public class UtilsTest { String fdroidFingerprint = "43238D512C1E5EB2D6569F4A3AFBF5523418B82E0A3ED1552770ABB9A9C9CCAB"; @@ -50,7 +51,7 @@ public class UtilsTest { @Test public void testFormatFingerprint() { - Context context = InstrumentationRegistry.getTargetContext(); + Context context = RuntimeEnvironment.application; String badResult = Utils.formatFingerprint(context, ""); // real fingerprints String formatted; @@ -145,22 +146,29 @@ public class UtilsTest { @Test public void testClearOldFiles() throws IOException, InterruptedException { - Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); - File dir = new File(TestUtils.getWriteableDir(instrumentation), "clearOldFiles"); + File tempDir = new File(System.getProperty("java.io.tmpdir")); + assertTrue(tempDir.isDirectory()); + assertTrue(tempDir.canWrite()); + + File dir = new File(tempDir, "F-Droid-test.clearOldFiles"); FileUtils.deleteQuietly(dir); - dir.mkdirs(); + assertTrue(dir.mkdirs()); assertTrue(dir.isDirectory()); File first = new File(dir, "first"); + first.deleteOnExit(); + File second = new File(dir, "second"); + second.deleteOnExit(); + assertFalse(first.exists()); assertFalse(second.exists()); - first.createNewFile(); + assertTrue(first.createNewFile()); assertTrue(first.exists()); Thread.sleep(7000); - second.createNewFile(); + assertTrue(second.createNewFile()); assertTrue(second.exists()); Utils.clearOldFiles(dir, 3); diff --git a/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java new file mode 100644 index 000000000..03e7ebfa0 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/ApkProviderTest.java @@ -0,0 +1,541 @@ +package org.fdroid.fdroid.data; + +import android.app.Application; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import org.fdroid.fdroid.Assert; +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.mock.MockApk; +import org.fdroid.fdroid.mock.MockApp; +import org.fdroid.fdroid.mock.MockRepo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static org.fdroid.fdroid.Assert.assertCantDelete; +import static org.fdroid.fdroid.Assert.assertContainsOnly; +import static org.fdroid.fdroid.Assert.assertResultCount; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +@Config(constants = BuildConfig.class, application = Application.class) +@RunWith(RobolectricGradleTestRunner.class) +public class ApkProviderTest extends FDroidProviderTest { + + private static final String[] PROJ = ApkProvider.DataColumns.ALL; + + @Test + public void testAppApks() { + for (int i = 1; i <= 10; i++) { + Assert.insertApk(contentResolver, "org.fdroid.fdroid", i); + Assert.insertApk(contentResolver, "com.example", i); + } + + assertTotalApkCount(20); + + Cursor fdroidApks = contentResolver.query( + ApkProvider.getAppUri("org.fdroid.fdroid"), + PROJ, + null, null, null); + assertResultCount(10, fdroidApks); + assertBelongsToApp(fdroidApks, "org.fdroid.fdroid"); + fdroidApks.close(); + + Cursor exampleApks = contentResolver.query( + ApkProvider.getAppUri("com.example"), + PROJ, + null, null, null); + assertResultCount(10, exampleApks); + assertBelongsToApp(exampleApks, "com.example"); + exampleApks.close(); + + ApkProvider.Helper.deleteApksByApp(context, new MockApp("com.example")); + + Cursor all = queryAllApks(); + assertResultCount(10, all); + assertBelongsToApp(all, "org.fdroid.fdroid"); + all.close(); + } + + @Test + public void testDeleteArbitraryApks() { + Apk one = insertApkForRepo("com.example.one", 1, 10); + Apk two = insertApkForRepo("com.example.two", 1, 10); + Apk three = insertApkForRepo("com.example.three", 1, 10); + Apk four = insertApkForRepo("com.example.four", 1, 10); + Apk five = insertApkForRepo("com.example.five", 1, 10); + + assertTotalApkCount(5); + + assertEquals("com.example.one", one.packageName); + assertEquals("com.example.two", two.packageName); + assertEquals("com.example.five", five.packageName); + + String[] expectedIds = { + "com.example.one", + "com.example.two", + "com.example.three", + "com.example.four", + "com.example.five", + }; + + List all = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL); + List actualIds = new ArrayList<>(); + for (Apk apk : all) { + actualIds.add(apk.packageName); + } + + assertContainsOnly(actualIds, expectedIds); + + List toDelete = new ArrayList<>(3); + toDelete.add(two); + toDelete.add(three); + toDelete.add(four); + ApkProvider.Helper.deleteApks(context, toDelete); + + assertTotalApkCount(2); + + List allRemaining = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL); + List actualRemainingIds = new ArrayList<>(); + for (Apk apk : allRemaining) { + actualRemainingIds.add(apk.packageName); + } + + String[] expectedRemainingIds = { + "com.example.one", + "com.example.five", + }; + + assertContainsOnly(actualRemainingIds, expectedRemainingIds); + } + + @Test + public void testInvalidDeleteUris() { + Apk apk = new MockApk("org.fdroid.fdroid", 10); + + assertCantDelete(contentResolver, ApkProvider.getContentUri()); + assertCantDelete(contentResolver, ApkProvider.getContentUri("org.fdroid.fdroid", 10)); + assertCantDelete(contentResolver, ApkProvider.getContentUri(apk)); + assertCantDelete(contentResolver, Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path")); + } + + private static final long REPO_KEEP = 1; + private static final long REPO_DELETE = 2; + + @Test + public void testRepoApks() { + + // Insert apks into two repos, one of which we will later purge the + // the apks from. + for (int i = 1; i <= 5; i++) { + insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP); + insertApkForRepo("com.example." + i, 1, REPO_DELETE); + } + for (int i = 6; i <= 10; i++) { + insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE); + insertApkForRepo("com.example." + i, 1, REPO_KEEP); + } + + assertTotalApkCount(20); + + Cursor cursor = contentResolver.query( + ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null); + assertResultCount(10, cursor); + assertBelongsToRepo(cursor, REPO_DELETE); + cursor.close(); + + int count = ApkProvider.Helper.deleteApksByRepo(context, new MockRepo(REPO_DELETE)); + assertEquals(10, count); + + assertTotalApkCount(10); + cursor = contentResolver.query( + ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null); + assertResultCount(0, cursor); + cursor.close(); + + // The only remaining apks should be those from REPO_KEEP. + assertBelongsToRepo(queryAllApks(), REPO_KEEP); + } + + @Test + public void testQuery() { + Cursor cursor = queryAllApks(); + assertNotNull(cursor); + cursor.close(); + } + + @Test + public void testInsert() { + + // Start with an empty database... + Cursor cursor = queryAllApks(); + assertNotNull(cursor); + assertEquals(0, cursor.getCount()); + cursor.close(); + + Apk apk = new MockApk("org.fdroid.fdroid", 13); + + // Insert a new record... + Uri newUri = Assert.insertApk(contentResolver, apk.packageName, apk.versionCode); + assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString()); + cursor = queryAllApks(); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + + // And now we should be able to recover these values from the apk + // value object (because the queryAllApks() helper asks for VERSION_CODE and + // PACKAGE_NAME. + cursor.moveToFirst(); + Apk toCheck = new Apk(cursor); + cursor.close(); + assertEquals("org.fdroid.fdroid", toCheck.packageName); + assertEquals(13, toCheck.versionCode); + } + + @Test(expected = IllegalArgumentException.class) + public void testCursorMustMoveToFirst() { + Assert.insertApk(contentResolver, "org.example.test", 12); + Cursor cursor = queryAllApks(); + new Apk(cursor); + } + + @Test + public void testCount() { + String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT}; + + for (int i = 0; i < 13; i++) { + Assert.insertApk(contentResolver, "com.example", i); + } + + Uri all = ApkProvider.getContentUri(); + Cursor allWithFields = contentResolver.query(all, PROJ, null, null, null); + Cursor allWithCount = contentResolver.query(all, projectionCount, null, null, null); + + assertResultCount(13, allWithFields); + allWithFields.close(); + assertResultCount(1, allWithCount); + + allWithCount.moveToFirst(); + int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT); + assertEquals(13, allWithCount.getInt(countColumn)); + allWithCount.close(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInsertWithInvalidExtraFieldDescription() { + assertInvalidExtraField(RepoProvider.DataColumns.DESCRIPTION); + } + + @Test(expected = IllegalArgumentException.class) + public void testInsertWithInvalidExtraFieldAddress() { + assertInvalidExtraField(RepoProvider.DataColumns.ADDRESS); + } + + @Test(expected = IllegalArgumentException.class) + public void testInsertWithInvalidExtraFieldFingerprint() { + assertInvalidExtraField(RepoProvider.DataColumns.FINGERPRINT); + } + + @Test(expected = IllegalArgumentException.class) + public void testInsertWithInvalidExtraFieldName() { + assertInvalidExtraField(RepoProvider.DataColumns.NAME); + } + + @Test(expected = IllegalArgumentException.class) + public void testInsertWithInvalidExtraFieldSigningCert() { + assertInvalidExtraField(RepoProvider.DataColumns.SIGNING_CERT); + } + + public void assertInvalidExtraField(String field) { + ContentValues invalidRepo = new ContentValues(); + invalidRepo.put(field, "Test data"); + Assert.insertApk(contentResolver, "org.fdroid.fdroid", 10, invalidRepo); + } + + @Test + public void testInsertWithValidExtraFields() { + + assertResultCount(0, queryAllApks()); + + ContentValues values = new ContentValues(); + values.put(ApkProvider.DataColumns.REPO_ID, 10); + values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com"); + values.put(ApkProvider.DataColumns.REPO_VERSION, 3); + values.put(ApkProvider.DataColumns.FEATURES, "Some features"); + Uri uri = Assert.insertApk(contentResolver, "com.example.com", 1, values); + + assertResultCount(1, queryAllApks()); + + String[] projections = ApkProvider.DataColumns.ALL; + Cursor cursor = contentResolver.query(uri, projections, null, null, null); + cursor.moveToFirst(); + Apk apk = new Apk(cursor); + cursor.close(); + + // These should have quietly been dropped when we tried to save them, + // because the provider only knows how to query them (not update them). + assertEquals(null, apk.repoAddress); + assertEquals(0, apk.repoVersion); + + // But this should have saved correctly... + assertEquals("Some features", apk.features.toString()); + assertEquals("com.example.com", apk.packageName); + assertEquals(1, apk.versionCode); + assertEquals(10, apk.repo); + } + + @Test + public void testKnownApks() { + + for (int i = 0; i < 7; i++) { + Assert.insertApk(contentResolver, "org.fdroid.fdroid", i); + } + + for (int i = 0; i < 9; i++) { + Assert.insertApk(contentResolver, "org.example", i); + } + + for (int i = 0; i < 3; i++) { + Assert.insertApk(contentResolver, "com.example", i); + } + + Assert.insertApk(contentResolver, "com.apk.thingo", 1); + + Apk[] known = { + new MockApk("org.fdroid.fdroid", 1), + new MockApk("org.fdroid.fdroid", 3), + new MockApk("org.fdroid.fdroid", 5), + + new MockApk("com.example", 1), + new MockApk("com.example", 2), + }; + + Apk[] unknown = { + new MockApk("org.fdroid.fdroid", 7), + new MockApk("org.fdroid.fdroid", 9), + new MockApk("org.fdroid.fdroid", 11), + new MockApk("org.fdroid.fdroid", 13), + + new MockApk("com.example", 3), + new MockApk("com.example", 4), + new MockApk("com.example", 5), + + new MockApk("info.example", 1), + new MockApk("info.example", 2), + }; + + List apksToCheck = new ArrayList<>(known.length + unknown.length); + Collections.addAll(apksToCheck, known); + Collections.addAll(apksToCheck, unknown); + + String[] projection = { + ApkProvider.DataColumns.PACKAGE_NAME, + ApkProvider.DataColumns.VERSION_CODE, + }; + + List knownApks = ApkProvider.Helper.knownApks(context, apksToCheck, projection); + + assertResultCount(known.length, knownApks); + + for (Apk knownApk : knownApks) { + assertContains(knownApks, knownApk); + } + } + + @Test + public void testFindByApp() { + + for (int i = 0; i < 7; i++) { + Assert.insertApk(contentResolver, "org.fdroid.fdroid", i); + } + + for (int i = 0; i < 9; i++) { + Assert.insertApk(contentResolver, "org.example", i); + } + + for (int i = 0; i < 3; i++) { + Assert.insertApk(contentResolver, "com.example", i); + } + + Assert.insertApk(contentResolver, "com.apk.thingo", 1); + + assertTotalApkCount(7 + 9 + 3 + 1); + + List fdroidApks = ApkProvider.Helper.findByPackageName(context, "org.fdroid.fdroid"); + assertResultCount(7, fdroidApks); + assertBelongsToApp(fdroidApks, "org.fdroid.fdroid"); + + List exampleApks = ApkProvider.Helper.findByPackageName(context, "org.example"); + assertResultCount(9, exampleApks); + assertBelongsToApp(exampleApks, "org.example"); + + List exampleApks2 = ApkProvider.Helper.findByPackageName(context, "com.example"); + assertResultCount(3, exampleApks2); + assertBelongsToApp(exampleApks2, "com.example"); + + List thingoApks = ApkProvider.Helper.findByPackageName(context, "com.apk.thingo"); + assertResultCount(1, thingoApks); + assertBelongsToApp(thingoApks, "com.apk.thingo"); + } + + @Test + public void testUpdate() { + + Uri apkUri = Assert.insertApk(contentResolver, "com.example", 10); + + String[] allFields = ApkProvider.DataColumns.ALL; + Cursor cursor = contentResolver.query(apkUri, allFields, null, null, null); + assertResultCount(1, cursor); + + cursor.moveToFirst(); + Apk apk = new Apk(cursor); + cursor.close(); + + assertEquals("com.example", apk.packageName); + assertEquals(10, apk.versionCode); + + assertNull(apk.features); + assertNull(apk.added); + assertNull(apk.hashType); + + apk.features = Utils.CommaSeparatedList.make("one,two,three"); + long dateTimestamp = System.currentTimeMillis(); + apk.added = new Date(dateTimestamp); + apk.hashType = "i'm a hash type"; + + ApkProvider.Helper.update(context, apk); + + // Should not have inserted anything else, just updated the already existing apk. + Cursor allCursor = contentResolver.query(ApkProvider.getContentUri(), allFields, null, null, null); + assertResultCount(1, allCursor); + allCursor.close(); + + Cursor updatedCursor = contentResolver.query(apkUri, allFields, null, null, null); + assertResultCount(1, updatedCursor); + + updatedCursor.moveToFirst(); + Apk updatedApk = new Apk(updatedCursor); + updatedCursor.close(); + + assertEquals("com.example", updatedApk.packageName); + assertEquals(10, updatedApk.versionCode); + + assertNotNull(updatedApk.features); + assertNotNull(updatedApk.added); + assertNotNull(updatedApk.hashType); + + assertEquals("one,two,three", updatedApk.features.toString()); + assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear()); + assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth()); + assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay()); + assertEquals("i'm a hash type", updatedApk.hashType); + } + + @Test + public void testFind() { + // Insert some random apks either side of the "com.example", so that + // the Helper.find() method doesn't stumble upon the app we are interested + // in by shear dumb luck... + for (int i = 0; i < 10; i++) { + Assert.insertApk(contentResolver, "org.fdroid.apk." + i, i); + } + + ContentValues values = new ContentValues(); + values.put(ApkProvider.DataColumns.VERSION_NAME, "v1.1"); + values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy"); + values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type"); + Assert.insertApk(contentResolver, "com.example", 11, values); + + // ...and a few more for good measure... + for (int i = 15; i < 20; i++) { + Assert.insertApk(contentResolver, "com.other.thing." + i, i); + } + + Apk apk = ApkProvider.Helper.find(context, "com.example", 11); + + assertNotNull(apk); + + // The find() method populates ALL fields if you don't specify any, + // so we expect to find each of the ones we inserted above... + assertEquals("com.example", apk.packageName); + assertEquals(11, apk.versionCode); + assertEquals("v1.1", apk.versionName); + assertEquals("xxxxyyyy", apk.hash); + assertEquals("a hash type", apk.hashType); + + String[] projection = { + ApkProvider.DataColumns.PACKAGE_NAME, + ApkProvider.DataColumns.HASH, + }; + + Apk apkLessFields = ApkProvider.Helper.find(context, "com.example", 11, projection); + + assertNotNull(apkLessFields); + + assertEquals("com.example", apkLessFields.packageName); + assertEquals("xxxxyyyy", apkLessFields.hash); + + // Didn't ask for these fields, so should be their default values... + assertNull(apkLessFields.hashType); + assertNull(apkLessFields.versionName); + assertEquals(0, apkLessFields.versionCode); + + Apk notFound = ApkProvider.Helper.find(context, "com.doesnt.exist", 1000); + assertNull(notFound); + } + + protected final Cursor queryAllApks() { + return contentResolver.query(ApkProvider.getContentUri(), PROJ, null, null, null); + } + + protected void assertContains(List apks, Apk apk) { + boolean found = false; + for (Apk a : apks) { + if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) { + found = true; + break; + } + } + if (!found) { + fail("Apk [" + apk + "] not found in " + Assert.listToString(apks)); + } + } + + protected void assertBelongsToApp(Cursor apks, String appId) { + assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId); + } + + protected void assertBelongsToApp(List apks, String appId) { + for (Apk apk : apks) { + assertEquals(appId, apk.packageName); + } + } + + protected void assertTotalApkCount(int expected) { + assertResultCount(expected, queryAllApks()); + } + + protected void assertBelongsToRepo(Cursor apkCursor, long repoId) { + for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) { + assertEquals(repoId, apk.repo); + } + } + + protected Apk insertApkForRepo(String id, int versionCode, long repoId) { + ContentValues additionalValues = new ContentValues(); + additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId); + Uri uri = Assert.insertApk(contentResolver, id, versionCode, additionalValues); + return ApkProvider.Helper.get(context, uri); + } +} diff --git a/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java new file mode 100644 index 000000000..9178eb2c3 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -0,0 +1,369 @@ +package org.fdroid.fdroid.data; + +import android.app.Application; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.R; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContentResolver; + +import java.util.ArrayList; +import java.util.List; + +import static org.fdroid.fdroid.Assert.assertContainsOnly; +import static org.fdroid.fdroid.Assert.assertResultCount; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +@Config(constants = BuildConfig.class, application = Application.class) +@RunWith(RobolectricGradleTestRunner.class) +public class AppProviderTest extends FDroidProviderTest { + + private static final String[] PROJ = AppProvider.DataColumns.ALL; + + @Before + public void setup() { + ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider()); + } + + /** + * Although this doesn't directly relate to the {@link AppProvider}, it is here because + * the {@link AppProvider} used to stumble across this bug when asking for installed apps, + * and the device had over 1000 apps installed. + */ + @Test + public void testMaxSqliteParams() { + insertApp("com.example.app1", "App 1"); + insertApp("com.example.app100", "App 100"); + insertApp("com.example.app1000", "App 1000"); + + for (int i = 0; i < 50; i++) { + InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1"); + } + assertResultCount(contentResolver, 1, AppProvider.getInstalledUri(), PROJ); + + for (int i = 50; i < 500; i++) { + InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1"); + } + assertResultCount(contentResolver, 2, AppProvider.getInstalledUri(), PROJ); + + for (int i = 500; i < 1100; i++) { + InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1"); + } + assertResultCount(contentResolver, 3, AppProvider.getInstalledUri(), PROJ); + } + + @Test + public void testCantFindApp() { + assertNull(AppProvider.Helper.findByPackageName(context.getContentResolver(), "com.example.doesnt-exist")); + } + + @Test + public void testQuery() { + Cursor cursor = queryAllApps(); + assertNotNull(cursor); + cursor.close(); + } + + private void insertApps(int count) { + for (int i = 0; i < count; i++) { + insertApp("com.example.test." + i, "Test app " + i); + } + } + + private void insertAndInstallApp( + String packageName, int installedVercode, int suggestedVercode, + boolean ignoreAll, int ignoreVercode) { + ContentValues values = new ContentValues(3); + values.put(AppProvider.DataColumns.SUGGESTED_VERSION_CODE, suggestedVercode); + values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, ignoreAll); + values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode); + insertApp(packageName, "App: " + packageName, values); + + InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode); + } + + @Test + public void testCanUpdate() { + insertApp("not installed", "not installed"); + insertAndInstallApp("installed, only one version available", 1, 1, false, 0); + insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0); + insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0); + insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10); + insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5); + insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0); + insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0); + insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10); + insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8); + + ContentResolver r = context.getContentResolver(); + + // Can't "update", although can "install"... + App notInstalled = AppProvider.Helper.findByPackageName(r, "not installed"); + assertFalse(notInstalled.canAndWantToUpdate()); + + App installedOnlyOneVersionAvailable = AppProvider.Helper.findByPackageName(r, "installed, only one version available"); + App installedAlreadyLatestNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, already latest, no ignore"); + App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore all"); + App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore latest"); + App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findByPackageName(r, "installed, already latest, ignore old"); + + assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate()); + assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate()); + assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate()); + assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate()); + assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate()); + + App installedOldNoIgnore = AppProvider.Helper.findByPackageName(r, "installed, old version, no ignore"); + App installedOldIgnoreAll = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore all"); + App installedOldIgnoreLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore latest"); + App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findByPackageName(r, "installed, old version, ignore newer, but not latest"); + + assertTrue(installedOldNoIgnore.canAndWantToUpdate()); + assertFalse(installedOldIgnoreAll.canAndWantToUpdate()); + assertFalse(installedOldIgnoreLatest.canAndWantToUpdate()); + assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate()); + + Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), AppProvider.DataColumns.ALL, null, null, null); + assertNotNull(canUpdateCursor); + canUpdateCursor.moveToFirst(); + List canUpdateIds = new ArrayList<>(canUpdateCursor.getCount()); + while (!canUpdateCursor.isAfterLast()) { + canUpdateIds.add(new App(canUpdateCursor).packageName); + canUpdateCursor.moveToNext(); + } + canUpdateCursor.close(); + + String[] expectedUpdateableIds = { + "installed, old version, no ignore", + "installed, old version, ignore newer, but not latest", + }; + + assertContainsOnly(expectedUpdateableIds, canUpdateIds); + } + + @Test + public void testIgnored() { + insertApp("not installed", "not installed"); + insertAndInstallApp("installed, only one version available", 1, 1, false, 0); + insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0); + insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0); + insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10); + insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5); + insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0); + insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0); + insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10); + insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8); + + assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ); + + String[] projection = {AppProvider.DataColumns.PACKAGE_NAME}; + List ignoredApps = AppProvider.Helper.findIgnored(context, projection); + + String[] expectedIgnored = { + "installed, already latest, ignore all", + "installed, already latest, ignore latest", + // NOT "installed, already latest, ignore old" - because it + // is should only ignore if "ignored version" is >= suggested + + "installed, old version, ignore all", + "installed, old version, ignore latest", + // NOT "installed, old version, ignore newer, but not latest" + // for the same reason as above. + }; + + assertContainsOnlyIds(ignoredApps, expectedIgnored); + } + + private void assertContainsOnlyIds(List actualApps, String[] expectedIds) { + List actualIds = new ArrayList<>(actualApps.size()); + for (App app : actualApps) { + actualIds.add(app.packageName); + } + assertContainsOnly(actualIds, expectedIds); + } + + @Test + public void testInstalled() { + insertApps(100); + + assertResultCount(contentResolver, 100, AppProvider.getContentUri(), PROJ); + assertResultCount(contentResolver, 0, AppProvider.getInstalledUri(), PROJ); + + for (int i = 10; i < 20; i++) { + InstalledAppTestUtils.install(context, "com.example.test." + i, i, "v1"); + } + + assertResultCount(contentResolver, 10, AppProvider.getInstalledUri(), PROJ); + } + + @Test + public void testInsert() { + + // Start with an empty database... + Cursor cursor = queryAllApps(); + assertNotNull(cursor); + assertEquals(0, cursor.getCount()); + cursor.close(); + + // Insert a new record... + insertApp("org.fdroid.fdroid", "F-Droid"); + cursor = queryAllApps(); + assertNotNull(cursor); + assertEquals(1, cursor.getCount()); + + // And now we should be able to recover these values from the app + // value object (because the queryAllApps() helper asks for NAME and + // PACKAGE_NAME. + cursor.moveToFirst(); + App app = new App(cursor); + cursor.close(); + assertEquals("org.fdroid.fdroid", app.packageName); + assertEquals("F-Droid", app.name); + + App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid"); + assertNotNull(otherApp); + assertEquals("org.fdroid.fdroid", otherApp.packageName); + assertEquals("F-Droid", otherApp.name); + } + + /** + * We intentionally throw an IllegalArgumentException if you haven't + * yet called cursor.move*(). + */ + @Test(expected = IllegalArgumentException.class) + public void testCursorMustMoveToFirst() { + insertApp("org.fdroid.fdroid", "F-Droid"); + Cursor cursor = queryAllApps(); + new App(cursor); + } + + private Cursor queryAllApps() { + String[] projection = new String[] { + AppProvider.DataColumns._ID, + AppProvider.DataColumns.NAME, + AppProvider.DataColumns.PACKAGE_NAME, + }; + return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null); + } + + + // ======================================================================== + // "Categories" + // (at this point) not an additional table, but we treat them sort of + // like they are. That means that if we change the implementation to + // use a separate table in the future, these should still pass. + // ======================================================================== + + @Test + public void testCategoriesSingle() { + insertAppWithCategory("com.dog", "Dog", "Animal"); + insertAppWithCategory("com.rock", "Rock", "Mineral"); + insertAppWithCategory("com.banana", "Banana", "Vegetable"); + + List categories = AppProvider.Helper.categories(context); + String[] expected = new String[] { + context.getResources().getString(R.string.category_Whats_New), + context.getResources().getString(R.string.category_Recently_Updated), + context.getResources().getString(R.string.category_All), + "Animal", + "Mineral", + "Vegetable", + }; + assertContainsOnly(categories, expected); + } + + @Test + public void testCategoriesMultiple() { + insertAppWithCategory("com.rock.dog", "Rock-Dog", "Mineral,Animal"); + insertAppWithCategory("com.dog.rock.apple", "Dog-Rock-Apple", "Animal,Mineral,Vegetable"); + insertAppWithCategory("com.banana.apple", "Banana", "Vegetable,Vegetable"); + + List categories = AppProvider.Helper.categories(context); + String[] expected = new String[] { + context.getResources().getString(R.string.category_Whats_New), + context.getResources().getString(R.string.category_Recently_Updated), + context.getResources().getString(R.string.category_All), + + "Animal", + "Mineral", + "Vegetable", + }; + assertContainsOnly(categories, expected); + + insertAppWithCategory("com.example.game", "Game", + "Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," + + "The quick brown fox jumps over the lazy dog,With apostrophe's"); + + List categoriesLonger = AppProvider.Helper.categories(context); + String[] expectedLonger = new String[] { + context.getResources().getString(R.string.category_Whats_New), + context.getResources().getString(R.string.category_Recently_Updated), + context.getResources().getString(R.string.category_All), + + "Animal", + "Mineral", + "Vegetable", + + "Running", + "Shooting", + "Jumping", + "Bleh", + "Sneh", + "Pleh", + "Blah", + "Test category", + "The quick brown fox jumps over the lazy dog", + "With apostrophe's", + }; + + assertContainsOnly(categoriesLonger, expectedLonger); + } + + // ======================================================================= + // Misc helper functions + // (to be used by any tests in this suite) + // ======================================================================= + + private void insertApp(String id, String name) { + insertApp(id, name, new ContentValues()); + } + + private void insertAppWithCategory(String id, String name, String categories) { + ContentValues values = new ContentValues(1); + values.put(AppProvider.DataColumns.CATEGORIES, categories); + insertApp(id, name, values); + } + + public void insertApp(String id, String name, ContentValues additionalValues) { + + ContentValues values = new ContentValues(); + values.put(AppProvider.DataColumns.PACKAGE_NAME, id); + values.put(AppProvider.DataColumns.NAME, name); + + // Required fields (NOT NULL in the database). + values.put(AppProvider.DataColumns.SUMMARY, "test summary"); + values.put(AppProvider.DataColumns.DESCRIPTION, "test description"); + values.put(AppProvider.DataColumns.LICENSE, "GPL?"); + values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1); + values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0); + values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0); + + values.putAll(additionalValues); + + Uri uri = AppProvider.getContentUri(); + + contentResolver.insert(uri, values); + } +} diff --git a/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java new file mode 100644 index 000000000..adddf695c --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java @@ -0,0 +1,38 @@ +package org.fdroid.fdroid.data; + +import android.content.ContentResolver; +import android.content.ContextWrapper; + +import org.junit.After; +import org.junit.Before; +import org.mockito.AdditionalAnswers; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.shadows.ShadowContentResolver; + +import static org.mockito.Mockito.mock; + +public abstract class FDroidProviderTest { + + protected ShadowContentResolver contentResolver; + protected ContextWrapper context; + + @Before + public final void setupBase() { + contentResolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver()); + final ContentResolver resolver = mock(ContentResolver.class, AdditionalAnswers.delegatesTo(contentResolver)); + context = new ContextWrapper(RuntimeEnvironment.application.getApplicationContext()) { + @Override + public ContentResolver getContentResolver() { + return resolver; + } + }; + ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider()); + } + + @After + public final void tearDownBase() { + FDroidProvider.clearDbHelperSingleton(); + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java new file mode 100644 index 000000000..e207f1a16 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java @@ -0,0 +1,189 @@ +package org.fdroid.fdroid.data; + +import android.app.Application; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import org.fdroid.fdroid.BuildConfig; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContentResolver; + +import static org.fdroid.fdroid.Assert.assertIsInstalledVersionInDb; +import static org.fdroid.fdroid.Assert.assertResultCount; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +@Config(constants = BuildConfig.class, application = Application.class) +@RunWith(RobolectricGradleTestRunner.class) +public class InstalledAppProviderTest extends FDroidProviderTest { + + @Before + public void setup() { + ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider()); + } + + @Test + public void insertSingleApp() { + Map foundBefore = InstalledAppProvider.Helper.all(RuntimeEnvironment.application); + assertEquals(foundBefore.size(), 0); + + ContentValues values = new ContentValues(); + values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, "org.example.test-app"); + values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Test App"); + values.put(InstalledAppProvider.DataColumns.VERSION_CODE, 1021); + values.put(InstalledAppProvider.DataColumns.VERSION_NAME, "Longhorn"); + values.put(InstalledAppProvider.DataColumns.HASH, "has of test app"); + values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "fake hash type"); + values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, 100000000L); + values.put(InstalledAppProvider.DataColumns.SIGNATURE, "000111222333444555666777888999aaabbbcccdddeeefff"); + contentResolver.insert(InstalledAppProvider.getContentUri(), values); + + Map foundAfter = InstalledAppProvider.Helper.all(RuntimeEnvironment.application); + assertEquals(1, foundAfter.size()); + assertEquals(100000000L, foundAfter.get("org.example.test-app").longValue()); + + Cursor cursor = contentResolver.query(InstalledAppProvider.getAppUri("org.example.test-app"), InstalledAppProvider.DataColumns.ALL, null, null, null); + assertEquals(cursor.getCount(), 1); + + cursor.moveToFirst(); + assertEquals("org.example.test-app", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME))); + assertEquals("Test App", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APPLICATION_LABEL))); + assertEquals(1021, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE))); + assertEquals("Longhorn", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME))); + assertEquals("has of test app", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.HASH))); + assertEquals("fake hash type", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.HASH_TYPE))); + assertEquals(100000000L, cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME))); + assertEquals("000111222333444555666777888999aaabbbcccdddeeefff", cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.SIGNATURE))); + + cursor.close(); + } + + @Test + public void testInsert() { + + assertResultCount(contentResolver, 0, InstalledAppProvider.getContentUri()); + + insertInstalledApp("com.example.com1", 1, "v1"); + insertInstalledApp("com.example.com2", 2, "v2"); + insertInstalledApp("com.example.com3", 3, "v3"); + + assertResultCount(contentResolver, 3, InstalledAppProvider.getContentUri()); + assertIsInstalledVersionInDb(contentResolver, "com.example.com1", 1, "v1"); + assertIsInstalledVersionInDb(contentResolver, "com.example.com2", 2, "v2"); + assertIsInstalledVersionInDb(contentResolver, "com.example.com3", 3, "v3"); + } + + @Test + public void testUpdate() { + insertInstalledApp("com.example.app1", 10, "1.0"); + insertInstalledApp("com.example.app2", 10, "1.0"); + + assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri()); + assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 10, "1.0"); + + contentResolver.insert( + InstalledAppProvider.getContentUri(), + createContentValues("com.example.app2", 11, "1.1") + ); + + assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri()); + assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 11, "1.1"); + } + + /** + * We expect this to happen, because we should be using insert() instead as it will + * do an insert/replace query. + */ + @Test(expected = UnsupportedOperationException.class) + public void testUpdateFails() { + contentResolver.update( + InstalledAppProvider.getAppUri("com.example.app2"), + createContentValues(11, "1.1"), + null, null + ); + } + + @Test + public void testLastUpdateTime() { + String packageName = "com.example.app"; + + insertInstalledApp(packageName, 10, "1.0"); + assertResultCount(contentResolver, 1, InstalledAppProvider.getContentUri()); + assertIsInstalledVersionInDb(contentResolver, packageName, 10, "1.0"); + + Uri uri = InstalledAppProvider.getAppUri(packageName); + + String[] projection = { + InstalledAppProvider.DataColumns.PACKAGE_NAME, + InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, + }; + + Cursor cursor = contentResolver.query(uri, projection, null, null, null); + assertNotNull(cursor); + assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount()); + cursor.moveToFirst(); + assertEquals(packageName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.PACKAGE_NAME))); + long lastUpdateTime = cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME)); + assertTrue(lastUpdateTime > 0); + assertTrue(lastUpdateTime < System.currentTimeMillis()); + cursor.close(); + + insertInstalledApp(packageName, 11, "1.1"); + cursor = contentResolver.query(uri, projection, null, null, null); + assertNotNull(cursor); + assertEquals("App \"" + packageName + "\" not installed", 1, cursor.getCount()); + cursor.moveToFirst(); + assertTrue(lastUpdateTime < cursor.getLong(cursor.getColumnIndex(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME))); + cursor.close(); + } + + @Test + public void testDelete() { + + insertInstalledApp("com.example.app1", 10, "1.0"); + insertInstalledApp("com.example.app2", 10, "1.0"); + + assertResultCount(contentResolver, 2, InstalledAppProvider.getContentUri()); + + contentResolver.delete(InstalledAppProvider.getAppUri("com.example.app1"), null, null); + + assertResultCount(contentResolver, 1, InstalledAppProvider.getContentUri()); + assertIsInstalledVersionInDb(contentResolver, "com.example.app2", 10, "1.0"); + + } + + private ContentValues createContentValues(int versionCode, String versionNumber) { + return createContentValues(null, versionCode, versionNumber); + } + + private ContentValues createContentValues(String appId, int versionCode, String versionNumber) { + ContentValues values = new ContentValues(3); + if (appId != null) { + values.put(InstalledAppProvider.DataColumns.PACKAGE_NAME, appId); + } + values.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL, "Mock app: " + appId); + values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode); + values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber); + values.put(InstalledAppProvider.DataColumns.SIGNATURE, ""); + values.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, System.currentTimeMillis()); + values.put(InstalledAppProvider.DataColumns.HASH_TYPE, "sha256"); + values.put(InstalledAppProvider.DataColumns.HASH, "cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe"); + return values; + } + + private void insertInstalledApp(String appId, int versionCode, String versionNumber) { + ContentValues values = createContentValues(appId, versionCode, versionNumber); + contentResolver.insert(InstalledAppProvider.getContentUri(), values); + } +} + +// https://github.com/robolectric/robolectric/wiki/2.4-to-3.0-Upgrade-Guide diff --git a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java new file mode 100644 index 000000000..51fd6e905 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java @@ -0,0 +1,27 @@ +package org.fdroid.fdroid.data; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; + +public class InstalledAppTestUtils { + + /** + * Will tell {@code pm} that we are installing {@code packageName}, and then update the + * "installed apps" table in the database. + */ + public static void install(Context context, + String packageName, + int versionCode, String versionName) { + PackageInfo info = new PackageInfo(); + info.packageName = packageName; + info.versionCode = versionCode; + info.versionName = versionName; + info.applicationInfo = new ApplicationInfo(); + info.applicationInfo.publicSourceDir = "/tmp/mock-location"; + String hashType = "sha256"; + String hash = "00112233445566778899aabbccddeeff"; + InstalledAppProviderService.insertAppIntoDb(context, packageName, info, hashType, hash); + } + +} diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java new file mode 100644 index 000000000..118adcf65 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -0,0 +1,148 @@ +package org.fdroid.fdroid.data; + +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.mock.MockApk; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowContentResolver; + +import java.util.ArrayList; +import java.util.List; + +import static org.fdroid.fdroid.Assert.assertInvalidUri; +import static org.fdroid.fdroid.Assert.assertValidUri; + +@Config(constants = BuildConfig.class) +@RunWith(RobolectricGradleTestRunner.class) +public class ProviderUriTests { + + private ShadowContentResolver resolver; + + @Before + public void setup() { + resolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver()); + } + + @After + public void teardown() { + FDroidProvider.clearDbHelperSingleton(); + } + + @Test + public void invalidInstalledAppProviderUris() { + ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider()); + assertInvalidUri(resolver, InstalledAppProvider.getAuthority()); + assertInvalidUri(resolver, "blah"); + } + + @Test + public void validInstalledAppProviderUris() { + ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider()); + String[] projection = new String[] {InstalledAppProvider.DataColumns._ID}; + assertValidUri(resolver, InstalledAppProvider.getContentUri(), projection); + assertValidUri(resolver, InstalledAppProvider.getAppUri("org.example.app"), projection); + assertValidUri(resolver, InstalledAppProvider.getSearchUri("blah"), projection); + assertValidUri(resolver, InstalledAppProvider.getSearchUri("\"blah\""), projection); + assertValidUri(resolver, InstalledAppProvider.getSearchUri("blah & sneh"), projection); + assertValidUri(resolver, InstalledAppProvider.getSearchUri("http://blah.example.com?sneh=\"sneh\""), projection); + } + + @Test + public void invalidRepoProviderUris() { + ShadowContentResolver.registerProvider(RepoProvider.getAuthority(), new RepoProvider()); + assertInvalidUri(resolver, RepoProvider.getAuthority()); + assertInvalidUri(resolver, "blah"); + } + + @Test + public void validRepoProviderUris() { + ShadowContentResolver.registerProvider(RepoProvider.getAuthority(), new RepoProvider()); + String[] projection = new String[] {RepoProvider.DataColumns._ID}; + assertValidUri(resolver, RepoProvider.getContentUri(), projection); + assertValidUri(resolver, RepoProvider.getContentUri(10000L), projection); + assertValidUri(resolver, RepoProvider.allExceptSwapUri(), projection); + } + + @Test + public void invalidAppProviderUris() { + ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider()); + assertInvalidUri(resolver, AppProvider.getAuthority()); + assertInvalidUri(resolver, "blah"); + } + + @Test + public void validAppProviderUris() { + ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider()); + String[] projection = new String[] {AppProvider.DataColumns._ID}; + assertValidUri(resolver, AppProvider.getContentUri(), "content://org.fdroid.fdroid.data.AppProvider", projection); + assertValidUri(resolver, AppProvider.getSearchUri("'searching!'"), "content://org.fdroid.fdroid.data.AppProvider/search/'searching!'", projection); + assertValidUri(resolver, AppProvider.getSearchUri("/"), "content://org.fdroid.fdroid.data.AppProvider/search/%2F", projection); + assertValidUri(resolver, AppProvider.getSearchUri(""), "content://org.fdroid.fdroid.data.AppProvider", projection); + assertValidUri(resolver, AppProvider.getSearchUri(null), "content://org.fdroid.fdroid.data.AppProvider", projection); + assertValidUri(resolver, AppProvider.getNoApksUri(), "content://org.fdroid.fdroid.data.AppProvider/noApks", projection); + assertValidUri(resolver, AppProvider.getInstalledUri(), "content://org.fdroid.fdroid.data.AppProvider/installed", projection); + assertValidUri(resolver, AppProvider.getCanUpdateUri(), "content://org.fdroid.fdroid.data.AppProvider/canUpdate", projection); + + App app = new App(); + app.packageName = "org.fdroid.fdroid"; + + List apps = new ArrayList<>(1); + apps.add(app); + + assertValidUri(resolver, AppProvider.getContentUri(app), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection); + assertValidUri(resolver, AppProvider.getContentUri(apps), "content://org.fdroid.fdroid.data.AppProvider/apps/org.fdroid.fdroid", projection); + assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection); + } + + @Test + public void invalidApkProviderUris() { + ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider()); + assertInvalidUri(resolver, ApkProvider.getAuthority()); + assertInvalidUri(resolver, "blah"); + } + + @Test + public void validApkProviderUris() { + ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider()); + String[] projection = new String[] {ApkProvider.DataColumns._ID}; + + List apks = new ArrayList<>(10); + for (int i = 0; i < 10; i++) { + apks.add(new MockApk("com.example." + i, i)); + } + + assertValidUri(resolver, ApkProvider.getContentUri(), "content://org.fdroid.fdroid.data.ApkProvider", projection); + assertValidUri(resolver, ApkProvider.getAppUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.ApkProvider/app/org.fdroid.fdroid", projection); + assertValidUri(resolver, ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection); + assertValidUri(resolver, ApkProvider.getContentUri(apks), projection); + assertValidUri(resolver, ApkProvider.getContentUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection); + assertValidUri(resolver, ApkProvider.getRepoUri(1000), "content://org.fdroid.fdroid.data.ApkProvider/repo/1000", projection); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidApkUrisWithTooManyApks() { + String[] projection = ApkProvider.DataColumns.ALL; + + List manyApks = new ArrayList<>(ApkProvider.MAX_APKS_TO_QUERY - 5); + for (int i = 0; i < ApkProvider.MAX_APKS_TO_QUERY - 1; i++) { + manyApks.add(new MockApk("com.example." + i, i)); + } + assertValidUri(resolver, ApkProvider.getContentUri(manyApks), projection); + + manyApks.add(new MockApk("org.fdroid.fdroid.1", 1)); + manyApks.add(new MockApk("org.fdroid.fdroid.2", 2)); + + // Technically, it is a valid URI, because it doesn't + // throw an UnsupportedOperationException. However it + // is still not okay (we run out of bindable parameters + // in the sqlite query. + assertValidUri(resolver, ApkProvider.getContentUri(manyApks), projection); + } + +} diff --git a/app/src/androidTest/java/org/fdroid/fdroid/mock/MockApk.java b/app/src/test/java/org/fdroid/fdroid/mock/MockApk.java similarity index 100% rename from app/src/androidTest/java/org/fdroid/fdroid/mock/MockApk.java rename to app/src/test/java/org/fdroid/fdroid/mock/MockApk.java diff --git a/app/src/androidTest/java/org/fdroid/fdroid/mock/MockApp.java b/app/src/test/java/org/fdroid/fdroid/mock/MockApp.java similarity index 100% rename from app/src/androidTest/java/org/fdroid/fdroid/mock/MockApp.java rename to app/src/test/java/org/fdroid/fdroid/mock/MockApp.java diff --git a/app/src/androidTest/java/org/fdroid/fdroid/mock/MockRepo.java b/app/src/test/java/org/fdroid/fdroid/mock/MockRepo.java similarity index 100% rename from app/src/androidTest/java/org/fdroid/fdroid/mock/MockRepo.java rename to app/src/test/java/org/fdroid/fdroid/mock/MockRepo.java diff --git a/app/src/androidTest/assets/README.md b/app/src/test/resources/README.md similarity index 100% rename from app/src/androidTest/assets/README.md rename to app/src/test/resources/README.md diff --git a/app/src/androidTest/assets/largeRepo.xml b/app/src/test/resources/largeRepo.xml similarity index 100% rename from app/src/androidTest/assets/largeRepo.xml rename to app/src/test/resources/largeRepo.xml diff --git a/app/src/androidTest/assets/masterKeyIndex.jar b/app/src/test/resources/masterKeyIndex.jar similarity index 100% rename from app/src/androidTest/assets/masterKeyIndex.jar rename to app/src/test/resources/masterKeyIndex.jar diff --git a/app/src/androidTest/assets/mediumRepo.xml b/app/src/test/resources/mediumRepo.xml similarity index 100% rename from app/src/androidTest/assets/mediumRepo.xml rename to app/src/test/resources/mediumRepo.xml diff --git a/app/src/androidTest/assets/multiRepo.archive.jar b/app/src/test/resources/multiRepo.archive.jar similarity index 100% rename from app/src/androidTest/assets/multiRepo.archive.jar rename to app/src/test/resources/multiRepo.archive.jar diff --git a/app/src/androidTest/assets/multiRepo.conflicting.jar b/app/src/test/resources/multiRepo.conflicting.jar similarity index 100% rename from app/src/androidTest/assets/multiRepo.conflicting.jar rename to app/src/test/resources/multiRepo.conflicting.jar diff --git a/app/src/androidTest/assets/multiRepo.normal.jar b/app/src/test/resources/multiRepo.normal.jar similarity index 100% rename from app/src/androidTest/assets/multiRepo.normal.jar rename to app/src/test/resources/multiRepo.normal.jar diff --git a/app/src/test/resources/simpleIndex.jar b/app/src/test/resources/simpleIndex.jar new file mode 100644 index 000000000..1c173ceb3 Binary files /dev/null and b/app/src/test/resources/simpleIndex.jar differ diff --git a/app/src/androidTest/assets/simpleIndex.xml b/app/src/test/resources/simpleIndex.xml similarity index 100% rename from app/src/androidTest/assets/simpleIndex.xml rename to app/src/test/resources/simpleIndex.xml diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedCertificate.jar b/app/src/test/resources/simpleIndexWithCorruptedCertificate.jar similarity index 100% rename from app/src/androidTest/assets/simpleIndexWithCorruptedCertificate.jar rename to app/src/test/resources/simpleIndexWithCorruptedCertificate.jar diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedEverything.jar b/app/src/test/resources/simpleIndexWithCorruptedEverything.jar similarity index 100% rename from app/src/androidTest/assets/simpleIndexWithCorruptedEverything.jar rename to app/src/test/resources/simpleIndexWithCorruptedEverything.jar diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedManifest.jar b/app/src/test/resources/simpleIndexWithCorruptedManifest.jar similarity index 100% rename from app/src/androidTest/assets/simpleIndexWithCorruptedManifest.jar rename to app/src/test/resources/simpleIndexWithCorruptedManifest.jar diff --git a/app/src/androidTest/assets/simpleIndexWithCorruptedSignature.jar b/app/src/test/resources/simpleIndexWithCorruptedSignature.jar similarity index 100% rename from app/src/androidTest/assets/simpleIndexWithCorruptedSignature.jar rename to app/src/test/resources/simpleIndexWithCorruptedSignature.jar diff --git a/app/src/androidTest/assets/simpleIndexWithoutSignature.jar b/app/src/test/resources/simpleIndexWithoutSignature.jar similarity index 100% rename from app/src/androidTest/assets/simpleIndexWithoutSignature.jar rename to app/src/test/resources/simpleIndexWithoutSignature.jar diff --git a/app/src/androidTest/assets/smallRepo.xml b/app/src/test/resources/smallRepo.xml similarity index 100% rename from app/src/androidTest/assets/smallRepo.xml rename to app/src/test/resources/smallRepo.xml diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 8bb944a11..735e5d608 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -32,8 +32,8 @@ - + diff --git a/config/pmd/rules-main.xml b/config/pmd/rules-main.xml new file mode 100644 index 000000000..5b33b45d8 --- /dev/null +++ b/config/pmd/rules-main.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/config/pmd/rules-test.xml b/config/pmd/rules-test.xml new file mode 100644 index 000000000..0889f5bcf --- /dev/null +++ b/config/pmd/rules-test.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/config/pmd/rules.xml b/config/pmd/rules.xml index 3e4600f4e..e9a161577 100644 --- a/config/pmd/rules.xml +++ b/config/pmd/rules.xml @@ -12,7 +12,6 @@ -