mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge pull request #1023 from psh/extract-and-test-category-db
Extracted and tested the database interactions from Category
This commit is contained in:
commit
ae24508300
7 changed files with 515 additions and 232 deletions
|
|
@ -22,7 +22,7 @@ import dagger.android.AndroidInjector;
|
|||
import dagger.android.DaggerApplication;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||
import fr.free.nrw.commons.data.Category;
|
||||
import fr.free.nrw.commons.data.CategoryDao;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationComponent;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
|
|
@ -49,15 +49,15 @@ public class CommonsApplication extends DaggerApplication {
|
|||
@Inject @Named("default_preferences") SharedPreferences defaultPrefs;
|
||||
@Inject @Named("application_preferences") SharedPreferences applicationPrefs;
|
||||
@Inject @Named("prefs") SharedPreferences otherPrefs;
|
||||
|
||||
|
||||
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using Android Commons app";
|
||||
|
||||
|
||||
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
|
||||
|
||||
|
||||
public static final String LOGS_PRIVATE_EMAIL = "commons-app-android-private@googlegroups.com";
|
||||
|
||||
|
||||
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
||||
|
||||
|
||||
private CommonsApplicationComponent component;
|
||||
private RefWatcher refWatcher;
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ public class CommonsApplication extends DaggerApplication {
|
|||
}
|
||||
return LeakCanary.install(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provides a way to get member refWatcher
|
||||
*
|
||||
|
|
@ -106,7 +106,7 @@ public class CommonsApplication extends DaggerApplication {
|
|||
CommonsApplication application = (CommonsApplication) context.getApplicationContext();
|
||||
return application.refWatcher;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helps in injecting dependency library Dagger
|
||||
* @return Dagger injector
|
||||
|
|
@ -169,7 +169,7 @@ public class CommonsApplication extends DaggerApplication {
|
|||
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
|
||||
|
||||
ModifierSequence.Table.onDelete(db);
|
||||
Category.Table.onDelete(db);
|
||||
CategoryDao.Table.onDelete(db);
|
||||
ContributionDao.Table.onDelete(db);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import butterknife.ButterKnife;
|
|||
import dagger.android.support.DaggerFragment;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.data.Category;
|
||||
import fr.free.nrw.commons.data.CategoryDao;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||
|
|
@ -79,7 +80,7 @@ public class CategorizationFragment extends DaggerFragment {
|
|||
private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> {
|
||||
if (item.isSelected()) {
|
||||
selectedCategories.add(item);
|
||||
updateCategoryCount(item, databaseClient);
|
||||
updateCategoryCount(item);
|
||||
} else {
|
||||
selectedCategories.remove(item);
|
||||
}
|
||||
|
|
@ -261,7 +262,7 @@ public class CategorizationFragment extends DaggerFragment {
|
|||
}
|
||||
|
||||
private Observable<CategoryItem> recentCategories() {
|
||||
return Observable.fromIterable(Category.recentCategories(databaseClient, SEARCH_CATS_LIMIT))
|
||||
return Observable.fromIterable(new CategoryDao(databaseClient).recentCategories(SEARCH_CATS_LIMIT))
|
||||
.map(s -> new CategoryItem(s, false));
|
||||
}
|
||||
|
||||
|
|
@ -311,24 +312,17 @@ public class CategorizationFragment extends DaggerFragment {
|
|||
|| item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)"));
|
||||
}
|
||||
|
||||
private void updateCategoryCount(CategoryItem item, ContentProviderClient client) {
|
||||
Category cat = lookupCategory(item.getName());
|
||||
cat.incTimesUsed();
|
||||
cat.save(client);
|
||||
}
|
||||
private void updateCategoryCount(CategoryItem item) {
|
||||
CategoryDao categoryDao = new CategoryDao(databaseClient);
|
||||
Category category = categoryDao.find(item.getName());
|
||||
|
||||
private Category lookupCategory(String name) {
|
||||
Category cat = Category.find(databaseClient, name);
|
||||
|
||||
if (cat == null) {
|
||||
// Newly used category...
|
||||
cat = new Category();
|
||||
cat.setName(name);
|
||||
cat.setLastUsed(new Date());
|
||||
cat.setTimesUsed(0);
|
||||
// Newly used category...
|
||||
if (category == null) {
|
||||
category = new Category(null, item.getName(), new Date(), 0);
|
||||
}
|
||||
|
||||
return cat;
|
||||
category.incTimesUsed();
|
||||
categoryDao.save(category);
|
||||
}
|
||||
|
||||
public int getCurrentSelectedCount() {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ import fr.free.nrw.commons.data.DBOpenHelper;
|
|||
import timber.log.Timber;
|
||||
|
||||
import static android.content.UriMatcher.NO_MATCH;
|
||||
import static fr.free.nrw.commons.data.Category.Table.ALL_FIELDS;
|
||||
import static fr.free.nrw.commons.data.Category.Table.COLUMN_ID;
|
||||
import static fr.free.nrw.commons.data.Category.Table.TABLE_NAME;
|
||||
import static fr.free.nrw.commons.data.CategoryDao.Table.ALL_FIELDS;
|
||||
import static fr.free.nrw.commons.data.CategoryDao.Table.COLUMN_ID;
|
||||
import static fr.free.nrw.commons.data.CategoryDao.Table.TABLE_NAME;
|
||||
|
||||
public class CategoryContentProvider extends ContentProvider {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +1,28 @@
|
|||
package fr.free.nrw.commons.data;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import fr.free.nrw.commons.category.CategoryContentProvider;
|
||||
|
||||
/**
|
||||
* Represents a category
|
||||
*/
|
||||
public class Category {
|
||||
private Uri contentUri;
|
||||
|
||||
private String name;
|
||||
private Date lastUsed;
|
||||
private int timesUsed;
|
||||
|
||||
// Getters/setters
|
||||
public Category() {
|
||||
}
|
||||
|
||||
public Category(Uri contentUri, String name, Date lastUsed, int timesUsed) {
|
||||
this.contentUri = contentUri;
|
||||
this.name = name;
|
||||
this.lastUsed = lastUsed;
|
||||
this.timesUsed = timesUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets name
|
||||
*
|
||||
|
|
@ -48,21 +46,11 @@ public class Category {
|
|||
*
|
||||
* @return Last used date
|
||||
*/
|
||||
private Date getLastUsed() {
|
||||
public Date getLastUsed() {
|
||||
// warning: Date objects are mutable.
|
||||
return (Date)lastUsed.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies last used date
|
||||
*
|
||||
* @param lastUsed Category date
|
||||
*/
|
||||
public void setLastUsed(Date lastUsed) {
|
||||
// warning: Date objects are mutable.
|
||||
this.lastUsed = (Date)lastUsed.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates new last used date
|
||||
*/
|
||||
|
|
@ -75,19 +63,10 @@ public class Category {
|
|||
*
|
||||
* @return no. of times used
|
||||
*/
|
||||
private int getTimesUsed() {
|
||||
public int getTimesUsed() {
|
||||
return timesUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies no. of times used
|
||||
*
|
||||
* @param timesUsed Category used times
|
||||
*/
|
||||
public void setTimesUsed(int timesUsed) {
|
||||
this.timesUsed = timesUsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments timesUsed by 1 and sets last used date as now.
|
||||
*/
|
||||
|
|
@ -96,181 +75,22 @@ public class Category {
|
|||
touch();
|
||||
}
|
||||
|
||||
//region Database/content-provider stuff
|
||||
|
||||
/**
|
||||
* Persist category.
|
||||
* @param client ContentProviderClient to handle DB connection
|
||||
*/
|
||||
public void save(ContentProviderClient client) {
|
||||
try {
|
||||
if (contentUri == null) {
|
||||
contentUri = client.insert(CategoryContentProvider.BASE_URI, this.toContentValues());
|
||||
} else {
|
||||
client.update(contentUri, toContentValues(), null, null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets content values
|
||||
* Gets the content URI for this category
|
||||
*
|
||||
* @return Content values
|
||||
* @return content URI
|
||||
*/
|
||||
private ContentValues toContentValues() {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(Table.COLUMN_NAME, getName());
|
||||
cv.put(Table.COLUMN_LAST_USED, getLastUsed().getTime());
|
||||
cv.put(Table.COLUMN_TIMES_USED, getTimesUsed());
|
||||
return cv;
|
||||
public Uri getContentUri() {
|
||||
return contentUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets category from cursor
|
||||
* @param cursor Category cursor
|
||||
* @return Category from cursor
|
||||
* Modifies the content URI - marking this category as already saved in the database
|
||||
*
|
||||
* @param contentUri the content URI
|
||||
*/
|
||||
private static Category fromCursor(Cursor cursor) {
|
||||
// Hardcoding column positions!
|
||||
Category c = new Category();
|
||||
c.contentUri = CategoryContentProvider.uriForId(cursor.getInt(0));
|
||||
c.name = cursor.getString(1);
|
||||
c.lastUsed = new Date(cursor.getLong(2));
|
||||
c.timesUsed = cursor.getInt(3);
|
||||
return c;
|
||||
public void setContentUri(Uri contentUri) {
|
||||
this.contentUri = contentUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find persisted category in database, based on its name.
|
||||
* @param client ContentProviderClient to handle DB connection
|
||||
* @param name Category's name
|
||||
* @return category from database, or null if not found
|
||||
*/
|
||||
public static @Nullable Category find(ContentProviderClient client, String name) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = client.query(
|
||||
CategoryContentProvider.BASE_URI,
|
||||
Category.Table.ALL_FIELDS,
|
||||
Category.Table.COLUMN_NAME + "=?",
|
||||
new String[]{name},
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return Category.fromCursor(cursor);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// This feels lazy, but to hell with checked exceptions. :)
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve recently-used categories, ordered by descending date.
|
||||
* @return a list containing recent categories
|
||||
*/
|
||||
public static @NonNull ArrayList<String> recentCategories(ContentProviderClient client, int limit) {
|
||||
ArrayList<String> items = new ArrayList<>();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = client.query(
|
||||
CategoryContentProvider.BASE_URI,
|
||||
Category.Table.ALL_FIELDS,
|
||||
null,
|
||||
new String[]{},
|
||||
Category.Table.COLUMN_LAST_USED + " DESC");
|
||||
// fixme add a limit on the original query instead of falling out of the loop?
|
||||
while (cursor != null && cursor.moveToNext()
|
||||
&& cursor.getPosition() < limit) {
|
||||
Category cat = Category.fromCursor(cursor);
|
||||
items.add(cat.getName());
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public static class Table {
|
||||
public static final String TABLE_NAME = "categories";
|
||||
|
||||
public static final String COLUMN_ID = "_id";
|
||||
public static final String COLUMN_NAME = "name";
|
||||
public static final String COLUMN_LAST_USED = "last_used";
|
||||
public static final String COLUMN_TIMES_USED = "times_used";
|
||||
|
||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||
public static final String[] ALL_FIELDS = {
|
||||
COLUMN_ID,
|
||||
COLUMN_NAME,
|
||||
COLUMN_LAST_USED,
|
||||
COLUMN_TIMES_USED
|
||||
};
|
||||
|
||||
private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_ID + " INTEGER PRIMARY KEY,"
|
||||
+ COLUMN_NAME + " STRING,"
|
||||
+ COLUMN_LAST_USED + " INTEGER,"
|
||||
+ COLUMN_TIMES_USED + " INTEGER"
|
||||
+ ");";
|
||||
|
||||
/**
|
||||
* Creates new table with provided SQLite database
|
||||
*
|
||||
* @param db Category database
|
||||
*/
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes existing table
|
||||
* @param db Category database
|
||||
*/
|
||||
public static void onDelete(SQLiteDatabase db) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates given database
|
||||
* @param db Category database
|
||||
* @param from Exiting category id
|
||||
* @param to New category id
|
||||
*/
|
||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if (from < 4) {
|
||||
// doesn't exist yet
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 4) {
|
||||
// table added in version 5
|
||||
onCreate(db);
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 5) {
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
174
app/src/main/java/fr/free/nrw/commons/data/CategoryDao.java
Normal file
174
app/src/main/java/fr/free/nrw/commons/data/CategoryDao.java
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
package fr.free.nrw.commons.data;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.category.CategoryContentProvider;
|
||||
|
||||
public class CategoryDao {
|
||||
|
||||
private final ContentProviderClient client;
|
||||
|
||||
public CategoryDao(ContentProviderClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void save(Category category) {
|
||||
try {
|
||||
if (category.getContentUri() == null) {
|
||||
category.setContentUri(client.insert(CategoryContentProvider.BASE_URI, toContentValues(category)));
|
||||
} else {
|
||||
client.update(category.getContentUri(), toContentValues(category), null, null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find persisted category in database, based on its name.
|
||||
*
|
||||
* @param name Category's name
|
||||
* @return category from database, or null if not found
|
||||
*/
|
||||
public @Nullable
|
||||
Category find(String name) {
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = client.query(
|
||||
CategoryContentProvider.BASE_URI,
|
||||
Table.ALL_FIELDS,
|
||||
Table.COLUMN_NAME + "=?",
|
||||
new String[]{name},
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return fromCursor(cursor);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// This feels lazy, but to hell with checked exceptions. :)
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve recently-used categories, ordered by descending date.
|
||||
*
|
||||
* @return a list containing recent categories
|
||||
*/
|
||||
public @NonNull
|
||||
List<String> recentCategories(int limit) {
|
||||
List<String> items = new ArrayList<>();
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = client.query(
|
||||
CategoryContentProvider.BASE_URI,
|
||||
Table.ALL_FIELDS,
|
||||
null,
|
||||
new String[]{},
|
||||
Table.COLUMN_LAST_USED + " DESC");
|
||||
// fixme add a limit on the original query instead of falling out of the loop?
|
||||
while (cursor != null && cursor.moveToNext()
|
||||
&& cursor.getPosition() < limit) {
|
||||
items.add(fromCursor(cursor).getName());
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
Category fromCursor(Cursor cursor) {
|
||||
// Hardcoding column positions!
|
||||
return new Category(
|
||||
CategoryContentProvider.uriForId(cursor.getInt(0)),
|
||||
cursor.getString(1),
|
||||
new Date(cursor.getLong(2)),
|
||||
cursor.getInt(3)
|
||||
);
|
||||
}
|
||||
|
||||
private ContentValues toContentValues(Category category) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(CategoryDao.Table.COLUMN_NAME, category.getName());
|
||||
cv.put(CategoryDao.Table.COLUMN_LAST_USED, category.getLastUsed().getTime());
|
||||
cv.put(CategoryDao.Table.COLUMN_TIMES_USED, category.getTimesUsed());
|
||||
return cv;
|
||||
}
|
||||
|
||||
public static class Table {
|
||||
public static final String TABLE_NAME = "categories";
|
||||
|
||||
public static final String COLUMN_ID = "_id";
|
||||
static final String COLUMN_NAME = "name";
|
||||
static final String COLUMN_LAST_USED = "last_used";
|
||||
static final String COLUMN_TIMES_USED = "times_used";
|
||||
|
||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||
public static final String[] ALL_FIELDS = {
|
||||
COLUMN_ID,
|
||||
COLUMN_NAME,
|
||||
COLUMN_LAST_USED,
|
||||
COLUMN_TIMES_USED
|
||||
};
|
||||
|
||||
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||
|
||||
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_ID + " INTEGER PRIMARY KEY,"
|
||||
+ COLUMN_NAME + " STRING,"
|
||||
+ COLUMN_LAST_USED + " INTEGER,"
|
||||
+ COLUMN_TIMES_USED + " INTEGER"
|
||||
+ ");";
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
public static void onDelete(SQLiteDatabase db) {
|
||||
db.execSQL(DROP_TABLE_STATEMENT);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if (from < 4) {
|
||||
// doesn't exist yet
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 4) {
|
||||
// table added in version 5
|
||||
onCreate(db);
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 5) {
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,13 +23,13 @@ public class DBOpenHelper extends SQLiteOpenHelper {
|
|||
public void onCreate(SQLiteDatabase sqLiteDatabase) {
|
||||
ContributionDao.Table.onCreate(sqLiteDatabase);
|
||||
ModifierSequence.Table.onCreate(sqLiteDatabase);
|
||||
Category.Table.onCreate(sqLiteDatabase);
|
||||
CategoryDao.Table.onCreate(sqLiteDatabase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
|
||||
ContributionDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
Category.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
295
app/src/test/java/fr/free/nrw/commons/data/CategoryDaoTest.java
Normal file
295
app/src/test/java/fr/free/nrw/commons/data/CategoryDaoTest.java
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
package fr.free.nrw.commons.data;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.MatrixCursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.TestCommonsApplication;
|
||||
import fr.free.nrw.commons.category.CategoryContentProvider;
|
||||
import fr.free.nrw.commons.data.CategoryDao.Table;
|
||||
|
||||
import static fr.free.nrw.commons.category.CategoryContentProvider.BASE_URI;
|
||||
import static fr.free.nrw.commons.category.CategoryContentProvider.uriForId;
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNull;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isA;
|
||||
import static org.mockito.Matchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, sdk = 21, application = TestCommonsApplication.class)
|
||||
public class CategoryDaoTest {
|
||||
|
||||
@Mock
|
||||
ContentProviderClient client;
|
||||
@Mock
|
||||
SQLiteDatabase database;
|
||||
@Captor
|
||||
ArgumentCaptor<ContentValues> captor;
|
||||
@Captor
|
||||
ArgumentCaptor<String[]> queryCaptor;
|
||||
|
||||
private CategoryDao testObject;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
testObject = new CategoryDao(client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createTable() {
|
||||
Table.onCreate(database);
|
||||
verify(database).execSQL(Table.CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteTable() {
|
||||
Table.onDelete(database);
|
||||
InOrder inOrder = Mockito.inOrder(database);
|
||||
inOrder.verify(database).execSQL(Table.DROP_TABLE_STATEMENT);
|
||||
inOrder.verify(database).execSQL(Table.CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateTableVersionFrom_v1_to_v2() {
|
||||
Table.onUpdate(database, 1, 2);
|
||||
// Table didnt exist before v5
|
||||
verifyZeroInteractions(database);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateTableVersionFrom_v2_to_v3() {
|
||||
Table.onUpdate(database, 2, 3);
|
||||
// Table didnt exist before v5
|
||||
verifyZeroInteractions(database);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateTableVersionFrom_v3_to_v4() {
|
||||
Table.onUpdate(database, 3, 4);
|
||||
// Table didnt exist before v5
|
||||
verifyZeroInteractions(database);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateTableVersionFrom_v4_to_v5() {
|
||||
Table.onUpdate(database, 4, 5);
|
||||
verify(database).execSQL(Table.CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void migrateTableVersionFrom_v5_to_v6() {
|
||||
Table.onUpdate(database, 5, 6);
|
||||
// Table didnt change in version 6
|
||||
verifyZeroInteractions(database);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromCursor() {
|
||||
MatrixCursor cursor = createCursor(1);
|
||||
cursor.moveToFirst();
|
||||
Category category = testObject.fromCursor(cursor);
|
||||
|
||||
assertEquals(uriForId(1), category.getContentUri());
|
||||
assertEquals("foo", category.getName());
|
||||
assertEquals(123, category.getLastUsed().getTime());
|
||||
assertEquals(2, category.getTimesUsed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveExistingCategory() throws Exception {
|
||||
MatrixCursor cursor = createCursor(1);
|
||||
cursor.moveToFirst();
|
||||
Category category = testObject.fromCursor(cursor);
|
||||
|
||||
testObject.save(category);
|
||||
|
||||
verify(client).update(eq(category.getContentUri()), captor.capture(), isNull(String.class), isNull(String[].class));
|
||||
ContentValues cv = captor.getValue();
|
||||
assertEquals(3, cv.size());
|
||||
assertEquals(category.getName(), cv.getAsString(Table.COLUMN_NAME));
|
||||
assertEquals(category.getLastUsed().getTime(), cv.getAsLong(Table.COLUMN_LAST_USED).longValue());
|
||||
assertEquals(category.getTimesUsed(), cv.getAsInteger(Table.COLUMN_TIMES_USED).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveNewCategory() throws Exception {
|
||||
Uri contentUri = CategoryContentProvider.uriForId(111);
|
||||
when(client.insert(isA(Uri.class), isA(ContentValues.class))).thenReturn(contentUri);
|
||||
Category category = new Category(null, "foo", new Date(234L), 1);
|
||||
|
||||
testObject.save(category);
|
||||
|
||||
verify(client).insert(eq(BASE_URI), captor.capture());
|
||||
ContentValues cv = captor.getValue();
|
||||
assertEquals(3, cv.size());
|
||||
assertEquals(category.getName(), cv.getAsString(Table.COLUMN_NAME));
|
||||
assertEquals(category.getLastUsed().getTime(), cv.getAsLong(Table.COLUMN_LAST_USED).longValue());
|
||||
assertEquals(category.getTimesUsed(), cv.getAsInteger(Table.COLUMN_TIMES_USED).intValue());
|
||||
assertEquals(contentUri, category.getContentUri());
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testSaveTranslatesRemoteExceptions() throws Exception {
|
||||
when(client.insert(isA(Uri.class), isA(ContentValues.class))).thenThrow(new RemoteException(""));
|
||||
testObject.save(new Category());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenTheresNoDataFindReturnsNull_nullCursor() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(null);
|
||||
|
||||
assertNull(testObject.find("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenTheresNoDataFindReturnsNull_emptyCursor() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(createCursor(0));
|
||||
|
||||
assertNull(testObject.find("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cursorsAreClosedAfterUse() throws Exception {
|
||||
Cursor mockCursor = mock(Cursor.class);
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(mockCursor);
|
||||
when(mockCursor.moveToFirst()).thenReturn(false);
|
||||
|
||||
testObject.find("foo");
|
||||
|
||||
verify(mockCursor).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findCategory() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(createCursor(1));
|
||||
|
||||
Category category = testObject.find("foo");
|
||||
|
||||
assertEquals(uriForId(1), category.getContentUri());
|
||||
assertEquals("foo", category.getName());
|
||||
assertEquals(123, category.getLastUsed().getTime());
|
||||
assertEquals(2, category.getTimesUsed());
|
||||
|
||||
verify(client).query(
|
||||
eq(BASE_URI),
|
||||
eq(Table.ALL_FIELDS),
|
||||
eq(Table.COLUMN_NAME + "=?"),
|
||||
queryCaptor.capture(),
|
||||
isNull(String.class)
|
||||
);
|
||||
assertEquals("foo", queryCaptor.getValue()[0]);
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void findCategoryTranslatesExceptions() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenThrow(new RemoteException(""));
|
||||
testObject.find("foo");
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void recentCategoriesTranslatesExceptions() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenThrow(new RemoteException(""));
|
||||
testObject.recentCategories(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recentCategoriesReturnsEmptyList_nullCursor() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(null);
|
||||
|
||||
assertTrue(testObject.recentCategories(1).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recentCategoriesReturnsEmptyList_emptyCursor() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(createCursor(0));
|
||||
|
||||
assertTrue(testObject.recentCategories(1).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cursorsAreClosedAfterRecentCategoriesQuery() throws Exception {
|
||||
Cursor mockCursor = mock(Cursor.class);
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(mockCursor);
|
||||
when(mockCursor.moveToFirst()).thenReturn(false);
|
||||
|
||||
testObject.recentCategories(1);
|
||||
|
||||
verify(mockCursor).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recentCategoriesReturnsLessThanLimit() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(createCursor(1));
|
||||
|
||||
List<String> result = testObject.recentCategories(10);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("foo", result.get(0));
|
||||
|
||||
verify(client).query(
|
||||
eq(BASE_URI),
|
||||
eq(Table.ALL_FIELDS),
|
||||
isNull(String.class),
|
||||
queryCaptor.capture(),
|
||||
eq(Table.COLUMN_LAST_USED + " DESC")
|
||||
);
|
||||
assertEquals(0, queryCaptor.getValue().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void recentCategoriesHomorsLimit() throws Exception {
|
||||
when(client.query(any(), any(), anyString(), any(), anyString())).thenReturn(createCursor(10));
|
||||
|
||||
List<String> result = testObject.recentCategories(5);
|
||||
|
||||
assertEquals(5, result.size());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private MatrixCursor createCursor(int rowCount) {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{
|
||||
Table.COLUMN_ID,
|
||||
Table.COLUMN_NAME,
|
||||
Table.COLUMN_LAST_USED,
|
||||
Table.COLUMN_TIMES_USED
|
||||
}, rowCount);
|
||||
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
cursor.addRow(Arrays.asList("1", "foo", "123", "2"));
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue