/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.gtasks;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Semaphore;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.Intent;
import android.os.Bundle;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.google.api.services.tasks.model.Tasks;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.api.GtasksApiUtilities;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator;
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.sync.SyncResultCallbackAdapter;
import com.todoroo.astrid.test.DatabaseTestCase;
@SuppressWarnings("nls")
public class GtasksNewSyncTest extends DatabaseTestCase {
private static GtasksInvoker gtasksService;
private static boolean initialized = false;
private boolean bypassTests = false;
private static String DEFAULT_LIST = "@default";
private static final String TEST_ACCOUNT = "sync_tester2@astrid.com";
private static final long TIME_BETWEEN_SYNCS = 3000l;
@Autowired TaskService taskService;
@Autowired MetadataService metadataService;
@Autowired GtasksMetadataService gtasksMetadataService;
@Autowired GtasksPreferenceService gtasksPreferenceService;
/*
* Basic creation tests
*/
public void testTaskCreatedLocally() {
if(bypassTests) return;
String title = "Astrid task 1";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
assertTaskExistsRemotely(localTask, title);
}
public void testTaskCreatedRemotely() throws Exception {
if(bypassTests) return;
String title = "Gtasks task 1";
com.google.api.services.tasks.model.Task remoteTask = new com.google.api.services.tasks.model.Task();
remoteTask.setTitle(title);
remoteTask = gtasksService.createGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
assertTaskExistsLocally(remoteTask, title);
}
/*
* Title editing tests
*/
public void testTitleChangedLocally() throws Exception {
if(bypassTests) return;
String title = "Astrid task 2";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new title on local task
String newTitle = "Astrid task 2 edited";
localTask.setValue(Task.TITLE, newTitle);
taskService.save(localTask);
whenInvokeSync();
//Refetch remote task and assert that both local and remote titles match expected
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newTitle, localTask.getValue(Task.TITLE));
assertEquals(newTitle, remoteTask.getTitle());
}
public void testTitleChangedRemotely() throws Exception {
if(bypassTests) return;
String title = "Astrid task 3";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new title on remote task
String newRemoteTitle = "Task 3 edited on gtasks";
remoteTask.setTitle(newRemoteTitle);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
//Refetch local/remote tasks, assert that both titles match expected
remoteTask = refetchRemoteTask(remoteTask);
localTask = refetchLocalTask(localTask);
assertEquals(newRemoteTitle, remoteTask.getTitle());
assertEquals(newRemoteTitle, localTask.getValue(Task.TITLE));
}
public void testDateChangedLocally() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForDateTests(" locally");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertTrue(String.format("Expected %s, was %s", new Date(startDate), new Date(localTask.getValue(Task.DUE_DATE))),
Math.abs(startDate - localTask.getValue(Task.DUE_DATE)) < 5000);
long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0);
long createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate);
assertEquals(startDate, createdDate);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on local task
long newDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(116, 1, 8).getTime());
localTask.setValue(Task.DUE_DATE, newDueDate);
taskService.save(localTask);
whenInvokeSync();
//Refetch remote task and assert that both tasks match expected due date
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newDueDate, localTask.getValue(Task.DUE_DATE).longValue());
dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0);
createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate);
assertEquals(newDueDate, createdDate);
}
public void testDateChangedRemotely() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForDateTests(" remotely");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertTrue(String.format("Expected %s, was %s", new Date(startDate), new Date(localTask.getValue(Task.DUE_DATE))),
Math.abs(startDate - localTask.getValue(Task.DUE_DATE)) < 5000);
long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0);
long createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate);
assertEquals(startDate, createdDate);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on remote task
long newDueDate = new Date(116, 1, 8).getTime();
remoteTask.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(newDueDate));
newDueDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, newDueDate);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
//Refetch remote task and assert that both tasks match expected due date
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newDueDate, localTask.getValue(Task.DUE_DATE).longValue());
dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0);
createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate);
assertEquals(newDueDate, createdDate);
}
public void testDateChangedBoth_ChooseLocal() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForDateTests(" remotely");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertTrue(String.format("Expected %s, was %s", new Date(startDate), new Date(localTask.getValue(Task.DUE_DATE))),
Math.abs(startDate - localTask.getValue(Task.DUE_DATE)) < 5000);
long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0);
long createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate);
assertEquals(startDate, createdDate);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on remote task first
long newLocalDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, new Date(128, 5, 11).getTime());
long newRemoteDate = new Date(121, 5, 25).getTime();
remoteTask.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(newRemoteDate));
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
localTask.setValue(Task.DUE_DATE, newLocalDate);
taskService.save(localTask);
whenInvokeSync();
//Refetch both and assert that due dates match the one we set to local (more recent)
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newLocalDate, localTask.getValue(Task.DUE_DATE).longValue());
dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0);
createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate);
assertEquals(newLocalDate, createdDate);
}
public void DISABLED_testDateChangedBoth_ChooseRemote() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForDateTests(" remotely");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertTrue(String.format("Expected %s, was %s", new Date(startDate), new Date(localTask.getValue(Task.DUE_DATE))),
Math.abs(startDate - localTask.getValue(Task.DUE_DATE)) < 5000);
assertEquals(startDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0));
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on local task first
long newLocalDate = new Date(128, 5, 11).getTime();
long newRemoteDate = new Date(121, 5, 25).getTime();
localTask.setValue(Task.DUE_DATE, newLocalDate);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
remoteTask.setDue(GtasksApiUtilities.unixTimeToGtasksDueDate(newRemoteDate));
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
//Refetch both and assert that due dates match the one we set to local (more recent)
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newLocalDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(newLocalDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.getDue(), 0));
}
/*
* Helper method for due date tests
*/
private Task createLocalTaskForDateTests(String addToTitle) {
Task localTask = createNewLocalTask("Due date will change" + addToTitle);
Date date = new Date(115, 2, 14);
date.setHours(12);
date.setMinutes(0);
date.setSeconds(0);
long dueDate = date.getTime();
localTask.setValue(Task.DUE_DATE, dueDate);
taskService.save(localTask);
return localTask;
}
public void testNoteEditedLocally() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForNoteTests(" locally");
String title = localTask.getValue(Task.TITLE);
String originalNote = localTask.getValue(Task.NOTES);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
assertEquals(originalNote, localTask.getValue(Task.NOTES));
assertEquals(originalNote, remoteTask.getNotes());
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
String newNote = "New local note";
localTask.setValue(Task.NOTES, newNote);
taskService.save(localTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newNote, localTask.getValue(Task.NOTES));
assertEquals(newNote, remoteTask.getNotes());
}
public void testNoteEditedRemotely() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForNoteTests(" remotely");
String title = localTask.getValue(Task.TITLE);
String originalNote = localTask.getValue(Task.NOTES);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
assertEquals(originalNote, localTask.getValue(Task.NOTES));
assertEquals(originalNote, remoteTask.getNotes());
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
String newNote = "New remote note";
remoteTask.setNotes(newNote);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newNote, localTask.getValue(Task.NOTES));
assertEquals(newNote, remoteTask.getNotes());
}
public void DISABLED_testNoteEditedBoth() throws Exception {
if(bypassTests) return;
Task localTask = createLocalTaskForNoteTests(" remotely");
String title = localTask.getValue(Task.TITLE);
String originalNote = localTask.getValue(Task.NOTES);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
assertEquals(originalNote, localTask.getValue(Task.NOTES));
assertEquals(originalNote, remoteTask.getNotes());
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
String newLocalNote = "New local note";
String newRemoteNote = "New remote note";
localTask.setValue(Task.NOTES, newLocalNote);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
remoteTask.setNotes(newRemoteNote);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
System.err.println("Local note: " + localTask.getValue(Task.NOTES));
System.err.println("Remote note: " + remoteTask.getNotes());
}
private Task createLocalTaskForNoteTests(String addToTitle) {
Task localTask = createNewLocalTask("Note will change" + addToTitle);
String note = "Original note";
localTask.setValue(Task.NOTES, note);
taskService.save(localTask);
return localTask;
}
/*
* Completion tests
*/
public void testTaskCompletedLocally() throws Exception {
if(bypassTests) return;
String title = "Will complete locally";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
long completion = DateUtilities.now();
localTask.setValue(Task.COMPLETION_DATE, completion);
taskService.save(localTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertTrue(String.format("Expected %s, was %s", new Date(completion), new Date(localTask.getValue(Task.COMPLETION_DATE))),
Math.abs(completion - localTask.getValue(Task.COMPLETION_DATE)) < 5000);
assertEquals("completed", remoteTask.getStatus());
}
public void testTaskCompletedRemotely() throws Exception {
if(bypassTests) return;
String title = "Will complete remotely";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
long completion = DateUtilities.now();
remoteTask.setStatus("completed");
remoteTask.setCompleted(GtasksApiUtilities.unixTimeToGtasksCompletionTime(completion));
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertTrue(String.format("Expected %s, was %s", new Date(completion), new Date(localTask.getValue(Task.COMPLETION_DATE))),
Math.abs(completion - localTask.getValue(Task.COMPLETION_DATE)) < 5000);
assertEquals("completed", remoteTask.getStatus());
}
private com.google.api.services.tasks.model.Task assertTaskExistsRemotely(Task localTask, String title) {
//Get the corresponding remote id for a local task
Metadata metadata = gtasksMetadataService.getTaskMetadata(localTask.getId());
String taskId = metadata.getValue(GtasksMetadata.ID);
String listId = metadata.getValue(GtasksMetadata.LIST_ID);
//Fetch the remote task belonging to that id
com.google.api.services.tasks.model.Task remoteTask = null;
try {
remoteTask = gtasksService.getGtask(listId, taskId);
} catch (Exception e) {
e.printStackTrace();
fail("Failed to find remote task " + taskId);
}
//Do a basic title match
assertNotNull(remoteTask);
assertEquals(title, localTask.getValue(Task.TITLE));
assertEquals(title, remoteTask.getTitle());
return remoteTask;
}
private Task assertTaskExistsLocally(com.google.api.services.tasks.model.Task remoteTask, String title) {
long localId = localIdForTask(remoteTask);
//Fetch the local task from the database
Task localTask = taskService.fetchById(localId, Task.PROPERTIES);
assertNotNull(localTask);
assertEquals(title, remoteTask.getTitle());
assertEquals(title, localTask.getValue(Task.TITLE));
return localTask;
}
private Task refetchLocalTask(Task localTask) {
return taskService.fetchById(localTask.getValue(Task.ID), Task.PROPERTIES);
}
private com.google.api.services.tasks.model.Task refetchRemoteTask(com.google.api.services.tasks.model.Task remoteTask) throws Exception {
return gtasksService.getGtask(DEFAULT_LIST, remoteTask.getId());
}
private long localIdForTask(com.google.api.services.tasks.model.Task remoteTask) {
TodorooCursor<Metadata> cursor = metadataService.query(Query.select(Metadata.TASK).
where(Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), GtasksMetadata.ID.eq(remoteTask.getId()))));
try {
assertEquals(1, cursor.getCount());
cursor.moveToFirst();
return cursor.get(Metadata.TASK);
} finally {
cursor.close();
}
}
//Create a new Astrid task and save it to the database
private Task createNewLocalTask(String title) {
Task task = new Task();
task.setValue(Task.TITLE, title);
taskService.save(task);
return task;
}
//Perform a synchronization
private void whenInvokeSync() {
final Semaphore sema = new Semaphore(0);
GtasksSyncV2Provider.getInstance().synchronizeActiveTasks(true, new SyncResultCallbackAdapter() {
@Override
public void finished() {
sema.release();
}
});
try {
sema.acquire();
} catch (InterruptedException e) {
fail("Interrupted while waiting for sync to finish");
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
if (!initialized) {
initializeTestService();
}
setupTestList();
}
private void initializeTestService() throws Exception {
GoogleAccountManager manager = new GoogleAccountManager(ContextManager.getContext());
Account[] accounts = manager.getAccounts();
Account toUse = null;
for (Account a : accounts) {
if (a.name.equals(TEST_ACCOUNT)) {
toUse = a;
break;
}
}
if (toUse == null) {
if (accounts.length == 0) {
bypassTests = true;
return;
}
toUse = accounts[0];
}
Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, toUse.name);
AccountManagerFuture<Bundle> accountManagerFuture = manager.manager.getAuthToken(toUse, "oauth2:https://www.googleapis.com/auth/tasks", true, null, null);
Bundle authTokenBundle = accountManagerFuture.getResult();
if (authTokenBundle.containsKey(AccountManager.KEY_INTENT)) {
Intent i = (Intent) authTokenBundle.get(AccountManager.KEY_INTENT);
ContextManager.getContext().startActivity(i);
return;
}
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
authToken = GtasksTokenValidator.validateAuthToken(getContext(), authToken);
gtasksPreferenceService.setToken(authToken);
gtasksService = new GtasksInvoker(authToken);
initialized = true;
}
private void setupTestList() throws Exception {
Tasks defaultListTasks = gtasksService.getAllGtasksFromListId(DEFAULT_LIST, false, false, 0);
List<com.google.api.services.tasks.model.Task> items = defaultListTasks.getItems();
if (items != null) {
for (com.google.api.services.tasks.model.Task t : items) {
gtasksService.deleteGtask(DEFAULT_LIST, t.getId());
}
}
}
}