Convert the LoginClient to kotlin (#5479)

* Convert the result classes to kotlin

* Convert response and callback to kotlin

* Cleanup code-quality warnings before converting

* Converted the LoginClient to kotlin

* Updated the UserExtendedInfoClientTest to be kotlin, and live in the correct spot
This commit is contained in:
Paul Hawke 2024-01-26 21:39:00 -06:00 committed by GitHub
parent 0541aacdff
commit 02ce017c98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 370 additions and 450 deletions

View file

@ -31,11 +31,10 @@ import fr.free.nrw.commons.auth.login.LoginResult;
import fr.free.nrw.commons.databinding.ActivityLoginBinding;
import fr.free.nrw.commons.utils.ActivityUtils;
import java.util.Locale;
import org.wikipedia.AppAdapter;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import fr.free.nrw.commons.auth.login.LoginClient.LoginCallback;
import fr.free.nrw.commons.auth.login.LoginCallback;
import javax.inject.Inject;
import javax.inject.Named;

View file

@ -8,8 +8,8 @@ import org.wikipedia.dataclient.SharedPreferenceCookieManager
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import fr.free.nrw.commons.auth.login.LoginClient
import fr.free.nrw.commons.auth.login.LoginClient.LoginCallback
import fr.free.nrw.commons.auth.login.LoginClient.LoginFailedException
import fr.free.nrw.commons.auth.login.LoginCallback
import fr.free.nrw.commons.auth.login.LoginFailedException
import fr.free.nrw.commons.auth.login.LoginResult
import retrofit2.Call
import retrofit2.Response
@ -129,7 +129,7 @@ class CsrfTokenClient(
) = LoginClient()
.request(csrfWikiSite, username, password, object : LoginCallback {
override fun success(loginResult: LoginResult) {
if (loginResult.pass()) {
if (loginResult.pass) {
sessionManager.updateAccount(loginResult)
retryCallback()
} else {

View file

@ -0,0 +1,8 @@
package fr.free.nrw.commons.auth.login
interface LoginCallback {
fun success(loginResult: LoginResult)
fun twoFactorPrompt(caught: Throwable, token: String?)
fun passwordResetPrompt(token: String?)
fun error(caught: Throwable)
}

View file

@ -1,259 +0,0 @@
package fr.free.nrw.commons.auth.login;
import android.annotation.SuppressLint;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.lang3.StringUtils;
import org.wikipedia.dataclient.Service;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.dataclient.mwapi.ListUserResponse;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.dataclient.mwapi.MwServiceError;
import org.wikipedia.util.log.L;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Responsible for making login related requests to the server.
*/
public class LoginClient {
@Nullable private Call<MwQueryResponse> tokenCall;
@Nullable private Call<LoginResponse> loginCall;
/**
* userLanguage
* It holds the value of the user's device language code.
* For example, if user's device language is English it will hold En
* The value will be fetched when the user clicks Login Button in the LoginActivity
*/
@NonNull private String userLanguage;
public interface LoginCallback {
void success(@NonNull LoginResult result);
void twoFactorPrompt(@NonNull Throwable caught, @Nullable String token);
void passwordResetPrompt(@Nullable String token);
void error(@NonNull Throwable caught);
}
public void request(@NonNull final WikiSite wiki, @NonNull final String userName,
@NonNull final String password, @NonNull final LoginCallback cb) {
cancel();
tokenCall = ServiceFactory.get(wiki, LoginInterface.class).getLoginToken();
tokenCall.enqueue(new Callback<MwQueryResponse>() {
@Override public void onResponse(@NonNull Call<MwQueryResponse> call,
@NonNull Response<MwQueryResponse> response) {
login(wiki, userName, password, null, null, response.body().query().loginToken(),
userLanguage, cb);
}
@Override
public void onFailure(@NonNull Call<MwQueryResponse> call, @NonNull Throwable caught) {
if (call.isCanceled()) {
return;
}
cb.error(caught);
}
});
}
public void login(@NonNull final WikiSite wiki, @NonNull final String userName, @NonNull final String password,
@Nullable final String retypedPassword, @Nullable final String twoFactorCode,
@Nullable final String loginToken, @NonNull final String userLanguage, @NonNull final LoginCallback cb) {
this.userLanguage = userLanguage;
loginCall = TextUtils.isEmpty(twoFactorCode) && TextUtils.isEmpty(retypedPassword)
? ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL)
: ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, retypedPassword, twoFactorCode, loginToken,
userLanguage, true);
loginCall.enqueue(new Callback<LoginResponse>() {
@Override
public void onResponse(@NonNull Call<LoginResponse> call, @NonNull Response<LoginResponse> response) {
LoginResponse loginResponse = response.body();
LoginResult loginResult = loginResponse.toLoginResult(wiki, password);
if (loginResult != null) {
if (loginResult.pass() && !TextUtils.isEmpty(loginResult.getUserName())) {
// The server could do some transformations on user names, e.g. on some
// wikis is uppercases the first letter.
String actualUserName = loginResult.getUserName();
getExtendedInfo(wiki, actualUserName, loginResult, cb);
} else if ("UI".equals(loginResult.getStatus())) {
if (loginResult instanceof LoginOAuthResult) {
cb.twoFactorPrompt(new LoginFailedException(loginResult.getMessage()), loginToken);
} else if (loginResult instanceof LoginResetPasswordResult) {
cb.passwordResetPrompt(loginToken);
} else {
cb.error(new LoginFailedException(loginResult.getMessage()));
}
} else {
cb.error(new LoginFailedException(loginResult.getMessage()));
}
} else {
cb.error(new IOException("Login failed. Unexpected response."));
}
}
@Override
public void onFailure(@NonNull Call<LoginResponse> call, @NonNull Throwable t) {
if (call.isCanceled()) {
return;
}
cb.error(t);
}
});
}
public void loginBlocking(@NonNull final WikiSite wiki, @NonNull final String userName,
@NonNull final String password, @Nullable final String twoFactorCode) throws Throwable {
Response<MwQueryResponse> tokenResponse = ServiceFactory.get(wiki, LoginInterface.class).getLoginToken().execute();
if (tokenResponse.body() == null || TextUtils.isEmpty(tokenResponse.body().query().loginToken())) {
throw new IOException("Unexpected response when getting login token.");
}
String loginToken = tokenResponse.body().query().loginToken();
Call<LoginResponse> tempLoginCall = StringUtils.defaultIfEmpty(twoFactorCode, "").isEmpty()
? ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL)
: ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, null, twoFactorCode, loginToken,
userLanguage, true);
Response<LoginResponse> response = tempLoginCall.execute();
LoginResponse loginResponse = response.body();
if (loginResponse == null) {
throw new IOException("Unexpected response when logging in.");
}
LoginResult loginResult = loginResponse.toLoginResult(wiki, password);
if (loginResult == null) {
throw new IOException("Unexpected response when logging in.");
}
if ("UI".equals(loginResult.getStatus())) {
if (loginResult instanceof LoginOAuthResult) {
// TODO: Find a better way to boil up the warning about 2FA
throw new LoginFailedException(loginResult.getMessage());
} else {
throw new LoginFailedException(loginResult.getMessage());
}
} else if (!loginResult.pass() || TextUtils.isEmpty(loginResult.getUserName())) {
throw new LoginFailedException(loginResult.getMessage());
}
}
@SuppressLint("CheckResult")
private void getExtendedInfo(@NonNull final WikiSite wiki, @NonNull String userName,
@NonNull final LoginResult loginResult, @NonNull final LoginCallback cb) {
ServiceFactory.get(wiki, LoginInterface.class).getUserInfo(userName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(response -> {
ListUserResponse user = response.query().getUserResponse(userName);
int id = response.query().userInfo().id();
loginResult.setUserId(id);
loginResult.setGroups(user.getGroups());
cb.success(loginResult);
L.v("Found user ID " + id + " for " + wiki.subdomain());
}, caught -> {
L.e("Login succeeded but getting group information failed. " + caught);
cb.error(caught);
});
}
public void cancel() {
cancelTokenRequest();
cancelLogin();
}
private void cancelTokenRequest() {
if (tokenCall == null) {
return;
}
tokenCall.cancel();
tokenCall = null;
}
private void cancelLogin() {
if (loginCall == null) {
return;
}
loginCall.cancel();
loginCall = null;
}
public static final class LoginResponse {
@SuppressWarnings("unused") @SerializedName("error") @Nullable
private MwServiceError error;
@SuppressWarnings("unused") @SerializedName("clientlogin") @Nullable
private ClientLogin clientLogin;
@Nullable public MwServiceError getError() {
return error;
}
@Nullable LoginResult toLoginResult(@NonNull WikiSite site, @NonNull String password) {
return clientLogin != null ? clientLogin.toLoginResult(site, password) : null;
}
private static class ClientLogin {
@SuppressWarnings("unused,NullableProblems") @NonNull private String status;
@SuppressWarnings("unused") @Nullable private List<Request> requests;
@SuppressWarnings("unused") @Nullable private String message;
@SuppressWarnings("unused") @SerializedName("username") @Nullable private String userName;
LoginResult toLoginResult(@NonNull WikiSite site, @NonNull String password) {
String userMessage = message;
if ("UI".equals(status)) {
if (requests != null) {
for (Request req : requests) {
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest".equals(req.id())) {
return new LoginOAuthResult(site, status, userName, password, message);
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest".equals(req.id())) {
return new LoginResetPasswordResult(site, status, userName, password, message);
}
}
}
} else if (!"PASS".equals(status) && !"FAIL".equals(status)) {
//TODO: String resource -- Looks like needed for others in this class too
userMessage = "An unknown error occurred.";
}
return new LoginResult(site, status, userName, password, userMessage);
}
}
private static class Request {
@SuppressWarnings("unused") @Nullable private String id;
//@SuppressWarnings("unused") @Nullable private JsonObject metadata;
@SuppressWarnings("unused") @Nullable private String required;
@SuppressWarnings("unused") @Nullable private String provider;
@SuppressWarnings("unused") @Nullable private String account;
@SuppressWarnings("unused") @Nullable private Map<String, RequestField> fields;
@Nullable String id() {
return id;
}
}
private static class RequestField {
@SuppressWarnings("unused") @Nullable private String type;
@SuppressWarnings("unused") @Nullable private String label;
@SuppressWarnings("unused") @Nullable private String help;
}
}
public static class LoginFailedException extends Throwable {
public LoginFailedException(String message) {
super(message);
}
}
}

View file

@ -0,0 +1,172 @@
package fr.free.nrw.commons.auth.login
import android.text.TextUtils
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.wikipedia.dataclient.Service
import org.wikipedia.dataclient.ServiceFactory
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import timber.log.Timber
import java.io.IOException
/**
* Responsible for making login related requests to the server.
*/
class LoginClient {
private var tokenCall: Call<MwQueryResponse?>? = null
private var loginCall: Call<LoginResponse?>? = null
/**
* userLanguage
* It holds the value of the user's device language code.
* For example, if user's device language is English it will hold En
* The value will be fetched when the user clicks Login Button in the LoginActivity
*/
private var userLanguage = ""
fun request(wiki: WikiSite, userName: String, password: String, cb: LoginCallback) {
cancel()
tokenCall = ServiceFactory.get(wiki, LoginInterface::class.java).getLoginToken()
tokenCall!!.enqueue(object : Callback<MwQueryResponse?> {
override fun onResponse(call: Call<MwQueryResponse?>, response: Response<MwQueryResponse?>) {
login(wiki, userName, password, null, null,
response.body()!!.query()!!.loginToken(), userLanguage, cb)
}
override fun onFailure(call: Call<MwQueryResponse?>, caught: Throwable) {
if (call.isCanceled) {
return
}
cb.error(caught)
}
})
}
fun login(
wiki: WikiSite, userName: String, password: String, retypedPassword: String?,
twoFactorCode: String?, loginToken: String?, userLanguage: String, cb: LoginCallback
) {
this.userLanguage = userLanguage
loginCall = if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
ServiceFactory.get(wiki, LoginInterface::class.java)
.postLogIn(userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL)
} else {
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
userName, password, retypedPassword, twoFactorCode, loginToken, userLanguage, true
)
}
loginCall!!.enqueue(object : Callback<LoginResponse?> {
override fun onResponse(
call: Call<LoginResponse?>,
response: Response<LoginResponse?>
) {
val loginResult = response.body()?.toLoginResult(wiki, password)
if (loginResult != null) {
if (loginResult.pass && !loginResult.userName.isNullOrEmpty()) {
// The server could do some transformations on user names, e.g. on some
// wikis is uppercases the first letter.
getExtendedInfo(wiki, loginResult.userName, loginResult, cb)
} else if ("UI" == loginResult.status) {
when (loginResult) {
is OAuthResult -> cb.twoFactorPrompt(
LoginFailedException(loginResult.message),
loginToken
)
is ResetPasswordResult -> cb.passwordResetPrompt(loginToken)
is LoginResult.Result -> cb.error(
LoginFailedException(loginResult.message)
)
}
} else {
cb.error(LoginFailedException(loginResult.message))
}
} else {
cb.error(IOException("Login failed. Unexpected response."))
}
}
override fun onFailure(call: Call<LoginResponse?>, t: Throwable) {
if (call.isCanceled) {
return
}
cb.error(t)
}
})
}
@Throws(Throwable::class)
fun loginBlocking(wiki: WikiSite, userName: String, password: String, twoFactorCode: String?) {
val tokenResponse = ServiceFactory.get(wiki, LoginInterface::class.java).getLoginToken().execute()
if (tokenResponse.body()?.query()?.loginToken().isNullOrEmpty()) {
throw IOException("Unexpected response when getting login token.")
}
val loginToken = tokenResponse.body()?.query()?.loginToken()
val tempLoginCall = if (twoFactorCode.isNullOrEmpty()) {
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL
)
} else {
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
userName, password, null, twoFactorCode, loginToken, userLanguage, true
)
}
val response = tempLoginCall.execute()
val loginResponse = response.body() ?: throw IOException("Unexpected response when logging in.")
val loginResult = loginResponse.toLoginResult(wiki, password) ?: throw IOException("Unexpected response when logging in.")
if ("UI" == loginResult.status) {
if (loginResult is OAuthResult) {
// TODO: Find a better way to boil up the warning about 2FA
throw LoginFailedException(loginResult.message)
}
throw LoginFailedException(loginResult.message)
}
if (!loginResult.pass || TextUtils.isEmpty(loginResult.userName)) {
throw LoginFailedException(loginResult.message)
}
}
private fun getExtendedInfo(
wiki: WikiSite, userName: String, loginResult: LoginResult, cb: LoginCallback
) = ServiceFactory.get(wiki, LoginInterface::class.java).getUserInfo(userName)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe({ response: MwQueryResponse? ->
loginResult.userId = response?.query()?.userInfo()?.id() ?: 0
loginResult.groups = response?.query()?.getUserResponse(userName)?.groups ?: emptySet()
cb.success(loginResult)
Timber.v(
"Found user ID %s for %s",
response?.query()?.userInfo()?.id(),
wiki.subdomain()
)
}, { caught: Throwable ->
Timber.e(caught, "Login succeeded but getting group information failed. ")
cb.error(caught)
})
fun cancel() {
tokenCall?.let {
it.cancel()
tokenCall = null
}
loginCall?.let {
it.cancel()
loginCall = null
}
}
}

