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;
+ }
+
+ abstract protected 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
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mResolver = new MockContentResolver();
+ final String filenamePrefix = "test.";
+ 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, 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
+ protected void tearDown() throws Exception {
+ mProvider.shutdown();
+ super.tearDown();
+ }
+
+ /**
+ * 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/test/src/mock/MockCategoryResources.java b/test/src/mock/MockCategoryResources.java
new file mode 100644
index 000000000..2831049d8
--- /dev/null
+++ b/test/src/mock/MockCategoryResources.java
@@ -0,0 +1,26 @@
+package mock;
+
+import android.content.Context;
+import android.test.mock.*;
+import org.fdroid.fdroid.*;
+
+public class MockCategoryResources extends MockFDroidResources {
+
+ public MockCategoryResources(Context getStringDelegatingContext) {
+ super(getStringDelegatingContext);
+ }
+
+ @Override
+ public String getString(int id) {
+ if (id == R.string.category_all) {
+ return "All";
+ } else if (id == R.string.category_recentlyupdated) {
+ return "Recently Updated";
+ } else if (id == R.string.category_whatsnew) {
+ return "Whats New";
+ } else {
+ return "";
+ }
+}
+
+}
diff --git a/test/src/mock/MockContextEmptyComponents.java b/test/src/mock/MockContextEmptyComponents.java
new file mode 100644
index 000000000..eb962bbe9
--- /dev/null
+++ b/test/src/mock/MockContextEmptyComponents.java
@@ -0,0 +1,14 @@
+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/test/src/mock/MockContextSwappableComponents.java b/test/src/mock/MockContextSwappableComponents.java
new file mode 100644
index 000000000..9cb09f466
--- /dev/null
+++ b/test/src/mock/MockContextSwappableComponents.java
@@ -0,0 +1,43 @@
+package mock;
+
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.test.mock.*;
+
+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/test/src/mock/MockEmptyPackageManager.java b/test/src/mock/MockEmptyPackageManager.java
new file mode 100644
index 000000000..39fdee310
--- /dev/null
+++ b/test/src/mock/MockEmptyPackageManager.java
@@ -0,0 +1,16 @@
+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/test/src/mock/MockEmptyResources.java b/test/src/mock/MockEmptyResources.java
new file mode 100644
index 000000000..fdc06e47f
--- /dev/null
+++ b/test/src/mock/MockEmptyResources.java
@@ -0,0 +1,12 @@
+package mock;
+
+import android.test.mock.MockResources;
+
+public class MockEmptyResources extends MockResources {
+
+ @Override
+ public String getString(int id) {
+ return "";
+ }
+
+}
diff --git a/test/src/mock/MockFDroidResources.java b/test/src/mock/MockFDroidResources.java
new file mode 100644
index 000000000..c2b716d2f
--- /dev/null
+++ b/test/src/mock/MockFDroidResources.java
@@ -0,0 +1,36 @@
+package mock;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.mock.*;
+import org.fdroid.fdroid.*;
+
+public class MockFDroidResources extends MockResources {
+
+ private 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) {
+ if (id == R.integer.default_repo_inuse1) {
+ return 1;
+ } else if (id == R.integer.default_repo_inuse2) {
+ return 0;
+ } else if (id == R.integer.default_repo_priority1) {
+ return 10;
+ } else if (id == R.integer.default_repo_priority2) {
+ return 20;
+ } else {
+ return 0;
+ }
+}
+
+}
diff --git a/test/src/mock/MockInstallablePackageManager.java b/test/src/mock/MockInstallablePackageManager.java
new file mode 100644
index 000000000..f291c1517
--- /dev/null
+++ b/test/src/mock/MockInstallablePackageManager.java
@@ -0,0 +1,52 @@
+package mock;
+
+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 List info = new ArrayList();
+
+ @Override
+ public List getInstalledPackages(int flags) {
+ return info;
+ }
+
+ public void install(String id, int version, String versionName) {
+ PackageInfo existing = getPackageInfo(id);
+ if (existing != null) {
+ existing.versionCode = version;
+ existing.versionName = versionName;
+ } else {
+ PackageInfo p = new PackageInfo();
+ p.packageName = id;
+ p.versionCode = version;
+ p.versionName = versionName;
+ info.add(p);
+ }
+ }
+
+ public PackageInfo getPackageInfo(String id) {
+ for (PackageInfo i : info) {
+ if (i.packageName.equals(id)) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ 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/test/src/org/fdroid/fdroid/ApkProviderHelperTest.java b/test/src/org/fdroid/fdroid/ApkProviderHelperTest.java
new file mode 100644
index 000000000..678c9903f
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/ApkProviderHelperTest.java
@@ -0,0 +1,202 @@
+package org.fdroid.fdroid;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.mock.MockApk;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+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.APK_ID,
+ 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.findByApp(getMockContext(), "org.fdroid.fdroid");
+ assertResultCount(7, fdroidApks);
+ assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
+
+ List exampleApks = ApkProvider.Helper.findByApp(getMockContext(), "org.example");
+ assertResultCount(9, exampleApks);
+ assertBelongsToApp(exampleApks, "org.example");
+
+ List exampleApks2 = ApkProvider.Helper.findByApp(getMockContext(), "com.example");
+ assertResultCount(3, exampleApks2);
+ assertBelongsToApp(exampleApks2, "com.example");
+
+ List thingoApks = ApkProvider.Helper.findByApp(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);
+
+ assertEquals("com.example", apk.id);
+ assertEquals(10, apk.vercode);
+
+ 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);
+
+ Cursor updatedCursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
+ assertResultCount(1, updatedCursor);
+
+ updatedCursor.moveToFirst();
+ Apk updatedApk = new Apk(updatedCursor);
+
+ assertEquals("com.example", updatedApk.id);
+ assertEquals(10, updatedApk.vercode);
+
+ 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, "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.id);
+ assertEquals(11, apk.vercode);
+ assertEquals("v1.1", apk.version);
+ assertEquals("xxxxyyyy", apk.hash);
+ assertEquals("a hash type", apk.hashType);
+
+ String[] projection = {
+ ApkProvider.DataColumns.APK_ID,
+ ApkProvider.DataColumns.HASH
+ };
+
+ Apk apkLessFields = ApkProvider.Helper.find(getMockContext(), "com.example", 11, projection);
+
+ assertNotNull(apkLessFields);
+
+ assertEquals("com.example", apkLessFields.id);
+ assertEquals("xxxxyyyy", apkLessFields.hash);
+
+ // Didn't ask for these fields, so should be their default values...
+ assertNull(apkLessFields.hashType);
+ assertNull(apkLessFields.version);
+ assertEquals(0, apkLessFields.vercode);
+
+ Apk notFound = ApkProvider.Helper.find(getMockContext(), "com.doesnt.exist", 1000);
+ assertNull(notFound);
+ }
+
+}
diff --git a/test/src/org/fdroid/fdroid/ApkProviderTest.java b/test/src/org/fdroid/fdroid/ApkProviderTest.java
new file mode 100644
index 000000000..e77ef41bc
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/ApkProviderTest.java
@@ -0,0 +1,313 @@
+package org.fdroid.fdroid;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.AppProvider;
+import org.fdroid.fdroid.data.RepoProvider;
+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;
+
+public class ApkProviderTest extends BaseApkProviderTest {
+
+ 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(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 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");
+
+ Cursor exampleApks = getMockContentResolver().query(
+ ApkProvider.getAppUri("com.example"),
+ getMinimalProjection(),
+ null, null, null);
+ assertResultCount(10, exampleApks);
+ assertBelongsToApp(exampleApks, "com.example");
+
+ ApkProvider.Helper.deleteApksByApp(getMockContext(), new MockApp("com.example"));
+
+ Cursor all = queryAllApks();
+ assertResultCount(10, all);
+ assertBelongsToApp(all, "org.fdroid.fdroid");
+ }
+
+ 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(ApkProvider.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.id);
+ assertEquals("com.example.two", two.id);
+ assertEquals("com.example.five", five.id);
+
+ 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.id);
+ }
+
+ 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.id);
+ }
+
+ 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"));
+ }
+
+ public void testRepoApks() {
+
+ final long REPO_KEEP = 1;
+ final long REPO_DELETE = 2;
+
+ // 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);
+
+ 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);
+
+ // The only remaining apks should be those from REPO_KEEP.
+ assertBelongsToRepo(queryAllApks(), REPO_KEEP);
+ }
+
+ public void testQuery() {
+ Cursor cursor = queryAllApks();
+ assertNotNull(cursor);
+ }
+
+ public void testInsert() {
+
+ // Start with an empty database...
+ Cursor cursor = queryAllApks();
+ assertNotNull(cursor);
+ assertEquals(0, cursor.getCount());
+
+ Apk apk = new MockApk("org.fdroid.fdroid", 13);
+
+ // Insert a new record...
+ Uri newUri = TestUtils.insertApk(this, apk.id, apk.vercode);
+ 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
+ // APK_ID.
+ cursor.moveToFirst();
+ Apk toCheck = new Apk(cursor);
+ assertEquals("org.fdroid.fdroid", toCheck.id);
+ assertEquals(13, toCheck.vercode);
+ }
+
+ 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);
+ assertResultCount(1, allWithCount);
+
+ allWithCount.moveToFirst();
+ int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
+ assertEquals(13, allWithCount.getInt(countColumn));
+ }
+
+ public void testInsertWithExtraFields() {
+
+ assertResultCount(0, queryAllApks());
+
+ String[] repoFields = new String[] {
+ RepoProvider.DataColumns.DESCRIPTION,
+ RepoProvider.DataColumns.ADDRESS,
+ RepoProvider.DataColumns.FINGERPRINT,
+ RepoProvider.DataColumns.NAME,
+ RepoProvider.DataColumns.PUBLIC_KEY
+ };
+
+ 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);
+
+ // 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.id);
+ assertEquals(1, apk.vercode);
+ assertEquals(10, apk.repo);
+ }
+
+}
diff --git a/test/src/org/fdroid/fdroid/AppProviderTest.java b/test/src/org/fdroid/fdroid/AppProviderTest.java
new file mode 100644
index 000000000..12124848e
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/AppProviderTest.java
@@ -0,0 +1,377 @@
+package org.fdroid.fdroid;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.database.Cursor;
+import mock.MockCategoryResources;
+import mock.MockContextSwappableComponents;
+import mock.MockInstallablePackageManager;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.data.AppProvider;
+import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.APP_ID,
+ 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 ++) {
+ pm.install("com.example.app" + i, 1, "v" + 1);
+ }
+ InstalledAppCacheUpdater.updateInForeground(getMockContext());
+
+ assertResultCount(1, AppProvider.getInstalledUri());
+
+ for (int i = 50; i < 500; i ++) {
+ pm.install("com.example.app" + i, 1, "v" + 1);
+ }
+ InstalledAppCacheUpdater.updateInForeground(getMockContext());
+
+ assertResultCount(2, AppProvider.getInstalledUri());
+
+ for (int i = 500; i < 1100; i ++) {
+ pm.install("com.example.app" + i, 1, "v" + 1);
+ }
+ InstalledAppCacheUpdater.updateInForeground(getMockContext());
+
+ assertResultCount(3, AppProvider.getInstalledUri());
+ }
+
+ public void testCantFindApp() {
+ assertNull(AppProvider.Helper.findById(getMockContentResolver(), "com.example.doesnt-exist"));
+ }
+
+ public void testUris() {
+ assertInvalidUri(AppProvider.getAuthority());
+ assertInvalidUri(ApkProvider.getContentUri());
+
+ assertValidUri(AppProvider.getContentUri());
+ assertValidUri(AppProvider.getSearchUri("'searching!'"));
+ assertValidUri(AppProvider.getNoApksUri());
+ assertValidUri(AppProvider.getInstalledUri());
+ assertValidUri(AppProvider.getCanUpdateUri());
+
+ App app = new App();
+ app.id = "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);
+ }
+
+ 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);
+
+ TestUtils.installAndBroadcast(getMockContext(), 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.findById(r, "not installed");
+ assertFalse(notInstalled.canAndWantToUpdate());
+
+ App installedOnlyOneVersionAvailable = AppProvider.Helper.findById(r, "installed, only one version available");
+ App installedAlreadyLatestNoIgnore = AppProvider.Helper.findById(r, "installed, already latest, no ignore");
+ App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findById(r, "installed, already latest, ignore all");
+ App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findById(r, "installed, already latest, ignore latest");
+ App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findById(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.findById(r, "installed, old version, no ignore");
+ App installedOldIgnoreAll = AppProvider.Helper.findById(r, "installed, old version, ignore all");
+ App installedOldIgnoreLatest = AppProvider.Helper.findById(r, "installed, old version, ignore latest");
+ App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findById(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).id);
+ canUpdateCursor.moveToNext();
+ }
+
+ 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.APP_ID };
+ 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.id);
+ }
+ 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 ++) {
+ TestUtils.installAndBroadcast(getMockContext(), 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());
+
+ // 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
+ // APP_ID.
+ cursor.moveToFirst();
+ App app = new App(cursor);
+ assertEquals("org.fdroid.fdroid", app.id);
+ 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_whatsnew),
+ getMockContext().getResources().getString(R.string.category_recentlyupdated),
+ 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_whatsnew),
+ getMockContext().getResources().getString(R.string.category_recentlyupdated),
+ 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_whatsnew),
+ getMockContext().getResources().getString(R.string.category_recentlyupdated),
+ 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/test/src/org/fdroid/fdroid/BaseApkProviderTest.java b/test/src/org/fdroid/fdroid/BaseApkProviderTest.java
new file mode 100644
index 000000000..7c2e42bc4
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/BaseApkProviderTest.java
@@ -0,0 +1,78 @@
+package org.fdroid.fdroid;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.ApkProvider;
+
+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 {
+
+ public BaseApkProviderTest() {
+ super(ApkProvider.class, ApkProvider.getAuthority());
+ }
+
+ @Override
+ protected String[] getMinimalProjection() {
+ return new String[] {
+ ApkProvider.DataColumns.APK_ID,
+ 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.vercode == apk.vercode && a.id.equals(apk.id)) {
+ 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.id);
+ }
+ }
+
+ 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/test/src/org/fdroid/fdroid/FDroidProviderTest.java b/test/src/org/fdroid/fdroid/FDroidProviderTest.java
new file mode 100644
index 000000000..1be7eec3d
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/FDroidProviderTest.java
@@ -0,0 +1,168 @@
+package org.fdroid.fdroid;
+
+import android.annotation.TargetApi;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.ContactsContract;
+import android.test.ProviderTestCase2MockContext;
+import mock.MockContextEmptyComponents;
+import mock.MockContextSwappableComponents;
+import mock.MockFDroidResources;
+import org.fdroid.fdroid.data.*;
+
+import java.util.List;
+
+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();
+
+ // 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());
+
+ }
+
+ @TargetApi(Build.VERSION_CODES.ECLAIR)
+ 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ 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.APP_ID,
+ InstalledAppProvider.DataColumns.VERSION_CODE,
+ InstalledAppProvider.DataColumns.VERSION_NAME,
+ };
+
+ 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.APP_ID)));
+ assertEquals(versionCode, cursor.getInt(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_CODE)));
+ assertEquals(versionName, cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.VERSION_NAME)));
+ }
+
+}
diff --git a/test/src/org/fdroid/fdroid/FDroidTest.java b/test/src/org/fdroid/fdroid/FDroidTest.java
new file mode 100644
index 000000000..07d9f3ee3
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/FDroidTest.java
@@ -0,0 +1,14 @@
+package org.fdroid.fdroid;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.test.ActivityInstrumentationTestCase2;
+
+@TargetApi(Build.VERSION_CODES.CUPCAKE)
+public class FDroidTest extends ActivityInstrumentationTestCase2 {
+
+ public FDroidTest() {
+ super("org.fdroid.fdroid", FDroid.class);
+ }
+
+}
diff --git a/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java b/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java
new file mode 100644
index 000000000..2a849da7f
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/InstalledAppCacheTest.java
@@ -0,0 +1,179 @@
+package org.fdroid.fdroid;
+
+import android.database.Cursor;
+import android.net.Uri;
+import mock.MockInstallablePackageManager;
+import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
+import org.fdroid.fdroid.data.InstalledAppProvider;
+
+/**
+ * Tests the ability of the {@link org.fdroid.fdroid.data.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.
+ */
+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.APP_ID
+ };
+ }
+
+ public void install(String appId, int versionCode, String versionName) {
+ packageManager.install(appId, versionCode, versionName);
+ }
+
+ public void remove(String appId) {
+ packageManager.remove(appId);
+ }
+
+ 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.APP_ID };
+ 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.APP_ID));
+ 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/test/src/org/fdroid/fdroid/InstalledAppProviderTest.java b/test/src/org/fdroid/fdroid/InstalledAppProviderTest.java
new file mode 100644
index 000000000..14ff885c9
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/InstalledAppProviderTest.java
@@ -0,0 +1,178 @@
+package org.fdroid.fdroid;
+
+import android.content.ContentValues;
+import mock.MockInstallablePackageManager;
+import org.fdroid.fdroid.data.ApkProvider;
+import org.fdroid.fdroid.data.AppProvider;
+import org.fdroid.fdroid.data.InstalledAppProvider;
+import org.fdroid.fdroid.data.RepoProvider;
+
+public class InstalledAppProviderTest extends FDroidProviderTest {
+
+ private MockInstallablePackageManager packageManager;
+
+ public InstalledAppProviderTest() {
+ super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ packageManager = new MockInstallablePackageManager();
+ getSwappableContext().setPackageManager(packageManager);
+ }
+
+ protected MockInstallablePackageManager getPackageManager() {
+ return packageManager;
+ }
+
+ 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 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");
+
+ }
+
+ public void testInsertWithBroadcast() {
+
+ installAndBroadcast("com.example.broadcasted1", 10, "v1.0");
+ installAndBroadcast("com.example.broadcasted2", 105, "v1.05");
+
+ assertResultCount(2, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb("com.example.broadcasted1", 10, "v1.0");
+ assertIsInstalledVersionInDb("com.example.broadcasted2", 105, "v1.05");
+ }
+
+ public void testUpdateWithBroadcast() {
+
+ installAndBroadcast("com.example.toUpgrade", 1, "v0.1");
+
+ assertResultCount(1, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb("com.example.toUpgrade", 1, "v0.1");
+
+ upgradeAndBroadcast("com.example.toUpgrade", 2, "v0.2");
+
+ assertResultCount(1, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb("com.example.toUpgrade", 2, "v0.2");
+
+ }
+
+ public void testDeleteWithBroadcast() {
+
+ installAndBroadcast("com.example.toKeep", 1, "v0.1");
+ installAndBroadcast("com.example.toDelete", 1, "v0.1");
+
+ assertResultCount(2, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
+ assertIsInstalledVersionInDb("com.example.toDelete", 1, "v0.1");
+
+ removeAndBroadcast("com.example.toDelete");
+
+ assertResultCount(1, InstalledAppProvider.getContentUri());
+ assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
+
+ }
+
+ @Override
+ protected String[] getMinimalProjection() {
+ return new String[] {
+ InstalledAppProvider.DataColumns.APP_ID,
+ 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.APP_ID, appId);
+ }
+ values.put(InstalledAppProvider.DataColumns.VERSION_CODE, versionCode);
+ values.put(InstalledAppProvider.DataColumns.VERSION_NAME, versionNumber);
+ return values;
+ }
+
+ private void insertInstalledApp(String appId, int versionCode, String versionNumber) {
+ ContentValues values = createContentValues(appId, versionCode, versionNumber);
+ getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
+ }
+
+ private void removeAndBroadcast(String appId) {
+ TestUtils.removeAndBroadcast(getMockContext(), getPackageManager(), appId);
+ }
+
+ private void upgradeAndBroadcast(String appId, int versionCode, String versionName) {
+ TestUtils.upgradeAndBroadcast(getMockContext(), getPackageManager(), appId, versionCode, versionName);
+ }
+
+ private void installAndBroadcast(String appId, int versionCode, String versionName) {
+ TestUtils.installAndBroadcast(getMockContext(), getPackageManager(), appId, versionCode, versionName);
+ }
+
+}
diff --git a/test/src/org/fdroid/fdroid/TestUtils.java b/test/src/org/fdroid/fdroid/TestUtils.java
new file mode 100644
index 000000000..e4927a3fe
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/TestUtils.java
@@ -0,0 +1,178 @@
+package org.fdroid.fdroid;
+
+import android.content.*;
+import android.net.Uri;
+import junit.framework.AssertionFailedError;
+import mock.MockInstallablePackageManager;
+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[] 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.APP_ID, 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.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();
+
+ return providerTest.getMockContentResolver().insert(uri, values);
+ }
+
+ /**
+ * Will tell {@code pm} that we are installing {@code appId}, and then alert the
+ * {@link org.fdroid.fdroid.PackageAddedReceiver}. This will in turn update the
+ * "installed apps" table in the database.
+ *
+ * Note: in order for this to work, the {@link AppProviderTest#getSwappableContext()}
+ * will need to be aware of the package manager that we have passed in. Therefore,
+ * you will have to have called
+ * {@link mock.MockContextSwappableComponents#setPackageManager(android.content.pm.PackageManager)}
+ * on the {@link AppProviderTest#getSwappableContext()} before invoking this method.
+ */
+ public static void installAndBroadcast(
+ Context context, MockInstallablePackageManager pm,
+ String appId, int versionCode, String versionName) {
+
+ pm.install(appId, versionCode, versionName);
+ Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
+ installIntent.setData(Uri.parse("package:" + appId));
+ new PackageAddedReceiver().onReceive(context, installIntent);
+
+ }
+
+ /**
+ * @see org.fdroid.fdroid.TestUtils#installAndBroadcast(android.content.Context context, mock.MockInstallablePackageManager, String, int, String)
+ */
+ public static void upgradeAndBroadcast(
+ Context context, MockInstallablePackageManager pm,
+ String appId, int versionCode, String versionName) {
+ /*
+ removeAndBroadcast(context, pm, appId);
+ installAndBroadcast(context, pm, appId, versionCode, versionName);
+ */
+ pm.install(appId, versionCode, versionName);
+ Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ installIntent.setData(Uri.parse("package:" + appId));
+ new PackageUpgradedReceiver().onReceive(context, installIntent);
+
+ }
+
+ /**
+ * @see org.fdroid.fdroid.TestUtils#installAndBroadcast(android.content.Context context, mock.MockInstallablePackageManager, String, int, String)
+ */
+ public static void removeAndBroadcast(Context context, MockInstallablePackageManager pm, String appId) {
+
+ pm.remove(appId);
+ Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ installIntent.setData(Uri.parse("package:" + appId));
+ new PackageRemovedReceiver().onReceive(context, installIntent);
+
+ }
+
+}
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;
+ }
+
+}
diff --git a/test/src/org/fdroid/fdroid/mock/MockRepo.java b/test/src/org/fdroid/fdroid/mock/MockRepo.java
new file mode 100644
index 000000000..3b3fce976
--- /dev/null
+++ b/test/src/org/fdroid/fdroid/mock/MockRepo.java
@@ -0,0 +1,11 @@
+package org.fdroid.fdroid.mock;
+
+import org.fdroid.fdroid.data.Repo;
+
+public class MockRepo extends Repo {
+
+ public MockRepo(long repoId) {
+ id = repoId;
+ }
+
+}
diff --git a/tests/gen/org/fdroid/fdroid/tests/BuildConfig.java b/tests/gen/org/fdroid/fdroid/tests/BuildConfig.java
deleted file mode 100644
index 2892c52fb..000000000
--- a/tests/gen/org/fdroid/fdroid/tests/BuildConfig.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/*___Generated_by_IDEA___*/
-
-/** Automatically generated file. DO NOT MODIFY */
-package org.fdroid.fdroid.tests;
-
-public final class BuildConfig {
- public final static boolean DEBUG = true;
-}
\ No newline at end of file
diff --git a/tests/gen/org/fdroid/fdroid/tests/Manifest.java b/tests/gen/org/fdroid/fdroid/tests/Manifest.java
deleted file mode 100644
index 15e60435f..000000000
--- a/tests/gen/org/fdroid/fdroid/tests/Manifest.java
+++ /dev/null
@@ -1,7 +0,0 @@
-/*___Generated_by_IDEA___*/
-
-package org.fdroid.fdroid.tests;
-
-/* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */
-public final class Manifest {
-}
\ No newline at end of file
diff --git a/tests/gen/org/fdroid/fdroid/tests/R.java b/tests/gen/org/fdroid/fdroid/tests/R.java
deleted file mode 100644
index 40f6d3574..000000000
--- a/tests/gen/org/fdroid/fdroid/tests/R.java
+++ /dev/null
@@ -1,7 +0,0 @@
-/*___Generated_by_IDEA___*/
-
-package org.fdroid.fdroid.tests;
-
-/* This stub is for using by IDE only. It is NOT the R class actually packed into APK */
-public final class R {
-}
\ No newline at end of file
diff --git a/tests/local.properties b/tests/local.properties
deleted file mode 100644
index 12a01149a..000000000
--- a/tests/local.properties
+++ /dev/null
@@ -1,10 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must *NOT* be checked into Version Control Systems,
-# as it contains information specific to your local configuration.
-
-# location of the SDK. This is only used by Ant
-# For customization when using a Version Control System, please read the
-# header note.
-sdk.dir=/opt/android-sdk
diff --git a/tools/fix-ellipsis.sh b/tools/fix-ellipsis.sh
index 5a69f89b1..b3f02fb85 100755
--- a/tools/fix-ellipsis.sh
+++ b/tools/fix-ellipsis.sh
@@ -2,4 +2,4 @@
# Fix TypographyEllipsis programmatically
-find res -name strings.xml -type f | xargs -n 1 sed -i 's/\.\.\./…/g'
+sed -i 's/\.\.\./…/g' res/values*/*.xml
diff --git a/tools/fix-formats.sh b/tools/fix-formats.sh
new file mode 100755
index 000000000..972ff6d1f
--- /dev/null
+++ b/tools/fix-formats.sh
@@ -0,0 +1,5 @@
+#!/bin/bash -x
+
+# Fix StringFormatMatches programmatically
+
+sed -i 's/\(%[0-9]\)%\([a-z]\)/\1$\2/g' res/values*/*.xml
diff --git a/tools/remove-unused-trans.sh b/tools/remove-unused-trans.sh
new file mode 100755
index 000000000..574c2d2ab
--- /dev/null
+++ b/tools/remove-unused-trans.sh
@@ -0,0 +1,13 @@
+#!/bin/bash -x
+
+# Remove extra translations
+
+lint . --quiet --check ExtraTranslation --nolines | \
+ sed -n 's@res/values-[^/]\+/\([^\.]\+\)\.xml:.*Error: "\([^"]*\)" is translated here but not found in default locale.*@\1 \2@p' | \
+ while read file name; do
+ if [[ $file == strings ]]; then
+ sed -i "/name=\"$name\"/d" res/values-*/strings.xml
+ elif [[ $file == array ]]; then
+ sed -i "/