Commit 1169e150 authored by Ricki Hirner's avatar Ricki Hirner 🐑

Update okhttp, tests in Kotlin, refactoring

parent 568f0285
Pipeline #18748357 passed with stages
in 3 minutes and 50 seconds
buildscript {
ext.kotlin_version = '1.2.21'
ext.kotlin_version = '1.2.30'
ext.dokka_version = '0.9.15'
repositories {
......@@ -52,12 +52,12 @@ android {
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile 'com.squareup.okhttp3:okhttp:3.9.1'
compile 'com.squareup.okhttp3:okhttp:3.10.0'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.10.0'
androidTestCompile 'com.android.support.test:runner:1.0.1'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
testCompile 'junit:junit:4.12'
testCompile 'com.squareup.okhttp3:mockwebserver:3.9.1'
testCompile 'com.squareup.okhttp3:mockwebserver:3.10.0'
}
......@@ -6,290 +6,271 @@
* http://www.gnu.org/licenses/gpl.html
*/
package at.bitfire.dav4android;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.HttpURLConnection;
import at.bitfire.dav4android.exception.DavException;
import at.bitfire.dav4android.exception.HttpException;
import at.bitfire.dav4android.exception.InvalidDavResponseException;
import at.bitfire.dav4android.exception.PreconditionFailedException;
import at.bitfire.dav4android.property.DisplayName;
import at.bitfire.dav4android.property.GetCTag;
import at.bitfire.dav4android.property.GetContentType;
import at.bitfire.dav4android.property.GetETag;
import at.bitfire.dav4android.property.ResourceType;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class DavResourceTest {
private static final String
sampleText = "SAMPLE RESPONSE";
private OkHttpClient httpClient = new OkHttpClient.Builder()
package at.bitfire.dav4android
import at.bitfire.dav4android.exception.HttpException
import at.bitfire.dav4android.exception.InvalidDavResponseException
import at.bitfire.dav4android.exception.PreconditionFailedException
import at.bitfire.dav4android.property.*
import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody
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 java.net.HttpURLConnection
class DavResourceTest {
private val sampleText = "SAMPLE RESPONSE"
private val httpClient = OkHttpClient.Builder()
.followRedirects(false)
.build();
private MockWebServer mockServer = new MockWebServer();
.build()
private val mockServer = MockWebServer()
@Before
public void startServer() throws IOException {
mockServer.start();
fun startServer() {
mockServer.start()
}
@After
public void stopServer() throws IOException {
mockServer.shutdown();
fun stopServer() {
mockServer.shutdown()
}
private HttpUrl sampleUrl() {
return mockServer.url("/dav/");
}
private fun sampleUrl() = mockServer.url("/dav/")
@Test
public void testOptions() throws InterruptedException, IOException, HttpException, DavException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
fun testOptions() {
val url = sampleUrl()
val dav = DavResource(httpClient, url)
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setHeader("DAV", "1,2,3, hyperactive-access"));
dav.options();
assertTrue(dav.getCapabilities().contains("1"));
assertTrue(dav.getCapabilities().contains("2"));
assertTrue(dav.getCapabilities().contains("3"));
assertTrue(dav.getCapabilities().contains("hyperactive-access"));
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK));
dav.options();
assertTrue(dav.getCapabilities().isEmpty());
.setHeader("DAV", "1,2,3, hyperactive-access"))
dav.options()
assertTrue(dav.capabilities.contains("1"))
assertTrue(dav.capabilities.contains("2"))
assertTrue(dav.capabilities.contains("3"))
assertTrue(dav.capabilities.contains("hyperactive-access"))
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK))
dav.options()
assertTrue(dav.capabilities.isEmpty())
}
@Test
public void testGet() throws InterruptedException, IOException, HttpException, DavException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
fun testGet() {
val url = sampleUrl()
val dav = DavResource(httpClient, url)
/* POSITIVE TEST CASES */
// 200 OK
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setHeader("ETag", "W/\"My Weak ETag\"")
.setHeader("Content-Type", "application/x-test-result")
.setBody(sampleText));
ResponseBody body = dav.get("*/*");
assertEquals(sampleText, body.string());
assertEquals("My Weak ETag", dav.getProperties().get(GetETag.class).getETag());
assertEquals("application/x-test-result", dav.getProperties().get(GetContentType.class).getType());
.setBody(sampleText))
var body = dav.get("*/*")
assertEquals(sampleText, body.string())
assertEquals("My Weak ETag", dav.properties[GetETag::class.java]?.eTag)
assertEquals("application/x-test-result", dav.properties[GetContentType::class.java]?.type)
RecordedRequest rq = mockServer.takeRequest();
assertEquals("GET", rq.getMethod());
assertEquals(url.encodedPath(), rq.getPath());
assertEquals("*/*", rq.getHeader("Accept"));
var rq = mockServer.takeRequest()
assertEquals("GET", rq.method)
assertEquals(url.encodedPath(), rq.path)
assertEquals("*/*", rq.getHeader("Accept"))
// 302 Moved Temporarily + 200 OK
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
.setHeader("Location", "/target")
.setBody("This resource was moved."));
mockServer.enqueue(new MockResponse()
.setBody("This resource was moved."))
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setHeader("ETag", "\"StrongETag\"")
.setBody(sampleText));
body = dav.get("*/*");
assertEquals(sampleText, body.string());
assertEquals("StrongETag", dav.getProperties().get(GetETag.class).getETag());
.setBody(sampleText))
body = dav.get("*/*")
assertEquals(sampleText, body.string())
assertEquals("StrongETag", dav.properties[GetETag::class.java]?.eTag)
mockServer.takeRequest();
rq = mockServer.takeRequest();
assertEquals("GET", rq.getMethod());
assertEquals("/target", rq.getPath());
mockServer.takeRequest()
rq = mockServer.takeRequest()
assertEquals("GET", rq.method)
assertEquals("/target", rq.path)
// 200 OK without ETag in response
dav.getProperties().set(GetETag.NAME, new GetETag("test"));
mockServer.enqueue(new MockResponse()
dav.properties[GetETag.NAME] = GetETag("test")
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(sampleText));
dav.get("*/*");
assertNull(dav.getProperties().get(GetETag.class));
.setBody(sampleText))
dav.get("*/*")
assertNull(dav.properties[GetETag::class.java])
}
@Test
public void testPut() throws InterruptedException, IOException, HttpException, DavException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
fun testPut() {
val url = sampleUrl()
val dav = DavResource(httpClient, url)
/* POSITIVE TEST CASES */
// no preconditions, 201 Created
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_CREATED)
.setHeader("ETag", "W/\"Weak PUT ETag\""));
assertFalse(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, false));
assertEquals("Weak PUT ETag", dav.getProperties().get(GetETag.class).getETag());
.setHeader("ETag", "W/\"Weak PUT ETag\""))
assertFalse(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, false))
assertEquals("Weak PUT ETag", dav.properties[GetETag::class.java]?.eTag)
RecordedRequest rq = mockServer.takeRequest();
assertEquals("PUT", rq.getMethod());
assertEquals(url.encodedPath(), rq.getPath());
assertNull(rq.getHeader("If-Match"));
assertNull(rq.getHeader("If-None-Match"));
var rq = mockServer.takeRequest()
assertEquals("PUT", rq.method)
assertEquals(url.encodedPath(), rq.path)
assertNull(rq.getHeader("If-Match"))
assertNull(rq.getHeader("If-None-Match"))
// precondition: If-None-Match, 301 Moved Permanently + 204 No Content, no ETag in response
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_MOVED_PERM)
.setHeader("Location", "/target"));
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_NO_CONTENT));
assertTrue(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, true));
assertEquals(url.resolve("/target"), dav.getLocation());
assertNull(dav.getProperties().get(GetETag.class));
mockServer.takeRequest();
rq = mockServer.takeRequest();
assertEquals("PUT", rq.getMethod());
assertEquals("*", rq.getHeader("If-None-Match"));
.setHeader("Location", "/target"))
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_NO_CONTENT))
assertTrue(dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), null, true))
assertEquals(url.resolve("/target"), dav.location)
assertNull(dav.properties[GetETag::class.java])
mockServer.takeRequest()
rq = mockServer.takeRequest()
assertEquals("PUT", rq.method)
assertEquals("*", rq.getHeader("If-None-Match"))
// precondition: If-Match, 412 Precondition Failed
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_PRECON_FAILED));
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_PRECON_FAILED))
try {
dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), "ExistingETag", false);
fail();
} catch(PreconditionFailedException e) {
dav.put(RequestBody.create(MediaType.parse("text/plain"), sampleText), "ExistingETag", false)
fail()
} catch(e: PreconditionFailedException) {
}
rq = mockServer.takeRequest();
assertEquals("\"ExistingETag\"", rq.getHeader("If-Match"));
assertNull(rq.getHeader("If-None-Match"));
rq = mockServer.takeRequest()
assertEquals("\"ExistingETag\"", rq.getHeader("If-Match"))
assertNull(rq.getHeader("If-None-Match"))
}
@Test
public void testDelete() throws InterruptedException, IOException, HttpException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
fun testDelete() {
val url = sampleUrl()
val dav = DavResource(httpClient, url)
/* POSITIVE TEST CASES */
// no preconditions, 204 No Content
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_NO_CONTENT));
dav.delete(null);
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_NO_CONTENT))
dav.delete(null)
RecordedRequest rq = mockServer.takeRequest();
assertEquals("DELETE", rq.getMethod());
assertEquals(url.encodedPath(), rq.getPath());
assertNull(rq.getHeader("If-Match"));
var rq = mockServer.takeRequest()
assertEquals("DELETE", rq.method)
assertEquals(url.encodedPath(), rq.path)
assertNull(rq.getHeader("If-Match"))
// precondition: If-Match, 200 OK
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody("Resource has been deleted."));
dav.delete("DeleteOnlyThisETag");
.setBody("Resource has been deleted."))
dav.delete("DeleteOnlyThisETag")
rq = mockServer.takeRequest();
assertEquals("\"DeleteOnlyThisETag\"", rq.getHeader("If-Match"));
rq = mockServer.takeRequest()
assertEquals("\"DeleteOnlyThisETag\"", rq.getHeader("If-Match"))
// 302 Moved Temporarily
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_MOVED_TEMP)
.setHeader("Location", "/new-location")
);
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK));
dav.delete(null);
)
mockServer.enqueue(MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK))
dav.delete(null)
/* NEGATIVE TEST CASES */
// 207 multi-status (e.g. single resource couldn't be deleted when DELETEing a collection)
mockServer.enqueue(new MockResponse()
.setResponseCode(207));
mockServer.enqueue(MockResponse()
.setResponseCode(207))
try {
dav.delete(null);
fail();
} catch(HttpException e) {
dav.delete(null)
fail()
} catch(e: HttpException) {
// treat 207 as an error
}
}
@Test
public void testPropfindAndMultiStatus() throws IOException, HttpException, DavException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
fun testPropfindAndMultiStatus() {
val url = sampleUrl()
val dav = DavResource(httpClient, url)
/*** NEGATIVE TESTS ***/
// test for non-multi-status responses:
// * 500 Internal Server Error
mockServer.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR));
mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_INTERNAL_ERROR))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected HttpException");
} catch(HttpException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected HttpException")
} catch(e: HttpException) {
}
// * 200 OK (instead of 207 Multi-Status)
mockServer.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_OK));
mockServer.enqueue(MockResponse().setResponseCode(HttpURLConnection.HTTP_OK))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected InvalidDavResponseException");
} catch(InvalidDavResponseException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected InvalidDavResponseException")
} catch(e: InvalidDavResponseException) {
}
// test for invalid multi-status responses:
// * non-XML response
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "text/html")
.setBody("<html></html>"));
.setBody("<html></html>"))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected InvalidDavResponseException");
} catch(InvalidDavResponseException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected InvalidDavResponseException")
} catch(e: InvalidDavResponseException) {
}
// * malformed XML response
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<malformed-xml>"));
.setBody("<malformed-xml>"))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected InvalidDavResponseException");
} catch(InvalidDavResponseException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected InvalidDavResponseException")
} catch(e: InvalidDavResponseException) {
}
// * response without <multistatus> root element
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<test></test>"));
.setBody("<test></test>"))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected InvalidDavResponseException");
} catch(InvalidDavResponseException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected InvalidDavResponseException")
} catch(e: InvalidDavResponseException) {
}
// * multi-status response with invalid <status> in <response>
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<multistatus xmlns='DAV:'>" +
......@@ -297,15 +278,15 @@ public class DavResourceTest {
" <href>/dav</href>" +
" <status>Invalid Status Line</status>" +
" </response>" +
"</multistatus>"));
"</multistatus>"))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected HttpException");
} catch(HttpException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected HttpException")
} catch(e: HttpException) {
}
// * multi-status response with <response>/<status> element indicating failure
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<multistatus xmlns='DAV:'>" +
......@@ -313,15 +294,15 @@ public class DavResourceTest {
" <href>/dav</href>" +
" <status>HTTP/1.1 403 Forbidden</status>" +
" </response>" +
"</multistatus>"));
"</multistatus>"))
try {
dav.propfind(0, ResourceType.NAME);
fail("Expected HttpException");
} catch(HttpException e) {
dav.propfind(0, ResourceType.NAME)
fail("Expected HttpException")
} catch(e: HttpException) {
}
// * multi-status response with invalid <status> in <propstat>
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<multistatus xmlns='DAV:'>" +
......@@ -334,24 +315,24 @@ public class DavResourceTest {
" <status>Invalid Status Line</status>" +
" </propstat>" +
" </response>" +
"</multistatus>"));
dav.propfind(0, ResourceType.NAME);
assertNull(dav.getProperties().get(ResourceType.class));
"</multistatus>"))
dav.propfind(0, ResourceType.NAME)
assertNull(dav.properties[ResourceType::class.java])
/*** POSITIVE TESTS ***/
// multi-status response without <response> elements
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<multistatus xmlns='DAV:'></multistatus>"));
dav.propfind(0, ResourceType.NAME);
assertEquals(0, dav.getProperties().size());
assertEquals(0, dav.getMembers().size());
.setBody("<multistatus xmlns='DAV:'></multistatus>"))
dav.propfind(0, ResourceType.NAME)
assertEquals(0, dav.properties.size())
assertEquals(0, dav.members.size)
// multi-status response with <response>/<status> element indicating success
mockServer.enqueue(new MockResponse()
mockServer.enqueue(MockResponse()