diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a788fa49..da3ef7cd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,13 +2,13 @@ dokka = "2.0.0" junit4 = "4.13.2" kotlin = "2.2.0" -okhttpVersion = "4.12.0" +okhttpVersion = "5.1.0" xpp3Version = "1.1.6" [libraries] junit4 = { module = "junit:junit", version.ref = "junit4" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttpVersion" } -okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttpVersion" } +okhttp-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver3", version.ref = "okhttpVersion" } xpp3 = { module = "org.ogce:xpp3", version.ref = "xpp3Version" } [plugins] diff --git a/src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt b/src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt index 660c06ae..b0c5204a 100644 --- a/src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt +++ b/src/main/kotlin/at/bitfire/dav4jvm/DavResource.kt @@ -733,30 +733,29 @@ open class DavResource @JvmOverloads constructor( if (response.code != HTTP_MULTISTATUS) throw DavException("Expected 207 Multi-Status, got ${response.code} ${response.message}", httpResponse = response) - val body = response.body ?: - throw DavException("Received 207 Multi-Status without body", httpResponse = response) - - body.contentType()?.let { mimeType -> - if (((mimeType.type != "application" && mimeType.type != "text")) || mimeType.subtype != "xml") { - /* Content-Type is not application/xml or text/xml although that is expected here. - Some broken servers return an XML response with some other MIME type. So we try to see - whether the response is maybe XML although the Content-Type is something else. */ - try { - val firstBytes = ByteArray(XML_SIGNATURE.size) - body.source().peek().readFully(firstBytes) - if (XML_SIGNATURE.contentEquals(firstBytes)) { - logger.warning("Received 207 Multi-Status that seems to be XML but has MIME type $mimeType") - - // response is OK, return and do not throw Exception below - return + response.peekBody(XML_SIGNATURE.size.toLong()).use { body -> + body.contentType()?.let { mimeType -> + if (((mimeType.type != "application" && mimeType.type != "text")) || mimeType.subtype != "xml") { + /* Content-Type is not application/xml or text/xml although that is expected here. + Some broken servers return an XML response with some other MIME type. So we try to see + whether the response is maybe XML although the Content-Type is something else. */ + try { + response.peekBody(XML_SIGNATURE.size.toLong()).use { body -> + if (XML_SIGNATURE.contentEquals(body.bytes())) { + logger.warning("Received 207 Multi-Status that seems to be XML but has MIME type $mimeType") + + // response is OK, return and do not throw Exception below + return + } + } + } catch (e: Exception) { + logger.log(Level.WARNING, "Couldn't scan for XML signature", e) } - } catch (e: Exception) { - logger.log(Level.WARNING, "Couldn't scan for XML signature", e) - } - throw DavException("Received non-XML 207 Multi-Status", httpResponse = response) - } - } ?: logger.warning("Received 207 Multi-Status without Content-Type, assuming XML") + throw DavException("Received non-XML 207 Multi-Status", httpResponse = response) + } + } ?: logger.warning("Received 207 Multi-Status without Content-Type, assuming XML") + } } @@ -778,8 +777,8 @@ open class DavResource @JvmOverloads constructor( protected fun processMultiStatus(response: Response, callback: MultiResponseCallback): List { checkStatus(response) assertMultiStatus(response) - response.body!!.use { - return processMultiStatus(it.charStream(), callback) + return response.body.use { + processMultiStatus(it.charStream(), callback) } } diff --git a/src/test/kotlin/at/bitfire/dav4jvm/DavCalendarTest.kt b/src/test/kotlin/at/bitfire/dav4jvm/DavCalendarTest.kt index 46726130..86bc9b81 100644 --- a/src/test/kotlin/at/bitfire/dav4jvm/DavCalendarTest.kt +++ b/src/test/kotlin/at/bitfire/dav4jvm/DavCalendarTest.kt @@ -10,9 +10,9 @@ package at.bitfire.dav4jvm +import mockwebserver3.MockResponse +import mockwebserver3.MockWebServer import okhttp3.OkHttpClient -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Assert.assertEquals import org.junit.Before @@ -33,14 +33,14 @@ class DavCalendarTest { @After fun stopServer() { - mockServer.shutdown() + mockServer.close() } @Test fun calendarQuery_formatStartEnd() { val cal = DavCalendar(httpClient, mockServer.url("/")) - mockServer.enqueue(MockResponse().setResponseCode(207).setBody("")) + mockServer.enqueue(MockResponse.Builder().code(207).body("").build()) cal.calendarQuery("VEVENT", start = Instant.ofEpochSecond(784111777), end = Instant.ofEpochSecond(1689324577)) { _, _ -> } @@ -57,7 +57,7 @@ class DavCalendarTest { "" + "" + "" + - "", rq.body.readUtf8()) + "", rq.body?.utf8()) } } \ No newline at end of file diff --git a/src/test/kotlin/at/bitfire/dav4jvm/DavCollectionTest.kt b/src/test/kotlin/at/bitfire/dav4jvm/DavCollectionTest.kt index e6623030..c88b9db4 100644 --- a/src/test/kotlin/at/bitfire/dav4jvm/DavCollectionTest.kt +++ b/src/test/kotlin/at/bitfire/dav4jvm/DavCollectionTest.kt @@ -14,16 +14,13 @@ import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.property.webdav.GetETag import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV import at.bitfire.dav4jvm.property.webdav.SyncToken +import mockwebserver3.MockResponse +import mockwebserver3.MockWebServer import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Assert.fail +import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.net.HttpURLConnection @@ -42,7 +39,7 @@ class DavCollectionTest { fun startServer() = mockServer.start() @After - fun stopServer() = mockServer.shutdown() + fun stopServer() = mockServer.close() /** @@ -53,58 +50,62 @@ class DavCollectionTest { val url = sampleUrl() val collection = DavCollection(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(207) + mockServer.enqueue( + MockResponse.Builder() + .code(207) .setHeader("Content-Type", "text/xml; charset=\"utf-8\"") - .setBody("\n" + - " \n" + - " \n" + - " ${sampleUrl()}test.doc\n" + - " \n" + - " \n" + - " \"00001-abcd1\"\n" + - " \n" + - " Box type A\n" + - " \n" + - " \n" + - " HTTP/1.1 200 OK\n" + - " \n" + - " \n" + - " \n" + - " ${sampleUrl()}vcard.vcf\n" + - " \n" + - " \n" + - " \"00002-abcd1\"\n" + - " \n" + - " HTTP/1.1 200 OK\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " HTTP/1.1 404 Not Found\n" + - " \n" + - " \n" + - " \n" + - " ${sampleUrl()}calendar.ics\n" + - " \n" + - " \n" + - " \"00003-abcd1\"\n" + - " \n" + - " HTTP/1.1 200 OK\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " HTTP/1.1 404 Not Found\n" + - " \n" + - " \n" + - " http://example.com/ns/sync/1234\n" + - " ") + .body( + "\n" + + " \n" + + " \n" + + " ${sampleUrl()}test.doc\n" + + " \n" + + " \n" + + " \"00001-abcd1\"\n" + + " \n" + + " Box type A\n" + + " \n" + + " \n" + + " HTTP/1.1 200 OK\n" + + " \n" + + " \n" + + " \n" + + " ${sampleUrl()}vcard.vcf\n" + + " \n" + + " \n" + + " \"00002-abcd1\"\n" + + " \n" + + " HTTP/1.1 200 OK\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " HTTP/1.1 404 Not Found\n" + + " \n" + + " \n" + + " \n" + + " ${sampleUrl()}calendar.ics\n" + + " \n" + + " \n" + + " \"00003-abcd1\"\n" + + " \n" + + " HTTP/1.1 200 OK\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " HTTP/1.1 404 Not Found\n" + + " \n" + + " \n" + + " http://example.com/ns/sync/1234\n" + + " " + ) + .build() ) var nrCalled = 0 val result = collection.reportChanges(null, false, null, GetETag.NAME) { response, relation -> @@ -147,40 +148,44 @@ class DavCollectionTest { val url = sampleUrl() val collection = DavCollection(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(207) + mockServer.enqueue( + MockResponse.Builder() + .code(207) .setHeader("Content-Type", "text/xml; charset=\"utf-8\"") - .setBody("\n" + - " \n" + - " \n" + - " ${sampleUrl()}test.doc\n" + - " \n" + - " \n" + - " \"00001-abcd1\"\n" + - " \n" + - " HTTP/1.1 200 OK\n" + - " \n" + - " \n" + - " \n" + - " ${sampleUrl()}vcard.vcf\n" + - " \n" + - " \n" + - " \"00002-abcd1\"\n" + - " \n" + - " HTTP/1.1 200 OK\n" + - " \n" + - " \n" + - " \n" + - " ${sampleUrl()}removed.txt\n" + - " HTTP/1.1 404 Not Found\n" + - " " + - " \n" + - " ${sampleUrl()}\n" + - " HTTP/1.1 507 Insufficient Storage\n" + - " \n" + - " " + - " http://example.com/ns/sync/1233\n" + - " ") + .body( + "\n" + + " \n" + + " \n" + + " ${sampleUrl()}test.doc\n" + + " \n" + + " \n" + + " \"00001-abcd1\"\n" + + " \n" + + " HTTP/1.1 200 OK\n" + + " \n" + + " \n" + + " \n" + + " ${sampleUrl()}vcard.vcf\n" + + " \n" + + " \n" + + " \"00002-abcd1\"\n" + + " \n" + + " HTTP/1.1 200 OK\n" + + " \n" + + " \n" + + " \n" + + " ${sampleUrl()}removed.txt\n" + + " HTTP/1.1 404 Not Found\n" + + " " + + " \n" + + " ${sampleUrl()}\n" + + " HTTP/1.1 507 Insufficient Storage\n" + + " \n" + + " " + + " http://example.com/ns/sync/1233\n" + + " " + ) + .build() ) var nrCalled = 0 val result = collection.reportChanges(null, false, null, GetETag.NAME) { response, relation -> @@ -227,13 +232,17 @@ class DavCollectionTest { val url = sampleUrl() val collection = DavCollection(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(507) + mockServer.enqueue( + MockResponse.Builder() + .code(507) .setHeader("Content-Type", "text/xml; charset=\"utf-8\"") - .setBody("\n" + - " \n" + - " \n" + - " ") + .body( + "\n" + + " \n" + + " \n" + + " " + ) + .build() ) try { @@ -252,7 +261,7 @@ class DavCollectionTest { val dav = DavCollection(httpClient, url) // 201 Created - mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_CREATED)) + mockServer.enqueue(MockResponse.Builder().code(HttpURLConnection.HTTP_CREATED).build()) var called = false dav.post(sampleText.toRequestBody("text/plain".toMediaType())) { response -> assertEquals("POST", mockServer.takeRequest().method) diff --git a/src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt b/src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt index fdc73e9a..5673ac8e 100644 --- a/src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt +++ b/src/test/kotlin/at/bitfire/dav4jvm/DavResourceTest.kt @@ -17,6 +17,8 @@ import at.bitfire.dav4jvm.property.webdav.DisplayName import at.bitfire.dav4jvm.property.webdav.GetContentType import at.bitfire.dav4jvm.property.webdav.GetETag import at.bitfire.dav4jvm.property.webdav.ResourceType +import mockwebserver3.MockResponse +import mockwebserver3.MockWebServer import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -24,8 +26,6 @@ import okhttp3.Protocol import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.ResponseBody.Companion.toResponseBody -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse @@ -53,7 +53,7 @@ class DavResourceTest { @After fun stopServer() { - mockServer.shutdown() + mockServer.close() } private fun sampleUrl() = mockServer.url("/dav/") @@ -67,8 +67,9 @@ class DavResourceTest { /* POSITIVE TEST CASES */ // no preconditions, 201 Created, resulted in the creation of a new resource - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_CREATED)) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_CREATED) + .build()) var called = false DavResource(httpClient, url).let { dav -> dav.copy(destination, false) { @@ -79,14 +80,15 @@ class DavResourceTest { var rq = mockServer.takeRequest() assertEquals("COPY", rq.method) - assertEquals(url.encodedPath, rq.path) - assertEquals(destination.toString(), rq.getHeader("Destination")) - assertEquals("F", rq.getHeader("Overwrite")) + assertEquals(url, rq.url) + assertEquals(destination.toString(), rq.headers["Destination"]) + assertEquals("F", rq.headers["Overwrite"]) // no preconditions, 204 No content, resource successfully copied to a preexisting // destination resource - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_NO_CONTENT) + .build()) called = false DavResource(httpClient, url).let { dav -> dav.copy(destination, true) { @@ -97,24 +99,25 @@ class DavResourceTest { rq = mockServer.takeRequest() assertEquals("COPY", rq.method) - assertEquals(url.encodedPath, rq.path) - assertEquals(destination.toString(), rq.getHeader("Destination")) - assertNull(rq.getHeader("Overwrite")) + assertEquals(url, rq.url) + assertEquals(destination.toString(), rq.headers["Destination"]) + assertNull(rq.headers["Overwrite"]) /* NEGATIVE TEST CASES */ // 207 multi-status (e.g. errors on some of resources affected by // the COPY prevented the operation from taking place) - mockServer.enqueue(MockResponse() - .setResponseCode(207)) + mockServer.enqueue(MockResponse.Builder() + .code(207) + .build()) try { called = false DavResource(httpClient, url).let { dav -> dav.copy(destination, false) { called = true } fail("Expected HttpException") } - } catch(e: HttpException) { + } catch(_: HttpException) { assertFalse(called) } } @@ -127,8 +130,9 @@ class DavResourceTest { /* POSITIVE TEST CASES */ // no preconditions, 204 No Content - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_NO_CONTENT) + .build()) var called = false dav.delete { called = true @@ -137,13 +141,14 @@ class DavResourceTest { var rq = mockServer.takeRequest() assertEquals("DELETE", rq.method) - assertEquals(url.encodedPath, rq.path) - assertNull(rq.getHeader("If-Match")) + assertEquals(url, rq.url) + assertNull(rq.headers["If-Match"]) // precondition: If-Match / If-Schedule-Tag-Match, 200 OK - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody("Resource has been deleted.")) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .body("Resource has been deleted.") + .build()) called = false dav.delete("DeleteOnlyThisETag", "DeleteOnlyThisScheduleTag") { called = true @@ -151,16 +156,18 @@ class DavResourceTest { assertTrue(called) rq = mockServer.takeRequest() - assertEquals("\"DeleteOnlyThisETag\"", rq.getHeader("If-Match")) - assertEquals("\"DeleteOnlyThisScheduleTag\"", rq.getHeader("If-Schedule-Tag-Match")) + assertEquals("\"DeleteOnlyThisETag\"", rq.headers["If-Match"]) + assertEquals("\"DeleteOnlyThisScheduleTag\"", rq.headers["If-Schedule-Tag-Match"]) // 302 Moved Temporarily - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_MOVED_TEMP) .setHeader("Location", "/new-location") + .build() ) - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK)) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .build()) called = false dav.delete(null) { called = true @@ -170,13 +177,14 @@ class DavResourceTest { /* NEGATIVE TEST CASES */ // 207 multi-status (e.g. single resource couldn't be deleted when DELETEing a collection) - mockServer.enqueue(MockResponse() - .setResponseCode(207)) + mockServer.enqueue(MockResponse.Builder() + .code(207) + .build()) try { called = false dav.delete(null) { called = true } fail("Expected HttpException") - } catch(e: HttpException) { + } catch(_: HttpException) { assertFalse(called) } } @@ -241,41 +249,44 @@ class DavResourceTest { /* POSITIVE TEST CASES */ // 200 OK - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) .setHeader("ETag", "W/\"My Weak ETag\"") .setHeader("Content-Type", "application/x-test-result") - .setBody(sampleText)) + .body(sampleText) + .build()) var called = false dav.get("*/*", null) { response -> called = true - assertEquals(sampleText, response.body!!.string()) + assertEquals(sampleText, response.body.string()) val eTag = GetETag.fromResponse(response) assertEquals("My Weak ETag", eTag!!.eTag) assertTrue(eTag.weak) - assertEquals("application/x-test-result".toMediaType(), GetContentType(response.body!!.contentType()!!).type) + assertEquals("application/x-test-result".toMediaType(), GetContentType(response.body.contentType()!!).type) } assertTrue(called) var rq = mockServer.takeRequest() assertEquals("GET", rq.method) - assertEquals(url.encodedPath, rq.path) - assertEquals("*/*", rq.getHeader("Accept")) + assertEquals(url, rq.url) + assertEquals("*/*", rq.headers["Accept"]) // 302 Moved Temporarily + 200 OK - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_MOVED_TEMP) .setHeader("Location", "/target") - .setBody("This resource was moved.")) - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) + .body("This resource was moved.") + .build()) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) .setHeader("ETag", "\"StrongETag\"") - .setBody(sampleText)) + .body(sampleText) + .build()) called = false dav.get("*/*", null) { response -> called = true - assertEquals(sampleText, response.body!!.string()) + assertEquals(sampleText, response.body.string()) val eTag = GetETag(response.header("ETag")!!) assertEquals("StrongETag", eTag.eTag) assertFalse(eTag.weak) @@ -285,12 +296,13 @@ class DavResourceTest { mockServer.takeRequest() rq = mockServer.takeRequest() assertEquals("GET", rq.method) - assertEquals("/target", rq.path) + assertEquals("/target", rq.url.encodedPath) // 200 OK without ETag in response - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody(sampleText)) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .body(sampleText) + .build()) called = false dav.get("*/*", null) { response -> called = true @@ -304,8 +316,9 @@ class DavResourceTest { val url = sampleUrl() val dav = DavResource(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_PARTIAL)) + mockServer.enqueue(MockResponse.Builder() + .code(HttpURLConnection.HTTP_PARTIAL) + .build()) var called = false dav.getRange("*/*", 100, 342) { response -> assertEquals("bytes=100-441", response.request.header("Range")) @@ -323,51 +336,54 @@ class DavResourceTest { // 200 OK mockServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) + MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) .setHeader("ETag", "W/\"My Weak ETag\"") .setHeader("Content-Type", "application/x-test-result") - .setBody(sampleText) + .body(sampleText) + .build() ) var called = false dav.post( body = "body".toRequestBody("application/x-test-result".toMediaType()) ) { response -> called = true - assertEquals(sampleText, response.body!!.string()) + assertEquals(sampleText, response.body.string()) val eTag = GetETag.fromResponse(response) assertEquals("My Weak ETag", eTag!!.eTag) assertTrue(eTag.weak) - assertEquals("application/x-test-result".toMediaType(), GetContentType(response.body!!.contentType()!!).type) + assertEquals("application/x-test-result".toMediaType(), GetContentType(response.body.contentType()!!).type) } assertTrue(called) var rq = mockServer.takeRequest() assertEquals("POST", rq.method) - assertEquals(url.encodedPath, rq.path) - assertTrue(rq.getHeader("Content-Type")?.contains("application/x-test-result") == true) - assertEquals("body", rq.body.readUtf8()) + assertEquals(url, rq.url) + assertTrue(rq.headers["Content-Type"]?.contains("application/x-test-result") == true) + assertEquals("body", rq.body?.utf8()) // 302 Moved Temporarily + 200 OK mockServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP) + MockResponse.Builder() + .code(HttpURLConnection.HTTP_MOVED_TEMP) .setHeader("Location", "/target") - .setBody("This resource was moved.") + .body("This resource was moved.") + .build() ) mockServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) + MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) .setHeader("ETag", "\"StrongETag\"") - .setBody(sampleText) + .body(sampleText) + .build() ) called = false dav.post( body = "body".toRequestBody("application/x-test-result".toMediaType()) ) { response -> called = true - assertEquals(sampleText, response.body!!.string()) + assertEquals(sampleText, response.body.string()) val eTag = GetETag(response.header("ETag")!!) assertEquals("StrongETag", eTag.eTag) assertFalse(eTag.weak) @@ -377,13 +393,14 @@ class DavResourceTest { mockServer.takeRequest() rq = mockServer.takeRequest() assertEquals("POST", rq.method) - assertEquals("/target", rq.path) + assertEquals("/target", rq.url.encodedPath) // 200 OK without ETag in response mockServer.enqueue( - MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setBody(sampleText) + MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .body(sampleText) + .build() ) called = false dav.post( @@ -403,8 +420,11 @@ class DavResourceTest { /* POSITIVE TEST CASES */ // no preconditions, 201 Created, new URL mapping at the destination - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_CREATED)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_CREATED) + .build() + ) var called = false DavResource(httpClient, url).let { dav -> dav.move(destination, false) { @@ -416,13 +436,16 @@ class DavResourceTest { var rq = mockServer.takeRequest() assertEquals("MOVE", rq.method) - assertEquals(url.encodedPath, rq.path) - assertEquals(destination.toString(), rq.getHeader("Destination")) - assertEquals("F", rq.getHeader("Overwrite")) + assertEquals(url, rq.url) + assertEquals(destination.toString(), rq.headers["Destination"]) + assertEquals("F", rq.headers["Overwrite"]) // no preconditions, 204 No content, URL already mapped, overwrite - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_NO_CONTENT) + .build() + ) called = false DavResource(httpClient, url).let { dav -> dav.move(destination, true) { @@ -434,24 +457,27 @@ class DavResourceTest { rq = mockServer.takeRequest() assertEquals("MOVE", rq.method) - assertEquals(url.encodedPath, rq.path) - assertEquals(destination.toString(), rq.getHeader("Destination")) - assertNull(rq.getHeader("Overwrite")) + assertEquals(url, rq.url) + assertEquals(destination.toString(), rq.headers["Destination"]) + assertNull(rq.headers["Overwrite"]) /* NEGATIVE TEST CASES */ // 207 multi-status (e.g. errors on some of resources affected by // the MOVE prevented the operation from taking place) - mockServer.enqueue(MockResponse() - .setResponseCode(207)) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .build() + ) try { called = false DavResource(httpClient, url).let { dav -> dav.move(destination, false) { called = true } fail("Expected HttpException") } - } catch(e: HttpException) { + } catch(_: HttpException) { assertFalse(called) } } @@ -461,9 +487,12 @@ class DavResourceTest { val url = sampleUrl() val dav = DavResource(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK) - .setHeader("DAV", " 1, 2 ,3,hyperactive-access")) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .setHeader("DAV", " 1, 2 ,3,hyperactive-access") + .build() + ) var called = false dav.options { davCapabilities, _ -> called = true @@ -474,8 +503,11 @@ class DavResourceTest { } assertTrue(called) - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_OK)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .build() + ) called = false dav.options { davCapabilities, _ -> called = true @@ -493,74 +525,96 @@ class DavResourceTest { // test for non-multi-status responses: // * 500 Internal Server Error - mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_INTERNAL_ERROR) + .build() + ) var called = false try { dav.propfind(0, ResourceType.NAME) { _, _ -> called = true } fail("Expected HttpException") - } catch(e: HttpException) { + } catch(_: HttpException) { assertFalse(called) } // * 200 OK (instead of 207 Multi-Status) - mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_OK)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_OK) + .build() + ) try { called = false dav.propfind(0, ResourceType.NAME) { _, _ -> called = true } fail("Expected DavException") - } catch(e: DavException) { + } catch(_: DavException) { assertFalse(called) } // test for invalid multi-status responses: // * non-XML response - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "text/html") - .setBody("")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "text/html") + .body("") + .build() + ) try { called = false dav.propfind(0, ResourceType.NAME) { _, _ -> called = true } fail("Expected DavException") - } catch(e: DavException) { + } catch(_: DavException) { assertFalse(called) } // * malformed XML response - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body("") + .build() + ) try { called = false dav.propfind(0, ResourceType.NAME) { _, _ -> called = true } fail("Expected DavException") - } catch(e: DavException) { + } catch(_: DavException) { assertFalse(called) } // * response without root element - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body("") + .build() + ) try { called = false dav.propfind(0, ResourceType.NAME) { _, _ -> called = true } fail("Expected DavException") - } catch(e: DavException) { + } catch(_: DavException) { assertFalse(called) } // * multi-status response with invalid in - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " Invalid Status Line" + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " Invalid Status Line" + + " " + + "" + ) + .build() + ) called = false dav.propfind(0, ResourceType.NAME) { response, relation -> assertEquals(Response.HrefRelation.SELF, relation) @@ -570,15 +624,20 @@ class DavResourceTest { assertTrue(called) // * multi-status response with / element indicating failure - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " HTTP/1.1 403 Forbidden" + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " HTTP/1.1 403 Forbidden" + + " " + + "" + ) + .build() + ) called = false dav.propfind(0, ResourceType.NAME) { response, relation -> assertEquals(Response.HrefRelation.SELF, relation) @@ -588,20 +647,25 @@ class DavResourceTest { assertTrue(called) // * multi-status response with invalid in - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " " + - " " + - " " + - " " + - " Invalid Status Line" + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " " + + " " + + " " + + " " + + " Invalid Status Line" + + " " + + " " + + "" + ) + .build() + ) called = false dav.propfind(0, ResourceType.NAME) { response, relation -> called = true @@ -614,24 +678,31 @@ class DavResourceTest { /*** POSITIVE TESTS ***/ // multi-status response without elements - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body("").build() + ) dav.propfind(0, ResourceType.NAME) { _, _ -> fail("Shouldn't be called") } // multi-status response with / element indicating success - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " HTTP/1.1 200 OK" + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " HTTP/1.1 200 OK" + + " " + + "" + ) + .build() + ) called = false dav.propfind(0, ResourceType.NAME) { response, relation -> called = true @@ -642,21 +713,26 @@ class DavResourceTest { assertTrue(called) // multi-status response with / element - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " " + - " " + - " " + - " My DAV Collection" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " " + + " " + + " " + + " My DAV Collection" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + "" + ) + .build() + ) called = false dav.propfind(0, ResourceType.NAME, DisplayName.NAME) { response, relation -> called = true @@ -667,67 +743,71 @@ class DavResourceTest { assertTrue(called) // multi-status response for collection with several members; incomplete (not all s listed) - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " " + url.toString() + "" + - " " + - " " + - " " + - " My DAV Collection" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " /dav/subcollection" + - " " + - " " + - " " + - " A Subfolder" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " /dav/uid@host:file" + - " " + - " " + - " Absolute path with @ and :" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " relative-uid@host.file" + - " " + - " " + - " Relative path with @" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " relative:colon.vcf" + - " " + - " " + - " Relative path with colon" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " /something-very/else" + - " " + - " " + - " Not requested" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " " + url.toString() + "" + + " " + + " " + + " " + + " My DAV Collection" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " /dav/subcollection" + + " " + + " " + + " " + + " A Subfolder" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " /dav/uid@host:file" + + " " + + " " + + " Absolute path with @ and :" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " relative-uid@host.file" + + " " + + " " + + " Relative path with @" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " relative:colon.vcf" + + " " + + " " + + " Relative path with colon" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " /something-very/else" + + " " + + " " + + " Not requested" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + "" + ).build() + ) var nrCalled = 0 dav.propfind(1, ResourceType.NAME, DisplayName.NAME) { response, relation -> when (response.href) { @@ -771,27 +851,31 @@ class DavResourceTest { /*** SPECIAL CASES ***/ // same property is sent as 200 OK and 404 Not Found in same (seen in iCloud) - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " " + url.toString() + "" + - " " + - " " + - " " + - " My DAV Collection" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " " + - " " + - " HTTP/1.1 404 Not Found" + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " " + url.toString() + "" + + " " + + " " + + " " + + " My DAV Collection" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " " + + " " + + " HTTP/1.1 404 Not Found" + + " " + + " " + + "" + ).build() + ) called = false dav.propfind(0, ResourceType.NAME, DisplayName.NAME) { response, relation -> called = true @@ -804,19 +888,23 @@ class DavResourceTest { assertTrue(called) // multi-status response with that doesn't contain (=> assume 200 OK) - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " " + - " " + - " Without Status" + - " " + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " " + + " " + + " Without Status" + + " " + + " " + + " " + + "" + ).build() + ) called = false dav.propfind(0, DisplayName.NAME) { response, _ -> called = true @@ -832,26 +920,31 @@ class DavResourceTest { val dav = DavResource(httpClient, url) // multi-status response with / elements - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " " + - " " + - " Some Value" + - " " + - " HTTP/1.1 200 OK" + - " " + - " " + - " " + - " " + - " " + - " HTTP/1.1 404 Not Found" + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " " + + " " + + " Some Value" + + " " + + " HTTP/1.1 200 OK" + + " " + + " " + + " " + + " " + + " " + + " HTTP/1.1 404 Not Found" + + " " + + " " + + "" + ) + .build() + ) var called = false dav.proppatch( @@ -885,9 +978,12 @@ class DavResourceTest { /* POSITIVE TEST CASES */ // no preconditions, 201 Created - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_CREATED) - .setHeader("ETag", "W/\"Weak PUT ETag\"")) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_CREATED) + .setHeader("ETag", "W/\"Weak PUT ETag\"") + .build() + ) var called = false dav.put(sampleText.toRequestBody("text/plain".toMediaType())) { response -> called = true @@ -900,16 +996,20 @@ class DavResourceTest { var rq = mockServer.takeRequest() assertEquals("PUT", rq.method) - assertEquals(url.encodedPath, rq.path) - assertNull(rq.getHeader("If-Match")) - assertNull(rq.getHeader("If-None-Match")) + assertEquals(url, rq.url) + assertNull(rq.headers["If-Match"]) + assertNull(rq.headers["If-None-Match"]) // precondition: If-None-Match, 301 Moved Permanently + 204 No Content, no ETag in response - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_MOVED_PERM) - .setHeader("Location", "/target")) - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_NO_CONTENT)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_MOVED_PERM) + .setHeader("Location", "/target").build() + ) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_NO_CONTENT).build() + ) called = false dav.put(sampleText.toRequestBody("text/plain".toMediaType()), ifNoneMatch = true) { response -> called = true @@ -923,11 +1023,14 @@ class DavResourceTest { mockServer.takeRequest() rq = mockServer.takeRequest() assertEquals("PUT", rq.method) - assertEquals("*", rq.getHeader("If-None-Match")) + assertEquals("*", rq.headers["If-None-Match"]) // precondition: If-Match, 412 Precondition Failed - mockServer.enqueue(MockResponse() - .setResponseCode(HttpURLConnection.HTTP_PRECON_FAILED)) + mockServer.enqueue( + MockResponse.Builder() + .code(HttpURLConnection.HTTP_PRECON_FAILED) + .build() + ) called = false try { dav.put(sampleText.toRequestBody("text/plain".toMediaType()), "ExistingETag") { @@ -937,8 +1040,8 @@ class DavResourceTest { } catch(_: PreconditionFailedException) {} assertFalse(called) rq = mockServer.takeRequest() - assertEquals("\"ExistingETag\"", rq.getHeader("If-Match")) - assertNull(rq.getHeader("If-None-Match")) + assertEquals("\"ExistingETag\"", rq.headers["If-Match"]) + assertNull(rq.headers["If-None-Match"]) } @Test @@ -946,19 +1049,23 @@ class DavResourceTest { val url = sampleUrl() val dav = DavResource(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(207) - .setHeader("Content-Type", "application/xml; charset=utf-8") - .setBody("" + - " " + - " /dav" + - " " + - " " + - " Found something" + - " " + - " " + - " " + - "")) + mockServer.enqueue( + MockResponse.Builder() + .code(207) + .setHeader("Content-Type", "application/xml; charset=utf-8") + .body( + "" + + " " + + " /dav" + + " " + + " " + + " Found something" + + " " + + " " + + " " + + "" + ).build() + ) var called = false dav.search("") { response, hrefRelation -> assertEquals(Response.HrefRelation.SELF, hrefRelation) @@ -969,15 +1076,15 @@ class DavResourceTest { val rq = mockServer.takeRequest() assertEquals("SEARCH", rq.method) - assertEquals(url.encodedPath, rq.path) - assertEquals("", rq.body.readString(Charsets.UTF_8)) + assertEquals(url, rq.url) + assertEquals("", rq.body?.utf8()) } /** test helpers **/ - @Test(expected = DavException::class) - fun testAssertMultiStatus_NoBody_NoXML() { + @Test + fun testAssertMultiStatus_EmptyBody_NoXML() { val dav = DavResource(httpClient, "https://from.com".toHttpUrl()) dav.assertMultiStatus(okhttp3.Response.Builder() .request(Request.Builder().url(dav.location).build()) @@ -986,8 +1093,8 @@ class DavResourceTest { .build()) } - @Test(expected = DavException::class) - fun testAssertMultiStatus_NoBody_XML() { + @Test + fun testAssertMultiStatus_EmptyBody_XML() { val dav = DavResource(httpClient, "https://from.com".toHttpUrl()) dav.assertMultiStatus(okhttp3.Response.Builder() .request(Request.Builder().url(dav.location).build()) @@ -1040,8 +1147,7 @@ class DavResourceTest { .request(Request.Builder().url(dav.location).build()) .protocol(Protocol.HTTP_1_1) .code(207).message("Multi-Status") - .addHeader("Content-Type", "application/xml") - .body("".toResponseBody()) + .body("".toResponseBody("application/xml".toMediaType())) .build()) } @@ -1052,8 +1158,7 @@ class DavResourceTest { .request(Request.Builder().url(dav.location).build()) .protocol(Protocol.HTTP_1_1) .code(207).message("Multi-Status") - .addHeader("Content-Type", "text/xml") - .body("".toResponseBody()) + .body("".toResponseBody("text/xml".toMediaType())) .build()) } diff --git a/src/test/kotlin/at/bitfire/dav4jvm/exception/DavExceptionTest.kt b/src/test/kotlin/at/bitfire/dav4jvm/exception/DavExceptionTest.kt index a0385cf4..d7c2f9c1 100644 --- a/src/test/kotlin/at/bitfire/dav4jvm/exception/DavExceptionTest.kt +++ b/src/test/kotlin/at/bitfire/dav4jvm/exception/DavExceptionTest.kt @@ -14,19 +14,16 @@ import at.bitfire.dav4jvm.DavResource import at.bitfire.dav4jvm.Property import at.bitfire.dav4jvm.property.webdav.NS_WEBDAV import at.bitfire.dav4jvm.property.webdav.ResourceType +import mockwebserver3.MockResponse +import mockwebserver3.MockWebServer import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Protocol import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue -import org.junit.Assert.fail +import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.io.ByteArrayInputStream @@ -46,7 +43,7 @@ class DavExceptionTest { fun startServer() = mockServer.start() @After - fun stopServer() = mockServer.shutdown() + fun stopServer() = mockServer.close() /** @@ -92,10 +89,13 @@ class DavExceptionTest { builder.append("\u03C0") // Pi val body = builder.toString() - mockServer.enqueue(MockResponse() - .setResponseCode(404) + mockServer.enqueue( + MockResponse.Builder() + .code(404) .setHeader("Content-Type", "text/html") - .setBody(body)) + .body(body) + .build() + ) try { dav.propfind(0, ResourceType.NAME) { _, _ -> } fail("Expected HttpException") @@ -114,10 +114,13 @@ class DavExceptionTest { val url = sampleUrl() val dav = DavResource(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(403) + mockServer.enqueue( + MockResponse.Builder() + .code(403) .setHeader("Content-Type", "application/octet-stream") - .setBody("12345")) + .body("12345") + .build() + ) try { dav.propfind(0, ResourceType.NAME) { _, _ -> } fail("Expected HttpException") @@ -133,10 +136,13 @@ class DavExceptionTest { val url = sampleUrl() val dav = DavResource(httpClient, url) - mockServer.enqueue(MockResponse() - .setResponseCode(500) + mockServer.enqueue( + MockResponse.Builder() + .code(500) .setHeader("Content-Type", "text/plain") - .setBody("12345")) + .body("12345") + .build() + ) try { dav.propfind(0, ResourceType.NAME) { _, _ -> } fail("Expected DavException") @@ -166,10 +172,13 @@ class DavExceptionTest { " /workspace/webdav/\n" + " \n" + "\n" - mockServer.enqueue(MockResponse() - .setResponseCode(423) + mockServer.enqueue( + MockResponse.Builder() + .code(423) .setHeader("Content-Type", "application/xml; charset=\"utf-8\"") - .setBody(body)) + .body(body) + .build() + ) try { dav.propfind(0, ResourceType.NAME) { _, _ -> } fail("Expected HttpException")