Migrated Apk tests to robolectric.

Relatively straightforward port, nothing particularly special here.
This commit is contained in:
Peter Serwylo 2016-06-06 23:50:34 +10:00
parent 4e66bb810f
commit 839ebebd87
12 changed files with 649 additions and 632 deletions

View File

@ -9,7 +9,7 @@ import android.util.Log;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.mock.MockRepo;
import org.fdroid.fdroid.mock.MockRepoOld;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xml.sax.InputSource;
@ -678,7 +678,7 @@ public class RepoXMLHandlerTest {
parser = SAXParserFactory.newInstance().newSAXParser();
XMLReader reader = parser.getXMLReader();
RepoDetails repoDetails = new RepoDetails();
RepoXMLHandler handler = new RepoXMLHandler(new MockRepo(100), repoDetails);
RepoXMLHandler handler = new RepoXMLHandler(new MockRepoOld(100), repoDetails);
reader.setContentHandler(handler);
String resName = "assets/" + indexFilename;
Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(resName));

View File

@ -1,216 +0,0 @@
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.mock.MockApk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class ApkProviderHelperTest extends BaseApkProviderTest {
public void testKnownApks() {
for (int i = 0; i < 7; i++) {
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
}
for (int i = 0; i < 9; i++) {
TestUtils.insertApk(this, "org.example", i);
}
for (int i = 0; i < 3; i++) {
TestUtils.insertApk(this, "com.example", i);
}
TestUtils.insertApk(this, "com.apk.thingo", 1);
Apk[] known = {
new MockApk("org.fdroid.fdroid", 1),
new MockApk("org.fdroid.fdroid", 3),
new MockApk("org.fdroid.fdroid", 5),
new MockApk("com.example", 1),
new MockApk("com.example", 2),
};
Apk[] unknown = {
new MockApk("org.fdroid.fdroid", 7),
new MockApk("org.fdroid.fdroid", 9),
new MockApk("org.fdroid.fdroid", 11),
new MockApk("org.fdroid.fdroid", 13),
new MockApk("com.example", 3),
new MockApk("com.example", 4),
new MockApk("com.example", 5),
new MockApk("info.example", 1),
new MockApk("info.example", 2),
};
List<Apk> apksToCheck = new ArrayList<>(known.length + unknown.length);
Collections.addAll(apksToCheck, known);
Collections.addAll(apksToCheck, unknown);
String[] projection = {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.VERSION_CODE,
};
List<Apk> 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<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(getMockContext(), "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(getMockContext(), "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> thingoApks = ApkProvider.Helper.findByPackageName(getMockContext(), "com.apk.thingo");
assertResultCount(1, thingoApks);
assertBelongsToApp(thingoApks, "com.apk.thingo");
}
public void testUpdate() {
Uri apkUri = TestUtils.insertApk(this, "com.example", 10);
String[] allFields = ApkProvider.DataColumns.ALL;
Cursor cursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
assertResultCount(1, cursor);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
assertEquals("com.example", apk.packageName);
assertEquals(10, apk.versionCode);
assertNull(apk.features);
assertNull(apk.added);
assertNull(apk.hashType);
apk.features = Utils.CommaSeparatedList.make("one,two,three");
long dateTimestamp = System.currentTimeMillis();
apk.added = new Date(dateTimestamp);
apk.hashType = "i'm a hash type";
ApkProvider.Helper.update(getMockContext(), apk);
// Should not have inserted anything else, just updated the already existing apk.
Cursor allCursor = getMockContentResolver().query(ApkProvider.getContentUri(), allFields, null, null, null);
assertResultCount(1, allCursor);
allCursor.close();
Cursor updatedCursor = getMockContentResolver().query(apkUri, allFields, null, null, null);
assertResultCount(1, updatedCursor);
updatedCursor.moveToFirst();
Apk updatedApk = new Apk(updatedCursor);
updatedCursor.close();
assertEquals("com.example", updatedApk.packageName);
assertEquals(10, updatedApk.versionCode);
assertNotNull(updatedApk.features);
assertNotNull(updatedApk.added);
assertNotNull(updatedApk.hashType);
assertEquals("one,two,three", updatedApk.features.toString());
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
assertEquals("i'm a hash type", updatedApk.hashType);
}
public void testFind() {
// Insert some random apks either side of the "com.example", so that
// the Helper.find() method doesn't stumble upon the app we are interested
// in by shear dumb luck...
for (int i = 0; i < 10; i++) {
TestUtils.insertApk(this, "org.fdroid.apk." + i, i);
}
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.VERSION_NAME, "v1.1");
values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy");
values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type");
TestUtils.insertApk(this, "com.example", 11, values);
// ...and a few more for good measure...
for (int i = 15; i < 20; i++) {
TestUtils.insertApk(this, "com.other.thing." + i, i);
}
Apk apk = ApkProvider.Helper.find(getMockContext(), "com.example", 11);
assertNotNull(apk);
// The find() method populates ALL fields if you don't specify any,
// so we expect to find each of the ones we inserted above...
assertEquals("com.example", apk.packageName);
assertEquals(11, apk.versionCode);
assertEquals("v1.1", apk.versionName);
assertEquals("xxxxyyyy", apk.hash);
assertEquals("a hash type", apk.hashType);
String[] projection = {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.HASH,
};
Apk apkLessFields = ApkProvider.Helper.find(getMockContext(), "com.example", 11, projection);
assertNotNull(apkLessFields);
assertEquals("com.example", apkLessFields.packageName);
assertEquals("xxxxyyyy", apkLessFields.hash);
// Didn't ask for these fields, so should be their default values...
assertNull(apkLessFields.hashType);
assertNull(apkLessFields.versionName);
assertEquals(0, apkLessFields.versionCode);
Apk notFound = ApkProvider.Helper.find(getMockContext(), "com.doesnt.exist", 1000);
assertNull(notFound);
}
}