View file

@ -0,0 +1,3 @@
package fr.free.nrw.commons.auth.login
class LoginFailedException(message: String?) : Throwable(message)

View file

@ -25,7 +25,7 @@ interface LoginInterface {
@Field("logintoken") token: String?,
@Field("uselang") userLanguage: String?,
@Field("loginreturnurl") url: String?
): Call<LoginClient.LoginResponse?>
): Call<LoginResponse?>
@Headers("Cache-Control: no-cache")
@FormUrlEncoded
@ -38,7 +38,7 @@ interface LoginInterface {
@Field("logintoken") token: String?,
@Field("uselang") userLanguage: String?,
@Field("logincontinue") loginContinue: Boolean
): Call<LoginClient.LoginResponse?>
): Call<LoginResponse?>
@GET(Service.MW_API_PREFIX + "action=query&meta=userinfo&list=users&usprop=groups|cancreate")
fun getUserInfo(@Query("ususers") userName: String): Observable<MwQueryResponse?>

View file

@ -1,14 +0,0 @@
package fr.free.nrw.commons.auth.login;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.wikipedia.dataclient.WikiSite;
public class LoginOAuthResult extends LoginResult {
public LoginOAuthResult(@NonNull WikiSite site, @NonNull String status, @Nullable String userName,
@Nullable String password, @Nullable String message) {
super(site, status, userName, password, message);
}
}

