Extracted and tested the database interactions from ModifierSequence

This commit is contained in:
Paul Hawke 2017-12-20 09:12:18 -06:00
parent ae24508300
commit 08673c91c6
9 changed files with 330 additions and 122 deletions

View file

@ -27,7 +27,7 @@ import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsApplicationComponent;
import fr.free.nrw.commons.di.CommonsApplicationModule;
import fr.free.nrw.commons.di.DaggerCommonsApplicationComponent;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.utils.FileUtils;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
@ -168,7 +168,7 @@ public class CommonsApplication extends DaggerApplication {
dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
ModifierSequence.Table.onDelete(db);
ModifierSequenceDao.Table.onDelete(db);
CategoryDao.Table.onDelete(db);
ContributionDao.Table.onDelete(db);
}

View file

@ -5,7 +5,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
public class DBOpenHelper extends SQLiteOpenHelper {
@ -13,7 +13,8 @@ public class DBOpenHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 6;
/**
* Do not use, please call CommonsApplication.getDBOpenHelper()
* Do not use directly - @Inject an instance where it's needed and let
* dependency injection take care of managing this as a singleton.
*/
public DBOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
@ -22,14 +23,14 @@ public class DBOpenHelper extends SQLiteOpenHelper {
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
ContributionDao.Table.onCreate(sqLiteDatabase);
ModifierSequence.Table.onCreate(sqLiteDatabase);
ModifierSequenceDao.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);
ModifierSequenceDao.Table.onUpdate(sqLiteDatabase, from, to);
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
}
}

View file