View File

@ -1,334 +0,0 @@
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.TestUtils;
import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockApp;
import org.fdroid.fdroid.mock.MockRepo;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
public class ApkProviderTest extends BaseApkProviderTest {
/**
* I want to test the protected {@link org.fdroid.fdroid.data.ApkProvider#getContentUri(java.util.List)}
* method, but don't want to make it public. This exposes it.
*/
private static class PublicApkProvider extends ApkProvider {
public static final int MAX_APKS_TO_QUERY = ApkProvider.MAX_APKS_TO_QUERY;
public static Uri getContentUri(List<Apk> apks) {
return ApkProvider.getContentUri(apks);
}
}
public void testUris() {
assertInvalidUri(ApkProvider.getAuthority());
assertInvalidUri(RepoProvider.getContentUri());
List<Apk> apks = new ArrayList<>(3);
for (int i = 0; i < 10; i++) {
apks.add(new MockApk("com.example." + i, i));
}
assertValidUri(ApkProvider.getContentUri());
assertValidUri(ApkProvider.getAppUri("org.fdroid.fdroid"));
assertValidUri(ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)));
assertValidUri(ApkProvider.getContentUri());
assertValidUri(PublicApkProvider.getContentUri(apks));
assertValidUri(ApkProvider.getContentUri("org.fdroid.fdroid", 100));
assertValidUri(ApkProvider.getRepoUri(1000));
List<Apk> manyApks = new ArrayList<>(PublicApkProvider.MAX_APKS_TO_QUERY - 5);
for (int i = 0; i < PublicApkProvider.MAX_APKS_TO_QUERY - 1; i++) {
manyApks.add(new MockApk("com.example." + i, i));
}
assertValidUri(PublicApkProvider.getContentUri(manyApks));
manyApks.add(new MockApk("org.fdroid.fdroid.1", 1));
manyApks.add(new MockApk("org.fdroid.fdroid.2", 2));
try {
// Technically, it is a valid URI, because it doesn't
// throw an UnsupportedOperationException. However it
// is still not okay (we run out of bindable parameters
// in the sqlite query.
assertValidUri(PublicApkProvider.getContentUri(manyApks));
fail();
} catch (IllegalArgumentException e) {
// This is the expected error behaviour.
} catch (Exception e) {
fail();
}
}
public void testAppApks() {
for (int i = 1; i <= 10; i++) {
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
TestUtils.insertApk(this, "com.example", i);
}
assertTotalApkCount(20);
Cursor fdroidApks = getMockContentResolver().query(
ApkProvider.getAppUri("org.fdroid.fdroid"),
getMinimalProjection(),
null, null, null);
assertResultCount(10, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
fdroidApks.close();
Cursor exampleApks = getMockContentResolver().query(
ApkProvider.getAppUri("com.example"),
getMinimalProjection(),
null, null, null);
assertResultCount(10, exampleApks);
assertBelongsToApp(exampleApks, "com.example");
exampleApks.close();
ApkProvider.Helper.deleteApksByApp(getMockContext(), new MockApp("com.example"));
Cursor all = queryAllApks();
assertResultCount(10, all);
assertBelongsToApp(all, "org.fdroid.fdroid");
all.close();
}
public void testInvalidUpdateUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
List<Apk> apks = new ArrayList<>();
apks.add(apk);
assertCantUpdate(ApkProvider.getContentUri());
assertCantUpdate(ApkProvider.getAppUri("org.fdroid.fdroid"));
assertCantUpdate(ApkProvider.getRepoUri(1));
assertCantUpdate(PublicApkProvider.getContentUri(apks));
assertCantUpdate(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
// The only valid ones are:
// ApkProvider.getContentUri(apk)
// ApkProvider.getContentUri(id, version)
// which are tested elsewhere.
}
public void testDeleteArbitraryApks() {
Apk one = insertApkForRepo("com.example.one", 1, 10);
Apk two = insertApkForRepo("com.example.two", 1, 10);
Apk three = insertApkForRepo("com.example.three", 1, 10);
Apk four = insertApkForRepo("com.example.four", 1, 10);
Apk five = insertApkForRepo("com.example.five", 1, 10);
assertTotalApkCount(5);
assertEquals("com.example.one", one.packageName);
assertEquals("com.example.two", two.packageName);
assertEquals("com.example.five", five.packageName);
String[] expectedIds = {
"com.example.one",
"com.example.two",
"com.example.three",
"com.example.four",
"com.example.five",
};
List<Apk> all = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualIds = new ArrayList<>();
for (Apk apk : all) {
actualIds.add(apk.packageName);
}
TestUtils.assertContainsOnly(actualIds, expectedIds);
List<Apk> toDelete = new ArrayList<>(3);
toDelete.add(two);
toDelete.add(three);
toDelete.add(four);
ApkProvider.Helper.deleteApks(getSwappableContext(), toDelete);
assertTotalApkCount(2);
List<Apk> allRemaining = ApkProvider.Helper.findByRepo(getSwappableContext(), new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualRemainingIds = new ArrayList<>();
for (Apk apk : allRemaining) {
actualRemainingIds.add(apk.packageName);
}
String[] expectedRemainingIds = {
"com.example.one",
"com.example.five",
};
TestUtils.assertContainsOnly(actualRemainingIds, expectedRemainingIds);
}
public void testInvalidDeleteUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
assertCantDelete(ApkProvider.getContentUri());
assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10));
assertCantDelete(ApkProvider.getContentUri(apk));
assertCantDelete(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
}
private static final long REPO_KEEP = 1;
private static final long REPO_DELETE = 2;
public void testRepoApks() {
// Insert apks into two repos, one of which we will later purge the
// the apks from.
for (int i = 1; i <= 5; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP);
insertApkForRepo("com.example." + i, 1, REPO_DELETE);
}
for (int i = 6; i <= 10; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE);
insertApkForRepo("com.example." + i, 1, REPO_KEEP);
}
assertTotalApkCount(20);
Cursor cursor = getMockContentResolver().query(
ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
assertResultCount(10, cursor);
assertBelongsToRepo(cursor, REPO_DELETE);
cursor.close();
int count = ApkProvider.Helper.deleteApksByRepo(getMockContext(), new MockRepo(REPO_DELETE));
assertEquals(10, count);
assertTotalApkCount(10);
cursor = getMockContentResolver().query(
ApkProvider.getRepoUri(REPO_DELETE), getMinimalProjection(), null, null, null);
assertResultCount(0, cursor);
cursor.close();
// The only remaining apks should be those from REPO_KEEP.
assertBelongsToRepo(queryAllApks(), REPO_KEEP);
}
public void testQuery() {
Cursor cursor = queryAllApks();
assertNotNull(cursor);
cursor.close();
}
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
Apk apk = new MockApk("org.fdroid.fdroid", 13);
// Insert a new record...
Uri newUri = TestUtils.insertApk(this, apk.packageName, apk.versionCode);
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// We intentionally throw an IllegalArgumentException if you haven't
// yet called cursor.move*()...
try {
new Apk(cursor);
fail();
} catch (IllegalArgumentException e) {
// Success!
} catch (Exception e) {
fail();
}
// And now we should be able to recover these values from the apk
// value object (because the queryAllApks() helper asks for VERSION_CODE and
// PACKAGE_NAME.
cursor.moveToFirst();
Apk toCheck = new Apk(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", toCheck.packageName);
assertEquals(13, toCheck.versionCode);
}
public void testCount() {
String[] projectionFields = getMinimalProjection();
String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT};
for (int i = 0; i < 13; i++) {
TestUtils.insertApk(this, "com.example", i);
}
Uri all = ApkProvider.getContentUri();
Cursor allWithFields = getMockContentResolver().query(all, projectionFields, null, null, null);
Cursor allWithCount = getMockContentResolver().query(all, projectionCount, null, null, null);
assertResultCount(13, allWithFields);
allWithFields.close();
assertResultCount(1, allWithCount);
allWithCount.moveToFirst();
int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
assertEquals(13, allWithCount.getInt(countColumn));
allWithCount.close();
}
public void testInsertWithExtraFields() {
assertResultCount(0, queryAllApks());
String[] repoFields = new String[] {
RepoProvider.DataColumns.DESCRIPTION,
RepoProvider.DataColumns.ADDRESS,
RepoProvider.DataColumns.FINGERPRINT,
RepoProvider.DataColumns.NAME,
RepoProvider.DataColumns.SIGNING_CERT,
};
for (String field : repoFields) {
ContentValues invalidRepo = new ContentValues();
invalidRepo.put(field, "Test data");
try {
TestUtils.insertApk(this, "org.fdroid.fdroid", 10, invalidRepo);
fail();
} catch (IllegalArgumentException e) {
} catch (Exception e) {
fail();
}
assertResultCount(0, queryAllApks());
}
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.REPO_ID, 10);
values.put(ApkProvider.DataColumns.REPO_ADDRESS, "http://example.com");
values.put(ApkProvider.DataColumns.REPO_VERSION, 3);
values.put(ApkProvider.DataColumns.FEATURES, "Some features");
Uri uri = TestUtils.insertApk(this, "com.example.com", 1, values);
assertResultCount(1, queryAllApks());
String[] projections = ApkProvider.DataColumns.ALL;
Cursor cursor = getMockContentResolver().query(uri, projections, null, null, null);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
// These should have quietly been dropped when we tried to save them,
// because the provider only knows how to query them (not update them).
assertEquals(null, apk.repoAddress);
assertEquals(0, apk.repoVersion);
// But this should have saved correctly...
assertEquals("Some features", apk.features.toString());
assertEquals("com.example.com", apk.packageName);
assertEquals(1, apk.versionCode);
assertEquals(10, apk.repo);
}
}

