/***************************************************************************************
* *
* Copyright (c) 2015 Frank Oltmanns <frank.oltmanns@gmail.com> *
* Copyright (c) 2015 Timothy Rae <timothy.rae@gmail.com> *
* Copyright (c) 2016 Mark Carter <mark@marcardar.com> *
* *
* This program is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License as published by the Free Software *
* Foundation; either version 3 of the License, or (at your option) any later *
* version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT ANY *
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
* PARTICULAR PURPOSE. See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License along with *
* this program. If not, see <http://www.gnu.org/licenses/>. *
****************************************************************************************/
package com.ichi2.anki.tests;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.util.Log;
import com.ichi2.anki.AbstractFlashcardViewer;
import com.ichi2.anki.AnkiDroidApp;
import com.ichi2.anki.CollectionHelper;
import com.ichi2.anki.FlashCardsContract;
import com.ichi2.anki.exception.ConfirmModSchemaException;
import com.ichi2.libanki.Card;
import com.ichi2.libanki.Collection;
import com.ichi2.libanki.Decks;
import com.ichi2.libanki.Models;
import com.ichi2.libanki.Note;
import com.ichi2.libanki.Sched;
import com.ichi2.libanki.Utils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Test cases for {@link com.ichi2.anki.provider.CardContentProvider}.
* <p/>
* These tests should cover all supported operations for each URI.
*/
public class ContentProviderTest extends AndroidTestCase {
private static final String BASIC_MODEL_NAME = "com.ichi2.anki.provider.test.basic.x94oa3F";
private static final String TEST_FIELD_NAME = "TestFieldName";
private static final String TEST_FIELD_VALUE = "test field value";
private static final String TEST_TAG = "aldskfhewjklhfczmxkjshf";
private static final String[] TEST_DECKS = {"cmxieunwoogyxsctnjmv"
,"sstuljxgmfdyugiujyhq"
,"pdsqoelhmemmmbwjunnu"
,"scxipjiyozczaaczoawo"};
private static final String TEST_MODEL_NAME = "com.ichi2.anki.provider.test.a1x6h9l";
private static final String[] TEST_MODEL_FIELDS = {"FRONTS","BACK"};
private static final String[] TEST_MODEL_CARDS = {"cArD1", "caRD2"};
private static final String[] TEST_MODEL_QFMT = {"{{FRONTS}}", "{{BACK}}"};
private static final String[] TEST_MODEL_AFMT = {"{{BACK}}", "{{FRONTS}}"};
private static final String[] TEST_NOTE_FIELDS = {"dis is za Fr0nt", "Te$t"};
private static final String TEST_MODEL_CSS = "styleeeee";
private int mNumDecksBeforeTest;
private long[] mTestDeckIds = new long[TEST_DECKS.length];
private ArrayList<Uri> mCreatedNotes;
private long mModelId = 0;
private String[] mDummyFields = new String[1];
/**
* Initially create one note for each model.
*/
@Override
protected void setUp() throws Exception {
super.setUp();
Log.i(AnkiDroidApp.TAG, "setUp()");
mCreatedNotes = new ArrayList<>();
final Collection col = CollectionHelper.getInstance().getCol(getContext());
// Add a new basic model that we use for testing purposes (existing models could potentially be corrupted)
JSONObject model = Models.addBasicModel(col, BASIC_MODEL_NAME);
mModelId = model.getLong("id");
ArrayList<String> flds = col.getModels().fieldNames(model);
// Use the names of the fields as test values for the notes which will be added
mDummyFields = flds.toArray(new String[flds.size()]);
// create test decks and add one note for every deck
mNumDecksBeforeTest = col.getDecks().count();
for(int i = 0; i < TEST_DECKS.length; i++) {
long did = col.getDecks().id(TEST_DECKS[i]);
mTestDeckIds[i] = did;
mCreatedNotes.add(setupNewNote(col, mModelId, did, mDummyFields, TEST_TAG));
}
// Add a note to the default deck as well so that testQueryNextCard() works
mCreatedNotes.add(setupNewNote(col, mModelId, 1, mDummyFields, TEST_TAG));
}
private static Uri setupNewNote(Collection col, long mid, long did, String[] flds, String tag) {
Note newNote = new Note(col, col.getModels().get(mid));
for (int idx=0; idx < flds.length; idx++) {
newNote.setField(idx, flds[idx]);
}
newNote.addTag(tag);
assertTrue("At least one card added for note", col.addNote(newNote) >= 1);
for (Card c: newNote.cards()) {
c.setDid(did);
c.flush();
}
return Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, Long.toString(newNote.getId()));
}
/**
* Remove the notes and decks created in setUp().
*/
@Override
protected void tearDown() throws Exception {
Log.i(AnkiDroidApp.TAG, "tearDown()");
final Collection col = CollectionHelper.getInstance().getCol(getContext());
// Delete all notes
List<Long> remnantNotes = col.findNotes("tag:" + TEST_TAG);
if (remnantNotes.size() > 0) {
long[] nids = new long[remnantNotes.size()];
for (int i = 0; i < remnantNotes.size(); i++) {
nids[i] = remnantNotes.get(i);
}
col.remNotes(nids);
col.save();
assertEquals("Check that remnant notes have been deleted", 0, col.findNotes("tag:" + TEST_TAG).size());
}
// delete test decks
for(long did : mTestDeckIds) {
col.getDecks().rem(did, true);
}
col.getDecks().flush();
assertEquals("Check that all created decks have been deleted", mNumDecksBeforeTest, col.getDecks().count());
// Delete test model
col.modSchema(false);
col.getModels().rem(col.getModels().get(mModelId));
super.tearDown();
}
/**
* Check that inserting and removing a note into default deck works as expected
*/
public void testInsertAndRemoveNote() throws Exception {
// Get required objects for test
final ContentResolver cr = getContext().getContentResolver();
// Add the note
ContentValues values = new ContentValues();
values.put(FlashCardsContract.Note.MID, mModelId);
values.put(FlashCardsContract.Note.FLDS, Utils.joinFields(TEST_NOTE_FIELDS));
values.put(FlashCardsContract.Note.TAGS, TEST_TAG);
Uri newNoteUri = cr.insert(FlashCardsContract.Note.CONTENT_URI, values);
assertNotNull("Check that URI returned from addNewNote is not null", newNoteUri);
final Collection col = reopenCol(); // test that the changes are physically saved to the DB
// Check that it looks as expected
Note addedNote = new Note(col, Long.parseLong(newNoteUri.getLastPathSegment()));
addedNote.load();
assertTrue("Check that fields were set correctly", Arrays.equals(addedNote.getFields(), TEST_NOTE_FIELDS));
assertEquals("Check that tag was set correctly", TEST_TAG, addedNote.getTags().get(0));
int expectedNumCards = col.getModels().get(mModelId).getJSONArray("tmpls").length();
assertEquals("Check that correct number of cards generated", expectedNumCards, addedNote.cards().size());
// Now delete the note
cr.delete(newNoteUri, null, null);
try {
addedNote.load();
fail("Expected RuntimeException to be thrown when deleting note");
} catch (RuntimeException e) {
// Expect RuntimeException to be thrown when loading deleted note
}
}
/**
* Check that inserting and removing a note into default deck works as expected
*/
public void testInsertTemplate() throws Exception {
// Get required objects for test
final ContentResolver cr = getContext().getContentResolver();
Collection col = CollectionHelper.getInstance().getCol(getContext());
// Add a new basic model that we use for testing purposes (existing models could potentially be corrupted)
JSONObject model = Models.addBasicModel(col, BASIC_MODEL_NAME);
long modelId = model.getLong("id");
// Add the note
Uri modelUri = ContentUris.withAppendedId(FlashCardsContract.Model.CONTENT_URI, modelId);
int testIndex = TEST_MODEL_CARDS.length - 1; // choose the last one because not the same as the basic model template
int expectedOrd = model.getJSONArray("tmpls").length();
ContentValues cv = new ContentValues();
cv.put(FlashCardsContract.CardTemplate.NAME, TEST_MODEL_CARDS[testIndex]);
cv.put(FlashCardsContract.CardTemplate.QUESTION_FORMAT, TEST_MODEL_QFMT[testIndex]);
cv.put(FlashCardsContract.CardTemplate.ANSWER_FORMAT, TEST_MODEL_AFMT[testIndex]);
cv.put(FlashCardsContract.CardTemplate.BROWSER_QUESTION_FORMAT, TEST_MODEL_QFMT[testIndex]);
cv.put(FlashCardsContract.CardTemplate.BROWSER_ANSWER_FORMAT, TEST_MODEL_AFMT[testIndex]);
Uri templatesUri = Uri.withAppendedPath(modelUri, "templates");
Uri templateUri = cr.insert(templatesUri, cv);
col = reopenCol(); // test that the changes are physically saved to the DB
assertNotNull("Check template uri", templateUri);
assertEquals("Check template uri ord", expectedOrd, ContentUris.parseId(templateUri));
JSONObject template = col.getModels().get(modelId).getJSONArray("tmpls").getJSONObject(expectedOrd);
assertEquals("Check template JSONObject ord", expectedOrd, template.getInt("ord"));
assertEquals("Check template name", TEST_MODEL_CARDS[testIndex], template.getString("name"));
assertEquals("Check qfmt", TEST_MODEL_QFMT[testIndex], template.getString("qfmt"));
assertEquals("Check afmt", TEST_MODEL_AFMT[testIndex], template.getString("afmt"));
assertEquals("Check bqfmt", TEST_MODEL_QFMT[testIndex], template.getString("bqfmt"));
assertEquals("Check bafmt", TEST_MODEL_AFMT[testIndex], template.getString("bafmt"));
col.getModels().rem(model);
}
/**
* Check that inserting and removing a note into default deck works as expected
*/
public void testInsertField() throws Exception {
// Get required objects for test
final ContentResolver cr = getContext().getContentResolver();
Collection col = CollectionHelper.getInstance().getCol(getContext());
JSONObject model = Models.addBasicModel(col, BASIC_MODEL_NAME);
long modelId = model.getLong("id");
JSONArray initialFldsArr = model.getJSONArray("flds");
int initialFieldCount = initialFldsArr.length();
Uri noteTypeUri = ContentUris.withAppendedId(FlashCardsContract.Model.CONTENT_URI, modelId);
ContentValues insertFieldValues = new ContentValues();
insertFieldValues.put(FlashCardsContract.Model.FIELD_NAME, TEST_FIELD_NAME);
Uri fieldUri = cr.insert(Uri.withAppendedPath(noteTypeUri, "fields"), insertFieldValues);
assertNotNull("Check field uri", fieldUri);
// Ensure that the changes are physically saved to the DB
col = reopenCol();
model = col.getModels().get(modelId);
// Test the field is as expected
long fieldId = ContentUris.parseId(fieldUri);
assertEquals("Check field id", initialFieldCount, fieldId);
JSONArray fldsArr = model.getJSONArray("flds");
assertEquals("Check fields length", initialFieldCount + 1, fldsArr.length());
assertEquals("Check last field name", TEST_FIELD_NAME, fldsArr.getJSONObject(fldsArr.length() - 1).optString("name", ""));
col.getModels().rem(model);
}
/**
* Test queries to notes table using direct SQL URI
*/
public void testQueryDirectSqlQuery() {
// search for correct mid
final ContentResolver cr = getContext().getContentResolver();
Cursor cursor = cr.query(FlashCardsContract.Note.CONTENT_URI_V2, null, String.format("mid=%d", mModelId), null, null);
assertNotNull(cursor);
try {
assertEquals("Check number of results", mCreatedNotes.size(), cursor.getCount());
} finally {
cursor.close();
}
// search for bogus mid
cursor = cr.query(FlashCardsContract.Note.CONTENT_URI_V2, null, "mid=0", null, null);
assertNotNull(cursor);
try {
assertEquals("Check number of results", 0, cursor.getCount());
} finally {
cursor.close();
}
// check usage of selection args
cursor = cr.query(FlashCardsContract.Note.CONTENT_URI_V2, null, "mid=?", new String[]{"0"}, null);
assertNotNull(cursor);
}
/**
* Test that a query for all the notes added in setup() looks correct
*/
public void testQueryNoteIds() {
final ContentResolver cr = getContext().getContentResolver();
// Query all available notes
final Cursor allNotesCursor = cr.query(FlashCardsContract.Note.CONTENT_URI, null, "tag:" + TEST_TAG, null, null);
assertNotNull(allNotesCursor);
try {
assertEquals("Check number of results", mCreatedNotes.size(), allNotesCursor.getCount());
while (allNotesCursor.moveToNext()) {
// Check that it's possible to leave out columns from the projection
for (int i = 0; i < FlashCardsContract.Note.DEFAULT_PROJECTION.length; i++) {
String[] projection = removeFromProjection(FlashCardsContract.Note.DEFAULT_PROJECTION, i);
String noteId = allNotesCursor.getString(allNotesCursor.getColumnIndex(FlashCardsContract.Note._ID));
Uri noteUri = Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI, noteId);
final Cursor singleNoteCursor = cr.query(noteUri, projection, null, null, null);
assertNotNull("Check that there is a valid cursor for detail data", singleNoteCursor);
try {
assertEquals("Check that there is exactly one result", 1, singleNoteCursor.getCount());
assertTrue("Move to beginning of cursor after querying for detail data", singleNoteCursor.moveToFirst());
// Check columns
assertEquals("Check column count", projection.length, singleNoteCursor.getColumnCount());
for (int j = 0; j < projection.length; j++) {
assertEquals("Check column name " + j, projection[j], singleNoteCursor.getColumnName(j));
}
} finally {
singleNoteCursor.close();
}
}
}
} finally {
allNotesCursor.close();
}
}
/**
* Check that a valid Cursor is returned when querying notes table with non-default projections
*/
public void testQueryNotesProjection() {
final ContentResolver cr = getContext().getContentResolver();
// Query all available notes
for (int i = 0; i < FlashCardsContract.Note.DEFAULT_PROJECTION.length; i++) {
String[] projection = removeFromProjection(FlashCardsContract.Note.DEFAULT_PROJECTION, i);
final Cursor allNotesCursor = cr.query(FlashCardsContract.Note.CONTENT_URI, projection, "tag:" + TEST_TAG, null, null);
assertNotNull("Check that there is a valid cursor", allNotesCursor);
try {
assertEquals("Check number of results", mCreatedNotes.size(), allNotesCursor.getCount());
// Check columns
assertEquals("Check column count", projection.length, allNotesCursor.getColumnCount());
for (int j = 0; j < projection.length; j++) {
assertEquals("Check column name " + j, projection[j], allNotesCursor.getColumnName(j));
}
} finally {
allNotesCursor.close();
}
}
}
private String[] removeFromProjection(String[] inputProjection, int idx) {
String[] outputProjection = new String[inputProjection.length - 1];
for (int i = 0; i < idx; i++) {
outputProjection[i] = inputProjection[i];
}
for (int i = idx + 1; i < inputProjection.length; i++) {
outputProjection[i - 1] = inputProjection[i];
}
return outputProjection;
}
/**
* Check that updating the flds column works as expected
*/
public void testUpdateNoteFields() {
final ContentResolver cr = getContext().getContentResolver();
ContentValues cv = new ContentValues();
// Change the fields so that the first field is now "newTestValue"
String[] dummyFields2 = mDummyFields.clone();
dummyFields2[0] = TEST_FIELD_VALUE;
for (Uri uri: mCreatedNotes) {
// Update the flds
cv.put(FlashCardsContract.Note.FLDS, Utils.joinFields(dummyFields2));
cr.update(uri, cv, null, null);
// Query the table again
Cursor noteCursor = cr.query(uri, FlashCardsContract.Note.DEFAULT_PROJECTION, null, null, null);
try {
assertNotNull("Check that there is a valid cursor for detail data after update", noteCursor);
assertEquals("Check that there is one and only one entry after update", 1, noteCursor.getCount());
assertTrue("Move to first item in cursor", noteCursor.moveToFirst());
String[] newFlds = Utils.splitFields(
noteCursor.getString(noteCursor.getColumnIndex(FlashCardsContract.Note.FLDS)));
assertTrue("Check that the flds have been updated correctly", Arrays.equals(newFlds, dummyFields2));
} finally {
noteCursor.close();
}
}
}
/**
* Check that inserting a new model works as expected
*/
public void testInsertAndUpdateModel() throws Exception {
final ContentResolver cr = getContext().getContentResolver();
ContentValues cv = new ContentValues();
// Insert a new model
cv.put(FlashCardsContract.Model.NAME, TEST_MODEL_NAME);
cv.put(FlashCardsContract.Model.FIELD_NAMES, Utils.joinFields(TEST_MODEL_FIELDS));
cv.put(FlashCardsContract.Model.NUM_CARDS, TEST_MODEL_CARDS.length);
Uri modelUri = cr.insert(FlashCardsContract.Model.CONTENT_URI, cv);
assertNotNull("Check inserted model isn't null", modelUri);
long mid = Long.parseLong(modelUri.getLastPathSegment());
Collection col = reopenCol();
try {
JSONObject model = col.getModels().get(mid);
assertEquals("Check model name", TEST_MODEL_NAME, model.getString("name"));
assertEquals("Check templates length", TEST_MODEL_CARDS.length, model.getJSONArray("tmpls").length());
assertEquals("Check field length", TEST_MODEL_FIELDS.length, model.getJSONArray("flds").length());
JSONArray flds = model.getJSONArray("flds");
for (int i = 0; i < flds.length(); i++) {
assertEquals("Check name of fields", flds.getJSONObject(i).getString("name"), TEST_MODEL_FIELDS[i]);
}
// Test updating the model CSS (to test updating MODELS_ID Uri)
cv = new ContentValues();
cv.put(FlashCardsContract.Model.CSS, TEST_MODEL_CSS);
assertTrue(cr.update(modelUri, cv, null, null) > 0);
col = reopenCol();
assertEquals("Check css", TEST_MODEL_CSS, col.getModels().get(mid).getString("css"));
// Update each of the templates in model (to test updating MODELS_ID_TEMPLATES_ID Uri)
for (int i = 0; i < TEST_MODEL_CARDS.length; i++) {
cv = new ContentValues();
cv.put(FlashCardsContract.CardTemplate.NAME, TEST_MODEL_CARDS[i]);
cv.put(FlashCardsContract.CardTemplate.QUESTION_FORMAT, TEST_MODEL_QFMT[i]);
cv.put(FlashCardsContract.CardTemplate.ANSWER_FORMAT, TEST_MODEL_AFMT[i]);
cv.put(FlashCardsContract.CardTemplate.BROWSER_QUESTION_FORMAT, TEST_MODEL_QFMT[i]);
cv.put(FlashCardsContract.CardTemplate.BROWSER_ANSWER_FORMAT, TEST_MODEL_AFMT[i]);
Uri tmplUri = Uri.withAppendedPath(Uri.withAppendedPath(modelUri, "templates"), Integer.toString(i));
assertTrue("Update rows", cr.update(tmplUri, cv, null, null) > 0);
col = reopenCol();
JSONObject template = col.getModels().get(mid).getJSONArray("tmpls").getJSONObject(i);
assertEquals("Check template name", TEST_MODEL_CARDS[i], template.getString("name"));
assertEquals("Check qfmt", TEST_MODEL_QFMT[i], template.getString("qfmt"));
assertEquals("Check afmt", TEST_MODEL_AFMT[i], template.getString("afmt"));
assertEquals("Check bqfmt", TEST_MODEL_QFMT[i], template.getString("bqfmt"));
assertEquals("Check bafmt", TEST_MODEL_AFMT[i], template.getString("bafmt"));
}
} finally {
// Delete the model (this will force a full-sync)
try {
col.modSchema(false);
col.getModels().rem(col.getModels().get(mid));
} catch (ConfirmModSchemaException e) {
// This will never happen
throw new IllegalStateException("Unexpected ConfirmModSchemaException trying to remove model");
}
}
}
/**
* Query .../models URI
*/
public void testQueryAllModels() {
final ContentResolver cr = getContext().getContentResolver();
// Query all available models
final Cursor allModels = cr.query(FlashCardsContract.Model.CONTENT_URI, null, null, null, null);
assertNotNull(allModels);
try {
assertTrue("Check that there is at least one result", allModels.getCount() > 0);
while (allModels.moveToNext()) {
long modelId = allModels.getLong(allModels.getColumnIndex(FlashCardsContract.Model._ID));
Uri modelUri = Uri.withAppendedPath(FlashCardsContract.Model.CONTENT_URI, Long.toString(modelId));
final Cursor singleModel = cr.query(modelUri, null, null, null, null);
assertNotNull(singleModel);
try {
assertEquals("Check that there is exactly one result", 1, singleModel.getCount());
assertTrue("Move to beginning of cursor", singleModel.moveToFirst());
String nameFromModels = allModels.getString(allModels.getColumnIndex(FlashCardsContract.Model.NAME));
String nameFromModel = singleModel.getString(allModels.getColumnIndex(FlashCardsContract.Model.NAME));
assertEquals("Check that model names are the same", nameFromModel, nameFromModels);
String flds = allModels.getString(allModels.getColumnIndex(FlashCardsContract.Model.FIELD_NAMES));
assertTrue("Check that valid number of fields", Utils.splitFields(flds).length >= 1);
Integer numCards = allModels.getInt(allModels.getColumnIndex(FlashCardsContract.Model.NUM_CARDS));
assertTrue("Check that valid number of cards", numCards >= 1);
} finally {
singleModel.close();
}
}
} finally {
allModels.close();
}
}
/**
* Move all the cards from their old decks to the first deck that was added in setup()
*/
public void testMoveCardsToOtherDeck() {
final ContentResolver cr = getContext().getContentResolver();
// Query all available notes
final Cursor allNotesCursor = cr.query(FlashCardsContract.Note.CONTENT_URI, null, "tag:" + TEST_TAG, null, null);
assertNotNull(allNotesCursor);
try {
assertEquals("Check number of results", mCreatedNotes.size(), allNotesCursor.getCount());
while (allNotesCursor.moveToNext()) {
// Now iterate over all cursors
Uri cardsUri = Uri.withAppendedPath(Uri.withAppendedPath(FlashCardsContract.Note.CONTENT_URI,
allNotesCursor.getString(allNotesCursor.getColumnIndex(FlashCardsContract.Note._ID))), "cards");
final Cursor cardsCursor = cr.query(cardsUri, null, null, null, null);
assertNotNull("Check that there is a valid cursor after query for cards", cardsCursor);
try {
assertTrue("Check that there is at least one result for cards", cardsCursor.getCount() > 0);
while (cardsCursor.moveToNext()) {
long targetDid = mTestDeckIds[0];
// Move to test deck (to test NOTES_ID_CARDS_ORD Uri)
ContentValues values = new ContentValues();
values.put(FlashCardsContract.Card.DECK_ID, targetDid);
Uri cardUri = Uri.withAppendedPath(cardsUri,
cardsCursor.getString(cardsCursor.getColumnIndex(FlashCardsContract.Card.CARD_ORD)));
cr.update(cardUri, values, null, null);
reopenCol();
Cursor movedCardCur = cr.query(cardUri, null, null, null, null);
assertNotNull("Check that there is a valid cursor after moving card", movedCardCur);
assertTrue("Move to beginning of cursor after moving card", movedCardCur.moveToFirst());
long did = movedCardCur.getLong(movedCardCur.getColumnIndex(FlashCardsContract.Card.DECK_ID));
assertEquals("Make sure that card is in new deck", targetDid, did);
}
} finally {
cardsCursor.close();
}
}
} finally {
allNotesCursor.close();
}
}
/**
* Check that querying the current model gives a valid result
*/
public void testQueryCurrentModel() {
final ContentResolver cr = getContext().getContentResolver();
Uri uri = Uri.withAppendedPath(FlashCardsContract.Model.CONTENT_URI, FlashCardsContract.Model.CURRENT_MODEL_ID);
final Cursor modelCursor = cr.query(uri, null, null, null, null);
assertNotNull(modelCursor);
try {
assertEquals("Check that there is exactly one result", 1, modelCursor.getCount());
assertTrue("Move to beginning of cursor", modelCursor.moveToFirst());
assertNotNull("Check non-empty field names",
modelCursor.getString(modelCursor.getColumnIndex(FlashCardsContract.Model.FIELD_NAMES)));
assertTrue("Check at least one template",
modelCursor.getInt(modelCursor.getColumnIndex(FlashCardsContract.Model.NUM_CARDS)) > 0);
} finally {
modelCursor.close();
}
}
/**
* Check that an Exception is thrown when unsupported operations are performed
*/
public void testUnsupportedOperations() {
final ContentResolver cr = getContext().getContentResolver();
ContentValues dummyValues = new ContentValues();
Uri[] updateUris = {
// Can't update most tables in bulk -- only via ID
FlashCardsContract.Note.CONTENT_URI,
FlashCardsContract.Model.CONTENT_URI,
FlashCardsContract.Deck.CONTENT_ALL_URI,
FlashCardsContract.Note.CONTENT_URI.buildUpon()
.appendPath("1234")
.appendPath("cards")
.build(),
};
for (Uri uri : updateUris) {
try {
cr.update(uri, dummyValues, null, null);
fail("Update on " + uri + " was supposed to throw exception");
} catch (UnsupportedOperationException e) {
// This was expected ...
} catch (IllegalArgumentException e) {
// ... or this.
}
}
Uri[] deleteUris = {
FlashCardsContract.Note.CONTENT_URI,
// Only note/<id> is supported
FlashCardsContract.Note.CONTENT_URI.buildUpon()
.appendPath("1234")
.appendPath("cards")
.build(),
FlashCardsContract.Note.CONTENT_URI.buildUpon()
.appendPath("1234")
.appendPath("cards")
.appendPath("2345")
.build(),
FlashCardsContract.Model.CONTENT_URI,
FlashCardsContract.Model.CONTENT_URI.buildUpon()
.appendPath("1234")
.build(),
};
for (Uri uri : deleteUris) {
try {
cr.delete(uri, null, null);
fail("Delete on " + uri + " was supposed to throw exception");
} catch (UnsupportedOperationException e) {
// This was expected
}
}
Uri[] insertUris = {
// Can't do an insert with specific ID on the following tables
FlashCardsContract.Note.CONTENT_URI.buildUpon()
.appendPath("1234")
.build(),
FlashCardsContract.Note.CONTENT_URI.buildUpon()
.appendPath("1234")
.appendPath("cards")
.build(),
FlashCardsContract.Note.CONTENT_URI.buildUpon()
.appendPath("1234")
.appendPath("cards")
.appendPath("2345")
.build(),
FlashCardsContract.Model.CONTENT_URI.buildUpon()
.appendPath("1234")
.build(),
};
for (Uri uri : insertUris) {
try {
cr.insert(uri, dummyValues);
fail("Insert on " + uri + " was supposed to throw exception");
} catch (UnsupportedOperationException e) {
// This was expected ...
} catch (IllegalArgumentException e) {
// ... or this.
}
}
}
/**
* Test query to decks table
* @throws Exception
*/
public void testQueryAllDecks() throws Exception{
Collection col;
col = CollectionHelper.getInstance().getCol(getContext());
Decks decks = col.getDecks();
Cursor decksCursor = getContext().getContentResolver()
.query(FlashCardsContract.Deck.CONTENT_ALL_URI, FlashCardsContract.Deck.DEFAULT_PROJECTION, null, null, null);
assertNotNull(decksCursor);
try {
assertEquals("Check number of results", decks.count(), decksCursor.getCount());
while (decksCursor.moveToNext()) {
long deckID = decksCursor.getLong(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_ID));
String deckName = decksCursor.getString(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_NAME));
JSONObject deck = decks.get(deckID);
assertNotNull("Check that the deck we received actually exists", deck);
assertEquals("Check that the received deck has the correct name", deck.getString("name"), deckName);
}
} finally {
decksCursor.close();
}
}
/**
* Test query to specific deck ID
* @throws Exception
*/
public void testQueryCertainDeck() throws Exception {
Collection col;
col = CollectionHelper.getInstance().getCol(getContext());
long deckId = mTestDeckIds[0];
Uri deckUri = Uri.withAppendedPath(FlashCardsContract.Deck.CONTENT_ALL_URI, Long.toString(deckId));
Cursor decksCursor = getContext().getContentResolver().query(deckUri, null, null, null, null);
try {
if (decksCursor == null || !decksCursor.moveToFirst()) {
fail("No deck received. Should have delivered deck with id " + deckId);
} else {
long returnedDeckID = decksCursor.getLong(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_ID));
String returnedDeckName = decksCursor.getString(decksCursor.getColumnIndex(FlashCardsContract.Deck.DECK_NAME));
JSONObject realDeck = col.getDecks().get(deckId);
assertEquals("Check that received deck ID equals real deck ID", deckId, returnedDeckID);
assertEquals("Check that received deck name equals real deck name", realDeck.getString("name"), returnedDeckName);
}
} finally {
decksCursor.close();
}
}
/**
* Test that query for the next card in the schedule returns a valid result without any deck selector
*/
public void testQueryNextCard(){
Collection col;
col = CollectionHelper.getInstance().getCol(getContext());
Sched sched = col.getSched();
Cursor reviewInfoCursor = getContext().getContentResolver().query(
FlashCardsContract.ReviewInfo.CONTENT_URI, null, null, null, null);
assertNotNull(reviewInfoCursor);
assertEquals("Check that we actually received one card", 1, reviewInfoCursor.getCount());
reviewInfoCursor.moveToFirst();
int cardOrd = reviewInfoCursor.getInt(reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.CARD_ORD));
long noteID = reviewInfoCursor.getLong(reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.NOTE_ID));
Card nextCard = null;
for(int i = 0; i < 10; i++) {//minimizing fails, when sched.reset() randomly chooses between multiple cards
sched.reset();
nextCard = sched.getCard();
if(nextCard.note().getId() == noteID && nextCard.getOrd() == cardOrd)break;
}
assertNotNull("Check that there actually is a next scheduled card", nextCard);
assertEquals("Check that received card and actual card have same note id", nextCard.note().getId(), noteID);
assertEquals("Check that received card and actual card have same card ord", nextCard.getOrd(), cardOrd);
}
/**
* Test that query for the next card in the schedule returns a valid result WITH a deck selector
*/
public void testQueryCardFromCertainDeck(){
long deckToTest = mTestDeckIds[0];
String deckSelector = "deckID=?";
String deckArguments[] = {Long.toString(deckToTest)};
Collection col;
col = CollectionHelper.getInstance().getCol(getContext());
Sched sched = col.getSched();
long selectedDeckBeforeTest = col.getDecks().selected();
col.getDecks().select(1); //select Default deck
Cursor reviewInfoCursor = getContext().getContentResolver().query(
FlashCardsContract.ReviewInfo.CONTENT_URI, null, deckSelector, deckArguments, null);
assertNotNull(reviewInfoCursor);
assertEquals("Check that we actually received one card", 1, reviewInfoCursor.getCount());
try {
reviewInfoCursor.moveToFirst();
int cardOrd = reviewInfoCursor.getInt(reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.CARD_ORD));
long noteID = reviewInfoCursor.getLong(reviewInfoCursor.getColumnIndex(FlashCardsContract.ReviewInfo.NOTE_ID));
assertEquals("Check that the selected deck has not changed", 1, col.getDecks().selected());
col.getDecks().select(deckToTest);
Card nextCard = null;
for(int i = 0; i < 10; i++) {//minimizing fails, when sched.reset() randomly chooses between multiple cards
sched.reset();
nextCard = sched.getCard();
if(nextCard.note().getId() == noteID && nextCard.getOrd() == cardOrd)break;
}
assertNotNull("Check that there actually is a next scheduled card", nextCard);
assertEquals("Check that received card and actual card have same note id", nextCard.note().getId(), noteID);
assertEquals("Check that received card and actual card have same card ord", nextCard.getOrd(), cardOrd);
}finally {
reviewInfoCursor.close();
}
col.getDecks().select(selectedDeckBeforeTest);
}
/**
* Test changing the selected deck
*/
public void testSetSelectedDeck(){
long deckId = mTestDeckIds[0];
ContentResolver cr = getContext().getContentResolver();
Uri selectDeckUri = FlashCardsContract.Deck.CONTENT_SELECTED_URI;
ContentValues values = new ContentValues();
values.put(FlashCardsContract.Deck.DECK_ID, deckId);
cr.update(selectDeckUri, values, null, null);
Collection col = reopenCol();
assertEquals("Check that the selected deck has been correctly set", deckId, col.getDecks().selected());
}
/**
* Test giving the answer for a reviewed card
*/
public void testAnswerCard(){
Collection col;
col = CollectionHelper.getInstance().getCol(getContext());
long deckId = mTestDeckIds[0];
col.getDecks().select(deckId);
Card card = col.getSched().getCard();
ContentResolver cr = getContext().getContentResolver();
Uri reviewInfoUri = FlashCardsContract.ReviewInfo.CONTENT_URI;
ContentValues values = new ContentValues();
long noteId = card.note().getId();
int cardOrd = card.getOrd();
int ease = AbstractFlashcardViewer.EASE_3; //<- insert real ease here
values.put(FlashCardsContract.ReviewInfo.NOTE_ID, noteId);
values.put(FlashCardsContract.ReviewInfo.CARD_ORD, cardOrd);
values.put(FlashCardsContract.ReviewInfo.EASE, ease);
int updateCount = cr.update(reviewInfoUri, values, null, null);
assertEquals("Check if update returns 1", 1, updateCount);
col.getSched().reset();
Card newCard = col.getSched().getCard();
if(newCard != null){
if(newCard.note().getId() == card.note().getId() && newCard.getOrd() == card.getOrd()){
fail("Next scheduled card has not changed");
}
}else{
//We expected this
}
}
private Collection reopenCol() {
CollectionHelper.getInstance().closeCollection(false);
return CollectionHelper.getInstance().getCol(getContext());
}
}