Almost 100% test coverage of ApkProvider and ApkProvider.Helper

Removed unused code from ApkProvider.Helper, made it throw proper
exceptions when trying unsupported operations. Refactored tests
a little bit to facilitate separate test cases for the provider
and its helper.
This commit is contained in:
Peter Serwylo 2014-02-20 07:16:09 +11:00
parent 51a2159559
commit 2dcd87cd41
6 changed files with 369 additions and 139 deletions

View File

@ -25,45 +25,12 @@ public class ApkProvider extends FDroidProvider {
private Helper() {}
public static void update(Context context, Apk apk,
String id, int versionCode) {
ContentResolver resolver = context.getContentResolver();
Uri uri = getContentUri(id, versionCode);
resolver.update(uri, apk.toContentValues(), null, null);
}
public static void update(Context context, Apk apk) {
ContentResolver resolver = context.getContentResolver();
Uri uri = getContentUri(apk.id, apk.vercode);
resolver.update(uri, apk.toContentValues(), null, null);
}
/**
* This doesn't do anything other than call "insert" on the content
* resolver, but I thought I'd put it here in the interests of having
* each of the CRUD methods available in the helper class.
*/
public static void insert(Context context, ContentValues values) {
ContentResolver resolver = context.getContentResolver();
resolver.insert(getContentUri(), values);
}
public static void insert(Context context, Apk apk) {
insert(context, apk.toContentValues());
}
public static List<Apk> all(Context context) {
return all(context, DataColumns.ALL);
}
public static List<Apk> all(Context context, String[] projection) {
ContentResolver resolver = context.getContentResolver();
Uri uri = ApkProvider.getContentUri();
Cursor cursor = resolver.query(uri, projection, null, null, null);
return cursorToList(cursor);
}
public static List<Apk> cursorToList(Cursor cursor) {
List<Apk> apks = new ArrayList<Apk>();
if (cursor != null) {
@ -105,12 +72,6 @@ public class ApkProvider extends FDroidProvider {
}
}
public static void delete(Context context, String id, int versionCode) {
ContentResolver resolver = context.getContentResolver();
Uri uri = getContentUri(id, versionCode);
resolver.delete(uri, null, null);
}
public static List<Apk> findByApp(Context context, String appId) {
return findByApp(context, appId, ApkProvider.DataColumns.ALL);
}
@ -139,7 +100,7 @@ public class ApkProvider extends FDroidProvider {
public interface DataColumns extends BaseColumns {
public static String APK_ID = "id";
public static String APK_ID = "id";
public static String VERSION = "version";
public static String REPO_ID = "repo";
public static String HASH = "hash";
@ -273,8 +234,8 @@ public class ApkProvider extends FDroidProvider {
addRepoField(REPO_FIELDS.get(field), field);
} else if (field.equals(DataColumns._ID)) {
appendField("rowid", "apk", "_id");
} else if (field.startsWith("COUNT")) {
appendField(field);
} else if (field.equals(DataColumns._COUNT)) {
appendField("COUNT(*) AS " + DataColumns._COUNT);
} else {
appendField(field, "apk");
}
@ -496,20 +457,16 @@ public class ApkProvider extends FDroidProvider {
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
QuerySelection query = new QuerySelection(where, whereArgs);
validateFields(DataColumns.ALL, values);
switch (matcher.match(uri)) {
case CODE_LIST:
return 0;
case CODE_SINGLE:
query = query.add(querySingle(uri));
break;
if (matcher.match(uri) != CODE_SINGLE) {
throw new UnsupportedOperationException("Cannot update anything other than a single apk.");
}
validateFields(DataColumns.ALL, values);
removeRepoFields(values);
QuerySelection query = new QuerySelection(where, whereArgs);
query = query.add(querySingle(uri));
int numRows = write().update(getTableName(), values, query.getSelection(), query.getArgs());
if (!isApplyingBatch()) {
getContext().getContentResolver().notifyChange(uri, null);

View File

@ -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<Apk> apksToCheck = new ArrayList<Apk>(known.length + unknown.length);
Collections.addAll(apksToCheck, known);
Collections.addAll(apksToCheck, unknown);
String[] projection = {
ApkProvider.DataColumns.APK_ID,
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.findByApp(getMockContext(), "org.fdroid.fdroid");
assertResultCount(7, fdroidApks);
assertBelongsToApp(fdroidApks, "org.fdroid.fdroid");
List<Apk> exampleApks = ApkProvider.Helper.findByApp(getMockContext(), "org.example");
assertResultCount(9, exampleApks);
assertBelongsToApp(exampleApks, "org.example");
List<Apk> exampleApks2 = ApkProvider.Helper.findByApp(getMockContext(), "com.example");
assertResultCount(3, exampleApks2);
assertBelongsToApp(exampleApks2, "com.example");
List<Apk> 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);
}
}

View File

@ -5,7 +5,6 @@ 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;
@ -14,20 +13,7 @@ import org.fdroid.fdroid.mock.MockRepo;
import java.util.ArrayList;
import java.util.List;
public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
public ApkProviderTest() {
super(ApkProvider.class, ApkProvider.getAuthority());
}
protected String[] getMinimalProjection() {
return new String[] {
ApkProvider.DataColumns.APK_ID,
ApkProvider.DataColumns.VERSION_CODE,
ApkProvider.DataColumns.NAME,
ApkProvider.DataColumns.REPO_ID
};
}
public class ApkProviderTest extends BaseApkProviderTest {
public void testUris() {
assertInvalidUri(ApkProvider.getAuthority());
@ -70,8 +56,8 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
public void testAppApks() {
for (int i = 1; i <= 10; i ++) {
insertApk("org.fdroid.fdroid", i);
insertApk("com.example", i);
TestUtils.insertApk(this, "org.fdroid.fdroid", i);
TestUtils.insertApk(this, "com.example", i);
}
assertTotalApkCount(20);
@ -97,20 +83,35 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
assertBelongsToApp(all, "org.fdroid.fdroid");
}
public void testInvalidDeleteUris() {
assertCantDelete(ApkProvider.getContentUri());
assertCantDelete(ApkProvider.getContentUri(new ArrayList<Apk>()));
assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10));
assertCantDelete(ApkProvider.getContentUri(new MockApk("org.fdroid.fdroid", 10)));
public void testInvalidUpdateUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
try {
getMockContentResolver().delete(RepoProvider.getContentUri(), null, null);
fail();
} catch (IllegalArgumentException e) {
// Don't fail, it is what we were looking for...
} catch (Exception e) {
fail();
}
List<Apk> apks = new ArrayList<Apk>();
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 testInvalidDeleteUris() {
Apk apk = new MockApk("org.fdroid.fdroid", 10);
List<Apk> apks = new ArrayList<Apk>();
apks.add(apk);
assertCantDelete(ApkProvider.getContentUri());
assertCantDelete(ApkProvider.getContentUri(apks));
assertCantDelete(ApkProvider.getContentUri("org.fdroid.fdroid", 10));
assertCantDelete(ApkProvider.getContentUri(apk));
assertCantDelete(Uri.withAppendedPath(ApkProvider.getContentUri(), "some-random-path"));
}
public void testRepoApks() {
@ -163,7 +164,7 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
Apk apk = new MockApk("org.fdroid.fdroid", 13);
// Insert a new record...
Uri newUri = insertApk(apk.id, apk.vercode);
Uri newUri = TestUtils.insertApk(this, apk.id, apk.vercode);
assertEquals(ApkProvider.getContentUri(apk).toString(), newUri.toString());
cursor = queryAllApks();
assertNotNull(cursor);
@ -189,6 +190,26 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
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());
@ -205,7 +226,7 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
ContentValues invalidRepo = new ContentValues();
invalidRepo.put(field, "Test data");
try {
insertApk("org.fdroid.fdroid", 10, invalidRepo);
TestUtils.insertApk(this, "org.fdroid.fdroid", 10, invalidRepo);
fail();
} catch (IllegalArgumentException e) {
} catch (Exception e) {
@ -219,24 +240,17 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
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("com.example.com", 1, values);
Uri uri = TestUtils.insertApk(this, "com.example.com", 1, values);
assertResultCount(1, queryAllApks());
String[] projections = {
ApkProvider.DataColumns.REPO_ID,
ApkProvider.DataColumns.REPO_ADDRESS,
ApkProvider.DataColumns.REPO_VERSION,
ApkProvider.DataColumns.FEATURES,
ApkProvider.DataColumns.APK_ID,
ApkProvider.DataColumns.VERSION_CODE
};
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...
// 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);
@ -247,45 +261,4 @@ public class ApkProviderTest extends FDroidProviderTest<ApkProvider> {
assertEquals(10, apk.repo);
}
public void testIgnore() {
/*for (int i = 0; i < 10; i ++) {
insertApk("org.fdroid.fdroid", i);
}*/
}
private void assertBelongsToApp(Cursor apks, String appId) {
for (Apk apk : ApkProvider.Helper.cursorToList(apks)) {
assertEquals(appId, apk.id);
}
}
private void assertTotalApkCount(int expected) {
assertResultCount(expected, queryAllApks());
}
private void assertBelongsToRepo(Cursor apkCursor, long repoId) {
for (Apk apk : ApkProvider.Helper.cursorToList(apkCursor)) {
assertEquals(repoId, apk.repo);
}
}
private void insertApkForRepo(String id, int versionCode, long repoId) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
insertApk(id, versionCode, additionalValues);
}
private Cursor queryAllApks() {
return getMockContentResolver().query(ApkProvider.getContentUri(), getMinimalProjection(), null, null, null);
}
private Uri insertApk(String id, int versionCode) {
return insertApk(id, versionCode, new ContentValues());
}
private Uri insertApk(String id, int versionCode,
ContentValues additionalValues) {
return TestUtils.insertApk(getMockContentResolver(), id, versionCode, additionalValues);
}
}

View File

@ -0,0 +1,76 @@
package org.fdroid.fdroid;
import android.content.ContentValues;
import android.database.Cursor;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider;
import 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<ApkProvider> {
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<Apk> 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<Apk> 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 void insertApkForRepo(String id, int versionCode, long repoId) {
ContentValues additionalValues = new ContentValues();
additionalValues.put(ApkProvider.DataColumns.REPO_ID, repoId);
TestUtils.insertApk(this, id, versionCode, additionalValues);
}
}

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid;
import android.annotation.TargetApi;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
@ -12,6 +13,8 @@ import mock.MockContextSwappableComponents;
import org.fdroid.fdroid.data.FDroidProvider;
import org.fdroid.fdroid.mock.MockInstalledApkCache;
import java.util.List;
public abstract class FDroidProviderTest<T extends FDroidProvider> extends ProviderTestCase2MockContext<T> {
private MockContextSwappableComponents swappableContext;
@ -60,6 +63,16 @@ public abstract class FDroidProviderTest<T extends FDroidProvider> extends Provi
}
}
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));
}
@ -96,6 +109,11 @@ public abstract class FDroidProviderTest<T extends FDroidProvider> extends Provi
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());

View File

@ -82,7 +82,11 @@ public class TestUtils {
resolver.insert(uri, values);
}
public static Uri insertApk(ContentResolver resolver, String id, int versionCode, ContentValues additionalValues) {
public static Uri insertApk(FDroidProviderTest<ApkProvider> providerTest, String id, int versionCode) {
return insertApk(providerTest, id, versionCode, new ContentValues());
}
public static Uri insertApk(FDroidProviderTest<ApkProvider> providerTest, String id, int versionCode, ContentValues additionalValues) {
ContentValues values = new ContentValues();
@ -101,6 +105,6 @@ public class TestUtils {
Uri uri = ApkProvider.getContentUri();
return resolver.insert(uri, values);
return providerTest.getMockContentResolver().insert(uri, values);
}
}