View File

@ -1,78 +0,0 @@
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.TestUtils;
import java.util.List;
/**
* Provides helper methods that can be used by both Helper and plain old
* Provider tests. Allows the test classes to contain only test methods,
* hopefully making them easier to understand.
*
* This should not contain any test methods, or else they get executed
* once for every concrete subclass.
*/
abstract class BaseApkProviderTest extends FDroidProviderTestOld<ApkProvider> {
BaseApkProviderTest() {
super(ApkProvider.class, ApkProvider.getAuthority());
}
@Override
protected String[] getMinimalProjection() {
return new String[] {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.VERSION_CODE,
ApkProvider.DataColumns.NAME,
ApkProvider.DataColumns.REPO_ID,
};
}
protected final Cursor queryAllApks() {
return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null);
}
protected void assertContains(List<Apk> apks, Apk apk) {
boolean found = false;
for (Apk a : apks) {
if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) {
found = true;
break;
}
}
if (!found) {
fail("Apk [" + apk + "] not found in " + TestUtils.listToString(apks));
}
}
protected void assertBelongsToApp(Cursor apks, String appId) {
assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId);
}
protected void assertBelongsToApp(List<Apk> apks, String appId) {
for (Apk apk : apks) {
assertEquals(appId, apk.packageName);
}
}
protected void assertTotalApkCount(int expected) {
assertResultCount(expected, queryAllApks());
}
protected void assertBelongsToRepo(Cursor apkCursor, long repoId) {
for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
assertEquals(repoId, apk.repo);
}
}
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
Uri uri = TestUtils.insertApk(this, id, versionCode, additionalValues);
return ApkProvider.Helper.get(getSwappableContext(), uri);
}
}

