mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
Moved the CSRF token client over into main commons code (#5471)
This commit is contained in:
parent
3d0e65c92c
commit
8b8eb84fae
18 changed files with 252 additions and 37 deletions
111
app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.java
Normal file
111
app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.java
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.AbstractExecutorService;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.Dispatcher;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import org.wikipedia.json.GsonUtil;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public abstract class MockWebServerTest {
|
||||
private OkHttpClient okHttpClient;
|
||||
private final TestWebServer server = new TestWebServer();
|
||||
|
||||
@Before public void setUp() throws Throwable {
|
||||
AppAdapter.set(new TestAppAdapter());
|
||||
OkHttpClient.Builder builder = AppAdapter.get().getOkHttpClient(new WikiSite(Service.WIKIPEDIA_URL)).newBuilder();
|
||||
okHttpClient = builder.dispatcher(new Dispatcher(new ImmediateExecutorService())).build();
|
||||
server.setUp();
|
||||
}
|
||||
|
||||
@After public void tearDown() throws Throwable {
|
||||
server.tearDown();
|
||||
}
|
||||
|
||||
@NonNull protected TestWebServer server() {
|
||||
return server;
|
||||
}
|
||||
|
||||
protected void enqueueFromFile(@NonNull String filename) throws Throwable {
|
||||
String json = TestFileUtil.readRawFile(filename);
|
||||
server.enqueue(json);
|
||||
}
|
||||
|
||||
protected void enqueue404() {
|
||||
final int code = 404;
|
||||
server.enqueue(new MockResponse().setResponseCode(code).setBody("Not Found"));
|
||||
}
|
||||
|
||||
protected void enqueueMalformed() {
|
||||
server.enqueue("(╯°□°)╯︵ ┻━┻");
|
||||
}
|
||||
|
||||
protected void enqueueEmptyJson() {
|
||||
server.enqueue(new MockResponse().setBody("{}"));
|
||||
}
|
||||
|
||||
@NonNull protected OkHttpClient okHttpClient() {
|
||||
return okHttpClient;
|
||||
}
|
||||
|
||||
@NonNull protected <T> T service(Class<T> clazz) {
|
||||
return service(clazz, server().getUrl());
|
||||
}
|
||||
|
||||
@NonNull protected <T> T service(Class<T> clazz, @NonNull String url) {
|
||||
return new Retrofit.Builder()
|
||||
.baseUrl(url)
|
||||
.callbackExecutor(new ImmediateExecutor())
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonUtil.getDefaultGson()))
|
||||
.build()
|
||||
.create(clazz);
|
||||
}
|
||||
|
||||
public final class ImmediateExecutorService extends AbstractExecutorService {
|
||||
@Override public void shutdown() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@NonNull @Override public List<Runnable> shutdownNow() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override public boolean isShutdown() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override public boolean isTerminated() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override public boolean awaitTermination(long l, @NonNull TimeUnit timeUnit)
|
||||
throws InterruptedException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override public void execute(@NonNull Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
public class ImmediateExecutor implements Executor {
|
||||
@Override
|
||||
public void execute(@NonNull Runnable runnable) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/src/test/kotlin/fr/free/nrw/commons/TestFileUtil.java
Normal file
37
app/src/test/kotlin/fr/free/nrw/commons/TestFileUtil.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import androidx.annotation.NonNull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
public final class TestFileUtil {
|
||||
private static final String RAW_DIR = "src/test/res/raw/";
|
||||
|
||||
public static File getRawFile(@NonNull String rawFileName) {
|
||||
return new File(RAW_DIR + rawFileName);
|
||||
}
|
||||
|
||||
public static String readRawFile(String basename) throws IOException {
|
||||
return readFile(getRawFile(basename));
|
||||
}
|
||||
|
||||
@TargetApi(19)
|
||||
private static String readFile(File file) throws IOException {
|
||||
return FileUtils.readFileToString(file, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@TargetApi(19)
|
||||
public static String readStream(InputStream stream) throws IOException {
|
||||
StringWriter writer = new StringWriter();
|
||||
IOUtils.copy(stream, writer, StandardCharsets.UTF_8);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private TestFileUtil() { }
|
||||
}
|
||||
56
app/src/test/kotlin/fr/free/nrw/commons/TestWebServer.java
Normal file
56
app/src/test/kotlin/fr/free/nrw/commons/TestWebServer.java
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
|
||||
public class TestWebServer {
|
||||
public static final int TIMEOUT_DURATION = 5;
|
||||
public static final TimeUnit TIMEOUT_UNIT = TimeUnit.SECONDS;
|
||||
|
||||
private final MockWebServer server;
|
||||
|
||||
public TestWebServer() {
|
||||
server = new MockWebServer();
|
||||
}
|
||||
|
||||
public void setUp() throws IOException {
|
||||
server.start();
|
||||
}
|
||||
|
||||
public void tearDown() throws IOException {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return getUrl("");
|
||||
}
|
||||
|
||||
public String getUrl(String path) {
|
||||
return server.url(path).url().toString();
|
||||
}
|
||||
|
||||
public int getRequestCount() {
|
||||
return server.getRequestCount();
|
||||
}
|
||||
|
||||
public void enqueue(@NonNull String body) {
|
||||
enqueue(new MockResponse().setBody(body));
|
||||
}
|
||||
|
||||
public void enqueue(MockResponse response) {
|
||||
server.enqueue(response);
|
||||
}
|
||||
|
||||
@NonNull public RecordedRequest takeRequest() throws InterruptedException {
|
||||
RecordedRequest req = server.takeRequest(TIMEOUT_DURATION,
|
||||
TIMEOUT_UNIT);
|
||||
if (req == null) {
|
||||
throw new InterruptedException("Timeout elapsed.");
|
||||
}
|
||||
return req;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,7 @@ import org.mockito.ArgumentMatchers
|
|||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import org.wikipedia.dataclient.Service
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
|
||||
import org.wikipedia.edit.Edit
|
||||
|
||||
class PageEditClientTest {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,9 @@ import org.mockito.MockedStatic
|
|||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.powermock.api.mockito.PowerMockito
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import org.wikipedia.dataclient.Service
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@PrepareForTest(CommonsApplication::class)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package fr.free.nrw.commons.auth.csrf;
|
||||
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import com.google.gson.stream.MalformedJsonException;
|
||||
import fr.free.nrw.commons.MockWebServerTest;
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient.Callback;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.Mockito;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import org.wikipedia.dataclient.mwapi.MwException;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import org.wikipedia.dataclient.okhttp.HttpStatusException;
|
||||
import retrofit2.Call;
|
||||
|
||||
public class CsrfTokenClientTest extends MockWebServerTest {
|
||||
private static final WikiSite TEST_WIKI = new WikiSite("test.wikipedia.org");
|
||||
@NonNull private final CsrfTokenClient subject = new CsrfTokenClient(TEST_WIKI, TEST_WIKI);
|
||||
|
||||
@Test public void testRequestSuccess() throws Throwable {
|
||||
String expected = "b6f7bd58c013ab30735cb19ecc0aa08258122cba+\\";
|
||||
enqueueFromFile("csrf_token.json");
|
||||
|
||||
Callback cb = Mockito.mock(Callback.class);
|
||||
request(cb);
|
||||
|
||||
server().takeRequest();
|
||||
assertCallbackSuccess(cb, expected);
|
||||
}
|
||||
|
||||
@Test public void testRequestResponseApiError() throws Throwable {
|
||||
enqueueFromFile("api_error.json");
|
||||
|
||||
Callback cb = Mockito.mock(Callback.class);
|
||||
request(cb);
|
||||
|
||||
server().takeRequest();
|
||||
assertCallbackFailure(cb, MwException.class);
|
||||
}
|
||||
|
||||
@Test public void testRequestResponseFailure() throws Throwable {
|
||||
enqueue404();
|
||||
|
||||
Callback cb = Mockito.mock(Callback.class);
|
||||
request(cb);
|
||||
|
||||
server().takeRequest();
|
||||
assertCallbackFailure(cb, HttpStatusException.class);
|
||||
}
|
||||
|
||||
@Test public void testRequestResponseMalformed() throws Throwable {
|
||||
enqueueMalformed();
|
||||
|
||||
Callback cb = Mockito.mock(Callback.class);
|
||||
request(cb);
|
||||
|
||||
server().takeRequest();
|
||||
assertCallbackFailure(cb, MalformedJsonException.class);
|
||||
}
|
||||
|
||||
private void assertCallbackSuccess(@NonNull Callback cb,
|
||||
@NonNull String expected) {
|
||||
verify(cb).success(ArgumentMatchers.eq(expected));
|
||||
//noinspection unchecked
|
||||
verify(cb, never()).failure(ArgumentMatchers.any(Throwable.class));
|
||||
}
|
||||
|
||||
private void assertCallbackFailure(@NonNull Callback cb,
|
||||
@NonNull Class<? extends Throwable> throwable) {
|
||||
//noinspection unchecked
|
||||
verify(cb, never()).success(ArgumentMatchers.any(String.class));
|
||||
verify(cb).failure(ArgumentMatchers.isA(throwable));
|
||||
}
|
||||
|
||||
private Call<MwQueryResponse> request(@NonNull Callback cb) {
|
||||
return subject.request(service(Service.class), cb);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ import org.mockito.Mockito.verify
|
|||
import org.mockito.MockitoAnnotations
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.annotation.LooperMode
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResult
|
||||
import org.wikipedia.json.GsonUtil
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import org.mockito.InjectMocks
|
|||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
|
||||
|
||||
class WikiBaseClientUnitTest {
|
||||
|
||||
|
|
|
|||
10
app/src/test/res/raw/api_error.json
Normal file
10
app/src/test/res/raw/api_error.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": "unknown_action",
|
||||
"text": "Unrecognized value for parameter \"action\": oscillate."
|
||||
}
|
||||
],
|
||||
"docref": "See https://en.wikipedia.org/w/api.php for API usage.",
|
||||
"servedby": "mw1286"
|
||||
}
|
||||
8
app/src/test/res/raw/csrf_token.json
Normal file
8
app/src/test/res/raw/csrf_token.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"batchcomplete": true,
|
||||
"query": {
|
||||
"tokens": {
|
||||
"csrftoken": "b6f7bd58c013ab30735cb19ecc0aa08258122cba+\\"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue