diff --git a/app/build.gradle b/app/build.gradle
index c4794016e..a731a9e3a 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -28,6 +28,7 @@ dependencies {
compile "com.google.guava:guava:${GUAVA_VERSION}"
testCompile 'junit:junit:4.12'
+ androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
androidTestCompile "com.android.support:support-annotations:${project.supportLibVersion}"
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
diff --git a/app/src/androidTest/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.java b/app/src/androidTest/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.java
new file mode 100644
index 000000000..046962499
--- /dev/null
+++ b/app/src/androidTest/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.java
@@ -0,0 +1,229 @@
+package fr.free.nrw.commons.mwapi;
+
+import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import fr.free.nrw.commons.BuildConfig;
+import okhttp3.HttpUrl;
+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.assertTrue;
+
+/*
+ * XML for individual tests was captured by hand from the API sandbox -
+ * https://en.wikipedia.org/wiki/Special:ApiSandbox
+ */
+@RunWith(AndroidJUnit4.class)
+public class ApacheHttpClientMediaWikiApiTest {
+
+ private ApacheHttpClientMediaWikiApi testObject;
+ private MockWebServer server;
+
+ @Before
+ public void setUp() throws Exception {
+ server = new MockWebServer();
+ testObject = new ApacheHttpClientMediaWikiApi("http://localhost:" + server.getPort() + "/");
+ }
+
+ @After
+ public void teardown() throws IOException {
+ server.shutdown();
+ }
+
+ @Test
+ public void authCookiesAreHandled() {
+ assertEquals("", testObject.getAuthCookie());
+
+ testObject.setAuthCookie("cookie=chocolate-chip");
+
+ assertEquals("cookie=chocolate-chip", testObject.getAuthCookie());
+ }
+
+ @Test
+ public void simpleLoginWithWrongPassword() throws Exception {
+ server.enqueue(new MockResponse().setBody(""));
+ server.enqueue(new MockResponse().setBody(""));
+
+ String result = testObject.login("foo", "bar");
+
+ RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "POST");
+ Map body = parseBody(loginTokenRequest.getBody().readUtf8());
+ assertEquals("xml", body.get("format"));
+ assertEquals("query", body.get("action"));
+ assertEquals("login", body.get("type"));
+ assertEquals("tokens", body.get("meta"));
+
+ RecordedRequest loginRequest = assertBasicRequestParameters(server, "POST");
+ body = parseBody(loginRequest.getBody().readUtf8());
+ assertEquals("1", body.get("rememberMe"));
+ assertEquals("foo", body.get("username"));
+ assertEquals("bar", body.get("password"));
+ assertEquals("baz", body.get("logintoken"));
+ assertEquals("https://commons.wikimedia.org", body.get("loginreturnurl"));
+ assertEquals("xml", body.get("format"));
+
+ assertEquals("wrongpassword", result);
+ }
+
+ @Test
+ public void simpleLogin() throws Exception {
+ server.enqueue(new MockResponse().setBody(""));
+ server.enqueue(new MockResponse().setBody(""));
+
+ String result = testObject.login("foo", "bar");
+
+ RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "POST");
+ Map body = parseBody(loginTokenRequest.getBody().readUtf8());
+ assertEquals("xml", body.get("format"));
+ assertEquals("query", body.get("action"));
+ assertEquals("login", body.get("type"));
+ assertEquals("tokens", body.get("meta"));
+
+ RecordedRequest loginRequest = assertBasicRequestParameters(server, "POST");
+ body = parseBody(loginRequest.getBody().readUtf8());
+ assertEquals("1", body.get("rememberMe"));
+ assertEquals("foo", body.get("username"));
+ assertEquals("bar", body.get("password"));
+ assertEquals("baz", body.get("logintoken"));
+ assertEquals("https://commons.wikimedia.org", body.get("loginreturnurl"));
+ assertEquals("xml", body.get("format"));
+
+ assertEquals("PASS", result);
+ }
+
+ @Test
+ public void twoFactorLogin() throws Exception {
+ server.enqueue(new MockResponse().setBody(""));
+ server.enqueue(new MockResponse().setBody(""));
+
+ String result = testObject.login("foo", "bar", "2fa");
+
+ RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "POST");
+ Map body = parseBody(loginTokenRequest.getBody().readUtf8());
+ assertEquals("xml", body.get("format"));
+ assertEquals("query", body.get("action"));
+ assertEquals("login", body.get("type"));
+ assertEquals("tokens", body.get("meta"));
+
+ RecordedRequest loginRequest = assertBasicRequestParameters(server, "POST");
+ body = parseBody(loginRequest.getBody().readUtf8());
+ assertEquals("1", body.get("rememberMe"));
+ assertEquals("foo", body.get("username"));
+ assertEquals("bar", body.get("password"));
+ assertEquals("baz", body.get("logintoken"));
+ assertEquals("1", body.get("logincontinue"));
+ assertEquals("2fa", body.get("OATHToken"));
+ assertEquals("xml", body.get("format"));
+
+ assertEquals("PASS", result);
+ }
+
+ @Test
+ public void validateLoginForLoggedInUser() throws Exception {
+ server.enqueue(new MockResponse().setBody(""));
+
+ boolean result = testObject.validateLogin();
+
+ RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "GET");
+ Map body = parseQueryParams(loginTokenRequest);
+ assertEquals("xml", body.get("format"));
+ assertEquals("query", body.get("action"));
+ assertEquals("userinfo", body.get("meta"));
+
+ assertTrue(result);
+ }
+
+ @Test
+ public void validateLoginForLoggedOutUser() throws Exception {
+ server.enqueue(new MockResponse().setBody(""));
+
+ boolean result = testObject.validateLogin();
+
+ RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "GET");
+ Map params = parseQueryParams(loginTokenRequest);
+ assertEquals("xml", params.get("format"));
+ assertEquals("query", params.get("action"));
+ assertEquals("userinfo", params.get("meta"));
+
+ assertFalse(result);
+ }
+
+ @Test
+ public void editToken() throws Exception{
+ server.enqueue(new MockResponse().setBody(""));
+
+ String result = testObject.getEditToken();
+
+ RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "GET");
+ Map params = parseQueryParams(loginTokenRequest);
+ assertEquals("xml", params.get("format"));
+ assertEquals("tokens", params.get("action"));
+ assertEquals("edit", params.get("type"));
+
+ assertEquals("baz", result);
+ }
+
+ @Test
+ public void fileExistsWithName_FileNotFound() throws Exception {
+ server.enqueue(new MockResponse().setBody(" "));
+
+ boolean result = testObject.fileExistsWithName("foo");
+
+ RecordedRequest request = assertBasicRequestParameters(server, "GET");
+ Map params = parseQueryParams(request);
+ assertEquals("xml", params.get("format"));
+ assertEquals("query", params.get("action"));
+ assertEquals("imageinfo", params.get("prop"));
+ assertEquals("File:foo", params.get("titles"));
+
+ assertFalse(result);
+ }
+
+
+ private RecordedRequest assertBasicRequestParameters(MockWebServer server, String method) throws InterruptedException {
+ RecordedRequest request = server.takeRequest();
+ assertEquals("/", request.getRequestUrl().encodedPath());
+ assertEquals(method, request.getMethod());
+ assertEquals("Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE, request.getHeader("User-Agent"));
+ if ("POST".equals(method)) {
+ assertEquals("application/x-www-form-urlencoded", request.getHeader("Content-Type"));
+ }
+ return request;
+ }
+
+ private Map parseQueryParams(RecordedRequest request) {
+ Map result = new HashMap<>();
+ HttpUrl url = request.getRequestUrl();
+ Set params = url.queryParameterNames();
+ for (String name : params) {
+ result.put(name, url.queryParameter(name));
+ }
+ return result;
+ }
+
+ private Map parseBody(String body) throws UnsupportedEncodingException {
+ String[] props = body.split("&");
+ Map result = new HashMap<>();
+ for (String prop : props) {
+ String[] pair = prop.split("=");
+ result.put(pair[0], URLDecoder.decode(pair[1], "utf-8"));
+ }
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
index 080aec51a..69bfd521d 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApi.java
@@ -63,7 +63,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
.param("rememberMe", "1")
.param("username", username)
.param("password", password)
- .param("logintoken", this.getLoginToken())
+ .param("logintoken", getLoginToken())
.param("loginreturnurl", "https://commons.wikimedia.org")
.post());
}