View file

@ -1,13 +0,0 @@
package fr.free.nrw.commons.auth.login;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.wikipedia.dataclient.WikiSite;
public class LoginResetPasswordResult extends LoginResult {
public LoginResetPasswordResult(@NonNull WikiSite site, @NonNull String status, @Nullable String userName,
@Nullable String password, @Nullable String message) {
super(site, status, userName, password, message);
}
}

View file

@ -0,0 +1,64 @@
package fr.free.nrw.commons.auth.login
import com.google.gson.annotations.SerializedName
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
import fr.free.nrw.commons.auth.login.LoginResult.Result
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwServiceError
class LoginResponse {
@SerializedName("error")
val error: MwServiceError? = null
@SerializedName("clientlogin")
private val clientLogin: ClientLogin? = null
fun toLoginResult(site: WikiSite, password: String): LoginResult? {
return clientLogin?.toLoginResult(site, password)
}
}
internal class ClientLogin {
private val status: String? = null
private val requests: List<Request>? = null
private val message: String? = null
@SerializedName("username")
private val userName: String? = null
fun toLoginResult(site: WikiSite, password: String): LoginResult {
var userMessage = message
if ("UI" == status) {
if (requests != null) {
for (req in requests) {
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) {
return OAuthResult(site, status, userName, password, message)
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) {
return ResetPasswordResult(site, status, userName, password, message)
}
}
}
} else if ("PASS" != status && "FAIL" != status) {
//TODO: String resource -- Looks like needed for others in this class too
userMessage = "An unknown error occurred."
}
return Result(site, status ?: "", userName, password, userMessage)
}
}
internal class Request {
private val id: String? = null
private val required: String? = null
private val provider: String? = null
private val account: String? = null
private val fields: Map<String, RequestField>? = null
fun id(): String? = id
}
internal class RequestField {
private val type: String? = null
private val label: String? = null
private val help: String? = null
}

