From 137a27432a7249b3c6fd8326f95165940b1f0dc4 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Thu, 21 Jan 2021 16:39:20 -0600 Subject: [PATCH] Add tests for 11.3 migration --- .../astrid/service/Upgrade_11_3_Test.kt | 243 ++++++++++++++++++ .../java/org/tasks/TestUtilities.kt | 9 +- .../java/org/tasks/makers/CaldavTaskMaker.kt | 2 + .../java/org/tasks/makers/TaskMaker.kt | 3 +- .../todoroo/astrid/service/Upgrade_11_3.kt | 55 ++++ .../com/todoroo/astrid/service/Upgrader.kt | 46 +--- .../main/java/org/tasks/data/OpenTaskDao.kt | 13 +- 7 files changed, 323 insertions(+), 48 deletions(-) create mode 100644 app/src/androidTest/java/com/todoroo/astrid/service/Upgrade_11_3_Test.kt create mode 100644 app/src/main/java/com/todoroo/astrid/service/Upgrade_11_3.kt diff --git a/app/src/androidTest/java/com/todoroo/astrid/service/Upgrade_11_3_Test.kt b/app/src/androidTest/java/com/todoroo/astrid/service/Upgrade_11_3_Test.kt new file mode 100644 index 000000000..2c5fc9060 --- /dev/null +++ b/app/src/androidTest/java/com/todoroo/astrid/service/Upgrade_11_3_Test.kt @@ -0,0 +1,243 @@ +@file:Suppress("ClassName") + +package com.todoroo.astrid.service + +import android.content.ContentProviderResult +import at.bitfire.ical4android.BatchOperation +import com.natpryce.makeiteasy.MakeItEasy.with +import com.todoroo.astrid.data.Task +import com.todoroo.astrid.helper.UUIDHelper +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules +import kotlinx.coroutines.runBlocking +import org.dmfs.tasks.contract.TaskContract +import org.dmfs.tasks.contract.TaskContract.CALLER_IS_SYNCADAPTER +import org.dmfs.tasks.contract.TaskContract.TaskLists +import org.junit.Test +import org.tasks.SuspendFreeze.Companion.freezeAt +import org.tasks.TestUtilities.assertEquals +import org.tasks.TestUtilities.fromString +import org.tasks.data.* +import org.tasks.injection.InjectingTestCase +import org.tasks.injection.ProductionModule +import org.tasks.makers.CaldavTaskMaker.CALENDAR +import org.tasks.makers.CaldavTaskMaker.REMOTE_ID +import org.tasks.makers.CaldavTaskMaker.TASK +import org.tasks.makers.CaldavTaskMaker.VTODO +import org.tasks.makers.CaldavTaskMaker.newCaldavTask +import org.tasks.makers.TaskMaker.DUE_DATE +import org.tasks.makers.TaskMaker.HIDE_TYPE +import org.tasks.makers.TaskMaker.MODIFICATION_TIME +import org.tasks.makers.TaskMaker.newTask +import org.tasks.time.DateTime +import javax.inject.Inject + +@UninstallModules(ProductionModule::class) +@HiltAndroidTest +class Upgrade_11_3_Test : InjectingTestCase() { + @Inject lateinit var taskDao: TaskDao + @Inject lateinit var caldavDao: CaldavDao + @Inject lateinit var openTaskDao: OpenTaskDao + @Inject lateinit var upgrader: Upgrade_11_3 + + @Test + fun applyRemoteiCalendarStartDate() = runBlocking { + val taskId = taskDao.insert(newTask()) + caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE))) + upgrader.applyiCalendarStartDates() + + assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil) + } + + @Test + fun ignoreRemoteiCalendarStartDate() = runBlocking { + val taskId = taskDao.insert(newTask( + with(DUE_DATE, DateTime(2021, 1, 20)), + with(HIDE_TYPE, Task.HIDE_UNTIL_DUE) + )) + caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE))) + upgrader.applyiCalendarStartDates() + + assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil) + } + + @Test + fun touchTaskWithLocaliCalendarStartDate() = runBlocking { + val upgradeTime = DateTime(2021, 1, 21, 11, 47, 32, 450) + val taskId = taskDao.insert(newTask( + with(DUE_DATE, DateTime(2021, 1, 20)), + with(HIDE_TYPE, Task.HIDE_UNTIL_DUE), + with(MODIFICATION_TIME, DateTime(2021, 1, 21, 9, 50, 4, 348)) + )) + caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_WITH_START_DATE))) + + freezeAt(upgradeTime) { + upgrader.applyiCalendarStartDates() + } + + assertEquals(upgradeTime, taskDao.fetch(taskId)?.modificationDate) + } + + @Test + fun dontTouchWhenNoiCalendarStartDate() = runBlocking { + val modificationTime = DateTime(2021, 1, 21, 9, 50, 4, 348) + val taskId = taskDao.insert(newTask(with(MODIFICATION_TIME, modificationTime))) + caldavDao.insert(newCaldavTask(with(TASK, taskId), with(VTODO, VTODO_NO_START_DATE))) + + upgrader.applyiCalendarStartDates() + + assertEquals(modificationTime, taskDao.fetch(taskId)?.modificationDate) + } + + @Test + fun applyRemoteOpenTaskStartDate() = runBlocking { + val (listId, list) = insertList() + applyOperation( + MyAndroidTask(fromString(VTODO_WITH_START_DATE)) + .toBuilder(openTaskDao.tasks, true) + .withValue(TaskContract.TaskColumns.LIST_ID, listId) + ) + val taskId = taskDao.insert(newTask()) + caldavDao.insert(newCaldavTask( + with(CALENDAR, list.uuid), + with(REMOTE_ID, "4586964443060640060"), + with(TASK, taskId) + )) + + upgrader.applyOpenTaskStartDates() + + assertEquals(DateTime(2021, 1, 21), taskDao.fetch(taskId)?.hideUntil) + } + + @Test + fun ignoreRemoteOpenTaskStartDate() = runBlocking { + val (listId, list) = insertList() + applyOperation( + MyAndroidTask(fromString(VTODO_WITH_START_DATE)) + .toBuilder(openTaskDao.tasks, true) + .withValue(TaskContract.TaskColumns.LIST_ID, listId) + ) + val taskId = taskDao.insert(newTask( + with(DUE_DATE, DateTime(2021, 1, 20)), + with(HIDE_TYPE, Task.HIDE_UNTIL_DUE) + )) + caldavDao.insert(newCaldavTask( + with(CALENDAR, list.uuid), + with(REMOTE_ID, "4586964443060640060"), + with(TASK, taskId) + )) + + upgrader.applyOpenTaskStartDates() + + assertEquals(DateTime(2021, 1, 20), taskDao.fetch(taskId)?.hideUntil) + } + + @Test + fun touchWithOpenTaskStartDate() = runBlocking { + val upgradeTime = DateTime(2021, 1, 21, 11, 47, 32, 450) + val (listId, list) = insertList() + applyOperation( + MyAndroidTask(fromString(VTODO_WITH_START_DATE)) + .toBuilder(openTaskDao.tasks, true) + .withValue(TaskContract.TaskColumns.LIST_ID, listId) + ) + val taskId = taskDao.insert(newTask( + with(DUE_DATE, DateTime(2021, 1, 20)), + with(HIDE_TYPE, Task.HIDE_UNTIL_DUE), + with(MODIFICATION_TIME, DateTime(2021, 1, 21, 9, 50, 4, 348)) + )) + caldavDao.insert(newCaldavTask( + with(CALENDAR, list.uuid), + with(REMOTE_ID, "4586964443060640060"), + with(TASK, taskId) + )) + + freezeAt(upgradeTime) { + upgrader.applyOpenTaskStartDates() + } + + assertEquals(upgradeTime, taskDao.fetch(taskId)?.modificationDate) + } + + @Test + fun dontTouchNoOpenTaskStartDate() = runBlocking { + val modificationTime = DateTime(2021, 1, 21, 9, 50, 4, 348) + val (listId, list) = insertList() + applyOperation( + MyAndroidTask(fromString(VTODO_NO_START_DATE)) + .toBuilder(openTaskDao.tasks, true) + .withValue(TaskContract.TaskColumns.LIST_ID, listId) + ) + val taskId = taskDao.insert(newTask(with(MODIFICATION_TIME, modificationTime))) + caldavDao.insert(newCaldavTask( + with(CALENDAR, list.uuid), + with(REMOTE_ID, "4586964443060640060"), + with(TASK, taskId) + )) + + upgrader.applyOpenTaskStartDates() + + assertEquals(modificationTime, taskDao.fetch(taskId)?.modificationDate) + } + + private suspend fun insertList( + type: String = OpenTaskDao.ACCOUNT_TYPE_DAVx5, + account: String = "account" + ): Pair { + val url = UUIDHelper.newUUID() + val uri = openTaskDao.taskLists.buildUpon() + .appendQueryParameter(CALLER_IS_SYNCADAPTER, "true") + .appendQueryParameter(TaskLists.ACCOUNT_NAME, account) + .appendQueryParameter(TaskLists.ACCOUNT_TYPE, type) + .build() + val result = applyOperation( + BatchOperation.CpoBuilder.newInsert(uri) + .withValue(TaskContract.CommonSyncColumns._SYNC_ID, url) + .withValue(TaskLists.SYNC_ENABLED, "1") + ) + return Pair(result.uri!!.lastPathSegment!!, CaldavCalendar().apply { + uuid = UUIDHelper.newUUID() + this.account = "$type:$account" + this.url = url + caldavDao.insert(this) + }) + } + + private fun applyOperation(build: BatchOperation.CpoBuilder): ContentProviderResult = + context.contentResolver.applyBatch(openTaskDao.authority, arrayListOf(build.build()))[0] + + companion object { + val VTODO_WITH_START_DATE = """ + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:+//IDN tasks.org//android-110301//EN + BEGIN:VTODO + DTSTAMP:20210121T153032Z + UID:4586964443060640060 + CREATED:20210121T153000Z + LAST-MODIFIED:20210121T153029Z + SUMMARY:Test + PRIORITY:9 + X-APPLE-SORT-ORDER:-27 + DTSTART;VALUE=DATE:20210121 + END:VTODO + END:VCALENDAR + """.trimIndent() + + val VTODO_NO_START_DATE = """ + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:+//IDN tasks.org//android-110301//EN + BEGIN:VTODO + DTSTAMP:20210121T153032Z + UID:4586964443060640060 + CREATED:20210121T153000Z + LAST-MODIFIED:20210121T153029Z + SUMMARY:Test + PRIORITY:9 + X-APPLE-SORT-ORDER:-27 + END:VTODO + END:VCALENDAR + """.trimIndent() + } +} \ No newline at end of file diff --git a/app/src/commonTest/java/org/tasks/TestUtilities.kt b/app/src/commonTest/java/org/tasks/TestUtilities.kt index 76c569885..0bcc61ed0 100644 --- a/app/src/commonTest/java/org/tasks/TestUtilities.kt +++ b/app/src/commonTest/java/org/tasks/TestUtilities.kt @@ -6,11 +6,18 @@ import com.todoroo.astrid.data.Task import org.tasks.caldav.CaldavConverter import org.tasks.data.CaldavTask import org.tasks.preferences.Preferences +import org.tasks.time.DateTime import java.io.StringReader import java.nio.file.Files import java.nio.file.Paths object TestUtilities { + fun assertEquals(expected: Long, actual: DateTime) = + org.junit.Assert.assertEquals(expected, actual.millis) + + fun assertEquals(expected: DateTime, actual: Long?) = + org.junit.Assert.assertEquals(expected.millis, actual) + fun newPreferences(context: Context): Preferences { return Preferences(context, "test_preferences") } @@ -39,7 +46,7 @@ object TestUtilities { return String(Files.readAllBytes(paths), Charsets.UTF_8) } - private fun fromString(task: String): at.bitfire.ical4android.Task = + fun fromString(task: String): at.bitfire.ical4android.Task = tasksFromReader(StringReader(task)) .takeIf { it.size == 1 } ?.first() diff --git a/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt b/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt index 6cd00b63a..003f839ad 100644 --- a/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt +++ b/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt @@ -12,11 +12,13 @@ object CaldavTaskMaker { val TASK: Property = newProperty() val REMOTE_ID: Property = newProperty() val REMOTE_PARENT: Property = newProperty() + val VTODO: Property = newProperty() private val instantiator = Instantiator { val task = CaldavTask(it.valueOf(TASK, 1L), it.valueOf(CALENDAR, "calendar")) task.remoteId = it.valueOf(REMOTE_ID, task.remoteId) task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?) + task.vtodo = it.valueOf(VTODO, null as String?) task } diff --git a/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt b/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt index 4fb3d5dcb..c79604468 100644 --- a/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt +++ b/app/src/commonTest/java/org/tasks/makers/TaskMaker.kt @@ -21,6 +21,7 @@ object TaskMaker { val RANDOM_REMINDER_PERIOD: Property = newProperty() val HIDE_TYPE: Property = newProperty() val REMINDERS: Property = newProperty() + val MODIFICATION_TIME: Property = newProperty() val CREATION_TIME: Property = newProperty() val COMPLETION_TIME: Property = newProperty() val DELETION_TIME: Property = newProperty() @@ -89,7 +90,7 @@ object TaskMaker { task.uuid = lookup.valueOf(UUID, NO_UUID) val creationTime = lookup.valueOf(CREATION_TIME, DateTimeUtils.newDateTime()) task.creationDate = creationTime.millis - task.modificationDate = creationTime.millis + task.modificationDate = lookup.valueOf(MODIFICATION_TIME, creationTime).millis task.parent = lookup.valueOf(PARENT, 0L) task } diff --git a/app/src/main/java/com/todoroo/astrid/service/Upgrade_11_3.kt b/app/src/main/java/com/todoroo/astrid/service/Upgrade_11_3.kt new file mode 100644 index 000000000..55a06ced9 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/service/Upgrade_11_3.kt @@ -0,0 +1,55 @@ +@file:Suppress("ClassName") + +package com.todoroo.astrid.service + +import org.tasks.caldav.iCalendar +import org.tasks.caldav.iCalendar.Companion.apply +import org.tasks.data.OpenTaskDao +import org.tasks.data.TaskDao +import org.tasks.data.UpgraderDao +import javax.inject.Inject + +class Upgrade_11_3 @Inject constructor( + private val upgraderDao: UpgraderDao, + private val openTaskDao: OpenTaskDao, + private val taskDao: TaskDao +) { + internal suspend fun applyiCalendarStartDates() { + val (hasStartDate, noStartDate) = + upgraderDao.tasksWithVtodos().partition { it.startDate > 0 } + for (task in noStartDate) { + task.vtodo?.let { iCalendar.fromVtodo(it) }?.dtStart?.let { + it.apply(task.task) + upgraderDao.setStartDate(task.id, task.startDate) + } + } + hasStartDate + .map { it.id } + .let { taskDao.touch(it) } + } + + internal suspend fun applyOpenTaskStartDates() { + openTaskDao.getLists().forEach { list -> + val (hasStartDate, noStartDate) = + upgraderDao + .getOpenTasksForList(list.account!!, list.url!!) + .partition { it.startDate > 0 } + for (task in noStartDate) { + openTaskDao + .getTask(list.id, task.remoteId!!) + ?.dtStart + ?.let { + it.apply(task.task) + upgraderDao.setStartDate(task.id, task.startDate) + } + } + hasStartDate + .map { it.id } + .let { taskDao.touch(it) } + } + } + + companion object { + const val VERSION = 110300 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/service/Upgrader.kt b/app/src/main/java/com/todoroo/astrid/service/Upgrader.kt index 459a1c187..e63377b8d 100644 --- a/app/src/main/java/com/todoroo/astrid/service/Upgrader.kt +++ b/app/src/main/java/com/todoroo/astrid/service/Upgrader.kt @@ -7,12 +7,12 @@ import com.google.common.collect.ListMultimap import com.google.common.collect.Multimaps import com.todoroo.astrid.api.GtasksFilter import com.todoroo.astrid.dao.TaskDao +import dagger.Lazy import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.runBlocking import org.tasks.R import org.tasks.Strings.isNullOrEmpty import org.tasks.caldav.iCalendar -import org.tasks.caldav.iCalendar.Companion.apply import org.tasks.caldav.iCalendar.Companion.fromVtodo import org.tasks.caldav.iCalendar.Companion.getParent import org.tasks.caldav.iCalendar.Companion.order @@ -43,7 +43,7 @@ class Upgrader @Inject constructor( private val widgetManager: AppWidgetManager, private val taskMover: TaskMover, private val upgraderDao: UpgraderDao, - private val openTaskDao: OpenTaskDao) { + private val upgrade_11_3: Lazy) { fun upgrade(from: Int, to: Int) { if (from > 0) { @@ -72,9 +72,11 @@ class Upgrader @Inject constructor( .filter { it.getSql().trim() == "WHERE" } .forEach { filterDao.delete(it) } } - run(from, V11_3) { - applyiCalendarStartDates() - applyOpenTaskStartDates() + run(from, Upgrade_11_3.VERSION) { + with(upgrade_11_3.get()) { + applyiCalendarStartDates() + applyOpenTaskStartDates() + } } preferences.setBoolean(R.string.p_just_updated, true) } @@ -122,39 +124,6 @@ class Upgrader @Inject constructor( return getAndroidColor(context, index) } - private suspend fun applyiCalendarStartDates() { - val (hasStartDate, noStartDate) = upgraderDao.tasksWithVtodos().partition { it.startDate > 0 } - for (task in noStartDate) { - task.vtodo?.let { fromVtodo(it) }?.dtStart?.let { - it.apply(task.task) - upgraderDao.setStartDate(task.id, task.startDate) - } - } - hasStartDate - .map { it.id } - .let { taskDao.touch(it) } - } - - private suspend fun applyOpenTaskStartDates() { - openTaskDao.getLists().forEach { list -> - val (hasStartDate, noStartDate) = - upgraderDao - .getOpenTasksForList(list.account!!, list.url!!) - .partition { it.startDate > 0 } - for (task in noStartDate) { - openTaskDao - .getTask(list.id, task.remoteId!!) - ?.dtStart - ?.let { - it.apply(task.task) - upgraderDao.setStartDate(task.id, task.startDate) - } - } - hasStartDate - .map { it.id } - .let { taskDao.touch(it) } - } - } private suspend fun applyCaldavOrder() { for (task in upgraderDao.tasksWithVtodos().map(CaldavTaskContainer::caldavTask)) { @@ -357,7 +326,6 @@ class Upgrader @Inject constructor( const val V9_7 = 90700 const val V9_7_3 = 90704 const val V10_0_2 = 100012 - const val V11_3 = 110300 @JvmStatic fun getAndroidColor(context: Context, index: Int): Int { diff --git a/app/src/main/java/org/tasks/data/OpenTaskDao.kt b/app/src/main/java/org/tasks/data/OpenTaskDao.kt index 806fbfde6..1c9f9ff9a 100644 --- a/app/src/main/java/org/tasks/data/OpenTaskDao.kt +++ b/app/src/main/java/org/tasks/data/OpenTaskDao.kt @@ -6,6 +6,7 @@ import android.content.ContentProviderOperation.newInsert import android.content.ContentValues import android.content.Context import android.database.Cursor +import android.net.Uri import at.bitfire.ical4android.BatchOperation import at.bitfire.ical4android.Task import at.bitfire.ical4android.UnknownProperty @@ -28,7 +29,8 @@ class OpenTaskDao @Inject constructor( ) { private val cr = context.contentResolver val authority = context.getString(R.string.opentasks_authority) - val tasks = Tasks.getContentUri(authority) + val tasks: Uri = Tasks.getContentUri(authority) + val taskLists: Uri = TaskLists.getContentUri(authority) private val properties = Properties.getContentUri(authority) suspend fun newAccounts(): List = getListsByAccount().newAccounts(caldavDao) @@ -39,7 +41,7 @@ class OpenTaskDao @Inject constructor( suspend fun getLists(): List = withContext(Dispatchers.IO) { val calendars = ArrayList() cr.query( - TaskLists.getContentUri(authority), + taskLists, null, "${TaskListColumns.SYNC_ENABLED}=1 AND ($SUPPORTED_TYPE_FILTER)", null, @@ -192,10 +194,7 @@ class OpenTaskDao @Inject constructor( suspend fun getTask(listId: Long, uid: String): Task? = withContext(Dispatchers.IO) { cr.query( - Tasks.getContentUri(authority) - .buildUpon() - .appendQueryParameter(LOAD_PROPERTIES, "1") - .build(), + tasks.buildUpon().appendQueryParameter(LOAD_PROPERTIES, "1").build(), null, "${Tasks.LIST_ID} = $listId AND ${Tasks._UID} = '$uid'", null, @@ -210,7 +209,7 @@ class OpenTaskDao @Inject constructor( companion object { private const val OPENTASK_BATCH_LIMIT = 499 - private const val ACCOUNT_TYPE_DAVx5 = "bitfire.at.davdroid" + const val ACCOUNT_TYPE_DAVx5 = "bitfire.at.davdroid" private const val ACCOUNT_TYPE_ETESYNC = "com.etesync.syncadapter" private const val ACCOUNT_TYPE_DECSYNC = "org.decsync.tasks" val SUPPORTED_TYPES = setOf(