diff --git a/app/build.gradle b/app/build.gradle index bb41a86ed..9443a8917 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,6 +44,8 @@ dependencies { // 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' diff --git a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java b/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java index 8b01eb630..e51790abb 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/TestUtils.java @@ -13,7 +13,7 @@ import junit.framework.AssertionFailedError; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.AppProvider; -import org.fdroid.fdroid.data.FDroidProviderTest; +import org.fdroid.fdroid.data.FDroidProviderTestOld; import java.io.File; import java.io.FileOutputStream; @@ -113,11 +113,11 @@ public class TestUtils { resolver.insert(uri, values); } - public static Uri insertApk(FDroidProviderTest providerTest, String id, int versionCode) { + public static Uri insertApk(FDroidProviderTestOld providerTest, String id, int versionCode) { return insertApk(providerTest, id, versionCode, new ContentValues()); } - public static Uri insertApk(FDroidProviderTest providerTest, String id, int versionCode, ContentValues additionalValues) { + public static Uri insertApk(FDroidProviderTestOld providerTest, String id, int versionCode, ContentValues additionalValues) { ContentValues values = new ContentValues(); 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 0bce53b68..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); - - InstalledAppTestUtils.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++) { - InstalledAppTestUtils.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 index f5a73197c..41d6b0a6c 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/data/BaseApkProviderTest.java @@ -16,7 +16,7 @@ import java.util.List; * This should not contain any test methods, or else they get executed * once for every concrete subclass. */ -abstract class BaseApkProviderTest extends FDroidProviderTest { +abstract class BaseApkProviderTest extends FDroidProviderTestOld { BaseApkProviderTest() { super(ApkProvider.class, ApkProvider.getAuthority()); diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTestOld.java similarity index 96% rename from app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java rename to app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTestOld.java index 40e81fd28..4767aa4c4 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/data/FDroidProviderTestOld.java @@ -15,7 +15,7 @@ import mock.MockContextSwappableComponents; import mock.MockFDroidResources; @SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics -public abstract class FDroidProviderTest extends ProviderTestCase2MockContext { +public abstract class FDroidProviderTestOld extends ProviderTestCase2MockContext { private FDroidProvider[] allProviders = { new AppProvider(), @@ -26,7 +26,7 @@ public abstract class FDroidProviderTest extends Provi private MockContextSwappableComponents swappableContext; - public FDroidProviderTest(Class providerClass, String providerAuthority) { + public FDroidProviderTestOld(Class providerClass, String providerAuthority) { super(providerClass, providerAuthority); } diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java index cb63f631a..f63d11b90 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppCacheTest.java @@ -5,11 +5,11 @@ 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 + * For practical reasons, it extends FDroidProviderTestOld, 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 { +public class InstalledAppCacheTest extends FDroidProviderTestOld { private MockInstallablePackageManager packageManager; diff --git a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java b/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java deleted file mode 100644 index 77959530c..000000000 --- a/app/src/androidTest/java/org/fdroid/fdroid/data/InstalledAppTestUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.fdroid.fdroid.data; - -import android.content.pm.PackageInfo; - -import mock.MockContextSwappableComponents; -import mock.MockInstallablePackageManager; - -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(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/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..3aa382521 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/AppProviderTest.java @@ -0,0 +1,374 @@ +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.data.ProviderTestUtils.assertContainsOnly; +import static org.fdroid.fdroid.data.ProviderTestUtils.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; +import static org.junit.Assert.fail; + +@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()); + + // 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); + + App otherApp = AppProvider.Helper.findByPackageName(context.getContentResolver(), "org.fdroid.fdroid"); + assertNotNull(otherApp); + assertEquals("org.fdroid.fdroid", otherApp.packageName); + assertEquals("F-Droid", otherApp.name); + } + + 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); + } + + +} + +// https://github.com/robolectric/robolectric/wiki/2.4-to-3.0-Upgrade-Guide 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..5bef2d769 --- /dev/null +++ b/app/src/test/java/org/fdroid/fdroid/data/FDroidProviderTest.java @@ -0,0 +1,39 @@ +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 index 75a4dcded..1d537de18 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java +++ b/app/src/test/java/org/fdroid/fdroid/data/InstalledAppProviderTest.java @@ -6,13 +6,11 @@ import android.database.Cursor; import android.net.Uri; import org.fdroid.fdroid.BuildConfig; -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; @@ -27,21 +25,13 @@ import java.util.Map; @Config(constants = BuildConfig.class, application = Application.class) @RunWith(RobolectricGradleTestRunner.class) -public class InstalledAppProviderTest { - - private ShadowContentResolver contentResolver; +public class InstalledAppProviderTest extends FDroidProviderTest{ @Before public void setup() { - contentResolver = Shadows.shadowOf(RuntimeEnvironment.application.getContentResolver()); ShadowContentResolver.registerProvider(InstalledAppProvider.getAuthority(), new InstalledAppProvider()); } - @After - public void teardown() { - FDroidProvider.clearDbHelperSingleton(); - } - @Test public void insertSingleApp() { Map foundBefore = InstalledAppProvider.Helper.all(RuntimeEnvironment.application); 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/ProviderTestUtils.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderTestUtils.java index 9c3b2cc4b..97a5481b2 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderTestUtils.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderTestUtils.java @@ -4,8 +4,12 @@ import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; +import junit.framework.AssertionFailedError; + import org.robolectric.shadows.ShadowContentResolver; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import static org.junit.Assert.assertEquals; @@ -15,6 +19,66 @@ import static org.junit.Assert.fail; public class ProviderTestUtils { + 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); @@ -62,7 +126,11 @@ public class ProviderTestUtils { } public static void assertResultCount(ShadowContentResolver resolver, int expectedCount, Uri uri) { - Cursor cursor = resolver.query(uri, new String[] {}, null, null, null); + 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(); } diff --git a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java index c62aa465b..57e68624a 100644 --- a/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java +++ b/app/src/test/java/org/fdroid/fdroid/data/ProviderUriTests.java @@ -11,6 +11,9 @@ 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.data.ProviderTestUtils.assertInvalidUri; import static org.fdroid.fdroid.data.ProviderTestUtils.assertValidUri; @@ -64,6 +67,36 @@ public class ProviderUriTests { 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); } }