package org.fdroid.fdroid.data;
import android.app.Application;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.ArrayList;
import java.util.List;
import static org.fdroid.fdroid.Assert.assertContainsOnly;
import static org.fdroid.fdroid.Assert.assertResultCount;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
// TODO: Use sdk=24 when Robolectric supports this
@Config(constants = BuildConfig.class, application = Application.class, sdk = 23)
@RunWith(RobolectricGradleTestRunner.class)
public class AppProviderTest extends FDroidProviderTest {
private static final String[] PROJ = Cols.ALL;
@Before
public void setup() {
ShadowContentResolver.registerProvider(AppProvider.getAuthority(), new AppProvider());
}
/**
* Although this doesn't directly relate to the {@link AppProvider}, it is here because
* the {@link AppProvider} used to stumble across this bug when asking for installed apps,
* and the device had over 1000 apps installed.
*/
@Test
public void testMaxSqliteParams() {
insertApp("com.example.app1", "App 1");
insertApp("com.example.app100", "App 100");
insertApp("com.example.app1000", "App 1000");
for (int i = 0; i < 50; i++) {
InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
}
assertResultCount(contentResolver, 1, AppProvider.getInstalledUri(), PROJ);
for (int i = 50; i < 500; i++) {
InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
}
assertResultCount(contentResolver, 2, AppProvider.getInstalledUri(), PROJ);
for (int i = 500; i < 1100; i++) {
InstalledAppTestUtils.install(context, "com.example.app" + i, 1, "v1");
}
assertResultCount(contentResolver, 3, AppProvider.getInstalledUri(), PROJ);
}
@Test
public void testCantFindApp() {
assertNull(AppProvider.Helper.findSpecificApp(context.getContentResolver(), "com.example.doesnt-exist", 1, Cols.ALL));
}
@Test
public void testQuery() {
Cursor cursor = queryAllApps();
assertNotNull(cursor);
cursor.close();
}
private void insertApps(int count) {
for (int i = 0; i < count; i++) {
insertApp("com.example.test." + i, "Test app " + i);
}
}
private void insertAndInstallApp(
String packageName, int installedVercode, int suggestedVercode,
boolean ignoreAll, int ignoreVercode) {
ContentValues values = new ContentValues(3);
values.put(Cols.SUGGESTED_VERSION_CODE, suggestedVercode);
App app = insertApp(contentResolver, context, packageName, "App: " + packageName, values);
AppPrefsProvider.Helper.update(context, app, new AppPrefs(ignoreVercode, ignoreAll));
InstalledAppTestUtils.install(context, packageName, installedVercode, "v" + installedVercode);
}
@Test
public void testCanUpdate() {
insertApp("not installed", "not installed");
insertAndInstallApp("installed, only one version available", 1, 1, false, 0);
insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0);
insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0);
insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10);
insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5);
insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0);
insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0);
insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10);
insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8);
ContentResolver r = context.getContentResolver();
// Can't "update", although can "install"...
App notInstalled = AppProvider.Helper.findSpecificApp(r, "not installed", 1, Cols.ALL);
assertFalse(notInstalled.canAndWantToUpdate(context));
App installedOnlyOneVersionAvailable = AppProvider.Helper.findSpecificApp(r, "installed, only one version available", 1, Cols.ALL);
App installedAlreadyLatestNoIgnore = AppProvider.Helper.findSpecificApp(r, "installed, already latest, no ignore", 1, Cols.ALL);
App installedAlreadyLatestIgnoreAll = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore all", 1, Cols.ALL);
App installedAlreadyLatestIgnoreLatest = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore latest", 1, Cols.ALL);
App installedAlreadyLatestIgnoreOld = AppProvider.Helper.findSpecificApp(r, "installed, already latest, ignore old", 1, Cols.ALL);
assertFalse(installedOnlyOneVersionAvailable.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestNoIgnore.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestIgnoreAll.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestIgnoreLatest.canAndWantToUpdate(context));
assertFalse(installedAlreadyLatestIgnoreOld.canAndWantToUpdate(context));
App installedOldNoIgnore = AppProvider.Helper.findSpecificApp(r, "installed, old version, no ignore", 1, Cols.ALL);
App installedOldIgnoreAll = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore all", 1, Cols.ALL);
App installedOldIgnoreLatest = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore latest", 1, Cols.ALL);
App installedOldIgnoreNewerNotLatest = AppProvider.Helper.findSpecificApp(r, "installed, old version, ignore newer, but not latest", 1, Cols.ALL);
assertTrue(installedOldNoIgnore.canAndWantToUpdate(context));
assertFalse(installedOldIgnoreAll.canAndWantToUpdate(context));
assertFalse(installedOldIgnoreLatest.canAndWantToUpdate(context));
assertTrue(installedOldIgnoreNewerNotLatest.canAndWantToUpdate(context));
Cursor canUpdateCursor = r.query(AppProvider.getCanUpdateUri(), Cols.ALL, null, null, null);
assertNotNull(canUpdateCursor);
canUpdateCursor.moveToFirst();
List<String> canUpdateIds = new ArrayList<>(canUpdateCursor.getCount());
while (!canUpdateCursor.isAfterLast()) {
canUpdateIds.add(new App(canUpdateCursor).packageName);
canUpdateCursor.moveToNext();
}
canUpdateCursor.close();
String[] expectedUpdateableIds = {
"installed, old version, no ignore",
"installed, old version, ignore newer, but not latest",
};
assertContainsOnly(expectedUpdateableIds, canUpdateIds);
}
@Test
public void testIgnored() {
insertApp("not installed", "not installed");
insertAndInstallApp("installed, only one version available", 1, 1, false, 0);
insertAndInstallApp("installed, already latest, no ignore", 10, 10, false, 0);
insertAndInstallApp("installed, already latest, ignore all", 10, 10, true, 0);
insertAndInstallApp("installed, already latest, ignore latest", 10, 10, false, 10);
insertAndInstallApp("installed, already latest, ignore old", 10, 10, false, 5);
insertAndInstallApp("installed, old version, no ignore", 5, 10, false, 0);
insertAndInstallApp("installed, old version, ignore all", 5, 10, true, 0);
insertAndInstallApp("installed, old version, ignore latest", 5, 10, false, 10);
insertAndInstallApp("installed, old version, ignore newer, but not latest", 5, 10, false, 8);
assertResultCount(contentResolver, 10, AppProvider.getContentUri(), PROJ);
String[] projection = {Cols.Package.PACKAGE_NAME};
List<App> canUpdateApps = AppProvider.Helper.findCanUpdate(context, projection);
String[] expectedCanUpdate = {
"installed, old version, no ignore",
"installed, old version, ignore newer, but not latest",
// These are ignored because they don't have updates available:
// "installed, only one version available",
// "installed, already latest, no ignore",
// "installed, already latest, ignore old",
// "not installed",
// These four should be ignored due to the app preferences:
// "installed, already latest, ignore all",
// "installed, already latest, ignore latest",
// "installed, old version, ignore all",
// "installed, old version, ignore latest",
};
assertContainsOnlyIds(canUpdateApps, expectedCanUpdate);
}
public static void assertContainsOnlyIds(List<App> actualApps, String[] expectedIds) {
List<String> actualIds = new ArrayList<>(actualApps.size());
for (App app : actualApps) {
actualIds.add(app.packageName);
}
assertContainsOnly(actualIds, expectedIds);
}
@Test
public void testInstalled() {
insertApps(100);
assertResultCount(contentResolver, 100, AppProvider.getContentUri(), PROJ);
assertResultCount(contentResolver, 0, AppProvider.getInstalledUri(), PROJ);
for (int i = 10; i < 20; i++) {
InstalledAppTestUtils.install(context, "com.example.test." + i, i, "v1");
}
assertResultCount(contentResolver, 10, AppProvider.getInstalledUri(), PROJ);
}
@Test
public void testInsert() {
// Start with an empty database...
Cursor cursor = queryAllApps();
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
// Insert a new record...
insertApp("org.fdroid.fdroid", "F-Droid");
cursor = queryAllApps();
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
// And now we should be able to recover these values from the app
// value object (because the queryAllApps() helper asks for NAME and
// PACKAGE_NAME.
cursor.moveToFirst();
App app = new App(cursor);
cursor.close();
assertEquals("org.fdroid.fdroid", app.packageName);
assertEquals("F-Droid", app.name);
App otherApp = AppProvider.Helper.findSpecificApp(context.getContentResolver(), "org.fdroid.fdroid", 1, Cols.ALL);
assertNotNull(otherApp);
assertEquals("org.fdroid.fdroid", otherApp.packageName);
assertEquals("F-Droid", otherApp.name);
}
/**
* We intentionally throw an IllegalArgumentException if you haven't
* yet called cursor.move*().
*/
@Test(expected = IllegalArgumentException.class)
public void testCursorMustMoveToFirst() {
insertApp("org.fdroid.fdroid", "F-Droid");
Cursor cursor = queryAllApps();
new App(cursor);
}
private Cursor queryAllApps() {
String[] projection = new String[] {
Cols._ID,
Cols.NAME,
Cols.Package.PACKAGE_NAME,
};
return contentResolver.query(AppProvider.getContentUri(), projection, null, null, null);
}
// =======================================================================
// Misc helper functions
// (to be used by any tests in this suite)
// =======================================================================
private void insertApp(String id, String name) {
insertApp(contentResolver, context, id, name, new ContentValues());
}
public static App insertApp(ShadowContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues) {
return insertApp(contentResolver, context, id, name, additionalValues, 1);
}
public static App insertApp(ShadowContentResolver contentResolver, Context context, String id, String name, ContentValues additionalValues, long repoId) {
ContentValues values = new ContentValues();
values.put(Cols.Package.PACKAGE_NAME, id);
values.put(Cols.REPO_ID, repoId);
values.put(Cols.NAME, name);
// Required fields (NOT NULL in the database).
values.put(Cols.SUMMARY, "test summary");
values.put(Cols.DESCRIPTION, "test description");
values.put(Cols.LICENSE, "GPL?");
values.put(Cols.IS_COMPATIBLE, 1);
values.putAll(additionalValues);
Uri uri = AppProvider.getContentUri();
contentResolver.insert(uri, values);
AppProvider.Helper.recalculatePreferredMetadata(context);
return AppProvider.Helper.findSpecificApp(context.getContentResolver(), id, 1, Cols.ALL);
}
}