diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java index 4422b03f5..333371f96 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java @@ -270,6 +270,20 @@ public class ActFmSyncProvider extends SyncProvider { return local; } + @Override + protected void readRemotelyUpdated(SyncData data) throws IOException { + int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0); + ArrayList remoteTasks = new ArrayList(); + + try { + fetchRemoteTasks(serverTime, remoteTasks); + data.remoteUpdated = remoteTasks; + } catch (JSONException e) { + // Ingnored + } + super.readRemotelyUpdated(data); + } + // ---------------------------------------------------------------------- // --------------------------------------------------------- read / write // ---------------------------------------------------------------------- diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java index de6b5ec98..5da31face 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java @@ -183,6 +183,8 @@ public class GtasksSyncProvider extends SyncProvider { SyncData syncData = populateSyncData(); try { synchronizeTasks(syncData); + AndroidUtilities.sleepDeep(3000L); // Wait for changes to be saved (i.e. for repeating tasks to be cloned) + checkForCreatedDuringSync(); } finally { syncData.localCreated.close(); syncData.localUpdated.close(); @@ -207,6 +209,19 @@ public class GtasksSyncProvider extends SyncProvider { } } + private void checkForCreatedDuringSync() { + TodorooCursor localCreated = gtasksMetadataService.getLocallyCreated(PROPERTIES); + try { + SyncData localCreatedData = new SyncData(null, localCreated, null); + sendLocallyCreated(localCreatedData, new HashMap()); + } catch (IOException e) { + handleException("gtasks-sync", e, true); + } finally { + System.err.println("Sent " + localCreated.getCount() + " new tasks"); + localCreated.close(); + } + } + private void getActiveList(TaskLists taskView) throws IOException { String listId; if(taskView.items.size() == 0) { diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index 5015ac573..4a868baa5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -228,6 +228,8 @@ public class ProducteevSyncProvider extends SyncProvider syncData = populateSyncData(remoteTasks); try { synchronizeTasks(syncData); + AndroidUtilities.sleepDeep(3000L); + checkForCreatedDuringSync(); } finally { syncData.localCreated.close(); syncData.localUpdated.close(); @@ -253,6 +255,19 @@ public class ProducteevSyncProvider extends SyncProvider localCreated = dataService.getLocallyCreated(PROPERTIES); + try { + SyncData localCreatedData = new SyncData(null, localCreated, null); + sendLocallyCreated(localCreatedData, new HashMap()); + } catch (IOException e) { + handleException("gtasks-sync", e, true); + } finally { + System.err.println("Sent " + localCreated.getCount() + " new tasks"); + localCreated.close(); + } + } + /** * @param activities * @return @@ -592,7 +607,8 @@ public class ProducteevSyncProvider extends SyncProvider 0) { - startDate = new Date(startDate.getTime() + DateUtilities.ONE_WEEK * rrule.getInterval() - - DateUtilities.ONE_DAY); + startDate = new Date(startDate.getTime() + DateUtilities.ONE_WEEK * rrule.getInterval()); rrule.setInterval(1); } diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 37999febe..989a369c5 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -1098,6 +1098,8 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, Task original = new Task(); original.setId(itemId); + Flags.set(Flags.ACTFM_SUPPRESS_SYNC); + Flags.set(Flags.GTASKS_SUPPRESS_SYNC); Task clone = taskService.clone(original); clone.setValue(Task.CREATION_DATE, DateUtilities.now()); clone.setValue(Task.COMPLETION_DATE, 0L); diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index 75e6a3bf1..057813b79 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -111,6 +111,7 @@ public class TaskService { try { if(cursor.getCount() > 0) { Metadata metadata = new Metadata(); + taskDao.save(newTask); long newId = newTask.getId(); for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { metadata.readFromCursor(cursor); @@ -119,7 +120,7 @@ public class TaskService { continue; if(GtasksMetadata.METADATA_KEY.equals(metadata.getValue(Metadata.KEY))) - metadata.setValue(GtasksMetadata.ID, "0"); //$NON-NLS-1$ + metadata.setValue(GtasksMetadata.ID, ""); if(ProducteevTask.METADATA_KEY.equals(metadata.getValue(Metadata.KEY))) metadata.setValue(ProducteevTask.ID, 0L); if(MilkTaskFields.METADATA_KEY.equals(metadata.getValue(Metadata.KEY))) { diff --git a/tests/src/com/todoroo/astrid/repeats/NewRepeatTests.java b/tests/src/com/todoroo/astrid/repeats/NewRepeatTests.java new file mode 100644 index 000000000..41389dfc5 --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/NewRepeatTests.java @@ -0,0 +1,421 @@ +package com.todoroo.astrid.repeats; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import android.content.Intent; + +import com.google.ical.values.Frequency; +import com.google.ical.values.RRule; +import com.google.ical.values.Weekday; +import com.google.ical.values.WeekdayNum; +import com.timsu.astrid.R; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.dao.MetadataDao; +import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.service.StartupService; +import com.todoroo.astrid.test.DatabaseTestCase; +import com.todoroo.astrid.utility.Flags; + +public class NewRepeatTests extends DatabaseTestCase { + + @Autowired + TaskDao taskDao; + + @Autowired + MetadataDao metadataDao; + + @Override + protected void setUp() throws Exception { + super.setUp(); + Preferences.setStringFromInteger(R.string.p_default_urgency_key, 0); + RepeatTaskCompleteListener.setSkipActFmCheck(true); + } + + private void saveAndTriggerRepeatListener(Task task) { + Flags.set(Flags.SUPPRESS_HOOKS); + if(task.isSaved()) + taskDao.saveExisting(task); + else + taskDao.createNew(task); + + Intent intent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_COMPLETED); + intent.putExtra(AstridApiConstants.EXTRAS_TASK_ID, task.getId()); + new RepeatTaskCompleteListener().onReceive(getContext(), intent); + } + + protected void waitAndSync() { + // Subclasses can override this to insert sync functionality + } + + /** + * @param t + * @param expectedDueDate + */ + protected REMOTE_MODEL assertTaskExistsRemotely(Task t, long expectedDueDate) { + // Subclasses can override this to check the existence of remote objects + return null; + } + + protected void assertTaskCompletedRemotely(Task t) { + // Subclasses can override this to check the status of the corresponding remote task + } + + + /** + * @param remoteModel + */ + protected long setCompletionDate(boolean completeBefore, Task t, + REMOTE_MODEL remoteModel, long dueDate) { + long completionDate; + if (completeBefore) + completionDate = dueDate - DateUtilities.ONE_DAY; + else + completionDate = dueDate + DateUtilities.ONE_DAY; + t.setValue(Task.COMPLETION_DATE, completionDate); + saveAndTriggerRepeatListener(t); + return completionDate; + } + + protected void assertTimesMatch(long expectedTime, long newDueDate) { + assertTrue(String.format("Expected %s, was %s", new Date(expectedTime), new Date(newDueDate)), + Math.abs(expectedTime - newDueDate) < 5000); + } + + /* + * Tests for no sync + */ + + public void testNoRepeat() { + Task t = new Task(); + t.setValue(Task.TITLE, "no repeat"); + taskDao.save(t); + + t.setValue(Task.COMPLETION_DATE, DateUtilities.now()); + saveAndTriggerRepeatListener(t); + + TodorooCursor cursor = taskDao.query(Query.select(Task.ID)); + try { + assertEquals(1, cursor.getCount()); + } finally { + cursor.close(); + } + } + + protected void testRepeating(boolean completeBefore, boolean fromCompletion, RRule rrule, Frequency frequency, String title) { + for (int i = 0; i < StartupService.INTRO_TASK_SIZE; i++) { // Create startup tasks so sync services don't miss the test tasks + Task temp = new Task(); + temp.setValue(Task.TITLE, "" + i); + taskDao.save(temp); + } + Task t = new Task(); + t.setValue(Task.TITLE, title); + long dueDate = DateUtilities.now() + DateUtilities.ONE_DAY * 3; + dueDate = (dueDate / 1000L) * 1000L; // Strip milliseconds + if (fromCompletion) + t.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true); + + t.setValue(Task.DUE_DATE, dueDate); + + if (rrule == null) { + rrule = new RRule(); + rrule.setFreq(frequency); + int interval = 5; + rrule.setInterval(interval); + } + t.setValue(Task.RECURRENCE, rrule.toIcal()); + taskDao.save(t); + + waitAndSync(); + t = taskDao.fetch(t.getId(), Task.PROPERTIES); // Refetch + REMOTE_MODEL remoteModel = assertTaskExistsRemotely(t, dueDate); + + long completionDate = setCompletionDate(completeBefore, t, remoteModel, dueDate); + + waitAndSync(); + assertTaskCompletedRemotely(t); + + TodorooCursor cursor = taskDao.query(Query.select(Task.PROPERTIES).where(TaskCriteria.notDeleted())); + try { + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + Task task = new Task(cursor); + System.err.println("Task: " + task.getValue(Task.TITLE) + ", due: " + task.getValue(Task.DUE_DATE)); + } + assertEquals(StartupService.INTRO_TASK_SIZE + 2, cursor.getCount()); + cursor.moveToFirst(); + for (int i = 0; i < StartupService.INTRO_TASK_SIZE; i++) { + cursor.moveToNext(); + } + t.readFromCursor(cursor); + + assertEquals(title, t.getValue(Task.TITLE)); + assertEquals(dueDate, (long)t.getValue(Task.DUE_DATE)); + assertTrue(t.isCompleted()); + + cursor.moveToNext(); + t.readFromCursor(cursor); + assertEquals(title, t.getValue(Task.TITLE)); + assertFalse(t.isCompleted()); + long newDueDate = t.getValue(Task.DUE_DATE); + assertTrue(t.hasDueTime()); + + long fromDate = (fromCompletion? completionDate : dueDate); + long expectedTime = computeNextDueDateFromDate(fromDate, rrule, fromCompletion); + + assertTaskExistsRemotely(t, expectedTime); + assertTimesMatch(expectedTime, newDueDate); + } finally { + cursor.close(); + } + } + + private long computeWeeklyCaseDueDate(long fromDate, RRule rrule, boolean fromCompletion) { + long result = fromDate; + Frequency frequency = rrule.getFreq(); + assertTrue(frequency.equals(Frequency.WEEKLY)); + List weekdayNums = rrule.getByDay(); + + if (weekdayNums.size() == 0) { + result += DateUtilities.ONE_WEEK * rrule.getInterval(); + return result; + } + HashSet weekdays = new HashSet(); + for (WeekdayNum curr : weekdayNums) { + weekdays.add(curr.wday); + } + + Weekday[] allWeekdays = Weekday.values(); + result -= DateUtilities.ONE_DAY; + Date date = new Date(result); + Weekday start = allWeekdays[date.getDay()]; + int i; + for (i = 0; i < allWeekdays.length; i++) { + if (start == allWeekdays[i]) break; + } + int index = i; + int daysToAdd = 0; + Weekday next = null; + for (i = index + 1; i < allWeekdays.length; i++) { + Weekday curr = allWeekdays[i]; + daysToAdd++; + if (weekdays.contains(curr)) { + next = curr; + break; + } + } + + if (next == null) { + for (i = 0; i < index + 1; i++) { + Weekday curr = allWeekdays[i]; + daysToAdd++; + if (weekdays.contains(curr)) { + next = curr; + break; + } + } + } + + if (fromCompletion) { + result += DateUtilities.ONE_WEEK * rrule.getInterval(); + } + result += DateUtilities.ONE_DAY * daysToAdd; + return result; + } + + + /** Advanced weekly repeating tests */ + protected long computeNextDueDateFromDate(long fromDate, RRule rrule, boolean fromCompletion) { + long expectedTime = fromDate; + Frequency frequency = rrule.getFreq(); + int interval = rrule.getInterval(); + if (frequency.equals(Frequency.MINUTELY)) { + expectedTime += DateUtilities.ONE_MINUTE * interval; + } else if (frequency.equals(Frequency.HOURLY)) { + expectedTime += DateUtilities.ONE_HOUR * interval; + } else if (frequency.equals(Frequency.DAILY)) { + expectedTime += DateUtilities.ONE_DAY * interval; + } else if (frequency.equals(Frequency.WEEKLY)) { + expectedTime = computeWeeklyCaseDueDate(fromDate, rrule, fromCompletion); + } else if (frequency.equals(Frequency.MONTHLY)) { + Date originalDate = new Date(expectedTime); + for (int i = 0; i < interval; i++) { + int month = originalDate.getMonth(); + if (month == 11) { // Roll over the year and set the month to January + originalDate.setYear(originalDate.getYear() + 1); + originalDate.setMonth(0); + } else { + originalDate.setMonth(originalDate.getMonth() + 1); + } + } + expectedTime = originalDate.getTime(); + } else if (frequency.equals(Frequency.YEARLY)) { + Date originalCompleteDate = new Date(expectedTime); + originalCompleteDate.setYear(originalCompleteDate.getYear() + interval); + expectedTime = originalCompleteDate.getTime(); + } + return expectedTime; + } + + private void testFromDueDate(boolean completeBefore, Frequency frequency, String title) { + testRepeating(completeBefore, false, null, frequency, title); + } + + private void testFromCompletionDate(boolean completeBefore, Frequency frequency, String title) { + testRepeating(completeBefore, true, null, frequency, title); + } + + + /** Tests for repeating from due date */ + + public void testRepeatMinutelyFromDueDateCompleteBefore() { + testFromDueDate(true, Frequency.MINUTELY, "minutely-before"); + } + + public void testRepeatMinutelyFromDueDateCompleteAfter() { + testFromDueDate(false, Frequency.MINUTELY, "minutely-after"); + } + + public void testRepeatHourlyFromDueDateCompleteBefore() { + testFromDueDate(true, Frequency.HOURLY, "hourly-before"); + } + + public void testRepeatHourlyFromDueDateCompleteAfter() { + testFromDueDate(false, Frequency.HOURLY, "hourly-after"); + } + + public void testRepeatDailyFromDueDateCompleteBefore() { + testFromDueDate(true, Frequency.DAILY, "daily-before"); + } + + public void testRepeatDailyFromDueDateCompleteAfter() { + testFromDueDate(false, Frequency.DAILY, "daily-after"); + } + + public void testRepeatWeeklyFromDueDateCompleteBefore() { + testFromDueDate(true, Frequency.WEEKLY, "weekly-before"); + } + + public void testRepeatWeeklyFromDueDateCompleteAfter() { + testFromDueDate(false, Frequency.WEEKLY, "weekly-after"); + } + + public void testRepeatMonthlyFromDueDateCompleteBefore() { + testFromDueDate(true, Frequency.MONTHLY, "monthly-before"); + } + + public void testRepeatMonthlyFromDueDateCompleteAfter() { + testFromDueDate(false, Frequency.MONTHLY, "monthly-after"); + } + + public void testRepeatYearlyFromDueDateCompleteBefore() { + testFromDueDate(true, Frequency.YEARLY, "yearly-before"); + } + + public void testRepeatYearlyFromDueDateCompleteAfter() { + testFromDueDate(false, Frequency.YEARLY, "yearly-after"); + } + + + /** Tests for repeating from completionDate */ + + public void testRepeatMinutelyFromCompleteDateCompleteBefore() { + testFromCompletionDate(true, Frequency.MINUTELY, "minutely-before"); + } + + public void testRepeatMinutelyFromCompleteDateCompleteAfter() { + testFromCompletionDate(false, Frequency.MINUTELY, "minutely-after"); + } + + public void testRepeatHourlyFromCompleteDateCompleteBefore() { + testFromCompletionDate(true, Frequency.HOURLY, "hourly-before"); + } + + public void testRepeatHourlyFromCompleteDateCompleteAfter() { + testFromCompletionDate(false, Frequency.HOURLY, "hourly-after"); + } + + public void testRepeatDailyFromCompleteDateCompleteBefore() { + testFromCompletionDate(true, Frequency.DAILY, "daily-before"); + } + + public void testRepeatDailyFromCompleteDateCompleteAfter() { + testFromCompletionDate(false, Frequency.DAILY, "daily-after"); + } + + public void testRepeatWeeklyFromCompleteDateCompleteBefore() { + testFromCompletionDate(true, Frequency.WEEKLY, "weekly-before"); + } + + public void testRepeatWeeklyFromCompleteDateCompleteAfter() { + testFromCompletionDate(false, Frequency.WEEKLY, "weekly-after"); + } + + public void testRepeatMonthlyFromCompleteDateCompleteBefore() { + testFromCompletionDate(true, Frequency.MONTHLY, "monthly-before"); + } + + public void testRepeatMonthlyFromCompleteDateCompleteAfter() { + testFromCompletionDate(false, Frequency.MONTHLY, "monthly-after"); + } + + public void testRepeatYearlyFromCompleteDateCompleteBefore() { + testFromCompletionDate(true, Frequency.YEARLY, "yearly-before"); + } + + public void testRepeatYearlyFromCompleteDateCompleteAfter() { + testFromCompletionDate(false, Frequency.YEARLY, "yearly-after"); + } + + private void testAdvancedWeeklyFromDueDate(boolean completeBefore, String title) { + RRule rrule = new RRule(); + rrule.setFreq(Frequency.WEEKLY); + + int interval = 1; + rrule.setInterval(interval); + List weekdays = new ArrayList(); + weekdays.add(new WeekdayNum(0, Weekday.MO)); + weekdays.add(new WeekdayNum(0, Weekday.WE)); + rrule.setByDay(weekdays); + testRepeating(completeBefore, false, rrule, Frequency.WEEKLY, title); + } + + private void testAdvancedWeeklyFromCompleteDate(boolean completeBefore, String title) { + RRule rrule = new RRule(); + rrule.setFreq(Frequency.WEEKLY); + + int interval = 1; + rrule.setInterval(interval); + List weekdays = new ArrayList(); + weekdays.add(new WeekdayNum(0, Weekday.MO)); + weekdays.add(new WeekdayNum(0, Weekday.WE)); + rrule.setByDay(weekdays); + testRepeating(completeBefore, true, rrule, Frequency.WEEKLY, title); + } + + + + public void testAdvancedRepeatWeeklyFromDueDateCompleteBefore() { + testAdvancedWeeklyFromDueDate(true, "advanced-weekly-before"); + } + + public void testAdvancedRepeatWeeklyFromDueDateCompleteAfter() { + testAdvancedWeeklyFromDueDate(false, "advanced-weekly-after"); + } + + public void testAdvancedRepeatWeeklyFromCompleteDateCompleteBefore() { + testAdvancedWeeklyFromCompleteDate(true, "advanced-weekly-before"); + } + + public void testAdvancedRepeatWeeklyFromCompleteDateCompleteAfter() { + testAdvancedWeeklyFromCompleteDate(false, "advanced-weekly-after"); + } +} diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTests.java b/tests/src/com/todoroo/astrid/repeats/RepeatTests.java index c06401333..f90a4e818 100644 --- a/tests/src/com/todoroo/astrid/repeats/RepeatTests.java +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTests.java @@ -43,7 +43,6 @@ public class RepeatTests extends DatabaseTestCase { task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); saveAndTriggerRepeatListener(task); - TodorooCursor cursor = taskDao.query(Query.select(Task.ID)); try { assertEquals(1, cursor.getCount()); diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTestsActFmSync.java b/tests/src/com/todoroo/astrid/repeats/RepeatTestsActFmSync.java new file mode 100644 index 000000000..a770915b5 --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTestsActFmSync.java @@ -0,0 +1,177 @@ +package com.todoroo.astrid.repeats; + +import java.io.IOException; +import java.util.ArrayList; + +import org.json.JSONArray; +import org.json.JSONObject; + +import com.google.ical.values.Frequency; +import com.google.ical.values.RRule; +import com.timsu.astrid.R; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +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.actfm.sync.ActFmDataService; +import com.todoroo.astrid.actfm.sync.ActFmInvoker; +import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; +import com.todoroo.astrid.actfm.sync.ActFmSyncProvider; +import com.todoroo.astrid.actfm.sync.ActFmSyncService; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.service.StartupService; + +public class RepeatTestsActFmSync extends NewRepeatTests { + + @Autowired MetadataService metadataService; + @Autowired ActFmDataService actFmDataService; + @Autowired ActFmSyncService actFmSyncService; + @Autowired ActFmPreferenceService actFmPreferenceService; + protected static ActFmInvoker invoker = null; + + private static final String TEST_ACCOUNT = "sync_tester2@astrid.com"; + private static final String TEST_PASSWORD = "wonkwonkjj"; + private static boolean initialized = false; + + @Override + protected void setUp() throws Exception { + super.setUp(); + Preferences.setStringFromInteger(R.string.p_default_urgency_key, 0); + RepeatTaskCompleteListener.setSkipActFmCheck(false); + + if (!initialized) { + initializeTestService(); + } + + clearTasks(); + } + + private void initializeTestService() throws Exception { + invoker = new ActFmInvoker(); + authenticate(TEST_ACCOUNT, null, ActFmInvoker.PROVIDER_PASSWORD, TEST_PASSWORD); + initialized = true; + } + + private void clearTasks() throws Exception { + JSONObject result = invoker.invoke("task_list", "active", 1); + + JSONArray taskList = result.getJSONArray("list"); + for(int i = 0; i < taskList.length(); i++) { + Task remote = new Task(); + ActFmSyncService.JsonHelper.taskFromJson(taskList.getJSONObject(i), remote, new ArrayList()); + + remote.setValue(Task.DELETION_DATE, DateUtilities.now()); + actFmSyncService.pushTaskOnSave(remote, remote.getSetValues()); + } + } + + private void authenticate(String email, String name, String provider, String secret) { + try { + JSONObject result = invoker.authenticate(email, name, provider, secret); + String token = invoker.getToken(); + postAuthenticate(result, token); + } catch (IOException e) { + e.printStackTrace(); + fail("Error authenticating"); + } + } + + @SuppressWarnings("nls") + private void postAuthenticate(JSONObject result, String token) { + actFmPreferenceService.setToken(token); + + Preferences.setLong(ActFmPreferenceService.PREF_USER_ID, + result.optLong("id")); + Preferences.setString(ActFmPreferenceService.PREF_NAME, result.optString("name")); + Preferences.setString(ActFmPreferenceService.PREF_EMAIL, result.optString("email")); + Preferences.setString(ActFmPreferenceService.PREF_PICTURE, result.optString("picture")); + } + + + @Override + protected void waitAndSync() { + AndroidUtilities.sleepDeep(3000L); + new ActFmSyncProvider().synchronize(null); + AndroidUtilities.sleepDeep(3000L); + } + + /** + * @param t + * @param expectedDueDate + */ + @Override + protected Task assertTaskExistsRemotely(Task t, long expectedDueDate) { + Task remote = new Task(); + try { + ActFmSyncService.JsonHelper.taskFromJson(invoker.invoke("task_show", "id", t.getValue(Task.REMOTE_ID)), remote, + new ArrayList()); + assertTimesMatch(expectedDueDate, remote.getValue(Task.DUE_DATE).longValue()); + } catch (Exception e) { + e.printStackTrace(); + fail("Error in ActFm invoker"); + } + return remote; + } + + @Override + protected void testRepeating(boolean completeBefore, boolean fromCompletion, RRule rrule, Frequency frequency, String title) { + for (int i = 0; i < StartupService.INTRO_TASK_SIZE; i++) { // Create startup tasks so sync services don't miss the test tasks + Task temp = new Task(); + temp.setValue(Task.TITLE, "" + i); + taskDao.save(temp); + } + Task t = new Task(); + t.setValue(Task.TITLE, title); + long dueDate = DateUtilities.now() + DateUtilities.ONE_DAY * 3; + dueDate = (dueDate / 1000L) * 1000L; // Strip milliseconds + if (fromCompletion) + t.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true); + + t.setValue(Task.DUE_DATE, dueDate); + + if (rrule == null) { + rrule = new RRule(); + rrule.setFreq(frequency); + int interval = 5; + rrule.setInterval(interval); + } + t.setValue(Task.RECURRENCE, rrule.toIcal()); + taskDao.save(t); + + waitAndSync(); + t = taskDao.fetch(t.getId(), Task.PROPERTIES); // Refetch + Task remoteModel = assertTaskExistsRemotely(t, dueDate); + + long completionDate = setCompletionDate(completeBefore, t, remoteModel, dueDate); + + waitAndSync(); + + TodorooCursor cursor = taskDao.query(Query.select(Task.PROPERTIES).where(TaskCriteria.notDeleted())); + try { + assertEquals(StartupService.INTRO_TASK_SIZE + 1, cursor.getCount()); + cursor.moveToFirst(); + for (int i = 0; i < StartupService.INTRO_TASK_SIZE; i++) { + cursor.moveToNext(); + } + t.readFromCursor(cursor); + + long fromDate = (fromCompletion? completionDate : dueDate); + long expectedTime = computeNextDueDateFromDate(fromDate, rrule, fromCompletion); + long newDueDate = t.getValue(Task.DUE_DATE); + + assertTaskExistsRemotely(t, expectedTime); + assertTrue(t.hasDueTime()); + assertEquals(title, t.getValue(Task.TITLE)); + assertTimesMatch(expectedTime, newDueDate); + assertFalse(t.isCompleted()); + + } finally { + cursor.close(); + } + } +} diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTestsActFmSyncRemote.java b/tests/src/com/todoroo/astrid/repeats/RepeatTestsActFmSyncRemote.java new file mode 100644 index 000000000..0032e8847 --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTestsActFmSyncRemote.java @@ -0,0 +1,112 @@ +package com.todoroo.astrid.repeats; + +import java.util.ArrayList; + +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.data.Task; + +public class RepeatTestsActFmSyncRemote extends RepeatTestsActFmSync { + @Override + protected long setCompletionDate(boolean completeBefore, Task t, + Task remoteModel, long dueDate) { + long completionDate; + if (completeBefore) + completionDate = dueDate - DateUtilities.ONE_DAY; + else + completionDate = dueDate + DateUtilities.ONE_DAY; + + ArrayList params = new ArrayList(); + params.add("completed"); params.add(completionDate / 1000L); + + params.add("id"); params.add(remoteModel.getValue(Task.REMOTE_ID)); + try { + invoker.invoke("task_save", params.toArray(new Object[params.size()])); + } catch (Exception e) { + e.printStackTrace(); + fail("Error in actfm invoker"); + } + return completionDate; + } + + @Override + public void testRepeatMinutelyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testRepeatMinutelyFromCompleteDateCompleteBefore(); + } + + @Override + public void testRepeatMinutelyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testRepeatMinutelyFromCompleteDateCompleteAfter(); + } + + @Override + public void testRepeatHourlyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testRepeatHourlyFromCompleteDateCompleteBefore(); + } + + @Override + public void testRepeatHourlyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testRepeatHourlyFromCompleteDateCompleteAfter(); + } + + @Override + public void testRepeatDailyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testRepeatDailyFromCompleteDateCompleteBefore(); + } + + @Override + public void testRepeatDailyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testRepeatDailyFromCompleteDateCompleteAfter(); + } + + @Override + public void testRepeatWeeklyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testRepeatWeeklyFromCompleteDateCompleteBefore(); + } + + @Override + public void testRepeatWeeklyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testRepeatWeeklyFromCompleteDateCompleteAfter(); + } + + @Override + public void testRepeatMonthlyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testRepeatMonthlyFromCompleteDateCompleteBefore(); + } + + @Override + public void testRepeatMonthlyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testRepeatMonthlyFromCompleteDateCompleteAfter(); + } + + @Override + public void testRepeatYearlyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testRepeatYearlyFromCompleteDateCompleteBefore(); + } + + @Override + public void testRepeatYearlyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testRepeatYearlyFromCompleteDateCompleteAfter(); + } + + @Override + public void testAdvancedRepeatWeeklyFromDueDateCompleteBefore() { + // (DISABLED) super.testAdvancedRepeatWeeklyFromDueDateCompleteBefore(); + } + + @Override + public void testAdvancedRepeatWeeklyFromDueDateCompleteAfter() { + // (DISABLED) super.testAdvancedRepeatWeeklyFromDueDateCompleteAfter(); + } + + @Override + public void testAdvancedRepeatWeeklyFromCompleteDateCompleteBefore() { + // (DISABLED) super.testAdvancedRepeatWeeklyFromCompleteDateCompleteBefore(); + } + + @Override + public void testAdvancedRepeatWeeklyFromCompleteDateCompleteAfter() { + // (DISABLED) super.testAdvancedRepeatWeeklyFromCompleteDateCompleteAfter(); + } + + +} diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTestsGtasksSync.java b/tests/src/com/todoroo/astrid/repeats/RepeatTestsGtasksSync.java new file mode 100644 index 000000000..7f04f9b3d --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTestsGtasksSync.java @@ -0,0 +1,152 @@ +package com.todoroo.astrid.repeats; + +import java.io.IOException; +import java.util.Date; + +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.v1.model.Tasks; +import com.timsu.astrid.R; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.gtasks.GtasksMetadata; +import com.todoroo.astrid.gtasks.GtasksMetadataService; +import com.todoroo.astrid.gtasks.GtasksPreferenceService; +import com.todoroo.astrid.gtasks.api.GtasksApiUtilities; +import com.todoroo.astrid.gtasks.api.GtasksService; +import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator; +import com.todoroo.astrid.gtasks.sync.GtasksSyncProvider; +import com.todoroo.astrid.service.MetadataService; + +public class RepeatTestsGtasksSync extends NewRepeatTests { + + @Autowired MetadataService metadataService; + @Autowired GtasksMetadataService gtasksMetadataService; + @Autowired GtasksPreferenceService gtasksPreferenceService; + + private static final String TEST_ACCOUNT = "sync_tester2@astrid.com"; + private static final String DEFAULT_LIST = "@default"; + + private static boolean initialized = false; + protected static GtasksService gtasksService; + + @Override + protected void setUp() throws Exception { + super.setUp(); + Preferences.setStringFromInteger(R.string.p_default_urgency_key, 0); + RepeatTaskCompleteListener.setSkipActFmCheck(true); + + if (!initialized) { + initializeTestService(); + } + + setupTestList(); + } + + @Override + protected void waitAndSync() { + AndroidUtilities.sleepDeep(3000L); + new GtasksSyncProvider().synchronize(null); + AndroidUtilities.sleepDeep(3000L); + } + + @Override + protected com.google.api.services.tasks.v1.model.Task assertTaskExistsRemotely(Task t, long expectedRemoteTime) { + Metadata metadata = gtasksMetadataService.getTaskMetadata(t.getId()); + assertNotNull(metadata); + String listId = metadata.getValue(GtasksMetadata.LIST_ID); + String taskId = metadata.getValue(GtasksMetadata.ID); + com.google.api.services.tasks.v1.model.Task remote = null; + try { + remote = gtasksService.getGtask(listId, taskId); + } catch (IOException e){ + e.printStackTrace(); + fail("Exception in gtasks service"); + } + assertNotNull(remote); + assertEquals(t.getValue(Task.TITLE), remote.title); + + Date expected = new Date(expectedRemoteTime); + expected.setHours(0); + expected.setMinutes(0); + expected.setSeconds(0); + + long gtasksTime = GtasksApiUtilities.gtasksDueTimeToUnixTime(remote.due, 0); + assertEquals(expected.getTime(), gtasksTime); + return remote; + } + + @Override + protected void assertTaskCompletedRemotely(Task t) { + Metadata metadata = gtasksMetadataService.getTaskMetadata(t.getId()); + assertNotNull(metadata); + String listId = metadata.getValue(GtasksMetadata.LIST_ID); + String taskId = metadata.getValue(GtasksMetadata.ID); + com.google.api.services.tasks.v1.model.Task remote = null; + try { + remote = gtasksService.getGtask(listId, taskId); + } catch (IOException e) { + e.printStackTrace(); + fail("Exception in gtasks service"); + } + + assertNotNull(remote); + assertEquals(t.getValue(Task.TITLE), remote.title); + + assertEquals("completed", remote.status); + } + + 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) { + return; + } + toUse = accounts[0]; + } + + Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, toUse.name); + AccountManagerFuture 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(authToken); + gtasksPreferenceService.setToken(authToken); + + gtasksService = new GtasksService(authToken); + + initialized = true; + } + + private void setupTestList() throws Exception { + Tasks defaultListTasks = gtasksService.getAllGtasksFromListId(DEFAULT_LIST, false, false); + if (defaultListTasks.items != null) { + for (com.google.api.services.tasks.v1.model.Task t : defaultListTasks.items) { + gtasksService.deleteGtask(DEFAULT_LIST, t.id); + } + } + } +} diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTestsGtasksSyncRemote.java b/tests/src/com/todoroo/astrid/repeats/RepeatTestsGtasksSyncRemote.java new file mode 100644 index 000000000..7d7782e82 --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTestsGtasksSyncRemote.java @@ -0,0 +1,49 @@ +package com.todoroo.astrid.repeats; + +import java.io.IOException; +import java.util.Date; + +import com.google.ical.values.Frequency; +import com.google.ical.values.RRule; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.gtasks.api.GtasksApiUtilities; + +public class RepeatTestsGtasksSyncRemote extends RepeatTestsGtasksSync { + + // Test logic in superclass + + @Override + protected long setCompletionDate(boolean completeBefore, Task t, + com.google.api.services.tasks.v1.model.Task remoteModel, long dueDate) { + long completionDate; + if (completeBefore) + completionDate = dueDate - DateUtilities.ONE_DAY; + else + completionDate = dueDate + DateUtilities.ONE_DAY; + remoteModel.completed = GtasksApiUtilities.unixTimeToGtasksDate(completionDate); + remoteModel.status = "completed"; + try { + gtasksService.updateGtask(GtasksApiUtilities.extractListIdFromSelfLink(remoteModel), remoteModel); + } catch (IOException e) { + e.printStackTrace(); + fail("Exception in gtasks service"); + } + return completionDate; + } + @Override + protected long computeNextDueDateFromDate(long fromDate, RRule rrule, boolean fromCompletion) { + long expectedDate = super.computeNextDueDateFromDate(fromDate, rrule, fromCompletion); + Frequency freq = rrule.getFreq(); + if (fromCompletion && (freq == Frequency.HOURLY || freq == Frequency.MINUTELY)) { + long millis = (freq == Frequency.HOURLY ? DateUtilities.ONE_HOUR : DateUtilities.ONE_MINUTE); + Date rounded = new Date(expectedDate); + rounded.setHours(0); + rounded.setMinutes(0); + rounded.setSeconds(0); + return rounded.getTime() + rrule.getInterval() * millis; + } else { + return expectedDate; + } + } +} diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTestsProducteevSync.java b/tests/src/com/todoroo/astrid/repeats/RepeatTestsProducteevSync.java new file mode 100644 index 000000000..56ff678e9 --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTestsProducteevSync.java @@ -0,0 +1,128 @@ +package com.todoroo.astrid.repeats; + +import org.json.JSONArray; +import org.json.JSONObject; + +import com.timsu.astrid.R; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.producteev.ProducteevUtilities; +import com.todoroo.astrid.producteev.api.ApiUtilities; +import com.todoroo.astrid.producteev.api.ProducteevInvoker; +import com.todoroo.astrid.producteev.sync.ProducteevDataService; +import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; +import com.todoroo.astrid.producteev.sync.ProducteevTask; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.service.TaskService; +import com.todoroo.astrid.tags.TagService; + +public class RepeatTestsProducteevSync extends NewRepeatTests { + + private static boolean initialized = false; + + protected static ProducteevInvoker invoker; + + @Autowired TaskService taskService; + @Autowired MetadataService metadataService; + @Autowired TagService tagService; + protected ProducteevDataService producteevDataService; + + private static final String TEST_USER = "sync_tester2@astrid.com"; + private static final String TEST_PASSWORD = "wonkwonkjj"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + Preferences.setString(ProducteevUtilities.PREF_SERVER_LAST_SYNC, null); + if (!initialized) { + initializeTestService(); + } + producteevDataService = ProducteevDataService.getInstance(); + producteevDataService.updateDashboards(new JSONArray()); + clearAllRemoteTasks(); + } + + private void initializeTestService() throws Exception { + //Set the username and password for the service + Preferences.setString(R.string.producteev_PPr_email, TEST_USER); + Preferences.setString(R.string.producteev_PPr_password, TEST_PASSWORD); + + invoker = ProducteevSyncProvider.getInvoker(); + + invoker.authenticate(TEST_USER, TEST_PASSWORD); + + initialized = true; + } + + /* + * Deletes all the remote tasks on the default dashboard to ensure + * clean tests. + */ + private void clearAllRemoteTasks() { + try { + JSONArray remoteTasks = invoker.tasksShowList(null, null); + for (int i = 0; i < remoteTasks.length(); i++) { + JSONObject task = remoteTasks.getJSONObject(i).getJSONObject("task"); + System.err.println(invoker.tasksDelete(getRemoteId(task))); + } + } catch (Exception e) { + fail("Failed to clear remote tasks before tests"); + } + } + + protected long getRemoteId(JSONObject remoteTask) { + long remoteId = 0; + try { + remoteId = remoteTask.getLong("id_task"); + } catch (Exception e) { + fail("Remote task object did not contain id_task field"); + } + return remoteId; + } + + @Override + protected void waitAndSync() { + AndroidUtilities.sleepDeep(3000L); + new ProducteevSyncProvider().synchronize(null); + AndroidUtilities.sleepDeep(3000L); + } + + /** + * @param t + * @param expectedDueDate + */ + @Override + protected JSONObject assertTaskExistsRemotely(Task t, long expectedDueDate) { + long remoteId = producteevDataService.getTaskMetadata(t.getId()).getValue(ProducteevTask.ID); + JSONObject remoteTask = null; + try { + remoteTask = invoker.tasksView(remoteId).getJSONObject("task"); + assertNotNull(remoteTask); + + long remoteDueDate = ApiUtilities.producteevToUnixTime(remoteTask.getString("deadline"), 0); + assertTimesMatch(expectedDueDate, remoteDueDate); + } catch (Exception e) { + e.printStackTrace(); + fail("Error in producteev invoker"); + } + return remoteTask; + } + + @Override + protected void assertTaskCompletedRemotely(Task t) { + long remoteId = producteevDataService.getTaskMetadata(t.getId()).getValue(ProducteevTask.ID); + JSONObject remoteTask = null; + try { + remoteTask = invoker.tasksView(remoteId).getJSONObject("task"); + assertNotNull(remoteTask); + System.err.println(remoteTask); + assertEquals(2, remoteTask.getInt("status")); + } catch (Exception e) { + e.printStackTrace(); + fail("Error in producteev invoker"); + } + } + +} diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTestsProducteevSyncRemote.java b/tests/src/com/todoroo/astrid/repeats/RepeatTestsProducteevSyncRemote.java new file mode 100644 index 000000000..b8a73a42c --- /dev/null +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTestsProducteevSyncRemote.java @@ -0,0 +1,44 @@ +package com.todoroo.astrid.repeats; + +import java.util.Date; + +import org.json.JSONObject; + +import com.google.ical.values.RRule; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.data.Task; + +public class RepeatTestsProducteevSyncRemote extends RepeatTestsProducteevSync { + + @Override + protected long setCompletionDate(boolean completeBefore, Task t, + JSONObject remoteModel, long dueDate) { + long completionDate; + if (completeBefore) + completionDate = dueDate - DateUtilities.ONE_DAY; + else + completionDate = dueDate + DateUtilities.ONE_DAY; + + try { + invoker.tasksSetStatus(getRemoteId(remoteModel), 2); + } catch (Exception e) { + e.printStackTrace(); + fail("Error in producteev invoker"); + } + return completionDate; + } + + @Override + protected long computeNextDueDateFromDate(long fromDate, RRule rrule, boolean fromCompletion) { + if (fromCompletion) { + fromDate = DateUtilities.now() - 5000L; + } + return super.computeNextDueDateFromDate(fromDate, rrule, fromCompletion); + } + + @Override + protected void assertTimesMatch(long expectedTime, long newDueDate) { + assertTrue(String.format("Expected %s, was %s", new Date(expectedTime), new Date(newDueDate)), + Math.abs(expectedTime - newDueDate) < 60000); // More lenient with producteev since we use now() in setting completion during sync + } +}