View file

@ -1,73 +0,0 @@
package fr.free.nrw.commons.auth.login;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.wikipedia.dataclient.WikiSite;
import java.util.Collections;
import java.util.Set;
public class LoginResult {
@NonNull private final WikiSite site;
@NonNull private final String status;
@Nullable private final String userName;
@Nullable private final String password;
@Nullable private final String message;
private int userId;
@NonNull private Set<String> groups = Collections.emptySet();
public LoginResult(@NonNull WikiSite site, @NonNull String status, @Nullable String userName,
@Nullable String password, @Nullable String message) {
this.site = site;
this.status = status;
this.userName = userName;
this.password = password;
this.message = message;
}
@NonNull public WikiSite getSite() {
return site;
}
@NonNull public String getStatus() {
return status;
}
public boolean pass() {
return "PASS".equals(status);
}
public boolean fail() {
return "FAIL".equals(status);
}
@Nullable public String getUserName() {
return userName;
}
@Nullable public String getPassword() {
return password;
}
@Nullable public String getMessage() {
return message;
}
public void setUserId(int id) {
this.userId = id;
}
public int getUserId() {
return userId;
}
public void setGroups(@NonNull Set<String> groups) {
this.groups = groups;
}
@NonNull public Set<String> getGroups() {
return groups;
}
}

