Add MicrosoftConverter

pull/2068/head
Alex Baker 2 years ago
parent dd48d074be
commit f38b2324e9

@ -0,0 +1,78 @@
package org.tasks.sync.microsoft
import com.todoroo.astrid.data.Task
import org.tasks.data.CaldavTask
import org.tasks.data.TagData
import org.tasks.time.DateTime
import java.text.SimpleDateFormat
import java.util.*
object MicrosoftConverter {
private const val TYPE_TEXT = "text"
fun Task.applyRemote(
remote: Tasks.Task,
defaultPriority: Int,
) {
title = remote.title
notes = remote.body?.content?.takeIf { it.isNotBlank() }
priority = when {
remote.importance == Tasks.Task.Importance.high -> Task.Priority.HIGH
priority != Task.Priority.HIGH -> priority
defaultPriority != Task.Priority.HIGH -> defaultPriority
else -> Task.Priority.NONE
}
completionDate = remote.completedDateTime
?.let {
val tz = TimeZone.getTimeZone(it.timeZone)
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.ssssss")
.apply { timeZone = tz }
.parse(it.dateTime)
?.time
?.let { ts -> DateTime(ts, tz).toLocal().millis }
?: System.currentTimeMillis()
}
?: 0L
// checklist to subtasks
// due date
// repeat
// modification date
// creation date
// sync reminders
// sync files
}
fun Task.toRemote(caldavTask: CaldavTask, tags: List<TagData>): Tasks.Task {
return Tasks.Task(
id = caldavTask.remoteId,
title = title,
body = notes?.let {
Tasks.Task.Body(
content = it,
contentType = TYPE_TEXT,
)
},
importance = when (priority) {
Task.Priority.HIGH -> Tasks.Task.Importance.high
Task.Priority.MEDIUM -> Tasks.Task.Importance.normal
else -> Tasks.Task.Importance.low
},
status = if (isCompleted) {
Tasks.Task.Status.completed
} else {
Tasks.Task.Status.notStarted
},
categories = tags.map { it.name!! }.takeIf { it.isNotEmpty() },
completedDateTime = if (isCompleted) {
Tasks.Task.CompletedDateTime(
dateTime = DateTime(completionDate).toUTC()
.toString("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS"),
timeZone = "UTC",
)
} else {
null
}
)
}
}

