diff --git a/src/org/fdroid/fdroid/data/ApkProvider.java b/src/org/fdroid/fdroid/data/ApkProvider.java index a4b16b39c..b957b2093 100644 --- a/src/org/fdroid/fdroid/data/ApkProvider.java +++ b/src/org/fdroid/fdroid/data/ApkProvider.java @@ -366,6 +366,11 @@ public class ApkProvider extends FDroidProvider { String[] apkDetails = apkKeys.split(","); String[] args = new String[apkDetails.length * 2]; StringBuilder sb = new StringBuilder(); + if (apkDetails.length > MAX_APKS_TO_QUERY) { + throw new IllegalArgumentException( + "Cannot query more than " + MAX_APKS_TO_QUERY + ". " + + "You tried to query " + apkDetails.length); + } for (int i = 0; i < apkDetails.length; i ++) { String[] parts = apkDetails[i].split(":"); String id = parts[0]; diff --git a/test/src/org/fdroid/fdroid/ApkProviderTest.java b/test/src/org/fdroid/fdroid/ApkProviderTest.java new file mode 100644 index 000000000..795172d27 --- /dev/null +++ b/test/src/org/fdroid/fdroid/ApkProviderTest.java @@ -0,0 +1,131 @@ +package org.fdroid.fdroid; + +import android.content.ContentValues; +import android.database.Cursor; +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.AppProvider; +import org.fdroid.fdroid.mock.MockApk; + +import java.util.ArrayList; +import java.util.List; + +public class ApkProviderTest extends FDroidProviderTest { + + public ApkProviderTest() { + super(ApkProvider.class, ApkProvider.getAuthority()); + } + + protected String[] getMinimalProjection() { + return new String[] { + ApkProvider.DataColumns.APK_ID, + ApkProvider.DataColumns.VERSION_CODE, + ApkProvider.DataColumns.NAME + }; + } + + public void testUris() { + assertInvalidUri(ApkProvider.getAuthority()); + assertInvalidUri(AppProvider.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(ApkProvider.getContentUri(apks)); + assertValidUri(ApkProvider.getContentUri("org.fdroid.fdroid", 100)); + assertValidUri(ApkProvider.getRepoUri(1000)); + + 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(ApkProvider.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(ApkProvider.getContentUri(manyApks)); + fail(); + } catch (IllegalArgumentException e) { + // This is the expected error behaviour. + } catch (Exception e) { + fail(); + } + + } + + public void testQuery() { + Cursor cursor = queryAllApks(); + assertNotNull(cursor); + } + + private void insertApks(int count) { + for (int i = 0; i < count; i ++) { + insertApk("com.example.test." + i, i); + } + } + + public void testInsert() { + + // Start with an empty database... + Cursor cursor = queryAllApks(); + assertNotNull(cursor); + assertEquals(0, cursor.getCount()); + + // Insert a new record... + insertApk("org.fdroid.fdroid", 13); + 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 + // APK_ID. + cursor.moveToFirst(); + Apk apk = new Apk(cursor); + assertEquals("org.fdroid.fdroid", apk.id); + assertEquals(13, apk.vercode); + } + + public void testIgnore() { + for (int i = 0; i < 10; i ++) { + insertApk("org.fdroid.fdroid", i); + } + + } + + private Cursor queryAllApks() { + return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null); + } + + private void insertApk(String id, int versionCode) { + insertApk(id, versionCode, new ContentValues()); + } + + private void insertApk(String id, int versionCode, + ContentValues additionalValues) { + TestUtils.insertApk(getMockContentResolver(), id, versionCode, additionalValues); + } + +} diff --git a/test/src/org/fdroid/fdroid/AppProviderTest.java b/test/src/org/fdroid/fdroid/AppProviderTest.java index 3252fd829..ae10ffc34 100644 --- a/test/src/org/fdroid/fdroid/AppProviderTest.java +++ b/test/src/org/fdroid/fdroid/AppProviderTest.java @@ -3,7 +3,6 @@ package org.fdroid.fdroid; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; -import junit.framework.AssertionFailedError; import mock.MockCategoryResources; import mock.MockInstallablePackageManager; import org.fdroid.fdroid.data.ApkProvider; @@ -11,7 +10,6 @@ import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.AppProvider; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class AppProviderTest extends FDroidProviderTest { @@ -24,7 +22,6 @@ public class AppProviderTest extends FDroidProviderTest { public void setUp() throws Exception { super.setUp(); getSwappableContext().setResources(new MockCategoryResources()); - getSwappableContext().setContentResolver(getMockContentResolver()); } protected String[] getMinimalProjection() { @@ -75,20 +72,14 @@ public class AppProviderTest extends FDroidProviderTest { insertApps(100); - assertAppCount(100, AppProvider.getContentUri()); - assertAppCount(0, AppProvider.getInstalledUri()); + assertResultCount(100, AppProvider.getContentUri()); + assertResultCount(0, AppProvider.getInstalledUri()); for (int i = 10; i < 20; i ++) { pm.install("com.example.test." + i, i, "v1"); } - assertAppCount(10, AppProvider.getInstalledUri()); - } - - private void assertAppCount(int expectedCount, Uri uri) { - Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null); - assertNotNull(cursor); - assertEquals(expectedCount, cursor.getCount()); + assertResultCount(10, AppProvider.getInstalledUri()); } public void testInsert() { @@ -142,7 +133,7 @@ public class AppProviderTest extends FDroidProviderTest { "Mineral", "Vegetable" }; - assertContainsOnly(categories, expected); + TestUtils.assertContainsOnly(categories, expected); } public void testCategoriesMultiple() { @@ -160,7 +151,7 @@ public class AppProviderTest extends FDroidProviderTest { "Mineral", "Vegetable" }; - assertContainsOnly(categories, expected); + TestUtils.assertContainsOnly(categories, expected); insertAppWithCategory("com.example.game", "Game", "Running,Shooting,Jumping,Bleh,Sneh,Pleh,Blah,Test category," + @@ -188,7 +179,7 @@ public class AppProviderTest extends FDroidProviderTest { "With apostrophe's" }; - assertContainsOnly(categoriesLonger, expectedLonger); + TestUtils.assertContainsOnly(categoriesLonger, expectedLonger); } private void insertApp(String id, String name) { @@ -207,52 +198,4 @@ public class AppProviderTest extends FDroidProviderTest { TestUtils.insertApp(getMockContentResolver(), id, name, additionalValues); } - private void assertContainsOnly(List actualList, T[] expectedContains) { - List containsList = new ArrayList(expectedContains.length); - Collections.addAll(containsList, expectedContains); - assertContainsOnly(actualList, containsList); - } - - private 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; - } - - private 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); - } - } - } - } diff --git a/test/src/org/fdroid/fdroid/FDroidProviderTest.java b/test/src/org/fdroid/fdroid/FDroidProviderTest.java index 776202ba0..fe85ea6d2 100644 --- a/test/src/org/fdroid/fdroid/FDroidProviderTest.java +++ b/test/src/org/fdroid/fdroid/FDroidProviderTest.java @@ -24,6 +24,13 @@ public abstract class FDroidProviderTest extends Provi public void setUp() throws Exception { super.setUp(); Utils.setupInstalledApkCache(new MockInstalledApkCache()); + + // 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()); } @TargetApi(Build.VERSION_CODES.ECLAIR) @@ -74,4 +81,9 @@ public abstract class FDroidProviderTest extends Provi */ protected abstract String[] getMinimalProjection(); + protected void assertResultCount(int expectedCount, Uri uri) { + Cursor cursor = getMockContentResolver().query(uri, getMinimalProjection(), null, null, null); + assertNotNull(cursor); + assertEquals(expectedCount, cursor.getCount()); + } } diff --git a/test/src/org/fdroid/fdroid/TestUtils.java b/test/src/org/fdroid/fdroid/TestUtils.java index a01f85460..19299e10a 100644 --- a/test/src/org/fdroid/fdroid/TestUtils.java +++ b/test/src/org/fdroid/fdroid/TestUtils.java @@ -2,10 +2,65 @@ package org.fdroid.fdroid; import android.content.*; import android.net.Uri; +import android.test.mock.MockContentResolver; +import junit.framework.AssertionFailedError; +import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.AppProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + public class TestUtils { + public static void assertContainsOnly(List actualList, T[] expectedContains) { + List containsList = new ArrayList(expectedContains.length); + Collections.addAll(containsList, expectedContains); + assertContainsOnly(actualList, containsList); + } + + 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 id, String name, ContentValues additionalValues) { ContentValues values = new ContentValues(); @@ -27,4 +82,25 @@ public class TestUtils { resolver.insert(uri, values); } + public static void insertApk(ContentResolver resolver, String id, int versionCode, ContentValues additionalValues) { + + ContentValues values = new ContentValues(); + + values.put(ApkProvider.DataColumns.APK_ID, 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, "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(); + + resolver.insert(uri, values); + } } diff --git a/test/src/org/fdroid/fdroid/mock/MockApk.java b/test/src/org/fdroid/fdroid/mock/MockApk.java new file mode 100644 index 000000000..f3da31d6d --- /dev/null +++ b/test/src/org/fdroid/fdroid/mock/MockApk.java @@ -0,0 +1,12 @@ +package org.fdroid.fdroid.mock; + +import org.fdroid.fdroid.data.Apk; + +public class MockApk extends Apk { + + public MockApk(String id, int versionCode) { + this.id = id; + this.vercode = versionCode; + } + +} diff --git a/test/src/org/fdroid/fdroid/mock/MockApp.java b/test/src/org/fdroid/fdroid/mock/MockApp.java new file mode 100644 index 000000000..1a983865c --- /dev/null +++ b/test/src/org/fdroid/fdroid/mock/MockApp.java @@ -0,0 +1,16 @@ +package org.fdroid.fdroid.mock; + +import org.fdroid.fdroid.data.App; + +public class MockApp extends App { + + public MockApp(String id) { + this(id, "App " + id); + } + + public MockApp(String id, String name) { + this.id = id; + this.name = name; + } + +}