View file

@ -0,0 +1,39 @@
package fr.free.nrw.commons.auth.login
import org.wikipedia.dataclient.WikiSite
sealed class LoginResult(
val site: WikiSite,
val status: String,
val userName: String?,
val password: String?,
val message: String?
) {
var userId = 0
var groups = emptySet<String>()
val pass: Boolean get() = "PASS" == status
class Result(
site: WikiSite,
status: String,
userName: String?,
password: String?,
message: String?
): LoginResult(site, status, userName, password, message)
class OAuthResult(
site: WikiSite,
status: String,
userName: String?,
password: String?,
message: String?
) : LoginResult(site, status, userName, password, message)
class ResetPasswordResult(
site: WikiSite,
status: String,
userName: String?,
password: String?,
message: String?
) : LoginResult(site, status, userName, password, message)
}

View file

@ -0,0 +1,78 @@
package fr.free.nrw.commons.auth.login
import android.net.Uri
import com.google.gson.GsonBuilder
import com.google.gson.stream.MalformedJsonException
import fr.free.nrw.commons.MockWebServerTest
import io.reactivex.observers.TestObserver
import org.junit.Before
import org.junit.Test
import org.wikipedia.dataclient.WikiSite
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import org.wikipedia.json.NamespaceTypeAdapter
import org.wikipedia.json.PostProcessingTypeAdapter
import org.wikipedia.json.UriTypeAdapter
import org.wikipedia.json.WikiSiteTypeAdapter
import org.wikipedia.page.Namespace
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
class UserExtendedInfoClientTest : MockWebServerTest() {
private var apiService: LoginInterface? = null
private val observer = TestObserver<MwQueryResponse>()
private val gson = GsonBuilder()
.registerTypeHierarchyAdapter(Uri::class.java, UriTypeAdapter().nullSafe())
.registerTypeHierarchyAdapter(Namespace::class.java, NamespaceTypeAdapter().nullSafe())
.registerTypeAdapter(WikiSite::class.java, WikiSiteTypeAdapter().nullSafe())
.registerTypeAdapterFactory(PostProcessingTypeAdapter())
.create()
@Before
@Throws(Throwable::class)
override fun setUp() {
super.setUp()
apiService = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(server().url)
.build()
.create(LoginInterface::class.java)
}
@Test
@Throws(Throwable::class)
fun testRequestSuccess() {
enqueueFromFile("user_extended_info.json")
apiService!!.getUserInfo("USER").subscribe(observer)
observer
.assertComplete()
.assertNoErrors()
.assertValue { result: MwQueryResponse ->
result.query()!!
.userInfo()!!.id() == 24531888 && result.query()!!.getUserResponse("USER")!!
.name() == "USER"
}
}
@Test
fun testRequestResponse404() {
enqueue404()
apiService!!.getUserInfo("USER").subscribe(observer)
observer.assertError(Exception::class.java)
}
@Test
fun testRequestResponseMalformed() {
enqueueMalformed()
apiService!!.getUserInfo("USER").subscribe(observer)
observer.assertError(MalformedJsonException::class.java)
}
}