@ -2,6 +2,7 @@ package org.tasks
import android.content.Context
import at.bitfire.ical4android.Task.Companion.tasksFromReader
import com.squareup.moshi.Moshi
import com.todoroo.astrid.data.Task
import kotlinx.coroutines.runBlocking
import org.tasks.caldav.applyRemote
@ -9,6 +10,8 @@ import org.tasks.caldav.iCalendar.Companion.reminders
import org.tasks.data.Alarm
import org.tasks.data.CaldavTask
import org.tasks.preferences.Preferences
import org.tasks.sync.microsoft.MicrosoftConverter.applyRemote
import org.tasks.sync.microsoft.Tasks
import org.tasks.time.DateTime
import java.io.StringReader
import java.nio.file.Files
@ -16,7 +19,8 @@ import java.nio.file.Paths
import java.util.*
object TestUtilities {
fun withTZ(id: String, runnable: suspend () -> Unit) = withTZ(TimeZone.getTimeZone(id), runnable)
fun withTZ(id: String, runnable: suspend () -> Unit) =
withTZ(TimeZone.getTimeZone(id), runnable)
fun withTZ(tz: TimeZone, runnable: suspend () -> Unit) {
val def = TimeZone.getDefault()
@ -31,10 +35,10 @@ object TestUtilities {
}
fun assertEquals(expected: Long, actual: DateTime) =
org.junit.Assert.assertEquals(expected, actual.millis)
org.junit.Assert.assertEquals(expected, actual.millis)
fun assertEquals(expected: DateTime, actual: Long?) =
org.junit.Assert.assertEquals(expected.millis, actual)
org.junit.Assert.assertEquals(expected.millis, actual)
fun newPreferences(context: Context): Preferences {
return Preferences(context, "test_preferences")
@ -42,34 +46,43 @@ object TestUtilities {
fun vtodo(path: String): Task {
val task = Task()
task.applyRemote(fromResource(path), null)
task.applyRemote(icalendarFromFile(path), null)
return task
}
val String.alarms: List<Alarm>
get() = fromResource(this).reminders
get() = icalendarFromFile(this).reminders
fun setup(path: String): Triple<Task, CaldavTask, at.bitfire.ical4android.Task> {
val task = Task()
val vtodo = readFile(path)
val remote = fromString(vtodo)
val remote = icalendarFromFile(path)
task.applyRemote(remote, null)
return Triple(task, CaldavTask(), remote)
}
private fun fromResource(path: String): at.bitfire.ical4android.Task =
fromString(readFile(path))
fun icalendarFromFile(path: String): at.bitfire.ical4android.Task =
tasksFromReader(StringReader(readFile(path)))
.takeIf { it.size == 1 }
?.first()
?: throw IllegalStateException()
fun mstodo(
path: String,
task: Task = Task(),
defaultPriority: Int = Task.Priority.NONE,
): Pair<Task, Tasks.Task> {
val remote = mstodoFromFile(path)
task.applyRemote(remote, defaultPriority)
return Pair(task, remote)
}
private fun mstodoFromFile(path: String): Tasks.Task =
Moshi.Builder().build().adapter(Tasks::class.java).fromJson(readFile(path))!!.value.first()
fun readFile(path: String): String {
val uri = javaClass.classLoader?.getResource(path)?.toURI()
?: throw IllegalArgumentException()
?: throw IllegalArgumentException()
val paths = Paths.get(uri)
return String(Files.readAllBytes(paths), Charsets.UTF_8)
}
fun fromString(task: String): at.bitfire.ical4android.Task =
tasksFromReader(StringReader(task))
.takeIf { it.size == 1 }
?.first()
?: throw IllegalStateException()
}

@ -0,0 +1,65 @@
package org.tasks.sync.microsoft
import com.natpryce.makeiteasy.MakeItEasy
import com.todoroo.astrid.data.Task
import org.junit.Assert
import org.junit.Test
import org.tasks.TestUtilities
import org.tasks.makers.TaskMaker
import org.tasks.time.DateTime
class ConvertFromMicrosoftTests {
@Test
fun titleFromRemote() {
val (local, _) = TestUtilities.mstodo("microsoft/basic_task.txt")
Assert.assertEquals("Basic task", local.title)
}
@Test
fun useNullForBlankBody() {
val (local, _) = TestUtilities.mstodo("microsoft/basic_task.txt")
Assert.assertNull(local.notes)
}
@Test
fun keepPriority() {
val (local, _) = TestUtilities.mstodo(
"microsoft/basic_task.txt",
task = TaskMaker.newTask(MakeItEasy.with(TaskMaker.PRIORITY, Task.Priority.MEDIUM)),
defaultPriority = Task.Priority.LOW
)
Assert.assertEquals(Task.Priority.MEDIUM, local.priority)
}
@Test
fun useDefaultPriority() {
val (local, _) = TestUtilities.mstodo(
"microsoft/basic_task.txt",
task = TaskMaker.newTask(MakeItEasy.with(TaskMaker.PRIORITY, Task.Priority.HIGH)),
defaultPriority = Task.Priority.LOW
)
Assert.assertEquals(Task.Priority.LOW, local.priority)
}
@Test
fun noPriorityWhenDefaultIsHigh() {
val (local, _) = TestUtilities.mstodo(
"microsoft/basic_task.txt",
task = TaskMaker.newTask(MakeItEasy.with(TaskMaker.PRIORITY, Task.Priority.HIGH)),
defaultPriority = Task.Priority.HIGH
)
Assert.assertEquals(Task.Priority.NONE, local.priority)
}
@Test
fun noCompletionDate() {
val (local, _) = TestUtilities.mstodo("microsoft/basic_task.txt")
Assert.assertEquals(0, local.completionDate)
}
@Test
fun parseCompletionDate() {
val (local, _) = TestUtilities.mstodo("microsoft/completed_task.txt")
Assert.assertEquals(DateTime(2022, 9, 18, 0, 0).millis, local.completionDate)
}
}

@ -0,0 +1,115 @@
package org.tasks.sync.microsoft
import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.astrid.data.Task
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TagDataMaker.NAME
import org.tasks.makers.TagDataMaker.newTagData
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.DESCRIPTION
import org.tasks.makers.TaskMaker.PRIORITY
import org.tasks.makers.TaskMaker.TITLE
import org.tasks.makers.TaskMaker.newTask
import org.tasks.sync.microsoft.MicrosoftConverter.toRemote
import org.tasks.sync.microsoft.Tasks.Task.Importance
import org.tasks.time.DateTime
class ConvertToMicrosoftTests {
@Test
fun noIdForNewTask() {
val remote =
newTask().toRemote(newCaldavTask(with(REMOTE_ID, null as String?)), emptyList())
assertNull(remote.id)
}
@Test
fun setTitle() {
val remote =
newTask(with(TITLE, "title")).toRemote(newCaldavTask(), emptyList())
assertEquals("title", remote.title)
}
@Test
fun noBody() {
val remote =
newTask(with(DESCRIPTION, null as String?))
.toRemote(newCaldavTask(), emptyList())
assertNull(remote.body)
}
@Test
fun setBody() {
val remote =
newTask(with(DESCRIPTION, "Description"))
.toRemote(newCaldavTask(), emptyList())
assertEquals("Description", remote.body?.content)
assertEquals("text", remote.body?.contentType)
}
@Test
fun setHighPriority() {
val remote =
newTask(with(PRIORITY, Task.Priority.HIGH))
.toRemote(newCaldavTask(), emptyList())
assertEquals(Importance.high, remote.importance)
}
@Test
fun setNormalPriority() {
val remote =
newTask(with(PRIORITY, Task.Priority.MEDIUM))
.toRemote(newCaldavTask(), emptyList())
assertEquals(Importance.normal, remote.importance)
}
@Test
fun setLowPriority() {
val remote =
newTask(with(PRIORITY, Task.Priority.LOW))
.toRemote(newCaldavTask(), emptyList())
assertEquals(Importance.low, remote.importance)
}
@Test
fun setNoPriorityToLow() {
val remote =
newTask(with(PRIORITY, Task.Priority.NONE))
.toRemote(newCaldavTask(), emptyList())
assertEquals(Importance.low, remote.importance)
}
@Test
fun statusForUncompletedTask() {
val remote = newTask().toRemote(newCaldavTask(), emptyList())
assertEquals(Tasks.Task.Status.notStarted, remote.status)
}
@Test
fun statusForCompletedTask() {
val remote =
newTask(with(COMPLETION_TIME, DateTime())).toRemote(newCaldavTask(), emptyList())
assertEquals(Tasks.Task.Status.completed, remote.status)
}
@Test
fun noCategories() {
val remote = newTask().toRemote(newCaldavTask(), emptyList())
assertNull(remote.categories)
}
@Test
fun setCategories() {
val remote = newTask().toRemote(
newCaldavTask(),
listOf(
newTagData(with(NAME, "tag1")),
newTagData(with(NAME, "tag2")),
)
)
assertEquals(listOf("tag1", "tag2"), remote.categories)
}
}

@ -0,0 +1,23 @@
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(todoTask)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/todo/lists/AQMkADAwATNiZmYAZC04OABiMC0xZDlkLTAwAi0wMAoALgAAA8dKmrSa60tBjeiKoPukmoQBAEkTxp6Wx2BEtV1vFJWK7bIAAAIBEgAAAA==/tasks/delta?$deltatoken=l7WI41swwioT5csv4k99nj_e6d30OPWG40P_OyQlkkny185Z38qUU3jLaP2oRC5t77tVxtd6SMRj0dEaTBh3MyHws0GnzQINZQHOs_ww6Z8.lmA23NEL8QGEOxyZEu-Uk2FszHDtjqkPsyfdoZaFIeE",
"value": [
{
"@odata.type": "#microsoft.graph.todoTask",
"@odata.etag": "W/\"SRPGnpbHYES1XW8UlYrtsgAFlRV2EQ==\"",
"importance": "normal",
"isReminderOn": false,
"status": "notStarted",
"title": "Basic task",
"createdDateTime": "2022-09-18T05:25:19.778574Z",
"lastModifiedDateTime": "2022-09-18T05:25:19.8723482Z",
"hasAttachments": false,
"categories": [],
"id": "AQMkADAwATNiZmYAZC04OABiMC0xZDlkLTAwAi0wMAoARgAAA8dKmrSa60tBjeiKoPukmoQHAEkTxp6Wx2BEtV1vFJWK7bIAAAIBEgAAAEkTxp6Wx2BEtV1vFJWK7bIABZUEdtwAAAA=",
"body": {
"content": "",
"contentType": "text"
}
}
]
}

@ -0,0 +1,27 @@
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(todoTask)",
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/todo/lists/AQMkADAwATNiZmYAZC04OABiMC0xZDlkLTAwAi0wMAoALgAAA8dKmrSa60tBjeiKoPukmoQBAEkTxp6Wx2BEtV1vFJWK7bIAAAIBEgAAAA==/tasks/delta?$deltatoken=l7WI41swwioT5csv4k99nj_e6d30OPWG40P_OyQlkkny185Z38qUU3jLaP2oRC5tqLCV4OXibNfHoYNFTO2TnftnvrLwkmDPtGMUcu3SygY.rtzLcPg02xeQVyGPsoU5bQ8c2Nt8tY8ZhjhN1OUfJdQ",
"value": [
{
"@odata.type": "#microsoft.graph.todoTask",
"@odata.etag": "W/\"SRPGnpbHYES1XW8UlYrtsgAFlRV2eQ==\"",
"importance": "normal",
"isReminderOn": false,
"status": "completed",
"title": "Basic task",
"createdDateTime": "2022-09-18T05:25:19.778574Z",
"lastModifiedDateTime": "2022-09-18T06:25:27.6073845Z",
"hasAttachments": false,
"categories": [],
"id": "AQMkADAwATNiZmYAZC04OABiMC0xZDlkLTAwAi0wMAoARgAAA8dKmrSa60tBjeiKoPukmoQHAEkTxp6Wx2BEtV1vFJWK7bIAAAIBEgAAAEkTxp6Wx2BEtV1vFJWK7bIABZUEdtwAAAA=",
"body": {
"content": "",
"contentType": "text"
},
"completedDateTime": {
"dateTime": "2022-09-18T05:00:00.0000000",
"timeZone": "UTC"
}
}
]
}
Loading…
Cancel
Save