diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d09cd547..c9c855a28 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -228,6 +228,7 @@ dependencies { androidTestImplementation("androidx.test:rules:${Versions.androidx_test}") androidTestImplementation("androidx.test.ext:junit:1.1.2") androidTestImplementation("androidx.annotation:annotation:1.1.0") + androidTestImplementation("com.squareup.okhttp3:mockwebserver:${Versions.okhttp}") testImplementation("junit:junit:4.13.1") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2") diff --git a/app/src/androidTest/java/org/tasks/caldav/CaldavSynchronizerTest.kt b/app/src/androidTest/java/org/tasks/caldav/CaldavSynchronizerTest.kt new file mode 100644 index 000000000..cfafc97c0 --- /dev/null +++ b/app/src/androidTest/java/org/tasks/caldav/CaldavSynchronizerTest.kt @@ -0,0 +1,219 @@ +package org.tasks.caldav + +import com.natpryce.makeiteasy.MakeItEasy.with +import com.todoroo.astrid.helper.UUIDHelper +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules +import kotlinx.coroutines.runBlocking +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import org.tasks.R +import org.tasks.data.CaldavAccount +import org.tasks.data.CaldavCalendar +import org.tasks.data.CaldavDao +import org.tasks.data.TaskDao +import org.tasks.injection.InjectingTestCase +import org.tasks.injection.ProductionModule +import org.tasks.makers.CaldavTaskMaker.CALENDAR +import org.tasks.makers.CaldavTaskMaker.ETAG +import org.tasks.makers.CaldavTaskMaker.OBJECT +import org.tasks.makers.CaldavTaskMaker.newCaldavTask +import org.tasks.preferences.Preferences +import org.tasks.security.KeyStoreEncryption +import javax.inject.Inject + +@UninstallModules(ProductionModule::class) +@HiltAndroidTest +class CaldavSynchronizerTest : InjectingTestCase() { + @Inject lateinit var synchronizer: CaldavSynchronizer + @Inject lateinit var encryption: KeyStoreEncryption + @Inject lateinit var preferences: Preferences + @Inject lateinit var caldavDao: CaldavDao + @Inject lateinit var taskDao: TaskDao + private val server = MockWebServer() + lateinit var account: CaldavAccount + + @Before + override fun setUp() = runBlocking { + super.setUp() + preferences.setBoolean(R.string.p_debug_pro, true) + server.start() + account = CaldavAccount().apply { + uuid = UUIDHelper.newUUID() + username = "username" + password = encryption.encrypt("password") + url = server.url("/remote.php/dav/calendars/user1/").toString() + id = caldavDao.insert(this) + } + } + + @After + fun after() = server.shutdown() + + @Test + fun setMessageOnError() = runBlocking { + enqueueFailure(500) + + synchronizer.sync(account) + + assertEquals("HTTP 500 Server Error", caldavDao.getAccounts().first().error) + } + + @Test + fun dontFetchCalendarIfCtagMatches() = runBlocking { + caldavDao.insert(CaldavCalendar().apply { + account = this@CaldavSynchronizerTest.account.uuid + ctag = "http://sabre.io/ns/sync/1" + url = "${this@CaldavSynchronizerTest.account.url}test-shared/" + }) + enqueue(OC_SHARE_PROPFIND) + enqueueFailure() + + synchronizer.sync(account) + + assertFalse(caldavDao.getAccountByUuid(account.uuid!!)!!.hasError) + } + + @Test + fun dontFetchTaskIfEtagMatches() = runBlocking { + val calendar = CaldavCalendar().apply { + account = this@CaldavSynchronizerTest.account.uuid + uuid = UUIDHelper.newUUID() + url = "${this@CaldavSynchronizerTest.account.url}test-shared/" + caldavDao.insert(this) + } + caldavDao.insert(newCaldavTask( + with(OBJECT, "3164728546640386952.ics"), + with(ETAG, "43b3ffaac5131880e4dd07a79adba82a"), + with(CALENDAR, calendar.uuid) + )) + enqueue(OC_SHARE_PROPFIND, OC_SHARE_REPORT) + enqueueFailure() + + synchronizer.sync(account) + + assertFalse(caldavDao.getAccountByUuid(account.uuid!!)!!.hasError) + } + + @Test + fun syncNewTask() = runBlocking { + enqueue(OC_SHARE_PROPFIND, OC_SHARE_REPORT, OC_SHARE_TASK) + + synchronizer.sync(account) + + val calendar = caldavDao.getCalendars().takeIf { it.size == 1 }!!.first() + val caldavTask = caldavDao.getTaskByRemoteId(calendar.uuid!!, "3164728546640386952")!! + assertEquals("Test task", taskDao.fetch(caldavTask.task)!!.title) + } + + private fun enqueue(vararg responses: String) = responses.forEach { + server.enqueue( + MockResponse() + .setResponseCode(207) + .setHeader("Content-Type", "text/xml; charset=\"utf-8\"") + .setBody(it) + ) + } + + private fun enqueueFailure(code: Int = 500) = + server.enqueue(MockResponse().setResponseCode(code)) + + companion object { + private val OC_SHARE_PROPFIND = """ + + + + /remote.php/dav/calendars/user1/test-shared/ + + + + + + + Test shared + + + + http://sabre.io/ns/sync/1 + #0082c9 + http://sabre.io/ns/sync/1 + principals/users/user1 + + + principal:principals/users/user2 + user2 + + + + + + + + HTTP/1.1 200 OK + + + + + + + HTTP/1.1 404 Not Found + + + + """.trimIndent() + + private val OC_SHARE_REPORT = """ + + + + /remote.php/dav/calendars/user1/test-shared/3164728546640386952.ics + + + "43b3ffaac5131880e4dd07a79adba82a" + + HTTP/1.1 200 OK + + + + """.trimIndent() + + private val OC_SHARE_TASK = """ + + + + /remote.php/dav/calendars/user1/test-shared/3164728546640386952.ics + + + text/calendar; charset=utf-8; component=vtodo + "43b3ffaac5131880e4dd07a79adba82a" + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:+//IDN tasks.org//android-110500//EN + BEGIN:VTODO + DTSTAMP:20210223T154147Z + UID:3164728546640386952 + CREATED:20210223T154134Z + LAST-MODIFIED:20210223T154140Z + SUMMARY:Test task + PRIORITY:9 + END:VTODO + END:VCALENDAR + + HTTP/1.1 200 OK + + + + + + HTTP/1.1 404 Not Found + + + + """.trimIndent() + } +} \ No newline at end of file diff --git a/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt b/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt index 07b0f7b48..050cfee62 100644 --- a/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt +++ b/app/src/commonTest/java/org/tasks/makers/CaldavTaskMaker.kt @@ -14,6 +14,8 @@ object CaldavTaskMaker { val REMOTE_PARENT: Property = newProperty() val REMOTE_ORDER: Property = newProperty() val VTODO: Property = newProperty() + val ETAG: Property = newProperty() + val OBJECT: Property = newProperty() private val instantiator = Instantiator { val task = CaldavTask(it.valueOf(TASK, 1L), it.valueOf(CALENDAR, "calendar")) @@ -21,6 +23,8 @@ object CaldavTaskMaker { task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?) task.vtodo = it.valueOf(VTODO, null as String?) task.order = it.valueOf(REMOTE_ORDER, null as Long?) + task.etag = it.valueOf(ETAG, null as String?) + task.`object` = it.valueOf(OBJECT, task.remoteId?.let { id -> "$id.ics" }) task } diff --git a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt index b35ba49c3..06a03a1b0 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt @@ -105,7 +105,7 @@ class CaldavSynchronizer @Inject constructor( } } setError(account, message) - } catch (e: DavException) { + } catch (e: Exception) { setError(account, e.message) firebase.reportException(e) }