@ -16,13 +16,15 @@ import dagger.android.AndroidInjection;
import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
import static fr.free.nrw.commons.modifications.ModifierSequenceDao.Table.TABLE_NAME;
public class ModificationsContentProvider extends ContentProvider {
private static final int MODIFICATIONS = 1;
private static final int MODIFICATIONS_ID = 2;
public static final String AUTHORITY = "fr.free.nrw.commons.modifications.contentprovider";
private static final String BASE_PATH = "modifications";
public static final String BASE_PATH = "modifications";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
@ -47,7 +49,7 @@ public class ModificationsContentProvider extends ContentProvider {
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(ModifierSequence.Table.TABLE_NAME);
queryBuilder.setTables(TABLE_NAME);
int uriType = uriMatcher.match(uri);
@ -78,7 +80,7 @@ public class ModificationsContentProvider extends ContentProvider {
long id = 0;
switch (uriType) {
case MODIFICATIONS:
id = sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, contentValues);
id = sqlDB.insert(TABLE_NAME, null, contentValues);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
@ -94,7 +96,7 @@ public class ModificationsContentProvider extends ContentProvider {
switch (uriType) {
case MODIFICATIONS_ID:
String id = uri.getLastPathSegment();
sqlDB.delete(ModifierSequence.Table.TABLE_NAME,
sqlDB.delete(TABLE_NAME,
"_id = ?",
new String[] { id }
);
@ -114,7 +116,7 @@ public class ModificationsContentProvider extends ContentProvider {
case MODIFICATIONS:
for (ContentValues value: values) {
Timber.d("Inserting! %s", value);
sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
sqlDB.insert(TABLE_NAME, null, value);
}
break;
default:
@ -140,7 +142,7 @@ public class ModificationsContentProvider extends ContentProvider {
int rowsUpdated = 0;
switch (uriType) {
case MODIFICATIONS:
rowsUpdated = sqlDB.update(ModifierSequence.Table.TABLE_NAME,
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
selection,
selectionArgs);
@ -149,9 +151,9 @@ public class ModificationsContentProvider extends ContentProvider {
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(ModifierSequence.Table.TABLE_NAME,
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
ModifierSequence.Table.COLUMN_ID + " = ?",
ModifierSequenceDao.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
} else {
throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID");

View file

@ -83,8 +83,8 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
while (!allModifications.isAfterLast()) {
ModifierSequence sequence = ModifierSequence.fromCursor(allModifications);
sequence.setContentProviderClient(contentProviderClient);
ModifierSequence sequence = ModifierSequenceDao.fromCursor(allModifications);
ModifierSequenceDao dao = new ModifierSequenceDao(contributionsClient);
Contribution contrib;
Cursor contributionCursor;
@ -122,7 +122,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
// FIXME: Log this somewhere else
Timber.d("Non success result! %s", editResult);
} else {
sequence.delete();
dao.delete(sequence);
}
}
allModifications.moveToNext();

View file

@ -1,14 +1,8 @@
package fr.free.nrw.commons.modifications;
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 org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
@ -17,14 +11,13 @@ public class ModifierSequence {
private Uri mediaUri;
private ArrayList<PageModifier> modifiers;
private Uri contentUri;
private ContentProviderClient client;
public ModifierSequence(Uri mediaUri) {
this.mediaUri = mediaUri;
modifiers = new ArrayList<>();
}
public ModifierSequence(Uri mediaUri, JSONObject data) {
ModifierSequence(Uri mediaUri, JSONObject data) {
this(mediaUri);
JSONArray modifiersJSON = data.optJSONArray("modifiers");
for (int i = 0; i < modifiersJSON.length(); i++) {
@ -32,7 +25,7 @@ public class ModifierSequence {
}
}
public Uri getMediaUri() {
Uri getMediaUri() {
return mediaUri;
}
@ -40,14 +33,14 @@ public class ModifierSequence {
modifiers.add(modifier);
}
public String executeModifications(String pageName, String pageContents) {
String executeModifications(String pageName, String pageContents) {
for (PageModifier modifier: modifiers) {
pageContents = modifier.doModification(pageName, pageContents);
}
return pageContents;
}
public String getEditSummary() {
String getEditSummary() {
StringBuilder editSummary = new StringBuilder();
for (PageModifier modifier: modifiers) {
editSummary.append(modifier.getEditSumary()).append(" ");
@ -56,97 +49,16 @@ public class ModifierSequence {
return editSummary.toString();
}
public JSONObject toJSON() {
JSONObject data = new JSONObject();
try {
JSONArray modifiersJSON = new JSONArray();
for (PageModifier modifier: modifiers) {
modifiersJSON.put(modifier.toJSON());
}
data.put("modifiers", modifiersJSON);
return data;
} catch (JSONException e) {
throw new RuntimeException(e);
}
ArrayList<PageModifier> getModifiers() {
return modifiers;
}
public ContentValues toContentValues() {
ContentValues cv = new ContentValues();
cv.put(Table.COLUMN_MEDIA_URI, mediaUri.toString());
cv.put(Table.COLUMN_DATA, toJSON().toString());
return cv;
Uri getContentUri() {
return contentUri;
}
public static ModifierSequence fromCursor(Cursor cursor) {
// Hardcoding column positions!
ModifierSequence ms = null;
try {
ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
new JSONObject(cursor.getString(2)));
} catch (JSONException e) {
throw new RuntimeException(e);
}
ms.contentUri = ModificationsContentProvider.uriForId(cursor.getInt(0));
return ms;
void setContentUri(Uri contentUri) {
this.contentUri = contentUri;
}
public void save() {
try {
if (contentUri == null) {
contentUri = client.insert(ModificationsContentProvider.BASE_URI, this.toContentValues());
} else {
client.update(contentUri, toContentValues(), null, null);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public void delete() {
try {
client.delete(contentUri, null, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public void setContentProviderClient(ContentProviderClient client) {
this.client = client;
}
public static class Table {
public static final String TABLE_NAME = "modifications";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_MEDIA_URI = "mediauri";
public static final String COLUMN_DATA = "data";
// 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_MEDIA_URI,
COLUMN_DATA
};
private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ "_id INTEGER PRIMARY KEY,"
+ "mediauri STRING,"
+ "data STRING"
+ ");";
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
}
public static void onUpdate(SQLiteDatabase db, int from, int to) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
public static void onDelete(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
}
}

View file

@ -0,0 +1,113 @@
package fr.free.nrw.commons.modifications;
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 org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class ModifierSequenceDao {
private final ContentProviderClient client;
public ModifierSequenceDao(ContentProviderClient client) {
this.client = client;
}
public static ModifierSequence fromCursor(Cursor cursor) {
// Hardcoding column positions!
ModifierSequence ms = null;
try {
ms = new ModifierSequence(Uri.parse(cursor.getString(1)),
new JSONObject(cursor.getString(2)));
} catch (JSONException e) {
throw new RuntimeException(e);
}
ms.setContentUri( ModificationsContentProvider.uriForId(cursor.getInt(0)));
return ms;
}
public void save(ModifierSequence sequence) {
try {
if (sequence.getContentUri() == null) {
sequence.setContentUri(client.insert(ModificationsContentProvider.BASE_URI, toContentValues(sequence)));
} else {
client.update(sequence.getContentUri(), toContentValues(sequence), null, null);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
public void delete(ModifierSequence sequence) {
try {
client.delete(sequence.getContentUri(), null, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
private JSONObject toJSON(ModifierSequence sequence) {
JSONObject data = new JSONObject();
try {
JSONArray modifiersJSON = new JSONArray();
for (PageModifier modifier: sequence.getModifiers()) {
modifiersJSON.put(modifier.toJSON());
}
data.put("modifiers", modifiersJSON);
return data;
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private ContentValues toContentValues(ModifierSequence sequence) {
ContentValues cv = new ContentValues();
cv.put(Table.COLUMN_MEDIA_URI, sequence.getMediaUri().toString());
cv.put(Table.COLUMN_DATA, toJSON(sequence).toString());
return cv;
}
public static class Table {
static final String TABLE_NAME = "modifications";
static final String COLUMN_ID = "_id";
static final String COLUMN_MEDIA_URI = "mediauri";
static final String COLUMN_DATA = "data";
// 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_MEDIA_URI,
COLUMN_DATA
};
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ "_id INTEGER PRIMARY KEY,"
+ "mediauri STRING,"
+ "data STRING"
+ ");";
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
}
public static void onUpdate(SQLiteDatabase db, int from, int to) {
db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
}
public static void onDelete(SQLiteDatabase db) {
db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
}
}
}

View file

@ -40,6 +40,7 @@ import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
@ -165,15 +166,14 @@ public class MultipleShareActivity extends AuthenticatedActivity
@Override
public void onCategoriesSave(List<String> categories) {
if (categories.size() > 0) {
ContentProviderClient client = getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY);
ModifierSequenceDao dao = new ModifierSequenceDao(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
for (Contribution contribution : photosList) {
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
categoriesSequence.setContentProviderClient(client);
categoriesSequence.save();
dao.save(categoriesSequence);
}
}
// FIXME: Make sure that the content provider is up

View file

@ -39,7 +39,6 @@ import javax.inject.Inject;
import javax.inject.Named;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.SessionManager;
@ -50,8 +49,8 @@ import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import fr.free.nrw.commons.mwapi.EventLog;
import fr.free.nrw.commons.mwapi.MediaWikiApi;
import timber.log.Timber;
@ -167,8 +166,8 @@ public class ShareActivity
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
categoriesSequence.setContentProviderClient(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
categoriesSequence.save();
ModifierSequenceDao dao = new ModifierSequenceDao(getContentResolver().acquireContentProviderClient(ModificationsContentProvider.AUTHORITY));
dao.save(categoriesSequence);
}
// FIXME: Make sure that the content provider is up

View file

@ -0,0 +1,181 @@
package fr.free.nrw.commons.modifications;
import android.content.ContentProviderClient;
import android.content.ContentValues;
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.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.util.Arrays;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.TestCommonsApplication;
import static fr.free.nrw.commons.modifications.ModificationsContentProvider.BASE_URI;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, application = TestCommonsApplication.class)
public class ModifierSequenceDaoTest {
private static final String EXPECTED_MEDIA_URI = "http://example.com/";
@Mock
ContentProviderClient client;
@Mock
SQLiteDatabase database;
@Captor
ArgumentCaptor<ContentValues> contentValuesCaptor;
private ModifierSequenceDao testObject;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
testObject = new ModifierSequenceDao(client);
}
@Test
public void createFromCursorWithEmptyModifiers() {
MatrixCursor cursor = createCursor("");
ModifierSequence seq = ModifierSequenceDao.fromCursor(cursor);
assertEquals(EXPECTED_MEDIA_URI, seq.getMediaUri().toString());
assertEquals(BASE_URI.buildUpon().appendPath("1").toString(), seq.getContentUri().toString());
assertTrue(seq.getModifiers().isEmpty());
}
@Test
public void createFromCursorWtihCategoryModifier() {
MatrixCursor cursor = createCursor("{\"name\": \"CategoriesModifier\", \"data\": {}}");
ModifierSequence seq = ModifierSequenceDao.fromCursor(cursor);
assertEquals(1, seq.getModifiers().size());
assertTrue(seq.getModifiers().get(0) instanceof CategoryModifier);
}
@Test
public void createFromCursorWithRemoveModifier() {
MatrixCursor cursor = createCursor("{\"name\": \"TemplateRemoverModifier\", \"data\": {}}");
ModifierSequence seq = ModifierSequenceDao.fromCursor(cursor);
assertEquals(1, seq.getModifiers().size());
assertTrue(seq.getModifiers().get(0) instanceof TemplateRemoveModifier);
}
@Test
public void deleteSequence() throws Exception {
when(client.delete(isA(Uri.class), isNull(String.class), isNull(String[].class))).thenReturn(1);
ModifierSequence seq = ModifierSequenceDao.fromCursor(createCursor(""));
testObject.delete(seq);
verify(client).delete(eq(seq.getContentUri()), isNull(String.class), isNull(String[].class));
}
@Test(expected = RuntimeException.class)
public void deleteTranslatesRemoteExceptions() throws Exception {
when(client.delete(isA(Uri.class), isNull(String.class), isNull(String[].class))).thenThrow(new RemoteException(""));
ModifierSequence seq = ModifierSequenceDao.fromCursor(createCursor(""));
testObject.delete(seq);
}
@Test
public void saveExistingSequence() throws Exception {
String modifierJson = "{\"name\":\"CategoriesModifier\",\"data\":{}}";
String expectedData = "{\"modifiers\":[" + modifierJson + "]}";
MatrixCursor cursor = createCursor(modifierJson);
testObject.save(ModifierSequenceDao.fromCursor(cursor));
verify(client).update(eq(ModifierSequenceDao.fromCursor(cursor).getContentUri()), contentValuesCaptor.capture(), isNull(String.class), isNull(String[].class));
ContentValues cv = contentValuesCaptor.getValue();
assertEquals(2, cv.size());
assertEquals(EXPECTED_MEDIA_URI, cv.get(ModifierSequenceDao.Table.COLUMN_MEDIA_URI));
assertEquals(expectedData, cv.get(ModifierSequenceDao.Table.COLUMN_DATA));
}
@Test
public void saveNewSequence() throws Exception {
Uri expectedContentUri = BASE_URI.buildUpon().appendPath("1").build();
when(client.insert(isA(Uri.class), isA(ContentValues.class))).thenReturn(expectedContentUri);
ModifierSequence seq = new ModifierSequence(Uri.parse(EXPECTED_MEDIA_URI));
testObject.save(seq);
verify(client).insert(eq(ModificationsContentProvider.BASE_URI), contentValuesCaptor.capture());
ContentValues cv = contentValuesCaptor.getValue();
assertEquals(2, cv.size());
assertEquals(EXPECTED_MEDIA_URI, cv.get(ModifierSequenceDao.Table.COLUMN_MEDIA_URI));
assertEquals("{\"modifiers\":[]}", cv.get(ModifierSequenceDao.Table.COLUMN_DATA));
assertEquals(expectedContentUri.toString(), seq.getContentUri().toString());
}
@Test(expected = RuntimeException.class)
public void saveTranslatesRemoteExceptions() throws Exception {
when(client.insert(isA(Uri.class), isA(ContentValues.class))).thenThrow(new RemoteException(""));
testObject.save(new ModifierSequence(Uri.parse(EXPECTED_MEDIA_URI)));
}
@Test
public void createTable() {
ModifierSequenceDao.Table.onCreate(database);
verify(database).execSQL(ModifierSequenceDao.Table.CREATE_TABLE_STATEMENT);
}
@Test
public void updateTable() {
ModifierSequenceDao.Table.onUpdate(database, 1, 2);
InOrder inOrder = inOrder(database);
inOrder.verify(database).execSQL(ModifierSequenceDao.Table.DROP_TABLE_STATEMENT);
inOrder.verify(database).execSQL(ModifierSequenceDao.Table.CREATE_TABLE_STATEMENT);
}
@Test
public void deleteTable() {
ModifierSequenceDao.Table.onDelete(database);
InOrder inOrder = inOrder(database);
inOrder.verify(database).execSQL(ModifierSequenceDao.Table.DROP_TABLE_STATEMENT);
inOrder.verify(database).execSQL(ModifierSequenceDao.Table.CREATE_TABLE_STATEMENT);
}
@NonNull
private MatrixCursor createCursor(String modifierJson) {
MatrixCursor cursor = new MatrixCursor(new String[]{
ModifierSequenceDao.Table.COLUMN_ID,
ModifierSequenceDao.Table.COLUMN_MEDIA_URI,
ModifierSequenceDao.Table.COLUMN_DATA
}, 1);
cursor.addRow(Arrays.asList("1", EXPECTED_MEDIA_URI, "{\"modifiers\": [" + modifierJson + "]}"));
cursor.moveToFirst();
return cursor;
}
}