package com.nononsenseapps.notepad.test;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import com.nononsenseapps.notepad.data.model.sql.RemoteTask;
import com.nononsenseapps.notepad.data.model.sql.RemoteTaskList;
import com.nononsenseapps.notepad.data.model.sql.Task;
import com.nononsenseapps.notepad.data.model.sql.TaskList;
import com.nononsenseapps.notepad.data.local.orgmode.OrgConverter;
import com.nononsenseapps.notepad.data.local.orgmode.SDSynchronizer;
import org.cowboyprogrammer.org.OrgFile;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import static com.nononsenseapps.notepad.data.local.sql.DatabaseHandler.resetTestDatabase;
import static com.nononsenseapps.notepad.data.local.sql.DatabaseHandler.setEmptyTestDatabase;
import static com.nononsenseapps.notepad.data.local.sql.DatabaseHandler.setTestDatabase;
/**
* Test the synchronizer code.
* Methods starting with 'testFresh' are meant to be reused in higher-order
* tests.
*/
public class OrgSyncTest extends AndroidTestCase {
private static final String ACCOUNT = "bobtester";
private static String DIR;
@Override
public void setUp() throws Exception {
File d = getContext().getDir("ORGSYNCTEST", Context.MODE_PRIVATE);
DIR = d.getPath();
if (!d.exists()) {
d.mkdirs();
}
reset();
setEmptyTestDatabase(getContext(), getClass().getName());
}
@Override
public void tearDown() {
reset();
}
private void reset() {
resetTestDatabase(getContext(), getClass().getName());
File d = new File(DIR);
for (File f : d.listFiles()) {
f.delete();
}
}
public ArrayList<TaskList> getTaskLists() {
ContentResolver resolver = getContext().getContentResolver();
Cursor c = resolver.query(TaskList.URI, TaskList.Columns
.FIELDS, null, null, null);
ArrayList<TaskList> result = new ArrayList<TaskList>();
while (c.moveToNext()) {
result.add(new TaskList(c));
}
c.close();
return result;
}
public ArrayList<Task> getTasks(final long listid) {
ContentResolver resolver = getContext().getContentResolver();
Cursor c = resolver.query(Task.URI, Task.Columns
.FIELDS, Task.Columns.DBLIST + " IS ?",
new String[]{Long.toString(listid)}, null
);
ArrayList<Task> result = new ArrayList<Task>();
while (c.moveToNext()) {
result.add(new Task(c));
}
c.close();
return result;
}
public ArrayList<RemoteTaskList> getRemoteTaskLists() {
ContentResolver resolver = getContext().getContentResolver();
Cursor c = resolver.query(RemoteTaskList.URI, RemoteTaskList.Columns
.FIELDS, RemoteTaskList.Columns.ACCOUNT + " IS ?",
new String[]{ACCOUNT}, null
);
ArrayList<RemoteTaskList> result = new ArrayList<RemoteTaskList>();
while (c.moveToNext()) {
result.add(new RemoteTaskList(c));
}
c.close();
return result;
}
public ArrayList<RemoteTask> getRemoteTasks() {
ContentResolver resolver = getContext().getContentResolver();
Cursor c = resolver.query(RemoteTask.URI, RemoteTask.Columns
.FIELDS, RemoteTask.Columns.ACCOUNT + " IS ?",
new String[]{ACCOUNT}, null
);
ArrayList<RemoteTask> result = new ArrayList<RemoteTask>();
while (c.moveToNext()) {
result.add(new RemoteTask(c));
}
c.close();
return result;
}
@SmallTest
public void testPass() {
// This always passes
assertTrue(true);
}
@SmallTest
public void testTester() {
TestSynchronizer tester = new TestSynchronizer(getContext());
assertTrue(tester.isConfigured());
}
/**
* End result: synced state of one tasklist with two tasks.
* Tested flow branches:
* - Lists: Create file
* - Tasks: Create node
*/
public void testFreshSimple() {
// First create a list with 2 tasks
TaskList list = new TaskList();
list.title = "TestList";
list.save(getContext());
assertTrue(list._id > 0);
final int taskCount = 2;
for (int i = 0; i < taskCount; i++) {
Task t = new Task();
t.dblist = list._id;
t.title = "Task" + i;
t.note = "A body for the task";
t.save(getContext());
assertTrue(t._id > 0);
}
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// See the result
HashSet<String> filenames = synchronizer.getRemoteFilenames();
assertEquals("Only one list was created.", 1, filenames.size());
String filename = null;
for (String f : filenames) {
filename = f;
}
assertEquals("Wrong filename", list.title + ".org", filename);
// Check that the database is correct
ArrayList<RemoteTaskList> remoteLists = getRemoteTaskLists();
assertEquals("Should only be one RemoteList!", 1, remoteLists.size());
ArrayList<RemoteTask> remoteTasks = getRemoteTasks();
assertEquals("Should be exactly 2 RemoteTasks", taskCount, remoteTasks.size());
long lastDbid = -1;
for (int i = 1; i < remoteTasks.size() + 1; i++) {
RemoteTask r = remoteTasks.remove(i - 1);
// Check for duplicates
assertEquals("Id is not correct", i, r._id);
assertTrue(lastDbid != r.dbid);
lastDbid = r.dbid;
}
}
public void syncAndAssertNothingChanged(final int taskCount) {
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// It should NOT have written to disk at all
assertEquals("No changes should not be written!", 0,
synchronizer.getPutRemoteCount());
// Check that the database is still correct
ArrayList<TaskList> lists = getTaskLists();
assertEquals("Should only be one list", 1, lists.size());
ArrayList<Task> tasks = getTasks(lists.get(0)._id);
assertEquals("Should be only 2 tasks in list", taskCount, tasks.size());
ArrayList<RemoteTaskList> remoteLists = getRemoteTaskLists();
assertEquals("Should only be one RemoteList!", 1, remoteLists.size());
ArrayList<RemoteTask> remoteTasks = getRemoteTasks();
assertEquals("Should be exactly 2 RemoteTasks", taskCount, remoteTasks.size());
long lastDbid = -1;
for (int i = 1; i < remoteTasks.size() + 1; i++) {
RemoteTask r = remoteTasks.get(i - 1);
// Check for duplicates
assertEquals("Id is not correct", i, r._id);
assertTrue(lastDbid != r.dbid);
lastDbid = r.dbid;
}
}
/**
* Nothing has changed here.
* Tested flow branches:
* - Lists: Update Merge
* - Tasks: Update Merge
*/
public void testNothingNew() {
final int taskCount = 2;
testFreshSimple();
syncAndAssertNothingChanged(taskCount);
syncAndAssertNothingChanged(taskCount);
syncAndAssertNothingChanged(taskCount);
syncAndAssertNothingChanged(taskCount);
}
/**
* Having two lists with the same name is possible in the app,
* but obviously impossible at the filesystem level.
* Tested flow branches:
* - Lists: Create file
*/
public void testDuplicateName() throws IOException, ParseException {
// Create first list
TaskList list1 = new TaskList();
list1.title = "TestList";
list1.save(getContext());
assertTrue(list1._id > 0);
// Create second list
TaskList list2 = new TaskList();
list2.title = "TestList";
list2.save(getContext());
assertTrue(list2._id > 0);
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
synchronizer.fullSync();
// Make sure the second one was renamed!
for (TaskList tl : getTaskLists()) {
if (tl._id == list1._id) {
assertEquals(list1.title, tl.title);
} else if (tl._id == list2._id) {
assertEquals("List should have been renamed",
list2.title + 1, tl.title);
}
}
HashSet<String> filenames = synchronizer.getRemoteFilenames();
assertEquals(2, filenames.size());
assertTrue(filenames.contains(list1.title + ".org"));
assertTrue(filenames.contains(list2.title + 1 + ".org"));
}
/**
* Renaming a list in the app should rename the file.
* Tested branches:
* - Update list, renamed
*/
public void testRenamedList() throws IOException, ParseException {
// Create first list
TaskList list1 = new TaskList();
list1.title = "TestList";
list1.save(getContext());
assertTrue(list1._id > 0);
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
synchronizer.fullSync();
File org = new File(DIR, OrgConverter.getTitleAsFilename
(list1));
// Make sure original file is there
assertTrue(org.exists());
// Rename the list
list1.title = "RenamedList";
list1.save(getContext());
// Sync it
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// Make sure rename was successful
assertFalse(org.exists());
File renamed = new File(DIR, OrgConverter.getTitleAsFilename
(list1));
assertTrue(renamed.exists());
}
/**
* Deleting a list should delete the corresponding file and all tasks.
* Tested branches:
* - Delete File Db
*/
public void testDeletedList() throws IOException, ParseException {
// Setup simple DB
final int taskCount = 2;
testFreshSimple();
// Delete list(s)
File file = null;
ArrayList<TaskList> lists = getTaskLists();
for (TaskList list: lists) {
file = new File(DIR, OrgConverter.getTitleAsFilename(list));
list.delete(getContext());
}
assertNotNull(file);
// Make sure it exists at this point
assertTrue(file.exists());
// And that the database still has a record of it
ArrayList<RemoteTaskList> remoteLists = getRemoteTaskLists();
assertEquals("Should be one RemoteList!", 1, remoteLists.size());
ArrayList<RemoteTask> remoteTasks = getRemoteTasks();
assertEquals("Should be exactly 2 RemoteTasks", taskCount, remoteTasks.size());
// Sync it again
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
synchronizer.fullSync();
// Check that the database has removed it
lists = getTaskLists();
assertTrue("Should be no list", lists.isEmpty());
remoteLists = getRemoteTaskLists();
assertTrue("Should be no RemoteList!", remoteLists.isEmpty());
remoteTasks = getRemoteTasks();
assertTrue("Should be no RemoteTasks", remoteTasks.isEmpty());
// Make sure no file exists anymore
assertFalse(file.exists());
}
/** Test moving 1 task from List A to List B
*
*/
public void testMoveOne() throws IOException, ParseException {
// First create Two lists
TaskList listA = new TaskList();
listA.title = "TestListA";
listA.save(getContext());
assertTrue(listA._id > 0);
TaskList listB = new TaskList();
listB.title = "TestListB";
listB.save(getContext());
assertTrue(listB._id > 0);
// Add one task in ListA
Task t = new Task();
t.dblist = listA._id;
t.title = "Task";
t.note = "A body for the task";
t.save(getContext());
assertTrue(t._id > 0);
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
synchronizer.fullSync();
// Check state of sync
ArrayList<RemoteTaskList> remoteLists = getRemoteTaskLists();
assertEquals("Should be two RemoteLists!", 2, remoteLists.size());
ArrayList<RemoteTask> remoteTasks = getRemoteTasks();
assertEquals("Should be exactly 1 RemoteTask", 1, remoteTasks.size());
assertEquals("RemoteTask is in wrong list!", listA._id,
(long) remoteTasks.get(0).listdbid);
// Move the task
t.dblist = listB._id;
t.save(getContext());
// Trigger should have deleted remotes now
remoteTasks = getRemoteTasks();
for (RemoteTask rt: remoteTasks) {
assertEquals("RemoteTask should be deleted after move before sync", "deleted", rt.deleted);
}
// Sync it
synchronizer.fullSync();
// Check state of sync
remoteLists = getRemoteTaskLists();
assertEquals("Should be two RemoteLists after move!", 2, remoteLists.size());
remoteTasks = getRemoteTasks();
assertEquals("Should be exactly 1 RemoteTask after move", 1, remoteTasks.size());
assertEquals("RemoteTask is in wrong list after move!", listB._id,
(long) remoteTasks.get(0).listdbid);
}
/** Test moving 20 tasks from List A to List B
*
*/
public void testMoveMany() throws IOException, ParseException {
// First create Two lists
TaskList listA = new TaskList();
listA.title = "TestListA";
listA.save(getContext());
assertTrue(listA._id > 0);
TaskList listB = new TaskList();
listB.title = "TestListB";
listB.save(getContext());
assertTrue(listB._id > 0);
final int taskCount = 20;
ArrayList<Task> tasks = new ArrayList<Task>();
for (int i = 0; i < taskCount; i++) {
Task t = new Task();
t.dblist = listA._id;
t.title = "Task" + i;
t.note = "A body for the task";
t.save(getContext());
assertTrue(t._id > 0);
tasks.add(t);
}
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
synchronizer.fullSync();
// Check state of sync
ArrayList<RemoteTaskList> remoteLists = getRemoteTaskLists();
assertEquals("Should be two RemoteLists!", 2, remoteLists.size());
ArrayList<RemoteTask> remoteTasks = getRemoteTasks();
assertEquals("Should be exactly x RemoteTask", taskCount, remoteTasks.size());
for (RemoteTask remoteTask: remoteTasks) {
assertEquals("RemoteTask is in wrong list!", listA._id,
(long) remoteTask.listdbid);
}
// Move the tasks
for (Task t: tasks) {
t.dblist = listB._id;
t.save(getContext());
}
// Trigger should have deleted remotes now
remoteTasks = getRemoteTasks();
for (RemoteTask rt: remoteTasks) {
assertEquals("RemoteTask should be deleted after move before sync", "deleted", rt.deleted);
}
// Sync it
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// Check state of sync
remoteLists = getRemoteTaskLists();
assertEquals("Should be two RemoteLists after move!", 2, remoteLists.size());
remoteTasks = getRemoteTasks();
assertEquals("Should be exactly x RemoteTask after move and sync", taskCount, remoteTasks.size());
for (RemoteTask remoteTask: remoteTasks) {
assertEquals("RemoteTask is in wrong list after move!", listB._id,
(long) remoteTask.listdbid);
}
}
/** Test moving 12 tasks from List A to List B where there are 12 lists each with 20 tasks
*
*/
public void testMoveManyAmongMany() throws IOException, ParseException {
final int listCount = 12;
final int taskCount = 20;
final int movedTaskCount = 12;
ArrayList<Task> tasksToMove = new ArrayList<Task>();
TaskList listA = null, listB = null;
// First create Lists
for (int listIndex = 0; listIndex < listCount; listIndex++) {
TaskList list = new TaskList();
list.title = "TestList" + listIndex;
list.save(getContext());
assertTrue(list._id > 0);
if (listA == null)
listA = list;
else if (listB == null)
listB = list;
for (int i = 0; i < taskCount; i++) {
Task t = new Task();
t.dblist = list._id;
t.title = "Task" + listIndex + "." + i;
t.note = "A body for the task";
t.save(getContext());
assertTrue(t._id > 0);
if (tasksToMove.size() < movedTaskCount) {
tasksToMove.add(t);
}
}
}
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
synchronizer.fullSync();
// Check state of sync
ArrayList<RemoteTaskList> remoteLists = getRemoteTaskLists();
assertEquals("Should be X RemoteLists!", listCount, remoteLists.size());
ArrayList<RemoteTask> remoteTasks = getRemoteTasks();
assertEquals("Should be exactly x RemoteTask", taskCount*listCount, remoteTasks.size());
// Move the tasks
assertNotNull(listA);
assertNotNull(listB);
assertTrue("List A and B should be different!", listA._id != listB._id);
assertEquals("Expected something to move", movedTaskCount, tasksToMove.size());
for (Task t: tasksToMove) {
assertEquals("Expected task to be in list A!", listA._id, (long) t.dblist);
t.dblist = listB._id;
t.save(getContext());
}
// Trigger should have deleted remotes now
remoteTasks = getRemoteTasks();
int deletecount = 0;
int realcount = 0;
for (RemoteTask rt: remoteTasks) {
if ("deleted".equals(rt.deleted)) {
deletecount += 1;
} else {
realcount += 1;
}
}
assertEquals("Deleted remotetasks did not match", movedTaskCount, deletecount);
assertEquals("Remaining remotetasks did not match", taskCount*listCount - movedTaskCount, realcount);
// Sync it
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// Check state of sync
ArrayList<RemoteTaskList> remoteTaskLists = getRemoteTaskLists();
remoteTasks = getRemoteTasks();
deletecount = 0;
realcount = 0;
for (RemoteTask rt: remoteTasks) {
if ("deleted".equals(rt.deleted)) {
deletecount += 1;
} else {
realcount += 1;
}
}
assertEquals("Number of remote lits did not match", listCount, remoteTaskLists.size());
assertEquals("Deleted remotetasks did not match", 0, deletecount);
assertEquals("Remaining remotetasks did not match", taskCount*listCount, realcount);
int nowInB = 0;
for (RemoteTask remoteTask: remoteTasks) {
assertTrue(!"deleted".equals(remoteTask.deleted));
if (remoteTask.listdbid == listB._id) {
nowInB += 1;
}
}
assertEquals("RemoteTasks in b not expected count", taskCount + movedTaskCount,
nowInB);
// Check same things for local tasks
ArrayList<TaskList> taskLists = getTaskLists();
assertEquals("Number of lists did not match", listCount, taskLists.size());
for (TaskList list: taskLists) {
ArrayList<Task> tasks = getTasks(list._id);
if (listA._id == list._id) {
assertEquals("Not expected count in A", taskCount - movedTaskCount, tasks.size());
} else if (listB._id == list._id) {
assertEquals("Not expected count in B", taskCount + movedTaskCount, tasks.size());
} else {
assertEquals("Not expected count in C->", taskCount, tasks.size());
}
}
}
public void testFilenameWithSlash() {
// Filenames with slashes are not permitted
final TaskList lista = new TaskList();
lista.title = "Test/List/Slash/Name";
lista.save(getContext());
assertTrue(lista._id > 0);
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// Check contents after sync
final TaskList listb = getTaskLists().get(0);
// Should no longer have slashes in name
assertTrue(!listb.title.contains("/"));
assertEquals("Test_List_Slash_Name", listb.title);
}
public void testContentStability() {
// Make sure content is not changed
// Create list
final TaskList lista = new TaskList();
lista.title = "TestList";
lista.save(getContext());
assertTrue(lista._id > 0);
final Task task1a = new Task();
task1a.title = "The title1";
task1a.note = "A note without newline";
task1a.dblist = lista._id;
task1a.save(getContext());
assertTrue(task1a._id > 0);
final Task task2a = new Task();
task2a.title = "The title2";
task2a.note = "Another note\non two lines";
task2a.dblist = lista._id;
task2a.save(getContext());
assertTrue(task2a._id > 0);
// Sync it
TestSynchronizer synchronizer = new TestSynchronizer(getContext());
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// Check contents after sync
final TaskList listb = getTaskLists().get(0);
assertEquals(lista.title, listb.title);
for (Task taskb: getTasks(listb._id)) {
Task org;
if (taskb._id == task1a._id) {
org = task1a;
} else {
org = task2a;
}
// Compare title and note
assertEquals(org.title, taskb.title);
assertEquals(org.note, taskb.note);
}
// Sync it again
try {
synchronizer.fullSync();
} catch (Exception e) {
assertTrue(e.getLocalizedMessage(), false);
}
// Check contents after sync
final TaskList listc = getTaskLists().get(0);
assertEquals(lista.title, listc.title);
for (Task taskc: getTasks(listb._id)) {
Task org;
if (taskc._id == task1a._id) {
org = task1a;
} else {
org = task2a;
}
// Compare title and note
assertEquals(org.title, taskc.title);
assertEquals(org.note, taskc.note);
}
}
class TestSynchronizer extends SDSynchronizer {
private int putRemoteCount = 0;
public TestSynchronizer(Context context) {
super(context);
ORG_DIR = OrgSyncTest.DIR;
}
@Override
public boolean isConfigured() {
return true;
}
/**
* @return A unique name for this service. Should be descriptive, like
* DropboxOrg, SDOrg or SSHOrg.
*/
@Override
public String getServiceName() {
return ACCOUNT;
}
@Override
public String getAccountName() {
return ACCOUNT;
}
/**
* Replaces the file on the remote end with the given content.
*
* @param orgFile The file to save. Uses the filename stored in the object.
*/
@Override
public void putRemoteFile(OrgFile orgFile) throws IOException {
putRemoteCount += 1;
super.putRemoteFile(orgFile);
}
public int getPutRemoteCount() {
return putRemoteCount;
}
public void setPutRemoteCount(final int putRemoteCount) {
this.putRemoteCount = putRemoteCount;
}
}
}