Merge pull request #1023 from psh/extract-and-test-category-db

Extracted and tested the database interactions from Category
This commit is contained in:
neslihanturan 2018-01-04 21:01:33 +03:00 committed by GitHub
commit ae24508300
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 515 additions and 232 deletions

View file

@ -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;
@ -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);
}

View file

@ -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);
if (category == null) {
category = new Category(null, item.getName(), new Date(), 0);
}
return cat;
category.incTimesUsed();
categoryDao.save(category);
}
public int getCurrentSelectedCount() {

View file

@ -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 {

View file

@ -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
*/
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;
}
/**
* 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
* Modifies the content URI - marking this category as already saved in the database
*
* @param db Category database
* @param contentUri the content URI
*/
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
public void setContentUri(Uri contentUri) {
this.contentUri = contentUri;
}
/**
* 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
}

View 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;
}
}
}
}

View file

@ -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);
}
}

View 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;
}
}