View file

@ -1,84 +0,0 @@
package fr.free.nrw.commons.login;
import android.net.Uri;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.MalformedJsonException;
import fr.free.nrw.commons.MockWebServerTest;
import fr.free.nrw.commons.auth.login.LoginInterface;
import io.reactivex.observers.TestObserver;
import org.junit.Before;
import org.junit.Test;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.json.NamespaceTypeAdapter;
import org.wikipedia.json.PostProcessingTypeAdapter;
import org.wikipedia.json.UriTypeAdapter;
import org.wikipedia.json.WikiSiteTypeAdapter;
import org.wikipedia.page.Namespace;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
public class UserExtendedInfoClientTest extends MockWebServerTest {
private LoginInterface apiService;
@Override
@Before
public void setUp() throws Throwable {
super.setUp();
apiService = new Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(getGson()))
.baseUrl(server().getUrl())
.build()
.create(LoginInterface.class);
}
@Test
public void testRequestSuccess() throws Throwable {
enqueueFromFile("user_extended_info.json");
TestObserver<MwQueryResponse> observer = new TestObserver<>();
apiService.getUserInfo("USER").subscribe(observer);
observer
.assertComplete()
.assertNoErrors()
.assertValue(
result -> result.query().userInfo().id() == 24531888
&& result.query().getUserResponse("USER").name().equals("USER")
);
}
@Test
public void testRequestResponse404() {
enqueue404();
TestObserver<MwQueryResponse> observer = new TestObserver<>();
apiService.getUserInfo("USER").subscribe(observer);
observer.assertError(Exception.class);
}
@Test
public void testRequestResponseMalformed() {
enqueueMalformed();
TestObserver<MwQueryResponse> observer = new TestObserver<>();
apiService.getUserInfo("USER").subscribe(observer);
observer.assertError(MalformedJsonException.class);
}
private Gson getGson() {
return new GsonBuilder()
.registerTypeHierarchyAdapter(Uri.class, new UriTypeAdapter().nullSafe())
.registerTypeHierarchyAdapter(Namespace.class, new NamespaceTypeAdapter().nullSafe())
.registerTypeAdapter(WikiSite.class, new WikiSiteTypeAdapter().nullSafe())
.registerTypeAdapterFactory(new PostProcessingTypeAdapter())
.create();
}
}