package com.nononsenseapps.notepad.test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import com.nononsenseapps.notepad.data.model.sql.DAO;
import com.nononsenseapps.notepad.data.model.sql.Task;
import com.nononsenseapps.notepad.data.model.sql.TaskList;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteConstraintException;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.util.Log;
public class DBProviderMovementTest extends AndroidTestCase {
private ContentResolver resolver;
private Context context;
@Override
public void setUp() throws Exception {
super.setUp();
context = getContext();
resolver = context.getContentResolver();
}
@Override
public void tearDown() throws Exception {
super.tearDown();
}
/*
* Util methods
*/
private void assertUriReturnsResult(final Uri uri, final String[] fields) {
assertUriReturnsResult(uri, fields, null, null);
}
private void assertUriReturnsResult(final Uri uri, final String[] fields,
final String where, final String[] whereArgs) {
final Cursor c = resolver.query(uri, fields, where, whereArgs, null);
final boolean notEmpty = c.moveToFirst();
c.close();
assertTrue("Uri did not return a result: " + uri.getEncodedPath(),
notEmpty);
}
private Cursor assertCursorGood(final Cursor c) {
assertNotNull(c);
assertEquals(false, c.isClosed());
return c;
}
private void assertTasksCountIs(final long listId, final int count) {
assertEquals(count, getTasks(listId).size());
}
private void assertTaskLeftRightAreSequential(final long listId) {
// Get ordered
ArrayList<Task> tasks = getTasks(listId);
long prev = 0;
for (Task t : tasks) {
assertTrue("Left must be less than right! " + t.left + " !< "
+ t.right, t.left < t.right);
assertTrue("Previous item must have smaller left",
prev < (long) t.left);
if (t.right == t.left + 1) {
prev = t.right;
}
else {
prev = t.left;
}
}
// Test maximum value
Cursor c = resolver.query(Task.URI, Task.Columns.FIELDS,
Task.Columns.DBLIST + " IS ?",
new String[] { Long.toString(listId) }, Task.Columns.RIGHT
+ " DESC");
assertCursorGood(c);
HashSet<Long> positions = new HashSet<Long>();
if (c.getCount() > 0) {
// Right most will be twice the number of tasks
assertTrue(c.moveToFirst());
final Task last = new Task(c);
assertTrue(String.format("%d != 2 * %d", last.right, c.getCount()),
last.right == 2 * c.getCount());
// Make sure there are no duplicates and such
for (long i = 1; i <= c.getCount() * 2; i++) {
positions.add(i);
}
positions.remove(last.left);
positions.remove(last.right);
while (c.moveToNext()) {
Task task = new Task(c);
positions.remove(task.left);
positions.remove(task.right);
}
}
c.close();
assertEquals("Must be duplicate positions in the list", 0,
positions.size());
}
private TaskList insertList() {
final TaskList tl = new TaskList();
tl.title = "A test list";
tl.setId(resolver.insert(tl.getBaseUri(), tl.getContent()));
assertTrue(0 < tl._id);
return tl;
}
private void deleteList(final TaskList tl) {
assertTrue(0 < resolver.delete(tl.getUri(), null, null));
}
private ArrayList<Task> insertTasks(final long listId, final int number) {
ArrayList<Task> results = new ArrayList<Task>(number);
for (int i = 0; i < number; i++) {
int count = 0;
Task t = new Task();
t.title = "Task" + ++count;
t.dblist = listId;
Uri uri = resolver.insert(Task.URI, t.getContent());
if (uri != null) {
t.setId(uri);
results.add(t);
}
assertTaskLeftRightAreSequential(listId);
}
assertTaskLeftRightAreSequential(listId);
assertTasksCountIs(listId, number);
return results;
}
private ArrayList<Task> getTasks(final long listId) {
final ArrayList<Task> results = new ArrayList<Task>();
final Cursor c = resolver.query(Task.URI,
Task.Columns.FIELDS, Task.Columns.DBLIST + " IS ?",
new String[] { Long.toString(listId) }, Task.Columns.LEFT);
assertCursorGood(c);
while (c.moveToNext()) {
results.add(new Task(c));
}
c.close();
return results;
}
private Task getTask(final long id) {
final Cursor c = resolver.query(Task.getUri(id), Task.Columns.FIELDS,
null, null, null);
assertCursorGood(c);
Task t = null;
if (c.moveToFirst()) {
t = new Task(c);
}
c.close();
return t;
}
private ArrayList<Task> getDeletedTask(final String title) {
final ArrayList<Task> results = new ArrayList<Task>();
final Cursor c = resolver.query(Task.URI_DELETED_QUERY,
Task.Columns.FIELDS, Task.Columns.TITLE + "IS ?",
new String[] { title }, null);
assertCursorGood(c);
while (c.moveToNext()) {
results.add(new Task(c));
}
c.close();
return results;
}
private void deleteTasks(final ArrayList<Task> tasks) {
for (final Task t : tasks) {
deleteTask(t);
}
}
private void deleteTask(Task t) {
assertTrue(0 < resolver.delete(t.getUri(), null, null));
assertTaskLeftRightAreSequential(t.dblist);
}
private void moveTasksToList(final TaskList tl, final Task... ts) {
long[] ids = new long[ts.length];
for (int i = 0; i < ts.length; i++) {
ids[i] = ts[i]._id;
}
final ContentValues val = new ContentValues();
val.put(Task.Columns.DBLIST, tl._id);
// where _ID in (1, 2, 3)
final String whereId = new StringBuilder(Task.Columns._ID).append(" IN (")
.append(DAO.arrayToCommaString(ids)).append(")").toString();
mContext.getContentResolver().update(Task.URI, val, whereId, null);
// Verify that task was moved
// Check new
assertTaskLeftRightAreSequential(tl._id);
}
private ArrayList<Task> moveAndAssert(final TaskList tl, final int fromPos,
final int toPos) {
Log.i("nononsenseapps test", "Testing move from: " + fromPos + " to "
+ toPos);
// Get ordered
final ArrayList<Task> oldtasks = getTasks(tl._id);
// Move 5 to 4
final Task movingTask = oldtasks.get(fromPos);
final Task targetTask = oldtasks.get(toPos);
final int result = movingTask.moveTo(resolver, targetTask);
// Verity that things changed or not
if (movingTask._id != targetTask._id)
assertTrue("Moving a task should update rows", 0 < result);
else
assertTrue("Moving a task to itself shouldn't change anything",
0 == result);
// Find new values
final ArrayList<Task> newtasks = getTasks(tl._id);
Task newone = null;
Task newtarget = null;
for (Task t : newtasks) {
if (t._id == movingTask._id) {
newone = t;
}
if (t._id == targetTask._id) {
newtarget = t;
}
}
Log.d("nononsenseapps test", "old, target, new, newtarget: "
+ movingTask.left + "," + movingTask.right + " "
+ targetTask.left + "," + targetTask.right + " " + newone.left
+ "," + newone.right + " " + newtarget.left + ","
+ newtarget.right);
assertNotNull("Couldnt find the moved task", newone);
if (targetTask.left < movingTask.left) {
assertEquals("Left value does not equal target", targetTask.left,
newone.left);
assertEquals("Right value does not equal target left + 1",
targetTask.left + 1, (long) newone.right);
}
else if (targetTask.right > movingTask.right) {
assertEquals("Left value does not equal target right - 1",
targetTask.right - 1, (long) newone.left);
assertEquals("Right value does not equal target", targetTask.right,
newone.right);
}
assertEquals("Width should be 1 after a move", 1, newone.right
- newone.left);
// assertEquals("Target should have moved 2 steps", 2,);
assertTrue("Number of tasks should not change",
oldtasks.size() == newtasks.size());
assertTaskLeftRightAreSequential(tl._id);
return getTasks(tl._id);
}
/*
* Test methods
*/
public void testDeleteList() {
final TaskList tl = insertList();
final int count = 10;
final long listId = tl._id;
assertTasksCountIs(listId, 0);
insertTasks(listId, count);
assertTasksCountIs(listId, count);
deleteList(tl);
// Should return nothing
// Cursor c = resolver.query(TaskList.URI, TaskList.Columns.FIELDS,
// TaskList.Columns._ID + " IS ?",
// new String[] { Long.toString(listId) }, null);
// assertCursorGood(c);
// assertEquals("List should be gone", 0, c.getCount());
// removing list should delete all tasks within
assertTasksCountIs(listId, 0);
// c.close();
}
public void testInsertAndRemoveTasks() {
final TaskList tl = insertList();
ArrayList<Task> tasks = insertTasks(tl._id, 10);
assertTasksCountIs(tl._id, 10);
deleteTasks(tasks);
assertTasksCountIs(tl._id, 0);
deleteList(tl);
}
public void testInsertTaskInWrongList() {
// Should not be possible to insert
// into a non-existing list because of foreign key constraints
final long wrongId = 92525;
assertTasksCountIs(wrongId, 0);
Task t = new Task();
t.title = "Task";
t.dblist = wrongId;
boolean thrown = false;
Uri uri = null;
try {
uri = resolver.insert(Task.URI, t.getContent());
}
catch (SQLException e) {
thrown = true;
}
assertTrue(uri == null);
assertTasksCountIs(wrongId, 0);
}
public void testInvalidPos() {
// Positions must be greater than 0
// or should throw constraint failed
TaskList tl = insertList();
ArrayList<Task> ts = insertTasks(tl._id, 1);
Task t = ts.get(0);
t.left = 0L;
boolean failed = false;
try {
resolver.update(t.getUri(), t.getContent(), null, null);
}
catch (SQLiteConstraintException e) {
failed = true;
}
//assertTrue("Setting left to 0 should throw exception!", failed);
t.left = 5L;
t.right = 0L;
failed = false;
try {
resolver.update(t.getUri(), t.getContent(), null, null);
}
catch (SQLiteConstraintException e) {
failed = true;
}
//assertTrue("Setting right to 0 should throw exception", failed);
deleteList(tl);
}
public void testMoveTask() {
final TaskList tl = insertList();
int count = 10;
insertTasks(tl._id, count);
assertTaskLeftRightAreSequential(tl._id);
// Move some tasks around
moveAndAssert(tl, 0, count - 1);
moveAndAssert(tl, count - 1, 0);
moveAndAssert(tl, 1, count - 2);
moveAndAssert(tl, count - 2, 1);
moveAndAssert(tl, 4, 0);
moveAndAssert(tl, 4, 9);
for (int i = 0; i < count * 2; i++) {
moveAndAssert(tl, 0, count - 1);
}
for (int i = 0; i < count * 2; i++) {
moveAndAssert(tl, count - 2, 2);
}
Random rand = new Random();
int min = 0, max = count - 1;
for (int i = 0; i < count * 2; i++) {
int fromPos = rand.nextInt(max - min + 1) + min;
int toPos = fromPos;
while (toPos == fromPos) {
toPos = rand.nextInt(max - min + 1) + min;
}
// two unique positions generated, now move
moveAndAssert(tl, fromPos, toPos);
}
// Clean up
deleteList(tl);
}
public void testMoveTaskToList() {
final TaskList tl = insertList();
final TaskList tl2 = insertList();
int count = 10;
ArrayList<Task> tasks1 = insertTasks(tl._id, count);
ArrayList<Task> tasks2 = insertTasks(tl2._id, count);
assertTaskLeftRightAreSequential(tl._id);
assertTaskLeftRightAreSequential(tl2._id);
// Move some tasks around
Random rand = new Random();
int min = 0, max = count - 1;
for (int i = 0; i < 100; i++) {
if (rand.nextBoolean() && tasks1.size() > 1) {
int taskIndex = rand.nextInt(tasks1.size());
Task t1 = tasks1.remove(taskIndex);
taskIndex = rand.nextInt(tasks1.size());
Task t2 = tasks1.remove(taskIndex);
moveTasksToList(tl2, t1, t2);
tasks2.add(t1);
tasks2.add(t2);
}
else if (tasks2.size() > 1) {
int taskIndex = rand.nextInt(tasks2.size());
Task t1 = tasks2.remove(taskIndex);
taskIndex = rand.nextInt(tasks2.size());
Task t2 = tasks2.remove(taskIndex);
moveTasksToList(tl, t1, t2);
tasks1.add(t1);
tasks1.add(t2);
}
}
// Clean up
deleteList(tl);
deleteList(tl2);
}
// public void testIndents() {
// final TaskList tl = insertList();
// int count = 7;
// insertTasks(tl._id, count);
// ArrayList<Task> orgTasks = getTasks(tl._id);
//
// // Indenting the first item should fail (not change anything)
// // as it's impossible to do
//
// indentAndAssert(orgTasks.get(0), false);
//
// // Test a successful one
// /*
// * a0 b1 c0 d0 e0
// */
// orgTasks = indentAndAssert(orgTasks.get(1), true);
// assertEquals("Task level should be two", 2, orgTasks.get(1).level);
//
// // Indenting it again should fail though
// /*
// * a0 b1 c0 d0 e0 f0 g0
// */
// orgTasks = indentAndAssert(orgTasks.get(1), false);
// assertEquals("Task level should still be two", 2, orgTasks.get(1).level);
//
// // Try last item
// /*
// * a0 b1 c0 d0 e0 f0 g1
// */
// orgTasks = indentAndAssert(orgTasks.get(count - 1), true);
// assertEquals("Task level should be two", 2,
// orgTasks.get(count - 1).level);
//
// /*
// * a0 b1 c2 d1 e0 f1 g2
// */
// orgTasks = indentAndAssert(orgTasks.get(2), true);
// orgTasks = indentAndAssert(orgTasks.get(2), true);
// orgTasks = indentAndAssert(orgTasks.get(2), false);
// assertEquals("Task level should be three", 3, orgTasks.get(2).level);
// orgTasks = indentAndAssert(orgTasks.get(3), true);
// assertEquals("Task level incorrect", 2, orgTasks.get(3).level);
// orgTasks = indentAndAssert(orgTasks.get(5), true);
// orgTasks = indentAndAssert(orgTasks.get(5), false);
// assertEquals("Task level incorrect", 2, orgTasks.get(5).level);
// orgTasks = indentAndAssert(orgTasks.get(6), true);
// orgTasks = indentAndAssert(orgTasks.get(6), false);
// assertEquals("Task level incorrect", 3, orgTasks.get(6).level);
//
// /*
// * a0 b1 c2 d3 e4 f5 g6
// */
// orgTasks = indentAndAssert(orgTasks.get(0), false);
// orgTasks = indentAndAssert(orgTasks.get(1), false);
// orgTasks = indentAndAssert(orgTasks.get(2), false);
// orgTasks = indentAndAssert(orgTasks.get(3), true);
// orgTasks = indentAndAssert(orgTasks.get(3), true);
// orgTasks = indentAndAssert(orgTasks.get(3), false);
// orgTasks = indentAndAssert(orgTasks.get(4), true);
// orgTasks = indentAndAssert(orgTasks.get(4), true);
// orgTasks = indentAndAssert(orgTasks.get(4), true);
// orgTasks = indentAndAssert(orgTasks.get(4), true);
// orgTasks = indentAndAssert(orgTasks.get(4), false);
// orgTasks = indentAndAssert(orgTasks.get(5), true);
// orgTasks = indentAndAssert(orgTasks.get(5), true);
// orgTasks = indentAndAssert(orgTasks.get(5), true);
// orgTasks = indentAndAssert(orgTasks.get(5), true);
// orgTasks = indentAndAssert(orgTasks.get(5), false);
// orgTasks = indentAndAssert(orgTasks.get(6), true);
// orgTasks = indentAndAssert(orgTasks.get(6), true);
// orgTasks = indentAndAssert(orgTasks.get(6), true);
// orgTasks = indentAndAssert(orgTasks.get(6), true);
// orgTasks = indentAndAssert(orgTasks.get(6), false);
//
// for (int i = 0; i < orgTasks.size(); i++) {
// assertEquals("Task level incorrect", 1 + i, orgTasks.get(i).level);
// }
//
// // Let's start unindenting stuff!
//
// // Unindent root should fail
// orgTasks = unIndentAndAssert(orgTasks.get(0), false);
// // Last one should succeed many times
// for (int i = orgTasks.size(); i > 1; i--) {
// assertEquals("Level incorrect (i = " + i + ")", i,
// orgTasks.get(orgTasks.size() - 1).level);
// orgTasks = unIndentAndAssert(orgTasks.get(orgTasks.size() - 1),
// true);
// }
// // Should now be a root
// assertEquals("Level incorrect", 1,
// orgTasks.get(orgTasks.size() - 1).level);
//
// // Let's do the rest, top to bottom
// // All preceeding items are affected by this amount
// int cum = 0;
// for (int j = 1; j < orgTasks.size() - 1; j++) {
// for (int i = j + 1; i - cum > 1; i--) {
// assertEquals("Level incorrect (i = " + i + ")", i - cum,
// orgTasks.get(j).level);
// orgTasks = unIndentAndAssert(orgTasks.get(j), true);
// }
// cum += 1;
// // Should now be a root
// assertEquals("Level incorrect", 1, orgTasks.get(j).level);
// }
//
// deleteList(tl);
// }
//
// public void testIndentsHarder() {
// // Was something I actually did and noticed a crash
// final TaskList tl = insertList();
// int count = 3;
// insertTasks(tl._id, count);
// ArrayList<Task> orgTasks = getTasks(tl._id);
//
// // The issue is the order things are moved in the statement
// // Moved in order of their IDs!
//
// // Now at 2,1,0
// // Want 0,2,1 (where 2 is indented)
// orgTasks = moveAndAssert(tl, 2, 0);
// orgTasks = indentAndAssert(orgTasks.get(1), true);
//
// // This crashed by trying to set right to null
// // Since the parent was re-assigned before the child
// orgTasks = unIndentAndAssert(orgTasks.get(1), true);
//
// deleteList(tl);
// }
//
// public void testMoveIndentedTrees() {
// // important to test moving tasks in a tree structure
// final TaskList tl = insertList();
// int count = 9;
// insertTasks(tl._id, count);
// ArrayList<Task> orgTasks = getTasks(tl._id);
//
// // Roots at 0, 2 and 6
// // 0
// indentAndAssert(orgTasks.get(1), true);
// // 2
// indentAndAssert(orgTasks.get(3), true);
// indentAndAssert(orgTasks.get(4), true);
// indentAndAssert(orgTasks.get(4), true);
// indentAndAssert(orgTasks.get(5), true);
// indentAndAssert(orgTasks.get(5), true);
// indentAndAssert(orgTasks.get(5), true);
// // 6
// indentAndAssert(orgTasks.get(7), true);
// indentAndAssert(orgTasks.get(8), true);
// indentAndAssert(orgTasks.get(8), true);
//
// // move 2 tree to bottom
// // now part of six tree
// moveAndAssert(tl, 2, 8);
//
// // Move 6 tree to top
// moveAndAssert(tl, 3, 0);
//
// // Move 2 tree to top
// moveAndAssert(tl, 6, 0);
//
// deleteList(tl);
// }
public void testInvalidMoves() {
// TODO
// fail("TODO");
}
public void testMultipleDeletes() {
// TODO
// fail("TODO");
}
public void testTaskContent() {
Task t = new Task();
t.title = "Hej";
t.dblist = 1L;
ContentValues values = t.getContent();
assertFalse(values.containsKey(Task.Columns.LEFT));
assertFalse(values.containsKey(Task.Columns.RIGHT));
}
public void testDeleteTrigger() {
// Deleting an item should place a copy of it in the delete-table
final TaskList tl = insertList();
insertTasks(tl._id, 1);
ArrayList<Task> orgTasks = getTasks(tl._id);
final Task orgTask = orgTasks.get(0);
final String t = "HABANA MAMAMANA";
orgTask.title = t;
resolver.update(orgTask.getUri(), orgTask.getContent(), null, null);
resolver.delete(orgTask.getUri(), null, null);
orgTasks = getTasks(tl._id);
assertEquals("List should be empty now", 0, orgTasks.size());
// Get the item from backup table instead
Cursor c = resolver.query(Task.URI_DELETED_QUERY,
Task.Columns.DELETEFIELDS, Task.Columns.TITLE + " IS ?",
new String[] { t }, null);
assertTrue("Task should be found in delete table after delete!",
c.moveToFirst());
deleteList(tl);
}
}