View File

@ -0,0 +1,11 @@
package org.fdroid.fdroid.mock;
import org.fdroid.fdroid.data.Repo;
public class MockRepoOld extends Repo {
public MockRepoOld(long repoId) {
id = repoId;
}
}

View File

@ -26,7 +26,7 @@ public class ApkProvider extends FDroidProvider {
* we may want to add additional constraints, so we give our self some
* room by saying only 450 apks can be queried at once.
*/
protected static final int MAX_APKS_TO_QUERY = 450;
static final int MAX_APKS_TO_QUERY = 450;
public static final class Helper {
@ -319,7 +319,7 @@ public class ApkProvider extends FDroidProvider {
* this directly, think about using
* {@link org.fdroid.fdroid.data.ApkProvider.Helper#knownApks(android.content.Context, java.util.List, String[])}
*/
protected static Uri getContentUri(List<Apk> apks) {
static Uri getContentUri(List<Apk> apks) {
return getContentUri().buildUpon()
.appendPath(PATH_APKS)
.appendPath(buildApkString(apks))

View File

@ -0,0 +1,535 @@
package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.mock.MockApk;
import org.fdroid.fdroid.mock.MockApp;
import org.fdroid.fdroid.mock.MockRepo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import static org.fdroid.fdroid.data.ProviderTestUtils.assertCantDelete;
import static org.fdroid.fdroid.data.ProviderTestUtils.assertContainsOnly;
import static org.fdroid.fdroid.data.ProviderTestUtils.assertResultCount;
import static org.fdroid.fdroid.data.ProviderTestUtils.insertApk;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
@Config(constants = BuildConfig.class, application = Application.class)
@RunWith(RobolectricGradleTestRunner.class)
public class ApkProviderTest extends FDroidProviderTest {
private static final String[] PROJ = ApkProvider.DataColumns.ALL;
@Test
public void testAppApks() {
for (int i = 1; i <= 10; i++) {
insertApk(contentResolver, "org.fdroid.fdroid", i);
insertApk(contentResolver, "com.example", i);
}
assertTotalApkCount(20);
Cursor fdroidApks = contentResolver.query(
ApkProvider.getAppUri("org.fdroid.fdroid"),
PROJ,
null, null, null);
assertResultCount(10, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
fdroidApks.close();
Cursor exampleApks = contentResolver.query(
ApkProvider.getAppUri("com.example"),
PROJ,
null, null, null);
assertResultCount(10, exampleApks);
assertBelongsToApp(exampleApks, "com.example");
exampleApks.close();
ApkProvider.Helper.deleteApksByApp(context, new MockApp("com.example"));
Cursor all = queryAllApks();
assertResultCount(10, all);
assertBelongsToApp(all, "org.fdroid.fdroid");
all.close();
}
@Test
public void testDeleteArbitraryApks() {
Apk one = insertApkForRepo("com.example.one", 1, 10);
Apk two = insertApkForRepo("com.example.two", 1, 10);
Apk three = insertApkForRepo("com.example.three", 1, 10);
Apk four = insertApkForRepo("com.example.four", 1, 10);
Apk five = insertApkForRepo("com.example.five", 1, 10);
assertTotalApkCount(5);
assertEquals("com.example.one", one.packageName);
assertEquals("com.example.two", two.packageName);
assertEquals("com.example.five", five.packageName);
String[] expectedIds = {
"com.example.one",
"com.example.two",
"com.example.three",
"com.example.four",
"com.example.five",
};
List<Apk> all = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualIds = new ArrayList<>();
for (Apk apk : all) {
actualIds.add(apk.packageName);
}
assertContainsOnly(actualIds, expectedIds);
List<Apk> toDelete = new ArrayList<>(3);
toDelete.add(two);
toDelete.add(three);
toDelete.add(four);
ApkProvider.Helper.deleteApks(context, toDelete);
assertTotalApkCount(2);
List<Apk> allRemaining = ApkProvider.Helper.findByRepo(context, new MockRepo(10), ApkProvider.DataColumns.ALL);
List<String> actualRemainingIds = new ArrayList<>();
for (Apk apk : allRemaining) {
actualRemainingIds.add(apk.packageName);
}
String[] expectedRemainingIds = {
"com.example.one",
"com.example.five",
};
assertContainsOnly(actualRemainingIds, expectedRemainingIds);
}
@Test
public void testInvalidDeleteUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
assertCantDelete(contentResolver, ApkProvider.getContentUri());
assertCantDelete(contentResolver, ApkProvider.getContentUri("org.fdroid.fdroid", 10));
assertCantDelete(contentResolver, ApkProvider.getContentUri(apk));
assertCantDelete(contentResolver, Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
}
private static final long REPO_KEEP = 1;
private static final long REPO_DELETE = 2;
@Test
public void testRepoApks() {
// Insert apks into two repos, one of which we will later purge the
// the apks from.
for (int i = 1; i <= 5; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_KEEP);
insertApkForRepo("com.example." + i, 1, REPO_DELETE);
}
for (int i = 6; i <= 10; i++) {
insertApkForRepo("org.fdroid.fdroid", i, REPO_DELETE);
insertApkForRepo("com.example." + i, 1, REPO_KEEP);
}
assertTotalApkCount(20);
Cursor cursor = contentResolver.query(
ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null);
assertResultCount(10, cursor);
assertBelongsToRepo(cursor, REPO_DELETE);
cursor.close();
int count = ApkProvider.Helper.deleteApksByRepo(context, new MockRepo(REPO_DELETE));
assertEquals(10, count);
assertTotalApkCount(10);
cursor = contentResolver.query(
ApkProvider.getRepoUri(REPO_DELETE), PROJ, null, null, null);
assertResultCount(0, cursor);
cursor.close();
// The only remaining apks should be those from REPO_KEEP.
assertBelongsToRepo(queryAllApks(), REPO_KEEP);
}
@Test
public void testQuery() {
Cursor cursor = queryAllApks();
assertNotNull(cursor);
cursor.close();
}
@Test
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
Apk apk = new MockApk("org.fdroid.fdroid", 13);
// Insert a new record...
Uri newUri = insertApk(contentResolver, apk.packageName, apk.versionCode);
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
cursor = queryAllApks();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// We intentionally throw an IllegalArgumentException if you haven't
// yet called cursor.move*()...
try {
new Apk(cursor);
fail();
} catch (IllegalArgumentException e) {
// Success!
} catch (Exception e) {
fail();
}
// And now we should be able to recover these values from the apk
// value object (because the queryAllApks() helper asks for VERSION_CODE and
// PACKAGE_NAME.
cursor.moveToFirst();
Apk toCheck = new Apk(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", toCheck.packageName);
assertEquals(13, toCheck.versionCode);
}
@Test
public void testCount() {
String[] projectionCount = new String[] {ApkProvider.DataColumns._COUNT};
for (int i = 0; i < 13; i++) {
insertApk(contentResolver, "com.example", i);
}
Uri all = ApkProvider.getContentUri();
Cursor allWithFields = contentResolver.query(all, PROJ, null, null, null);
Cursor allWithCount = contentResolver.query(all, projectionCount, null, null, null);
assertResultCount(13, allWithFields);
allWithFields.close();
assertResultCount(1, allWithCount);
allWithCount.moveToFirst();
int countColumn = allWithCount.getColumnIndex(ApkProvider.DataColumns._COUNT);
assertEquals(13, allWithCount.getInt(countColumn));
allWithCount.close();
}
@Test
public void testInsertWithExtraFields() {
assertResultCount(0, queryAllApks());
String[] repoFields = new String[] {
RepoProvider.DataColumns.DESCRIPTION,
RepoProvider.DataColumns.ADDRESS,
RepoProvider.DataColumns.FINGERPRINT,
RepoProvider.DataColumns.NAME,
RepoProvider.DataColumns.SIGNING_CERT,
};
for (String field : repoFields) {
ContentValues invalidRepo = new ContentValues();
invalidRepo.put(field, "Test data");
try {
insertApk(contentResolver, "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 = insertApk(contentResolver, "com.example.com", 1, values);
assertResultCount(1, queryAllApks());
String[] projections = ApkProvider.DataColumns.ALL;
Cursor cursor = contentResolver.query(uri, projections, null, null, null);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
// These should have quietly been dropped when we tried to save them,
// because the provider only knows how to query them (not update them).
assertEquals(null, apk.repoAddress);
assertEquals(0, apk.repoVersion);
// But this should have saved correctly...
assertEquals("Some features", apk.features.toString());
assertEquals("com.example.com", apk.packageName);
assertEquals(1, apk.versionCode);
assertEquals(10, apk.repo);
}
@Test
public void testKnownApks() {
for (int i = 0; i < 7; i++) {
insertApk(contentResolver, "org.fdroid.fdroid", i);
}
for (int i = 0; i < 9; i++) {
insertApk(contentResolver, "org.example", i);
}
for (int i = 0; i < 3; i++) {
insertApk(contentResolver, "com.example", i);
}
insertApk(contentResolver, "com.apk.thingo", 1);
Apk[] known = {
new MockApk("org.fdroid.fdroid", 1),
new MockApk("org.fdroid.fdroid", 3),
new MockApk("org.fdroid.fdroid", 5),
new MockApk("com.example", 1),
new MockApk("com.example", 2),
};
Apk[] unknown = {
new MockApk("org.fdroid.fdroid", 7),
new MockApk("org.fdroid.fdroid", 9),
new MockApk("org.fdroid.fdroid", 11),
new MockApk("org.fdroid.fdroid", 13),
new MockApk("com.example", 3),
new MockApk("com.example", 4),
new MockApk("com.example", 5),
new MockApk("info.example", 1),
new MockApk("info.example", 2),
};
List<Apk> apksToCheck = new ArrayList<>(known.length + unknown.length);
Collections.addAll(apksToCheck, known);
Collections.addAll(apksToCheck, unknown);
String[] projection = {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.VERSION_CODE,
};
List<Apk> knownApks = ApkProvider.Helper.knownApks(context, apksToCheck, projection);
assertResultCount(known.length, knownApks);
for (Apk knownApk : knownApks) {
assertContains(knownApks, knownApk);
}
}
@Test
public void testFindByApp() {
for (int i = 0; i < 7; i++) {
insertApk(contentResolver, "org.fdroid.fdroid", i);
}
for (int i = 0; i < 9; i++) {
insertApk(contentResolver, "org.example", i);
}
for (int i = 0; i < 3; i++) {
insertApk(contentResolver, "com.example", i);
}
insertApk(contentResolver, "com.apk.thingo", 1);
assertTotalApkCount(7 + 9 + 3 + 1);
List<Apk> fdroidApks = ApkProvider.Helper.findByPackageName(context, "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByPackageName(context, "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByPackageName(context, "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> thingoApks = ApkProvider.Helper.findByPackageName(context, "com.apk.thingo");
assertResultCount(1, thingoApks);
assertBelongsToApp(thingoApks, "com.apk.thingo");
}
@Test
public void testUpdate() {
Uri apkUri = insertApk(contentResolver, "com.example", 10);
String[] allFields = ApkProvider.DataColumns.ALL;
Cursor cursor = contentResolver.query(apkUri, allFields, null, null, null);
assertResultCount(1, cursor);
cursor.moveToFirst();
Apk apk = new Apk(cursor);
cursor.close();
assertEquals("com.example", apk.packageName);
assertEquals(10, apk.versionCode);
assertNull(apk.features);
assertNull(apk.added);
assertNull(apk.hashType);
apk.features = Utils.CommaSeparatedList.make("one,two,three");
long dateTimestamp = System.currentTimeMillis();
apk.added = new Date(dateTimestamp);
apk.hashType = "i'm a hash type";
ApkProvider.Helper.update(context, apk);
// Should not have inserted anything else, just updated the already existing apk.
Cursor allCursor = contentResolver.query(ApkProvider.getContentUri(), allFields, null, null, null);
assertResultCount(1, allCursor);
allCursor.close();
Cursor updatedCursor = contentResolver.query(apkUri, allFields, null, null, null);
assertResultCount(1, updatedCursor);
updatedCursor.moveToFirst();
Apk updatedApk = new Apk(updatedCursor);
updatedCursor.close();
assertEquals("com.example", updatedApk.packageName);
assertEquals(10, updatedApk.versionCode);
assertNotNull(updatedApk.features);
assertNotNull(updatedApk.added);
assertNotNull(updatedApk.hashType);
assertEquals("one,two,three", updatedApk.features.toString());
assertEquals(new Date(dateTimestamp).getYear(), updatedApk.added.getYear());
assertEquals(new Date(dateTimestamp).getMonth(), updatedApk.added.getMonth());
assertEquals(new Date(dateTimestamp).getDay(), updatedApk.added.getDay());
assertEquals("i'm a hash type", updatedApk.hashType);
}
@Test
public void testFind() {
// Insert some random apks either side of the "com.example", so that
// the Helper.find() method doesn't stumble upon the app we are interested
// in by shear dumb luck...
for (int i = 0; i < 10; i++) {
insertApk(contentResolver, "org.fdroid.apk." + i, i);
}
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.VERSION_NAME, "v1.1");
values.put(ApkProvider.DataColumns.HASH, "xxxxyyyy");
values.put(ApkProvider.DataColumns.HASH_TYPE, "a hash type");
insertApk(contentResolver, "com.example", 11, values);
// ...and a few more for good measure...
for (int i = 15; i < 20; i++) {
insertApk(contentResolver, "com.other.thing." + i, i);
}
Apk apk = ApkProvider.Helper.find(context, "com.example", 11);
assertNotNull(apk);
// The find() method populates ALL fields if you don't specify any,
// so we expect to find each of the ones we inserted above...
assertEquals("com.example", apk.packageName);
assertEquals(11, apk.versionCode);
assertEquals("v1.1", apk.versionName);
assertEquals("xxxxyyyy", apk.hash);
assertEquals("a hash type", apk.hashType);
String[] projection = {
ApkProvider.DataColumns.PACKAGE_NAME,
ApkProvider.DataColumns.HASH,
};
Apk apkLessFields = ApkProvider.Helper.find(context, "com.example", 11, projection);
assertNotNull(apkLessFields);
assertEquals("com.example", apkLessFields.packageName);
assertEquals("xxxxyyyy", apkLessFields.hash);
// Didn't ask for these fields, so should be their default values...
assertNull(apkLessFields.hashType);
assertNull(apkLessFields.versionName);
assertEquals(0, apkLessFields.versionCode);
Apk notFound = ApkProvider.Helper.find(context, "com.doesnt.exist", 1000);
assertNull(notFound);
}
protected final Cursor queryAllApks() {
return contentResolver.query(ApkProvider.getContentUri(), PROJ, null, null, null);
}
protected void assertContains(List<Apk> apks, Apk apk) {
boolean found = false;
for (Apk a : apks) {
if (a.versionCode == apk.versionCode && a.packageName.equals(apk.packageName)) {
found = true;
break;
}
}
if (!found) {
fail("Apk [" + apk + "] not found in " + ProviderTestUtils.listToString(apks));
}
}
protected void assertBelongsToApp(Cursor apks, String appId) {
assertBelongsToApp(ApkProvider.Helper.cursorToList(apks), appId);
}
protected void assertBelongsToApp(List<Apk> apks, String appId) {
for (Apk apk : apks) {
assertEquals(appId, apk.packageName);
}
}
protected void assertTotalApkCount(int expected) {
assertResultCount(expected, queryAllApks());
}
protected void assertBelongsToRepo(Cursor apkCursor, long repoId) {
for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
assertEquals(repoId, apk.repo);
}
}
protected Apk insertApkForRepo(String id, int versionCode, long repoId) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
Uri uri = insertApk(contentResolver, id, versionCode, additionalValues);
return ApkProvider.Helper.get(context, uri);
}
}

View File

@ -168,4 +168,55 @@ public class ProviderTestUtils {
cursor.close();
}
public static void insertApp(ShadowContentResolver resolver, String appId, String name) {
insertApp(resolver, appId, name, new ContentValues());
}
public static void insertApp(ShadowContentResolver resolver, String id, String name, ContentValues additionalValues) {
ContentValues values = new ContentValues();
values.put(AppProvider.DataColumns.PACKAGE_NAME, id);
values.put(AppProvider.DataColumns.NAME, name);
// Required fields (NOT NULL in the database).
values.put(AppProvider.DataColumns.SUMMARY, "test summary");
values.put(AppProvider.DataColumns.DESCRIPTION, "test description");
values.put(AppProvider.DataColumns.LICENSE, "GPL?");
values.put(AppProvider.DataColumns.IS_COMPATIBLE, 1);
values.put(AppProvider.DataColumns.IGNORE_ALLUPDATES, 0);
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, 0);
values.putAll(additionalValues);
Uri uri = AppProvider.getContentUri();
resolver.insert(uri, values);
}
public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode) {
return insertApk(resolver, id, versionCode, new ContentValues());
}
public static Uri insertApk(ShadowContentResolver resolver, String id, int versionCode, ContentValues additionalValues) {
ContentValues values = new ContentValues();
values.put(ApkProvider.DataColumns.PACKAGE_NAME, id);
values.put(ApkProvider.DataColumns.VERSION_CODE, versionCode);
// Required fields (NOT NULL in the database).
values.put(ApkProvider.DataColumns.REPO_ID, 1);
values.put(ApkProvider.DataColumns.VERSION_NAME, "The good one");
values.put(ApkProvider.DataColumns.HASH, "11111111aaaaaaaa");
values.put(ApkProvider.DataColumns.NAME, "Test Apk");
values.put(ApkProvider.DataColumns.SIZE, 10000);
values.put(ApkProvider.DataColumns.IS_COMPATIBLE, 1);
values.putAll(additionalValues);
Uri uri = ApkProvider.getContentUri();
return resolver.insert(uri, values);
}
}

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.data;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.mock.MockApk;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -16,6 +17,7 @@ import java.util.List;
import static org.fdroid.fdroid.data.ProviderTestUtils.assertInvalidUri;
import static org.fdroid.fdroid.data.ProviderTestUtils.assertValidUri;
import static org.junit.Assert.fail;
@Config(constants = BuildConfig.class)
@RunWith(RobolectricGradleTestRunner.class)
@ -99,4 +101,50 @@ public class ProviderUriTests {
assertValidUri(resolver, AppProvider.getContentUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.AppProvider/org.fdroid.fdroid", projection);
}
@Test
public void invalidApkProviderUris() {
ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider());
assertInvalidUri(resolver, ApkProvider.getAuthority());
assertInvalidUri(resolver, "blah");
}
@Test
public void validApkProviderUris() {
ShadowContentResolver.registerProvider(ApkProvider.getAuthority(), new ApkProvider());
String[] projection = new String[] { ApkProvider.DataColumns._ID };
List<Apk> apks = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
apks.add(new MockApk("com.example." + i, i));
}
assertValidUri(resolver, ApkProvider.getContentUri(), "content://org.fdroid.fdroid.data.ApkProvider", projection);
assertValidUri(resolver, ApkProvider.getAppUri("org.fdroid.fdroid"), "content://org.fdroid.fdroid.data.ApkProvider/app/org.fdroid.fdroid", projection);
assertValidUri(resolver, ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 100)), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection);
assertValidUri(resolver, ApkProvider.getContentUri(apks), projection);
assertValidUri(resolver, ApkProvider.getContentUri("org.fdroid.fdroid", 100), "content://org.fdroid.fdroid.data.ApkProvider/apk/100/org.fdroid.fdroid", projection);
assertValidUri(resolver, ApkProvider.getRepoUri(1000), "content://org.fdroid.fdroid.data.ApkProvider/repo/1000", projection);
List<Apk> manyApks = new ArrayList<>(ApkProvider.MAX_APKS_TO_QUERY - 5);
for (int i = 0; i < ApkProvider.MAX_APKS_TO_QUERY - 1; i++) {
manyApks.add(new MockApk("com.example." + i, i));
}
assertValidUri(resolver, ApkProvider.getContentUri(manyApks), projection);
manyApks.add(new MockApk("org.fdroid.fdroid.1", 1));
manyApks.add(new MockApk("org.fdroid.fdroid.2", 2));
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(resolver, ApkProvider.getContentUri(manyApks), projection);
fail();
} catch (IllegalArgumentException e) {
// This is the expected error behaviour.
} catch (Exception e) {
fail();
}
}
}